GAIA_Agent / agents /synthesis_agent.py
Delanoe Pirard
cookies.txt
68bd1d5
import os
import logging
from typing import Any, Dict
from llama_index.core.agent.workflow import ReActAgent
from llama_index.core.tools import FunctionTool
from llama_index.core.workflow import Context
from llama_index.llms.google_genai import GoogleGenAI
# -----------------------------------------------------------------------------
# Context helper tools ---------------------------------------------------------
# -----------------------------------------------------------------------------
async def write_state(ctx: Context, key: str, value: Any) -> str:
state_dict = await ctx.get("state")
state_dict[key] = value
await ctx.set("state", state_dict)
return f"state['{key}'] written"
async def read_state(ctx: Context, key: str) -> Any:
state_dict = await ctx.get("state")
return state_dict.get(key, "")
write_state_tool = FunctionTool.from_defaults(
fn=write_state,
name="write_state",
description="Store or overwrite a value in the shared workflow state.",
)
read_state_tool = FunctionTool.from_defaults(
fn=read_state,
name="read_state",
description="Retrieve a value from the shared workflow state.",
)
# -----------------------------------------------------------------------------
# Fresh implementation of answer_question -------------------------------------
# -----------------------------------------------------------------------------
def answer_question(question: str) -> str:
"""Return chain‑of‑thought and FINAL ANSWER following strict template."""
gemini_api_key = os.getenv("GEMINI_API_KEY")
if not gemini_api_key:
logging.warning("GEMINI_API_KEY not set – returning fallback answer.")
return f"Chain of thought: (api key missing)\n\nFINAL ANSWER: {question}"
meta_prompt = (
"You are a professional assistant. Respond with two sections:"\
"\n1. Chain of thought: concise reasoning (3–5 sentences)."\
"\n2. FINAL ANSWER: the concise answer following these rules:"\
"\n • If numeric, no thousands separators or units unless requested."\
"\n • If text, as few words as possible, no unnecessary articles."\
"\n • If list, comma‑separate applying the above rules."\
"\n • Must start exactly with 'FINAL ANSWER:' (uppercase)."\
f"\n\nQuestion: {question}\n\nAnswer:"
)
llm = GoogleGenAI(api_key=gemini_api_key, model="gemini-2.5-pro-preview-03-25", temperature=0.05)
return llm.complete(meta_prompt).text.strip()
answer_question_tool = FunctionTool.from_defaults(
fn=answer_question,
name="answer_question",
description="Generate reasoning and emit 'FINAL ANSWER: ...' following the strict format rules.",
)
# -----------------------------------------------------------------------------
# System prompt (unchanged) ----------------------------------------------------
# -----------------------------------------------------------------------------
SYNTHESIS_SYSTEM_PROMPT = r"""
You are SynthesisAgent, the final composer in a multi‑agent workflow.
Your goal is to merge validated outputs from specialised agents into a concise
user‑facing answer.
POTENTIAL STATE KEYS TO CONSULT
--------------------------------
objective – str (restated user goal)
plan – dict (PlannerAgent JSON plan)
evidence – list[str] (ResearchAgent facts)
calculations – list[dict] (MathAgent results)
code_outputs – list[dict] (CodeAgent execution)
image_analysis – list[dict] (ImageAnalyzerAgent)
figure_interpretation – list[dict] (FigureInterpretationAgent)
video_analysis – list[dict] (VideoAnalyzerAgent)
text_analysis – list[dict] (TextAnalyzerAgent)
role_draft – str (RoleAgent draft, optional)
reasoning – list[str] (ReasoningAgent chain‑of‑thought)
validation – list[dict] (AdvancedValidationAgent)
WORKFLOW
--------
1. Read every relevant key. Create a short internal outline.
2. If contradictions or missing evidence exist, hand off to
advanced_validation_agent or research_agent.
3. Draft a clear, well‑structured answer (<= 200 words or 7 bullet points).
4. Call the tool `answer_question` with the **user question** to format the
final output as required.
STYLE
-----
* Formal but approachable language; no internal state leakage.
* Cite numeric values plainly; no inline URLs.
* Prefer paragraph then bullets for details.
HANDOFF POLICY
--------------
Allowed targets when more work required:
• advanced_validation_agent – contradictions or doubt
• research_agent – missing data
• reasoning_agent – reconcile complex logic
• long_context_management_agent – compress oversized context before answer
If your response exceeds the maximum token limit and cannot be completed in a single reply, please conclude your output with the marker [CONTINUE]. In subsequent interactions, I will prompt you with “continue” to receive the next portion of the response.
"""
# -----------------------------------------------------------------------------
# Factory ---------------------------------------------------------------------
# -----------------------------------------------------------------------------
def initialize_synthesis_agent() -> ReActAgent:
logger = logging.getLogger(__name__)
logger.info("Initialising SynthesisAgent …")
gemini_api_key = os.getenv("GEMINI_API_KEY")
if not gemini_api_key:
raise ValueError("GEMINI_API_KEY required for SynthesisAgent")
llm = GoogleGenAI(api_key=gemini_api_key, model="gemini-2.5-pro-preview-03-25", temperature=0.05)
agent = ReActAgent(
name="synthesis_agent",
description=(
"Aggregates all validated information, resolves residual issues and "
"produces the final user answer via answer_question, adhering to the "
"required template."),
tools=[write_state_tool, read_state_tool, answer_question_tool],
llm=llm,
system_prompt=SYNTHESIS_SYSTEM_PROMPT,
can_handoff_to=[
"advanced_validation_agent",
"research_agent",
"reasoning_agent",
"long_context_management_agent",
],
)
return agent
# -----------------------------------------------------------------------------
# Stand‑alone test ------------------------------------------------------------
# -----------------------------------------------------------------------------
if __name__ == "__main__":
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
ag = initialize_synthesis_agent()
print("SynthesisAgent ready.")