Delanoe Pirard commited on
Commit
6caec8d
·
1 Parent(s): a23082c

Agent Improvment

Browse files
Files changed (36) hide show
  1. .env +3 -1
  2. __pycache__/app.cpython-311.pyc +0 -0
  3. agents/__pycache__/__init__.cpython-311.pyc +0 -0
  4. agents/__pycache__/advanced_validation_agent.cpython-311.pyc +0 -0
  5. agents/__pycache__/code_agent.cpython-311.pyc +0 -0
  6. agents/__pycache__/figure_interpretation_agent.cpython-311.pyc +0 -0
  7. agents/__pycache__/image_analyzer_agent.cpython-311.pyc +0 -0
  8. agents/__pycache__/long_context_management_agent.cpython-311.pyc +0 -0
  9. agents/__pycache__/math_agent.cpython-311.pyc +0 -0
  10. agents/__pycache__/planner_agent.cpython-311.pyc +0 -0
  11. agents/__pycache__/reasoning_agent.cpython-311.pyc +0 -0
  12. agents/__pycache__/research_agent.cpython-311.pyc +0 -0
  13. agents/__pycache__/role_agent.cpython-311.pyc +0 -0
  14. agents/__pycache__/text_analyzer_agent.cpython-311.pyc +0 -0
  15. agents/__pycache__/verifier_agent.cpython-311.pyc +0 -0
  16. agents/__pycache__/video_analyzer_agent.cpython-311.pyc +0 -0
  17. agents/advanced_validation_agent.py +0 -1
  18. agents/agent_types.py +32 -0
  19. agents/code_agent.py +31 -3
  20. agents/image_analyzer_agent.py +1 -1
  21. agents/long_context_management_agent.py +5 -2
  22. agents/math_agent.py +23 -2
  23. agents/planner_agent.py +76 -11
  24. agents/reasoning_agent.py +70 -6
  25. agents/research_agent.py +91 -153
  26. agents/router.py +25 -0
  27. agents/text_analyzer_agent.py +1 -1
  28. agents/verifier_agent.py +1 -1
  29. agents/video_analyzer_agent.py +336 -0
  30. app.py +112 -89
  31. prompts/code_gen_prompt.txt +44 -3
  32. prompts/planner_agent_prompt.txt +31 -26
  33. prompts/reasoning_agent_prompt.txt +19 -9
  34. prompts/video_analyzer_prompt.txt +85 -0
  35. pyproject.toml +17 -1
  36. uv.lock +0 -0
.env CHANGED
@@ -6,11 +6,13 @@ GOOGLE_API_KEY="AIzaSyACcl4uzlyqz4glW-_uCj0xGPSSH0uloAY" # For Google Custom Sea
6
  GOOGLE_CSE_ID="004c6b8673f0c4dd5" # For Google Custom Search Engine ID
7
  TAVILY_API_KEY="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" # For Tavily Search API
8
  ALPAFLOW_OPENAI_API_KEY="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" # For o4-mini model (or other OpenAI compatible endpoint)
9
- WOLFRAM_ALPHA_APP_ID="YOUR_WOLFRAM_ALPHA_APP_ID" # For WolframAlpha API
10
 
11
  # GAIA Benchmark API
12
  GAIA_API_URL="https://agents-course-unit4-scoring.hf.space"
13
 
 
 
14
  # Model Names (using defaults from original code, can be overridden)
15
  ROLE_EMBED_MODEL="Snowflake/snowflake-arctic-embed-l-v2.0"
16
  ROLE_RERANKER_MODEL="Alibaba-NLP/gte-multilingual-reranker-base"
 
6
  GOOGLE_CSE_ID="004c6b8673f0c4dd5" # For Google Custom Search Engine ID
7
  TAVILY_API_KEY="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" # For Tavily Search API
8
  ALPAFLOW_OPENAI_API_KEY="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" # For o4-mini model (or other OpenAI compatible endpoint)
9
+ WOLFRAM_ALPHA_APP_ID="Y7YG2L-TEU4RGXRVG" # For WolframAlpha API
10
 
11
  # GAIA Benchmark API
12
  GAIA_API_URL="https://agents-course-unit4-scoring.hf.space"
13
 
14
+ LLM_MODEL="models/gemini-1.5-pro"
15
+
16
  # Model Names (using defaults from original code, can be overridden)
17
  ROLE_EMBED_MODEL="Snowflake/snowflake-arctic-embed-l-v2.0"
18
  ROLE_RERANKER_MODEL="Alibaba-NLP/gte-multilingual-reranker-base"
__pycache__/app.cpython-311.pyc ADDED
Binary file (28 kB). View file
 
agents/__pycache__/__init__.cpython-311.pyc CHANGED
Binary files a/agents/__pycache__/__init__.cpython-311.pyc and b/agents/__pycache__/__init__.cpython-311.pyc differ
 
agents/__pycache__/advanced_validation_agent.cpython-311.pyc CHANGED
Binary files a/agents/__pycache__/advanced_validation_agent.cpython-311.pyc and b/agents/__pycache__/advanced_validation_agent.cpython-311.pyc differ
 
agents/__pycache__/code_agent.cpython-311.pyc CHANGED
Binary files a/agents/__pycache__/code_agent.cpython-311.pyc and b/agents/__pycache__/code_agent.cpython-311.pyc differ
 
agents/__pycache__/figure_interpretation_agent.cpython-311.pyc CHANGED
Binary files a/agents/__pycache__/figure_interpretation_agent.cpython-311.pyc and b/agents/__pycache__/figure_interpretation_agent.cpython-311.pyc differ
 
agents/__pycache__/image_analyzer_agent.cpython-311.pyc CHANGED
Binary files a/agents/__pycache__/image_analyzer_agent.cpython-311.pyc and b/agents/__pycache__/image_analyzer_agent.cpython-311.pyc differ
 
agents/__pycache__/long_context_management_agent.cpython-311.pyc CHANGED
Binary files a/agents/__pycache__/long_context_management_agent.cpython-311.pyc and b/agents/__pycache__/long_context_management_agent.cpython-311.pyc differ
 
agents/__pycache__/math_agent.cpython-311.pyc CHANGED
Binary files a/agents/__pycache__/math_agent.cpython-311.pyc and b/agents/__pycache__/math_agent.cpython-311.pyc differ
 
agents/__pycache__/planner_agent.cpython-311.pyc CHANGED
Binary files a/agents/__pycache__/planner_agent.cpython-311.pyc and b/agents/__pycache__/planner_agent.cpython-311.pyc differ
 
agents/__pycache__/reasoning_agent.cpython-311.pyc CHANGED
Binary files a/agents/__pycache__/reasoning_agent.cpython-311.pyc and b/agents/__pycache__/reasoning_agent.cpython-311.pyc differ
 
agents/__pycache__/research_agent.cpython-311.pyc CHANGED
Binary files a/agents/__pycache__/research_agent.cpython-311.pyc and b/agents/__pycache__/research_agent.cpython-311.pyc differ
 
agents/__pycache__/role_agent.cpython-311.pyc CHANGED
Binary files a/agents/__pycache__/role_agent.cpython-311.pyc and b/agents/__pycache__/role_agent.cpython-311.pyc differ
 
agents/__pycache__/text_analyzer_agent.cpython-311.pyc CHANGED
Binary files a/agents/__pycache__/text_analyzer_agent.cpython-311.pyc and b/agents/__pycache__/text_analyzer_agent.cpython-311.pyc differ
 
agents/__pycache__/verifier_agent.cpython-311.pyc CHANGED
Binary files a/agents/__pycache__/verifier_agent.cpython-311.pyc and b/agents/__pycache__/verifier_agent.cpython-311.pyc differ
 
agents/__pycache__/video_analyzer_agent.cpython-311.pyc ADDED
Binary file (17 kB). View file
 
agents/advanced_validation_agent.py CHANGED
@@ -347,7 +347,6 @@ def initialize_advanced_validation_agent() -> ReActAgent:
347
  llm=llm,
348
  system_prompt=system_prompt,
349
  can_handoff_to=valid_handoffs,
350
- verbose=True # Enable verbose logging
351
  )
352
  logger.info("AdvancedValidationAgent initialized successfully.")
353
  return agent
 
347
  llm=llm,
348
  system_prompt=system_prompt,
349
  can_handoff_to=valid_handoffs,
 
350
  )
351
  logger.info("AdvancedValidationAgent initialized successfully.")
352
  return agent
agents/agent_types.py ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # agent_types.py
2
+ from enum import Enum
3
+ from datetime import datetime
4
+ from typing import Any, Dict
5
+ from pydantic import BaseModel
6
+ import uuid
7
+
8
+ class AgentRole(str, Enum):
9
+ USER = "user"
10
+ PLANNER = "planner"
11
+ ROUTER = "router"
12
+ # tu complèteras plus tard (research, reasoning, …)
13
+
14
+ class AgentMessage(BaseModel):
15
+ id: str
16
+ role: AgentRole
17
+ parent_id: str | None
18
+ task: str # phrase courte qui décrit l’action
19
+ payload: Dict[str, Any] # données brutes (texte, url, …)
20
+ metadata: Dict[str, Any] = {}
21
+ status: str = "created" # created / done / error …
22
+ timestamp: str = datetime.utcnow().isoformat()
23
+
24
+ def new_message(role: AgentRole, task: str, payload: Dict[str, Any] | str = "",
25
+ parent_id: str | None = None) -> AgentMessage:
26
+ return AgentMessage(
27
+ id=str(uuid.uuid4()),
28
+ role=role,
29
+ parent_id=parent_id,
30
+ task=task,
31
+ payload=payload if isinstance(payload, dict) else {"text": payload},
32
+ )
agents/code_agent.py CHANGED
@@ -145,14 +145,42 @@ def initialize_code_agent() -> ReActAgent:
145
  6. **Final Output**: Once the code works correctly and achieves the goal, output *only* the final functional code or the final execution result, as appropriate for the task.
146
  7. **Hand-Off**: If further logical reasoning or verification is needed, delegate to **reasoning_agent**. Otherwise, pass your final output to **planner_agent** for synthesis.
147
  """
148
- # system_prompt = load_prompt_from_file("code_agent_system_prompt.txt", default_system_prompt)
149
- system_prompt = default_system_prompt # Using inline for now
150
 
151
  agent = ReActAgent(
152
  name="code_agent",
153
  description=(
154
  "Generates Python code using `python_code_generator` and executes it safely using `code_interpreter`. "
155
- "Iteratively debugs and refines code based on execution results."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
156
  ),
157
  # REMOVED: code_execute_fn - Execution is handled by the code_interpreter tool via the agent loop.
158
  tools=[
 
145
  6. **Final Output**: Once the code works correctly and achieves the goal, output *only* the final functional code or the final execution result, as appropriate for the task.
146
  7. **Hand-Off**: If further logical reasoning or verification is needed, delegate to **reasoning_agent**. Otherwise, pass your final output to **planner_agent** for synthesis.
147
  """
148
+ system_prompt = load_prompt_from_file("code_agent_system_prompt.txt", default_system_prompt)
 
149
 
150
  agent = ReActAgent(
151
  name="code_agent",
152
  description=(
153
  "Generates Python code using `python_code_generator` and executes it safely using `code_interpreter`. "
154
+ "Iteratively debugs and refines code based on execution results. "
155
+ "The agent has access to the following Python packages:\n"
156
+ "- beautifulsoup4>=4.13.4\n"
157
+ "- certifi>=2025.4.26\n"
158
+ "- datasets>=3.5.1\n"
159
+ "- dotenv>=0.9.9\n"
160
+ "- duckdb>=1.2.2\n"
161
+ "- ffmpeg-python>=0.2.0\n"
162
+ "- gradio[oauth]>=5.28.0\n"
163
+ "- helium>=5.1.1\n"
164
+ "- huggingface>=0.0.1\n"
165
+ "- imageio>=2.37.0\n"
166
+ "- matplotlib>=3.10.1\n"
167
+ "- numpy>=2.2.5\n"
168
+ "- openai-whisper>=20240930\n"
169
+ "- opencv-python>=4.11.0.86\n"
170
+ "- openpyxl>=3.1.5\n"
171
+ "- pandas>=2.2.3\n"
172
+ "- pyarrow>=20.0.0\n"
173
+ "- pygame>=2.6.1\n"
174
+ "- python-chess>=1.999\n"
175
+ "- requests>=2.32.3\n"
176
+ "- scikit-learn>=1.6.1\n"
177
+ "- scipy>=1.15.2\n"
178
+ "- seaborn>=0.13.2\n"
179
+ "- sqlalchemy>=2.0.40\n"
180
+ "- statsmodels>=0.14.4\n"
181
+ "- sympy>=1.14.0\n"
182
+ "- youtube-transcript-api>=1.0.3\n"
183
+ "- yt-dlp>=2025.3.31"
184
  ),
185
  # REMOVED: code_execute_fn - Execution is handled by the code_interpreter tool via the agent loop.
186
  tools=[
agents/image_analyzer_agent.py CHANGED
@@ -69,7 +69,7 @@ def initialize_image_analyzer_agent() -> FunctionAgent:
69
  system_prompt=system_prompt,
70
  # No explicit tools needed if relying on direct multimodal LLM call
71
  # tools=[],
72
- can_handoff_to=["planner_agent", "research_agent", "reasoning_agent"],
73
  )
74
  logger.info("ImageAnalyzerAgent initialized successfully.")
75
  return agent
 
69
  system_prompt=system_prompt,
70
  # No explicit tools needed if relying on direct multimodal LLM call
71
  # tools=[],
72
+ can_handoff_to=["planner_agent", "research_agent", "reasoning_agent", "figure_interpretation_agent"],
73
  )
74
  logger.info("ImageAnalyzerAgent initialized successfully.")
75
  return agent
