import os import asyncio 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 from langgraph.checkpoint.memory import MemorySaver from rich.console import Console # ---------- 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_price(query: str) -> str: """Return a mock price for a city and date.""" return f"Price for {query} is $42" # ---------- Memory ---------- memory = MemorySaver() # ---------- Console ---------- console = Console() # ---------- Agent ---------- agent = create_deep_agent( model=llm, tools=[get_price], backend=backend, system_prompt="You are a helpful agent that can fetch prices.", checkpointer=memory, interrupt_before=["tools"], ) # ---------- Helper to display tool call ---------- async def display_tool_call(state): # Get the last tool call from the messages messages = state.values.get("messages", []) if not messages: return last_msg = messages[-1] if hasattr(last_msg, "tool_calls") and last_msg.tool_calls: tool_call = last_msg.tool_calls[0] name = tool_call.get("name") args = tool_call.get("args") console.print(f"\n[bold cyan]Agent wants to call tool:[/bold cyan] {name}({args})") # ---------- Main loop ---------- async def ask_and_run(user_input, config): async for chunk in agent.stream(user_input, config=config, stream_mode=["messages", "updates"]): chunk_type, chunk_data = chunk state = agent.get_state(config) if chunk_type == "messages": # Stream token output console.print(chunk_data, end="") if chunk_type == "updates": # Tool execution output console.print(chunk_data, end="") # Handle interrupt before tool call if "__interrupt__" in chunk_data and state.next == ("tools",): await display_tool_call(state) answer = input("Разрешить? (Y/n): ") if answer.lower().strip() in ("", "y", "yes"): # Resume by calling ask_and_run with None to continue await ask_and_run(None, config) else: console.print("[red]Отменено[/red]") break async def main(): config = {"configurable": {"thread_id": "conversation-1"}} console.print("[bold green]Добро пожаловать! Введите 'exit' для выхода.[/bold green]") 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())