Skip to content

ToolDescriptorSchemer

ToolDescriptorSchemer は、ToolDescriptor を特定の LLM プロバイダーと互換性のある JSON Schema オブジェクトに変換する拡張ポイントです。Kotlin と Java の両方で実装できます。

主なポイント:

  • 場所: ai.koog.agents.core.tools.serialization.ToolDescriptorSchemer
  • コントラクト: 単一の関数 scheme(toolDescriptor: ToolDescriptor): JsonObject または generate(ToolDescriptor toolDescriptor): JsonObject (Java)
  • 提供されている実装:
    • OpenAICompatibleToolDescriptorSchemer — OpenAI スタイルの関数/ツール定義と互換性のあるスキーマを生成します。
    • OllamaToolDescriptorSchemer — Ollama ツールの JSON と互換性のあるスキーマを生成します。
kotlin
// インターフェース
interface ToolDescriptorSchemaGenerator {
fun generate(toolDescriptor: ToolDescriptor): JsonObject
}

なぜこれを使用するのか

Kotlin または Java で、既存または新しい LLM プロバイダーに対してカスタムスキーマを提供したい場合、このインターフェースを実装して、Koog の ToolDescriptor を期待される JSON Schema 形式に変換します。

実装例

以下は、SPI への組み込み方法を示すために、パラメータ型の一部のみを処理する Kotlin と Java の最小限のカスタム実装例です。実際の実装では、すべての ToolParameterType(String、Integer、Float、Boolean、Null、Enum、List、Object、AnyOf)をカバーする必要があります。

=== "Kotlin"

<!--- INCLUDE
import ai.koog.agents.core.tools.ToolDescriptor
import ai.koog.agents.core.tools.ToolParameterDescriptor
import ai.koog.agents.core.tools.ToolParameterType
import ai.koog.agents.core.tools.serialization.ToolDescriptorSchemaGenerator
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.put
import kotlinx.serialization.json.putJsonArray
import kotlinx.serialization.json.putJsonObject
-->
```kotlin

class MinimalSchemer : ToolDescriptorSchemaGenerator {
    override fun generate(toolDescriptor: ToolDescriptor): JsonObject = buildJsonObject {
        put("type", "object")
        putJsonObject("properties") {
            (toolDescriptor.requiredParameters + toolDescriptor.optionalParameters).forEach { p ->
                put(p.name, buildJsonObject {
                    put("description", p.description)
                    when (val t = p.type) {
                        ToolParameterType.String -> put("type", "string")
                        ToolParameterType.Integer -> put("type", "integer")
                        is ToolParameterType.Enum -> {
                            put("type", "string")
                            putJsonArray("enum") { t.entries.forEach { add(JsonPrimitive(it)) } }
                        }
                        else -> put("type", "string") // 簡略化のためのフォールバック
                    }
                })
            }
        }
        putJsonArray("required") { toolDescriptor.requiredParameters.forEach { add(JsonPrimitive(it.name)) } }
    }
}
```
<!--- KNIT example-tool-descriptor-schemer-02.kt -->

=== "Java"

<!--- INCLUDE
/**
-->
<!--- SUFFIX
**/
-->
```java
public static class MinimalSchemer extends OpenAICompatibleToolDescriptorSchemaGenerator {
    @Override
    public JsonObject generate(ToolDescriptor toolDescriptor) {
        Map<String, JsonElement> root = new LinkedHashMap<>();
        root.put("type", JsonPrimitive("object"));

        // properties
        Map<String, JsonElement> props = new LinkedHashMap<>();
        for (ToolParameterDescriptor p : concat(toolDescriptor.getRequiredParameters(), toolDescriptor.getOptionalParameters())) {
            Map<String, JsonElement> prop = new LinkedHashMap<>();
            prop.put("description", JsonPrimitive(p.getDescription()));

            ToolParameterType t = p.getType();
            if (t == ToolParameterType.String.INSTANCE) {
                prop.put("type", JsonPrimitive("string"));
            } else if (t == ToolParameterType.Integer.INSTANCE) {
                prop.put("type", JsonPrimitive("integer"));
            } else if (t instanceof ToolParameterType.Enum) {
                prop.put("type", JsonPrimitive("string"));
                String[] entries = ((ToolParameterType.Enum) t).getEntries();
                List<JsonElement> enumVals = new ArrayList<>();
                for (String e : entries) enumVals.add(JsonPrimitive(e));
                prop.put("enum", new JsonArray(enumVals));
            } else {
                prop.put("type", JsonPrimitive("string")); // 簡略化のためのフォールバック
            }

            props.put(p.getName(), new JsonObject(prop));
        }
        root.put("properties", new JsonObject(props));

        // required array
        List<JsonElement> required = new ArrayList<>();
        for (ToolParameterDescriptor p : toolDescriptor.getRequiredParameters()) {
            required.add(JsonPrimitive(p.getName()));
        }
        root.put("required", new JsonArray(required));

        return new JsonObject(root);
    }

    private static List<ToolParameterDescriptor> concat(List<ToolParameterDescriptor> a, List<ToolParameterDescriptor> b) {
        List<ToolParameterDescriptor> res = new ArrayList<>(a.size() + b.size());
        res.addAll(a);
        res.addAll(b);
        return res;
    }
}
```
<!--- KNIT example-tool-descriptor-schemer-java-01.java -->

