DriptoBhattacharyya Claude Opus 4.8 commited on
Commit
0c9c31a
·
1 Parent(s): 7f8f0e0

Gemini vision (solves chess), solver plain-text fallback, pandas in python_repl

Browse files

Vision provider configurable, default Gemini (reads the chess board -> Rd5). Solver falls back to plain-text when structured output returns empty (was dropping the Excel question). python_repl preloads pandas as pd and prompts push numeric work through it. HF_TOKEN documented.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

.env.example CHANGED
@@ -11,6 +11,9 @@ HF_TOKEN=
11
  # "openai_compatible" (freellmapi gateway, best for rate limits), "gemini", "groq".
12
  LLM_PROVIDER=gemini
13
  GEMINI_TEXT_MODEL=gemini-2.5-flash
 
 
 
14
  GROQ_TEXT_MODEL=llama-3.1-8b-instant
15
  # Global pace across all text-LLM calls (req/sec). 0.15 = ~9/min (<10 RPM free).
16
  # Ignored when LLM_PROVIDER=openai_compatible (gateway manages its own limits).
 
11
  # "openai_compatible" (freellmapi gateway, best for rate limits), "gemini", "groq".
12
  LLM_PROVIDER=gemini
13
  GEMINI_TEXT_MODEL=gemini-2.5-flash
14
+ # Image understanding: "gemini" (best), "groq", or "openai_compatible"
15
+ VISION_PROVIDER=gemini
16
+ GEMINI_VISION_MODEL=gemini-2.0-flash
17
  GROQ_TEXT_MODEL=llama-3.1-8b-instant
18
  # Global pace across all text-LLM calls (req/sec). 0.15 = ~9/min (<10 RPM free).
19
  # Ignored when LLM_PROVIDER=openai_compatible (gateway manages its own limits).
gaia_agent/config.py CHANGED
@@ -28,6 +28,10 @@ class Settings(BaseSettings):
28
  # Vision + Whisper always use Groq.
29
  llm_provider: str = "gemini"
30
 
 
 
 
 
31
  # --- Models (override via env) ---
32
  gemini_text_model: str = "gemini-2.5-flash"
33
  groq_text_model: str = "llama-3.1-8b-instant"
 
28
  # Vision + Whisper always use Groq.
29
  llm_provider: str = "gemini"
30
 
31
+ # --- Vision (image understanding) provider: "gemini", "groq", or "openai_compatible" ---
32
+ vision_provider: str = "gemini"
33
+ gemini_vision_model: str = "gemini-2.0-flash"
34
+
35
  # --- Models (override via env) ---
36
  gemini_text_model: str = "gemini-2.5-flash"
37
  groq_text_model: str = "llama-3.1-8b-instant"
gaia_agent/llm.py CHANGED
@@ -87,14 +87,40 @@ def get_structured_llm(schema, temperature: float = 0.0):
87
 
88
  @lru_cache(maxsize=1)
89
  def get_vision_llm():
90
- """Return the Groq multimodal model used for image understanding."""
91
- from langchain_groq import ChatGroq
92
 
 
 
 
93
  s = get_settings()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94
  return ChatGroq(
95
  model=s.groq_vision_model,
96
  api_key=s.groq_api_key,
97
  temperature=0.0,
98
  max_retries=2,
99
- rate_limiter=_rate_limiter(),
100
  )
 
87
 
88
  @lru_cache(maxsize=1)
89
  def get_vision_llm():
90
+ """Return the multimodal model used for image understanding.
 
91
 
92
+ Defaults to Gemini (strongest vision); falls back to Groq if no Google key.
93
+ Image questions are rare (1-2/run) so a low-quota key is fine here.
94
+ """
95
  s = get_settings()
96
+ vp = s.vision_provider.lower()
97
+
98
+ if vp == "gemini" and s.google_api_key:
99
+ from langchain_google_genai import ChatGoogleGenerativeAI
100
+
101
+ return ChatGoogleGenerativeAI(
102
+ model=s.gemini_vision_model,
103
+ google_api_key=s.google_api_key,
104
+ temperature=0.0,
105
+ max_retries=2,
106
+ )
107
+
108
+ if vp == "openai_compatible" and s.openai_compatible_base_url:
109
+ from langchain_openai import ChatOpenAI
110
+
111
+ return ChatOpenAI(
112
+ model=s.openai_compatible_model,
113
+ base_url=s.openai_compatible_base_url,
114
+ api_key=s.openai_compatible_api_key,
115
+ temperature=0.0,
116
+ max_retries=2,
117
+ )
118
+
119
+ from langchain_groq import ChatGroq
120
+
121
  return ChatGroq(
122
  model=s.groq_vision_model,
123
  api_key=s.groq_api_key,
124
  temperature=0.0,
125
  max_retries=2,
 
126
  )
gaia_agent/nodes.py CHANGED
@@ -144,18 +144,27 @@ def route_after_research(state: GraphState) -> str:
144
 
145
  def solver(state: GraphState) -> dict:
146
  """Synthesize the research conversation + context into one Candidate answer."""
 
 
 
 
 
 
147
  try:
148
- llm = get_structured_llm(Candidate)
149
- system = SOLVER_PROMPT.format(
150
- rules=GAIA_RULES,
151
- question=state["question"],
152
- context_notes=state.get("context_notes", "") or "(none)",
153
- )
154
- cand = llm.invoke([SystemMessage(system), *state.get("messages", [])])
155
  except Exception: # noqa: BLE001
156
  cand = None
157
- if cand is None: # structured output can return None when the model emits no call
158
- cand = Candidate(answer="", justification="solver-fallback")
 
 
 
 
 
 
 
 
 
159
  return {"candidate": cand}
160
 
161
 
 
144
 
145
  def solver(state: GraphState) -> dict:
146
  """Synthesize the research conversation + context into one Candidate answer."""
147
+ system = SOLVER_PROMPT.format(
148
+ rules=GAIA_RULES,
149
+ question=state["question"],
150
+ context_notes=state.get("context_notes", "") or "(none)",
151
+ )
152
+ msgs = [SystemMessage(system), *state.get("messages", [])]
153
  try:
154
+ cand = get_structured_llm(Candidate).invoke(msgs)
 
 
 
 
 
 
155
  except Exception: # noqa: BLE001
156
  cand = None
157
+
158
+ # Structured output sometimes returns None/empty (model emits no function call).
159
+ # Fall back to a plain-text answer so we never drop a solvable question.
160
+ if cand is None or not (cand.answer or "").strip():
161
+ try:
162
+ txt = get_text_llm().invoke(
163
+ msgs + [HumanMessage("Output ONLY the final answer, nothing else.")]
164
+ ).content
165
+ cand = Candidate(answer=(txt or "").strip(), justification="plain-fallback")
166
+ except Exception: # noqa: BLE001
167
+ cand = Candidate(answer="", justification="solver-fallback")
168
  return {"candidate": cand}
169
 
170
 
gaia_agent/prompts.py CHANGED
@@ -32,8 +32,10 @@ file, attachment, image, audio, spreadsheet, table, or document, set needs_file=
32
 
33
  RESEARCH_PROMPT = """\
34
  You are the RESEARCHER. Gather the facts needed to answer the question precisely.
35
- Use the available tools. Prefer authoritative sources. Do exact arithmetic / string
36
- work with python_repl rather than guessing. Stop calling tools once you have enough.
 
 
37
 
38
  {rules}
39
 
 
32
 
33
  RESEARCH_PROMPT = """\
34
  You are the RESEARCHER. Gather the facts needed to answer the question precisely.
35
+ Use the available tools. Prefer authoritative sources. NEVER do arithmetic in your head
36
+ -- for ANY counting, summing, or numeric work call python_repl (pandas is available as
37
+ `pd`; read attached spreadsheets with pd.read_excel(path) using the file path in context).
38
+ Stop calling tools once you have enough.
39
 
40
  {rules}
41
 
gaia_agent/tools/python_tool.py CHANGED
@@ -23,9 +23,11 @@ _SAFE_BUILTINS = {
23
  def python_repl(code: str) -> str:
24
  """Execute a short Python snippet and return its stdout (and `result` if set).
25
 
26
- Use for exact arithmetic, string manipulation, sorting, and date math. The
27
- ``math``, ``statistics``, ``datetime``, ``re``, ``itertools``, and ``collections``
28
- modules are available. Assign to a variable named ``result`` to return a value.
 
 
29
 
30
  Args:
31
  code: Python source to execute.
@@ -45,6 +47,14 @@ def python_repl(code: str) -> str:
45
  "itertools": itertools,
46
  "collections": collections,
47
  }
 
 
 
 
 
 
 
 
48
  buf = io.StringIO()
49
  try:
50
  with contextlib.redirect_stdout(buf):
 
23
  def python_repl(code: str) -> str:
24
  """Execute a short Python snippet and return its stdout (and `result` if set).
25
 
26
+ Use for exact arithmetic, string manipulation, sorting, and date math. ALWAYS use
27
+ this for numeric work instead of computing in your head. ``math``, ``statistics``,
28
+ ``datetime``, ``re``, ``itertools``, ``collections`` and ``pd`` (pandas) are
29
+ available -- e.g. read a spreadsheet with ``pd.read_excel(path)``. Assign to a
30
+ variable named ``result`` to return a value.
31
 
32
  Args:
33
  code: Python source to execute.
 
47
  "itertools": itertools,
48
  "collections": collections,
49
  }
50
+ # Preload pandas as `pd` so spreadsheet math is exact, e.g.
51
+ # df = pd.read_excel(path); result = df[["Burgers","Fries"]].sum().sum()
52
+ try:
53
+ import pandas as pd
54
+
55
+ env["pd"] = pd
56
+ except Exception: # noqa: BLE001
57
+ pass
58
  buf = io.StringIO()
59
  try:
60
  with contextlib.redirect_stdout(buf):