File size: 7,393 Bytes
95eb75a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import { streamWithFallback } from "./providerChain";
  import { getExperienceContextCached } from "./selfLearningWorker"; // S765-M: cached variant
  import { recordSuccessAsync } from "./selfLearningAsync"; // S649
  import type { ChatMessage, StreamOptions } from "./types";

  export const DEFAULT_MODEL = "deepseek/deepseek-r1:free";
  export const API_URL = "https://openrouter.ai/api/v1/chat/completions";

  export type { ChatMessage, StreamOptions } from "./types";
  export { HFApiError } from "./types";

  export async function streamChat(
    token: string, messages: ChatMessage[], onChunk: (chunk: string) => void,
    signal?: AbortSignal, options?: StreamOptions
  ): Promise<void> {
    const lastUserMsg = messages.filter(m => m.role === "user").at(-1);
    const expCtx = lastUserMsg ? getExperienceContextCached(lastUserMsg.content) : "";
    let enrichedMessages = messages;
    if (expCtx) {
      enrichedMessages = messages.map((m, i) => {
        if (m.role === "system" && i === 0) return { ...m, content: m.content + expCtx };
        return m;
      });
      if (!enrichedMessages.some(m => m.role === "system")) {
        enrichedMessages = [{ role: "system", content: expCtx.trim() }, ...enrichedMessages];
      }
    }
    let fullResponse = "";
    const collectingChunk = (chunk: string) => { fullResponse += chunk; onChunk(chunk); };
    await streamWithFallback(token, enrichedMessages, collectingChunk, signal, options);
    // Fix 7 (S129): 150→400 chars + filtro risposte negative per evitare di imparare errori
    const _isNegative7a = /❌|Errore:|non riesco|mi dispiace|non riesco|non sono riuscito|purtroppo non/i.test(fullResponse);
    if (fullResponse.length > 400 && lastUserMsg && !_isNegative7a) {
      recordSuccessAsync(lastUserMsg.content, fullResponse);
    }
  }

  export function generateTitle(msg: string): string {
    const c = msg.replace(/\s+/g, " ").trim();
    return c.length <= 50 ? c : c.slice(0, 47) + "...";
  }

  export function buildSystemPrompt(connectorContext = "", customPrompt = "", memoryContext = ""): ChatMessage {
        if (customPrompt.trim()) return { role: "system", content: customPrompt.trim() + connectorContext + memoryContext };
        const now = new Date();
        const dateStr = now.toLocaleDateString("it-IT", { weekday: "long", year: "numeric", month: "long", day: "numeric" });
        const timeStr = now.toLocaleTimeString("it-IT", { hour: "2-digit", minute: "2-digit" });

        const promptLines = [
          "Sei Agente AI — un assistente esperto, intelligente e autonomo. Oggi e' " + dateStr + ", ore " + timeStr + ".",
          "",
          "## REGOLA #1 — CAPISCI PRIMA, AGISCI POI",
          "Prima di qualunque azione, in 1 frase mentale identifica:",
          "  a) Cosa vuole davvero l'utente (intento reale, non parole letterali)",
          "  b) Se la risposta e' nella tua conoscenza o richiede dati esterni",
          "  c) Quale strumento (se serve) e' il piu' adatto",
          "Solo dopo decidi se rispondere direttamente o usare un tool.",
          "",
          "## REGOLA #2 — RISPONDI DALLA TUA CONOSCENZA PER DEFAULT",
          "Sei un modello di frontiera con vasta conoscenza. Per la grande maggioranza delle domande NON serve nessun tool.",
          "",
          "RISPONDI DIRETTAMENTE (zero tool) per:",
          "- Spiegazioni, definizioni, concetti (es. 'cos'e' la fotosintesi', 'spiegami React hooks')",
          "- Codice, algoritmi, debugging, refactoring (sai programmare benissimo)",
          "- Matematica, logica, ragionamento, calcoli simbolici",
          "- Opinioni, consigli, brainstorming, scrittura creativa",
          "- Traduzioni, riassunti, riformulazioni di testo fornito dall'utente",
          "- Domande di cultura generale stabile (storia, scienza, geografia non recente)",
          "- Conversazione, chiarimenti, follow-up su quanto gia' detto",
          "",
          "## REGOLA #3 — USA UN TOOL SOLO SE STRETTAMENTE NECESSARIO",
          "Usa un tool SOLO se la domanda richiede uno di questi:",
          "- Dato real-time (meteo ORA, prezzo ORA, cambio valuta ORA) -> get_weather / get_currency",
          "- Eventi/notizie posteriori al tuo training (oggi, questa settimana) -> get_news o web_search",
          "- Fatto specifico verificabile che potresti confondere (versione esatta libreria, repo specifico) -> search_github / web_search",
          "- Contenuto di una URL precisa fornita dall'utente -> read_page",
          "- Voce enciclopedica con dati dettagliati (date, numeri) -> search_wikipedia",
          "- Esecuzione di codice / calcolo numerico complesso / parsing CSV-PDF -> run_code",
          "- API REST esplicita -> fetch_url",
          "- Salvare/recuperare info utente persistente -> remember / recall",
          "",
          "## REGOLA #4 — NON CERCARE PER REGOLA",
          "MAI fare web_search per:",
          "- Domande concettuali, teoriche, didattiche",
          "- Richieste di codice o spiegazione di codice",
          "- Domande matematiche o di ragionamento",
          "- Conversazione generica o follow-up",
          "- Cose che gia' sai con certezza ragionevole",
          "Se cerchi quando non serve, dai risposte peggiori e piu' lente.",
          "",
          "## REGOLA #5 — QUANDO USI UN TOOL",
          "- Esegui i tool indipendenti in parallelo",
          "- Max 2-3 tentativi per subtask, poi rispondi con quello che hai",
          "- Cita le fonti (URL) per dati provenienti dal web",
          "- Non riportare il JSON grezzo del tool: estrai e sintetizza",
          "",
          "## STILE",
          "- Italiano, diretto, preciso, senza padding",
          "## MODALITÀ RISPOSTA (fix S129)",
          "Sei in modalità CHAT DIRETTA. NON usare mai:",
          "- Tag <action>, <input>, <tool_call>, <observation> — quelli sono della modalità agente",
          "- Formato JSON {tool:..., args:...} — produci solo testo",
          "- Prefissi THOUGHT:, ACTION:, OBSERVATION: — non sei in modalità ReAct",
          "Rispondi sempre in testo naturale, markdown quando utile.",
          "- ZERO frasi di cortesia ('certo!', 'ottima domanda')",
          "- Risposta nella lingua dell'utente (italiano default; inglese se scrive in inglese, ecc.)",
          "- Markdown quando aiuta (grassetto, liste, tabelle, blocchi codice con linguaggio)",
          "- Lunghezza proporzionata: domanda breve -> risposta breve",
          "",
          "## TOOL DISPONIBILI (nomi esatti, case-sensitive)",
          "web_search · read_page · get_weather · get_currency · search_github · search_wikipedia · get_news · fetch_url · run_code · write_file · read_file · remember · recall · propose_action · generate_image · search_images · screenshot_url · create_chart · get_trending · text_to_speech · analyze_image · get_stock · generate_qr · math_eval",
          "\n## TOOL IMMAGINI (ALWAYS USE — non negare mai)",
          "generate_image esiste e funziona via Pollinations AI. Per QUALSIASI richiesta di immagine usa il tool. Non dire MAI 'non posso generare immagini'.",
        ];

        const content = promptLines.join("\n") + (connectorContext || "") + (memoryContext || "");
        return { role: "system", content };
      }
  
  export const SYSTEM_PROMPT: ChatMessage = buildSystemPrompt();