agents/long_context_management_agent.py CHANGED
@@ -348,8 +348,11 @@ def initialize_long_context_management_agent() -> ReActAgent:
348
  agent = ReActAgent(
349
  name="long_context_management_agent",
350
  description=(
351
- "Manages and processes long textual context. Can load text (`load_text_context`), summarize (`summarize_long_context`), "
352
- "extract key info (`extract_key_information`), filter by relevance (`filter_by_relevance`), and answer questions based on the context (`query_context_index`)."
 
 
 
353
  ),
354
  tools=tools,
355
  llm=llm,
 
348
  agent = ReActAgent(
349
  name="long_context_management_agent",
350
  description=(
351
+ "Manages and processes long textual context efficiently. Handles large documents, transcripts, or datasets "
352
+ "by summarizing (`summarize_long_context`), extracting key information (`extract_key_information`), "
353
+ "filtering relevant content (`filter_by_relevance`), and answering questions based on the context (`query_context_index`). "
354
+ "Supports internal indexing for efficient retrieval and repeated queries. Optimized for chunked input processing "
355
+ "and contextual distillation. Only relies on the provided input and avoids external augmentation unless explicitly requested."
356
  ),
357
  tools=tools,
358
  llm=llm,
agents/math_agent.py CHANGED
@@ -7,6 +7,7 @@ import sympy as sp
7
  import numpy as np
8
  import scipy.linalg as la
9
  import scipy.special as special
 
10
  from scipy.integrate import odeint, quad
11
  from scipy.stats import binom, norm, poisson
12
  import numpy.fft as fft
@@ -603,6 +604,26 @@ def get_wolfram_alpha_tools() -> List[FunctionTool]:
603
  _wolfram_alpha_tools = []
604
  return _wolfram_alpha_tools
605
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
606
  # --- Agent Initialization ---
607
 
608
  def initialize_math_agent() -> ReActAgent:
@@ -625,7 +646,7 @@ def initialize_math_agent() -> ReActAgent:
625
  logger.info(f"Using agent LLM: {agent_llm_model}")
626
 
627
  # Combine Python tools and Wolfram Alpha tools
628
- all_tools = get_python_math_tools() + get_wolfram_alpha_tools()
629
  if not all_tools:
630
  logger.warning("No math tools available (Python or WolframAlpha). MathAgent may be ineffective.")
631
 
@@ -661,7 +682,7 @@ def initialize_math_agent() -> ReActAgent:
661
  tools=all_tools,
662
  llm=llm,
663
  system_prompt=system_prompt,
664
- can_handoff_to=["planner_agent"],
665
  )
666
  logger.info("MathAgent initialized successfully.")
667
  return agent
 
7
  import numpy as np
8
  import scipy.linalg as la
9
  import scipy.special as special
10
+ from llama_index.tools.code_interpreter import CodeInterpreterToolSpec
11
  from scipy.integrate import odeint, quad
12
  from scipy.stats import binom, norm, poisson
13
  import numpy.fft as fft
 
604
  _wolfram_alpha_tools = []
605
  return _wolfram_alpha_tools
606
 
607
+
608
+ # Use LlamaIndex's built-in Code Interpreter Tool Spec for safe execution
609
+ # This assumes the necessary environment (e.g., docker) for the spec is available
610
+ try:
611
+ code_interpreter_spec = CodeInterpreterToolSpec()
612
+ # Get the tool(s) from the spec. It might return multiple tools.
613
+ code_interpreter_tools = code_interpreter_spec.to_tool_list()
614
+ if not code_interpreter_tools:
615
+ raise RuntimeError("CodeInterpreterToolSpec did not return any tools.")
616
+ # Assuming the primary tool is the first one, or find by name if necessary
617
+ code_interpreter_tool = next((t for t in code_interpreter_tools if t.metadata.name == "code_interpreter"), None)
618
+ if code_interpreter_tool is None:
619
+ raise RuntimeError("Could not find 'code_interpreter' tool in CodeInterpreterToolSpec results.")
620
+ logger.info("CodeInterpreterToolSpec initialized successfully.")
621
+ except Exception as e:
622
+ logger.error(f"Failed to initialize CodeInterpreterToolSpec: {e}", exc_info=True)
623
+ # Fallback: Define a dummy tool or raise error to prevent agent start?
624
+ # For now, let initialization fail if the safe interpreter isn't available.
625
+ raise RuntimeError("CodeInterpreterToolSpec failed to initialize. Cannot create code_agent.") from e
626
+
627
  # --- Agent Initialization ---
628
 
629
  def initialize_math_agent() -> ReActAgent:
 
646
  logger.info(f"Using agent LLM: {agent_llm_model}")
647
 
648
  # Combine Python tools and Wolfram Alpha tools
649
+ all_tools = get_python_math_tools() + get_wolfram_alpha_tools() + [code_interpreter_tool]
650
  if not all_tools:
651
  logger.warning("No math tools available (Python or WolframAlpha). MathAgent may be ineffective.")
652
 
 
682
  tools=all_tools,
683
  llm=llm,
684
  system_prompt=system_prompt,
685
+ can_handoff_to=["planner_agent", "reasoning_agent"],
686
  )
687
  logger.info("MathAgent initialized successfully.")
688
  return agent
agents/planner_agent.py CHANGED
@@ -48,7 +48,7 @@ def plan(objective: str) -> List[str]:
48
  gemini_api_key = os.getenv("GEMINI_API_KEY")
49
  if not gemini_api_key:
50
  logger.error("GEMINI_API_KEY not found for planning tool LLM.")
51
- return ["Error: GEMINI_API_KEY not set for planning."]
52
 
53
  # Prompt for the LLM to generate sub-steps
