Spaces:
Runtime error
Runtime error
File size: 7,616 Bytes
690538b 400e97a 7d74ca3 400e97a 690538b 400e97a 7cc5531 690538b 5aa21de 7cc5531 690538b 5aa21de 400e97a 690538b 7cc5531 400e97a 7cc5531 9b6ad5a 400e97a 9b6ad5a d046ba6 7cc5531 400e97a 7cc5531 7d74ca3 400e97a 7cc5531 400e97a 7d74ca3 400e97a 7d74ca3 7cc5531 400e97a 7cc5531 400e97a 7cc5531 e8d1b6b 7d74ca3 e8d1b6b 400e97a 7d74ca3 400e97a 7cc5531 400e97a 7cc5531 400e97a 7cc5531 e8d1b6b 7d74ca3 400e97a 7d74ca3 400e97a 7cc5531 400e97a 7cc5531 400e97a 7cc5531 400e97a 7cc5531 60684f0 7d74ca3 400e97a 7d74ca3 7cc5531 d046ba6 7d74ca3 400e97a 7cc5531 400e97a 7cc5531 400e97a 7cc5531 400e97a 7cc5531 400e97a 7cc5531 400e97a 895b1b5 400e97a 7cc5531 400e97a 7cc5531 895b1b5 400e97a c8c37ed 7cc5531 400e97a c329c72 400e97a c329c72 7cc5531 400e97a c329c72 400e97a c329c72 400e97a c329c72 400e97a 130522b 400e97a 7cc5531 400e97a 7cc5531 400e97a 60684f0 400e97a 60684f0 400e97a |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 |
import os
import re
import time
import functools
from typing import Dict, Any, List
import pandas as pd
# LangGraph
from langgraph.graph import StateGraph, START, END, MessagesState
from langgraph.prebuilt import ToolNode, tools_condition
# LangChain Core
from langchain_core.messages import SystemMessage, HumanMessage
from langchain_core.tools import tool
# Google Gemini
from langchain_google_genai import ChatGoogleGenerativeAI
# Tools
from langchain_community.tools.tavily_search import TavilySearchResults
from langchain_community.utilities.wikipedia import WikipediaAPIWrapper
# Python REPL Tool
try:
from langchain_experimental.tools.python.tool import PythonAstREPLTool
except ImportError:
from langchain.tools.python.tool import PythonAstREPLTool
# ---------------------------------------------------------------------
# 0) Optionale LangSmith-Tracing (setze ENV: LANGCHAIN_API_KEY)
# ---------------------------------------------------------------------
if os.getenv("LANGCHAIN_API_KEY"):
os.environ["LANGCHAIN_TRACING_V2"] = "true"
os.environ["LANGCHAIN_ENDPOINT"] = "https://api.smith.langchain.com"
os.environ.setdefault("LANGCHAIN_PROJECT", "gaia-agent")
print("📡 LangSmith tracing enabled.")
# ---------------------------------------------------------------------
# 1) Helfer: Fehler-Decorator + Backoff-Wrapper
# ---------------------------------------------------------------------
def error_guard(fn):
"""Fängt Tool-Fehler ab & gibt String zurück (bricht Agent nicht ab)."""
@functools.wraps(fn)
def wrapper(*args, **kw):
try:
return fn(*args, **kw)
except Exception as e:
return f"ERROR: {e}"
return wrapper
def with_backoff(fn, tries: int = 4, delay: int = 4):
"""Synchrones Retry-Wrapper für LLM-Aufrufe."""
for t in range(tries):
try:
return fn()
except Exception as e:
if ("429" in str(e) or "RateLimit" in str(e)) and t < tries - 1:
time.sleep(delay)
delay *= 2
continue
raise
# ---------------------------------------------------------------------
# 2) Eigene Tools (CSV / Excel)
# ---------------------------------------------------------------------
@tool
@error_guard
def parse_csv(file_path: str, query: str = "") -> str:
"""Load a CSV file and (optional) run a pandas query."""
df = pd.read_csv(file_path)
if not query:
return f"Rows={len(df)}, Cols={list(df.columns)}"
try:
return df.query(query).to_markdown(index=False)
except Exception as e:
return f"ERROR query: {e}"
@tool
@error_guard
def parse_excel(file_path: str, sheet: str | int | None = None, query: str = "") -> str:
"""Load an Excel sheet (name or index) and (optional) run a pandas query."""
sheet_arg = int(sheet) if isinstance(sheet, str) and sheet.isdigit() else sheet or 0
df = pd.read_excel(file_path, sheet_name=sheet_arg)
if not query:
return f"Rows={len(df)}, Cols={list(df.columns)}"
try:
return df.query(query).to_markdown(index=False)
except Exception as e:
return f"ERROR query: {e}"
# ---------------------------------------------------------------------
# 3) Externe Search-Tools (Tavily, Wikipedia)
# ---------------------------------------------------------------------
@tool
@error_guard
def web_search(query: str, max_results: int = 5) -> str:
"""Search the web via Tavily and return markdown list of results."""
api_key = os.getenv("TAVILY_API_KEY")
hits = TavilySearchResults(max_results=max_results, api_key=api_key).invoke(query)
if not hits:
return "No results."
return "\n".join(f"{h['title']} – {h['url']}" for h in hits)
@tool
@error_guard
def wiki_search(query: str, sentences: int = 3) -> str:
"""Quick Wikipedia summary."""
wrapper = WikipediaAPIWrapper(top_k_results=1, doc_content_chars_max=4000)
res = wrapper.run(query)
return "\n".join(res.split(". ")[:sentences]) if res else "No article found."
# ---------------------------------------------------------------------
# 4) Python-REPL Tool (fertig aus LangChain)
# ---------------------------------------------------------------------
python_repl = PythonAstREPLTool()
# ---------------------------------------------------------------------
# 5) LLM – Gemini Flash, an Tools gebunden
# ---------------------------------------------------------------------
gemini_llm = ChatGoogleGenerativeAI(
google_api_key=os.getenv("GOOGLE_API_KEY"),
model="gemini-2.0-flash",
temperature=0,
max_output_tokens=2048,
)
# ---------------------------------------------------------------------
# 6) System-Prompt (ReAct, keine Prefixe im Final-Output!)
# ---------------------------------------------------------------------
SYSTEM_PROMPT = SystemMessage(
content=(
"You are a helpful assistant with access to several tools.\n"
"You can think step by step and use tools to find answers.\n\n"
"When you want to use a tool, write it like this:\n"
"Tool: <tool_name>\n"
"Input: <input for the tool>\n\n"
"Wait for the tool result before continuing.\n"
"When you know the final answer, reply with the answer **only**.\n"
"Don't include any prefix, explanation or formatting around the answer.\n"
"Answer formatting:\n"
"- For numbers: no units unless requested\n"
"- For strings: no articles or abbreviations\n"
"- For lists: comma + space separated, correct order\n"
)
)
# ---------------------------------------------------------------------
# 7) LangGraph – Planner + Tools + Router
# ---------------------------------------------------------------------
def planner(state: MessagesState):
msgs = state["messages"]
if msgs[0].type != "system":
msgs = [SYSTEM_PROMPT] + msgs
resp = with_backoff(lambda: gemini_llm.invoke(msgs))
# WICHTIG: Gib tool_calls weiter – sie lösen im ToolNode die Ausführung aus
return {
"messages": msgs + [resp],
"should_end": (
not getattr(resp, "tool_calls", None) # kein Tool gewünscht
and "\n" not in resp.content # einfache Heuristik
)
}
def route(state):
return "END" if state["should_end"] else "tools"
# Tool-Knoten
TOOLS = [web_search, wiki_search, parse_csv, parse_excel, python_repl]
graph = StateGraph(MessagesState)
graph.add_node("planner", planner)
graph.add_node("tools", ToolNode(TOOLS))
graph.add_edge(START, "planner")
graph.add_edge("tools", "planner") # 🔁 Rücksprung zum Planner nach Tool-Ausführung
graph.add_conditional_edges("planner", route, {
"tools": "tools",
"END": END,
})
# compile → LangGraph-Executor
agent_executor = graph.compile()
# ---------------------------------------------------------------------
# 8) Öffentliche Klasse – wird von app.py / logic.py verwendet
# ---------------------------------------------------------------------
class GaiaAgent:
"""LangChain·LangGraph-Agent für GAIA Level 1."""
def __init__(self):
print("✅ GaiaAgent initialised (LangGraph)")
def __call__(self, task_id: str, question: str) -> str:
"""Run the agent on a single GAIA question → exact answer string."""
start_state = {"messages": [HumanMessage(content=question)]}
final_state = agent_executor.invoke(start_state)
# letze Message enthält Antwort
answer = final_state["messages"][-1].content
return answer.strip() |