Frazer2810 commited on
Commit
80710dc
·
verified ·
1 Parent(s): a383ea0

Update agent.py

Browse files
Files changed (1) hide show
  1. agent.py +230 -178
agent.py CHANGED
@@ -1,187 +1,239 @@
1
  import os
2
- import logging
3
- from typing import List, Dict, Any, Optional
 
 
 
 
4
 
5
- from smolagents import CodeAgent, tool, InferenceClientModel
 
 
 
 
6
 
7
- # Configurazione del logging
8
- logging.basicConfig(
9
- level=logging.INFO,
10
- format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
11
- )
12
- logger = logging.getLogger("GaiaAgent")
 
 
 
 
 
 
13
 
14
  class GaiaAgent:
15
- """
16
- Agente AI per superare il test GAIA livello 1.
17
- Utilizza smolagents con capacità di ricerca Wikipedia, DuckDuckGo, arXiv e code execution.
18
- """
19
-
20
- def __init__(self):
21
- logger.info("Inizializzazione GaiaAgent")
22
-
23
- # Verifica della chiave API
24
- api_key = os.environ.get("OPENAI_API_KEY")
25
- if not api_key:
26
- logger.error("OPENAI_API_KEY non trovata nelle variabili d'ambiente")
27
- raise ValueError("OPENAI_API_KEY non trovata. Configura il secret in Hugging Face.")
28
-
29
- # Configurazione del modello LLM
30
- self.model = InferenceClientModel(
31
- model="gpt-4.1",
32
- api_key=api_key
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  )
34
-
35
- # Definizione dei tools necessari
36
- @tool
37
- def search_wikipedia(query: str) -> str:
38
- """
39
- Cerca informazioni su Wikipedia.
40
-
41
- Args:
42
- query: La query di ricerca
43
-
44
- Returns:
45
- I risultati della ricerca
46
- """
47
- import wikipedia
48
- try:
49
- return wikipedia.summary(query, sentences=5)
50
- except Exception as e:
51
- return f"Errore nella ricerca su Wikipedia: {str(e)}"
52
-
53
- @tool
54
- def search_web(query: str) -> str:
55
- """
56
- Cerca informazioni sul web usando DuckDuckGo.
57
-
58
- Args:
59
- query: La query di ricerca
60
-
61
- Returns:
62
- I risultati della ricerca
63
- """
64
- from duckduckgo_search import DDGS
65
- try:
66
- with DDGS() as ddgs:
67
- results = list(ddgs.text(query, max_results=5))
68
- return str(results)
69
- except Exception as e:
70
- return f"Errore nella ricerca web: {str(e)}"
71
-
72
- @tool
73
- def search_arxiv(query: str) -> str:
74
- """
75
- Cerca articoli scientifici su arXiv.
76
-
77
- Args:
78
- query: La query di ricerca
79
-
80
- Returns:
81
- I risultati della ricerca
82
- """
83
- import arxiv
84
- try:
85
- search = arxiv.Search(query=query, max_results=5)
86
- results = []
87
- for result in search.results():
88
- results.append({
89
- "title": result.title,
90
- "authors": ", ".join(author.name for author in result.authors),
91
- "summary": result.summary[:200] + "..." if len(result.summary) > 200 else result.summary
92
- })
93
- return str(results)
94
- except Exception as e:
95
- return f"Errore nella ricerca su arXiv: {str(e)}"
96
-
97
- @tool
98
- def execute_code(code: str) -> str:
99
- """
100
- Esegue codice Python.
101
 
102
- Args:
103
- code: Il codice da eseguire
 
 
 
 
 
 
 
 
104
 
105
- Returns:
106
- Il risultato dell'esecuzione
107
- """
108
- import sys
109
- from io import StringIO
110
- import traceback
111
-
112
- old_stdout = sys.stdout
113
- redirected_output = sys.stdout = StringIO()
114
-
115
- try:
116
- exec(code)
117
- sys.stdout = old_stdout
118
- return redirected_output.getvalue()
119
- except Exception as e:
120
- sys.stdout = old_stdout
121
- return f"Errore nell'esecuzione del codice: {str(e)}\n{traceback.format_exc()}"
122
-
123
- # Lista dei tools
124
- tools = [search_wikipedia, search_web, search_arxiv, execute_code]
125
-
126
- # Istruzioni specifiche per il formato delle risposte
127
- system_prompt = """You are a helpful assistant tasked with answering questions using a set of tools.
128
- Your final answer should be a number OR as few words as possible OR a comma separated list of numbers and/or strings.
129
- 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.
130
- If you are asked for a string, don't use articles, neither abbreviations, and write digits in plain text unless specified otherwise.
131
- Return ONLY the final answer line."""
132
-
133
- # Creazione dell'agente
134
- self.agent = CodeAgent(
135
- model=self.model,
136
- tools=tools,
137
- system_prompt=system_prompt,
138
- verbose=True # Per logging essenziale
139
- )
140
-
141
- logger.info("GaiaAgent inizializzato con successo")
142
-
143
- def __call__(self, question: str) -> str:
144
- """
145
- Processa una domanda e restituisce la risposta.
146
-
147
- Args:
148
- question: La domanda da processare
149
-
150
- Returns:
151
- La risposta alla domanda
152
- """
153
- logger.info(f"Elaborazione domanda: {question[:50]}...")
154
-
155
- try:
156
- # Esecuzione dell'agente sulla domanda
157
- response = self.agent.run(question)
158
-
159
- # Estrazione della risposta finale
160
- answer = self._extract_final_answer(response)
161
-
162
- logger.info(f"Risposta generata: {answer[:50]}...")
163
- return answer
164
-
165
- except Exception as e:
166
- logger.error(f"Errore durante l'elaborazione della domanda: {str(e)}")
167
- return f"Errore: {str(e)}"
168
-
169
- def _extract_final_answer(self, response: Dict[str, Any]) -> str:
170
- """
171
- Estrae la risposta finale dal risultato dell'agente.
172
-
173
- Args:
174
- response: Il risultato completo dell'agente
175
 
176
- Returns:
177
- La risposta finale estratta
178
- """
179
- # Estrazione della risposta finale dal risultato dell'agente
180
- if isinstance(response, dict) and "output" in response:
181
- return response["output"].strip()
182
- elif isinstance(response, str):
183
- return response.strip()
184
- else:
185
- logger.warning(f"Formato di risposta inatteso: {type(response)}")
186
- # Tentativo di conversione a stringa
187
- return str(response).strip()
 
 
 
 
 
 
 
 
 
 
 
 
1
  import os
