2025, Dec 16 15:03

Память LangGraph с Gemini: почему «теряется» имя и как это исправить

Почему агент на Gemini в LangGraph с MemorySaver «забывает» имя: как работают чекпоинты и thread_id, какие формулировки помогают и какие модели выбрать

Память LangGraph, которая «забывает»? Почему Gemini по‑разному отвечает на похожие запросы

Если вы создаёте простого агента в LangGraph и подключаете MemorySaver, логично ожидать, что предыдущие реплики сохранятся. Однако с gemini-2.5-flash агент может на первом ходу поприветствовать Боба, а на втором, на вопрос «Как меня зовут?», ответить: «Я не знаю вашего имени». С виду это похоже на сбой памяти, но на самом деле это не так.

Минимальный агент, который будто бы забывает

Ниже пример двухходового диалога. На первом ходу модели сообщают имя. На втором его запрашивают. Для сохранения состояния между ходами используются MemorySaver и фиксированный thread_id.

import os
from langchain_tavily import TavilySearch
from langchain.chat_models import init_chat_model
from langgraph.checkpoint.memory import MemorySaver
from langgraph.prebuilt import create_react_agent
from langchain_core.messages import HumanMessage

os.environ.get('TAVILY_API_KEY')
finder = TavilySearch(max_result=2)
skillset = [finder]

os.environ['HTTP_PROXY'] = 'http://127.0.0.1:7890'
os.environ['HTTPS_PROXY'] = 'http://127.0.0.1:7890'
os.environ.get('GOOGLE_API_KEY')
llm_client = init_chat_model('gemini-2.5-flash', model_provider='google-genai')

checkpoint_store = MemorySaver()
agent_runner = create_react_agent(llm_client, skillset, checkpointer=checkpoint_store)
run_cfg = {'configurable': {'thread_id': 'agent003'}}

# Первый ход
for event in agent_runner.stream({'messages': [HumanMessage('Hi! I am Bob!')]}, run_cfg, stream_mode='values'):
    event['messages'][-1].pretty_print()

# Собрать историю, добавить новый ввод пользователя и запустить второй ход
snapshot = checkpoint_store.get(run_cfg)
prior_msgs = snapshot['channel_values']['messages']
followup_msg = HumanMessage("What's my name?")
payload = {'messages': prior_msgs + [followup_msg]}

for event in agent_runner.stream(payload, run_cfg, stream_mode='values'):
    event['messages'][-1].pretty_print()

На практике многие получают ответ вроде: «Я не знаю вашего имени. Если хотите, можете мне его сказать!». Это удивляет, ведь в приветствии имя уже прозвучало.

Что происходит на самом деле

Многократные запуски с тем же окружением показывают разные результаты в зависимости от формулировки второго вопроса. С тем же gemini-2.5-flash наблюдались такие варианты реакции:

«Как меня зовут?» → «У меня нет памяти о прошлых разговорах.»

«Ты знаешь, как меня зовут?» → «Да, вас зовут Bob.»

«Ты помнишь моё имя?» → «Да, помню, Bob!»

Состояние действительно сохраняется; различие в том, как модель трактует запрос. У Gemini, как и у большинства LLM, нет внутренней структурированной памяти. На «Как меня зовут?» модель может реагировать как на задачу чистого воспоминания знаний и отвечать, что не знает. А вот «Ты знаешь, как меня зовут?» или «Ты помнишь моё имя?» побуждают её посмотреть на ближайшую историю чата, переданную в том же запросе, и извлечь «Bob».

Значит, дело не в механизме памяти LangGraph. Это особенность поведения самой модели Gemini. В официальном учебнике агент демонстрируется с anthropic:claude-3-5-sonnet-latest, и при той же схеме он ведёт себя иначе, чем модели Gemini.

Практическое решение: опирайтесь на чекпоинт и подкорректируйте формулировку запроса

