Read OSS

AI ゲートウェイとしての Kong:LLM ドライバーアーキテクチャ

中級

前提知識

  • 第1回:アーキテクチャと Nginx インテグレーション
  • 第4回:プラグインシステムとイテレーター(プラグインハンドラーパターン)
  • LLM API の基本的な理解(チャット補完、ストリーミング、トークン)

AI ゲートウェイとしての Kong:LLM ドライバーアーキテクチャ

Kong の最新の大型機能が、AI ゲートウェイとしての機能です。これは、大規模言語モデル(LLM)プロバイダーへのリクエストをプロキシ・変換・観測するためのサブシステムです。別途 AI プロキシを構築するのではなく、Kong は LLM サポートをプラグインアーキテクチャへと直接組み込みました。これにより、本シリーズで紹介してきたフェーズパイプライン・設定システム・オブザービリティ基盤をそのまま活用できます。

設計の中核となるのがドライバーパターンです。各 LLM プロバイダー(OpenAI、Anthropic、Azure、AWS Bedrock、Google Gemini、Cohere、Hugging Face)は、標準インターフェースを持つドライバーモジュールとして実装されています。共通の HTTP 変換処理は共有ユーティリティモジュールが担い、クラウド固有の認証はアダプターモジュールへと分離されています。

LLM モジュールのアーキテクチャとフォーマット検出

LLM サブシステムは kong/llm/ に配置されており、エントリーポイントは kong/llm/init.lua です。このモジュールの最初の役割はフォーマット検出です。受信リクエストがチャット補完かテキスト補完かを判別します。

local function identify_request(request)
  local formats = {}
  if type(request.messages) == "table" and #request.messages > 0 then
    table.insert(formats, "llm/v1/chat")
  end
  if type(request.prompt) == "string" then
    table.insert(formats, "llm/v1/completions")
  end
  -- ...
end

Kong の標準フォーマットは OpenAI 互換です。メッセージ配列を使うリクエストには llm/v1/chat、単一プロンプトのリクエストには llm/v1/completions を使用します。67〜82行目にある is_compatible 関数は、リクエストが想定されるルートタイプに合致するかを確認します。フォーマット検証をスキップしてリクエストをそのまま通す preserve モードも備えています。

利用可能なドライバーモジュールは、主要な LLM プロバイダーをカバーしています:

Driver File Provider
openai kong/llm/drivers/openai.lua OpenAI API
anthropic kong/llm/drivers/anthropic.lua Anthropic Claude
azure kong/llm/drivers/azure.lua Azure OpenAI
bedrock kong/llm/drivers/bedrock.lua AWS Bedrock
gemini kong/llm/drivers/gemini.lua Google Gemini
cohere kong/llm/drivers/cohere.lua Cohere
huggingface kong/llm/drivers/huggingface.lua Hugging Face
mistral kong/llm/drivers/mistral.lua Mistral AI
llama2 kong/llm/drivers/llama2.lua Llama 2 (self-hosted)
flowchart TD
    A[Incoming Request] --> B{Format Detection}
    B --> C["llm/v1/chat<br>(messages array)"]
    B --> D["llm/v1/completions<br>(prompt string)"]
    C --> E{Route Type Match?}
    D --> E
    E -->|Compatible| F[Select Driver]
    E -->|Incompatible| G[400 Error]
    F --> H[Transform to Provider Format]
    H --> I[Send to LLM Provider]
    I --> J[Transform Response to Kong Format]

ドライバーパターン:プロバイダーの抽象化

各ドライバーモジュールは to_formatfrom_format のトランスフォーマー関数で構成された標準インターフェースを実装しています。kong/llm/drivers/openai.lua は最もシンプルなドライバーです。Kong の標準フォーマットそのものが OpenAI フォーマットだからです。

local transformers_to = {
  ["llm/v1/chat"] = function(request_table, model_info, route_type)
    request_table.model = model_info.name or request_table.model
    request_table.stream = request_table.stream or false
    request_table.top_k = nil  -- unsupported by OpenAI
    return request_table, "application/json", nil
  end,
}

kong/llm/drivers/anthropic.lua ドライバーは対照的に、フォーマット間の変換が必要です。旧来の Claude モデルでは、Kong のメッセージ配列を Anthropic の Human:/Assistant: プロンプト形式に変換します:

local function kong_messages_to_claude_prompt(messages)
  local buf = buffer.new()
  for _, v in ipairs(messages) do
    if v.role == "assistant" then
      buf:put("Assistant: ")
    elseif v.role == "user" then
      buf:put("Human: ")
    end
    buf:put(v.content)
    buf:put("\n\n")
  end
  buf:put("Assistant:")
  return buf:get()
end

kong/llm/drivers/shared.lua の共有ドライバーユーティリティは、すべてのドライバーが利用する共通機能を提供しています。HTTP クライアント管理、ストリーミングのコンテントタイプ検出、SSE パース、そしてオブザービリティ用のログエントリーキー定数などが含まれます。このモジュールは利用状況を追跡するための標準キーを定義しています:

