LangGraph. Делаем State для LLM
Часто многие LLM требуют историю сообщений, чтобы лучше понимать контекст.
Так как передача истории сообщений довольно частая ситуация, то рассмотрим на примере:
- Как использовать State для определения схемы графа
- Как использовать reducers для управления обработкой обновлений состояния.
pip install -U langgraph langchain
Пример простого графа
Состоянием (State) в LangGraph может быть TypedDict, Pydantic model или dataclass. Ниже мы будем использовать TypedDict.
Определим State
from langchain_core.messages import AnyMessage
from typing_extensions import TypedDict
class State(TypedDict):
messages: list[AnyMessage]
extra_field: intВ состоянии будем хранить историю сообщений.
Определим структуру графа
Давайте построим пример графа с одним узлом (node). Наш node - это просто функция Python, которая считывает состояние нашего графа и вносит в него изменения. Первым аргументом этой функции всегда будет состояние:
from langchain_core.messages import AIMessage
def node(state: State):
messages = state["messages"]
new_message = AIMessage("Hello!")
return {"messages": messages + [new_message], "extra_field": 10}Этот узел (node) просто добавляет сообщение в наш список сообщений и заполняет дополнительное поле. Как видим, мы храним всю историю сообщений и дополняем новым сообщением историю.
Давайте далее определим простой граф, содержащий этот узел. Мы используем StateGraph для определения графа, который работает с этим состоянием. Затем мы используем add_node для заполнения нашего графа.
from langgraph.graph import StateGraph
graph_builder = StateGraph(State)
graph_builder.add_node(node)
graph_builder.set_entry_point("node")
graph = graph_builder.compile()LangGraph предоставляет встроенные утилиты для визуализации вашего графа. Давайте рассмотрим наш граф.
from IPython.display import Image, display display(Image(graph.get_graph().draw_mermaid_png()))
Использование графа
from langchain_core.messages import HumanMessage
result = graph.invoke({"messages": [HumanMessage("Hi")]})
print(result){'messages': [
HumanMessage(content='Hi', additional_kwargs={}, response_metadata={}),
AIMessage(content='Hello!', additional_kwargs={}, response_metadata={})
], 'extra_field': 10}Для удобства мы часто проверяем содержимое объектов сообщений с помощью pretty-print:
for message in result["messages"]:
message.pretty_print()========================== Human Message ============================ Hi =========================== Ai Message ============================== Hello!
Обновление состояния графа с помощью редьюсеров (reducers)
Каждый ключ в состоянии может иметь свою собственную независимую функцию-reducer, которая управляет тем, как применяются обновления с узлов. Если функция-reducer явно не указана, то предполагается, что все обновления ключа должны переопределять ее.
Для схем состояний TypedDict мы можем определить reducer, снабдив соответствующее поле состояния аннотацией с помощью функции reducer.
В предыдущем примере наш узел обновил ключ "сообщения" в состоянии, добавив к нему сообщение. Ниже мы добавим к этому ключу reducer, чтобы обновления добавлялись автоматически:
from typing_extensions import Annotated
def add(left: list[AnyMessage], right: list[AnyMessage]):
return left + right
class State(TypedDict):
messages: Annotated[list[AnyMessage], add]
extra_field: intТеперь наш узел можно упростить:
def node(state: State):
new_message = AIMessage("Hello!")
return {"messages": [new_message], "extra_field": 10}Теперь наша объявленная функция add будет сама делать конкатенацию списков.
from langgraph.graph import START
graph_builder = StateGraph(State)
graph = graph_builder.add_node(node).add_edge(START, "node").compile()
result = graph.invoke({"messages": [HumanMessage("Hi")]})
for message in result["messages"]:
message.pretty_print()========================== Human Message ============================ Hi =========================== Ai Message ============================== Hello!
MessagesState
LangGraph имеет встроенный reducer add_messages, который обрабатывает эти соображения:
from langgraph.graph.message import add_messages
class State(TypedDict):
messages: Annotated[list[AnyMessage], add_messages]
extra_field: int
def node(state: State):
new_message = AIMessage("Hello!")
return {"messages": [new_message], "extra_field": 10}
graph_builder = StateGraph(State)
graph = graph_builder.add_node(node).set_entry_point("node").compile()input_message = {"role": "user", "content": "Hi"}
result = graph.invoke({"messages": [input_message]})
for message in result["messages"]:
message.pretty_print()========================== Human Message ============================ Hi =========================== Ai Message ============================== Hello!
Это универсальное представление состояния для приложений, использующих модели чата. Для удобства в LangGraph встроен MessagesState, так что мы можем написать:
from langgraph.graph import MessagesState
class State(MessagesState):
extra_field: intТаким образом, данный MessageState сам делает структуру:
class State(TypedDict):
messages: Annotated[list[AnyMessage], add_messages]А мы лишь дальше от него наследуемся и можем переиспользовать функционал. Чтобы вручную самим не объявлять.
✨Основной Telegram-канал со всеми обновлениями✨
- Введение в Haystack
- Агенты искусственного интеллекта: что это такое, зачем нужны и почему за ними будущее
- Введение в библиотеку LangGraph