使用 Koog 构建支持工具调用的计算器智能体
在本简短教程中,我们将构建一个由 Koog 工具调用驱动的计算器智能体。 你将学习如何:
- 为算术运算设计小巧、纯净的工具 (tools)
- 使用 Koog 的多重调用策略编排并行工具调用
- 添加轻量级事件日志记录以提高透明度
- 使用 OpenAI(以及可选的 Ollama)运行
我们将保持 API 整洁且符合 Kotlin 惯例,返回可预测的结果并优雅地处理边缘情况(如除以零)。
设置
我们假设你处于已安装 Koog 的 Kotlin Notebook 环境中。 提供一个 LLM 执行器。
kotlin
%useLatestDescriptors
%use koog
val OPENAI_API_KEY = System.getenv("OPENAI_API_KEY")
?: error("请设置 OPENAI_API_KEY 环境变量")
val executor = simpleOpenAIExecutor(OPENAI_API_KEY)计算器工具
工具是具有清晰契约的小巧、纯净的函数。 我们将使用 Double 以获得更好的精度,并保持输出格式的一致性。
kotlin
import ai.koog.agents.core.tools.annotations.Tool
// 格式化辅助程序:整数清晰呈现,小数保持合理的精度。
private fun Double.pretty(): String =
if (abs(this % 1.0) < 1e-9) this.toLong().toString() else "%.10g".format(this)
@LLMDescription("用于基础计算器操作的工具")
class CalculatorTools : ToolSet {
@Tool
@LLMDescription("将两个数字相加并以文本形式返回和。")
fun plus(
@LLMDescription("第一个加数。") a: Double,
@LLMDescription("第二个加数。") b: Double
): String = (a + b).pretty()
@Tool
@LLMDescription("从第一个数字中减去第二个数字,并以文本形式返回差。")
fun minus(
@LLMDescription("被减数。") a: Double,
@LLMDescription("减数。") b: Double
): String = (a - b).pretty()
@Tool
@LLMDescription("将两个数字相乘并以文本形式返回积。")
fun multiply(
@LLMDescription("第一个因数。") a: Double,
@LLMDescription("第二个因数。") b: Double
): String = (a * b).pretty()
@Tool
@LLMDescription("将第一个数字除以第二个数字,并以文本形式返回商。除以零时返回错误消息。")
fun divide(
@LLMDescription("被除数。") a: Double,
@LLMDescription("除数(不能为零)。") b: Double
): String = if (abs(b) < 1e-12) {
"错误:除以零"
} else {
(a / b).pretty()
}
}工具注册表
公开我们的工具(以及两个用于交互/日志记录的内置工具)。
kotlin
val toolRegistry = ToolRegistry {
tool(AskUser) // 在需要时启用明确的用户澄清
tool(SayToUser) // 允许智能体向用户呈现最终消息
tools(CalculatorTools())
}策略:多工具调用(可选压缩)
此策略允许 LLM 同时提出多个工具调用(例如 plus、minus、multiply、divide),然后将结果发回。 如果 token 使用量增长过大,我们会在继续之前压缩工具结果的历史记录。
kotlin
import ai.koog.agents.core.environment.ReceivedToolResult
object CalculatorStrategy {
private const val MAX_TOKENS_THRESHOLD = 1000
val strategy = strategy<String, String>("test") {
val callLLM by nodeLLMRequestMultiple()
val executeTools by nodeExecuteMultipleTools(parallelTools = true)
val sendToolResults by nodeLLMSendMultipleToolResults()
val compressHistory by nodeLLMCompressHistory<List<ReceivedToolResult>>()
edge(nodeStart forwardTo callLLM)
// 如果助手生成了最终答案,则结束。
edge((callLLM forwardTo nodeFinish) transformed { it.first() } onAssistantMessage { true })
// 否则,运行 LLM 请求的工具(可能并行运行多个)。
edge((callLLM forwardTo executeTools) onMultipleToolCalls { true })
// 如果变得很大,在继续之前压缩过去的工具结果。
edge(
(executeTools forwardTo compressHistory)
onCondition { llm.readSession { prompt.latestTokenUsage > MAX_TOKENS_THRESHOLD } }
)
edge(compressHistory forwardTo sendToolResults)
// 正常路径:将工具结果发回给 LLM。
edge(
(executeTools forwardTo sendToolResults)
onCondition { llm.readSession { prompt.latestTokenUsage <= MAX_TOKENS_THRESHOLD } }
)
// LLM 在看到结果后可能会请求更多工具。
edge((sendToolResults forwardTo executeTools) onMultipleToolCalls { true })
// 或者它可以生成最终答案。
edge((sendToolResults forwardTo nodeFinish) transformed { it.first() } onAssistantMessage { true })
}
}智能体配置
以工具为导向的简洁提示词效果很好。保持较低的 temperature 以获得确定性的数学运算结果。
kotlin
val agentConfig = AIAgentConfig(
prompt = prompt("calculator") {
system("你是一个计算器。始终使用提供的工具进行算术运算。")
},
model = OpenAIModels.Chat.GPT4o,
maxAgentIterations = 50
)kotlin
import ai.koog.agents.features.eventHandler.feature.handleEvents
val agent = AIAgent(
promptExecutor = executor,
strategy = CalculatorStrategy.strategy,
agentConfig = agentConfig,
toolRegistry = toolRegistry
) {
handleEvents {
onToolCallStarting { e ->
println("工具已调用:${e.tool.name}, 参数=${e.toolArgs}")
}
onAgentExecutionFailed { e ->
println("智能体错误:${e.throwable.message}")
}
onAgentCompleted { e ->
println("最终结果:${e.result}")
}
}
}试一试
智能体应该将表达式分解为并行的工具调用,并返回格式整齐的结果。
kotlin
import kotlinx.coroutines.runBlocking
runBlocking {
agent.run("(10 + 20) * (5 + 5) / (2 - 11)")
}
// 预期最终值 ≈ -33.333...工具已调用:plus, 参数=VarArgs(args={parameter #1 a of fun Line_4_jupyter.CalculatorTools.plus(kotlin.Double, kotlin.Double): kotlin.String=10.0, parameter #2 b of fun Line_4_jupyter.CalculatorTools.plus(kotlin.Double, kotlin.Double): kotlin.String=20.0})
工具已调用:plus, 参数=VarArgs(args={parameter #1 a of fun Line_4_jupyter.CalculatorTools.plus(kotlin.Double, kotlin.Double): kotlin.String=5.0, parameter #2 b of fun Line_4_jupyter.CalculatorTools.plus(kotlin.Double, kotlin.Double): kotlin.String=5.0})
工具已调用:minus, 参数=VarArgs(args={parameter #1 a of fun Line_4_jupyter.CalculatorTools.minus(kotlin.Double, kotlin.Double): kotlin.String=2.0, parameter #2 b of fun Line_4_jupyter.CalculatorTools.minus(kotlin.Double, kotlin.Double): kotlin.String=11.0})
工具已调用:multiply, 参数=VarArgs(args={parameter #1 a of fun Line_4_jupyter.CalculatorTools.multiply(kotlin.Double, kotlin.Double): kotlin.String=30.0, parameter #2 b of fun Line_4_jupyter.CalculatorTools.multiply(kotlin.Double, kotlin.Double): kotlin.String=10.0})
工具已调用:divide, 参数=VarArgs(args={parameter #1 a of fun Line_4_jupyter.CalculatorTools.divide(kotlin.Double, kotlin.Double): kotlin.String=1.0, parameter #2 b of fun Line_4_jupyter.CalculatorTools.divide(kotlin.Double, kotlin.Double): kotlin.String=-9.0})
工具已调用:divide, 参数=VarArgs(args={parameter #1 a of fun Line_4_jupyter.CalculatorTools.divide(kotlin.Double, kotlin.Double): kotlin.String=300.0, parameter #2 b of fun Line_4_jupyter.CalculatorTools.divide(kotlin.Double, kotlin.Double): kotlin.String=-9.0})
最终结果:表达式 \((10 + 20) * (5 + 5) / (2 - 11)\) 的结果大约是 \(-33.33\)。
表达式 \((10 + 20) * (5 + 5) / (2 - 11)\) 的结果大约是 \(-33.33\)。
尝试强制并行调用
要求模型一次性调用所有需要的工具。 你仍然应该看到正确的方案和稳定的执行。
kotlin
runBlocking {
agent.run("使用工具计算 (10 + 20) * (5 + 5) / (2 - 11)。请一次性调用所有工具。")
}工具已调用:plus, 参数=VarArgs(args={parameter #1 a of fun Line_4_jupyter.CalculatorTools.plus(kotlin.Double, kotlin.Double): kotlin.String=10.0, parameter #2 b of fun Line_4_jupyter.CalculatorTools.plus(kotlin.Double, kotlin.Double): kotlin.String=20.0})
工具已调用:plus, 参数=VarArgs(args={parameter #1 a of fun Line_4_jupyter.CalculatorTools.plus(kotlin.Double, kotlin.Double): kotlin.String=5.0, parameter #2 b of fun Line_4_jupyter.CalculatorTools.plus(kotlin.Double, kotlin.Double): kotlin.String=5.0})
工具已调用:minus, 参数=VarArgs(args={parameter #1 a of fun Line_4_jupyter.CalculatorTools.minus(kotlin.Double, kotlin.Double): kotlin.String=2.0, parameter #2 b of fun Line_4_jupyter.CalculatorTools.minus(kotlin.Double, kotlin.Double): kotlin.String=11.0})
工具已调用:multiply, 参数=VarArgs(args={parameter #1 a of fun Line_4_jupyter.CalculatorTools.multiply(kotlin.Double, kotlin.Double): kotlin.String=30.0, parameter #2 b of fun Line_4_jupyter.CalculatorTools.multiply(kotlin.Double, kotlin.Double): kotlin.String=10.0})
工具已调用:divide, 参数=VarArgs(args={parameter #1 a of fun Line_4_jupyter.CalculatorTools.divide(kotlin.Double, kotlin.Double): kotlin.String=30.0, parameter #2 b of fun Line_4_jupyter.CalculatorTools.divide(kotlin.Double, kotlin.Double): kotlin.String=-9.0})
最终结果:\((10 + 20) * (5 + 5) / (2 - 11)\) 的结果大约是 \(-3.33\)。
\((10 + 20) * (5 + 5) / (2 - 11)\) 的结果大约是 \(-3.33\)。
使用 Ollama 运行
如果你更喜欢本地推理,请更换执行器和模型。
kotlin
val ollamaExecutor: PromptExecutor = simpleOllamaAIExecutor()
val ollamaAgentConfig = AIAgentConfig(
prompt = prompt("calculator", LLMParams(temperature = 0.0)) {
system("你是一个计算器。始终使用提供的工具进行算术运算。")
},
model = OllamaModels.Meta.LLAMA_3_2,
maxAgentIterations = 50
)
val ollamaAgent = AIAgent(
promptExecutor = ollamaExecutor,
strategy = CalculatorStrategy.strategy,
agentConfig = ollamaAgentConfig,
toolRegistry = toolRegistry
)
runBlocking {
ollamaAgent.run("(10 + 20) * (5 + 5) / (2 - 11)")
}智能体说:表达式 (10 + 20) * (5 + 5) / (2 - 11) 的结果大约是 -33.33。
如果您还有任何问题或需要进一步的帮助,请随时提问!