File size: 5,330 Bytes
3af908f
8eabc3b
 
 
 
 
 
ee8c58d
4a95c80
8eabc3b
ee8c58d
 
3af908f
 
 
 
 
 
 
 
 
3da4c60
8eabc3b
3af908f
8eabc3b
 
75f03cb
 
 
 
3af908f
 
75f03cb
3af908f
75f03cb
3da4c60
 
e340c1f
3da4c60
e340c1f
3da4c60
 
 
75f03cb
 
 
 
 
 
 
 
 
 
 
 
 
 
1bfda6d
8eabc3b
 
 
3af908f
 
 
 
 
 
4879dd8
 
 
 
 
 
75f03cb
4879dd8
 
3af908f
 
4879dd8
 
3af908f
 
 
 
4879dd8
 
 
 
75f03cb
 
4879dd8
8eabc3b
3af908f
 
 
4879dd8
8eabc3b
 
3af908f
4a95c80
 
4879dd8
8eabc3b
3af908f
8eabc3b
4879dd8
8eabc3b
 
4879dd8
8eabc3b
 
 
 
 
 
 
 
 
 
 
 
 
4879dd8
3af908f
4879dd8
 
8eabc3b
3af908f
4879dd8
3af908f
 
4879dd8
3af908f
4879dd8
3af908f
 
 
 
3da4c60
 
 
8eabc3b
3af908f
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
"""Run the smolagents CodeAgent with all GAIA tools."""

from __future__ import annotations

import os
from pathlib import Path

from dotenv import load_dotenv
from smolagents import CodeAgent

load_dotenv()

from agent.tools import build_agent_tools
from agent.think_mode import should_use_think_mode
from model_provider import (
    apply_think_mode,
    build_model,
    get_llm_provider,
    supports_think_toggle,
    use_markdown_code_blocks,
)
from provider_chain import ProviderFallbackModel

MAX_STEPS = int(os.getenv("AGENT_MAX_STEPS", "12"))

GAIA_SYSTEM_PROMPT = """
You are a general AI assistant solving GAIA Level 1 questions.

Use tools and Python code whenever needed:
- web search or Wikipedia for factual questions
- arxiv_search for academic papers
- file tools for attachments (PDF, Excel, CSV, images, audio)
- get_youtube_transcript for YouTube links
- execute_code for local Python or bash computation

Research tips:
- Prefer wikipedia_search() over visiting wikipedia.org URLs
- For discography counts, use wikipedia_studio_albums(title, start_year, end_year)
- Use web_search() snippets first; only visit_webpage() when snippets lack the fact
- Do not fetch Wikipedia API URLs manually or json.loads() tool output — use Wikipedia tools
- Never fetch github.com blob or raw JSON pages — they are not useful
- Keep tool use efficient: aim to answer in 3–5 steps when possible

Every action must be valid Python inside a code block.
When done, call final_answer("...") with ONLY the answer value.

Answer formatting rules (critical):
- Return a number, as few words as possible, or a comma-separated list
- Numbers: no commas in the number; no $ or % unless the question asks for them
- Strings: no articles; no abbreviations unless specified
- Lists: apply the same rules to each item
- Do NOT pass "FINAL ANSWER:" into final_answer()
- Do NOT add explanations, markdown, or extra punctuation

Examples:
- Question asks for a count -> final_answer("3")
- Question asks for a name -> final_answer("Smith")
- Question asks for a list -> final_answer("a, b, c") with a comma and space between items
""".strip()


def build_prompt(
    question: str,
    file_path: str | None,
    file_error: str | None = None,
    extra_sections: str | None = None,
) -> str:
    parts = [question]

    if file_path:
        path = Path(file_path)
        suffix = path.suffix.lower()
        attachment_hint = {
            ".py": "A Python file is attached. Use read_text_file, then run or reason over it.",
            ".xlsx": "An Excel file is attached. Use read_excel_summary.",
            ".xls": "An Excel file is attached. Use read_excel_summary.",
            ".csv": "A CSV file is attached. Use analyze_csv_file.",
            ".pdf": "A PDF file is attached. Use read_pdf.",
            ".mp3": "An audio file is attached. Use transcribe_audio.",
            ".wav": "An audio file is attached. Use transcribe_audio.",
            ".png": "An image file is attached. Use describe_image or extract_text_from_image.",
            ".jpg": "An image file is attached. Use describe_image or extract_text_from_image.",
            ".jpeg": "An image file is attached. Use describe_image or extract_text_from_image.",
            ".webp": "An image file is attached. Use describe_image or extract_text_from_image.",
        }.get(suffix, "A file is attached. Use the appropriate reading tool.")
        parts.extend([attachment_hint, f"Attached file path: {path.resolve()}"])
    elif file_error:
        parts.append(
            "Note: the benchmark file attachment could not be loaded "
            f"({file_error}). Answer using web search and other tools instead."
        )

    if extra_sections:
        parts.append(extra_sections)

    return "\n\n".join(parts)


class AgentRunner:
    def __init__(self):
        model = build_model()
        code_block_tags = "markdown" if use_markdown_code_blocks() else None
        self.agent = CodeAgent(
            tools=build_agent_tools(),
            model=model,
            instructions=GAIA_SYSTEM_PROMPT,
            max_steps=MAX_STEPS,
            verbosity_level=1,
            code_block_tags=code_block_tags,
            additional_authorized_imports=[
                "requests",
                "re",
                "json",
                "math",
                "statistics",
                "datetime",
                "collections",
                "itertools",
                "pandas",
                "numpy",
            ],
        )
        print(
            f"AgentRunner ready ({get_llm_provider()}, "
            f"code_blocks={'markdown' if code_block_tags else 'xml'})"
        )

    def run(
        self,
        prompt: str,
        question: str | None = None,
        file_path: str | None = None,
        think: bool | None = None,
    ) -> str:
        if think is None and question is not None:
            think = should_use_think_mode(question, file_path)
        if think is not None and supports_think_toggle():
            apply_think_mode(self.agent.model, think)
        model = self.agent.model
        if isinstance(model, ProviderFallbackModel):
            model.reset_for_question()
        print(f"Running agent on question (first 80 chars): {prompt[:80]}...")
        return str(self.agent.run(prompt))