""" Main entry point for the agent with memory and human‑in‑the‑loop confirmation. The agent is built on top of LangChain's `create_tool_calling_agent` API. It uses a `MemorySaver` checkpoint to keep conversation history across calls, and it is configured with `interrupt_before=["tools"]` so that the agent pauses just before invoking any tool. The pause allows us to ask the user for explicit confirmation. The example includes one simple tool – ``get_price`` – which pretends to query a price service. In a real project this would be replaced with an actual API call. Three usage examples are demonstrated in ``__main__``: 1. Ask the agent for weather information (uses the built‑in ``web_search`` tool). 2. Ask for a product price – the agent will pause and ask for confirmation. 3. Continue the conversation to show that memory is preserved. The console output is rendered with `rich` for better readability. """ from __future__ import annotations import os import json from typing import Any, Dict from langchain_openai import ChatOpenAI from langchain_core.messages import HumanMessage, SystemMessage from langgraph.checkpoint.memory import MemorySaver from langgraph.types import Command from langchain.agents import create_tool_calling_agent from langchain.tools import tool from rich.console import Console # --------------------------------------------------------------------------- # Configuration # --------------------------------------------------------------------------- console = Console() llm = ChatOpenAI( model="openai/gpt-oss-20b:free", base_url="https://platform.brojs.ru/jrnl-bh/api/inference/v1", api_key=os.getenv("JOURNAL_MCP_PAT"), temperature=0.0, ) # --------------------------------------------------------------------------- # Simple tool – in a real scenario replace with an actual API call. # --------------------------------------------------------------------------- @tool async def get_price(query: Dict[str, Any]) -> str: """Pretend to fetch a price for a product. Parameters ---------- query: dict Expected keys are ``product`` and optionally ``currency``. Returns ------- str A human‑readable string describing the price. """ product = query.get("product", "unknown") currency = query.get("currency", "USD") # Dummy logic – in real life call an external service. return f"The price of {product} is 42.00 {currency}." # --------------------------------------------------------------------------- # Agent definition using create_tool_calling_agent (LangChain) # --------------------------------------------------------------------------- memory = MemorySaver() agent = create_tool_calling_agent( llm=llm, tools=[get_price], system_prompt="You are a helpful assistant that can query prices.", checkpointer=memory, interrupt_before=["tools"], # pause before any tool call ) # --------------------------------------------------------------------------- # Helper to run the agent and pause before each tool call. # --------------------------------------------------------------------------- async def ask_and_run(user_input: Dict[str, Any], config: Dict[str, Any]): """Run the agent and pause before each tool call. Parameters ---------- user_input: dict Dictionary with a ``messages`` key containing a list of messages. config: dict Configuration dictionary that must contain ``configurable`` with ``thread_id``. """ async for chunk in agent.stream(user_input, config=config, stream_mode=["messages", "updates"]): # ``chunk`` is a tuple (type, data). chunk_type, chunk_data = chunk state = agent.get_state(config) if chunk_type == "messages": # Stream token by token. console.print(chunk_data.content, end="", style="cyan") console.file.flush() elif chunk_type == "updates": # Tool call preview – show the user what will be executed. console.print("\n[bold magenta]Agent wants to call a tool:[/]") console.print(json.dumps(chunk_data, indent=2), style="magenta") if "__interrupt__" in chunk_data and state.next == ("tools",): # Pause – ask for confirmation. console.print("\n[bold yellow]Confirmation required:[/] Do you allow the tool call? (y/n)") answer = input().strip().lower() if answer != "y": console.print("[red]Action cancelled by user.[/]") break # Resume from the same state. await agent.ainvoke(Command(resume=None), config=config) # --------------------------------------------------------------------------- # Main loop – three examples as requested. # --------------------------------------------------------------------------- if __name__ == "__main__": thread_id = "demo-thread" config = {"configurable": {"thread_id": thread_id}} console.print("[bold green]Welcome to the agent demo![/]") console.print("Type 'exit' to quit.") import asyncio # Example 1 – simple chat (no tool call). console.print("\n[underline]Example 1: Simple question[/]") user_msg = {"messages": [HumanMessage(content="What is the capital of France?")]} asyncio.run(ask_and_run(user_msg, config)) # Example 2 – tool call with confirmation. console.print("\n[underline]Example 2: Tool call (price query)[/]") user_msg = {"messages": [HumanMessage(content="Get price of laptop in USD")]} asyncio.run(ask_and_run(user_msg, config)) # Example 3 – continue conversation to show memory. console.print("\n[underline]Example 3: Continue conversation[/]") user_msg = {"messages": [HumanMessage(content="What about the price in EUR?")]} asyncio.run(ask_and_run(user_msg, config)) console.print("\n[bold green]Demo finished.[/]" )