2
+ import openai
3
+ import wikipedia
4
+ from duckduckgo_search import DDGS
5
+ import arxiv
6
+ import json
7
+ import re
8
 
9
+ # Carica la chiave API di OpenAI dalla variabile d'ambiente
10
+ # Per i test locali, puoi usare python-dotenv e un file .env
11
+ # from dotenv import load_dotenv
12
+ # load_dotenv()
13
+ # openai.api_key = os.getenv("OPENAI_API_KEY") # La libreria OpenAI >1.0.0 lo gestisce automaticamente
14
 
15
+ # Se la chiave API non è impostata tramite variabile d'ambiente,
16
+ # la libreria OpenAI cercherà di trovarla.
17
+ # È buona pratica assicurarsi che sia disponibile.
18
+ if not os.getenv("OPENAI_API_KEY"):
19
+ print("ATTENZIONE: La variabile d'ambiente OPENAI_API_KEY non è impostata.")
20
+ print("L'agente potrebbe non funzionare correttamente senza una chiave API OpenAI valida.")
21
+
22
+
23
+ MAX_WIKIPEDIA_RESULTS = 3
24
+ MAX_DDG_RESULTS = 4
25
+ MAX_ARXIV_RESULTS = 3
26
+ MAX_TOOL_ITERATIONS = 7 # Numero massimo di cicli di chiamata agli strumenti
27
 
28
  class GaiaAgent:
