Сегодня появляется все больше и больше приложений на основе больших языковых моделей — условным чат-ботом в Telegram уже никого не удивить. В рамках обучения в магистратуре AI Talent Hub мне не раз приходилось разрабатывать такие приложения с использованием ChatGPT или GigaChat. В этой статье я расскажу о полезном инструменте для работы с LLM - мы рассмотрим главные возможности фреймворка LangChain, а также методы мониторинга и проверки качества существующего приложения с ИИ.

Пара слов об LLM

Большие языковые модели (LLM) оперируют огромными объемами данных, что позволяет им эффективно решать различные задачи — от генерации и суммирования текста до анализа и понимания контекста.Однако работа с такими моделями сопряжена с рядом сложностей: высокая вычислительная нагрузка, сложная интеграция и необходимость обеспечения стабильной работы в продакшене. Для упрощения этого процесса существуют специализированные инструменты, которые облегчают взаимодействие с LLM. Одним из таких инструментов является LangChain, предоставляющий удобные возможности для подключения моделей, создания пайплайнов, мониторинга и проверки качества приложений на основе больших языковых моделей. Использование LangChain позволяет значительно сократить затраты времени и ресурсов на разработку, делая работу с LLM более доступной и эффективной.

LangChain

LangChain — это фреймворк для разработки приложений с LLM. Он облегчает каждый этап - от разработки до деплоя, от тестирования до мониторинга. Фреймворк состоит из нескольких open-source библиотек:

  • langchain-core — пакет для работы с базовыми абстракциями.

  • Пакеты с интеграциями, например, langchain-openai, langchain-anthropic.

  • langchain — основной пакет с агентами, цепочками и другой функциональностью.

  • langchain-community — интеграции с различными сервисами (например, GigaChat от Сбера).

  • langgraph — пакет для внедрения компонентов LangChain в продакшен.

Рассмотрим основные концепции, которые предоставляет langchain для лёгкого построения модульной системы.

Chains

Chains  —  последовательности шагов, компоненты, которые возможно переиспользовать, связанные вместе. ��акие элементы позволяют создать пайплайн, который объединяет разные функциональности (подготовка данных, внешние API вызовы, преобразование данных, вызов LLM). Пример простой цепочки, которая создает шаблонизированный запрос и получает ответ от модели.

from langchain_openai import ChatOpenAI
from langchain import PromptTemplate
from langchain.chains import LLMChain

# Инициализация модели LLM
llm = ChatOpenAI(model_name='gpt-3.5-turbo')

# Шаблон промпта для помощника по учёбе
study_helper_template = """
I want you to act as a study helper for a student.
Provide a list of study tips and recommend resources that would be beneficial for {study_topic}.
Ensure that the tips are practical and the resources are reliable and relevant.
What are some effective study strategies and resources for {study_topic}?
"""

# Создание объекта PromptTemplate с необходимыми переменными
prompt_template = PromptTemplate(
input_variables=["study_topic"],
template=study_helper_template,
)

# Описание учебной задачи или предмета
study_description = "preparing for a final exam in advanced calculus"

# Форматирование промпта с использованием описания
prompt_template.format(study_topic=study_description)

chain = LLMChain(llm=llm, prompt=prompt_template)

# Запуск цепочки и вывод результата
response = chain.run(study_description)
print(response)

Prompts

Prompts  —  это сами текстовые запросы, которые отправляются в LLM для генерации ответа. LangChain предоставляет инструменты для создания и работы с шаблонами промптов (PromptTemplate). Шаблоны позволяют динамически подставлять данные в промпт.  В инструментарии есть возможность разделять промпты по ролям - системное сообщение, пользовательское сообщение, AI сообщение и т.д. Также LangChain предоставляет инструменты по работе с few-shot examples при формировании запросов к модели.

Идея few-shot examples состоит в том, чтобы, прежде чем отправлять пользовательский запрос, предоставлять несколько примеров ответов. Пример кода, как это может выглядеть, приведен ниже.

from langchain_core.prompts import (
ChatPromptTemplate,
FewShotChatMessagePromptTemplate,
)

# Описание примеров вопросов и ответов
examples = [
{"input": "2+2", "output": "4"},
{"input": "2+3", "output": "5"},
]

# Форматирование примеров с помощью шаблонов промптов
example_prompt = ChatPromptTemplate.from_messages(
[
("human", "{input}"),
("ai", "{output}"),
]
)

few_shot_prompt = FewShotChatMessagePromptTemplate(
example_prompt=example_prompt,
examples=examples,
)

# Создание итогового промпта с системным промптом и несколькими примерами ответов
final_prompt = ChatPromptTemplate.from_messages(
[
("system", "You are a wondrous wizard of math."),
few_shot_prompt,
("human", "{input}"),
]
)

Memory

Memory  — в LangChain отвечает за сохранение состояния и контекста между вызовами LLM. Это особенно полезно для создания интерактивных приложений, таких как чат-боты, где важно помнить предыдущие взаимодействия с пользователем. 