local log_entry_keys = {
  USAGE_CONTAINER = "usage",
  PROMPT_TOKENS = "prompt_tokens",
  COMPLETION_TOKENS = "completion_tokens",
  TOTAL_TOKENS = "total_tokens",
  TIME_PER_TOKEN = "time_per_token",
  COST = "cost",
}
classDiagram
    class SharedDriver {
        +_CONST: SSE_TERMINATOR, etc.
        +_SUPPORTED_STREAMING_CONTENT_TYPES
        +log_entry_keys
        +HTTP utilities
    }
    class OpenAIDriver {
        +to_format(request, model_info)
        +from_format(response, model_info)
        +DRIVER_NAME: "openai"
    }
    class AnthropicDriver {
        +to_format(request, model_info)
        +from_format(response, model_info)
        +kong_messages_to_claude_prompt()
        +DRIVER_NAME: "anthropic"
    }
    class BedrockDriver {
        +to_format(request, model_info)
        +from_format(response, model_info)
        +DRIVER_NAME: "bedrock"
    }
    SharedDriver <|-- OpenAIDriver
    SharedDriver <|-- AnthropicDriver
    SharedDriver <|-- BedrockDriver

クラウドアダプターと認証

クラウドホスト型 LLM サービスへの認証処理は、ドライバーロジックとは分離されています。クラウド固有のアダプターが認証情報の管理を担うため、フォーマット変換コードが複雑になりません。

アダプターモジュールは kong/llm/adapters/ に配置されています:

  • bedrock.luaresty.aws を使った AWS SigV4 リクエスト署名
  • gemini.luaresty.gcp を通じた Google Cloud サービスアカウント認証

kong/llm/drivers/shared.lua の共有ドライバーモジュールは、ロード時にクラウド SDK を初期化します:

local GCP = require("resty.gcp.request.credentials.accesstoken")
local aws_config = require "resty.aws.config"
local AWS = require("resty.aws")
local AWS_REGION = os.getenv("AWS_REGION") or os.getenv("AWS_DEFAULT_REGION")

kong/llm/schemas/init.lua の認証スキーマは、ヘッダーベース認証(API キー)、クエリパラメーター認証、クラウドネイティブ認証に対応した柔軟な設定を提供します:

local auth_schema = {
  type = "record",
  fields = {
    { header_name = { type = "string", referenceable = true }},
    { header_value = { type = "string", encrypted = true, referenceable = true }},
    { param_name = { type = "string", referenceable = true }},
    { param_value = { type = "string", encrypted = true, referenceable = true }},
  },
}

