From 2ade066468edd418fd21a815091275204d275543 Mon Sep 17 00:00:00 2001 From: Rodribm10 Date: Fri, 1 May 2026 16:08:18 -0300 Subject: [PATCH] feat(captain): EmbeddingService aceita provider de embedding dedicado MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Permite trocar provider de embedding sem afetar o provider de chat. Útil quando OpenAI key tradicional está fora (ban, billing, etc) mas você quer usar outro provider OpenAI-compatible só pra embeddings — exemplo clássico: Gemini OpenAI-compatible em https://generativelanguage.googleapis.com/v1beta/openai com modelo gemini-embedding-001 + dimensions=1536 (pra bater com schema pgvector). Env vars novas (com fallback pro legacy_openai_settings se não setadas): CAPTAIN_EMBEDDING_API_KEY — API key dedicada pra embeddings CAPTAIN_EMBEDDING_ENDPOINT — base URL sem /v1 (default herda OpenAI) CAPTAIN_EMBEDDING_DIMENSIONS — força redução do vector (ex: 1536) Quando CAPTAIN_EMBEDDING_API_KEY está vazia, comportamento é idêntico ao de antes (legacy_openai_settings). Backward-compatible. Também aceita as variáveis via InstallationConfig (UI) ou ENV — ENV tem precedência (padrão Chatwoot). Co-Authored-By: Claude Opus 4.7 (1M context) --- .../services/captain/llm/embedding_service.rb | 42 ++++++++++++++++--- 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/enterprise/app/services/captain/llm/embedding_service.rb b/enterprise/app/services/captain/llm/embedding_service.rb index 7cbb3c03c..420ddb027 100644 --- a/enterprise/app/services/captain/llm/embedding_service.rb +++ b/enterprise/app/services/captain/llm/embedding_service.rb @@ -26,17 +26,47 @@ class Captain::Llm::EmbeddingService private - # Embeddings sempre vão direto pra OpenAI tradicional — o endpoint Codex - # via ChatGPT OAuth não expõe /embeddings. + # Embeddings vão pra OpenAI tradicional por default (o endpoint Codex + # via ChatGPT OAuth não expõe /embeddings). Override opcional via env vars + # dedicadas — útil pra trocar provider de embedding (ex: Gemini + # OpenAI-compatible) sem alterar o provider de chat: + # + # CAPTAIN_EMBEDDING_API_KEY — sobrescreve API key + # CAPTAIN_EMBEDDING_ENDPOINT — sobrescreve base URL (sem /v1 no final) + # CAPTAIN_EMBEDDING_DIMENSIONS — força reduction (ex: 1536 pra Gemini + # bater com schema pgvector(1536)) def embed_with_legacy_openai(content, model) - legacy = Captain::Llm::ProviderConfig.legacy_openai_settings - api_base = legacy[:api_base].present? ? "#{legacy[:api_base]}/v1" : nil + settings = embedding_settings + api_base = settings[:api_base].present? ? "#{settings[:api_base]}/v1" : nil + embed_options = embed_extra_options - Llm::Config.with_api_key(legacy[:api_key], api_base: api_base) do |ctx| - ctx.embed(content, model: model).vectors + Llm::Config.with_api_key(settings[:api_key], api_base: api_base) do |ctx| + ctx.embed(content, model: model, **embed_options).vectors end end + def embedding_settings + custom_key = installation_config_value('CAPTAIN_EMBEDDING_API_KEY') + return Captain::Llm::ProviderConfig.legacy_openai_settings if custom_key.blank? + + { + api_key: custom_key, + api_base: installation_config_value('CAPTAIN_EMBEDDING_ENDPOINT')&.chomp('/') + } + end + + def embed_extra_options + dims = installation_config_value('CAPTAIN_EMBEDDING_DIMENSIONS') + return {} if dims.blank? + + { dimensions: dims.to_i } + end + + def installation_config_value(name) + ENV.fetch(name, nil) || + InstallationConfig.find_by(name: name)&.value + end + def instrumentation_params(content, model) { span_name: 'llm.captain.embedding',