サードパーティとのインテグレーションに基づくAI機能

GitLab 15.11 で導入

機能

  • 長時間実行されるAPIリクエストの非同期実行
    • GraphQLアクションがリクエストを開始
    • バックグラウンドワーカーの実行
    • GraphQLサブスクリプションはリアルタイムで結果を返します。
  • 抽象化
    • OpenAI
    • グーグル バーテックスAI
    • アンソロピック
  • 速度制限
  • サーキットブレーカー
  • マルチレベル機能フラグ
  • グループレベルでのライセンスチェック
  • 除雪車の実行追跡
  • Prometheus で使用されたトークンのトラッキング
  • 入力のモデレーションチェック設定
  • 応答の自動マークダウンレンダリング
  • 実験およびサードパーティのグループレベルの集中設定
  • GitLabチームメンバーが認証情報なしでAI APIを探索できる実験用APIエンドポイント
    • OpenAI
    • グーグル バーテックスAI
    • アンソロピック

フィーチャーフラグ

以下の2つの機能フラグを任意のAIフィーチャーワークに適用します:

  • すべてのAI機能に適用される一般的なもの。
  • その機能に固有のフラグ。機能フラグ名は、ライセンスされた機能名とは異なる必要があります。

すべての機能フラグの一覧と使用方法については、機能フラグトラッカーを参照してください。

新しいAIアクションの実装

新しいAIアクションを実装するには、優先AIプロバイダに接続します。このAPIへの接続には

  • Experimental REST API。
  • 抽象化レイヤー。

すべてのAI機能は実験的なものです。

ローカルでAI機能をテスト

note
次のセクションの自動化には、このスニペットを使いましょう。
  1. 必要な一般機能フラグを有効にします:

    Feature.enable(:ai_related_settings)
    Feature.enable(:openai_experimentation)
    Feature.enable(:tofa_experimentation_main_flag)
    Feature.enable(:anthropic_experimentation)
    
  2. SaaSをシミュレートするためにGDKをシミュレートし、テストするグループがUltimateライセンスを持っていることを確認します。
  3. Experimental features を有効にしThird-party AI services
    1. Ultimateライセンスを持つグループに移動します。
    2. グループ設定>一般->権限とグループ機能
    3. 実験機能の有効化
    4. サードパーティAIサービスの有効化
  4. テストしたい機能の機能フラグを有効にします。
  5. 必要なアクセストークンを設定します。アクセストークンを受け取るには
    1. Vertexの場合は、以下の手順に従ってください。
    2. AnthropicやOpenAIのような他のプロバイダーについては、@m_gill@wayne@timzallmann が技術スタックのオーナーであるアクセスリクエストを作成してください。

埋め込みデータベースのセットアップ

note
次のセクションの自動化には、このスニペットを使いましょう。

埋め込みデータベースを使用する機能については、追加のセットアップが必要です。

  1. GDKでpgvectorを有効化
  2. GDKで埋め込みデータベースを有効化

      gdk config set gitlab.rails.databases.embedding.enabled true
    
  3. 走るgdk reconfigure
  4. データベースマイグレーションを実行し、埋め込みデータベースを作成します。

GitLab ドキュメンテーションチャット(レガシーチャット)のセットアップ

GitLabチャットの埋め込みデータベースを設定します:

  1. Rails コンソールを開きます。
  2. このスクリプトを実行して、埋め込みデータベースにデータを投入します。

GCP Vertexアクセスの設定

ローカル開発用のGCPサービスキーを取得するには、以下の手順に従ってください:

  • このページにアクセスし、指示に従ってサンドボックスGCP環境を作成するか、このテンプレートを使用して既存のグループ環境へのアクセスをリクエストしてください。
  • GCPコンソールで、IAM & Admin >Service Accounts 、”Create new service account “ボタンをクリックします。
  • サービスアカウントには、使用目的に応じた名前を付けます。Create and Continue」を選択します。Grant this service account access to project の下で、ロールVertex AI User を選択します。Continue を選択します。Done
  • 新しいサービスアカウントを選択し、Manage keys >Add Key >Create new key 。これにより、サービスアカウントの非公開JSON認証情報がダウンロードされます。
  • Railsコンソールを開きます。設定を次のように更新します:
Gitlab::CurrentSettings.update(vertex_ai_credentials: File.read('/YOUR_FILE.json'))

# Note: These credential examples will not work locally for all models
Gitlab::CurrentSettings.update(vertex_ai_host: "<root-domain>") # Example: us-central1-aiplatform.googleapis.com
Gitlab::CurrentSettings.update(vertex_ai_project: "<project-id>") # Example: cloud-large-language-models

内部チームのメンバーは、これらのエンドポイントの設定にこのスニペットを使用できます。

OpenAI アクセスの設定

Gitlab::CurrentSettings.update(openai_api_key: "<open-ai-key>")

Anthropicのアクセス設定

Feature.enable(:anthropic_experimentation)
Gitlab::CurrentSettings.update!(anthropic_api_key: <insert API key>)

埋め込みフィクスチャの作成と使用

GitLab Documentation用のembeddingsを開発者用データベースに投入するには、事前に生成されたembeddingsとRakeテストを使うことができます。

RAILS_ENV=development bundle exec rake gitlab:llm:embeddings:seed_pre_generated

私たちが使っている DBCleaner gem は、テストを実行する前にデータベースのテーブルをクリアします。ドキュメントの埋め込みを格納するテーブルtanuki_bot_mvc に完全に埋め込む代わりに、事前に生成されたフィクスチャから選択した埋め込みをテーブルに追加することができます。

例えば、”How can I reset my password “という質問が、関連する埋め込みを正しく取得し、回答しているかテストするために、質問に対する上位N個の埋め込みをフィクスチャに抽出し、少数の埋め込みだけを素早くリストアすることができます。抽出処理を容易にするために、Rakeタスクが書かれています。新しいフィクスチャを生成するためにRakeタスクを実行します。

RAILS_ENV=development bundle exec rake gitlab:llm:embeddings:extract_embeddings

埋め込みデータを使用する必要がある仕様では、RSpecの設定フック:ai_embedding_fixtures

context 'when asking about how to use GitLab', :ai_embedding_fixtures do
  # ...examples
end

GitLab Duo チャットでの作業

GitLab Duo Chatで作業するためのガイドラインをご覧ください。

実験的 REST API

実験的な REST API エンドポイントを使用して、AI 機能の実験やプロトタイプ作成をすばやく実行できます。

エンドポイントは以下の通りです:

  • https://gitlab.example.com/api/v4/ai/experimentation/openai/completions
  • https://gitlab.example.com/api/v4/ai/experimentation/openai/embeddings
  • https://gitlab.example.com/api/v4/ai/experimentation/openai/chat/completions
  • https://gitlab.example.com/api/v4/ai/experimentation/anthropic/complete
  • https://gitlab.example.com/api/v4/ai/experimentation/tofa/chat

これらのエンドポイントはプロトタイプ用であり、顧客に機能を提供するためのものではありません。実験的なエンドポイントは、GitLabチームメンバーの本番環境でのみ利用可能です。GitLab API トークンを使って認証してください。

抽象化レイヤー

GraphQL API

抽象化レイヤーを使用してAIプロバイダーAPIに接続するには、aiActionと呼ばれる拡張可能なGraphQL APIを使用します。input はキーと値のペアを受け付けます。key は実行する必要があるアクションです。変異リクエストごとに1つのAIアクションしか許可しません。

突然変異の例

mutation {
  aiAction(input: {summarizeComments: {resourceId: "gid://gitlab/Issue/52"}}) {
    clientMutationId
  }
}

例として、「コードを説明する」アクションを作りたいとします。そのために、input を新しいキーexplainCodeで拡張します。変異は次のようになります:

mutation {
  aiAction(input: {explainCode: {resourceId: "gid://gitlab/MergeRequest/52", code: "foo() { console.log()" }}) {
    clientMutationId
  }
}

