import os import json import asyncio from rich.console import Console from langchain_openai import ChatOpenAI from langchain.tools import tool from deepagents import create_deep_agent from deepagents.backends import FilesystemBackend, LocalShellBackend, CompositeBackend from langgraph.checkpoint.memory import MemorySaver # --- LLM ------------------------------------------------------------------- llm = ChatOpenAI( model="openai/gpt-oss-20b:free", base_url="https://openrouter.ai/api/v1", api_key=os.getenv("OPENAI_API_KEY"), temperature=0.0, ) # --- Backend -------------------------------------------------------------- backend = CompositeBackend([ LocalShellBackend(workspace_dir="./workspace"), FilesystemBackend(), ]) # --- Tool ------------------------------------------------------------------ @tool def get_weather(query: str) -> str: """Return a fake weather report for a given city and date. The argument is a JSON string with keys "city" and "date". """ try: data = json.loads(query) city = data.get("city", "unknown") date = data.get("date", "unknown") return f"Weather in {city} on {date}: sunny, 25°C." except Exception as e: return f"Error parsing query: {e}" # --- Agent ----------------------------------------------------------------- memory = MemorySaver() agent = create_deep_agent( model=llm, tools=[get_weather], backend=backend, system_prompt="You are a helpful weather assistant. Use the get_weather tool when asked about weather.", checkpointer=memory, interrupt_before=["tools"], # pause before each tool call ) console = Console() # --------------------------------------------------------------------------- async def ask_and_run(user_input, config): """Run the agent with optional user input. If user_input is None, the agent resumes from the paused state. """ # Stream the agent's response async for chunk in agent.stream(user_input, config=config, stream_mode=["messages", "updates"]): state = agent.get_state(config) chunk_type, chunk_data = chunk if chunk_type == "messages": # chunk_data can be a dict with 'content' if isinstance(chunk_data, dict) and "content" in chunk_data: console.print(chunk_data["content"], end="") else: console.print(chunk_data) elif chunk_type == "updates": console.print("\n[bold]Tool call:[/bold]", chunk_data) # Detect pause before tool call if "__interrupt__" in chunk_data and state.next == ("tools",): # Retrieve the pending tool call last_msg = state.values["messages"][-1] tool_call = None if hasattr(last_msg, "tool_calls") and last_msg.tool_calls: tool_call = last_msg.tool_calls[0] if tool_call: tool_name = tool_call["name"] tool_args = tool_call["args"] console.print(f"\n[bold]Agent wants to call {tool_name}({tool_args})[/bold]") answer = input("Разрешить? (Y/n): ") if answer.lower().strip() == "y": await ask_and_run(None, config) # resume else: console.print("[red]Отменено[/red]") break # --------------------------------------------------------------------------- async def main(): console.print("\n[bold green]Weather Agent ready! Type 'exit' to quit.[/bold green]") config = {"configurable": {"thread_id": "session-1"}} while True: user_input = input("\nВы: ") if user_input.lower() == "exit": break await ask_and_run({"messages": [{"role": "human", "content": user_input}]}, config) if __name__ == "__main__": asyncio.run(main())