LangGraphMiddleExperience

Расскажите о случае, когда вы использовали LangGraph для pipeline, evaluation, optimization, deployment или debugging.

Использовал LangGraph для построения RAG-агента с автоматической проверкой релевантности: если retrieval возвращал слабые результаты, граф делал шаг web-поиска перед генерацией. Checkpointing позволял возобновлять прерванные сессии.

Задача: RAG-агент с адаптивным retrieval

Нужно было построить агента для ответов на вопросы по внутренней документации компании. Проблема простого RAG: документация неполная, часть вопросов требовала актуальных данных из интернета. Решение — граф с условным web-поиском.

Архитектура графа

from langgraph.graph import StateGraph, END
from langgraph.checkpoint.sqlite import SqliteSaver
from typing import TypedDict, Annotated
import operator

class AgentState(TypedDict):
    question: str
    documents: Annotated[list[str], operator.add]
    generation: str
    web_search_needed: bool
    iterations: int

Узлы графа

from langchain_chroma import Chroma
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain_community.tools import TavilySearchResults

vectorstore = Chroma(embedding_function=OpenAIEmbeddings(), persist_directory="./db")
retriever = vectorstore.as_retriever(search_kwargs={"k": 4})
web_search = TavilySearchResults(max_results=3, api_key="tvly-...")
llm = ChatOpenAI(model="gpt-4o", temperature=0)

def retrieve(state: AgentState) -> AgentState:
    docs = retriever.invoke(state["question"])
    return {"documents": [d.page_content for d in docs]}

def grade_documents(state: AgentState) -> str:
    """Проверяем релевантность через LLM."""
    grader_prompt = f"""Are these documents relevant to '{state['question']}'?
    Documents: {state['documents'][:2]}
    Answer yes or no only."""
    result = llm.invoke(grader_prompt).content.lower()
    if "no" in result or len(state["documents"]) == 0:
        return "web_search"
    return "generate"

def web_search_node(state: AgentState) -> AgentState:
    results = web_search.invoke(state["question"])
    web_docs = [r["content"] for r in results]
    return {"documents": web_docs, "web_search_needed": True}

def generate(state: AgentState) -> AgentState:
    context = "\n\n".join(state["documents"][:6])
    prompt = f"""Answer based on context:\n{context}\n\nQuestion: {state['question']}"""
    response = llm.invoke(prompt)
    return {"generation": response.content, "iterations": state.get("iterations", 0) + 1}

Сборка и компиляция

graph = StateGraph(AgentState)
graph.add_node("retrieve", retrieve)
graph.add_node("web_search", web_search_node)
graph.add_node("generate", generate)

graph.set_entry_point("retrieve")
graph.add_conditional_edges(
    "retrieve",
    grade_documents,
    {"web_search": "web_search", "generate": "generate"}
)
graph.add_edge("web_search", "generate")
graph.add_edge("generate", END)

with SqliteSaver.from_conn_string("checkpoints.db") as checkpointer:
    app = graph.compile(checkpointer=checkpointer)

Запуск и debugging

config = {"configurable": {"thread_id": "session-001"}, "recursion_limit": 5}

# Streaming для отладки промежуточных шагов
for event in app.stream({"question": "What is the deployment process?"}, config=config):
    for key, value in event.items():
        print(f"Node: {key}")
        if "generation" in value:
            print(f"Answer: {value['generation'][:200]}")

Оценка качества

После запуска добавили LangSmith evaluation: собирали вопросы из реального трафика, вручную размечали корректные ответы и запускали evaluate раз в неделю для выявления деградации.

Подводные камни

  • Grader на основе LLM добавляет 500–800ms к каждому запросу и стоит дополнительных токенов — для высоконагруженных систем замените на embedding-similarity score.
  • SqliteSaver не thread-safe при параллельных запросах — в production заменили на PostgreSQL через langgraph-checkpoint-postgres.
  • Web search через Tavily стоит денег за каждый запрос — без кэширования одинаковых вопросов расходы быстро растут.
  • Streaming через app.stream() несовместим с некоторыми ASGI-фреймворками без дополнительной обёртки для Server-Sent Events.
  • State schema нельзя изменить без миграции checkpoints — при добавлении нового поля все старые сессии теряют это поле.

What hurts your answer

  • Выдумывать опыт или говорить слишком общими фразами
  • Не объяснять свою личную роль в работе с LangGraph
  • Не показывать результат, метрики или извлечённые уроки

What they're listening for

  • Может подготовить честный пример использования LangGraph
  • Показывает свою роль, решения и результат
  • Умеет рефлексировать над trade-offs и уроками

Related topics