Skip to content

プロンプト API

プロンプト API は、本番アプリケーションで大規模言語モデル (LLM) と対話するための包括的なツールキットを提供します。これは以下の機能を提供します。

  • 型安全な構造化プロンプトを作成するための Kotlin DSL
  • OpenAI、Anthropic、Google、その他の LLM プロバイダー向けの マルチプロバイダー対応
  • リトライロジック、エラーハンドリング、タイムアウト設定などの 本番環境向け機能
  • テキスト、画像、音声、ドキュメントを扱うための マルチモーダル機能

アーキテクチャの概要

プロンプト API は、主に3つのレイヤーで構成されます。

  • LLM クライアント: 特定のプロバイダー (OpenAI、Anthropicなど) への低レベルインターフェース。
  • デコレーター: リトライロジックなどの機能を追加するオプションのラッパー。
  • プロンプトエグゼキューター: クライアントのライフサイクルを管理し、使用を簡素化する高レベルの抽象化。

プロンプトの作成

プロンプト API は、Kotlin DSL を使用してプロンプトを作成します。以下の種類のメッセージをサポートしています。

  • system: LLMのコンテキストと指示を設定します。
  • user: ユーザー入力を表します。
  • assistant: LLMのレスポンスを表します。

シンプルなプロンプトの例を以下に示します。

kotlin
val prompt = prompt("prompt_name", LLMParams()) {
    // コンテキストを設定するためのシステムメッセージを追加します
    system("You are a helpful assistant.")

    // ユーザーメッセージを追加します
    user("Tell me about Kotlin")

    // Few-shotの例としてアシスタントメッセージを追加することもできます
    assistant("Kotlin is a modern programming language...")

    // 別のユーザーメッセージを追加します
    user("What are its key features?")
}

マルチモーダル入力

プロンプト内でテキストメッセージを提供するだけでなく、Koog ではuserメッセージとともに画像、音声、動画、ファイルを LLM に送信することもできます。 標準的なテキストのみのプロンプトと同様に、プロンプト構築のための DSL 構造を使用してメディアをプロンプトに追加します。

kotlin
val prompt = prompt("multimodal_input") {
    system("You are a helpful assistant.")

    user {
        +"Describe these images"
        
        image("https://example.com/test.png")
        image(Path("/User/koog/image.png"))
    }
}

テキストプロンプトコンテンツ

テキストメッセージとアタッチメントのリストを含むユーザーメッセージの一般的な形式は以下のとおりです。

kotlin
user {
    +"This is the text part of the user message"
    // Add attachment
    image("https://example.com/capture.png")
    file("https://example.com/data.pdf", "application/pdf")
}

ファイルアタッチメント

アタッチメントを含めるには、以下の形式に従ってファイルを提供します。

kotlin
user {
    +"Describe this image"
    image(
        ContentPart.Image(
            content = AttachmentContent.URL("https://example.com/capture.png"),
            format = "png",
            mimeType = "image/png",
            fileName = "capture.png"
        )
    )
}

attachmentsパラメータはファイル入力のリストを受け取り、各項目は以下のクラスのいずれかのインスタンスです。

  • Attachment.Image: jpgpngファイルなどの画像アタッチメント。
  • Attachment.Audio: mp3wavファイルなどの音声アタッチメント。
  • Attachment.Video: mpgaviファイルなどの動画アタッチメント。
  • Attachment.File: pdftxtファイルなどのファイルアタッチメント。

上記の各クラスは以下のパラメータを受け取ります。

名前データ型必須説明
contentAttachmentContentYes提供されるファイルコンテンツのソースです。詳細については、AttachmentContentを参照してください。
formatStringYes提供されるファイルの形式です。例: png
mimeTypeStringOnly for Attachment.File提供されるファイルの MIME タイプです。例: image/png
fileNameStringNo拡張子を含む提供されるファイルの名前です。例: screenshot.png

詳細については、API リファレンスを参照してください。

AttachmentContent

AttachmentContentは、LLMへの入力として提供されるコンテンツのタイプとソースを定義します。以下のクラスがサポートされています。

  • AttachmentContent.URL(val url: String)

    指定されたURLからファイルコンテンツを提供します。以下のパラメータを取ります。

    名前データ型必須説明
    urlStringYes提供されるコンテンツのURLです。

    詳細については、API リファレンスも参照してください。

  • AttachmentContent.Binary.Bytes(val data: ByteArray)

    バイト配列としてファイルコンテンツを提供します。以下のパラメータを取ります。

    名前データ型必須説明
    dataByteArrayYesバイト配列として提供されるファイルコンテンツです。

    詳細については、API リファレンスも参照してください。

  • AttachmentContent.Binary.Base64(val base64: String)

    Base64文字列としてエンコードされたファイルコンテンツを提供します。以下のパラメータを取ります。

    名前データ型必須説明
    base64StringYesファイルデータを含む Base64 文字列です。

    詳細については、API リファレンスも参照してください。

  • AttachmentContent.PlainText(val text: String)