Существуют различные типы памяти:

ConversationBufferMemory - такой тип памяти позволяет сохранять сообщения как есть и извлекать их, если требуется.

ConversationBufferWindowMemory - так же, как и ConversationBufferMemory, сохраняет сообщения, однако в этом случае он хранит последние k взаимодействий. С помощью такого скользящего окна можно рассматривать только самые последние сообщения, таким образом, чтобы буфер не стал слишком большим.

ConversationSummaryMemory - такой тип памяти позволяет создавать суммаризацию переписки. В процессе общения будет обрабатываться переписка, суммаризироваться и итог сохраняться в памяти. Это может быть полезно в долгих переписках, где нужно запоминать детали, но хранить всю переписку займет слишком много токенов.

ConversationSummaryBufferMemory - тип памяти, который совмещает две идеи: он хранит суммаризацию старых сообщений, а последние сообщения хранит классическим способом в буфере. Вместо количества взаимодействий, как в ConversationBufferWindowMemory, этот тип памяти принимает решения о необходимости суммаризации сообщений по ограничению на количество последних токенов, которые стоит помнить.

Пример использования ConversationBufferWindowMemory в LLM-цепочке.

from langchain_openai import OpenAI
from langchain.chains import ConversationChain
conversation_with_summary = ConversationChain(
llm=OpenAI(temperature=0),
# Устанавливаем низкое значение 2, чтобы сохранить последние 2 взаимодействия
memory=ConversationBufferWindowMemory(k=2),
verbose=True
)
conversation_with_summary.predict(input="Hi, what's up?")

Embeddings

Embeddings  —  это числовые представления текстов, которые позволяют моделям определять смысловую близость между определенными текстовыми записями. Они широко используются в решении задач в области обработки естественного языка. LangChain поддерживает создание эмбеддингов, их хранение в векторных базах данных и поиск по ним. В LangChain есть удобные инструменты для создания и работы с векторными хранилищами. Эти инструменты позволяют эффективно работать с эмбэддингами. Также в LangChain доступны retrievers, которые выполняют поиск релевантных документов или информации в векторных хранилищах. На основе этих инструментов можно построить RAG-систему, где поиск по запросу пользователя модель будет делать по внутренним данным, к примеру. Пример создания такой системы:

from langchain_community.vectorstores import FAISS
from langchain_openai import OpenAIEmbeddings
from langchain_text_splitters import CharacterTextSplitter

# Загружаем документы и обрабатываем их
documents = loader.load()
text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
texts = text_splitter.split_documents(documents)

# Используем эмбеддинги на основе OpenAi
embeddings = OpenAIEmbeddings()

# Загружаем документы в векторное хранилище, текст из документов обрабатываем с помощью эмбеддингов
db = FAISS.from_documents(texts, embeddings)

# Создаем retriever
retriever = db.as_retriever()

# Ищем ответ на пользовательский запрос в наших документах
docs = retriever.invoke("what did he say about ketanji brown jackson")

Tool

Tool  —  это такие компоненты, через которые цепочки, LLM или агенты, о которых мы поговорим чуть позже, могут взаимодействовать с внешними источниками. Инструмент должен содержать: название, описание, JSON-схему входных данных, вызов функции и факт возврата результата вызова напрямую пользователю. Эти данные позволяют встроенному механизму принятия решений понять, когда какой инструмент стоит использовать. Понимание JSON-схемы входных данных нужно системе для правильного промптирования запросов. Чем легче входные данные инструмента, тем легче LLM сможет использовать данный инструмент. В самом langchain есть реализации многих полезных инструментов, которые необходимо только импортировать для работы, к примеру: обертки вокруг вызова поисковых движков, использование различных плагинов ChatGPT, интеграции с базами данных, Wikipedia, YouTube и множество других. Использовать готовые инструменты можно следующим образом:

from langchain_community.tools import WikipediaQueryRun
from langchain_community.utilities import WikipediaAPIWrapper

api_wrapper = WikipediaAPIWrapper(top_k_results=1, doc_content_chars_max=100)
tool = WikipediaQueryRun(api_wrapper=api_wrapper)

tool.run("langchain")

Для создания собственного инструмента необходимо воспользоваться декоратором @tool на желаемой функции.

Agents

Agents  —  основная идея агентов заключается в том, что языковая модель, зная доступные инструменты, сама принимает решение о последовательности и необходимости вызова для ответа на пользовательский запрос. Если в цепочках порядок вызовов был строго определен с самого начала, то в агентах это решение передается самому механизму.

Агенты в LangChain функционируют как промежуточный слой между пользователем и набором доступных инструментов. Когда пользователь отправляет запрос, агент анализирует его, определяет, какие инструменты необходимы для выполнения задачи, и организует последовательность их вызовов. Это позволяет создавать более гибкие и адаптивные приложения, которые могут обрабатывать сложные и многогранные запросы без необходимости заранее определять жесткую последовательность действий.