54
  input_prompt = (
@@ -84,22 +84,23 @@ def plan(objective: str) -> List[str]:
84
 
85
  if not sub_steps:
86
  logger.warning("LLM generated no sub-steps for the objective.")
87
- return ["Error: Failed to generate sub-steps."]
88
 
89
  logger.info(f"Generated {len(sub_steps)} sub-steps.")
 
90
  return sub_steps
91
 
92
  except Exception as e:
93
  logger.error(f"LLM call failed during planning: {e}", exc_info=True)
94
- return [f"Error during planning: {e}"]
95
 
96
- def synthesize_and_respond(results: List[Dict[str, str]]) -> str:
97
  """
98
  Aggregate results from sub-steps into a coherent final report using an LLM.
99
  Args:
100
  results (List[Dict[str, str]]): List of dictionaries, each with "sub_step" and "answer" keys.
101
  Returns:
102
- str: A unified, well-structured response, or an error message.
103
  """
104
  logger.info(f"Synthesizing results from {len(results)} sub-steps...")
105
  if not results:
@@ -121,7 +122,9 @@ def synthesize_and_respond(results: List[Dict[str, str]]) -> str:
121
  return "Error: GEMINI_API_KEY not set for synthesis."
122
 
123
  # Prompt for the LLM
124
- input_prompt = f"""You are an expert synthesizer. Given the following sub-steps and their answers derived from an initial objective, produce a single, coherent, comprehensive final report that addresses the original objective:
 
 
125
 
126
  --- SUB-STEP RESULTS ---
127
  {summary_blocks.strip()}
@@ -140,10 +143,59 @@ def synthesize_and_respond(results: List[Dict[str, str]]) -> str:
140
  logger.error(f"LLM call failed during synthesis: {e}", exc_info=True)
141
  return f"Error during synthesis: {e}"
142
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
143
  # --- Tool Definitions ---
144
  synthesize_tool = FunctionTool.from_defaults(
145
- fn=synthesize_and_respond,
146
- name="synthesize_and_respond",
147
  description=(
148
  "Aggregates results from multiple sub-steps into a final coherent report. "
149
  "Input: results (List[Dict[str, str]]) where each dict has \"sub_step\" and \"answer\". "
@@ -160,6 +212,15 @@ generate_substeps_tool = FunctionTool.from_defaults(
160
  )
161
  )
162
 
 
 
 
 
 
 
 
 
 
163
  # --- Agent Initialization ---
164
  def initialize_planner_agent() -> ReActAgent:
165
  """Initializes the Planner Agent."""
@@ -185,7 +246,7 @@ def initialize_planner_agent() -> ReActAgent:
185
  logger.warning("Using default/fallback system prompt for PlannerAgent.")
186
 
187
  # Define available tools
188
- tools = [generate_substeps_tool, synthesize_tool]
189
 
190
  # Define valid handoff targets
191
  valid_handoffs = [
@@ -196,7 +257,11 @@ def initialize_planner_agent() -> ReActAgent:
196
  "image_analyzer_agent",
197
  "text_analyzer_agent",
198
  "verifier_agent",
199
- "reasoning_agent"
 
 
 
 
200
  ]
201
 
202
  agent = ReActAgent(
@@ -204,7 +269,7 @@ def initialize_planner_agent() -> ReActAgent:
204
  description=(
205
  "Strategically plans tasks by breaking down objectives into sub-steps using `generate_substeps`. "
206
  "Orchestrates execution by handing off sub-steps to specialized agents. "
207
- "Synthesizes final results using `synthesize_and_respond`."
208
  ),
209
  tools=tools,
210
  llm=llm,
 
48
  gemini_api_key = os.getenv("GEMINI_API_KEY")
49
  if not gemini_api_key:
50
  logger.error("GEMINI_API_KEY not found for planning tool LLM.")
51
+ return "Error: GEMINI_API_KEY not set for planning."
52
 
53
  # Prompt for the LLM to generate sub-steps
54
  input_prompt = (
 
84
 
85
  if not sub_steps:
86
  logger.warning("LLM generated no sub-steps for the objective.")
87
+ return "Error: Failed to generate sub-steps."
88
 
89
  logger.info(f"Generated {len(sub_steps)} sub-steps.")
90
+
91
  return sub_steps
92
 
93
  except Exception as e:
94
  logger.error(f"LLM call failed during planning: {e}", exc_info=True)
95
+ return f"Error during planning: {e}"
96
 
97
+ def synthesize_and_report(results: List[Dict[str, str]]) -> str:
98
  """
99
  Aggregate results from sub-steps into a coherent final report using an LLM.
100
  Args:
101
  results (List[Dict[str, str]]): List of dictionaries, each with "sub_step" and "answer" keys.
102
  Returns:
103
+ str: A unified, well-structured report, or an error message.
104
  """
105
  logger.info(f"Synthesizing results from {len(results)} sub-steps...")
106
  if not results:
 
122
  return "Error: GEMINI_API_KEY not set for synthesis."
123
 
124
  # Prompt for the LLM
125
+ input_prompt = f"""You are an expert synthesizer. Given the following sub-steps and their answers derived
126
+ from an initial objective, produce a single, coherent, comprehensive final report that
127
+ addresses the original objective:
128
 
129
  --- SUB-STEP RESULTS ---
130
  {summary_blocks.strip()}
 
143
  logger.error(f"LLM call failed during synthesis: {e}", exc_info=True)
144
  return f"Error during synthesis: {e}"
145
 
146
+ def answer_question(question: str) -> str:
147
+ """
148
+ Answer any question by following this strict format:
149
+ 1. Include your chain of thought (your reasoning steps).
150
+ 2. End your reply with the exact template:
151
+ FINAL ANSWER: [YOUR FINAL ANSWER]
152
+ YOUR FINAL ANSWER must be:
153
+ - A number, or
154
+ - As few words as possible, or
155
+ - A comma-separated list of numbers and/or strings.
156
+ Formatting rules:
157
+ * If asked for a number, do not use commas or units (e.g., $, %), unless explicitly requested.
158
+ * If asked for a string, do not include articles or abbreviations (e.g., city names), and write digits in plain text.
159
+ * If asked for a comma-separated list, apply the above rules to each element.
160
+ This tool should be invoked immediately after completing the final planning sub-step.
161
+ """
162
+ logger.info(f"Answering question: {question[:100]}")
163
+
164
+ gemini_api_key = os.getenv("GEMINI_API_KEY")
165
+ if not gemini_api_key:
166
+ logger.error("GEMINI_API_KEY not set for answer_question tool.")
167
+ return "Error: GEMINI_API_KEY not set."
168
+
169
+ model_name = os.getenv("ANSWER_TOOL_LLM_MODEL", "models/gemini-1.5-pro")
170
+
171
+ # Build the assistant prompt enforcing the required format
172
+ assistant_prompt = (
173
+ "You are a general AI assistant. I will ask you a question. "
174
+ "Report your thoughts, and finish your answer with the following template: "
175
+ "FINAL ANSWER: [YOUR FINAL ANSWER]. "
176
+ "YOUR FINAL ANSWER should be a number OR as few words as possible "
177
+ "OR a comma separated list of numbers and/or strings. "
178
+ "If you are asked for a number, don't use commas for thousands or any units like $ or % unless specified. "
179
+ "If you are asked for a string, omit articles and abbreviations, and write digits in plain text. "
180
+ "If you are asked for a comma separated list, apply these rules to each element.\n\n"
181
+ f"Question: {question}\n"
182
+ "Answer:"
183
+ )
184
+
185
+ try:
186
+ llm = GoogleGenAI(api_key=gemini_api_key, model=model_name)
187
+ logger.info(f"Using answer LLM: {model_name}")
188
+ response = llm.complete(assistant_prompt)
189
+ logger.info("Answer generated successfully.")
190
+ return response.text
191
+ except Exception as e:
192
+ logger.error(f"LLM call failed during answer generation: {e}", exc_info=True)
193
+ return f"Error during answer generation: {e}"
194
+
195
  # --- Tool Definitions ---
196
  synthesize_tool = FunctionTool.from_defaults(
197
+ fn=synthesize_and_report,
198
+ name="synthesize_and_report",
199
  description=(
200
  "Aggregates results from multiple sub-steps into a final coherent report. "
201
  "Input: results (List[Dict[str, str]]) where each dict has \"sub_step\" and \"answer\". "
 
212
  )
213
  )
214
 
215
+ answer_question = FunctionTool.from_defaults(
216
+ fn=answer_question,
217
+ name="answer_question",
218
+ description=(
219
+ "Répond à une question quelconque et renvoie le texte complet, "
220
+ "terminant toujours par « FINAL ANSWER: ... » conformément aux règles."
221
+ ),
222
+ )
223
+
224
  # --- Agent Initialization ---
225
  def initialize_planner_agent() -> ReActAgent:
226
  """Initializes the Planner Agent."""
 
246
  logger.warning("Using default/fallback system prompt for PlannerAgent.")
247
 
248
  # Define available tools
249
+ tools = [generate_substeps_tool, synthesize_tool, answer_question]
250
 
251
  # Define valid handoff targets
252
  valid_handoffs = [
 
257
  "image_analyzer_agent",
258
  "text_analyzer_agent",
259
  "verifier_agent",
260
+ "reasoning_agent",
261
+ "figure_interpretation_agent",
262
+ "long_context_management_agent",
263
+ "advanced_validation_agent",
264
+ "video_analyzer_agent"
265
  ]
266
 
267
  agent = ReActAgent(
 
269
  description=(
270
  "Strategically plans tasks by breaking down objectives into sub-steps using `generate_substeps`. "
271
  "Orchestrates execution by handing off sub-steps to specialized agents. "
272
+ "Synthesizes final results using `synthesize_and_report`."
273
  ),
274
  tools=tools,
275
  llm=llm,
agents/reasoning_agent.py CHANGED
@@ -75,7 +75,9 @@ def reasoning_tool_fn(context: str) -> str:
75
  llm = OpenAI(
76
  model=reasoning_llm_model,
77
  api_key=openai_api_key,
78
- # reasoning_effort="high" # Add if needed and supported by the specific OpenAI integration
 
 
79
  )
80
  logger.info(f"Using reasoning LLM: {reasoning_llm_model}")
81
  response = llm.complete(reasoning_prompt)
@@ -85,6 +87,57 @@ def reasoning_tool_fn(context: str) -> str:
85
  logger.error(f"Error during reasoning tool LLM call: {e}", exc_info=True)
86
  return f"Error during reasoning: {e}"
87
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
88
  # --- Tool Definition ---
89
  reasoning_tool = FunctionTool.from_defaults(
90
  fn=reasoning_tool_fn,
@@ -95,6 +148,15 @@ reasoning_tool = FunctionTool.from_defaults(
95
  ),
96
  )
97
 
 
 
 
 
 
 
 
 
 
98
  # --- Agent Initialization ---
99
  def initialize_reasoning_agent() -> ReActAgent:
100
  """Initializes the Reasoning Agent."""
@@ -122,15 +184,17 @@ def initialize_reasoning_agent() -> ReActAgent:
122
  agent = ReActAgent(
123
  name="reasoning_agent",
124
  description=(
125
- "A pure reasoning agent that uses the `reasoning_tool` for detailed chain-of-thought analysis "
126
- "on the provided context, then hands off the result to the `planner_agent`."
 
 
127
  ),
128
- tools=[reasoning_tool], # Only has access to the reasoning tool
129
  llm=llm,
130
  system_prompt=system_prompt,
131
- can_handoff_to=["planner_agent"],
132
  )
133
- logger.info("ReasoningAgent initialized successfully.")
134
  return agent
135
 
136
  except Exception as e:
 
75
  llm = OpenAI(
76
  model=reasoning_llm_model,
77
  api_key=openai_api_key,
78
+ reasoning_effort="high",
79
+ temperature=0.25,
80
+ max_tokens=16384
81
  )
82
  logger.info(f"Using reasoning LLM: {reasoning_llm_model}")
83
  response = llm.complete(reasoning_prompt)
 
87
  logger.error(f"Error during reasoning tool LLM call: {e}", exc_info=True)
88
  return f"Error during reasoning: {e}"
89
 
90
+
91
+ def answer_question(question: str) -> str:
92
+ """
93
+ Answer any question by following this strict format:
94
+ 1. Include your chain of thought (your reasoning steps).
95
+ 2. End your reply with the exact template:
96
+ FINAL ANSWER: [YOUR FINAL ANSWER]
97
+ YOUR FINAL ANSWER must be:
98
+ - A number, or
99
+ - As few words as possible, or
100
+ - A comma-separated list of numbers and/or strings.
101
+ Formatting rules:
102
+ * If asked for a number, do not use commas or units (e.g., $, %), unless explicitly requested.
103
+ * If asked for a string, do not include articles or abbreviations (e.g., city names), and write digits in plain text.
104
+ * If asked for a comma-separated list, apply the above rules to each element.
105
+ This tool should be invoked immediately after completing the final planning sub-step.
106
+ """
107
+ logger.info(f"Answering question: {question[:100]}")
108
+
109
+ gemini_api_key = os.getenv("GEMINI_API_KEY")
110
+ if not gemini_api_key:
111
+ logger.error("GEMINI_API_KEY not set for answer_question tool.")
112
+ return "Error: GEMINI_API_KEY not set."
113
+
114
+ model_name = os.getenv("ANSWER_TOOL_LLM_MODEL", "models/gemini-1.5-pro")
115
+
116
+ # Build the assistant prompt enforcing the required format
117
+ assistant_prompt = (
118
+ "You are a general AI assistant. I will ask you a question. "
119
+ "Report your thoughts, and finish your answer with the following template: "
120
+ "FINAL ANSWER: [YOUR FINAL ANSWER]. "
121
+ "YOUR FINAL ANSWER should be a number OR as few words as possible "
122
+ "OR a comma separated list of numbers and/or strings. "
123
+ "If you are asked for a number, don't use commas for thousands or any units like $ or % unless specified. "
124
+ "If you are asked for a string, omit articles and abbreviations, and write digits in plain text. "
125
+ "If you are asked for a comma separated list, apply these rules to each element.\n\n"
126
+ f"Question: {question}\n"
127
+ "Answer:"
128
+ )
129
+
130
+ try:
131
+ llm = GoogleGenAI(api_key=gemini_api_key, model=model_name)
132
+ logger.info(f"Using answer LLM: {model_name}")
133
+ response = llm.complete(assistant_prompt)
134
+ logger.info("Answer generated successfully.")
135
+ return response.text
136
+ except Exception as e:
137
+ logger.error(f"LLM call failed during answer generation: {e}", exc_info=True)
138
+ return f"Error during answer generation: {e}"
139
+
140
+
141
  # --- Tool Definition ---
142
  reasoning_tool = FunctionTool.from_defaults(
143
  fn=reasoning_tool_fn,
 
148
  ),
149
  )
150
 
151
+ answer_question = FunctionTool.from_defaults(
152
+ fn=answer_question,
153
+ name="answer_question",
154
+ description=(
155
+ "Use this tool to answer any question, reporting your reasoning steps and ending with 'FINAL ANSWER: ...'. "
156
+ "Invoke this tool immediately after the final sub-step of planning is complete."
157
+ ),
158
+ )
159
+
160
  # --- Agent Initialization ---
161
  def initialize_reasoning_agent() -> ReActAgent:
162
  """Initializes the Reasoning Agent."""
 
184
  agent = ReActAgent(
185
  name="reasoning_agent",
186
  description=(
187
+ "An autonomous reasoning specialist that applies `reasoning_tool` to perform "
188
+ "in-depth chain-of-thought analysis on incoming queries or contexts, "
189
+ "then seamlessly delegates the synthesized insights to `planner_agent` "
190
+ "or `long_context_management_agent` for subsequent task orchestration."
191
  ),
192
+ tools=[reasoning_tool, answer_question],
193
  llm=llm,
194
  system_prompt=system_prompt,
195
+ can_handoff_to=["planner_agent", "long_context_management_agent", "advanced_validation_agent", "code_agent"],
196
  )
197
+
198
  return agent
199
 
200
  except Exception as e:
agents/research_agent.py CHANGED
@@ -27,89 +27,12 @@ except ImportError:
27
  logging.warning("Selenium or Helium not installed. Browser interaction tools will be unavailable.")
28
  SELENIUM_AVAILABLE = False
29
 
30
- # Attempt to import YouTube transcript API
31
- try:
32
- from youtube_transcript_api import YouTubeTranscriptApi, TranscriptsDisabled, NoTranscriptFound
33
- YOUTUBE_TRANSCRIPT_API_AVAILABLE = True
34
- except ImportError:
35
- logging.warning("youtube-transcript-api not installed. YouTube transcript tool will be unavailable.")
36
- YOUTUBE_TRANSCRIPT_API_AVAILABLE = False
37
-
38
  # Load environment variables
39
  load_dotenv()
40
 
41
  # Setup logging
42
  logger = logging.getLogger(__name__)
43
 
44
- # --- Helper function to extract YouTube Video ID ---
45
- def extract_video_id(url: str) -> Optional[str]:
46
- """Extracts the YouTube video ID from various URL formats."""
47
- # Standard watch URL: https://www.youtube.com/watch?v=VIDEO_ID
48
- match = re.search(r'(?:v=|/v/|embed/|youtu\.be/|/shorts/)([A-Za-z0-9_-]+)', url)
49
- if match:
50
- return match.group(1)
51
- return None
52
-
53
- # --- YouTube Transcript Tool ---
54
- def get_youtube_transcript(video_url_or_id: str, languages=None) -> str:
55
- """Fetches the transcript for a YouTube video using its URL or video ID.
56
- Specify preferred languages as a list (e.g., ["en", "es"]).
57
- Returns the transcript text or an error message.
58
- """
59
- if languages is None:
60
- languages = ["en"]
61
- if not YOUTUBE_TRANSCRIPT_API_AVAILABLE:
62
- return "Error: youtube-transcript-api library is required but not installed."
63
-
64
- logger.info(f"Attempting to fetch YouTube transcript for: {video_url_or_id}")
65
- video_id = extract_video_id(video_url_or_id)
66
- if not video_id:
67
- # Assume it might be an ID already if extraction fails
68
- if re.match(r"^[a-zA-Z0-9_\-]+$", video_url_or_id):
69
- video_id = video_url_or_id
70
- logger.info("Input treated as video ID.")
71
- else:
72
- logger.error(f"Could not extract valid YouTube video ID from: {video_url_or_id}")
73
- return f"Error: Invalid YouTube URL or Video ID format: {video_url_or_id}"
74
-
75
- try:
76
- # Fetch available transcripts
77
- api = YouTubeTranscriptApi()
78
- transcript_list = api.list(video_id)
79
-
80
- # Try to find a transcript in the specified languages
81
- transcript = transcript_list.find_transcript(languages)
82
-
83
- # Fetch the actual transcript data (list of dicts)
84
- transcript_data = transcript.fetch()
85
-
86
- # Combine the text parts into a single string
87
- full_transcript = " ".join(snippet.text for snippet in transcript_data)
88
-
89
- full_transcript = " ".join(snippet.text for snippet in transcript_data)
90
- logger.info(f"Successfully fetched transcript for video ID {video_id} in language {transcript.language}.")
91
- return full_transcript
92
-
93
- except TranscriptsDisabled:
94
- logger.warning(f"Transcripts are disabled for video ID: {video_id}")
95
- return f"Error: Transcripts are disabled for this video (ID: {video_id})."
96
- except NoTranscriptFound as e:
97
- logger.warning(f"No transcript found for video ID {video_id} in languages {languages}. Available: {e.available_transcripts}")
98
- # Try fetching any available transcript if specific languages failed
99
- try:
100
- logger.info(f"Attempting to fetch any available transcript for {video_id}")
101
- any_transcript = transcript_list.find_generated_transcript(transcript_list.manually_created_transcripts.keys() or transcript_list.generated_transcripts.keys())
102
- any_transcript_data = any_transcript.fetch()
103
- full_transcript = " ".join([item["text"] for item in any_transcript_data])
104
- logger.info(f"Successfully fetched fallback transcript for video ID {video_id} in language {any_transcript.language}.")
105
- return full_transcript
106
- except Exception as fallback_e:
107
- logger.error(f"Could not find any transcript for video ID {video_id}. Original error: {e}. Fallback error: {fallback_e}")
108
- return f"Error: No transcript found for video ID {video_id} in languages {languages} or any fallback language."
109
- except Exception as e:
110
- logger.error(f"Unexpected error fetching transcript for video ID {video_id}: {e}", exc_info=True)
111
- return f"Error fetching transcript: {e}"
112
-
113
  # --- Browser Interaction Tools (Conditional on Selenium/Helium availability) ---
114
 
115
  # Global browser instance (managed by initializer)
@@ -286,7 +209,55 @@ def close_popups() -> str:
286
  time.sleep(0.5)
287
  return "Sent ESC key press."
288
 
289
- # --- Search Engine & Data Source Tools ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
290
 
291
  # --- Agent Initializer Class ---
292
  class ResearchAgentInitializer:
@@ -296,7 +267,6 @@ class ResearchAgentInitializer:
296
  self.browser_tools = []
297
  self.search_tools = []
298
  self.datasource_tools = []
299
- self.youtube_tool = None # Added for YouTube tool
300
 
301
  # Initialize LLM
302
  self._initialize_llm()
@@ -311,7 +281,15 @@ class ResearchAgentInitializer:
311
  # Initialize Search/Datasource Tools
312
  self._create_search_tools()
313
  self._create_datasource_tools()
314
- self._create_youtube_tool() # Added
 
 
 
 
 
 
 
 
315
 
316
  logger.info("ResearchAgent resources initialized.")
317
 
@@ -366,7 +344,7 @@ class ResearchAgentInitializer:
366
  self.browser_tools = [
367
  FunctionTool.from_defaults(fn=visit, name="visit_url"), # Renamed for clarity
368
  FunctionTool.from_defaults(fn=get_text_by_css, name="get_text_by_css"),
369
- FunctionTool.from_defaults(fn=get_page_html, name="get_page_html"),
370
  FunctionTool.from_defaults(fn=click_element_by_css, name="click_element_by_css"),
371
  FunctionTool.from_defaults(fn=input_text_by_css, name="input_text_by_css"),
372
  FunctionTool.from_defaults(fn=scroll_page, name="scroll_page"),
@@ -444,28 +422,14 @@ class ResearchAgentInitializer:
444
 
445
  logger.info(f"Created {len(self.datasource_tools)} specific data source tools.")
446
 
447
- def _create_youtube_tool(self): # Added method
448
- if YOUTUBE_TRANSCRIPT_API_AVAILABLE:
449
- self.youtube_tool = FunctionTool.from_defaults(
450
- fn=get_youtube_transcript,
451
- name="get_youtube_transcript",
452
- description=(
453
- "(YouTube) Fetches the transcript text for a given YouTube video URL or video ID. "
454
- "Specify preferred languages (e.g., [\"en\", \"es\"]). Returns transcript or error."
455
- )
456
- )
457
- logger.info("Created YouTube transcript tool.")
458
- else:
459
- self.youtube_tool = None
460
- logger.warning("YouTube transcript tool disabled because youtube-transcript-api is not installed.")
461
 
462
  def get_agent(self) -> ReActAgent:
463
  """Creates and returns the configured ReActAgent for research."""
464
  logger.info("Creating ResearchAgent ReActAgent instance...")
465
 
466
  all_tools = self.browser_tools + self.search_tools + self.datasource_tools
467
- if self.youtube_tool: # Add YouTube tool if available
468
- all_tools.append(self.youtube_tool)
469
 
470
  if not all_tools:
471
  logger.warning("No tools available for ResearchAgent. It will likely be unable to function.")
@@ -474,29 +438,43 @@ class ResearchAgentInitializer:
474
  # Updated prompt to include YouTube tool
475
  system_prompt = """\
476
  You are ResearchAgent, an autonomous web research assistant. Your goal is to gather information accurately and efficiently using the available tools.
477
-
478
  Available Tool Categories:
479
  - (Browser): Tools for direct web page interaction (visiting URLs, clicking, scrolling, extracting text/HTML, inputting text).
480
  - (Search): Tools for querying search engines (Google, DuckDuckGo, Tavily).
481
  - (Wikipedia): Tools for searching and loading Wikipedia pages.
482
  - (YahooFinance): Tools for retrieving financial data (balance sheets, income statements, stock info, news).
483
  - (ArXiv): Tool for searching academic papers on ArXiv.
484
- - (YouTube): Tool for fetching video transcripts (`get_youtube_transcript`).
485
-
486
- Workflow:
487
- 1. **Thought**: Analyze the research goal. Break it down if necessary. Choose the *single best tool* for the *next immediate step*. Explain your choice. Consider the information needed and which tool provides it most directly (e.g., use YahooFinance for stock prices, Google/DDG for general web search, Tavily for document search, ArXiv for papers, Wikipedia for encyclopedic info, YouTube for video transcripts, Browser tools for specific website interaction).
488
- 2. **Action**: Call the chosen tool with the correct arguments. Ensure inputs match the tool's requirements (e.g., URL or video ID for YouTube).
 
 
 
 
 
 
 
 
 
 
 
 
 
489
  3. **Observation**: Examine the tool's output. Extract the relevant information. Check for errors.
490
- 4. **Reflect & Iterate**: Does the observation satisfy the immediate goal? Do you have enough information for the overall research task? If not, return to step 1 (Thought) to plan the *next* single step. If a tool failed, consider why and try an alternative tool or approach.
491
- 5. **Synthesize**: Once all necessary information is gathered, synthesize the findings into a coherent answer to the original research goal.
492
- 6. **Hand-Off**: Pass the synthesized findings to the appropriate next agent: **code_agent** (for coding), **math_agent** (for math), **text_analyzer_agent** (for text analysis), **planner_agent** (for planning/synthesis), or **reasoning_agent** (for logic/reasoning).
493
-
494
- Constraints:
 
495
  - Use only one tool per Action step.
496
  - Think step-by-step.
497
  - If using browser tools, start with `visit_url`.
498
- - Be mindful of potential errors and try alternative tools if one fails.
499
- - Synthesize results *before* handing off.
500
  """
501
 
502
  agent = ReActAgent(
@@ -512,6 +490,8 @@ class ResearchAgentInitializer:
512
  "code_agent",
513
  "math_agent",
514
  "text_analyzer_agent", # Added based on original prompt
 
 
515
  "planner_agent",
516
  "reasoning_agent"
517
  ],
@@ -576,47 +556,5 @@ if __name__ == "__main__":
576
  missing_optional = [key for key in optional_keys if not os.getenv(key)]
577
  if missing_optional:
578
  print(f"Warning: Optional environment variable(s) not set: {', '.join(missing_optional)}. Some tools may be unavailable.")
579
-
580
- test_agent = None
581
- try:
582
- # Test YouTube transcript tool directly
583
- if YOUTUBE_TRANSCRIPT_API_AVAILABLE:
584
- print("\nTesting YouTube transcript tool...")
585
- # Example video: "Attention is All You Need" paper explanation
586
- yt_url = "https://www.youtube.com/watch?v=TQQlZhbC5ps"
587
- transcript = get_youtube_transcript(yt_url)
588
- if not transcript.startswith("Error:"):
589
- print(f"Transcript fetched (first 500 chars):\n{transcript[:500]}...")
590
- else:
591
- print(f"YouTube Transcript Fetch Failed: {transcript}")
592
- else:
593
- print("\nSkipping YouTube transcript test as youtube-transcript-api is not available.")
594
-
595
- # Initialize agent AFTER testing standalone functions
596
- test_agent = initialize_research_agent()
597
- print("\nResearch Agent initialized successfully for testing.")
598
-
599
- # Example test (requires browser tools to be available)
600
- # if SELENIUM_AVAILABLE:
601
- # print("\nTesting browser visit...")
602
- # result = test_agent.chat("Visit https://example.com and tell me the main heading text using CSS selector 'h1'")
603
- # print(f"Test query result: {result}")
604
- # else:
605
- # print("\nSkipping browser test as Selenium/Helium are not available.")
606
-
607
- # Example search test (requires GOOGLE keys)
608
- # if os.getenv("GOOGLE_API_KEY") and os.getenv("GOOGLE_CSE_ID"):
609
- # print("\nTesting Google Search...")
610
- # result_search = test_agent.chat("Search for 'LlamaIndex Agent Workflow'")
611
- # print(f"Search test result: {result_search}")
612
- # else:
613
- # print("\nSkipping Google Search test as API keys are not set.")
614
-
615
- except Exception as e:
616
- print(f"Error during testing: {e}")
617
- finally:
618
- # Clean up browser if it was started
619
- if test_agent:
620
- print("\nCleaning up resources...")
621
- cleanup_research_agent_resources()
622
 
 
27
  logging.warning("Selenium or Helium not installed. Browser interaction tools will be unavailable.")
28
  SELENIUM_AVAILABLE = False
29
 
 
 
 
 
 
 
 
 
30
  # Load environment variables
31
  load_dotenv()
32
 
33
  # Setup logging
34
  logger = logging.getLogger(__name__)
35
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
  # --- Browser Interaction Tools (Conditional on Selenium/Helium availability) ---
37
 
38
  # Global browser instance (managed by initializer)
 
209
  time.sleep(0.5)
210
  return "Sent ESC key press."
211
 
212
+ def answer_question(question: str) -> str:
213
+ """
214
+ Answer any question by following this strict format:
215
+ 1. Include your chain of thought (your reasoning steps).
216
+ 2. End your reply with the exact template:
217
+ FINAL ANSWER: [YOUR FINAL ANSWER]
218
+ YOUR FINAL ANSWER must be:
219
+ - A number, or
220
+ - As few words as possible, or
221
+ - A comma-separated list of numbers and/or strings.
222
+ Formatting rules:
223
+ * If asked for a number, do not use commas or units (e.g., $, %), unless explicitly requested.
224
+ * If asked for a string, do not include articles or abbreviations (e.g., city names), and write digits in plain text.
225
+ * If asked for a comma-separated list, apply the above rules to each element.
226
+ This tool should be invoked immediately after completing the final planning sub-step.
227
+ """
228
+ logger.info(f"Answering question: {question[:100]}")
229
+
230
+ gemini_api_key = os.getenv("GEMINI_API_KEY")
231
+ if not gemini_api_key:
232
+ logger.error("GEMINI_API_KEY not set for answer_question tool.")
233
+ return "Error: GEMINI_API_KEY not set."
234
+
235
+ model_name = os.getenv("ANSWER_TOOL_LLM_MODEL", "models/gemini-1.5-pro")
236
+
237
+ # Build the assistant prompt enforcing the required format
238
+ assistant_prompt = (
239
+ "You are a general AI assistant. I will ask you a question. "
240
+ "Report your thoughts, and finish your answer with the following template: "
241
+ "FINAL ANSWER: [YOUR FINAL ANSWER]. "
242
+ "YOUR FINAL ANSWER should be a number OR as few words as possible "
243
+ "OR a comma separated list of numbers and/or strings. "
244
+ "If you are asked for a number, don't use commas for thousands or any units like $ or % unless specified. "
245
+ "If you are asked for a string, omit articles and abbreviations, and write digits in plain text. "
246
+ "If you are asked for a comma separated list, apply these rules to each element.\n\n"
247
+ f"Question: {question}\n"
248
+ "Answer:"
249
+ )
250
+
251
+ try:
252
+ llm = GoogleGenAI(api_key=gemini_api_key, model=model_name)
253
+ logger.info(f"Using answer LLM: {model_name}")
254
+ response = llm.complete(assistant_prompt)
255
+ logger.info("Answer generated successfully.")
256
+ return response.text
257
+ except Exception as e:
258
+ logger.error(f"LLM call failed during answer generation: {e}", exc_info=True)
259
+ return f"Error during answer generation: {e}"
260
+
261
 
262
  # --- Agent Initializer Class ---
263
  class ResearchAgentInitializer:
 
267
  self.browser_tools = []
268
  self.search_tools = []
269
  self.datasource_tools = []
 
270
 
271
  # Initialize LLM
272
  self._initialize_llm()
 
281
  # Initialize Search/Datasource Tools
282
  self._create_search_tools()
283
  self._create_datasource_tools()
284
+
285
+ self.answer_question = FunctionTool.from_defaults(
286
+ fn=answer_question,
287
+ name="answer_question",
288
+ description=(
289
+ "Use this tool to answer any question, reporting your reasoning steps and ending with 'FINAL ANSWER: ...'. "
290
+ "Invoke this tool immediately after the final sub-step of planning is complete."
291
+ ),
292
+ )
293
 
294
  logger.info("ResearchAgent resources initialized.")
295
 
 
344
  self.browser_tools = [
345
  FunctionTool.from_defaults(fn=visit, name="visit_url"), # Renamed for clarity
346
  FunctionTool.from_defaults(fn=get_text_by_css, name="get_text_by_css"),
347
+ # FunctionTool.from_defaults(fn=get_page_html, name="get_page_html"),
348
  FunctionTool.from_defaults(fn=click_element_by_css, name="click_element_by_css"),
349
  FunctionTool.from_defaults(fn=input_text_by_css, name="input_text_by_css"),
350
  FunctionTool.from_defaults(fn=scroll_page, name="scroll_page"),
 
422
 
423
  logger.info(f"Created {len(self.datasource_tools)} specific data source tools.")
424
 
425
+
 
 
 
 
 
 
 
 
 
 
 
 
 
426
 
427
  def get_agent(self) -> ReActAgent:
428
  """Creates and returns the configured ReActAgent for research."""
429
  logger.info("Creating ResearchAgent ReActAgent instance...")
430
 
431
  all_tools = self.browser_tools + self.search_tools + self.datasource_tools
432
+ all_tools.append(self.answer_question)
 
433
 
434
  if not all_tools:
435
  logger.warning("No tools available for ResearchAgent. It will likely be unable to function.")
 
438
  # Updated prompt to include YouTube tool
439
  system_prompt = """\
440
  You are ResearchAgent, an autonomous web research assistant. Your goal is to gather information accurately and efficiently using the available tools.
441
+
442
  Available Tool Categories:
443
  - (Browser): Tools for direct web page interaction (visiting URLs, clicking, scrolling, extracting text/HTML, inputting text).
444
  - (Search): Tools for querying search engines (Google, DuckDuckGo, Tavily).
445
  - (Wikipedia): Tools for searching and loading Wikipedia pages.
446
  - (YahooFinance): Tools for retrieving financial data (balance sheets, income statements, stock info, news).
447
  - (ArXiv): Tool for searching academic papers on ArXiv.
448
+ - (Answer): `answer_question` use this when your research has yielded a definitive result and you need to reply in the strict “FINAL ANSWER” format.
449
+
450
+ **Answer Tool Usage**
451
+ When you know the final answer and no further data is required, invoke `answer_question` with the user’s query. It will return text ending with:
452
+
453
+ FINAL ANSWER: [YOUR FINAL ANSWER]
454
+
455
+ Formatting rules for **YOUR FINAL ANSWER**:
456
+ - A single number, or
457
+ - As few words as possible, or
458
+ - A comma-separated list of numbers and/or strings.
459
+ - If numeric: no thousands separators or units (%, $, etc.) unless explicitly requested.
460
+ - If string: omit articles and abbreviations; write digits in plain text.
461
+ - If a list: apply the above rules to each element.
462
+
463
+ **Workflow:**
464
+ 1. **Thought**: Analyze the research goal. Break it down if necessary. Choose the *single best tool* for the *next immediate step*. Explain your choice.
465
+ 2. **Action**: Call the chosen tool with the correct arguments. Ensure inputs match the tool's requirements.
466
  3. **Observation**: Examine the tool's output. Extract the relevant information. Check for errors.
467
+ 4. **Reflect & Iterate**: Does the observation satisfy the immediate goal? If not, return to step 1. If a tool failed, try an alternative approach.
468
+ 5. **Advanced Validation**: Before delivering any final response, invoke `advanced_validation_agent` with the combined insights from the reasoning and planning phases. If validation fails, pass the feedback back into **planner_agent** to refine the approach and repeat validation.
469
+ 6. **Synthesize**: Once validation is approved, synthesize all gathered information into a coherent answer.
470
+ 7. **Respond**: Invoke `answer_question` to emit the **FINAL ANSWER** according to the strict template rules.
471
+
472
+ **Constraints:**
473
  - Use only one tool per Action step.
474
  - Think step-by-step.
475
  - If using browser tools, start with `visit_url`.
476
+ - Synthesize results *before* handing off or responding.
477
+ - Do not skip any workflow step (reason → action → observation → reflect → validate → synthesize → respond).
478
  """
479
 
480
  agent = ReActAgent(
 
490
  "code_agent",
491
  "math_agent",
492
  "text_analyzer_agent", # Added based on original prompt
493
+ "advanced_validation_agent",
494
+ "long_context_management_agent"
495
  "planner_agent",
496
  "reasoning_agent"
497
  ],
 
556
  missing_optional = [key for key in optional_keys if not os.getenv(key)]
557
  if missing_optional:
558
  print(f"Warning: Optional environment variable(s) not set: {', '.join(missing_optional)}. Some tools may be unavailable.")
559
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
560
 
agents/router.py ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # router.py
2
+ from typing import List
3
+ from agent_types import AgentMessage, AgentRole
4
+ import logging
5
+
6
+ logger = logging.getLogger(__name__)
7
+
8
+ def route(messages: List[AgentMessage]) -> None:
9
+ for msg in messages:
10
+ if msg.role != AgentRole.PLANNER:
11
+ continue # autre type de lettre → plus tard
12
+ logger.info(f"Routing task: {msg.task}")
13
+ # Appel de l’agent existant (il attend un string)
14
+ # answer = research(msg.task)
15
+ # Ici on pourrait créer une nouvelle AgentMessage 'answer', mais on garde ça pour S-2
16
+
17
+
18
+ if __name__ == '__main__':
19
+ from planner_agent import plan
20
+
21
+ objective = "Comparer l’impact écologique des voitures électriques et thermiques."
22
+ plan_msgs = plan(objective)
23
+ assert isinstance(plan_msgs[0].task, str)
24
+ route(plan_msgs)
25
+ print("✅ Premier voyage des AgentMessages réussi !")
agents/text_analyzer_agent.py CHANGED
@@ -325,7 +325,7 @@ def initialize_text_analyzer_agent() -> ReActAgent:
325
  tools=tools,
326
  llm=llm,
327
  system_prompt=system_prompt,
328
- can_handoff_to=["planner_agent", "research_agent", "reasoning_agent"], # Example handoffs
329
  )
330
  logger.info("TextAnalyzerAgent initialized successfully.")
331
  return agent
 
325
  tools=tools,
326
  llm=llm,
327
  system_prompt=system_prompt,
328
+ can_handoff_to=["planner_agent", "research_agent", "reasoning_agent", "verifier_agent", "advanced_validation_agent"], # Example handoffs
329
  )
330
  logger.info("TextAnalyzerAgent initialized successfully.")
331
  return agent
agents/verifier_agent.py CHANGED
@@ -246,7 +246,7 @@ class VerifierInitializer:
246
  ],
