跳转至

基于类的工具

本节介绍 API,专为需要更高灵活性和定制行为的场景设计。 通过 Kotlin 中的这种方法,您可以完全控制工具,包括其参数、元数据、执行逻辑以及注册和调用方式。而在 Java 中,工具使用基于注解的方法创建,并通过反射进行注册。

这种控制级别非常适合创建扩展基础用例的复杂工具,使其能够无缝集成到智能体会话和工作流中。

本页描述如何在 Kotlin 和 Java 中实现工具、通过注册表管理工具、调用工具,以及在基于节点的智能体架构中使用工具。

Note

API 是 Kotlin 的多平台框架。Java 工具使用基于注解的方法实现,并通过反射注册。这使您可以在 Kotlin 的不同平台中使用相同的工具,而 Java 则提供完整的 JVM 互操作性。

工具实现

Koog 框架提供以下实现工具的方法:

对于 Kotlin:

  • 使用所有工具的基类 Tool。当您需要返回非文本结果或需要完全控制工具行为时,应使用此类。
  • 使用扩展了基类 ToolSimpleTool 类,它简化了返回文本结果的工具创建。当工具仅需返回文本时,应使用此方法。

这两种方法使用相同的核心组件,但在实现方式和返回结果上有所不同。

对于 Java:

  • 使用基于注解的方法(@Tool@LLMDescription)配合反射注册。这是实现 Java 互操作性的推荐方法,因为由于挂起函数的限制,不支持从 Java 子类化 Kotlin 的 ToolSimpleTool

工具类(Kotlin)

Tool<Args, Result> 抽象类是 Kotlin 中创建工具的基类。 它允许您创建接受特定参数类型(Args)并返回各种类型结果(Result)的工具。

每个工具包含以下组件:|

组件
| 描述 | |------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Args | 定义工具所需参数的可序列化数据类。 | | Result | 工具返回结果的可序列化类型。若需以自定义格式呈现工具结果,请继承 ToolResult.TextSerializable 类并实现 textForLLM(): String 方法 | | argsSerializer | 重写变量,用于定义工具参数的解析方式。另请参阅 argsSerializer。 | | resultSerializer | 重写变量,用于定义工具结果的解析方式。另请参阅 resultSerializer。若选择继承 ToolResult.TextSerializable,可考虑使用 ToolResultUtils.toTextSerializer() | | descriptor | 重写变量,用于指定工具元数据:
- name
- description
- requiredParameters(默认为空)
- optionalParameters(默认为空)
另请参阅 descriptor。 | | execute() | 实现工具逻辑的函数。接收类型为 Args 的参数,并返回类型为 Result 的结果。另请参阅 execute()。 |!!! note "Java 实现" 在 Java 中,无需继承 Tool<Args, Result> 类,而是使用基于注解的方法,配合 @Tool@LLMDescription。框架会通过反射自动处理序列化和注册。更多 详细信息,请参阅下方的基于注解的方法

Tip

请确保您的工具具有清晰的描述和定义明确的参数名称,以便 LLM 能够更容易地理解并正确使用它们。在 Kotlin 中,使用 descriptor 属性;在 Java 中,使用 @LLMDescription 注解。

使用示例

以下是一个使用 Tool 类实现自定义工具并返回数值结果的示例:

// 实现一个简单的计算器工具,用于将两个数字相加
object CalculatorTool : Tool<CalculatorTool.Args, Int>(
    argsType = typeToken<Args>(),
    resultType = typeToken<Int>(),
    name = "calculator",
    description = "一个简单的计算器,可以将两个数字(0-9)相加。"
) {

    // 计算器工具的参数
    @Serializable
    data class Args(
        @property:LLMDescription("要相加的第一个数字(0-9)")
        val digit1: Int,
        @property:LLMDescription("要相加的第二个数字(0-9)")
        val digit2: Int
    ) {
        init {
            require(digit1 in 0..9) { "digit1 必须是单个数字(0-9)" }
            require(digit2 in 0..9) { "digit2 必须是单个数字(0-9)" }
        }
    }

    // 将两个数字相加的函数
    override suspend fun execute(args: Args): Int = args.digit1 + args.digit2
}

实现工具后,您需要将其添加到工具注册表中,然后与智能体一起使用。详细信息请参阅工具注册表

更多详细信息,请参阅 API 参考

