From f765f87ac6bfdedacb2c79f67a2d8a8118a45d99 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 05:53:40 +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 | 110 ++++++++++++++++++++++++++++++++++---------------------- 1 file changed, 68 insertions(+), 42 deletions(-) diff --git a/main.py b/main.py index 32440a4..157e481 100644 --- a/main.py +++ b/main.py @@ -1,47 +1,74 @@ import os import asyncio -from langchain_openai import ChatOpenAI -from langchain.tools import tool -from langchain.agents import create_agent -from langchain_core.messages import HumanMessage -from deepagents import create_deep_agent -from deepagents.backends import CompositeBackend, LocalShellBackend, FilesystemBackend +from typing import Any -# ---------- LLM ---------- -# Use OpenRouter – cloud API, no local GPU required +from langchain_openai import ChatOpenAI +from langchain_core.messages import HumanMessage +from langchain.tools import tool +from deepagents import create_deep_agent +from deepagents.backends import FilesystemBackend, LocalShellBackend, CompositeBackend + +# LLM configuration according to the assignment specification llm = ChatOpenAI( - model="openai/gpt-oss-20b:free", - base_url="https://openrouter.ai/api/v1", - api_key=os.getenv("OPENAI_API_KEY"), + model="your-model-name", # replace with the actual model name in LM Studio + base_url="http://localhost:1234/v1", + api_key="fake", # OpenAI SDK requires a non-empty key temperature=0.7, ) -# ---------- Backend ---------- -backend = CompositeBackend([ - LocalShellBackend(workspace_dir="./workspace"), - FilesystemBackend(), -]) +# Backend for file operations and shell commands (required by deepagents) +backend = CompositeBackend( + [ + LocalShellBackend(workspace_dir="./workspace"), + FilesystemBackend(), + ] +) -# ---------- Tool with sub‑agent ---------- @tool def get_price(product: str, city: str) -> str: - """Return a realistic price table for a product in a city. - The tool internally creates a sub‑agent that generates the table. """ - # Sub‑agent that simply produces a price table - sub_agent = create_agent( + Retrieve a realistic price for the given product in the specified city. + The function creates a sub-agent that returns a markdown table row. + """ + # System prompt for the sub-agent - it must output a table with columns + # Product, Price (руб.), Store. + sub_system_prompt = ( + "You are a price generator. Provide a markdown table with columns " + "'Продукт', 'Цена (руб.)', 'Магазин' for the given product and city. " + "Give a realistic price and a plausible store name." + ) + + # Create the sub-agent (no additional tools needed) + sub_agent = create_deep_agent( model=llm, tools=[], - system_prompt=f"You are a price estimator for {city}. Provide a realistic price for {product} in a table format.", + backend=backend, + system_prompt=sub_system_prompt, ) - prompt = ( - f"Generate a table with columns Продукт, Цена (руб.), Магазин for product '{product}' in city '{city}'." + + # Prepare the query for the sub-agent + query = f"Provide price information for {product} in {city}." + + # Invoke the sub-agent synchronously + # DESIGN DECISION: Use asyncio.run to execute the sub-agent inside a + # synchronous tool. deepagents operates asynchronously, but the tool + # interface required by the main agent is synchronous. + # NECESSITY: The assignment defines the tool as a regular function. + # OPTIMALITY: This approach keeps the code simple and avoids mixing + # async/sync contexts incorrectly. + # ALTERNATIVES CONSIDERED: Making the tool async (deepagents supports + # async tools) would require changes to the main agent invocation pattern, + # which is unnecessary for this educational example. + result = asyncio.run( + sub_agent.ainvoke( + {"messages": [HumanMessage(content=query)]}, + {"configurable": {"thread_id": f"price-{product}-{city}"}}, + ) ) - result = sub_agent.invoke({"messages": [HumanMessage(content=prompt)]}) - # The last message contains the table + # Extract the final content from the sub-agent's response return result["messages"][-1].content -# ---------- Main agent ---------- +# Main shopping-list agent agent = create_deep_agent( model=llm, tools=[get_price], @@ -49,27 +76,26 @@ agent = create_deep_agent( system_prompt="Ты помощник по планированию покупок.", ) -# ---------- Helper to pretty‑print messages ---------- +def format_message(message: Any) -> str: + """Convert a LangChain message to a readable string.""" + if hasattr(message, "content") and message.content: + return message.content + if hasattr(message, "tool_calls") and message.tool_calls: + tc = message.tool_calls[0] + return f"{tc['name']}({tc['args']})" + return str(message) -def format_message(msg): - if hasattr(msg, "content") and msg.content: - return msg.content - if hasattr(msg, "tool_calls") and msg.tool_calls: - call = msg.tool_calls[0] - return f"{call['name']}({call['args']})" - return "" - -# ---------- Main execution ---------- -async def main(): +async def main() -> None: user_query = "Помоги составить список покупок: молоко, хлеб, яблоки. Я нахожусь в Казани." result = await agent.ainvoke( {"messages": [HumanMessage(content=user_query)]}, - {"configurable": {"thread_id": "session-1"}}, + {"configurable": {"thread_id": "shopping-session-1"}}, ) - # Print all messages in order - for msg in result["messages"]: + # Output the whole chain of messages + for idx, msg in enumerate(result["messages"], start=1): + print(f"--- Message {idx} ---") print(format_message(msg)) - print("---") + print() if __name__ == "__main__": asyncio.run(main()) \ No newline at end of file