feat: migrate editor to ruby-llm (#12961)

Co-authored-by: aakashb95 <aakash@chatwoot.com>
Co-authored-by: Shivam Mishra <scm.mymail@gmail.com>
This commit is contained in:
Aakash Bakhle
2025-12-04 12:51:35 +05:30
committed by GitHub
parent 5c3b85334b
commit 87fe1e9ad7
9 changed files with 333 additions and 235 deletions

View File

@@ -2,9 +2,19 @@
require 'opentelemetry_config'
require_relative 'llm_instrumentation_constants'
require_relative 'llm_instrumentation_helpers'
module Integrations::LlmInstrumentation
include Integrations::LlmInstrumentationConstants
include Integrations::LlmInstrumentationHelpers
PROVIDER_PREFIXES = {
'openai' => %w[gpt- o1 o3 o4 text-embedding- whisper- tts-],
'anthropic' => %w[claude-],
'google' => %w[gemini-],
'mistral' => %w[mistral- codestral-],
'deepseek' => %w[deepseek-]
}.freeze
def tracer
@tracer ||= OpentelemetryConfig.tracer
@@ -13,33 +23,38 @@ module Integrations::LlmInstrumentation
def instrument_llm_call(params)
return yield unless ChatwootApp.otel_enabled?
result = nil
executed = false
tracer.in_span(params[:span_name]) do |span|
setup_span_attributes(span, params)
result = yield
executed = true
record_completion(span, result)
result
end
rescue StandardError => e
ChatwootExceptionTracker.new(e, account: params[:account]).capture_exception
yield
ChatwootExceptionTracker.new(e, account: resolve_account(params)).capture_exception
executed ? result : yield
end
def instrument_agent_session(params)
return yield unless ChatwootApp.otel_enabled?
result = nil
executed = false
tracer.in_span(params[:span_name]) do |span|
set_metadata_attributes(span, params)
# By default, the input and output of a trace are set from the root observation
span.set_attribute(ATTR_LANGFUSE_OBSERVATION_INPUT, params[:messages].to_json)
result = yield
executed = true
span.set_attribute(ATTR_LANGFUSE_OBSERVATION_OUTPUT, result.to_json)
result
end
rescue StandardError => e
ChatwootExceptionTracker.new(e, account: params[:account]).capture_exception
yield
ChatwootExceptionTracker.new(e, account: resolve_account(params)).capture_exception
executed ? result : yield
end
def instrument_tool_call(tool_name, arguments)
@@ -55,8 +70,27 @@ module Integrations::LlmInstrumentation
end
end
def determine_provider(model_name)
return 'openai' if model_name.blank?
model = model_name.to_s.downcase
PROVIDER_PREFIXES.each do |provider, prefixes|
return provider if prefixes.any? { |prefix| model.start_with?(prefix) }
end
'openai'
end
private
def resolve_account(params)
return params[:account] if params[:account].is_a?(Account)
return Account.find_by(id: params[:account_id]) if params[:account_id].present?
nil
end
def setup_span_attributes(span, params)
set_request_attributes(span, params)
set_prompt_messages(span, params[:messages])
@@ -68,7 +102,8 @@ module Integrations::LlmInstrumentation
end
def set_request_attributes(span, params)
span.set_attribute(ATTR_GEN_AI_PROVIDER, 'openai')
provider = determine_provider(params[:model])
span.set_attribute(ATTR_GEN_AI_PROVIDER, provider)
span.set_attribute(ATTR_GEN_AI_REQUEST_MODEL, params[:model])
span.set_attribute(ATTR_GEN_AI_REQUEST_TEMPERATURE, params[:temperature]) if params[:temperature]
end
@@ -95,37 +130,4 @@ module Integrations::LlmInstrumentation
span.set_attribute(format(ATTR_LANGFUSE_METADATA, key), value.to_s)
end
end
def set_completion_attributes(span, result)
set_completion_message(span, result)
set_usage_metrics(span, result)
set_error_attributes(span, result)
end
def set_completion_message(span, result)
message = result[:message] || result.dig('choices', 0, 'message', 'content')
return if message.blank?
span.set_attribute(ATTR_GEN_AI_COMPLETION_ROLE, 'assistant')
span.set_attribute(ATTR_GEN_AI_COMPLETION_CONTENT, message)
end
def set_usage_metrics(span, result)
usage = result[:usage] || result['usage']
return if usage.blank?
span.set_attribute(ATTR_GEN_AI_USAGE_INPUT_TOKENS, usage['prompt_tokens']) if usage['prompt_tokens']
span.set_attribute(ATTR_GEN_AI_USAGE_OUTPUT_TOKENS, usage['completion_tokens']) if usage['completion_tokens']
span.set_attribute(ATTR_GEN_AI_USAGE_TOTAL_TOKENS, usage['total_tokens']) if usage['total_tokens']
end
def set_error_attributes(span, result)
error = result[:error] || result['error']
return if error.blank?
error_code = result[:error_code] || result['error_code']
span.set_attribute(ATTR_GEN_AI_RESPONSE_ERROR, error.to_json)
span.set_attribute(ATTR_GEN_AI_RESPONSE_ERROR_CODE, error_code) if error_code
span.status = OpenTelemetry::Trace::Status.error("API Error: #{error_code}")
end
end