247
  llm=self.verifier.agent_llm, # Use the agent LLM from the Verifier instance
248
  system_prompt=system_prompt,
249
- can_handoff_to=["reasoning_agent", "planner_agent"],
250
  )
251
  logger.info("VerifierAgent FunctionAgent instance created.")
252
  return agent
 
246
  ],
247
  llm=self.verifier.agent_llm, # Use the agent LLM from the Verifier instance
248
  system_prompt=system_prompt,
249
+ can_handoff_to=["reasoning_agent", "planner_agent", "advanced_validation_agent"],
250
  )
251
  logger.info("VerifierAgent FunctionAgent instance created.")
252
  return agent
agents/video_analyzer_agent.py ADDED
@@ -0,0 +1,336 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+ import os
5
+ import re
6
+ import shutil
7
+ from pathlib import Path
8
+ from typing import Optional
9
+
10
+ import cv2
11
+ import yt_dlp
12
+ from dotenv import load_dotenv
13
+ from llama_index.core.agent.workflow import FunctionAgent
14
+ from llama_index.core.base.llms.types import TextBlock, ImageBlock, ChatMessage
15
+ from llama_index.core.tools import FunctionTool
16
+ from llama_index.llms.google_genai import GoogleGenAI
17
+ from tqdm import tqdm
18
+ from youtube_transcript_api import YouTubeTranscriptApi, TranscriptsDisabled, NoTranscriptFound
19
+
20
+ # ---------------------------------------------------------------------------
21
+ # Environment setup & logging
22
+ # ---------------------------------------------------------------------------
23
+ load_dotenv()
24
+ logger = logging.getLogger(__name__)
25
+
26
+
27
+ # ---------------------------------------------------------------------------
28
+ # Prompt loader
29
+ # ---------------------------------------------------------------------------
30
+
31
+ def load_prompt_from_file(filename: str = "../prompts/video_analyzer_prompt.txt") -> str:
32
+ """Load the system prompt for video analysis from *filename*.
33
+
34
+ Falls back to a minimal prompt if the file cannot be read.
35
+ """
36
+ script_dir = Path(__file__).parent
37
+ prompt_path = (script_dir / filename).resolve()
38
+
39
+ try:
40
+ with prompt_path.open("r", encoding="utf-8") as fp:
41
+ prompt = fp.read()
42
+ logger.info("Successfully loaded system prompt from %s", prompt_path)
43
+ return prompt
44
+ except FileNotFoundError:
45
+ logger.error(
46
+ "Prompt file %s not found. Using fallback prompt.", prompt_path
47
+ )
48
+ except Exception as exc: # pylint: disable=broad-except
49
+ logger.error(
50
+ "Error loading prompt file %s: %s", prompt_path, exc, exc_info=True
51
+ )
52
+
53
+ # Fallback – keep it extremely short to save tokens
54
+ return (
55
+ "You are a video analyzer. Provide a factual, chronological "
56
+ "description of the video, identify key events, and summarise insights."
57
+ )
58
+
59
+
60
+ def extract_frames(video_path, output_dir, fps=1/2):
61
+ """
62
+ Extract frames from video at specified FPS
63
+ Returns a list of (frame_path, timestamp) tuples
64
+ """
65
+ os.makedirs(output_dir, exist_ok=True)
66
+
67
+ # Open video
68
+ cap = cv2.VideoCapture(video_path)
69
+ if not cap.isOpened():
70
+ print(f"Error: Could not open video {video_path}")
71
+ return [], None
72
+
73
+ # Get video properties
74
+ video_fps = cap.get(cv2.CAP_PROP_FPS)
75
+ frame_count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
76
+ duration = frame_count / video_fps
77
+
78
+ # Calculate frame interval
79
+ interval = int(video_fps / fps)
80
+ if interval < 1:
81
+ interval = 1
82
+
83
+ # Extract frames
84
+ frames = []
85
+ frame_idx = 0
86
+
87
+ with tqdm(total=frame_count, desc="Extracting frames") as pbar:
88
+ while cap.isOpened():
89
+ ret, frame = cap.read()
90
+ if not ret:
91
+ break
92
+
93
+ if frame_idx % interval == 0:
94
+ timestamp = frame_idx / video_fps
95
+ frame_path = os.path.join(output_dir, f"frame_{frame_idx:06d}.jpg")
96
+ cv2.imwrite(frame_path, frame)
97
+ frames.append((frame_path, timestamp))
98
+
99
+ frame_idx += 1
100
+ pbar.update(1)
101
+
102
+ cap.release()
103
+ return frames, duration
104
+
105
+
106
+ def download_video_and_analyze(video_url: str) -> str:
107
+ """Download a video from *video_url* and return the local file path."""
108
+ llm_model_name = os.getenv("VIDEO_ANALYZER_LLM_MODEL", "models/gemini-1.5-pro")
109
+ gemini_api_key = os.getenv("GEMINI_API_KEY")
110
+
111
+ ydl_opts = {
112
+ 'format': 'best',
113
+ 'outtmpl': os.path.join("downloaded_videos", 'temp_video.%(ext)s'),
114
+ }
115
+
116
+ with yt_dlp.YoutubeDL(ydl_opts) as ydl_download:
117
+ ydl_download.download(video_url)
118
+
119
+ print(f"Processing video: {video_url}")
120
+
121
+ # Create temporary directory for frames
122
+ temp_dir = "frame_downloaded_videos"
123
+ os.makedirs(temp_dir, exist_ok=True)
124
+
125
+ # Extract frames
126
+ frames, duration = extract_frames(os.path.join("downloaded_videos", 'temp_video.mp4'), temp_dir)
127
+ if not frames:
128
+ logging.info(f"No frames extracted from {video_url}")
129
+ return f"No frames extracted from {video_url}"
130
+
131
+ blocks = []
132
+ text_block = TextBlock(text=load_prompt_from_file())
133
+ blocks.append(text_block)
134
+
135
+ for frame_path, timestamp in tqdm(frames, desc="Collecting frames"):
136
+ blocks.append(ImageBlock(path=frame_path))
137
+
138
+
139
+ llm = GoogleGenAI(api_key=gemini_api_key, model=llm_model_name)
140
+ logger.info("Using LLM model: %s", llm_model_name)
141
+ response = llm.chat([ChatMessage(role="user", blocks=blocks)])
142
+
143
+ # Clean up temporary files
144
+ shutil.rmtree(temp_dir)
145
+ os.remove(os.path.join("downloaded_videos", 'temp_video.mp4'))
146
+
147
+ return response.message.content
148
+
149
+
150
+ # --- Helper function to extract YouTube Video ID ---
151
+ def extract_video_id(url: str) -> Optional[str]:
152
+ """Extracts the YouTube video ID from various URL formats."""
153
+ # Standard watch URL: https://www.youtube.com/watch?v=VIDEO_ID
154
+ pattern = re.compile(
155
+ r'^(?:https?://)?' # protocole optionnel
156
+ r'(?:www\.)?' # sous-domaine optionnel
157
+ r'youtube\.com/watch\?' # domaine et chemin fixe
158
+ r'(?:.*&)?' # éventuellement d'autres paramètres avant v=
159
+ r'v=([^&]+)' # capture de l'ID (tout jusqu'au prochain & ou fin)
160
+ )
161
+
162
+ match = pattern.search(url)
163
+ if match:
164
+ video_id = match.group(1)
165
+ return video_id # affiche "VIDEO_ID"
166
+ else:
167
+ print("Aucun ID trouvé")
168
+ return None
169
+
170
+
171
+ # --- YouTube Transcript Tool ---
172
+ def get_youtube_transcript(video_url_or_id: str, languages: str | None = None) -> str:
173
+ """Fetches the transcript for a YouTube video using its URL or video ID.
174
+ Specify preferred languages as a list (e.g., ["en", "es"]).
175
+ Returns the transcript text or an error message.
176
+ """
177
+ if languages is None:
178
+ languages = ["en"]
179
+
180
+ logger.info(f"Attempting to fetch YouTube transcript for: {video_url_or_id}")
181
+ video_id = extract_video_id(video_url_or_id)
182
+ if video_id is None or not video_id:
183
+ logger.error(f"Could not extract video ID from: {video_url_or_id}")
184
+ return f"Error: Invalid YouTube URL or Video ID format: {video_url_or_id}"
185
+
186
+ try:
187
+ # Fetch available transcripts
188
+ api = YouTubeTranscriptApi()
189
+ transcript_list = api.list(video_id)
190
+
191
+ # Try to find a transcript in the specified languages
192
+ transcript = transcript_list.find_transcript(languages)
193
+
194
+ # Fetch the actual transcript data (list of dicts)
195
+ transcript_data = transcript.fetch()
196
+
197
+ # Combine the text parts into a single string
198
+ full_transcript = " ".join(snippet.text for snippet in transcript_data)
199
+
200
+ full_transcript = " ".join(snippet.text for snippet in transcript_data)
201
+ logger.info(f"Successfully fetched transcript for video ID {video_id} in language {transcript.language}.")
202
+ return full_transcript
203
+
204
+ except TranscriptsDisabled:
205
+ logger.warning(f"Transcripts are disabled for video ID: {video_id}")
206
+ return f"Error: Transcripts are disabled for this video (ID: {video_id})."
207
+ except NoTranscriptFound as e:
208
+ logger.warning(
209
+ f"No transcript found for video ID {video_id} in languages {languages}. Available: {e.available_transcripts}")
210
+ # Try fetching any available transcript if specific languages failed
211
+ try:
212
+ logger.info(f"Attempting to fetch any available transcript for {video_id}")
213
+ any_transcript = transcript_list.find_generated_transcript(
214
+ transcript_list.manually_created_transcripts.keys() or transcript_list.generated_transcripts.keys())
215
+ any_transcript_data = any_transcript.fetch()
216
+ full_transcript = " ".join([item["text"] for item in any_transcript_data])
217
+ logger.info(
218
+ f"Successfully fetched fallback transcript for video ID {video_id} in language {any_transcript.language}.")
219
+ return full_transcript
220
+ except Exception as fallback_e:
221
+ logger.error(
222
+ f"Could not find any transcript for video ID {video_id}. Original error: {e}. Fallback error: {fallback_e}")
223
+ return f"Error: No transcript found for video ID {video_id} in languages {languages} or any fallback language."
224
+ except Exception as e:
225
+ logger.error(f"Unexpected error fetching transcript for video ID {video_id}: {e}", exc_info=True)
226
+ return f"Error fetching transcript: {e}"
227
+
228
+
229
+ download_video_and_analyze_tool = FunctionTool.from_defaults(
230
+ name="download_video_and_analyze",
231
+ description=(
232
+ "Downloads a video (YouTube or direct URL), samples representative frames, "
233
+ "and feeds them to Gemini for multimodal analysis—returning a rich textual summary "
234
+ "of the visual content."
235
+ ),
236
+ fn=download_video_and_analyze,
237
+ )
238
+
239
+ youtube_transcript_tool = FunctionTool.from_defaults(
240
+ fn=get_youtube_transcript,
241
+ name="get_youtube_transcript",
242
+ description=(
243
+ "(YouTube) Fetches the transcript text for a given YouTube video URL or video ID. "
244
+ "Specify preferred languages (e.g., 'en', 'es'). Returns transcript or error."
245
+ )
246
+ )
247
+
248
+
249
+ # ---------------------------------------------------------------------------
250
+ # Agent factory
251
+ # ---------------------------------------------------------------------------
252
+
253
+ def initialize_video_analyzer_agent() -> FunctionAgent:
254
+ """Initialise and return a *video_analyzer_agent* `FunctionAgent`."""
255
+
256
+ logger.info("Initialising VideoAnalyzerAgent …")
257
+
258
+ llm_model_name = os.getenv("VIDEO_ANALYZER_LLM_MODEL", "models/gemini-1.5-pro")
259
+ gemini_api_key = os.getenv("GEMINI_API_KEY")
260
+
261
+ if not gemini_api_key:
262
+ logger.error("GEMINI_API_KEY not found in environment variables.")
263
+ raise ValueError("GEMINI_API_KEY must be set")
264
+
265
+ try:
266
+ llm = GoogleGenAI(api_key=gemini_api_key, model=llm_model_name)
267
+ logger.info("Using LLM model: %s", llm_model_name)
268
+
269
+ system_prompt = load_prompt_from_file()
270
+
271
+ tools = [download_video_and_analyze_tool, youtube_transcript_tool]
272
+
273
+ agent = FunctionAgent(
274
+ name="video_analyzer_agent",
275
+ description=(
276
+ "VideoAnalyzerAgent inspects video files using Gemini's multimodal "
277
+ "video understanding capabilities, producing factual scene analysis, "
278
+ "temporal segmentation, and concise summaries as guided by the system "
279
+ "prompt."
280
+ ),
281
+ llm=llm,
282
+ system_prompt=system_prompt,
283
+ tools=tools,
284
+ can_handoff_to=[
285
+ "planner_agent",
286
+ "research_agent",
287
+ "reasoning_agent",
288
+ "code_agent",
289
+ ],
290
+ )
291
+
292
+ logger.info("VideoAnalyzerAgent initialised successfully.")
293
+ return agent
294
+
295
+ except Exception as exc: # pylint: disable=broad-except
296
+ logger.error("Error during VideoAnalyzerAgent initialisation: %s", exc, exc_info=True)
297
+ raise
298
+
299
+
300
+ if __name__ == "__main__":
301
+ logging.basicConfig(
302
+ level=logging.INFO,
303
+ format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
304
+ )
305
+
306
+ logger.info("Running video_analyzer_agent.py directly for testing …")
307
+
308
+ if not os.getenv("GEMINI_API_KEY"):
309
+ print("Error: GEMINI_API_KEY environment variable not set. Cannot run test.")
310
+ else:
311
+ try:
312
+ test_agent = initialize_video_analyzer_agent()
313
+ summary = download_video_and_analyze("https://www.youtube.com/watch?v=dQw4w9WgXcQ")
314
+ print("\n--- Gemini summary ---\n")
315
+ print(summary)
316
+ print("Video Analyzer Agent initialised successfully for testing.")
317
+ except Exception as exc:
318
+ print(f"Error during testing: {exc}")
319
+
320
+ test_agent = None
321
+ try:
322
+ # Test YouTube transcript tool directly
323
+ if YOUTUBE_TRANSCRIPT_API_AVAILABLE:
324
+ print("\nTesting YouTube transcript tool...")
325
+ # Example video: "Attention is All You Need" paper explanation
326
+ yt_url = "https://www.youtube.com/watch?v=TQQlZhbC5ps"
327
+ transcript = get_youtube_transcript(yt_url)
328
+ if not transcript.startswith("Error:"):
329
+ print(f"Transcript fetched (first 500 chars):\n{transcript[:500]}...")
330
+ else:
331
+ print(f"YouTube Transcript Fetch Failed: {transcript}")
332
+ else:
333
+ print("\nSkipping YouTube transcript test as youtube-transcript-api is not available.")
334
+
335
+ except Exception as e:
336
+ print(f"Error during testing: {e}")
app.py CHANGED
@@ -1,6 +1,8 @@
1
  import os