29
+ def __init__(self, model_name="gpt-4o"):
30
+ self.model_name = model_name
31
+ self.client = openai.OpenAI() # Inizializza il client OpenAI qui
32
+ print(f"GaiaAgent initialized with model: {self.model_name}")
33
+ if not self.client.api_key: # Verifica se la chiave è stata caricata dal client
34
+ print("WARNING: OpenAI API key not found by the client. Ensure OPENAI_API_KEY is set.")
35
+
36
+ def _call_openai_api(self, messages, tools=None, tool_choice=None):
37
+ try:
38
+ response = self.client.chat.completions.create(
39
+ model=self.model_name,
40
+ messages=messages,
41
+ tools=tools,
42
+ tool_choice=tool_choice,
43
+ temperature=0.1, # Bassa temperatura per risposte più deterministiche e fattuali
44
+ )
45
+ return response
46
+ except openai.APIError as e:
47
+ print(f"Errore API OpenAI: {e}")
48
+ # Potresti voler restituire un messaggio di errore specifico o sollevare l'eccezione
49
+ return f"Errore durante la chiamata all'API OpenAI: {str(e)}"
50
+ except Exception as e:
51
+ print(f"Errore imprevisto durante la chiamata OpenAI API: {e}")
52
+ return f"Errore imprevisto durante la chiamata OpenAI API: {str(e)}"
53
+
54
+
55
+ def _execute_python_code(self, code_string: str) -> str:
56
+ """Esegue codice Python e restituisce l'output o un errore."""
57
+ print(f"Esecuzione codice Python (primi 200 caratteri): {code_string[:200]}...")
58
+ try:
59
+ # Rimuovi i backtick e 'python' se presenti (comune nell'output LLM)
60
+ code_string = re.sub(r"^```python\n", "", code_string)
61
+ code_string = re.sub(r"\n```$", "", code_string)
62
+ code_string = code_string.strip()
63
+
64
+ # Prepara un ambiente per l'esecuzione del codice
65
+ # Reindirizza stdout per catturare i print
66
+ import io
67
+ from contextlib import redirect_stdout
68
+ f = io.StringIO()
69
+ with redirect_stdout(f):
70
+ exec(code_string, {}) # Usa un dizionario vuoto per globals per un minimo di isolamento
71
+ s = f.getvalue()
72
+
73
+ # Se non c'è output esplicito da print(), indica che l'esecuzione è avvenuta
74
+ if not s:
75
+ s = "Codice eseguito con successo, nessun output esplicito (print)."
76
+ print(f"Esecuzione codice riuscita. Output (primi 200 caratteri): {s[:200]}")
77
+ return s
78
+ except Exception as e:
79
+ print(f"Errore durante l'esecuzione del codice Python: {e}")
80
+ return f"Errore nell'esecuzione del codice: {str(e)}"
81
+
82
+ def _search_wikipedia(self, query: str) -> str:
83
+ print(f"Ricerca Wikipedia per: {query}")
84
+ try:
85
+ wikipedia.set_lang("en") # Le domande GAIA sono in inglese
86
+ results = wikipedia.search(query, results=MAX_WIKIPEDIA_RESULTS)
87
+ if not results:
88
+ return "Nessun risultato trovato su Wikipedia."
89
+ summaries = []
90
+ for res_title in results:
91
+ try:
92
+ page = wikipedia.page(res_title, auto_suggest=False, preload=True)
93
+ summary_text = page.summary.replace("\n", " ")[:700]
94
+ summaries.append(f"Title: {page.title}\nSummary: {summary_text}...")
95
+ except wikipedia.exceptions.DisambiguationError as e:
96
+ options = ", ".join(e.options[:3])
97
+ summaries.append(f"Pagina di disambiguazione per '{res_title}': Le opzioni includono {options}. Prova una query più specifica.")
98
+ except wikipedia.exceptions.PageError:
99
+ summaries.append(f"Pagina '{res_title}' non trovata su Wikipedia.")
100
+ except Exception as e_page:
101
+ summaries.append(f"Errore nel recuperare la pagina '{res_title}': {str(e_page)}")
102
+ return "\n\n".join(summaries) if summaries else "Nessun sommario trovato per i risultati di Wikipedia."
103
+ except Exception as e:
104
+ return f"Errore durante la ricerca su Wikipedia: {str(e)}"
105
+
106
+ def _search_duckduckgo(self, query: str) -> str:
107
+ print(f"Ricerca DuckDuckGo per: {query}")
108
+ try:
109
+ with DDGS() as ddgs:
110
+ # Usare ddgs.text per risultati testuali, o ddgs.answers per risposte più dirette se disponibili
111
+ results = list(ddgs.text(query, max_results=MAX_DDG_RESULTS))
112
+ if not results:
113
+ return "Nessun risultato trovato su DuckDuckGo."
114
+ return "\n\n".join([f"Title: {r['title']}\nSnippet: {r['body'][:500]}...\nURL: {r['href']}" for r in results])
115
+ except Exception as e:
116
+ return f"Errore durante la ricerca su DuckDuckGo: {str(e)}"
117
+
118
+ def _search_arxiv(self, query: str) -> str:
119
+ print(f"Ricerca Arxiv per: {query}")
120
+ try:
121
+ search = arxiv.Search(
122
+ query=query,
123
+ max_results=MAX_ARXIV_RESULTS,
124
+ sort_by=arxiv.SortCriterion.Relevance
125
+ )
126
+ client = arxiv.Client()
127
+ results_data = []
128
+ for r in client.results(search):
129
+ authors = ", ".join([str(a) for a in r.authors])
130
+ results_data.append(f"Title: {r.title}\nAuthors: {authors}\nPublished: {r.published.date()}\nSummary: {r.summary.replace_summary_newline_chars_with_spaces()[:700]}...\nLink: {r.pdf_url}")
131
+ return "\n\n".join(results_data) if results_data else "Nessun risultato trovato su Arxiv."
132
+ except Exception as e:
133
+ return f"Errore durante la ricerca su Arxiv: {str(e)}"
134
+
135
+ def __call__(self, question: str) -> str:
136
+ print(f"GaiaAgent ha ricevuto la domanda (primi 100 caratteri): {question[:100]}...")
137
+ if not self.client.api_key:
138
+ return "Errore: OPENAI_API_KEY non configurata o non valida per l'agente."
139
+
140
+ tools = [
141
+ {"type": "function", "function": {
142
+ "name": "search_wikipedia",
143
+ "description": "Cerca informazioni su Wikipedia. Utile per conoscenza generale, fatti, definizioni, storia.",
144
+ "parameters": {"type": "object", "properties": {"query": {"type": "string", "description": "La query di ricerca per Wikipedia."}}, "required": ["query"]}}},
145
+ {"type": "function", "function": {
146
+ "name": "search_duckduckgo",
147
+ "description": "Cerca sul web usando DuckDuckGo. Utile per eventi attuali, siti specifici, o quando Wikipedia non è sufficiente.",
148
+ "parameters": {"type": "object", "properties": {"query": {"type": "string", "description": "La query di ricerca per DuckDuckGo."}}, "required": ["query"]}}},
149
+ {"type": "function", "function": {
150
+ "name": "search_arxiv",
151
+ "description": "Cerca su Arxiv articoli scientifici e preprint (fisica, matematica, informatica, ecc.).",
152
+ "parameters": {"type": "object", "properties": {"query": {"type": "string", "description": "La query di ricerca per Arxiv (es. autore, titolo, parole chiave)."}}, "required": ["query"]}}},
153
+ {"type": "function", "function": {
154
+ "name": "execute_python_code",
155
+ "description": "Esegue una stringa di codice Python e restituisce il suo output. Usare per calcoli, manipolazione dati o qualsiasi task che richieda esecuzione di codice. Assicurarsi che il codice stampi il risultato su stdout. Il codice viene eseguito in un ambiente stateless. NON usare per installare pacchetti o interagire con file system.",
156
+ "parameters": {"type": "object", "properties": {"code_string": {"type": "string", "description": "Il codice Python da eseguire. Deve essere uno script Python valido. Esempio: 'print(sum([i for i in range(101)]))'"}}, "required": ["code_string"]}}}
157
+ ]
158
+
159
+ system_prompt = (
160
+ "Sei GaiaAgent, un assistente AI progettato per rispondere a domande in modo accurato e completo, specialmente per task complessi come quelli di GAIA. "
161
+ "Hai accesso ai seguenti strumenti: ricerca Wikipedia, ricerca web DuckDuckGo, ricerca Arxiv e un interprete di codice Python. "
162
+ "Segui questi passaggi per rispondere alla domanda dell'utente:\n"
163
+ "1. **Analizza la Domanda**: Comprendi a fondo cosa viene chiesto. Se la domanda è complessa, scomponila in sotto-problemi.\n"
164
+ "2. **Pianifica l'Uso degli Strumenti**: Decidi quali strumenti sono più appropriati e in quale ordine. Puoi usare più strumenti in sequenza.\n"
165
+ " - `search_wikipedia`: per conoscenza generale consolidata.\n"
166
+ " - `search_duckduckgo`: per informazioni recenti, argomenti di nicchia, o per confermare/trovare URL specifici.\n"
167
+ " - `search_arxiv`: per ricerca scientifica e tecnica.\n"
168
+ " - `execute_python_code`: per calcoli, elaborazione di dati testuali/numerici, o simulazioni. Il codice deve stampare esplicitamente i risultati.\n"
169
+ "3. **Esegui e Itera**: Usa gli strumenti. Se uno strumento non fornisce l'informazione necessaria, valuta se riformulare la query, provare un altro strumento, o concludere che l'informazione non è reperibile con gli strumenti attuali.\n"
170
+ "4. **Sintetizza e Rispondi**: Combina le informazioni raccolte e la tua conoscenza interna per formulare una risposta finale chiara, concisa e che risponda direttamente alla domanda originale. Se non riesci a trovare una risposta definitiva, indicalo chiaramente.\n"
171
+ "Limita il numero di chiamate agli strumenti a un massimo di {} iterazioni per domanda. ".format(MAX_TOOL_ITERATIONS) +
172
+ "Fornisci solo la risposta finale senza commentare il processo di ragionamento o l'uso degli strumenti, a meno che non sia esplicitamente richiesto o necessario per chiarire la provenienza di un'informazione cruciale."
173
  )