クライアントでの使用

通常、スキーマーを直接呼び出す必要はありません。Koog クライアントは ToolDescriptor オブジェクトのリストを受け取り、プロバイダー向けにリクエストをシリアライズする際に、内部で適切なスキーマーを適用します。

以下の例では、シンプルなツールを定義し、それを OpenAI クライアントに渡しています。クライアントは内部で OpenAICompatibleToolDescriptorSchemer を使用して JSON スキーマを構築します。

=== "Kotlin"

<!--- INCLUDE
import ai.koog.prompt.executor.clients.openai.OpenAILLMClient
import ai.koog.agents.core.tools.ToolDescriptor
import ai.koog.agents.core.tools.ToolParameterDescriptor
import ai.koog.agents.core.tools.ToolParameterType
import ai.koog.prompt.dsl.Prompt
import ai.koog.prompt.executor.clients.openai.OpenAIModels
import ai.koog.prompt.executor.clients.openai.base.OpenAICompatibleToolDescriptorSchemaGenerator
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.put
import kotlinx.serialization.json.putJsonArray
import kotlinx.serialization.json.putJsonObject
import kotlinx.coroutines.runBlocking
class MinimalSchemer : OpenAICompatibleToolDescriptorSchemaGenerator() {
    override fun generate(toolDescriptor: ToolDescriptor): JsonObject = buildJsonObject {
        put("type", "object")
        putJsonObject("properties") {
            (toolDescriptor.requiredParameters + toolDescriptor.optionalParameters).forEach { p ->
                put(p.name, buildJsonObject {
                    put("description", p.description)
                    when (val t = p.type) {
                        ToolParameterType.String -> put("type", "string")
                        ToolParameterType.Integer -> put("type", "integer")
                        is ToolParameterType.Enum -> {
                            put("type", "string")
                            putJsonArray("enum") { t.entries.forEach { add(JsonPrimitive(it)) } }
                        }
                        else -> put("type", "string") // 簡略化のためのフォールバック
                    }
                })
            }
        }
        putJsonArray("required") { toolDescriptor.requiredParameters.forEach { add(JsonPrimitive(it.name)) } }
    }
}
-->
```kotlin
val client = OpenAILLMClient(apiKey = System.getenv("OPENAI_API_KEY"), toolsConverter = MinimalSchemer())

val getUserTool = ToolDescriptor(
    name = "get_user",
    description = "Returns user profile by id",
    requiredParameters = listOf(
        ToolParameterDescriptor(
            name = "id",
            description = "User id",
            type = ToolParameterType.String
        )
    )
)

val prompt = Prompt.build(id = "p1") { user("Hello") }
val responses = runBlocking {
    client.execute(
        prompt = prompt,
        model = OpenAIModels.Chat.GPT4o,
        tools = listOf(getUserTool)
    )
}
```
<!--- KNIT example-tool-descriptor-schemer-03.kt -->

=== "Java"

<!--- INCLUDE
/**
-->
<!--- SUFFIX
**/
-->
```java
// OpenAI互換のものを拡張するカスタムスキーマーは、ドキュメント内ではKotlinのみです。Javaの例では、上記のMinimalSchemerを再利用します。
OpenAILLMClient client = new OpenAILLMClient(System.getenv("OPENAI_API_KEY"), new OpenAIClientSettings(), null, null, new OpenAICompatibleToolDescriptorSchemaGenerator());

ToolDescriptor getUserTool = new ToolDescriptor(
    "get_user",
    "Returns user profile by id",
    Collections.singletonList(new ToolParameterDescriptor(
        "id",
        "User id",
        ToolParameterType.String.INSTANCE
    )),
    Collections.emptyList()
);

Prompt prompt = Prompt.builder("p1")
    .user("Hello")
    .build();

List<Message.Response> responses = client.execute(prompt, OpenAIModels.Chat.GPT4o, java.util.List.of(getUserTool));
```
<!--- KNIT example-tool-descriptor-schemer-java-02.java -->

生成されたスキーマに直接アクセスする必要がある場合(デバッグやカスタムトランスポートのため)、プロバイダー固有のスキーマーをインスタンス化して、自分で JSON をシリアライズできます。

=== "Kotlin"

<!--- INCLUDE
import kotlinx.serialization.json.Json
import ai.koog.prompt.executor.clients.openai.base.OpenAICompatibleToolDescriptorSchemaGenerator
import ai.koog.agents.core.tools.ToolDescriptor
import ai.koog.agents.core.tools.ToolParameterDescriptor
import ai.koog.agents.core.tools.ToolParameterType
fun getUserTool(): ToolDescriptor {
    return ToolDescriptor(
        name = "get_user",
        description = "Returns user profile by id",
        requiredParameters = listOf(
            ToolParameterDescriptor(
                name = "id",
                description = "User id",
                type = ToolParameterType.String
            )
        )
    )
}
-->

```kotlin
val json = Json { prettyPrint = true }
val schema = OpenAICompatibleToolDescriptorSchemaGenerator().generate(getUserTool())
```
<!--- KNIT example-tool-descriptor-schemer-04.kt -->

=== "Java"

<!--- INCLUDE
/**
-->
<!--- SUFFIX
**/
-->
```java
```
<!--- KNIT example-tool-descriptor-schemer-java-03.java -->