"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.chunkText = chunkText; exports.hashText = hashText; exports.cosineSimilarity = cosineSimilarity; exports.embed = embed; exports.hasEmbeddingKey = hasEmbeddingKey; /** * RAG sem pgvector — embeddings + similaridade calculados na aplicação. * * Os vetores são gerados via OpenAI (text-embedding-3-small) ou Gemini * (gemini-embedding-001 @ 768 dims), conforme a chave configurada no plugin, e guardados * como JSON em coluna text. A busca por similaridade (cosseno) roda em Node. * Para a base pequena da secretária (dezenas/centenas de chunks) é rápido e * dispensa a extensão pgvector. */ const crypto_1 = __importDefault(require("crypto")); /** Quebra o texto em chunks (~maxLen chars), preferindo limites de parágrafo. */ function chunkText(text, maxLen = 600) { const paras = text.split(/\n\s*\n/).map((p) => p.trim()).filter(Boolean); const merged = []; let buf = ''; for (const p of paras) { if (buf && (buf.length + 2 + p.length) > maxLen) { merged.push(buf); buf = p; } else { buf = buf ? `${buf}\n\n${p}` : p; } } if (buf) merged.push(buf); // Fatia parágrafos gigantes que excedam bastante o limite. const out = []; for (const c of merged) { if (c.length <= maxLen * 1.5) { out.push(c); continue; } for (let i = 0; i < c.length; i += maxLen) out.push(c.slice(i, i + maxLen)); } return out.filter(Boolean); } function hashText(text) { return crypto_1.default.createHash('md5').update(text).digest('hex'); } function cosineSimilarity(a, b) { const len = Math.min(a.length, b.length); let dot = 0, na = 0, nb = 0; for (let i = 0; i < len; i++) { dot += a[i] * b[i]; na += a[i] * a[i]; nb += b[i] * b[i]; } if (!na || !nb) return 0; return dot / (Math.sqrt(na) * Math.sqrt(nb)); } /** * Gera o embedding de um texto. Usa OpenAI se houver openai_key, senão Gemini. * Retorna null em qualquer falha (o chamador deve cair no fallback). */ async function embed(text, cfg) { const input = (text ?? '').slice(0, 8000); if (!input.trim()) return null; const openaiKey = cfg?.openai_key; const geminiKey = cfg?.gemini_key; // Preferência: OpenAI; em QUALQUER falha (ex.: 429 insufficient_quota), cai para // o Gemini — assim uma chave OpenAI sem créditos não derruba o RAG. // ⚠️ OpenAI (1536-dim) e Gemini (768-dim) geram espaços vetoriais incompatíveis. // Ao trocar de provider é preciso REINDEXAR (sync-knowledge + memória de contato), // senão o cosseno mistura dimensões e o retrieval fica sem sentido. if (openaiKey) { const v = await embedOpenAI(input, openaiKey); if (v) return v; } if (geminiKey) { const v = await embedGemini(input, geminiKey); if (v) return v; } return null; } async function embedOpenAI(input, apiKey) { try { const res = await fetch('https://api.openai.com/v1/embeddings', { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${apiKey}` }, body: JSON.stringify({ model: 'text-embedding-3-small', input }), }); if (!res.ok) return null; const data = await res.json(); return data?.data?.[0]?.embedding ?? null; } catch { return null; } } // gemini-embedding-001 (text-embedding-004 foi descontinuado). Default = 3072 dims; // fixamos 768 (outputDimensionality) para vetores mais leves em JSON + cosseno. const GEMINI_EMBED_MODEL = 'gemini-embedding-001'; const GEMINI_EMBED_DIMS = 768; async function embedGemini(input, apiKey) { try { const url = `https://generativelanguage.googleapis.com/v1beta/models/${GEMINI_EMBED_MODEL}:embedContent?key=${apiKey}`; const res = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ model: `models/${GEMINI_EMBED_MODEL}`, content: { parts: [{ text: input }] }, outputDimensionality: GEMINI_EMBED_DIMS, }), }); if (!res.ok) return null; const data = await res.json(); return data?.embedding?.values ?? null; } catch { return null; } } function hasEmbeddingKey(cfg) { return !!(cfg?.openai_key || cfg?.gemini_key); } //# sourceMappingURL=embeddings.js.map