| """ |
| HERMES — il cervello (ciclo agentico con tool-calling). |
| |
| E qui che Hermes "ragiona": riceve la conversazione, decide DA SOLO se usare |
| uno strumento (ricerca web, leggere/aggiornare la dashboard, salvare in memoria), |
| ne osserva il risultato, e continua finche non ha una risposta per Ayman. |
| |
| Usa le API LLM in cascata: DeepSeek (principale) -> Groq -> OpenAI (riserve), |
| cosi se un provider e giu o a corto di credito, l'agente continua a funzionare. |
| """ |
|
|
| from __future__ import annotations |
|
|
| import json |
| import requests |
|
|
| from config import LLM_PROVIDERS, LLM_TIMEOUT |
| from skills import TOOLS, dispatch |
|
|
| MAX_STEPS = 6 |
|
|
|
|
| def _chat(provider: dict, messages: list, use_tools: bool) -> dict | None: |
| """Una chiamata a un provider. Restituisce il messaggio dell'assistente o None.""" |
| if not provider.get("api_key"): |
| return None |
| payload = { |
| "model": provider["model"], |
| "messages": messages, |
| "temperature": 0.4, |
| } |
| if use_tools: |
| payload["tools"] = TOOLS |
| payload["tool_choice"] = "auto" |
| try: |
| r = requests.post( |
| f"{provider['base_url']}/chat/completions", |
| headers={"Authorization": f"Bearer {provider['api_key']}", |
| "Content-Type": "application/json"}, |
| json=payload, timeout=LLM_TIMEOUT, |
| ) |
| if r.status_code != 200: |
| print(f"[brain] {provider['name']} HTTP {r.status_code}: {r.text[:160]}", flush=True) |
| return None |
| return r.json()["choices"][0]["message"] |
| except Exception as e: |
| print(f"[brain] {provider['name']} errore: {e}", flush=True) |
| return None |
|
|
|
|
| def _chiama_llm(messages: list, use_tools: bool = True) -> dict | None: |
| """Prova i provider in cascata finche uno risponde.""" |
| for provider in LLM_PROVIDERS: |
| msg = _chat(provider, messages, use_tools) |
| if msg is not None: |
| return msg |
| return None |
|
|
|
|
| def pensa(messages: list) -> str: |
| """Ciclo agentico completo. messages = [system, ...storia..., user].""" |
| msgs = list(messages) |
|
|
| for _ in range(MAX_STEPS): |
| msg = _chiama_llm(msgs, use_tools=True) |
| if msg is None: |
| return "Scusa, ora non riesco a ragionare (cervello AI non raggiungibile). Riprova tra poco." |
|
|
| tool_calls = msg.get("tool_calls") |
| if not tool_calls: |
| return (msg.get("content") or "").strip() or "(nessuna risposta)" |
|
|
| |
| msgs.append(msg) |
| for tc in tool_calls: |
| fn = tc.get("function", {}) |
| nome = fn.get("name", "") |
| try: |
| argomenti = json.loads(fn.get("arguments") or "{}") |
| except Exception: |
| argomenti = {} |
| print(f"[brain] strumento -> {nome}({argomenti})", flush=True) |
| risultato = dispatch(nome, argomenti) |
| msgs.append({ |
| "role": "tool", |
| "tool_call_id": tc.get("id", ""), |
| "content": risultato, |
| }) |
|
|
| |
| finale = _chiama_llm(msgs, use_tools=False) |
| return (finale.get("content") if finale else "") or "Ho fatto un po' di passaggi ma non sono arrivato a una risposta netta. Riformula?" |
|
|