diff --git a/newwhats.clube67.com/newwhats.local/plugins/secretaria/embeddings.js b/newwhats.clube67.com/newwhats.local/plugins/secretaria/embeddings.js index 6e09427..6792607 100644 --- a/newwhats.clube67.com/newwhats.local/plugins/secretaria/embeddings.js +++ b/newwhats.clube67.com/newwhats.local/plugins/secretaria/embeddings.js @@ -71,35 +71,55 @@ async function embed(text, cfg) { 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 { - if (openaiKey) { - const res = await fetch('https://api.openai.com/v1/embeddings', { - method: 'POST', - headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${openaiKey}` }, - 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; - } - if (geminiKey) { - const url = `https://generativelanguage.googleapis.com/v1beta/models/text-embedding-004:embedContent?key=${geminiKey}`; - const res = await fetch(url, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ model: 'models/text-embedding-004', content: { parts: [{ text: input }] } }), - }); - if (!res.ok) - return null; - const data = await res.json(); - return data?.embedding?.values ?? null; - } + 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; + } +} +async function embedGemini(input, apiKey) { + try { + const url = `https://generativelanguage.googleapis.com/v1beta/models/text-embedding-004:embedContent?key=${apiKey}`; + const res = await fetch(url, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ model: 'models/text-embedding-004', content: { parts: [{ text: input }] } }), + }); + if (!res.ok) + return null; + const data = await res.json(); + return data?.embedding?.values ?? null; } catch { return null; } - return null; } function hasEmbeddingKey(cfg) { return !!(cfg?.openai_key || cfg?.gemini_key); diff --git a/newwhats.clube67.com/newwhats.local/plugins/secretaria/embeddings.js.map b/newwhats.clube67.com/newwhats.local/plugins/secretaria/embeddings.js.map new file mode 100644 index 0000000..2246427 --- /dev/null +++ b/newwhats.clube67.com/newwhats.local/plugins/secretaria/embeddings.js.map @@ -0,0 +1 @@ +{"version":3,"file":"embeddings.js","sourceRoot":"","sources":["embeddings.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/newwhats.clube67.com/newwhats.local/plugins/secretaria/embeddings.ts b/newwhats.clube67.com/newwhats.local/plugins/secretaria/embeddings.ts index 1f204d3..75037ab 100644 --- a/newwhats.clube67.com/newwhats.local/plugins/secretaria/embeddings.ts +++ b/newwhats.clube67.com/newwhats.local/plugins/secretaria/embeddings.ts @@ -49,32 +49,52 @@ export async function embed(text: string, cfg: any): Promise { if (!input.trim()) return null const openaiKey = cfg?.openai_key as string | undefined const geminiKey = cfg?.gemini_key as string | undefined + + // 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: string, apiKey: string): Promise { try { - if (openaiKey) { - const res = await fetch('https://api.openai.com/v1/embeddings', { - method: 'POST', - headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${openaiKey}` }, - body: JSON.stringify({ model: 'text-embedding-3-small', input }), - }) - if (!res.ok) return null - const data: any = await res.json() - return data?.data?.[0]?.embedding ?? null - } - if (geminiKey) { - const url = `https://generativelanguage.googleapis.com/v1beta/models/text-embedding-004:embedContent?key=${geminiKey}` - const res = await fetch(url, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ model: 'models/text-embedding-004', content: { parts: [{ text: input }] } }), - }) - if (!res.ok) return null - const data: any = await res.json() - return data?.embedding?.values ?? null - } + 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: any = await res.json() + return data?.data?.[0]?.embedding ?? null + } catch { + return null + } +} + +async function embedGemini(input: string, apiKey: string): Promise { + try { + const url = `https://generativelanguage.googleapis.com/v1beta/models/text-embedding-004:embedContent?key=${apiKey}` + const res = await fetch(url, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ model: 'models/text-embedding-004', content: { parts: [{ text: input }] } }), + }) + if (!res.ok) return null + const data: any = await res.json() + return data?.embedding?.values ?? null } catch { return null } - return null } export function hasEmbeddingKey(cfg: any): boolean {