Если вы сохраняете один и тот же thread_id, MemorySaver в LangGraph подставит модельной службе всю переписку при следующем вызове. Вам не нужно вручную получать и склеивать историю для второго хода. В случае с Gemini переформулировка продолжения как «Ты знаешь, как меня зовут?» или «Ты помнишь моё имя?» заставляет модель обратиться к переданной истории диалога и ответить корректно.

import os
from langchain_tavily import TavilySearch
from langchain.chat_models import init_chat_model
from langgraph.checkpoint.memory import MemorySaver
from langgraph.prebuilt import create_react_agent
from langchain_core.messages import HumanMessage

os.environ.get('TAVILY_API_KEY')
finder = TavilySearch(max_result=2)
skillset = [finder]

os.environ['HTTP_PROXY'] = 'http://127.0.0.1:7890'
os.environ['HTTPS_PROXY'] = 'http://127.0.0.1:7890'
os.environ.get('GOOGLE_API_KEY')
llm_client = init_chat_model('gemini-2.5-flash', model_provider='google-genai')

checkpoint_store = MemorySaver()
agent_runner = create_react_agent(llm_client, skillset, checkpointer=checkpoint_store)
run_cfg = {'configurable': {'thread_id': 'agent003'}}

# Ход 1
for event in agent_runner.stream({'messages': [HumanMessage('Hi! I am Bob!')]}, run_cfg, stream_mode='values'):
    event['messages'][-1].pretty_print()

# Ход 2: без ручной склейки истории; тот же thread_id
for event in agent_runner.stream({'messages': [HumanMessage('Do you know my name?')]}, run_cfg, stream_mode='values'):
    event['messages'][-1].pretty_print()

Либо можно выбрать другую модель — и то же самое сообщение будет обработано иначе. Например, с llama3.2:latest через Ollama на «what’s my name?» приходит ожидаемое «Your name is Bob!» при точно такой же схеме агента.

import os
from langchain_tavily import TavilySearch
from langgraph.checkpoint.memory import MemorySaver
from langgraph.prebuilt import create_react_agent
from langchain_core.messages import HumanMessage
from langchain_ollama import ChatOllama
from dotenv import load_dotenv

load_dotenv()
os.environ.get('TAVILY_API_KEY')
finder = TavilySearch(max_result=2)
skillset = [finder]

llm_local = ChatOllama(model="llama3.2:latest", temperature=0)

checkpoint_store = MemorySaver()
agent_runner = create_react_agent(llm_local, skillset, checkpointer=checkpoint_store)
run_cfg = {"configurable": {"thread_id": "agent003"}}

for event in agent_runner.stream({"messages": [HumanMessage("Hi! I am Bob!")]}, run_cfg, stream_mode="values"):
    event["messages"][-1].pretty_print()

for event in agent_runner.stream({"messages": [HumanMessage("what's my name?")]}, run_cfg, stream_mode="values"):
    event["messages"][-1].pretty_print()

Почему это важно

Память агента в LangGraph работает через чекпоинты: состояние сохраняется и воспроизводится модели на следующем ходу. То, воспользуется ли модель этим контекстом так, как вы ожидаете, зависит от формулировки запроса и поведения самой модели. Gemini может по‑разному отвечать на смыслово близкие вопросы, даже когда в запросе присутствует одна и та же история диалога. Модель из официального туториала и ваша модель на рантайме могут вести себя по‑разному при идентичной оркестрации.

Выводы

Держите thread_id стабильным — тогда MemorySaver восстановит состояние при каждом вызове. Импортируйте create_react_agent из langgraph.prebuilt и подключите checkpointer. С gemini-2.5-flash для уточняющих вопросов предпочтительнее «Ты знаешь, как меня зовут?» или «Ты помнишь моё имя?», если хотите, чтобы модель обратилась к текущей истории чата. Если же вам важно, чтобы «Как меня зовут?» работал как ожидается, рассмотрите переход на модель, которая учитывает такую формулировку вместе с предоставленной историей, например llama3.2:latest в показанной конфигурации.