GraphQL API はOpenAI Clientを使用してレスポンスを送信します。

他のクライアントも利用可能であり、OpenAIを使用すべきではないことを覚えておいてください。

応答の受け取り方

OpenAI API のリクエストはバックグラウンドジョブで処理されるため、リクエストは保持されず、レスポンスはaiCompletionResponse のサブスクリプションを通じて送信されます:

subscription aiCompletionResponse($userId: UserID, $resourceId: AiModelID!) {
  aiCompletionResponse(userId: $userId, resourceId: $resourceId) {
    responseBody
    errors
  }
}
caution
変異が送信されたら、サブスクリプションを購読するだけです。複数のサブスクリプションが同じページでアクティビティされている場合、我々の識別子がユーザーとリソースであるため、現在すべてのサブスクリプションが更新を受け取ります。これを軽減するために、変異が送信されたときだけサブスクライブするべきです。この場合、[skip()](skip()この場合、skip()skip())を使うことができます。将来的にこの問題を防ぐために、リクエスト識別子を実装します。

現在の抽象化レイヤーの流れ

以下のグラフはOpenAIを例にしています。別のプロバイダを使用することもできます。

flowchart TD A[GitLab frontend] -->B[AiAction GraphQL mutation] B --> C[Llm::ExecuteMethodService] C --> D[One of services, for example: Llm::GenerateSummaryService] D -->|scheduled| E[AI worker:Llm::CompletionWorker] E -->F[::Gitlab::Llm::Completions::Factory] F -->G[`::Gitlab::Llm::OpenAi::Completions::...` class using `::Gitlab::Llm::OpenAi::Templates::...` class] G -->|calling| H[Gitlab::Llm::OpenAi::Client] H --> |response| I[::Gitlab::Llm::OpenAi::ResponseService] I --> J[GraphqlTriggers.ai_completion_response] J --> K[::GitlabSchema.subscriptions.trigger]

サーキットブレーカー

CircuitBreaker 関数は、サーキットブレーカー保護機能を持つコードを実行する必要があるクラスに含めることができる再利用可能なモジュールです。このコンサーンは、サーキットブレーカー機能を持つコードブロックをラップするrun_with_circuit メソッドを提供し、カスケード障害を防ぎ、システムの回復力を向上させます。サーキット・ブレーカー・パターンの詳細については、以下を参照してください:

CircuitBreakerの使用

CircuitBreakerを使用するには、クラスに含める必要があります。例えば

class MyService
  include Gitlab::Llm::Concerns::CircuitBreaker

  def call_external_service
    run_with_circuit do
      # Code that interacts with external service goes here

      raise InternalServerError
    end
  end
end

call_external_service メソッドは、外部サービスと対話するメソッドの例です。外部サービスと対話するコードをrun_with_circuitでラップすることで、このメソッドはサーキットブレーカ内で実行されます。サーキット・ブレーカは、circuit メソッドによって作成および設定されます。このメソッドは、CircuitBreaker モジュールがインクルードされると自動的に呼び出されます。このメソッドは、InternalServerError エラーを発生させる必要があります。このエラーは、コード・ブロックの実行中に発生した場合、エラーしきい値にカウントされます。

サーキットブレーカはエラーの数とリクエストのレートを追跡し、設定されたエラーのしきい値またはボリュームのしきい値に達すると回路を開きます。回路が開かれている場合、後続のリクエストはコードブロックを実行することなく高速に失敗します。サーキットブレーカは回路を再び閉じる前に、サービスの可用性をテストするために定期的に少数のリクエストを通過させます。

設定

サーキットブレーカは、回路が開くエラー数と要求数を制御する2つの定数で設定されています:

  • ERROR_THRESHOLD
  • VOLUME_THRESHOLD

これらの値は、特定のサービスや使用パターンに応じて調整することができます。InternalServerError は、コードブロックの実行中に発生した場合にエラーしきい値にカウントされる例外クラスです。これは、外部サービスとやりとりするコードによって発生したときにサーキットブレーカーをトリガーする例外クラスです。

