From 7da640e2c784f4ee9c1affe3255af49cbea81361 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=B0=D0=BD=D0=B8=D0=B8=D0=BB=20=D0=92=D0=B8=D0=BA?= =?UTF-8?q?=D1=82=D0=BE=D1=80=D0=BE=D0=B2?= Date: Thu, 2 Jul 2026 07:06:28 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20main.py=20=E2=80=94=20=D0=A1=D0=BE=D0=B7?= =?UTF-8?q?=D0=B4=D0=B0=D0=B9=D1=82=D1=8C=20=D0=BF=D1=80=D0=BE=D1=81=D1=82?= =?UTF-8?q?=D0=BE=20AI=20=D0=B0=D0=B3=D0=B5=D0=BD=D1=82=20=D0=BD=D0=B0=20P?= =?UTF-8?q?ython=20=D1=81=20=D0=BF=D1=80=D0=B8=D0=BC=D0=B5=D0=BD=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=D0=BC=20langchain?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 106 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 53 insertions(+), 53 deletions(-) diff --git a/main.py b/main.py index 220333f..310f235 100644 --- a/main.py +++ b/main.py @@ -1,17 +1,18 @@ import os import asyncio -from typing import Any, Dict, List +from typing import List, Dict, Any -from pydantic import SecretStr from langchain_openai import ChatOpenAI -from langchain_core.messages import HumanMessage, AIMessage, ToolMessage +from langchain_core.messages import HumanMessage, BaseMessage from langchain.tools import tool + from deepagents import create_deep_agent -from deepagents.backends import CompositeBackend, LocalShellBackend, FilesystemBackend +from deepagents.backends import FilesystemBackend, LocalShellBackend, CompositeBackend # ---------------------------------------------------------------------- -# LLM configuration (OpenRouter) +# Configuration # ---------------------------------------------------------------------- +# LLM - OpenRouter (free tier). The API key must be stored in the environment. llm = ChatOpenAI( model="openai/gpt-oss-20b:free", base_url="https://openrouter.ai/api/v1", @@ -19,9 +20,8 @@ llm = ChatOpenAI( temperature=0.7, ) -# ---------------------------------------------------------------------- -# Backend for sub-agents (allows file operations and shell commands) -# ---------------------------------------------------------------------- +# Backend for the agents - a simple composite that allows file operations +# and execution of shell commands inside a sandboxed workspace. backend = CompositeBackend( [ LocalShellBackend(workspace_dir="./workspace"), @@ -30,90 +30,90 @@ backend = CompositeBackend( ) # ---------------------------------------------------------------------- -# Sub-agent that generates a realistic price table for a product +# Sub-agent: price generator # ---------------------------------------------------------------------- -def create_price_subagent() -> Any: +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. + Creates a lightweight sub-agent that, given a product and a city, + returns a markdown table with a plausible price and a store name. + The sub-agent re-uses the same LLM and backend as the main agent. """ - 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 + tools=[], # No additional tools are required for price generation backend=backend, - system_prompt=system_prompt, + system_prompt=( + "You are a price-estimation sub-agent. " + "Given a product name and a city, generate a realistic price " + "in Russian rubles and suggest a typical store. " + "Return the result as a markdown table with columns: " + "`Продукт`, `Цена (руб.)`, `Магазин`." + ), ) return subagent -price_subagent = create_price_subagent() +_price_subagent = _create_price_subagent() -# ---------------------------------------------------------------------- -# Tool that calls the sub-agent -# ---------------------------------------------------------------------- @tool def get_price(product: str, city: str) -> str: """ - Generate a realistic price for the given product in the specified city. - Returns a markdown table with columns: Продукт, Цена (руб.), Магазин. + Estimate the price of a product in a given city. + The function creates a sub-agent that returns a markdown table: + | Продукт | Цена (руб.) | Магазин | """ # 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) + prompt = HumanMessage( + content=f"Продукт: {product}\nГород: {city}\nСгенерируй цену." + ) + # Invoke the sub-agent asynchronously and wait for the result result = asyncio.run( - price_subagent.ainvoke( - {"messages": [HumanMessage(content=prompt)]}, + _price_subagent.ainvoke( + {"messages": [prompt]}, {"configurable": {"thread_id": f"price-{product}-{city}"}}, ) ) # 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) + return final_message.content if isinstance(final_message, BaseMessage) else str(final_message) # ---------------------------------------------------------------------- -# Main shopping-list agent +# Main agent: shopping list planner # ---------------------------------------------------------------------- -shopping_agent = create_deep_agent( +main_agent = create_deep_agent( model=llm, tools=[get_price], backend=backend, system_prompt="Ты помощник по планированию покупок.", ) -def format_message(msg: Any) -> str: - """Human-readable representation of a message or tool call.""" - if isinstance(msg, (HumanMessage, AIMessage)): +def format_message(msg: BaseMessage) -> str: + """ + Convert a LangChain message to a readable string. + Handles normal text messages and tool calls. + """ + if hasattr(msg, "content") and msg.content: return msg.content - if isinstance(msg, ToolMessage): - return f"{msg.name}({msg.args}) -> {msg.content}" - # Fallback for generic dict-like messages + # Tool call representation if hasattr(msg, "tool_calls") and msg.tool_calls: call = msg.tool_calls[0] - return f"{call['name']}({call['args']})" + name = call["name"] + args = ", ".join(f"{k}={v!r}" for k, v in call["args"].items()) + return f"{name}({args})" return str(msg) async def main() -> None: - user_query = "Помоги составить список покупок: молоко, хлеб, яблоки. Я нахожусь в Казани." - result = await shopping_agent.ainvoke( + user_query = ( + "Помоги составить список покупок: молоко, хлеб, яблоки. " + "Я нахожусь в Казани." + ) + result = await main_agent.ainvoke( {"messages": [HumanMessage(content=user_query)]}, {"configurable": {"thread_id": "shopping-session-1"}}, ) - # Print the whole chain of messages - for i, message in enumerate(result["messages"]): - print(f"--- Message {i + 1} ---") - print(format_message(message)) + # Print the whole conversation chain + for i, msg in enumerate(result["messages"], start=1): + print(f"--- Message {i} ---") + print(format_message(msg)) print() if __name__ == "__main__":