import os import asyncio from typing import List from pydantic import SecretStr from langchain_openai import ChatOpenAI from langchain_core.messages import HumanMessage, AIMessage, ToolMessage from langchain.tools import tool from deepagents import create_deep_agent from deepagents.backends import CompositeBackend, LocalShellBackend, FilesystemBackend # ---------------------------------------------------------------------- # Configuration of the LLM (OpenRouter, as required by the course) # ---------------------------------------------------------------------- llm = ChatOpenAI( model="openai/gpt-oss-20b:free", base_url="https://openrouter.ai/api/v1", api_key=SecretStr(os.getenv("OPENAI_API_KEY")), temperature=0.7, ) # ---------------------------------------------------------------------- # Backend for the agents - allows file operations and shell commands # ---------------------------------------------------------------------- backend = CompositeBackend( [ LocalShellBackend(workspace_dir="./workspace"), FilesystemBackend(), ] ) # ---------------------------------------------------------------------- # Sub-agent tool: get_price # ---------------------------------------------------------------------- @tool def get_price(product: str, city: str) -> str: """ Получить примерную цену продукта в указанном городе. Возвращает markdown-таблицу с колонками: Продукт, Цена (руб.), Магазин. """ # Создаём суб-агента, который генерирует цену. sub_agent = create_deep_agent( model=llm, tools=[], backend=backend, system_prompt=( "Ты суб-агент, который генерирует реалистичную цену продукта " "в заданном городе. Выдай результат в виде markdown-таблицы " "с колонками: Продукт, Цена (руб.), Магазин." ), ) # Формируем запрос к суб-агенту query = f"Сгенерируй цену для продукта '{product}' в городе {city}." # Асинхронный вызов суб-агента async def _invoke(): result = await sub_agent.ainvoke( {"messages": [HumanMessage(content=query)]}, {"configurable": {"thread_id": f"price-{product}-{city}"}}, ) # Последнее сообщение содержит таблицу return result["messages"][-1].content # Запускаем цикл событий, если уже внутри async контекста try: loop = asyncio.get_running_loop() table = loop.create_task(_invoke()) # Если мы уже в async функции, вернём задачу, иначе дождёмся результата if isinstance(table, asyncio.Task): return asyncio.run(table) except RuntimeError: # Нет запущенного цикла - создаём новый return asyncio.run(_invoke()) # ---------------------------------------------------------------------- # Main shopping-assistant agent # ---------------------------------------------------------------------- assistant_agent = create_deep_agent( model=llm, tools=[get_price], backend=backend, system_prompt="Ты помощник по планированию покупок.", ) # ---------------------------------------------------------------------- # Helper to format the chain of messages for display # ---------------------------------------------------------------------- def format_message(msg) -> str: if isinstance(msg, HumanMessage): return f"Human: {msg.content}" if isinstance(msg, AIMessage): return f"AI: {msg.content}" if isinstance(msg, ToolMessage): # tool call result return f"ToolResult: {msg.content}" # Fallback for generic messages return str(msg) async def main(): user_query = "Помоги составить список покупок: молоко, хлеб, яблоки. Я нахожусь в Казани." result = await assistant_agent.ainvoke( {"messages": [HumanMessage(content=user_query)]}, {"configurable": {"thread_id": "shopping-session-1"}}, ) # Выводим всю цепочку сообщений print("\n--- Диалог с агентом ---\n") for m in result["messages"]: print(format_message(m)) print("---") if __name__ == "__main__": asyncio.run(main())