File size: 14,991 Bytes
71f9f04 80710dc 6eb8def 80710dc 378c064 6eb8def 80710dc ff95a9a 6eb8def 80710dc 6eb8def 80710dc 6ed5e0b 378c064 71f9f04 6ed5e0b 80710dc 6eb8def 80710dc 6eb8def 80710dc 6eb8def 80710dc 6eb8def 80710dc 7f0c067 80710dc 6eb8def 80710dc 6eb8def 80710dc 6eb8def 80710dc 6eb8def 80710dc 6eb8def 80710dc 6eb8def 80710dc 6eb8def 80710dc 6eb8def 80710dc 6eb8def 80710dc 7f0c067 5c20eca 80710dc f732e70 80710dc 6eb8def 80710dc 6eb8def f732e70 80710dc 6eb8def 80710dc 6eb8def 80710dc 71f9f04 6eb8def 80710dc 6eb8def 80710dc 1e866d2 6eb8def 80710dc 1e866d2 6eb8def 80710dc |
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 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 |
import os
import openai
import wikipedia
from tavily import TavilyClient # Importa TavilyClient
import arxiv
import json
import re
# Carica le chiavi API dalle variabili d'ambiente
# Per i test locali, puoi usare python-dotenv e un file .env
# from dotenv import load_dotenv
# load_dotenv()
# La libreria OpenAI >1.0.0 gestisce automaticamente OPENAI_API_KEY
# La chiave Tavily deve essere caricata esplicitamente o passata al client
TAVILY_API_KEY = os.getenv("TAVILY_API_KEY")
if not os.getenv("OPENAI_API_KEY"):
print("ATTENZIONE: La variabile d'ambiente OPENAI_API_KEY non è impostata.")
print("L'agente potrebbe non funzionare correttamente senza una chiave API OpenAI valida.")
if not TAVILY_API_KEY:
print("ATTENZIONE: La variabile d'ambiente TAVILY_API_KEY non è impostata.")
print("La ricerca Tavily non funzionerà senza una chiave API valida.")
MAX_WIKIPEDIA_RESULTS = 3
MAX_TAVILY_RESULTS = 3 # Tavily può restituire risultati più ricchi
MAX_ARXIV_RESULTS = 2
MAX_TOOL_ITERATIONS = 6
class GaiaAgent:
def __init__(self, model_name="o4-mini"):
self.model_name = model_name
self.openai_client = openai.OpenAI() # Inizializza il client OpenAI
if TAVILY_API_KEY:
self.tavily_client = TavilyClient(api_key=TAVILY_API_KEY)
else:
self.tavily_client = None
print("Cliente Tavily non inizializzato a causa della mancanza di TAVILY_API_KEY.")
print(f"GaiaAgent initialized with model: {self.model_name}")
if not self.openai_client.api_key:
print("WARNING: OpenAI API key not found by the client. Ensure OPENAI_API_KEY is set.")
def _call_openai_api(self, messages, tools=None, tool_choice=None):
try:
response = self.openai_client.chat.completions.create(
model=self.model_name,
messages=messages,
tools=tools,
tool_choice=tool_choice,
#temperature=0.1,
)
return response
except openai.APIError as e:
print(f"Errore API OpenAI: {e}")
return f"Errore durante la chiamata all'API OpenAI: {str(e)}"
except Exception as e:
print(f"Errore imprevisto durante la chiamata OpenAI API: {e}")
return f"Errore imprevisto durante la chiamata OpenAI API: {str(e)}"
def _execute_python_code(self, code_string: str) -> str:
print(f"Esecuzione codice Python (primi 200 caratteri): {code_string[:200]}...")
try:
code_string = re.sub(r"^```python\n", "", code_string)
code_string = re.sub(r"\n```$", "", code_string)
code_string = code_string.strip()
import io
from contextlib import redirect_stdout
f = io.StringIO()
with redirect_stdout(f):
exec(code_string, {})
s = f.getvalue()
if not s:
s = "Codice eseguito con successo, nessun output esplicito (print)."
print(f"Esecuzione codice riuscita. Output (primi 200 caratteri): {s[:200]}")
return s
except Exception as e:
print(f"Errore durante l'esecuzione del codice Python: {e}")
return f"Errore nell'esecuzione del codice: {str(e)}"
def _search_wikipedia(self, query: str) -> str:
print(f"Ricerca Wikipedia per: {query}")
try:
wikipedia.set_lang("en")
results = wikipedia.search(query, results=MAX_WIKIPEDIA_RESULTS)
if not results:
return "Nessun risultato trovato su Wikipedia."
summaries = []
for res_title in results:
try:
page = wikipedia.page(res_title, auto_suggest=False, preload=True)
summary_text = page.summary.replace("\n", " ")[:700]
summaries.append(f"Title: {page.title}\nSummary: {summary_text}...")
except wikipedia.exceptions.DisambiguationError as e:
options = ", ".join(e.options[:3])
summaries.append(f"Pagina di disambiguazione per '{res_title}': Le opzioni includono {options}. Prova una query più specifica.")
except wikipedia.exceptions.PageError:
summaries.append(f"Pagina '{res_title}' non trovata su Wikipedia.")
except Exception as e_page:
summaries.append(f"Errore nel recuperare la pagina '{res_title}': {str(e_page)}")
return "\n\n".join(summaries) if summaries else "Nessun sommario trovato per i risultati di Wikipedia."
except Exception as e:
return f"Errore durante la ricerca su Wikipedia: {str(e)}"
def _search_tavily(self, query: str) -> str:
print(f"Ricerca Tavily per: {query}")
if not self.tavily_client:
return "Errore: Tavily API key non configurata. Impossibile eseguire la ricerca."
try:
# Puoi personalizzare i parametri di ricerca di Tavily qui
# ad esempio: search_depth="advanced", include_answer=True, include_raw_content=False
response = self.tavily_client.search(
query=query,
search_depth="basic", # "basic" per velocità, "advanced" per risposte più approfondite
max_results=MAX_TAVILY_RESULTS,
include_answer=True # Chiede a Tavily di provare a fornire una risposta diretta
)
results_str = ""
if response.get("answer"):
results_str += f"Risposta diretta da Tavily: {response['answer']}\n\n"
if response.get("results"):
results_str += "Risultati della ricerca:\n"
for r in response["results"]:
results_str += f"Title: {r.get('title', 'N/A')}\nURL: {r.get('url', 'N/A')}\nContent Snippet: {r.get('content', 'N/A')[:500]}...\n\n"
return results_str if results_str else "Nessun risultato significativo trovato da Tavily."
except Exception as e:
return f"Errore durante la ricerca su Tavily: {str(e)}"
def _search_arxiv(self, query: str) -> str:
print(f"Ricerca Arxiv per: {query}")
try:
search = arxiv.Search(
query=query,
max_results=MAX_ARXIV_RESULTS,
sort_by=arxiv.SortCriterion.Relevance
)
client = arxiv.Client()
results_data = []
for r in client.results(search):
authors = ", ".join([str(a) for a in r.authors])
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}")
return "\n\n".join(results_data) if results_data else "Nessun risultato trovato su Arxiv."
except Exception as e:
return f"Errore durante la ricerca su Arxiv: {str(e)}"
def __call__(self, question: str) -> str:
print(f"GaiaAgent ha ricevuto la domanda (primi 100 caratteri): {question[:100]}...")
if not self.openai_client.api_key:
return "Errore: OPENAI_API_KEY non configurata o non valida per l'agente."
if not self.tavily_client:
print("Avviso: Tavily client non inizializzato. La ricerca web non sarà disponibile.")
tools = [
{"type": "function", "function": {
"name": "search_wikipedia",
"description": "Cerca informazioni su Wikipedia. Utile per conoscenza generale, fatti, definizioni, storia.",
"parameters": {"type": "object", "properties": {"query": {"type": "string", "description": "La query di ricerca per Wikipedia."}}, "required": ["query"]}}},
{"type": "function", "function": {
"name": "search_tavily", # Nome dello strumento aggiornato
"description": "Cerca sul web usando l'API di Tavily. Utile per eventi attuali, informazioni specifiche, siti web, o quando Wikipedia non è sufficiente. Può fornire risposte dirette e snippet di contenuto.",
"parameters": {"type": "object", "properties": {"query": {"type": "string", "description": "La query di ricerca per Tavily."}}, "required": ["query"]}}},
{"type": "function", "function": {
"name": "search_arxiv",
"description": "Cerca su Arxiv articoli scientifici e preprint (fisica, matematica, informatica, ecc.).",
"parameters": {"type": "object", "properties": {"query": {"type": "string", "description": "La query di ricerca per Arxiv (es. autore, titolo, parole chiave)."}}, "required": ["query"]}}},
{"type": "function", "function": {
"name": "execute_python_code",
"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.",
"parameters": {"type": "object", "properties": {"code_string": {"type": "string", "description": "Il codice Python da eseguire. Esempio: 'print(1+1)'"}}, "required": ["code_string"]}}}
]
system_prompt = (
"You are a general AI assistant that uses the tools available. I will ask you a question. You must think and output only the exact answer to the question with no comments, so your final answer must be a number OR as few words as possible OR a comma separated list of numbers and/or strings. 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. 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. 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. If you are asked for a code number, give the code number nothing else. Do not add a dot at the end of the answer. Pay attention at the question and at the expected output."
)
messages = [{"role": "system", "content": system_prompt}, {"role": "user", "content": question}]
for iteration in range(MAX_TOOL_ITERATIONS):
print(f"Agente: Chiamata OpenAI. Iterazione {iteration + 1}. Ultimo messaggio: {messages[-1]['role']}")
api_response_or_error = self._call_openai_api(messages, tools=tools, tool_choice="auto")
if isinstance(api_response_or_error, str):
return api_response_or_error
response_message = api_response_or_error.choices[0].message
if response_message.tool_calls:
print(f"OpenAI suggerisce chiamate a strumenti: {[tc.function.name for tc in response_message.tool_calls]}")
messages.append(response_message)
for tool_call in response_message.tool_calls:
function_name = tool_call.function.name
try:
function_args = json.loads(tool_call.function.arguments)
except json.JSONDecodeError as e:
print(f"Errore nel decodificare gli argomenti JSON per {function_name}: {e}")
tool_output = f"Errore: argomenti JSON invalidi per {function_name}."
messages.append({"tool_call_id": tool_call.id, "role": "tool", "name": function_name, "content": tool_output})
continue
print(f"Esecuzione strumento: {function_name} con argomenti: {function_args}")
tool_output = ""
if function_name == "search_wikipedia":
tool_output = self._search_wikipedia(query=function_args.get("query",""))
elif function_name == "search_tavily": # Aggiornato per chiamare Tavily
if not self.tavily_client:
tool_output = "Errore: Tavily client non inizializzato. Impossibile eseguire la ricerca Tavily."
else:
tool_output = self._search_tavily(query=function_args.get("query",""))
elif function_name == "search_arxiv":
tool_output = self._search_arxiv(query=function_args.get("query",""))
elif function_name == "execute_python_code":
tool_output = self._execute_python_code(code_string=function_args.get("code_string",""))
else:
tool_output = f"Strumento sconosciuto: {function_name}"
print(f"Output strumento {function_name} (primi 100 caratteri): {str(tool_output)[:100]}")
messages.append({"tool_call_id": tool_call.id, "role": "tool", "name": function_name, "content": str(tool_output)})
else:
final_answer = response_message.content
if final_answer:
print(f"Agente restituisce risposta finale (primi 200 caratteri): {final_answer[:200]}")
return final_answer
else:
print("L'LLM non ha chiamato strumenti e non ha fornito contenuto. Tento di forzare una risposta.")
messages.append({"role": "user", "content": "Please provide the best possible answer based on the information you have gathered so far, without using any other tools."})
final_attempt_response = self._call_openai_api(messages)
if isinstance(final_attempt_response, str): return final_attempt_response
if final_attempt_response and final_attempt_response.choices[0].message.content:
return final_attempt_response.choices[0].message.content
return "L'agente ha ricevuto una risposta finale vuota e non è riuscito a generarne una alternativa."
print("L'agente ha raggiunto il numero massimo di iterazioni degli strumenti.")
messages.append({"role": "user", "content": "You have reached your tool usage limit. Please provide the best possible answer based on the information you have gathered so far."})
final_summary_response = self._call_openai_api(messages)
if isinstance(final_summary_response, str): return final_summary_response
if final_summary_response and final_summary_response.choices[0].message.content:
return final_summary_response.choices[0].message.content
return "L'agente ha raggiunto il numero massimo di iterazioni e non è riuscito a formulare una risposta finale." |