Resolve duas camadas de problema identificadas em teste end-to-end: 1. Embeddings falhavam com HTTP 404 (/codex/v1/embeddings não existe). Solução: Captain::Llm::EmbeddingService sempre usa OpenAI tradicional via Llm::Config.with_api_key(legacy_settings). ProviderConfig expõe legacy_openai_settings pra isso. 2. Servidor Codex ocasionalmente responde com response.failed + code=server_error (instabilidade transitória). Client agora retenta até 2x com backoff exponencial (0.5s, 1.5s) em erros retryable: HTTP 5xx, server_error no response.failed, ou stream inacabado. Outras correções nesta etapa: - Scenario#agent_model: em modo Codex, ignora CAPTAIN_OPEN_AI_MODEL_SCENARIO (que pode ter gpt-4o legado) e usa ProviderConfig.model. - ExtractionService/ContradictionCheckerService/TranslateQueryService: trocam constantes hardcoded gpt-4o-mini/gpt-4.1-nano por ProviderConfig.light_model (respeitando o provider ativo). - ProviderConfig.DEFAULT_CODEX_MODEL agora é gpt-5.2 (reconhecido pelo RubyLLM; gpt-5.4 não está no catalog do gem). Validado ponta-a-ponta: WhatsApp → Chatwoot → Jasmine → handoff Daniela → faq_lookup com embedding OK → resposta com preços corretos. Docs em docs/captain-codex-oauth.md. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
50 lines
1.5 KiB
Ruby
50 lines
1.5 KiB
Ruby
class Captain::Llm::EmbeddingService
|
|
include Integrations::LlmInstrumentation
|
|
|
|
class EmbeddingsError < StandardError; end
|
|
|
|
def initialize(account_id: nil)
|
|
Llm::Config.initialize!
|
|
@account_id = account_id
|
|
@embedding_model = InstallationConfig.find_by(name: 'CAPTAIN_EMBEDDING_MODEL')&.value.presence || LlmConstants::DEFAULT_EMBEDDING_MODEL
|
|
end
|
|
|
|
def self.embedding_model
|
|
InstallationConfig.find_by(name: 'CAPTAIN_EMBEDDING_MODEL')&.value.presence || LlmConstants::DEFAULT_EMBEDDING_MODEL
|
|
end
|
|
|
|
def get_embedding(content, model: @embedding_model)
|
|
return [] if content.blank?
|
|
|
|
instrument_embedding_call(instrumentation_params(content, model)) do
|
|
embed_with_legacy_openai(content, model)
|
|
end
|
|
rescue RubyLLM::Error => e
|
|
Rails.logger.error "Embedding API Error: #{e.message}"
|
|
raise EmbeddingsError, "Failed to create an embedding: #{e.message}"
|
|
end
|
|
|
|
private
|
|
|
|
# Embeddings sempre vão direto pra OpenAI tradicional — o endpoint Codex
|
|
# via ChatGPT OAuth não expõe /embeddings.
|
|
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
|
|
|
|
Llm::Config.with_api_key(legacy[:api_key], api_base: api_base) do |ctx|
|
|
ctx.embed(content, model: model).vectors
|
|
end
|
|
end
|
|
|
|
def instrumentation_params(content, model)
|
|
{
|
|
span_name: 'llm.captain.embedding',
|
|
model: model,
|
|
input: content,
|
|
feature_name: 'embedding',
|
|
account_id: @account_id
|
|
}
|
|
end
|
|
end
|