import os import asyncio from typing import Any 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="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 for file operations and shell commands (required by deepagents) backend = CompositeBackend( [ LocalShellBackend(workspace_dir="./workspace"), FilesystemBackend(), ] ) @tool def get_price(product: str, city: str) -> str: """ 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=[], backend=backend, system_prompt=sub_system_prompt, ) # 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}"}}, ) ) # Extract the final content from the sub-agent's response return result["messages"][-1].content # Main shopping-list agent agent = create_deep_agent( model=llm, tools=[get_price], backend=backend, system_prompt="Ты помощник по планированию покупок.", ) 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) async def main() -> None: user_query = "Помоги составить список покупок: молоко, хлеб, яблоки. Я нахожусь в Казани." result = await agent.ainvoke( {"messages": [HumanMessage(content=user_query)]}, {"configurable": {"thread_id": "shopping-session-1"}}, ) # Output the whole chain of messages for idx, msg in enumerate(result["messages"], start=1): print(f"--- Message {idx} ---") print(format_message(msg)) print() if __name__ == "__main__": asyncio.run(main())