note
CircuitBreaker モジュールは、サーキットブレーカの実装を提供するためにCircuitbox gem に依存しています。デフォルトでは、サービス名は懸念モジュールが含まれているクラス名から推測されます。異なる名前にする必要がある場合は、service_name メソッドをオーバーライドしてください。

テスト

CircuitBreaker を使用するコードをテストするには、RSpec の共有サンプルを使用し、servicesubject の変数を渡します:

it_behaves_like 'has circuit breaker' do
  let(:service) { dummy_class.new }
  let(:subject) { service.dummy_method }
end

新しいアクションの実装方法

新しいメソッドの登録

Llm::ExecuteMethodService にアクセスし、これから作成する新しいサービスクラスで新しいメソッドを追加します。

class ExecuteMethodService < BaseService
  METHODS = {
    # ...
    amazing_new_ai_feature: Llm::AmazingNewAiFeatureService
  }.freeze

サービスの作成

  1. ee/app/services/llm/ の下に新しいサービスを作成し、BaseService から継承します。
  2. resource は、操作したいオブジェクトです。Ai::Model を含むオブジェクトであれば何でもかまいません。例えば、ProjectMergeRequestIssueなどです。
# ee/app/services/llm/amazing_new_ai_feature_service.rb

module Llm
  class AmazingNewAiFeatureService < BaseService
    private

    def perform
      ::Llm::CompletionWorker.perform_async(user.id, resource.id, resource.class.name, :amazing_new_ai_feature)
      success
    end

    def valid?
      super && Ability.allowed?(user, :amazing_new_ai_feature, resource)
    end
  end
end

作成者

機能の作成者は、ポリシーを使用して認可を行うことをお勧めします。現在のところ、以下のチェックをカバーする必要があります:

  1. 一般的なAI機能フラグが有効であること。
  2. 機能別フラグが有効
  3. ネームスペースに、その機能に必要なライセンスがあります。
  4. ユーザがグループ/プロジェクトのメンバであること
  5. experiment_features_enabled のメンバーであり、third_party_ai_features_enabled フラグがNamespace

この例では、allowed?(:amazing_new_ai_feature) コールを実装する必要があります。例として、コメント要約機能のイシュー・ポリシーを参照してください。この例では、イシューにもこの機能を実装します:

# ee/app/policies/ee/issue_policy.rb

module EE
  module IssuePolicy
    extend ActiveSupport::Concern
    prepended do
      with_scope :subject
      condition(:ai_available) do
        ::Feature.enabled?(:openai_experimentation)
      end

      with_scope :subject
      condition(:amazing_new_ai_feature_enabled) do
        ::Feature.enabled?(:amazing_new_ai_feature, subject_container) &&
          subject_container.licensed_feature_available?(:amazing_new_ai_feature)
      end

      rule do
        ai_available & amazing_new_ai_feature_enabled & is_project_member
      end.enable :amazing_new_ai_feature
    end
  end
end

リクエストとレスポンスのペアリング

複数のユーザーのリクエストを並行して処理することができるため、レスポンスを受け取るときに、レスポンスとその元のリクエストをペアリングするのが難しい場合があります。requestId リクエストとレスポンスは同じ requestIdUUIDを持つことがrequestId 保証されているので requestId、このrequestId フィールドはこの目的のために使う requestIdことができます。

キャッシュ

AIのリクエストとレスポンスはキャッシュすることができます。キャッシュされた会話は、AI機能とユーザーとの対話を表示するために使用されます。現在の実装では、このキャッシュは、ユーザーがリクエストを繰り返したときにAIサービスへの連続コールをスキップするためには使用されません。

query {
  aiMessages {
    nodes {
      id
      requestId
      content
      role
      errors
      timestamp
    }
  }
}

