Создание AI-агента на LangChain и OpenRouter

- -
- 100%
- +
Для упрощения мы можем использовать `AgentExecutor` с системой промптов, которая заставляет агента думать о памяти.
Однако более эффективный подход для "сценария чата" – использование `ConversationalAgent` (или `ToolCallingAgent`) с настроенной памятью.
Мы будем использовать `initialize_agent` из LangChain.
Важно: Агенту нужно передать инструменты, LLM, память и промпт.
Однако стандартная память `ConversationBufferMemory` в `initialize_agent` работает только как история сообщений. Чтобы встроить векторную базу, мы должны немного изменить подход.
Вместо того чтобы заставлять агента постоянно вызывать инструменты поиска, мы можем использовать подход `RetrievalQA` внутри логики агента, или сделать так, чтобы агент сам вызывал инструменты.
В нашем случае мы сделаем так: агент видит инструменты `SaveMemoryTool` и `LoadMemoryTool`. В промпте мы четко пропишем, когда их использовать.
Но для чистоты эксперимента и чтобы избежать бесконечных циклов вызова инструментов, лучше использовать `create_react_agent` (или `create_tool_calling_agent`), где инструменты памяти будут доступны, но агент будет использовать их разумно.
Промпт агента
Ключевой элемент – промпт. Он должен содержать инструкции:
* Ты – полезный ассистент с долгосрочной памятью.
* Ты можешь сохранять факты о пользователе, используя инструмент `save_memory`.
* Если пользователь спрашивает о том, что могло быть сказано ранее, используй `retrieve_memory`.
* История диалога доступна в переменной {chat_history}.
* {agent_scratchpad} – место для мыслей и инструментов.
Создадим промпт:
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain.agents import AgentExecutor, create_tool_calling_agent
# Базовый системный промпт
system_prompt = """
Вы – полезный ИИ-ассистент с расширенной долгосрочной памятью.
Ваши возможности:
1. Вы должны поддерживать непрерывный диалог.
2. Если пользователь делится личной информацией (имя, предпочтения, факты о жизни), вы ОБЯЗАНЫ использовать инструмент `save_memory` для сохранения этой информации.
3. Если пользователь задает вопрос, который требует знания фактов из прошлого (которые могли быть сказаны давно или в другом контексте), вы ОБЯЗАНЫ сначала использовать `retrieve_memory` для поиска релевантной информации.
4. Не изобретайте факты, опирайтесь только на историю диалога и результаты из памяти.
5. Всегда отвечай на русском языке.
"""
# Шаблон для агента (Tool Calling Agent)
prompt = ChatPromptTemplate.from_messages([
("system", system_prompt),
MessagesPlaceholder("chat_history"),
("user", "{input}"),
MessagesPlaceholder("agent_scratchpad"),
])
Инициализация агента
Теперь собираем всё вместе.
from langchain.agents import AgentExecutor
from langchain_openai import ChatOpenAI # Используем ChatOpenAI для поддержки функций/инструментов
# Используем ChatOpenAI для агента, так как он лучше работает с инструментами
# Но можно оставить и OpenAI, если переделать под существующие агенты.
# Для надежности используем ChatOpenAI через OpenRouter
llm_chat = ChatOpenAI(
base_url=BASE_URL,
api_key=OPENROUTER_API_KEY,
model="openai/gpt-3.5-turbo", # Модель с поддержкой function calling
temperature=0.3
)
# Наши инструменты
tools = [SaveMemoryTool(), LoadMemoryTool()]
# Создаем агента
agent = create_tool_calling_agent(llm_chat, tools, prompt)
# Исполнитель агента
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True, handle_parsing_errors=True)
# Инициализация памяти (для хранения истории чата)
chat_memory = ConversationBufferMemory(memory_key="chat_history", return_messages=True)
# Тестовый цикл взаимодействия
def run_chat():
print("Агент запущен. Введите 'exit' для выхода.")
while True:
user_input = input("Вы: ")
if user_input.lower() == 'exit':
break
# Получаем историю из памяти
history = chat_memory.load_memory_variables({})
chat_history = history.get("chat_history", [])
# Запуск агента
try:
response = agent_executor.invoke({
"input": user_input,
"chat_history": chat_history
})
answer = response['output']
print(f"Агент: {answer}")
# Сохраняем оба сообщения в память (историю диалога)
chat_memory.save_context({"input": user_input}, {"output": answer})
except Exception as e:
print(f"Ошибка: {e}")
if __name__ == "__main__":
run_chat()
Детальное объяснение работы цикла:
1. Пользователь вводит сообщение.
2. Мы загружаем историю диалога из `ConversationBufferMemory`.
3. Передаем в `agent_executor`: ввод, историю и доступные инструменты.
4. Агент анализирует запрос. Если он решает, что нужно сохранить информацию, он вызывает `SaveMemoryTool`. Мы видим это в `verbose` режиме. Этот вызов сохраняет данные в Chroma.
5. Если он решает, что нужно вспомнить прошлое, он вызывает `LoadMemoryTool`. Данные из Chroma возвращаются в контекст.
6. Агент генерирует финальный ответ на основе текущего ввода, истории и полученных из памяти фактов.
7. Ответ выводится пользователю и сохраняется в `ConversationBufferMemory`.
Продвинутое использование: Инструмент с реверсивной записью
Вышеприведенный код работает, но у него есть недостаток: агент должен явно вызывать инструменты `save_memory` и `retrieve_memory` через шаги мышления (Thought -> Action -> Observation). Это замедляет общение. Мы можем автоматизировать этот процесс, используя концепцию «Трансформеров памяти» (Memory Transformers) или «Скрытых действий» (Hidden Actions).
В LangChain есть возможность создать инструмент, который вызывается автоматически как часть промпта (через скрытый мыслительный процесс), но для простоты и прозрачности мы оставим явный вызов.
Однако мы можем улучшить инструмент `SaveMemoryTool`. Вместо простого сохранения строки, он может принимать JSON-структуру, чтобы обогатить метаданные.
Улучшенный инструмент сохранения:
class EnhancedSaveMemoryTool(BaseTool):
name = "save_memory_enhanced"
description = "Сохраняет информацию в долговременную память. Используй этот инструмент для сохранения фактов, имен, мест, предпочтений пользователя. Сохраняй только суть, убирая лишние слова."
args_schema: Type[BaseModel] = MemoryInput
def _run(self, content: str, category: str = "general") -> str:
# Здесь можно добавить векторизацию и сохранение
try:
# Допустим, мы хотим сохранять исходный контекст
vectorstore.add_texts(
texts=[f"Факт: {content}"],
metadatas=[{"source": "long_term_memory", "category": category}]
)
return "Факт сохранен."
except Exception as e:
return f"Ошибка сохранения: {e}"
Решение проблем и отладка
При работе с агентами памяти часто возникают следующие проблемы:
1. **Забывание контекста инструмента**: Агент вызывает инструмент, получает ответ (например, "Факт сохранен"), но забывает об этом в генерации финального ответа.
* *Решение*: В `agent_scratchpad` (истории мыслей) сохраняются все шаги. Модель видит, что инструмент был вызван, и обычно учитывает это. Если нет – можно добавить инструкцию в промпт: "После выполнения инструмента сделай вывод на основе Observation".
Конец ознакомительного фрагмента.
Текст предоставлен ООО «Литрес».
Прочитайте эту книгу целиком, купив полную легальную версию на Литрес.
Безопасно оплатить книгу можно банковской картой Visa, MasterCard, Maestro, со счета мобильного телефона, с платежного терминала, в салоне МТС или Связной, через PayPal, WebMoney, Яндекс.Деньги, QIWI Кошелек, бонусными картами или другим удобным Вам способом.



