diff --git a/main.py b/main.py index bfb1ff1..220333f 100644 --- a/main.py +++ b/main.py @@ -1,27 +1,26 @@ import os import asyncio -from typing import List +from typing import Any, Dict, 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 configuration (OpenRouter) # ---------------------------------------------------------------------- llm = ChatOpenAI( model="openai/gpt-oss-20b:free", base_url="https://openrouter.ai/api/v1", - api_key=SecretStr(os.getenv("OPENAI_API_KEY")), + api_key=os.getenv("OPENAI_API_KEY"), temperature=0.7, ) # ---------------------------------------------------------------------- -# Backend for the agents - allows file operations and shell commands +# Backend for sub-agents (allows file operations and shell commands) # ---------------------------------------------------------------------- backend = CompositeBackend( [ @@ -31,84 +30,91 @@ backend = CompositeBackend( ) # ---------------------------------------------------------------------- -# Sub-agent tool: get_price +# Sub-agent that generates a realistic price table for a product +# ---------------------------------------------------------------------- +def create_price_subagent() -> Any: + """ + Returns a deep agent that, given a product and a city, produces a markdown + table with product, price and store. The prompt forces the model to fabricate + plausible data based on typical market prices. + """ + system_prompt = ( + "You are a price-generation sub-agent. Given a product name and a city, " + "return a markdown table with columns: Продукт, Цена (руб.), Магазин. " + "Fabricate realistic prices based on typical Russian market data. " + "Do not add any extra commentary, only the table." + ) + subagent = create_deep_agent( + model=llm, + tools=[], # no external tools needed for this simple sub-agent + backend=backend, + system_prompt=system_prompt, + ) + return subagent + +price_subagent = create_price_subagent() + +# ---------------------------------------------------------------------- +# Tool that calls the sub-agent # ---------------------------------------------------------------------- @tool def get_price(product: str, city: str) -> str: """ - Получить примерную цену продукта в указанном городе. - Возвращает markdown-таблицу с колонками: Продукт, Цена (руб.), Магазин. + Generate a realistic price for the given product in the specified city. + Returns a markdown table with columns: Продукт, Цена (руб.), Магазин. """ - # Создаём суб-агента, который генерирует цену. - 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)]}, + # Build the prompt for the sub-agent + prompt = f"Продукт: {product}\nГород: {city}" + # Invoke the sub-agent synchronously (deepagents also supports async, + # but a simple sync call keeps the example straightforward) + result = asyncio.run( + price_subagent.ainvoke( + {"messages": [HumanMessage(content=prompt)]}, {"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()) + ) + # The sub-agent returns a list of messages; the last one contains the table + final_message = result["messages"][-1] + if isinstance(final_message, AIMessage): + return final_message.content + elif isinstance(final_message, ToolMessage): + return final_message.content + else: + return str(final_message) # ---------------------------------------------------------------------- -# Main shopping-assistant agent +# Main shopping-list agent # ---------------------------------------------------------------------- -assistant_agent = create_deep_agent( +shopping_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}" +def format_message(msg: Any) -> str: + """Human-readable representation of a message or tool call.""" + if isinstance(msg, (HumanMessage, AIMessage)): + return msg.content if isinstance(msg, ToolMessage): - # tool call result - return f"ToolResult: {msg.content}" - # Fallback for generic messages + return f"{msg.name}({msg.args}) -> {msg.content}" + # Fallback for generic dict-like messages + if hasattr(msg, "tool_calls") and msg.tool_calls: + call = msg.tool_calls[0] + return f"{call['name']}({call['args']})" return str(msg) -async def main(): +async def main() -> None: user_query = "Помоги составить список покупок: молоко, хлеб, яблоки. Я нахожусь в Казани." - result = await assistant_agent.ainvoke( + result = await shopping_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("---") + # Print the whole chain of messages + for i, message in enumerate(result["messages"]): + print(f"--- Message {i + 1} ---") + print(format_message(message)) + print() if __name__ == "__main__": asyncio.run(main()) \ No newline at end of file