encrypted = truereferenceable = true というアノテーションに注目してください。encrypted フラグはデータベースへの保存時に暗号化するフィールドをマークします(Enterprise 機能)。referenceable フラグは、{vault://env/OPENAI_API_KEY} のような Kong Vault 参照を値として使えることを意味し、Kong のシークレット管理システムとの連携を可能にします。

ヒント: AWS Bedrock などのクラウドプロバイダーでは、API キーを明示的に設定する必要はありません。アダプターは標準の AWS SDK 認証チェーン(環境変数、IAM ロール、インスタンスプロファイル)を使用します。AWS_REGION を設定し、Kong インスタンスに適切な IAM 権限を付与するだけで動作します。

AI プラグインファミリー

kong/plugins/ai-proxy/handler.lua の ai-proxy プラグインはわずか 19 行と驚くほどコンパクトです。それは ai_plugin_base モジュールをベースにしたフィルター構成のアーキテクチャに処理を委ねているためです:

local ai_plugin_base = require("kong.llm.plugin.base")

local NAME = "ai-proxy"
local PRIORITY = 770

local AIPlugin = ai_plugin_base.define(NAME, PRIORITY)

local SHARED_FILTERS = {
  "parse-request", "normalize-request", "enable-buffering",
  "normalize-response-header", "parse-sse-chunk", "normalize-sse-chunk",
  "parse-json-response", "normalize-json-response",
  "serialize-analytics",
}

for _, filter in ipairs(SHARED_FILTERS) do
  AIPlugin:enable(AIPlugin.register_filter(require("kong.llm.plugin.shared-filters." .. filter)))
end

return AIPlugin:as_kong_plugin()

kong/llm/plugin/base.lua モジュールは、Kong のフェーズにマッピングする独自の「ステージ」システムを持つメタプラグインフレームワークを提供します:

local STAGES = {
  SETUP = 0,
  REQ_INTROSPECTION = 1,
  REQ_TRANSFORMATION = 2,
  REQ_POST_PROCESSING = 3,
  RES_INTROSPECTION = 4,
  RES_TRANSFORMATION = 5,
  STREAMING = 6,
  RES_PRE_PROCESSING = 7,
  RES_POST_PROCESSING = 8,
}

各共有フィルターは特定のステージに登録されます。parse-request フィルターは REQ_INTROSPECTION ステージで実行され、受信リクエストボディをデコードします。normalize-request フィルターは REQ_TRANSFORMATION ステージで実行され、Kong の標準フォーマットからプロバイダーのフォーマットへと変換します。serialize-analytics フィルターは RES_POST_PROCESSING ステージで実行され、利用状況メトリクスを出力します。

このコンポーザブルなフィルターアーキテクチャにより、異なる AI プラグイン(ai-proxy、ai-request-transformer、ai-response-transformer)が共通ロジックを共有しながら、それぞれ異なる高レベルの動作を実装できます。ai-proxy はすべての標準フィルターを有効にする一方、ai-request-transformer はリクエスト側のフィルターと LLM イントロスペクションフィルターのみを有効にする、といった使い分けが可能です。

sequenceDiagram
    participant Client
    participant AIProxy as ai-proxy (access)
    participant ParseReq as parse-request filter
    participant NormReq as normalize-request filter
    participant LLM as LLM Provider
    participant ParseRes as parse-json-response filter
    participant NormRes as normalize-json-response filter
    participant Analytics as serialize-analytics filter

    Client->>AIProxy: POST /llm/v1/chat
    AIProxy->>ParseReq: STAGE: REQ_INTROSPECTION
    ParseReq->>ParseReq: Decode JSON body
    ParseReq->>NormReq: STAGE: REQ_TRANSFORMATION
    NormReq->>NormReq: Transform to provider format
    NormReq->>LLM: Forward transformed request
    LLM-->>ParseRes: Provider response
    ParseRes->>ParseRes: Decode provider JSON
    ParseRes->>NormRes: STAGE: RES_TRANSFORMATION
    NormRes->>NormRes: Normalize to Kong format
    NormRes->>Analytics: STAGE: RES_POST_PROCESSING
    Analytics->>Analytics: Record token usage
    Analytics-->>Client: Normalized response

kong/llm/plugin/observability.lua のオブザービリティモジュールは、Kong の既存のメトリクス基盤と連携します。トークン数・レイテンシ・コストはリクエストごとに追跡され、Kong の標準ログプラグインを通じて公開されます。追加設定なしで http-logdatadogprometheus を使って AI ゲートウェイのトラフィックを監視できます。

kong/llm/plugin/ctx.lua のコンテキストモジュールは、リクエストごとの名前空間付き状態管理を提供します。各 AI プラグインは独自のコンテキスト名前空間を持つため、同一リクエスト上で複数の AI プラグイン(例:ai-prompt-guard の後に ai-proxy)が動作する際にも競合が発生しません。

AI リクエストの完全なフロー

Kong を通じた AI ゲートウェイリクエストの全体的な流れは次のとおりです:

  1. クライアントが OpenAI 互換のリクエストボディで POST /ai/chat を送信
  2. Kong のルーターが ai-proxy プラグインを設定したルートにマッチング
  3. ai-proxy の access ハンドラーが実行:
    • parse-request:JSON をデコードし、フォーマットを llm/v1/chat と判別
    • normalize-request:設定済みのドライバー(例:anthropic)を選択し、リクエストを変換
    • enable-buffering:非ストリーミングリクエスト向けにレスポンスバッファリングを有効化
  4. Kong のバランサーが変換済みリクエストを LLM プロバイダーエンドポイントへ送信
  5. ai-proxy のレスポンスハンドラーが実行:
    • normalize-response-header:コンテントタイプヘッダーを調整
    • parse-json-response または parse-sse-chunk:プロバイダーのレスポンスをパース
    • normalize-json-response または normalize-sse-chunk:Kong フォーマットへ変換
    • serialize-analytics:トークン使用量・レイテンシ・コストを記録
  6. 正規化されたレスポンスがクライアントへ送信

ストリーミングレスポンスの場合、STREAMING ステージのフィルターは body_filter フェーズで SSE チャンクごとに実行されます。これが REPEATED_PHASES でストリーミングステージが繰り返し可能とマークされている理由です。

ヒント: kong/llm/schemas/init.lua の LLM スキーマには、bedrock_options_schema(AWS リージョンオーバーライド)や gemini_options_schema(Vertex AI のプロジェクト/ロケーション)といったプロバイダー固有のオプションが定義されています。これらはオプションであり、スキーマオプションが設定されていない場合、アダプターモジュールは環境変数へフォールバックします。

シリーズの結びに

この7本のシリーズを通じて、Kong の Nginx 基盤から始まり、初期化処理・リクエスト処理・プラグイン実行・スキーマ駆動のデータ管理・分散クラスタリング・AI ゲートウェイ機能まで、その内部構造を追いかけてきました。一貫して見えてきたのは、Kong がいくつかの強力な抽象化に忠実であることです。ライフサイクル管理のためのフェーズ、データモデリングのためのスキーマ、プラグイン実行のためのイテレーター、プロバイダー抽象化のためのドライバー——これらが Kong を支える骨格です。

カスタムプラグインの開発・本番環境でのデバッグ・API インフラとしての Kong の評価、どの場面においても、この内部構造への理解が Kong を「ブラックボックス」から「把握できるシステム」へと変えてくれるはずです。コードベースは大規模です(kong/init.lua 単体で約 2,000 行)。しかしパターンは一貫しており、命名は明快で、アーキテクチャは読み込むほどに理解が深まります。