174
+
175
+ messages = [{"role": "system", "content": system_prompt}, {"role": "user", "content": question}]
176
+
177
+ for iteration in range(MAX_TOOL_ITERATIONS):
178
+ print(f"Agente: Chiamata OpenAI. Iterazione {iteration + 1}. Ultimo messaggio: {messages[-1]['role']}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
179
 
180
+ api_response_or_error = self._call_openai_api(messages, tools=tools, tool_choice="auto")
181
+
182
+ if isinstance(api_response_or_error, str): # Errore gestito da _call_openai_api
183
+ return api_response_or_error # Restituisce il messaggio di errore
184
+
185
+ response_message = api_response_or_error.choices[0].message
186
+
187
+ if response_message.tool_calls:
188
+ print(f"OpenAI suggerisce chiamate a strumenti: {[tc.function.name for tc in response_message.tool_calls]}")
189
+ messages.append(response_message) # Aggiungi la risposta dell'assistente con le richieste di tool
190
 
191
+ for tool_call in response_message.tool_calls:
192
+ function_name = tool_call.function.name
193
+ try:
194
+ function_args = json.loads(tool_call.function.arguments)
195
+ except json.JSONDecodeError as e:
196
+ print(f"Errore nel decodificare gli argomenti JSON per {function_name}: {e}")
197
+ tool_output = f"Errore: argomenti JSON invalidi per {function_name}."
198
+ messages.append({"tool_call_id": tool_call.id, "role": "tool", "name": function_name, "content": tool_output})
199
+ continue # Salta al prossimo tool_call o iterazione
200
+
201
+ print(f"Esecuzione strumento: {function_name} con argomenti: {function_args}")
202
+ tool_output = ""
203
+ if function_name == "search_wikipedia":
204
+ tool_output = self._search_wikipedia(query=function_args.get("query",""))
205
+ elif function_name == "search_duckduckgo":
206
+ tool_output = self._search_duckduckgo(query=function_args.get("query",""))
207
+ elif function_name == "search_arxiv":
208
+ tool_output = self._search_arxiv(query=function_args.get("query",""))
209
+ elif function_name == "execute_python_code":
210
+ tool_output = self._execute_python_code(code_string=function_args.get("code_string",""))
211
+ else:
212
+ tool_output = f"Strumento sconosciuto: {function_name}"
213
+
214
+ print(f"Output strumento {function_name} (primi 100 caratteri): {str(tool_output)[:100]}")
215
+ messages.append({"tool_call_id": tool_call.id, "role": "tool", "name": function_name, "content": str(tool_output)})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
216
 
217
+ else: # Nessuna chiamata a strumento, l'LLM dovrebbe aver fornito una risposta finale
218
+ final_answer = response_message.content
219
+ if final_answer:
220
+ print(f"Agente restituisce risposta finale (primi 200 caratteri): {final_answer[:200]}")
221
+ return final_answer
222
+ else: # Risposta vuota, prova a forzare una risposta basata sulla cronologia
223
+ print("L'LLM non ha chiamato strumenti e non ha fornito contenuto. Tento di forzare una risposta.")
224
+ messages.append({"role": "user", "content": "Per favore, fornisci la migliore risposta possibile basata sulle informazioni raccolte finora, senza usare altri strumenti."})
225
+ final_attempt_response = self._call_openai_api(messages) # No tools this time
226
+ if isinstance(final_attempt_response, str): return final_attempt_response # Errore
227
+ if final_attempt_response and final_attempt_response.choices[0].message.content:
228
+ return final_attempt_response.choices[0].message.content
229
+ return "L'agente ha ricevuto una risposta finale vuota e non è riuscito a generarne una alternativa."
230
+
231
+
232
+ print("L'agente ha raggiunto il numero massimo di iterazioni degli strumenti.")
233
+ # Tenta un'ultima chiamata per sintetizzare
234
+ messages.append({"role": "user", "content": "Hai raggiunto il limite di utilizzo degli strumenti. Per favore, fornisci la migliore risposta possibile basata sulle informazioni raccolte finora."})
235
+ final_summary_response = self._call_openai_api(messages) # No tools
236
+ if isinstance(final_summary_response, str): return final_summary_response # Errore
237
+ if final_summary_response and final_summary_response.choices[0].message.content:
238
+ return final_summary_response.choices[0].message.content
239
+ return "L'agente ha raggiunto il numero massimo di iterazioni e non è riuscito a formulare una risposta finale."