TIP

アタッチメントタイプがAttachment.Fileの場合にのみ適用されます。

プレーンテキストファイル(text/plain MIME タイプなど)からコンテンツを提供します。以下のパラメータを取ります。

名前データ型必須説明
textStringYesファイルのコンテンツです。

詳細については、API リファレンスも参照してください。

混在するアタッチメントコンテンツ

異なるタイプのアタッチメントを個別のプロンプトやメッセージで提供するだけでなく、以下に示すように、単一のuserメッセージで複数かつ混在するタイプのアタッチメントを提供することもできます。

kotlin
val prompt = prompt("mixed_content") {
    system("You are a helpful assistant.")

    user {
        +"Compare the image with the document content."
        image(Path("/User/koog/page.png"))
        binaryFile(Path("/User/koog/page.pdf"), "application/pdf")
        +"Structure the result as a table"
    }
}

LLM クライアントとプロンプトエグゼキューターの選択

プロンプト API を使用する際、LLM クライアントまたはプロンプトエグゼキューターのいずれかを使用してプロンプトを実行できます。 クライアントとエグゼキューターのどちらを選択するかは、以下の要因を考慮してください。

  • 単一の LLM プロバイダーを扱い、高度なライフサイクル管理を必要としない場合は、LLM クライアントを直接使用してください。詳細については、「LLM クライアントを使用したプロンプトの実行」を参照してください。
  • LLM とそのライフサイクルを管理するためのより高レベルの抽象化が必要な場合、または複数のプロバイダー間で一貫した API でプロンプトを実行し、それらを動的に切り替えたい場合は、プロンプトエグゼキューターを使用してください。 詳細については、「プロンプトエグゼキューターを使用したプロンプトの実行」を参照してください。

!!!note LLM クライアントとプロンプトエグゼキューターはどちらも、ストリーミングレスポンス、複数選択肢の生成、コンテンツモデレーションを可能にします。 詳細については、特定のクライアントまたはエグゼキューターのAPI リファレンスを参照してください。

LLM クライアントを使用したプロンプトの実行

単一の LLM プロバイダーを扱い、高度なライフサイクル管理を必要としない場合は、LLM クライアントを使用してプロンプトを実行できます。 Koog は以下の LLM クライアントを提供します。

LLM クライアントを使用してプロンプトを実行するには、次の手順を実行します。

  1. アプリケーションと LLM プロバイダー間の接続を処理する、対応する LLM クライアントを作成します。例:
kotlin
// OpenAI クライアントを作成します
val client = OpenAILLMClient(apiKey)
  1. プロンプトと LLM を引数としてexecute メソッドを呼び出します。
kotlin
// プロンプトを実行します
val response = client.execute(
    prompt = prompt,
    model = OpenAIModels.Chat.GPT4o  // 異なるモデルを選択できます
)

以下は、OpenAI クライアントを使用してプロンプトを実行する例です。

kotlin

fun main() {
    runBlocking {
        // API キーを使用して OpenAI クライアントをセットアップします
        val token = System.getenv("OPENAI_API_KEY")
        val client = OpenAILLMClient(token)

        // プロンプトを作成します
        val prompt = prompt("prompt_name", LLMParams()) {
            // コンテキストを設定するためのシステムメッセージを追加します
            system("You are a helpful assistant.")

            // ユーザーメッセージを追加します
            user("Tell me about Kotlin")

            // Few-shotの例としてアシスタントメッセージを追加することもできます
            assistant("Kotlin is a modern programming language...")

            // 別のユーザーメッセージを追加します
            user("What are its key features?")
        }

        // プロンプトを実行し、レスポンスを取得します
        val response = client.execute(prompt = prompt, model = OpenAIModels.Chat.GPT4o)
        println(response)
    }
}

!!!note LLM クライアントは、ストリーミングレスポンス、複数選択肢の生成、コンテンツモデレーションを可能にします。 詳細については、特定のクライアントの API リファレンスを参照してください。 コンテンツモデレーションの詳細については、「コンテンツモデレーション」を参照してください。

プロンプトエグゼキューターを使用したプロンプトの実行

