| | """LangGraph‑powered autonomous agent able to use the *BetterThanMe* toolset. |
| | Target: GAIA level 1 competency without external API keys. |
| | Provides `build_graph()` for the evaluation harness. |
| | """ |
| | from __future__ import annotations |
| |
|
| | import os |
| | from typing import Any, List, TypedDict |
| |
|
| | from langchain.schema import AIMessage, HumanMessage, SystemMessage |
| | from langchain_openai import ChatOpenAI |
| | from langgraph.graph import StateGraph, END |
| |
|
| | from tools import TOOLS |
| |
|
| | |
| | |
| | |
| | class AgentState(TypedDict, total=False): |
| | messages: List[Any] |
| | tool_calls: list |
| | needs_tool: bool |
| |
|
| |
|
| | |
| | |
| | |
| | MODEL = os.getenv("OPENAI_MODEL", "gpt-3.5-turbo") |
| | llm = ChatOpenAI(model_name=MODEL, temperature=0) |
| |
|
| | SYSTEM_PROMPT = """ |
| | You are **BetterThanMe**, a tool‑using assistant. |
| | **Rules** |
| | 1. Think step‑by‑step *internally* but **never** reveal your reasoning. |
| | 2. Use JSON function‑call format to invoke tools when external data is required. |
| | 3. When you are completely confident in the answer, respond with **exactly** one line: |
| | Final Answer: <answer> |
| | – where <answer> is a single concise value (number, word, date, URL, etc.). |
| | – Do **not** add explanations, extra words, or additional lines. |
| | The automated grader will strip the first 14 characters ('Final Answer: ') to obtain the answer, so formatting must be perfect. |
| | """ |
| |
|
| | |
| | |
| | |
| |
|
| | def agent_node(state: AgentState) -> AgentState: |
| | messages = state.get("messages", []) |
| | if not messages or not isinstance(messages[0], SystemMessage): |
| | messages = [SystemMessage(content=SYSTEM_PROMPT)] + messages |
| |
|
| | response = llm.invoke(messages, tools=TOOLS) |
| | messages.append(response) |
| |
|
| | tool_calls = getattr(response, "tool_calls", None) |
| | needs_tool = bool(tool_calls) |
| |
|
| | return { |
| | "messages": messages, |
| | "tool_calls": tool_calls, |
| | "needs_tool": needs_tool, |
| | } |
| |
|
| |
|
| | def tool_executor_node(state: AgentState) -> AgentState: |
| | messages = state["messages"] |
| | tool_calls = state.get("tool_calls", []) or [] |
| |
|
| | for call in tool_calls: |
| | name = call["name"] |
| | args = call.get("arguments", {}) |
| |
|
| | tool = next((t for t in TOOLS if t.name == name), None) |
| | if tool is None: |
| | result = f"Tool '{name}' not found." |
| | else: |
| | try: |
| | result = tool.run(**args) |
| | except Exception as exc: |
| | result = f"Error running tool: {exc}" |
| |
|
| | messages.append(AIMessage(content=result, name=name)) |
| |
|
| | return { |
| | "messages": messages, |
| | "needs_tool": False, |
| | } |
| |
|
| |
|
| | |
| | |
| | |
| |
|
| | def _compile_graph(): |
| | g = StateGraph(AgentState) |
| |
|
| | g.add_node("agent", agent_node) |
| | g.add_node("executor", tool_executor_node) |
| |
|
| | g.add_conditional_edges( |
| | "agent", |
| | lambda s: s["needs_tool"], |
| | {True: "executor", False: END}, |
| | ) |
| | g.add_edge("executor", "agent") |
| | g.set_entry_point("agent") |
| | return g.compile() |
| |
|
| | _GRAPH = _compile_graph() |
| |
|
| |
|
| | def build_graph(): |
| | """Return the compiled LangGraph, conforming to evaluation harness.""" |
| | return _GRAPH |
| |
|
| |
|
| | |
| | |
| | |
| |
|
| | def chat_agent(user_input: str, history: List[List[str]] | None = None) -> str: |
| | """Single‑turn helper for interactive Gradio chat UI.""" |
| | history = history or [] |
| |
|
| | messages: List[Any] = [] |
| | for user, ai in history: |
| | messages.append(HumanMessage(content=user)) |
| | messages.append(AIMessage(content=ai)) |
| | messages.append(HumanMessage(content=user_input)) |
| |
|
| | final_state: AgentState = _GRAPH.invoke({"messages": messages}) |
| |
|
| | for msg in reversed(final_state["messages"]): |
| | if isinstance(msg, AIMessage): |
| | return msg.content |
| | return "(no response)" |