このキャッシュは、チャット機能で特に役立ちます。その他のサービスでは、キャッシュは無効になっています。(cache_response: true オプションを使用することで、サービスに対して有効にすることができます)。

キャッシュには以下の制限があります:

  • メッセージは Redis ストリームに保存されます。
  • ユーザーごとにメッセージのストリームは1つです。これは、現在すべてのサービスが同じキャッシュを共有していることを意味します。必要であれば、ユーザーごとに複数のストリームに拡張することもできます(Redisが推定メッセージ量を処理できることをインフラチームに確認した上で)。
  • 直近の50メッセージ(リクエスト+レスポンス)のみが保存されます。
  • ストリームの有効期限は、最後のメッセージを追加してから3日間です。
  • ユーザーは自分のメッセージにのみアクセスできます。キャッシュ・レベルでの作成者は存在せず、(現在のユーザー以外がアクセスする場合は)サービス・レイヤでの作成が期待されます。

名前空間の設定に基づき、このリソースに対して機能が許可されているかどうかをチェックします。

AI 機能の使用を制限する、ルート名前空間レベルで許可される 2 つの設定があります:

  • experiment_features_enabled
  • third_party_ai_features_enabled.

指定された名前空間でその機能が許可されているかどうかを確認するには、以下を呼び出します:

Gitlab::Llm::StageCheck.available?(namespace, :name_of_the_feature)

Gitlab::Llm::StageCheck クラスに機能の名前を追加します。そこには、実験的な機能とベータ版の機能を区別する配列があります。

これで、次のようなさまざまなケースに対応できるようになります:

  • その機能がどの配列にもない場合、チェックはtrue を返します。例えば、その機能がGAに移動され、サードパーティの設定を使用していない場合です。
  • feature が GA にあり、サードパーティの設定を使用している場合、このクラスは名前空間のサードパーティの設定に基づいて適切な回答を返します。

フィーチャーを実験フェーズからベータフェーズに移動するには、フィーチャーの名前をEXPERIMENTAL_FEATURES 配列からBETA_FEATURES 配列に移動します。

AI APIへの呼び出しとプロンプトの実装

CompletionWorkerCompletions::Factory を呼び出します。 は Service を初期化し、API への実際の呼び出しを実行します。この例では、OpenAIを使用し、2つの新しいクラスを実装します:

# /ee/lib/gitlab/llm/open_ai/completions/amazing_new_ai_feature.rb

module Gitlab
  module Llm
    module OpenAi
      module Completions
        class AmazingNewAiFeature
          def initialize(ai_prompt_class)
            @ai_prompt_class = ai_prompt_class
          end

          def execute(user, issue, options)
            options = ai_prompt_class.get_options(options[:messages])

            ai_response = Gitlab::Llm::OpenAi::Client.new(user).chat(content: nil, **options)

            ::Gitlab::Llm::OpenAi::ResponseService.new(user, issue, ai_response, options: {}).execute(
              Gitlab::Llm::OpenAi::ResponseModifiers::Chat.new
            )
          end

          private

          attr_reader :ai_prompt_class
        end
      end
    end
  end
end
# /ee/lib/gitlab/llm/open_ai/templates/amazing_new_ai_feature.rb

module Gitlab
  module Llm
    module OpenAi
      module Templates
        class AmazingNewAiFeature
          TEMPERATURE = 0.3

          def self.get_options(messages)
            system_content = <<-TEMPLATE
              You are an assistant that writes code for the following input:
              """
            TEMPLATE

            {
              messages: [
                { role: "system", content: system_content },
                { role: "user", content: messages },
              ],
              temperature: TEMPERATURE
            }
          end
        end
      end
    end
  end
end

OpenAIは複数のAIプロバイダをサポートしているため、同じ例でそれらのプロバイダを使用することもできます:

Gitlab::Llm::VertexAi::Client.new(user)
Gitlab::Llm::Anthropic::Client.new(user)

AiアクションのGraphQLへの追加

TODO

セキュリティ

人工知能(AI) 機能のセキュアコーディングガイドラインを参照してください。