LLM クライアントがプロバイダーへの直接アクセスを提供するのに対し、プロンプトエグゼキューターは、一般的なユースケースを簡素化し、クライアントのライフサイクル管理を処理する、より高レベルの抽象化を提供します。 これらは、次のような場合に理想的です。

  • クライアント設定を管理せずに迅速なプロトタイプ作成を行いたい場合。
  • 統一されたインターフェースを通じて複数のプロバイダーと連携したい場合。
  • 大規模なアプリケーションでの依存性注入 (Dependency Injection) を簡素化したい場合。
  • プロバイダー固有の詳細を抽象化したい場合。

エグゼキューターの種類

Koog は主に2つのプロンプトエグゼキューターを提供します。

名前
説明
SingleLLMPromptExecutor単一のプロバイダーの LLM クライアントをラップします。エージェントが単一の LLM プロバイダー内でモデルを切り替える機能のみを必要とする場合、このエグゼキューターを使用してください。
MultiLLMPromptExecutorプロバイダーごとに複数の LLM クライアントにルーティングし、リクエストされたプロバイダーが利用できない場合はオプションのフォールバックを使用します。エージェントが異なるプロバイダーのモデル間を切り替える必要がある場合、このエグゼキューターを使用してください。

これらは、LLM でプロンプトを実行するためのPromtExecutorインターフェースの実装です。

単一プロバイダーエグゼキューターの作成

特定の LLM プロバイダー向けのプロンプトエグゼキューターを作成するには、次の手順を実行します。

  1. 対応する API キーを使用して、特定のプロバイダーの LLM クライアントを構成します。
kotlin
val openAIClient = OpenAILLMClient(System.getenv("OPENAI_KEY"))
  1. SingleLLMPromptExecutorを使用してプロンプトエグゼキューターを作成します。
kotlin
val promptExecutor = SingleLLMPromptExecutor(openAIClient)

マルチプロバイダーエグゼキューターの作成

複数の LLM プロバイダーと連携するプロンプトエグゼキューターを作成するには、次の手順を実行します。

  1. 必要な LLM プロバイダーのクライアントを、対応する API キーを使用して構成します。
kotlin
val openAIClient = OpenAILLMClient(System.getenv("OPENAI_KEY"))
val ollamaClient = OllamaClient()
  1. 構成されたクライアントをMultiLLMPromptExecutor クラスのコンストラクタに渡し、複数の LLM プロバイダーを持つプロンプトエグゼキューターを作成します。
kotlin
val multiExecutor = MultiLLMPromptExecutor(
    LLMProvider.OpenAI to openAIClient,
    LLMProvider.Ollama to ollamaClient
)

事前定義されたプロンプトエグゼキューター

より迅速なセットアップのために、Koog は一般的なプロバイダー向けに、以下のすぐに使用できるエグゼキューター実装を提供します。

  • 特定の LLM クライアントで構成されたSingleLLMPromptExecutorを返す単一プロバイダーエグゼキューター:

    • simpleOpenAIExecutor: OpenAI モデルでプロンプトを実行するため。
    • simpleAzureOpenAIExecutor: Azure OpenAI Service を使用してプロンプトを実行するため。
    • simpleAnthropicExecutor: Anthropic モデルでプロンプトを実行するため。
    • simpleGoogleAIExecutor: Google モデルでプロンプトを実行するため。
    • simpleOpenRouterExecutor: OpenRouter でプロンプトを実行するため。
    • simpleOllamaAIExecutor: Ollama でプロンプトを実行するため。
  • マルチプロバイダーエグゼキューター:

    • DefaultMultiLLMPromptExecutor: OpenAI、Anthropic、Google プロバイダーをサポートするMultiLLMPromptExecutorの実装。

事前定義された単一およびマルチプロバイダーエグゼキューターを作成する例を以下に示します。

kotlin
// OpenAI エグゼキューターを作成します
val promptExecutor = simpleOpenAIExecutor("OPENAI_KEY")

// OpenAI、Anthropic、Google LLM クライアントを含む DefaultMultiLLMPromptExecutor を作成します
val openAIClient = OpenAILLMClient("OPENAI_KEY")
val anthropicClient = AnthropicLLMClient("ANTHROPIC_KEY")
val googleClient = GoogleLLMClient("GOOGLE_KEY")
val multiExecutor = DefaultMultiLLMPromptExecutor(openAIClient, anthropicClient, googleClient)

プロンプトの実行

