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',