fix(secretaria): embed() cai para Gemini se OpenAI falhar (não derruba RAG)
Antes, com openai_key presente, qualquer falha (ex.: 429 insufficient_quota) retornava null sem tentar o Gemini. Agora tenta OpenAI e, em falha, usa Gemini. Aviso documentado: OpenAI (1536d) e Gemini (768d) são incompatíveis — trocar de provider exige reindexar (sync-knowledge + memória de contato). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -71,35 +71,55 @@ async function embed(text, cfg) {
|
|||||||
return null;
|
return null;
|
||||||
const openaiKey = cfg?.openai_key;
|
const openaiKey = cfg?.openai_key;
|
||||||
const geminiKey = cfg?.gemini_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 {
|
try {
|
||||||
if (openaiKey) {
|
const res = await fetch('https://api.openai.com/v1/embeddings', {
|
||||||
const res = await fetch('https://api.openai.com/v1/embeddings', {
|
method: 'POST',
|
||||||
method: 'POST',
|
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${apiKey}` },
|
||||||
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${openaiKey}` },
|
body: JSON.stringify({ model: 'text-embedding-3-small', input }),
|
||||||
body: JSON.stringify({ model: 'text-embedding-3-small', input }),
|
});
|
||||||
});
|
if (!res.ok)
|
||||||
if (!res.ok)
|
return null;
|
||||||
return null;
|
const data = await res.json();
|
||||||
const data = await res.json();
|
return data?.data?.[0]?.embedding ?? null;
|
||||||
return data?.data?.[0]?.embedding ?? null;
|
}
|
||||||
}
|
catch {
|
||||||
if (geminiKey) {
|
return null;
|
||||||
const url = `https://generativelanguage.googleapis.com/v1beta/models/text-embedding-004:embedContent?key=${geminiKey}`;
|
}
|
||||||
const res = await fetch(url, {
|
}
|
||||||
method: 'POST',
|
async function embedGemini(input, apiKey) {
|
||||||
headers: { 'Content-Type': 'application/json' },
|
try {
|
||||||
body: JSON.stringify({ model: 'models/text-embedding-004', content: { parts: [{ text: input }] } }),
|
const url = `https://generativelanguage.googleapis.com/v1beta/models/text-embedding-004:embedContent?key=${apiKey}`;
|
||||||
});
|
const res = await fetch(url, {
|
||||||
if (!res.ok)
|
method: 'POST',
|
||||||
return null;
|
headers: { 'Content-Type': 'application/json' },
|
||||||
const data = await res.json();
|
body: JSON.stringify({ model: 'models/text-embedding-004', content: { parts: [{ text: input }] } }),
|
||||||
return data?.embedding?.values ?? null;
|
});
|
||||||
}
|
if (!res.ok)
|
||||||
|
return null;
|
||||||
|
const data = await res.json();
|
||||||
|
return data?.embedding?.values ?? null;
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
function hasEmbeddingKey(cfg) {
|
function hasEmbeddingKey(cfg) {
|
||||||
return !!(cfg?.openai_key || cfg?.gemini_key);
|
return !!(cfg?.openai_key || cfg?.gemini_key);
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"embeddings.js","sourceRoot":"","sources":["embeddings.ts"],"names":[],"mappings":"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"}
|
||||||
@@ -49,32 +49,52 @@ export async function embed(text: string, cfg: any): Promise<number[] | null> {
|
|||||||
if (!input.trim()) return null
|
if (!input.trim()) return null
|
||||||
const openaiKey = cfg?.openai_key as string | undefined
|
const openaiKey = cfg?.openai_key as string | undefined
|
||||||
const geminiKey = cfg?.gemini_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<number[] | null> {
|
||||||
try {
|
try {
|
||||||
if (openaiKey) {
|
const res = await fetch('https://api.openai.com/v1/embeddings', {
|
||||||
const res = await fetch('https://api.openai.com/v1/embeddings', {
|
method: 'POST',
|
||||||
method: 'POST',
|
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${apiKey}` },
|
||||||
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${openaiKey}` },
|
body: JSON.stringify({ model: 'text-embedding-3-small', input }),
|
||||||
body: JSON.stringify({ model: 'text-embedding-3-small', input }),
|
})
|
||||||
})
|
if (!res.ok) return null
|
||||||
if (!res.ok) return null
|
const data: any = await res.json()
|
||||||
const data: any = await res.json()
|
return data?.data?.[0]?.embedding ?? null
|
||||||
return data?.data?.[0]?.embedding ?? null
|
} catch {
|
||||||
}
|
return null
|
||||||
if (geminiKey) {
|
}
|
||||||
const url = `https://generativelanguage.googleapis.com/v1beta/models/text-embedding-004:embedContent?key=${geminiKey}`
|
}
|
||||||
const res = await fetch(url, {
|
|
||||||
method: 'POST',
|
async function embedGemini(input: string, apiKey: string): Promise<number[] | null> {
|
||||||
headers: { 'Content-Type': 'application/json' },
|
try {
|
||||||
body: JSON.stringify({ model: 'models/text-embedding-004', content: { parts: [{ text: input }] } }),
|
const url = `https://generativelanguage.googleapis.com/v1beta/models/text-embedding-004:embedContent?key=${apiKey}`
|
||||||
})
|
const res = await fetch(url, {
|
||||||
if (!res.ok) return null
|
method: 'POST',
|
||||||
const data: any = await res.json()
|
headers: { 'Content-Type': 'application/json' },
|
||||||
return data?.embedding?.values ?? null
|
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 {
|
} catch {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
return null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function hasEmbeddingKey(cfg: any): boolean {
|
export function hasEmbeddingKey(cfg: any): boolean {
|
||||||
|
|||||||
Reference in New Issue
Block a user