プロンプトエグゼキューターは、ストリーミング、複数選択肢の生成、コンテンツモデレーションなど、さまざまな機能を使用してプロンプトを実行するメソッドを提供します。

executeメソッドを使用して特定の LLM でプロンプトを実行する方法の例を以下に示します。

kotlin
// プロンプトを実行します
val response = promptExecutor.execute(
    prompt = prompt,
    model = OpenAIModels.Chat.GPT4o
)

これにより、GPT4oモデルでプロンプトが実行され、レスポンスが返されます。

!!!note プロンプトエグゼキューターは、ストリーミングレスポンス、複数選択肢の生成、コンテンツモデレーションを可能にします。 詳細については、特定の Executor の API リファレンスを参照してください。 コンテンツモデレーションの詳細については、「コンテンツモデレーション」を参照してください。

キャッシュ付きプロンプトエグゼキューター

繰り返されるリクエストの場合、LLM レスポンスをキャッシュしてパフォーマンスを最適化し、コストを削減できます。 Koog は、キャッシュ機能を追加するPromptExecutorのラッパーであるCachedPromptExecutorを提供します。 これにより、以前に実行されたプロンプトからのレスポンスを保存し、同じプロンプトが再度実行されたときにそれらを取得できます。

キャッシュ付きプロンプトエグゼキューターを作成するには、次の手順を実行します。

  1. レスポンスをキャッシュしたいプロンプトエグゼキューターを作成します。
kotlin
val client = OpenAILLMClient(System.getenv("OPENAI_KEY"))
val promptExecutor = SingleLLMPromptExecutor(client)
  1. 目的のキャッシュを使用してCachedPromptExecutorインスタンスを作成し、作成したプロンプトエグゼキューターを提供します。
kotlin
val cachedExecutor = CachedPromptExecutor(
    cache = FilePromptCache(Path("/cache_directory")),
    nested = promptExecutor
)
  1. 目的のプロンプトとモデルでキャッシュ付きプロンプトエグゼキューターを実行します。
kotlin
val response = cachedExecutor.execute(prompt, OpenAIModels.Chat.GPT4o)

これで、同じプロンプトを同じモデルで複数回実行しても、レスポンスはキャッシュから取得されます。

!!!note * キャッシュ付きプロンプトエグゼキューターでexecuteStreaming()を呼び出すと、レスポンスは単一のチャンクとして生成されます。 * キャッシュ付きプロンプトエグゼキューターでmoderate()を呼び出すと、リクエストはネストされたプロンプトエグゼキューターに転送され、キャッシュは使用されません。 * 複数選択肢のレスポンスのキャッシュはサポートされていません。

リトライ機能

LLM プロバイダーと連携する際、レート制限や一時的なサービス停止といった一時的なエラーに遭遇することがあります。RetryingLLMClientデコレーターは、あらゆる LLM クライアントに自動リトライロジックを追加します。

基本的な使用方法

既存のクライアントをリトライ機能でラップします。

kotlin
// 既存のクライアントをリトライ機能でラップします
val client = OpenAILLMClient(apiKey)
val resilientClient = RetryingLLMClient(client)

// これで、すべて操作は一時的なエラー発生時に自動的にリトライされます
val response = resilientClient.execute(prompt, OpenAIModels.Chat.GPT4o)

リトライ動作の設定

Koog は、いくつかの事前定義されたリトライ設定を提供します。

設定最大試行回数初期遅延最大遅延ユースケース
DISABLED1回 (リトライなし)--開発とテスト
CONSERVATIVE3回2秒30秒通常の本番運用
AGGRESSIVE5回500ミリ秒20秒クリティカルな操作
PRODUCTION3回1秒20秒推奨されるデフォルト

これらを直接使用するか、カスタム設定を作成できます。

kotlin
// 事前定義された設定を使用します
val conservativeClient = RetryingLLMClient(
    delegate = client,
    config = RetryConfig.CONSERVATIVE
)

// またはカスタム設定を作成します
val customClient = RetryingLLMClient(
    delegate = client,
    config = RetryConfig(
        maxAttempts = 5,
        initialDelay = 1.seconds,
        maxDelay = 30.seconds,
        backoffMultiplier = 2.0,
        jitterFactor = 0.2
    )
)

リトライ可能なエラーパターン