SimpleTool 类 (Kotlin)

SimpleTool<Args> 抽象类继承自 Tool<Args, ToolResult.Text>,简化了返回文本结果的工具创建过程。

每个简单工具包含以下组件:|

组件
| 描述 | |------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Args | 定义自定义工具所需参数的可序列化数据类。 | | argsSerializer | 重写变量,用于定义工具参数的序列化方式。另请参阅 argsSerializer。 | | descriptor | 重写变量,用于指定工具元数据:
- name
- description
- requiredParameters(默认为空)
- optionalParameters(默认为空)
另请参阅 descriptor。 | | doExecute() | 重写函数,用于描述工具执行的主要操作。它接收类型为 Args 的参数,并返回一个 String。另请参阅 doExecute()。 |

Java 实现

在 Java 中,等效方法是使用返回 String 的基于注解的方法。框架会自动处理文本结果的包装。更多详情,请参阅下方的基于注解的方法

Tip

确保您的工具具有清晰的描述和明确定义的参数名称,以便 LLM 更容易理解并正确使用它们。在 Kotlin 中,使用 descriptor 和构造函数参数;在 Java 中,使用 @Tool@LLMDescription 注解。

使用示例

以下是在 Kotlin 中使用 SimpleTool 实现自定义工具的示例:

// 创建一个将字符串表达式转换为双精度值的工具
object CastToDoubleTool : SimpleTool<CastToDoubleTool.Args>(
    argsType = typeToken<Args>(),
    name = "cast_to_double",
    description = "将传入的表达式转换为双精度值,若表达式无法转换则返回 0.0"
) {
    // 定义工具参数
    @Serializable
    data class Args(
        @property:LLMDescription("要转换为双精度值的表达式")
        val expression: String,
        @property:LLMDescription("关于如何处理表达式的注释")
        val comment: String
    )
```        // 使用提供的参数执行工具的函数
    override suspend fun execute(args: Args): String {
        return "结果: ${castToDouble(args.expression)}, " + "注释为: ${args.comment}"
    }

    // 将字符串表达式转换为双精度值的函数
    private fun castToDouble(expression: String): Double {
        return expression.toDoubleOrNull() ?: 0.0
    }
}

基于注解的方法 (Java)

要在 Java 中实现工具,无需子类化 ToolSimpleTool,可使用基于注解的方法配合 @Tool@LLMDescription。Koog 通过反射自动处理序列化和注册。要了解更多实现细节,请参阅以下 Java 示例。

使用示例

这是在 Java 中实现工具的示例,相当于在 Kotlin 中使用 Tool 类。

// Java 等效实现:将工具实现为 Java 方法并通过 ToolRegistry.builder() 注册。
// 这是推荐的 Java 互操作路径,而非子类化 Kotlin Tool 基类。
public final class CalculatorTool {
    private CalculatorTool() {}

    @Tool(customName = "calculator")
    @LLMDescription(description = "可计算两个数字(0-9)相加的简易计算器。")
    public static int calculator(
            @LLMDescription(description = "要相加的第一个数字(0-9)") int digit1,
            @LLMDescription(description = "要相加的第二个数字(0-9)") int digit2
    ) {
        if (digit1 < 0 || digit1 > 9) throw new IllegalArgumentException("digit1 必须是单个数字(0-9)");
        if (digit2 < 0 || digit2 > 9) throw new IllegalArgumentException("digit2 必须是单个数字(0-9)");
        return digit1 + digit2;
    }

    public static ToolRegistry registry() throws NoSuchMethodException {
        return ToolRegistry.builder()
            .tool(CalculatorTool.class.getMethod("calculator", int.class, int.class))
            .build();
    }
}
// 注意:不支持子类化 Kotlin Tool<TArgs, TResult> 并重写来自 Java 的 suspend execute(...) 方法。
// Java 互操作使用基于反射的 Java 方法注册为工具。

以下是在 Java 中实现工具的示例,相当于在 Kotlin 中使用 SimpleTool 类。此示例 实现了一个返回文本结果的简单工具。

```java // SimpleTool 的 Java 等效实现:提供 Java 方法并将其注册为工具。 public final class CastToDoubleTool { private CastToDoubleTool() {}

``` @Tool(customName = "cast_to_double") @LLMDescription(description = "将传入的表达式转换为 double 类型,若表达式无法转换则返回 0.0") public static String castToDouble( @LLMDescription(description = "要转换为 double 的表达式") String expression, @LLMDescription(description = "关于如何处理该表达式的注释") String comment ) { double value; try { value = Double.parseDouble(expression); } catch (Exception e) { value = 0.0; } return "结果: " + value + ", 注释为: " + comment; }

    public static ToolRegistry registry() throws NoSuchMethodException {
        return ToolRegistry.builder()
            .tool(CastToDoubleTool.class.getMethod("castToDouble", String.class, String.class))
            .build();
    }
}
// 注意:无需继承 Kotlin SimpleTool<TArgs> 来自 Java;注册 Java 方法是惯用做法。
```
<!--- KNIT example-class-based-tools-java-02.java -->

以自定义格式将工具结果发送至 LLM

对于 Kotlin:

如果您对发送至 LLMJSON 结果不满意(在某些情况下,若工具输出以 Markdown 等结构化格式呈现,LLM 可能工作得更好),您需要遵循以下步骤:

  1. 实现 ToolResult.TextSerializable 接口,并重写 textForLLM() 方法
  2. 使用 ToolResultUtils.toTextSerializer<T>() 重写 resultSerializer

对于 Java:

直接从您的注解方法返回格式化文本(如 Markdown)作为 String。框架会自动处理此过程。

示例

以下示例展示了在 Kotlin 和 Java 中自定义格式化输出的方式:

// 编辑文件的工具
object EditFile : Tool<EditFile.Args, EditFile.Result>(
    argsType = typeToken<Args>(),
    resultType = typeToken<Result>(),
    name = "edit_file",
    description = "编辑指定文件"
) {
    // 定义工具参数
    @Serializable
    public data class Args(
        val path: String,
        val original: String,
        val replacement: String
    )

    @Serializable
    public data class Result(
        private val patchApplyResult: PatchApplyResult
    ) {

        @Serializable
        public sealed interface PatchApplyResult {
            @Serializable
            public data class Success(val updatedContent: String) : PatchApplyResult

            @Serializable
            public sealed class Failure(public val reason: String) : PatchApplyResult
        }// 工具完成后将显示给 LLM 的文本输出(Markdown格式)。
        fun textForLLM(): String = markdown {
            if (patchApplyResult is PatchApplyResult.Success) {
                line {
                    bold("成功").text("编辑文件(补丁已应用)")
                }
            } else {
                line {
                    text("文件")
                        .bold("未")
                        .text("被修改(补丁应用失败:${(patchApplyResult as PatchApplyResult.Failure).reason})")
                }
            }
        }

        override fun toString(): String = textForLLM()
    }

    // 使用提供的参数执行工具的函数
    override suspend fun execute(args: Args): Result {
        return TODO("实现文件编辑")
    }
}

import ai.koog.agents.core.tools.ToolRegistry;
import ai.koog.agents.core.tools.annotations.LLMDescription;
import ai.koog.agents.core.tools.annotations.Tool;

// Java 等效方式:直接从 Java 方法返回 Markdown 文本给 LLM,并将其注册为工具。
// 这避免了需要自定义可序列化的 Result 类型(该类型需要 Kotlin 序列化支持)。
public final class EditFile {
    private EditFile() {}

    @Tool(customName = "edit_file")
    @LLMDescription(description = "编辑指定文件")
    public static String editFile(
            String path,
            String original,
            String replacement
    ) {
        // TODO:实现文件编辑逻辑;以下为展示 Markdown 输出的占位代码
        boolean success = false;
        if (success) {
            return "**成功**编辑文件(补丁已应用)";
        } else {
            return "文件**未**被修改(补丁应用失败:原因)";
        }
    }

    public static ToolRegistry registry() throws NoSuchMethodException {
        return ToolRegistry.builder()
            .tool(EditFile.class.getMethod("editFile", String.class, String.class, String.class))
            .build();
    }
}
// 注意:如果需要从 Java 返回结构化的自定义 Result 对象,必须暴露一个 Kotlin @Serializable 类型
// 或其他支持序列化的类型。返回 String 类型可与 Koog 的 Java 互操作直接使用。

在 Kotlin 或 Java 中实现工具后,您需要将其添加到工具注册表,然后与智能体一起使用。 详情请参阅工具注册表