diff --git a/lib/llm/gemini.mjs b/lib/llm/gemini.mjs index 04cf65d..8383238 100644 --- a/lib/llm/gemini.mjs +++ b/lib/llm/gemini.mjs @@ -21,8 +21,35 @@ export class GeminiProvider extends LLMProvider { body: JSON.stringify({ systemInstruction: { parts: [{ text: systemPrompt }] }, contents: [{ parts: [{ text: userMessage }] }], + // --- TRAVA DE SEGURANÇA DESATIVADA PARA OSINT --- + safetySettings: [ + { category: "HARM_CATEGORY_HARASSMENT", threshold: "BLOCK_NONE" }, + { category: "HARM_CATEGORY_HATE_SPEECH", threshold: "BLOCK_NONE" }, + { category: "HARM_CATEGORY_SEXUALLY_EXPLICIT", threshold: "BLOCK_NONE" }, + { category: "HARM_CATEGORY_DANGEROUS_CONTENT", threshold: "BLOCK_NONE" } + ], + // ------------------------------------------------ generationConfig: { maxOutputTokens: opts.maxTokens || 4096, + // Trava estrutural para garantir o JSON nativo + responseMimeType: "application/json", + responseSchema: { + type: "ARRAY", + items: { + type: "OBJECT", + properties: { + title: { type: "STRING", description: "Short title (max 10 words)" }, + type: { type: "STRING", enum: ["LONG", "SHORT", "HEDGE", "WATCH", "AVOID"] }, + ticker: { type: "STRING", description: "Primary instrument" }, + confidence: { type: "STRING", enum: ["HIGH", "MEDIUM", "LOW"] }, + rationale: { type: "STRING", description: "2-3 sentence explanation citing specific data" }, + risk: { type: "STRING", description: "Key risk factor" }, + horizon: { type: "STRING", description: "Intraday, Days, Weeks, or Months" }, + signals: { type: "ARRAY", items: { type: "STRING" } } + }, + required: ["title", "type", "ticker", "confidence", "rationale", "risk", "horizon", "signals"] + } + } }, }), signal: AbortSignal.timeout(opts.timeout || 60000), @@ -34,8 +61,21 @@ export class GeminiProvider extends LLMProvider { } const data = await res.json(); + + // Fallback de segurança caso a API bloqueie em um nível superior (independente do BLOCK_NONE) + if (data.candidates?.[0]?.finishReason === 'SAFETY') { + console.warn('[LLM] Alerta de Censura: O modelo recusou a análise do feed por violação de segurança.'); + } + const text = data.candidates?.[0]?.content?.parts?.[0]?.text || ''; + // --- INJEÇÃO DE DEBUG --- + if (!text || text.length < 10) { + console.log("\n[DEBUG CRÍTICO] A IA não retornou texto válido. Resposta bruta da API:"); + console.log(JSON.stringify(data, null, 2)); + } + // ------------------------ + return { text, usage: { @@ -45,4 +85,4 @@ export class GeminiProvider extends LLMProvider { model: this.model, }; } -} +} \ No newline at end of file