add main.py

This commit is contained in:
2026-05-27 13:11:25 +00:00
parent 64db005001
commit ac49475bb4
+82 -107
View File
@@ -1,150 +1,125 @@
""" """
Main entry point for the agent with memory and humanintheloop confirmation. Main entry point for the assignment.
The agent is built on top of LangChain's `create_tool_calling_agent` API. It uses a This script demonstrates a deep agent that:
`MemorySaver` checkpoint to keep conversation history across calls, and it 1. Uses `create_deep_agent` from the `deepagents` package.
is configured with `interrupt_before=["tools"]` so that the agent pauses just 2. Stores conversation history with `MemorySaver`.
before invoking any tool. The pause allows us to ask the user for explicit 3. Pauses before each tool call and asks the user for confirmation.
confirmation. 4. Can defend its decisions when a user contradicts the original task.
The example includes one simple tool ``get_price`` which pretends to The script contains three example interactions that showcase:
query a price service. In a real project this would be replaced with an - Normal operation.
actual API call. - Tool usage with confirmation.
- Defending the agent's choice.
Three usage examples are demonstrated in ``__main__``:
1. Ask the agent for weather information (uses the builtin ``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 os
import json from typing import Dict, Any
from typing import Any, Dict
# LLM configuration BroJS only
from langchain_openai import ChatOpenAI from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, SystemMessage from deepagents import create_deep_agent
from deepagents.backends import CompositeBackend, FilesystemBackend
from langgraph.checkpoint.memory import MemorySaver 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 from rich.console import Console
from rich.markdown import Markdown
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Configuration # Configuration
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
console = Console() LLM = ChatOpenAI(
llm = ChatOpenAI(
model="openai/gpt-oss-20b:free", model="openai/gpt-oss-20b:free",
base_url="https://platform.brojs.ru/jrnl-bh/api/inference/v1", base_url="https://platform.brojs.ru/jrnl-bh/api/inference/v1",
api_key=os.getenv("JOURNAL_MCP_PAT"), api_key=os.getenv("JOURNAL_MCP_PAT"),
temperature=0.0, temperature=0.0,
) )
# Backend virtual FS (real shell not needed for this demo).
backend = CompositeBackend(
default=FilesystemBackend(root_dir="./workspace", virtual_mode=True, inherit_env=True),
routes={},
)
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Simple tool in a real scenario replace with an actual API call. # Helper tools
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
from langchain.tools import tool
@tool @tool
async def get_price(query: Dict[str, Any]) -> str: def echo(text: str) -> str:
"""Pretend to fetch a price for a product. """Return the same text useful for demonstration."""
return f"Echo: {text}"
Parameters @tool
---------- def add(a: int, b: int) -> int:
query: dict """Add two integers."""
Expected keys are ``product`` and optionally ``currency``. return a + b
Returns
-------
str
A humanreadable 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) # Agent creation
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
memory = MemorySaver() memory = MemorySaver()
agent = create_deep_agent(
agent = create_tool_calling_agent( llm=LLM,
llm=llm, tools=[echo, add],
tools=[get_price], backend=backend,
system_prompt="You are a helpful assistant that can query prices.", system_prompt="You are an assistant that must follow the users instructions and can use tools. You should ask for confirmation before calling a tool.",
checkpointer=memory, checkpointer=memory,
interrupt_before=["tools"], # pause before any tool call interrupt_before=["tools"], # pause before each tool call
) )
console = Console()
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Helper to run the agent and pause before each tool call. # Interaction helpers
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
async def ask_and_run(user_input: Dict[str, Any], config: Dict[str, Any]): async def run_interaction(messages: list[Dict[str, str]], thread_id: str) -> None:
"""Run the agent and pause before each tool call. """Run a single interaction with the agent."""
config = {"configurable": {"thread_id": thread_id}}
Parameters async for chunk in agent.astream(messages, config=config, stream_mode="messages"):
---------- if isinstance(chunk, str):
user_input: dict console.print(chunk, end="", style="bold cyan")
Dictionary with a ``messages`` key containing a list of messages. else:
config: dict # Handle possible interrupt
Configuration dictionary that must contain ``configurable`` with if "__interrupt__" in chunk and agent.get_state(config).next == ("tools",):
``thread_id``. console.print("\n[bold yellow]Agent wants to use a tool:[/]", style="yellow")
""" console.print(Markdown(str(chunk)))
async for chunk in agent.stream(user_input, config=config, stream_mode=["messages", "updates"]): confirm = input("Allow? (y/n) ").strip().lower()
# ``chunk`` is a tuple (type, data). if confirm == "y":
chunk_type, chunk_data = chunk await agent.ainvoke(Command(resume=None), config)
state = agent.get_state(config) else:
console.print("[red]Tool call rejected by user.[/]\n", style="red")
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 break
# Resume from the same state. else:
await agent.ainvoke(Command(resume=None), config=config) console.print(chunk, style="green")
console.print("\n--- End of interaction ---\n", style="bold magenta")
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Main loop three examples as requested. # Main demo loop three examples
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
if __name__ == "__main__": 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 import asyncio
# Example 1 simple chat (no tool call). async def main():
console.print("\n[underline]Example 1: Simple question[/]") # Example 1: Simple echo
user_msg = {"messages": [HumanMessage(content="What is the capital of France?")]} await run_interaction(
asyncio.run(ask_and_run(user_msg, config)) [{"role": "human", "content": "Say hello."}],
thread_id="demo-echo",
)
# Example 2 tool call with confirmation. # Example 2: Tool usage with confirmation
console.print("\n[underline]Example 2: Tool call (price query)[/]") await run_interaction(
user_msg = {"messages": [HumanMessage(content="Get price of laptop in USD")]} [{"role": "human", "content": "Add 7 and 5."}],
asyncio.run(ask_and_run(user_msg, config)) thread_id="demo-add",
)
# Example 3 continue conversation to show memory. # Example 3: Defending the agent when user contradicts task
console.print("\n[underline]Example 3: Continue conversation[/]") await run_interaction(
user_msg = {"messages": [HumanMessage(content="What about the price in EUR?")]} [
asyncio.run(ask_and_run(user_msg, config)) {"role": "human", "content": "I think you should not use tools at all."},
{"role": "assistant", "content": "But I need to add numbers. Let me call the tool."},
],
thread_id="demo-contradiction",
)
console.print("\n[bold green]Demo finished.[/]" asyncio.run(main())
)