デフォルトでは、リトライメカニズムは一般的な一時的エラーを認識します。

  • HTTP ステータスコード:

    • 429: レート制限
    • 500: 内部サーバーエラー
    • 502: バッドゲートウェイ
    • 503: サービス利用不可
    • 504: ゲートウェイタイムアウト
    • 529: Anthropic 過負荷
  • エラーキーワード:

    • rate limit (レート制限)
    • too many requests (リクエストが多すぎます)
    • request timeout (リクエストタイムアウト)
    • connection timeout (接続タイムアウト)
    • read timeout (読み取りタイムアウト)
    • write timeout (書き込みタイムアウト)
    • connection reset by peer (ピアによって接続がリセットされました)
    • connection refused (接続拒否)
    • temporarily unavailable (一時的に利用不可)
    • service unavailable (サービス利用不可)

特定のニーズに合わせてカスタムパターンを定義できます。

kotlin
val config = RetryConfig(
    retryablePatterns = listOf(
        RetryablePattern.Status(429),           // 特定のステータスコード
        RetryablePattern.Keyword("quota"),      // エラーメッセージ内のキーワード
        RetryablePattern.Regex(Regex("ERR_\\d+")), // カスタム正規表現パターン
        RetryablePattern.Custom { error ->      // カスタムロジック
            error.contains("temporary") && error.length > 20
        }
    )
)

プロンプトエグゼキューターでのリトライ

プロンプトエグゼキューターを使用する際は、エグゼキューターを作成する前に基盤となる LLM クライアントをリトライメカニズムでラップします。

kotlin
// リトライ機能を備えた単一プロバイダーエグゼキューター
val resilientClient = RetryingLLMClient(
    OpenAILLMClient(System.getenv("OPENAI_KEY")),
    RetryConfig.PRODUCTION
)
val executor = SingleLLMPromptExecutor(resilientClient)

// 柔軟なクライアント構成を備えたマルチプロバイダーエグゼキューター
val multiExecutor = MultiLLMPromptExecutor(
    LLMProvider.OpenAI to RetryingLLMClient(
        OpenAILLMClient(System.getenv("OPENAI_KEY")),
        RetryConfig.CONSERVATIVE
    ),
    LLMProvider.Anthropic to RetryingLLMClient(
        AnthropicLLMClient(System.getenv("ANTHROPIC_API_KEY")),
        RetryConfig.AGGRESSIVE  
    ),
    // Bedrock クライアントにはすでに AWS SDK のリトライ機能が組み込まれています
    LLMProvider.Bedrock to BedrockLLMClient(
        identityProvider = StaticCredentialsProvider {
            accessKeyId = System.getenv("AWS_ACCESS_KEY_ID")
            secretAccessKey = System.getenv("AWS_SECRET_ACCESS_KEY")
            sessionToken = System.getenv("AWS_SESSION_TOKEN")
        },
    ),
)

ストリーミングでのリトライ

ストリーミング操作はオプションでリトライできます。この機能はデフォルトで無効になっています。

kotlin
val config = RetryConfig(
    maxAttempts = 3
)

val client = RetryingLLMClient(baseClient, config)
val stream = client.executeStreaming(prompt, OpenAIModels.Chat.GPT4o)

!!!note ストリーミングのリトライは、最初のトークンを受信する前の接続障害にのみ適用されます。ストリーミングが開始されると、コンテンツの整合性を維持するためにエラーはそのまま渡されます。

タイムアウト設定

すべての LLM クライアントは、リクエストのハングを防ぐためのタイムアウト設定をサポートしています。

kotlin
val client = OpenAILLMClient(
    apiKey = apiKey,
    settings = OpenAIClientSettings(
        timeoutConfig = ConnectionTimeoutConfig(
            connectTimeoutMillis = 5000,    // 接続確立まで5秒
            requestTimeoutMillis = 60000    // リクエスト全体で60秒
        )
    )
)

エラーハンドリング

本番環境で LLM を操作する際は、エラーハンドリング戦略を実装する必要があります。

  • 予期せぬエラーを処理するために、try-catch ブロックを使用してください
  • デバッグのために、コンテキスト情報とともにエラーをログに出力してください
  • クリティカルな操作には、フォールバック戦略を実装してください
  • 再発する問題やシステムの問題を特定するために、リトライパターンを監視してください

包括的なエラーハンドリングの例を以下に示します。

kotlin
try {
    val response = resilientClient.execute(prompt, model)
    processResponse(response)
} catch (e: Exception) {
    logger.error("LLM operation failed", e)
    
    when {
        e.message?.contains("rate limit") == true -> {
            // レート制限を特別に処理
            scheduleRetryLater()
        }
        e.message?.contains("invalid api key") == true -> {
            // 認証エラーを処理
            notifyAdministrator()
        }
        else -> {
            // 代替ソリューションにフォールバック
            useDefaultResponse()
        }
    }
}