Compare commits

...
6 changed files with 90 additions and 15 deletions
+2 -1
View File
@@ -87,6 +87,7 @@ Metrics/ModuleLength:
Rails/HelperInstanceVariable:
Exclude:
- enterprise/app/helpers/captain/chat_helper.rb
- enterprise/app/helpers/captain/loggable.rb
Rails/ApplicationController:
Exclude:
@@ -336,4 +337,4 @@ FactoryBot/RedundantFactoryOption:
Enabled: false
FactoryBot/FactoryAssociationWithStrategy:
Enabled: false
Enabled: false
+23 -11
View File
@@ -1,19 +1,21 @@
module Captain::ChatHelper
include Captain::Loggable
def request_chat_completion
log_chat_completion_request
response = @client.chat(
parameters: {
model: @model,
messages: @messages,
tools: @tool_registry&.registered_tools || [],
response_format: { type: 'json_object' },
temperature: @assistant&.config&.[]('temperature').to_f || 1
}
)
tools = @tool_registry&.registered_tools || []
temperature = @assistant&.config&.[]('temperature').to_f || 1
log_captain_request(tools, temperature)
start_time = Time.current
response = @client.chat(parameters: chat_parameters(tools, temperature))
duration_ms = ((Time.current - start_time) * 1000).round
log_captain_response(response, duration_ms)
handle_response(response)
rescue StandardError => e
log_captain_error(e)
Rails.logger.error "#{self.class.name} Assistant: #{@assistant.id}, Error in chat completion: #{e}"
raise e
end
@@ -61,6 +63,7 @@ module Captain::ChatHelper
'assistant_thinking'
)
result = @tool_registry.send(function_name, arguments)
log_captain_tool_call(function_name, arguments, result)
persist_message(
{
content: I18n.t('captain.copilot.completed_tool_call', function_name: function_name),
@@ -94,8 +97,17 @@ module Captain::ChatHelper
def log_chat_completion_request
Rails.logger.info(
"#{self.class.name} Assistant: #{@assistant.id}, Requesting chat completion
for messages #{@messages} with #{@tool_registry&.registered_tools&.length || 0} tools
"
for messages #{@messages} with #{@tool_registry&.registered_tools&.length || 0} tools"
)
end
def chat_parameters(tools, temperature)
{
model: @model,
messages: @messages,
tools: tools,
response_format: { type: 'json_object' },
temperature: temperature
}
end
end
@@ -0,0 +1,54 @@
module Captain::Loggable
private
def log_captain_activity(event_type, log_data = {})
return if ENV['CAPTAIN_ENHANCED_LOGGING'].blank?
conversation_id = log_data[:conversation_id]
prefix = conversation_id ? "[##{conversation_id}]" : ''
# Add timestamp to all log data
log_data[:timestamp] = Time.current.iso8601
Rails.logger.info "[CAPTAIN_DEBUG] #{prefix} [#{event_type}] #{JSON.generate(log_data)}"
end
def log_captain_request(tools, temperature)
log_captain_activity(Llm::BaseOpenAiService::REQUEST, {
conversation_id: @conversation&.id,
assistant_id: @assistant&.id,
model: @model,
temperature: temperature,
messages_count: @messages.size,
tools_count: tools.size,
last_message: @messages.last
})
end
def log_captain_response(response, duration_ms)
log_captain_activity(Llm::BaseOpenAiService::RESPONSE, {
conversation_id: @conversation&.id,
duration_ms: duration_ms,
response: response.dig('choices', 0, 'message'),
usage: response['usage']
})
end
def log_captain_error(error)
log_captain_activity(Llm::BaseOpenAiService::ERROR, {
conversation_id: @conversation&.id,
error_class: error.class.name,
error_message: error.message,
context: { model: @model, assistant_id: @assistant&.id }
})
end
def log_captain_tool_call(function_name, arguments, result)
log_captain_activity(Llm::BaseOpenAiService::TOOL_CALL, {
conversation_id: @conversation&.id,
tool_name: function_name,
arguments: arguments,
result: result.to_s.truncate(1000)
})
end
end
@@ -26,7 +26,7 @@ class Captain::Conversation::ResponseBuilderJob < ApplicationJob
delegate :account, :inbox, to: :@conversation
def generate_and_process_response
@response = Captain::Llm::AssistantChatService.new(assistant: @assistant).generate_response(
@response = Captain::Llm::AssistantChatService.new(assistant: @assistant, conversation: @conversation).generate_response(
message_history: collect_previous_messages
)
@@ -3,10 +3,11 @@ require 'openai'
class Captain::Llm::AssistantChatService < Llm::BaseOpenAiService
include Captain::ChatHelper
def initialize(assistant: nil)
def initialize(assistant: nil, conversation: nil)
super()
@assistant = assistant
@conversation = conversation
@messages = [system_message]
@response = ''
register_tools
@@ -1,6 +1,12 @@
class Llm::BaseOpenAiService
DEFAULT_MODEL = 'gpt-4o-mini'.freeze
# Captain logging event types
REQUEST = 'REQUEST'.freeze
RESPONSE = 'RESPONSE'.freeze
TOOL_CALL = 'TOOL_CALL'.freeze
ERROR = 'ERROR'.freeze
def initialize
@client = OpenAI::Client.new(
access_token: InstallationConfig.find_by!(name: 'CAPTAIN_OPEN_AI_API_KEY').value,
@@ -15,7 +21,8 @@ class Llm::BaseOpenAiService
private
def uri_base
InstallationConfig.find_by(name: 'CAPTAIN_OPEN_AI_ENDPOINT')&.value || 'https://api.openai.com/'
endpoint = InstallationConfig.find_by(name: 'CAPTAIN_OPEN_AI_ENDPOINT')&.value
endpoint.presence || 'https://api.openai.com/'
end
def setup_model