2
  import logging
3
  import mimetypes
 
 
4
  from dotenv import load_dotenv
5
 
6
  from typing import Any, List
@@ -11,6 +13,9 @@ import pandas as pd
11
 
12
  from llama_index.core.agent.workflow import AgentWorkflow, ToolCallResult, ToolCall, AgentOutput
13
  from llama_index.core.base.llms.types import ChatMessage, TextBlock, ImageBlock, AudioBlock
 
 
 
14
 
15
  # Assuming agent initializers are in the same directory or a known path
16
  # Adjust import paths if necessary based on deployment structure
@@ -82,12 +87,14 @@ try:
82
  advanced_validation_agent = initialize_advanced_validation_agent()
83
  figure_interpretation_agent = initialize_figure_interpretation_agent()
84
  long_context_management_agent = initialize_long_context_management_agent()
 
85
 
86
  # Check if all agents initialized successfully
87
  all_agents = [
88
  code_agent, role_agent, math_agent, planner_agent, research_agent,
89
  text_analyzer_agent, image_analyzer_agent, verifier_agent, reasoning_agent,
90
- advanced_validation_agent, figure_interpretation_agent, long_context_management_agent
 
91
  ]
92
  if not all(all_agents):
93
  raise RuntimeError("One or more agents failed to initialize.")
