From ec4a6e993e28ad221e678dc6470897fb82c6f665 Mon Sep 17 00:00:00 2001 From: Nicolas Dular <ndular@gitlab.com> Date: Tue, 16 May 2023 08:33:32 +0000 Subject: [PATCH] Add new llm logger for debugging AI features Adds a new logger and debugging statements for AI features. --- doc/administration/logs/index.md | 12 ++++++++++++ doc/development/ai_features.md | 9 +++++++++ ee/app/services/llm/base_service.rb | 18 ++++++++++++++++-- ee/app/workers/llm/completion_worker.rb | 14 ++++++++++++++ ee/lib/gitlab/llm/anthropic/client.rb | 11 +++++++++-- .../graphql_subscription_response_service.rb | 8 +++++++- ee/lib/gitlab/llm/logger.rb | 11 +++++++++++ ee/lib/gitlab/llm/open_ai/client.rb | 7 ++++++- ee/lib/gitlab/llm/vertex_ai/client.rb | 14 ++++++++++++-- 9 files changed, 96 insertions(+), 8 deletions(-) create mode 100644 ee/lib/gitlab/llm/logger.rb diff --git a/doc/administration/logs/index.md b/doc/administration/logs/index.md index 7fab97f76da4c..3533237f94619 100644 --- a/doc/administration/logs/index.md +++ b/doc/administration/logs/index.md @@ -1034,6 +1034,18 @@ can be used. } ``` +## `llm.log` **(ULTIMATE SAAS)** + +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/120506) in GitLab 16.0. + +The `llm.log` file logs information related to +[AI features](../../user/ai_features.md). + +Depending on your installation method, this file is located at: + +- Omnibus GitLab: `/var/log/gitlab/gitlab-rails/llm.log` +- Installations from source: `/home/git/gitlab/log/llm.log` + ## Registry logs For Omnibus GitLab installations, Container Registry logs are in `/var/log/gitlab/registry/current`. diff --git a/doc/development/ai_features.md b/doc/development/ai_features.md index 6ed1d59c3e076..25da99addc1a7 100644 --- a/doc/development/ai_features.md +++ b/doc/development/ai_features.md @@ -89,6 +89,15 @@ To populate the embedding database for GitLab chat: 1. Open a rails console 1. Run [this script](https://gitlab.com/gitlab-com/gl-infra/production/-/issues/10588#note_1373586079) to populate the embedding database +### Debugging + +To gather more insights about the full request, use the `Gitlab::Llm::Logger` file to debug logs. +To follow the debugging messages related to the AI requests on the abstraction layer, you can use: + +```shell +tail -f log/llm.log +``` + ### Configure GCP Vertex access In order to obtain a GCP service key for local development, please follow the steps below: diff --git a/ee/app/services/llm/base_service.rb b/ee/app/services/llm/base_service.rb index 4930417dae910..dac7daef38d1d 100644 --- a/ee/app/services/llm/base_service.rb +++ b/ee/app/services/llm/base_service.rb @@ -8,10 +8,14 @@ def initialize(user, resource, options = {}) @user = user @resource = resource @options = options + @logger = Gitlab::Llm::Logger.build end def execute - return error(INVALID_MESSAGE) unless valid? + unless valid? + logger.debug(message: "Returning from Service due to validation") + return error(INVALID_MESSAGE) + end perform end @@ -24,7 +28,7 @@ def valid? private - attr_reader :user, :resource, :options + attr_reader :user, :resource, :options, :logger def perform raise NotImplementedError @@ -33,6 +37,16 @@ def perform def perform_async(user, resource, action_name, options) request_id = SecureRandom.uuid options[:request_id] = request_id + + logger.debug( + message: "Enqueuing CompletionWorker", + user_id: user.id, + resource_id: resource.id, + resource_class: resource.class.name, + request_id: request_id, + action_name: action_name + ) + ::Llm::CompletionWorker.perform_async(user.id, resource.id, resource.class.name, action_name, options) success(request_id: request_id) diff --git a/ee/app/workers/llm/completion_worker.rb b/ee/app/workers/llm/completion_worker.rb index 3571a87134537..ad9e1163e4127 100644 --- a/ee/app/workers/llm/completion_worker.rb +++ b/ee/app/workers/llm/completion_worker.rb @@ -10,6 +10,13 @@ class CompletionWorker urgency :low def perform(user_id, resource_id, resource_class, ai_action_name, options = {}) + logger.debug( + message: "Performing CompletionWorker", + user_id: user_id, + resource_id: resource_id, + action_name: ai_action_name + ) + return unless Feature.enabled?(:openai_experimentation) options.symbolize_keys! @@ -23,12 +30,19 @@ def perform(user_id, resource_id, resource_class, ai_action_name, options = {}) return unless resource.send_to_ai? params = { request_id: options.delete(:request_id) } + ai_completion = ::Gitlab::Llm::CompletionsFactory.completion(ai_action_name.to_sym, params) + logger.debug(message: "Getting Completion Service from factory", class_name: ai_completion.class.name) + ai_completion.execute(user, resource, options) if ai_completion end private + def logger + @logger ||= Gitlab::Llm::Logger.build + end + def find_resource(resource_id, resource_class) resource_class.classify.constantize.find_by_id(resource_id) end diff --git a/ee/lib/gitlab/llm/anthropic/client.rb b/ee/lib/gitlab/llm/anthropic/client.rb index edcb81c7b5789..d3bef2f51633a 100644 --- a/ee/lib/gitlab/llm/anthropic/client.rb +++ b/ee/lib/gitlab/llm/anthropic/client.rb @@ -13,21 +13,28 @@ class Client def initialize(user) @user = user + @logger = Gitlab::Llm::Logger.build end def complete(prompt:, **options) return unless enabled? - Gitlab::HTTP.post( + logger.debug("Performing request to Anthropic") + + response = Gitlab::HTTP.post( URI.join(URL, '/v1/complete'), headers: request_headers, body: request_body(prompt: prompt, options: options).to_json ) + + logger.debug("Received response from Anthropic") + + response end private - attr_reader :user + attr_reader :user, :logger retry_methods_with_exponential_backoff :complete diff --git a/ee/lib/gitlab/llm/graphql_subscription_response_service.rb b/ee/lib/gitlab/llm/graphql_subscription_response_service.rb index 829055e641941..7efe87693aa59 100644 --- a/ee/lib/gitlab/llm/graphql_subscription_response_service.rb +++ b/ee/lib/gitlab/llm/graphql_subscription_response_service.rb @@ -8,6 +8,7 @@ def initialize(user, resource, response_modifier, options:) @resource = resource @response_modifier = response_modifier @options = options + @logger = Gitlab::Llm::Logger.build end def execute @@ -22,12 +23,17 @@ def execute errors: response_modifier.errors } + logger.debug( + message: "Broadcasting AI response", + data: data + ) + GraphqlTriggers.ai_completion_response(user.to_global_id, resource.to_global_id, data) end private - attr_reader :user, :resource, :response_modifier, :options + attr_reader :user, :resource, :response_modifier, :options, :logger def generate_response_body(response_body) return response_body if options[:markup_format].nil? || options[:markup_format] == :raw diff --git a/ee/lib/gitlab/llm/logger.rb b/ee/lib/gitlab/llm/logger.rb new file mode 100644 index 0000000000000..32c1114b4b28f --- /dev/null +++ b/ee/lib/gitlab/llm/logger.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module Gitlab + module Llm + class Logger < ::Gitlab::JsonLogger + def self.file_name_noext + 'llm' + end + end + end +end diff --git a/ee/lib/gitlab/llm/open_ai/client.rb b/ee/lib/gitlab/llm/open_ai/client.rb index 938b332430e0d..92d73fbdeffcc 100644 --- a/ee/lib/gitlab/llm/open_ai/client.rb +++ b/ee/lib/gitlab/llm/open_ai/client.rb @@ -14,6 +14,7 @@ class Client def initialize(user, request_timeout: nil) @user = user @request_timeout = request_timeout + @logger = Gitlab::Llm::Logger.build end def chat(content:, moderated: nil, **options) @@ -71,7 +72,7 @@ def moderations(input:, **options) retry_methods_with_exponential_backoff :chat, :completions, :edits, :embeddings, :moderations - attr_reader :user, :request_timeout + attr_reader :user, :request_timeout, :logger def client @client ||= OpenAI::Client.new(access_token: access_token, request_timeout: request_timeout) @@ -108,10 +109,14 @@ def warn_if_moderated_unset(moderated, default:) def request(endpoint:, moderated:, **options) return unless enabled? + logger.debug(message: "Performing request to OpenAI", endpoint: endpoint) + moderate!(:input, moderation_input(endpoint, options)) if should_moderate?(:input, moderated) response = client.public_send(endpoint, **options) # rubocop:disable GitlabSecurity/PublicSend + logger.debug(message: "Received response from OpenAI") + track_cost(endpoint, response.parsed_response&.dig('usage')) if should_moderate?(:output, moderated) diff --git a/ee/lib/gitlab/llm/vertex_ai/client.rb b/ee/lib/gitlab/llm/vertex_ai/client.rb index f82c11ad23f86..c7cecf0a886f3 100644 --- a/ee/lib/gitlab/llm/vertex_ai/client.rb +++ b/ee/lib/gitlab/llm/vertex_ai/client.rb @@ -6,7 +6,9 @@ module VertexAi class Client include ::Gitlab::Llm::Concerns::ExponentialBackoff - def initialize(_user); end + def initialize(_user) + @logger = Gitlab::Llm::Logger.build + end # @param [String] content - Input string # @param [Hash] options - Additional options to pass to the request @@ -63,14 +65,22 @@ def code(content:, **options) private + attr_reader :logger + retry_methods_with_exponential_backoff :chat, :text, :code, :messages_chat def request(content:, config:, **options) - HTTParty.post( # rubocop: disable Gitlab/HTTParty + logger.debug(message: "Performing request to Vertex", config: config) + + response = HTTParty.post( # rubocop: disable Gitlab/HTTParty config.url, headers: config.headers, body: config.payload(content).merge(options).to_json ) + + logger.debug(message: "Received response from Vertex") + + response end def service_name -- GitLab