Основные шаги работы агента в LangChain:

  1. Анализ запроса: Агент принимает пользовательский ввод и анализирует его с помощью LLM для определения намерений и необходимых действий.

  2. Выбор инструментов: На основе анализа запроса агент выбирает соответствующие инструменты из доступного набора, которые помогут выполнить задачу.

  3. Организация последовательности действий: Агент определяет порядок вызовов выбранных инструментов, обеспечивая эффективное и логичное выполнение задачи.

  4. Выполнение и интеграция результатов: Агент выполняет вызовы к инструментам, собирает результаты и интегрирует их в окончательный ответ для пользователя.

Пример создания агента, который может выполнять поисковые запросы в Википедии и отвечать на вопросы пользователя, используя найденную информацию.

from langchain import OpenAI, AgentExecutor, Tool
from langchain.tools import WikipediaAPIWrapper

# Инициализация модели LLM
llm = OpenAI(model_name='gpt-4', temperature=0)

# Создание инструмента для поиска в Википедии
wikipedia = WikipediaAPIWrapper()

# Определение инструмента с необходимыми параметрами
tools = [
Tool(
name="Wikipedia",
func=wikipedia.run,
description="Use this to search for lastest information in Wikipedia."
)
]

# Создание агента с доступными инструментами
agent = AgentExecutor.from_llm_and_tools(llm, tools)

# Пример запроса пользователя
user_query = "tell about lastest news in AI"

# Запуск агента и получение ответа
response = agent.run(user_query)
print(response)

Также из коробки LangChain поддерживает мониторинг и логирование через LangSmith. Для этого нужно включить трейсинг:

os.environ["LANGCHAIN_TRACING_V2"] = "true"
os.environ["LANGCHAIN_PROJECT"] = "" # Наименование проекта для мониторинга и логгирования
os.environ["LANGCHAIN_ENDPOINT"] = "https://api.smith.langchain.com"
os.environ["LANGCHAIN_API_KEY"] = "" # API ключ от langSmith

API ключ можно получить непосредственно на странице в LangSmith. Этот ключ нужен для интеграции и мониторинга запросов к моделям.

Мониторинг и логирование с LangChain

Для полного покрытия мониторингом и логами запросов к моделям необходимо использовать все реализации из библиотеки LangChain. Это обеспечивает всесторонний контроль над взаимодействием с большими языковыми моделями и позволяет отслеживать все этапы обработки запросов.

Аннотирование функций с @traceable

Определенные функции, внутри которых происходит собственное обращение к базе данных, RAG или LLM, и которые реализованы не с помощью цепочек, можно обернуть аннотацией @traceable из пакета LangSmith. Это позволяет автоматически отслеживать выполнение этих функций и интегрировать их в систему мониторинга.

Подробный мониторинг выполнения задач

С помощью LangSmith можно увидеть подробное состояние каждого шага во время выполнения задачи:

  • Запрос пользователя: исходный запрос, поступивший от пользователя.

  • Найденные документы: документы, отобранные в системе RAG.

  • Сформированный промпт: текст запроса, подготовленный по заданному шаблону.

  • Ответ модели: ответ, сгенерированный моделью LLM.

Сбор и привязка пользовательских оценок

Дополнительно можно реализовать запрос оценки от пользователя, например, через UI форму, и привязать эту оценку к пайплайну поrun_id. Это позволяет собирать обратную связь и улучшать качество ответов модели на основе реальных оценок пользователей.

На скриншоте ниже видно обращение к векторной базе данных, а затем формирование запроса в ChatGPT. Справа отображается оценка ответа модели, предоставленная пользователем. 

Пример мониторинга работы пайплайна с использованием LangSmith
Пример мониторинга работы пайплайна с использованием LangSmith

Также в LangSmith есть возможность проводить проверку качества LLM модели на своих данных. Для этого необходимо предсоздать набор данных с запросом и ожидаемым результатом и запустить на нем свою модель с помощью evaluate из пакета langsmith.evaluation. Можно перезапускать проверку с разными видами моделей и смотреть результат проверки через UI.

Заключение

LangChain — действительно полезный инструмент для быстрого и удобного создания приложений на основе больших языковых моделей. Он упрощает интеграцию LLM, предоставляет базовые строительные блоки (chains, prompts, memory, agents и т.д.) и даёт готовые инструменты для мониторинга и оценки качества. Всё это ускоряет разработку и упрощает отладку, позволяя сосредоточиться на логике приложения, а не на низкоуровневой интеграции.

Лично мне LangChain понравился за гибкость: легко комбинировать инструменты, подключать внешние сервисы и строить сложные пайплайны. А встроенный мониторинг (через LangSmith) даёт возможность видеть полный трекинг вызовов, что особенно важно при работе с LLM. Если вы работали с LangChain, расскажите в комментариях, чем он вам полезен или какие сложности возникли. Буду рада услышать ваше мнение!


С подробной документацией фреймворка можно ознакомиться тут https://python.langchain.com/docs/introduction/

Материал подготовила магистрантка AI Talent Hub, Елизавета Воляница.