@@ -126,7 +133,8 @@ class BasicAgent:
126
  and event.current_agent_name != current_agent
127
  ):
128
  current_agent = event.current_agent_name
129
- logger.info(f"{'=' * 50}\n")
 
130
  logger.info(f"{'=' * 50}\n")
131
 
132
  # Optional detailed logging (uncomment if needed)
@@ -158,6 +166,19 @@ class BasicAgent:
158
  logger.info(f"Agent returning final answer: {final_content[:500]}{'...' if len(final_content) > 500 else ''}")
159
  return answer.response # Return the actual response object expected by Gradio
160
 
 
 
 
 
 
 
 
 
 
 
 
 
 
161
  # --- Helper Functions for run_and_submit_all ---
162
 
163
  async def fetch_questions(questions_url: str) -> List[dict] | None:
@@ -262,28 +283,75 @@ async def process_question(agent: BasicAgent, item: dict, base_fetch_file_url: s
262
  # Extract content safely
263
  submitted_answer = submitted_answer_response.content if hasattr(submitted_answer_response, 'content') else str(submitted_answer_response)
264
 
265
- logger.info(f"👍 Agent submitted answer for task {task_id}: {submitted_answer[:200]}{'...' if len(submitted_answer) > 200 else ''}")
266
- return {"Task ID": task_id, "Question": question_text, "Submitted Answer": submitted_answer}
 
 
 
 
 
 
 
 
 
267
  except Exception as e:
268
  logger.error(f"Error running agent on task {task_id}: {e}", exc_info=True)
269
  return {"Task ID": task_id, "Question": question_text, "Submitted Answer": f"AGENT ERROR: {e}"}
270
 
271
- async def submit_answers(submit_url: str, username: str, agent_code: str, results: List[dict]) -> tuple[str, pd.DataFrame]:
272
- """Submits the collected answers to the GAIA benchmark API."""
273
- answers_payload = [
274
- {"task_id": r["Task ID"], "submitted_answer": r["Submitted Answer"]}
275
- for r in results if "Submitted Answer" in r and not str(r["Submitted Answer"]).startswith("AGENT ERROR:")
276
- ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
277
 
278
  if not answers_payload:
279
- logger.warning("Agent did not produce any valid answers to submit.")
280
- results_df = pd.DataFrame(results)
281
- return "Agent did not produce any valid answers to submit.", results_df
282
 
 
283
  submission_data = {"username": username.strip(), "agent_code": agent_code, "answers": answers_payload}
284
  status_update = f"Agent finished. Submitting {len(answers_payload)} answers for user '{username}'..."
285
- logger.info(status_update)
286
- logger.info(f"Submitting to: {submit_url}")
 
 
287
 
288
  try:
289
  response = requests.post(submit_url, json=submission_data, timeout=120) # Increased timeout
@@ -297,7 +365,7 @@ async def submit_answers(submit_url: str, username: str, agent_code: str, result
297
  f"Message: {result_data.get('message', 'No message received.')}"
298
  )
299
  logger.info("Submission successful.")
300
- results_df = pd.DataFrame(results)
301
  return final_status, results_df
302
  except requests.exceptions.HTTPError as e:
303
  error_detail = f"Server responded with status {e.response.status_code}."
@@ -308,103 +376,58 @@ async def submit_answers(submit_url: str, username: str, agent_code: str, result
308
  error_detail += f" Response: {e.response.text[:500]}"
309
  status_message = f"Submission Failed: {error_detail}"
310
  logger.error(status_message)
311
- results_df = pd.DataFrame(results)
312
  return status_message, results_df
313
  except requests.exceptions.Timeout:
314
  status_message = "Submission Failed: The request timed out."
315
  logger.error(status_message)
316
- results_df = pd.DataFrame(results)
317
  return status_message, results_df
318
  except requests.exceptions.RequestException as e:
319
  status_message = f"Submission Failed: Network error - {e}"
320
  logger.error(status_message)
321
- results_df = pd.DataFrame(results)
322
  return status_message, results_df
323
  except Exception as e:
324
  status_message = f"Submission Failed: An unexpected error occurred during submission - {e}"
325
  logger.error(status_message, exc_info=True)
326
- results_df = pd.DataFrame(results)
327
  return status_message, results_df
328
 
329
- # --- Main Function for Batch Processing ---
330
- async def run_and_submit_all(
331
- username: str,
332
- agent_code: str,
333
- api_url: str = DEFAULT_API_URL,
334
- level: int = 1,
335
- max_questions: int = 0, # 0 means all questions for the level
336
- progress=gr.Progress(track_tqdm=True)
337
- ) -> tuple[str, pd.DataFrame]:
338
- """Fetches all questions for a level, runs the agent, and submits answers."""
339
- if not AGENT_WORKFLOW:
340
- error_msg = "Agent Workflow is not initialized. Cannot run benchmark."
341
- logger.error(error_msg)
342
- return error_msg, pd.DataFrame()
343
-
344
- if not username or not username.strip():
345
- error_msg = "Username cannot be empty."
346
- logger.error(error_msg)
347
- return error_msg, pd.DataFrame()
348
-
349
- questions_url = f"{api_url}/questions?level={level}"
350
- submit_url = f"{api_url}/submit"
351
- base_fetch_file_url = f"{api_url}/get_file"
352
-
353
- questions = await fetch_questions(questions_url)
354
- if questions is None:
355
- error_msg = f"Failed to fetch questions for level {level}. Check logs."
356
- return error_msg, pd.DataFrame()
357
-
358
- # Limit number of questions if max_questions is set
359
- if max_questions > 0:
360
- questions = questions[:max_questions]
361
- logger.info(f"Processing a maximum of {max_questions} questions for level {level}.")
362
- else:
363
- logger.info(f"Processing all {len(questions)} questions for level {level}.")
364
-
365
- agent = BasicAgent(AGENT_WORKFLOW)
366
- results = []
367
- total_questions = len(questions)
368
-
369
- for i, item in enumerate(progress.tqdm(questions, desc=f"Processing Level {level} Questions")):
370
- result = await process_question(agent, item, base_fetch_file_url)
371
- if result:
372
- results.append(result)
373
- # Optional: Add a small delay between questions if needed
374
- # await asyncio.sleep(0.1)
375
-
376
- # Submit answers
377
- final_status, results_df = await submit_answers(submit_url, username, agent_code, results)
378
- return final_status, results_df
379
-
380
  # --- Gradio Interface ---
381
  def create_gradio_interface():
382
  """Creates and returns the Gradio interface."""
383
- logger.info("Creating Gradio interface...")
384
- with gr.Blocks(theme=gr.themes.Soft()) as demo:
385
- gr.Markdown("# GAIA Benchmark Agent Runner")
386
- gr.Markdown("Run the initialized multi-agent system against the GAIA benchmark questions and submit the results.")
 
 
 
 
 
 
 
 
 
 
 
 
 
387
 
388
- with gr.Row():
389
- username = gr.Textbox(label="Username", placeholder="Enter your username (e.g., your_email@example.com)")
390
- agent_code = gr.Textbox(label="Agent Code", placeholder="Enter a short code for your agent (e.g., v1.0)")
391
- with gr.Row():
392
- level = gr.Dropdown(label="Benchmark Level", choices=[1, 2, 3], value=1)
393
- max_questions = gr.Number(label="Max Questions (0 for all)", value=0, minimum=0, step=1)
394
- api_url = gr.Textbox(label="GAIA API URL", value=DEFAULT_API_URL)
395
 
396
- run_button = gr.Button("Run Benchmark and Submit", variant="primary")
397
 
398
- with gr.Accordion("Results", open=False):
399
- status_output = gr.Textbox(label="Submission Status", lines=5)
400
- results_dataframe = gr.DataFrame(label="Detailed Results")
401
 
402
  run_button.click(
403
  fn=run_and_submit_all,
404
- inputs=[username, agent_code, api_url, level, max_questions],
405
- outputs=[status_output, results_dataframe]
406
  )
407
- logger.info("Gradio interface created.")
408
  return demo
409
 
410
  # --- Main Execution ---
 
1
  import os
2
  import logging
3
  import mimetypes
4
+ from cgitb import handler
5
+
6
  from dotenv import load_dotenv
7
 
8
  from typing import Any, List
 
13
 
14
  from llama_index.core.agent.workflow import AgentWorkflow, ToolCallResult, ToolCall, AgentOutput
15
  from llama_index.core.base.llms.types import ChatMessage, TextBlock, ImageBlock, AudioBlock
16
+ from llama_index.llms.openai import OpenAI
17
+
18
+ from agents.video_analyzer_agent import initialize_video_analyzer_agent
19
 
20
  # Assuming agent initializers are in the same directory or a known path
21
  # Adjust import paths if necessary based on deployment structure
 
87
  advanced_validation_agent = initialize_advanced_validation_agent()
88
  figure_interpretation_agent = initialize_figure_interpretation_agent()
89
  long_context_management_agent = initialize_long_context_management_agent()
90
+ video_analyzer_agent = initialize_video_analyzer_agent()
91
 
92
  # Check if all agents initialized successfully
93
  all_agents = [
94
  code_agent, role_agent, math_agent, planner_agent, research_agent,
95
  text_analyzer_agent, image_analyzer_agent, verifier_agent, reasoning_agent,
96
+ advanced_validation_agent, figure_interpretation_agent, long_context_management_agent,
97
+ video_analyzer_agent
98
  ]
99
  if not all(all_agents):
100
  raise RuntimeError("One or more agents failed to initialize.")
 
133
  and event.current_agent_name != current_agent
134
  ):
135
  current_agent = event.current_agent_name
136
+ logger.info(f"{'=' * 50}")
137
+ logger.info(f"🤖 Agent: {current_agent}")
138
  logger.info(f"{'=' * 50}\n")
139
 
140
  # Optional detailed logging (uncomment if needed)
 
166
  logger.info(f"Agent returning final answer: {final_content[:500]}{'...' if len(final_content) > 500 else ''}")
167
  return answer.response # Return the actual response object expected by Gradio
168
 
169
+ system_prompt="""
170
+ You are a general AI assistant.
171
+ I will give you a result, and with it you will have to transform it to follow the following template: FINAL ANSWER: [YOUR FINAL ANSWER].
172
+ YOUR FINAL ANSWER should be a number OR 1 or 2 word(s) OR a comma separated list of numbers and/or strings.
173
+ If you are asked for a number, don't use comma to write your number neither use units such as $ or percent sign unless specified otherwise.
174
+ If you are asked for a string, don't use articles, neither abbreviations (e.g. for cities), and write the digits in plain text unless specified otherwise.
175
+ If you are asked for a comma separated list, apply the above rules depending of whether the element to be put in the list is a number or a string.
176
+ """
177
+
178
+ llm = OpenAI(model="gpt-4o-mini", api_key=os.getenv("OPENAI_API_KEY"), temperature=0.1, system_prompt=system_prompt)
179
+
180
+
181
+
182
  # --- Helper Functions for run_and_submit_all ---
183
 
184
  async def fetch_questions(questions_url: str) -> List[dict] | None:
 
283
  # Extract content safely
284
  submitted_answer = submitted_answer_response.content if hasattr(submitted_answer_response, 'content') else str(submitted_answer_response)
285
 
286
+ prompt = f"""
287
+ QUESTION: {question_text}
288
+ ANSWER: {submitted_answer}
289
+ INSTRUCTIONS: Based on the provided question and answer, generate a final answer that is clear, concise, and directly addresses the question.
290
+ [YOUR FINAL ANSWER]
291
+ """
292
+
293
+ final_answer = llm.complete(prompt)
294
+
295
+ logger.info(f"👍 Agent submitted answer for task {task_id}: {final_answer.text[:200]}{'...' if len(final_answer.text) > 200 else ''}")
296
+ return {"Task ID": task_id, "Question": question_text, "Submitted Answer": final_answer.text}
297
  except Exception as e:
298
  logger.error(f"Error running agent on task {task_id}: {e}", exc_info=True)
299
  return {"Task ID": task_id, "Question": question_text, "Submitted Answer": f"AGENT ERROR: {e}"}
300
 
301
+ async def run_and_submit_all( profile: gr.OAuthProfile | None):
302
+ """
303
+ Fetches all questions, runs the BasicAgent on them, submits all answers,
304
+ and displays the results.
305
+ """
306
+ # --- Determine HF Space Runtime URL and Repo URL ---
307
+ space_id = os.getenv("SPACE_ID") # Get the SPACE_ID for sending link to the code
308
+
309
+ if profile:
310
+ username= f"{profile.username}"
311
+ print(f"User logged in: {username}")
312
+ else:
313
+ print("User not logged in.")
314
+ return "Please Login to Hugging Face with the button.", None
315
+
316
+ api_url = DEFAULT_API_URL
317
+ questions_url = f"{api_url}/questions"
318
+ submit_url = f"{api_url}/submit"
319
+ fetch_file_url = f"{api_url}/files"
320
+
321
+ results_log = []
322
+ answers_payload = []
323
+
324
+ try:
325
+ agent = BasicAgent(AGENT_WORKFLOW)
326
+ except Exception as e:
327
+ print(f"Error instantiating agent: {e}")
328
+ return f"Error initializing agent: {e}", None
329
+ # In the case of an app running as a hugging Face space, this link points toward your codebase ( usefull for others so please keep it public)
330
+ agent_code = f"https://huggingface.co/spaces/{space_id}/tree/main"
331
+ print(agent_code)
332
+
333
+ questions_data = await fetch_questions(questions_url)
334
+ if not questions_data:
335
+ return "Failed to fetch questions.", None
336
+
337
+ # 3. Process Questions
338
+ # questions_data = [questions_data[3], questions_data[6]]
339
+ for item in questions_data:
340
+ answers = await process_question(agent, item, fetch_file_url)
341
+ results_log.append(answers)
342
+ answers_payload.append({"task_id": answers["Task ID"], "submitted_answer": answers["Submitted Answer"]})
343
 
344
  if not answers_payload:
345
+ print("Agent did not produce any answers to submit.")
346
+ return "Agent did not produce any answers to submit.", pd.DataFrame(results_log)
 
347
 
348
+ # 4. Prepare Submission
349
  submission_data = {"username": username.strip(), "agent_code": agent_code, "answers": answers_payload}
350
  status_update = f"Agent finished. Submitting {len(answers_payload)} answers for user '{username}'..."
351
+ print(status_update)
352
+
353
+ # 5. Submit
354
+ print(f"Submitting {len(answers_payload)} answers to: {submit_url}")
355
 
356
  try:
357
  response = requests.post(submit_url, json=submission_data, timeout=120) # Increased timeout
 
365
  f"Message: {result_data.get('message', 'No message received.')}"
366
  )
367
  logger.info("Submission successful.")
368
+ results_df = pd.DataFrame(results_log)
369
  return final_status, results_df
370
  except requests.exceptions.HTTPError as e:
371
  error_detail = f"Server responded with status {e.response.status_code}."
 
376
  error_detail += f" Response: {e.response.text[:500]}"
377
  status_message = f"Submission Failed: {error_detail}"
378
  logger.error(status_message)
379
+ results_df = pd.DataFrame(results_log)
380
  return status_message, results_df
381
  except requests.exceptions.Timeout:
382
  status_message = "Submission Failed: The request timed out."
383
  logger.error(status_message)
384
+ results_df = pd.DataFrame(results_log)
385
  return status_message, results_df
386
  except requests.exceptions.RequestException as e:
387
  status_message = f"Submission Failed: Network error - {e}"
388
  logger.error(status_message)
389
+ results_df = pd.DataFrame(results_log)
390
  return status_message, results_df
391
  except Exception as e:
392
  status_message = f"Submission Failed: An unexpected error occurred during submission - {e}"
393
  logger.error(status_message, exc_info=True)
394
+ results_df = pd.DataFrame(results_log)
395
  return status_message, results_df
396
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
397
  # --- Gradio Interface ---
398
  def create_gradio_interface():
399
  """Creates and returns the Gradio interface."""
400
+ # --- Build Gradio Interface using Blocks ---
401
+ with gr.Blocks() as demo:
402
+ gr.Markdown("# Basic Agent Evaluation Runner")
403
+ gr.Markdown(
404
+ """
405
+ **Instructions:**
406
+
407
+ 1. Please clone this space, then modify the code to define your agent's logic, the tools, the necessary packages, etc ...
408
+ 2. Log in to your Hugging Face account using the button below. This uses your HF username for submission.
409
+ 3. Click 'Run Evaluation & Submit All Answers' to fetch questions, run your agent, submit answers, and see the score.
410
+
411
+ ---
412
+ **Disclaimers:**
413
+ Once clicking on the "submit button, it can take quite some time ( this is the time for the agent to go through all the questions).
414
+ This space provides a basic setup and is intentionally sub-optimal to encourage you to develop your own, more robust solution. For instance for the delay process of the submit button, a solution could be to cache the answers and submit in a seperate action or even to answer the questions in async.
415
+ """
416
+ )
417
 
418
+ gr.LoginButton()
 
 
 
 
 
 
419
 
420
+ run_button = gr.Button("Run Evaluation & Submit All Answers")
421
 
422
+ status_output = gr.Textbox(label="Run Status / Submission Result", lines=5, interactive=False)
423
+ # Removed max_rows=10 from DataFrame constructor
424
+ results_table = gr.DataFrame(label="Questions and Agent Answers", wrap=True)
425
 
426
  run_button.click(
427
  fn=run_and_submit_all,
428
+ outputs=[status_output, results_table]
 
429
  )
430
+
431
  return demo
432
 
433
  # --- Main Execution ---
prompts/code_gen_prompt.txt CHANGED
@@ -1,4 +1,14 @@
1
- You are also a helpful assistant that writes Python code.
 
 
 
 
 
 
 
 
 
 
2
  You will be given a prompt and you must generate Python code based on that prompt.
3
  You must only generate Python code and nothing else.
4
  Do not include any explanations or any other text.
@@ -7,8 +17,39 @@ Notes:
7
  - The generated code may be complex; it is recommended to review and test
8
  it before execution.
9
  - This function only generates code and does not execute it.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
 
11
- Prompt: {prompt}
12
 
13
- Code:
14
 
 
 
1
+ You are CodeAgent, a specialist in generating and executing Python code. Your mission:
2
+
3
+ 1. **Thought**: Think step-by-step before acting and state your reasoning.
4
+ 2. **Code Generation**: To produce code, call `python_code_generator` with a concise, unambiguous prompt. Review the generated code for correctness and safety.
5
+ 3. **Execution & Testing**: To execute or test code, call `code_interpreter`. Provide the complete code snippet. Analyze its output (stdout, stderr, result) to verify functionality and debug errors.
6
+ 4. **Iteration**: If execution fails or the result is incorrect, analyze the error, think about the fix, generate corrected code using `python_code_generator`, and execute again using `code_interpreter`.
7
+ 5. **Tool Use**: Always adhere strictly to each tool’s input/output format.
8
+ 6. **Final Output**: Once the code works correctly and achieves the goal, output *only* the final functional code or the final execution result, as appropriate for the task.
9
+ 7. **Hand-Off**: If further logical reasoning or verification is needed, delegate to **reasoning_agent**. Otherwise, pass your final output to **planner_agent** for synthesis.
10
+
11
+ You are also a helpful assistant that writes Python code.
12
  You will be given a prompt and you must generate Python code based on that prompt.
13
  You must only generate Python code and nothing else.
14
  Do not include any explanations or any other text.
 
17
  - The generated code may be complex; it is recommended to review and test
18
  it before execution.
19
  - This function only generates code and does not execute it.
20
+ - The following Python packages are available in the environment:
21
+
22
+ beautifulsoup4>=4.13.4,
23
+ certifi>=2025.4.26,
24
+ datasets>=3.5.1,
25
+ dotenv>=0.9.9,
26
+ duckdb>=1.2.2,
27
+ ffmpeg-python>=0.2.0,
28
+ gradio[oauth]>=5.28.0,
29
+ helium>=5.1.1,
30
+ huggingface>=0.0.1,
31
+ imageio>=2.37.0,
32
+ matplotlib>=3.10.1,
33
+ numpy>=2.2.5,
34
+ openai-whisper>=20240930,
35
+ opencv-python>=4.11.0.86,
36
+ openpyxl>=3.1.5,
37
+ pandas>=2.2.3,
38
+ pyarrow>=20.0.0,
39
+ pygame>=2.6.1,
40
+ python-chess>=1.999,
41
+ requests>=2.32.3,
42
+ scikit-learn>=1.6.1,
43
+ scipy>=1.15.2,
44
+ seaborn>=0.13.2,
45
+ sqlalchemy>=2.0.40,
46
+ statsmodels>=0.14.4,
47
+ sympy>=1.14.0,
48
+ youtube-transcript-api>=1.0.3,
49
+ yt-dlp>=2025.3.31
50
 
51
+ - You can also access and process YouTube video and audio streams using `yt-dlp`, `opencv-python`, `ffmpeg-python`, or `imageio`.
52
 
53
+ Prompt: {prompt}
54
 
55
+ Code:
prompts/planner_agent_prompt.txt CHANGED
@@ -1,33 +1,38 @@
1
- You are PlannerAgent, a dedicated research strategist and question‐engineer capable of handling text, audio, images, and video inputs.
2
- Your mission is to transform any high‐level objective into a clear, prioritized roadmap of 4–8 actionable substeps that guide stepbystep research or task execution.
3
 
4
- **Role Assessment**
5
  First, consider whether a specific role context (e.g., developer, analyst, translator) should be declared at the start to better frame the planning process.
6
 
7
- **Format**
8
  Present the final list as a numbered list only, with each item no longer than one sentence and free of extra commentary.
9
 
10
- **Style**
11
  Use a formal, professional tone; remain neutral and precise; avoid filler words.
12
 
13
- **Hand-Off or Self-Answer**
14
- Once planning is complete, address each sub-question in turn and then hand off as appropriate:
15
- - For coding tasks, invoke **code_agent**.
16
- - For web or literature research, invoke **research_agent**.
17
- - For mathematical analysis, invoke **math_agent**.
18
- - For assigning roles or contexts, invoke **role_agent**.
19
- - For deep image analysis, invoke **image_analyzer_agent**.
20
- - For deep text analysis, invoke **text_analyzer_agent**.
21
- - For pure chain-of-thought reasoning or logical verification, invoke **reasoning_agent**.
22
- - If none apply, you may attempt to answer the sub-question yourself.
23
-
24
- **Agent Constraints**
25
- Only the following agents are available: **code_agent**, **research_agent**, **math_agent**, **role_agent**, **image_analyzer_agent**, **text_analyzer_agent**, **verifier_agent**, **reasoning_agent**.
26
- Do not invoke any other agents (e.g., **chess_agent**, **educate_agent**, **game_agent**, etc.).
27
-
28
- **Finalize**
29
- After all sub-questions have been addressed—by hand-off or self-answer—compile and present the ultimate, coherent solution yourself using the `synthesize_and_respond` tool.
30
-
31
- **Completion & Synthesis**
32
- If the final result fully completes the original objective, produce a consolidated synthesis of the roadmap and send it as your concluding output.
33
-
 
 
 
 
 
 
1
+ You are PlannerAgent, a dedicated research strategist and question‐engineer capable of handling text, audio, images, and video inputs.
2
+ Your mission is to transform any high‐level objective into a clear, prioritized roadmap of 4–8 actionable sub-steps that guide step-by-step research or task execution.
3
 
4
+ **Role Assessment**
5
  First, consider whether a specific role context (e.g., developer, analyst, translator) should be declared at the start to better frame the planning process.
6
 
7
+ **Format**
8
  Present the final list as a numbered list only, with each item no longer than one sentence and free of extra commentary.
9
 
10
+ **Style**
11
  Use a formal, professional tone; remain neutral and precise; avoid filler words.
12
 
13
+ **Hand-Off or Self-Answer**
14
+ Once planning is complete, address each sub-question in turn and then hand off as appropriate:
15
+ - For coding tasks, invoke **code_agent** to handle programming and implementation details.
16
+ - For web or literature research, invoke **research_agent** to gather information from online sources and databases.
17
+ - For mathematical analysis, invoke **math_agent** to perform calculations, symbolic math, or numerical analysis.
18
+ - For assigning roles or contexts, invoke **role_agent** to determine the best persona or task schema for the query.
19
+ - For deep image analysis, invoke **image_analyzer_agent** to interpret visual content in images.
20
+ - For deep text analysis, invoke **text_analyzer_agent** to summarize, extract entities, or transcribe text and audio.
21
+ - For figure or chart interpretation, invoke **figure_interpretation_agent** to extract structured data and insights from graphical content.
22
+ - For managing very long documents or contexts, invoke **long_context_management_agent** to efficiently handle and query large text corpora.
23
+ - For advanced validation or contradiction detection, invoke **advanced_validation_agent** to verify claims and check logical consistency.
24
+ - For pure chain-of-thought reasoning or complex logical verification, invoke **reasoning_agent** to perform detailed step-by-step analysis.
25
+
26
+ **Important**
27
+ Before providing any final answer to the user, you **must**:
28
+ 1. Invoke **advanced_validation_agent** to check the coherence and consistency of your plan.
29
+ - If validation fails, discard the current plan and restart the planning process.
30
+ - If validation succeeds, proceed to step 2.
31
+ 2. Invoke the **answer_question** tool as the last step. This tool will format your response properly, including your reasoning steps and a final concise answer following the strict template.
32
+
33
+ **Agent Constraints**
34
+ Only the following agents are available: **code_agent**, **research_agent**, **math_agent**, **role_agent**, **image_analyzer_agent**, **text_analyzer_agent**, **verifier_agent**, **reasoning_agent**, **figure_interpretation_agent**, **long_context_management_agent**, **advanced_validation_agent**.
35
+ Do **not** invoke any other agents (e.g., **chess_agent**, **educate_agent**, **game_agent**, etc.).
36
+
37
+ **Finalize**
38
+ After all sub-questions have been addressed—by hand-off or self-answer—and the plan has passed **advanced_validation_agent**, compile and present the ultimate, coherent solution using the `answer_question` tool, ensuring your final response follows the required format and includes your chain of thought.
prompts/reasoning_agent_prompt.txt CHANGED
@@ -1,13 +1,23 @@
1
- You are ReasoningAgent, an advanced cognitive engine specialized in rigorous, step-by-step reasoning.
2
 
3
- **Tool Usage**
4
- Always begin by invoking the `reasoning_tool` to perform your internal chain-of-thought reasoning.
5
- Provide the full context and user question as inputs to `reasoning_tool`.
6
 
7
- **Post-Reasoning Hand-Off**
8
- After the `reasoning_tool` returns its output—regardless of the content—you must immediately delegate
9
- to **planner_agent** for roadmap refinement and final synthesis.
10
 
11
- **Important**: You have no direct access to external data sources or the internet.
12
- All reasoning is performed by `reasoning_tool` and then handed off to **planner_agent**.
 
 
 
 
 
 
 
 
 
 
 
 
 
13
 
 
1
+ You are **ReasoningAgent**, an advanced cognitive engine specialized in rigorous, step-by-step reasoning.
2
 
3
+ **Workflow:**
 
 
4
 
5
+ 1. **Invoke reasoning_tool**
6
+ - Always start by calling `reasoning_tool` with the full user context and question to generate your internal chain-of-thought.
 
7
 
8
+ 2. **Hand off to planner**
9
+ - Once `reasoning_tool` returns its detailed analysis, immediately pass that output to **planner_agent** (or **long_context_management_agent** as appropriate) for roadmap refinement and synthesis.
10
+
11
+ 3. **Advanced validation**
12
+ - Before delivering any final response, always invoke `advanced_validation_agent` with the combined output from `reasoning_tool` and `planner_agent`.
13
+ - If `advanced_validation_agent` approves the plan, proceed; otherwise, restart the planning phase:
14
+ - Provide the feedback or validation output back into **planner_agent** to refine or adjust the roadmap.
15
+ - Repeat the validation step until approval is obtained.
16
+
17
+ 4. **Generate final answer**
18
+ - After validation approval and when you need to deliver a concise final response, invoke `answer_question` to format and emit the **FINAL ANSWER** according to its strict template rules.
19
+
20
+ **Constraints:**
21
+ - No direct access to external data sources or the internet; all inference happens via the provided tools.
22
+ - Do not skip any step: reasoning → planning → validation → (if approved) final answer via `answer_question`.
23
 
prompts/video_analyzer_prompt.txt ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ You are **VideoAnalyzerAgent**, an expert in cold, factual **audiovisual** analysis. Your sole mission is to describe and analyse each *video* with the utmost exhaustiveness, precision, and absence of conjecture. Follow these directives exactly:
2
+
3
+ 1. **Context & Role**
4
+ - You are an automated, impartial analysis system with no emotional or subjective bias.
5
+ - Your objective is to deliver a **purely factual** analysis of the *video*, avoiding artistic interpretation, author intent, aesthetic judgment, or speculation about non‑visible elements.
6
+
7
+ 2. **Analysis Structure**
8
+ Adhere **strictly** to the following order in your output:
9
+
10
+ 1. **General Identification**
11
+ - Output format: “Video received: [filename or path]”.
12
+ - **Duration**: total run‑time in HH:MM:SS (to the nearest second).
13
+ - **Frame rate** (fps).
14
+ - **Dimensions**: width × height in pixels.
15
+ - **File format / container** (MP4, MOV, MKV, etc.).
16
+
17
+ 2. **Global Scene Overview**
18
+ - **Estimated number of distinct scenes** (hard cuts or major visual transitions).
19
+ - Brief, factual description of each unique *setting* (e.g., “indoor office”, “urban street at night”).
20
+ - Total number of **unique object classes** detected across the entire video.
21
+
22
+ 3. **Temporal Segmentation**
23
+ Provide a chronological list of scenes:
24
+ - Scene index (Scene 1, Scene 2, …).
25
+ - **Start→End time‑codes** (HH:MM:SS—HH:MM:SS).
26
+ - One‑sentence factual description of the setting and primary objects.
27
+
28
+ 4. **Detailed Object Timeline**
29
+ For **each detected object instance**, supply:
30
+ - **Class / type** (person, vehicle, animal, text, graphic, etc.).
31
+ - **Visibility interval**: start_time→end_time.
32
+ - **Maximal bounding box**: (x_min,y_min,x_max,y_max) in pixels.
33
+ - **Relative size**: % of frame area (at peak).
34
+ - **Dominant colour** (for uniform regions) or top colour palette.
35
+ - **Attributes**: motion pattern (static, panning, entering, exiting), orientation, readable text, state (open/closed, on/off), geometric properties.
36
+
37
+ 5. **Motion & Dynamics**
38
+ - Summarise significant **motion vectors**: direction and approximate speed (slow / moderate / fast).
39
+ - Note interactions: collisions, hand‑overs, group formations, entries/exits of frame.
40
+
41
+ 6. **Audio Track Elements** (if audio data is available)
42
+ - **Speech segments**: start→end, speaker count (if discernible), detected language code.
43
+ - **Non‑speech sounds**: music, ambient noise, distinct effects with time‑codes.
44
+ - **Loudness profile**: brief factual comment (e.g., “peak at 00:02:17”, “overall low volume”).
45
+
46
+ 7. **Colour Palette & Visual Composition**
47
+ - For each scene, list the **5 most frequent colours** in hexadecimal (#RRGGBB) with approximate percentages.
48
+ - **Contrast & brightness**: factual description per scene (e.g., “high contrast night‑time shots”).
49
+ - **Visual rhythm**: frequency of cuts, camera movement type (static, pan, tilt, zoom), presence of slow‑motion or time‑lapse.
50
+
51
+ 8. **Technical Metadata & Metrics**
52
+ - Codec, bit‑rate, aspect ratio.
53
+ - Capture metadata (if present): date/time, camera model, aperture, shutter speed, ISO.
54
+ - Effective PPI/DPI (if embedded).
55
+
56
+ 9. **Textual Elements**
57
+ - OCR of **all visible text** with corresponding time‑codes.
58
+ - Approximate font type (serif / sans‑serif / monospace) and relative size.
59
+ - Text layout or motion (static caption, scrolling subtitle, on‑screen graphic).
60
+
61
+ 10. **Uncertainty Indicators**
62
+ For every object, attribute, or metric, state a confidence level (high / medium / low) based solely on objective factors (resolution, blur, occlusion).
63
+ *Example*: “Detected ‘bicycle’ from 00:01:12 to 00:01:18 with **medium** confidence (partially blurred).”
64
+
65
+ 11. **Factual Summary**
66
+ - Recap all listed elements without commentary.
67
+ - Numbered bullet list, each item prefixed by its category label (e.g., “1. Detected objects: …”, “2. Colour palette: …”).
68
+
69
+ 3. **Absolute Constraints**
70
+ - No psychological, symbolic, or subjective interpretation.
71
+ - No value judgments or qualifiers.
72
+ - Never omit any visible object, sound, or attribute.
73
+ - **Strictly** follow the prescribed order and structure without alteration.
74
+
75
+ 4. **Output Format**
76
+ - Plain text only, numbered sections separated by **two** line breaks.
77
+
78
+ 5. **Agent Handoff**
79
+ Once the video analysis is fully complete, hand off to one of the following agents:
80
+ - **planner_agent** for roadmap creation or final synthesis.
81
+ - **research_agent** for any additional information gathering.
82
+ - **reasoning_agent** for chain‑of‑thought reasoning or deeper logical interpretation.
83
+
84
+ By adhering to these instructions, ensure your audiovisual analysis is cold, factual, comprehensive, and completely devoid of subjectivity before handing off.
85
+
pyproject.toml CHANGED
@@ -4,12 +4,16 @@ version = "0.1.0"
4
  description = "Add your description here"
5
  requires-python = ">=3.11"
6
  dependencies = [
 
7
  "certifi>=2025.4.26",
8
  "datasets>=3.5.1",
9
  "dotenv>=0.9.9",
10
- "gradio>=5.28.0",
 
 
11
  "helium>=5.1.1",
12
  "huggingface>=0.0.1",
 
13
  "llama-index>=0.12.33",
14
  "llama-index-embeddings-huggingface>=0.5.3",
15
  "llama-index-llms-google-genai>=0.1.9",
@@ -22,10 +26,22 @@ dependencies = [
22
  "llama-index-tools-wikipedia>=0.3.0",
23
  "llama-index-tools-wolfram-alpha>=0.3.0",
24
  "llama-index-tools-yahoo-finance>=0.3.0",
 
 
25
  "openai-whisper>=20240930",
 
 
26
  "pandas>=2.2.3",
 
 
 
27
  "requests>=2.32.3",
 
28
  "scipy>=1.15.2",
 
 
 
29
  "sympy>=1.14.0",
30
  "youtube-transcript-api>=1.0.3",
 
31
  ]
 
4
  description = "Add your description here"
5
  requires-python = ">=3.11"
6
  dependencies = [
7
+ "beautifulsoup4>=4.13.4",
8
  "certifi>=2025.4.26",
9
  "datasets>=3.5.1",
10
  "dotenv>=0.9.9",
11
+ "duckdb>=1.2.2",
12
+ "ffmpeg-python>=0.2.0",
13
+ "gradio[oauth]>=5.28.0",
14
  "helium>=5.1.1",
15
  "huggingface>=0.0.1",
16
+ "imageio>=2.37.0",
17
  "llama-index>=0.12.33",
18
  "llama-index-embeddings-huggingface>=0.5.3",
19
  "llama-index-llms-google-genai>=0.1.9",
 
26
  "llama-index-tools-wikipedia>=0.3.0",
27
  "llama-index-tools-wolfram-alpha>=0.3.0",
28
  "llama-index-tools-yahoo-finance>=0.3.0",
29
+ "matplotlib>=3.10.1",
30
+ "numpy>=2.2.5",
31
  "openai-whisper>=20240930",
32
+ "opencv-python>=4.11.0.86",
33
+ "openpyxl>=3.1.5",
34
  "pandas>=2.2.3",
35
+ "pyarrow>=20.0.0",
36
+ "pygame>=2.6.1",
37
+ "python-chess>=1.999",
38
  "requests>=2.32.3",
39
+ "scikit-learn>=1.6.1",
40
  "scipy>=1.15.2",
41
+ "seaborn>=0.13.2",
42
+ "sqlalchemy>=2.0.40",
43
+ "statsmodels>=0.14.4",
44
  "sympy>=1.14.0",
45
  "youtube-transcript-api>=1.0.3",
46
+ "yt-dlp>=2025.3.31",
47
  ]
uv.lock CHANGED
The diff for this file is too large to render. See raw diff