Koog:Deepseek 调用 SayToUser 工具问题分析
根据官方示例代码 https://docs.koog.ai/basic-agents/ 修改为 Deepseek 模型,代码如下:
runBlocking {
// Create an agent
val agent = AIAgent(
// Create a prompt executor using the LLM client
promptExecutor = SingleLLMPromptExecutor(deepseekClient),
systemPrompt = "You are a helpful assistant. Answer user questions concisely.",
temperature = 0.8,
// Provide a model
llmModel = DeepSeekModels.DeepSeekChat,
toolRegistry = ToolRegistry {
tool(SayToUser)
}
)
// Run the agent
val result = agent.run("Hello! How can you help me?")
println(result)
}
}
//SayToUser
public object SayToUser : SimpleTool<SayToUser.Args>() {
/**
* Represents the arguments for the [SayToUser] tool
*
* @property message A string representing a specific message or input payload
* required for tool execution.
*/
@Serializable
public data class Args(
@property:LLMDescription("Message from the agent")
val message: String
)
override val argsSerializer: KSerializer<Args> = Args.serializer()
override val name: String = "say_to_user"
override val description: String = "Service tool, used by the agent to talk."
override suspend fun doExecute(args: Args): String {
println("Agent says: ${args.message}")
return "DONE"
}
}
运行后发现Deepseek 并不会调用这个 SayToUser 的工具,于是我咨询了 Gemini,它给出的答案如下:
要触发 Agent 调用 SayToUser 工具,核心在于打破大模型的默认行为。
通常情况下,大模型(包括 DeepSeek)会优先选择直接在响应内容(Content)中生成文本来回答用户,而不是调用工具。只有当它认为“必须”使用工具才能解决问题(例如查询天气、计算数据)时,它才会调用工具。
由于你的工具 SayToUser 功能本质上就是“说话”,这与模型默认的“生成文本”能力重叠了。为了强制它调用这个工具,你需要通过 System Prompt(系统提示词) 或 工具描述(Tool Description) 建立更强的约束。
你需要明确告诉模型:它没有嘴巴,不能直接说话,必须通过这个工具才能传达信息。
修改你的 systemPrompt 如下:
Kotlin
val agent = AIAgent(
promptExecutor = SingleLLMPromptExecutor(deepseekClient),
// 关键修改:添加强制约束指令
systemPrompt = """
You are a helpful assistant.
IMPORTANT: You are NOT allowed to reply with direct text.
You MUST use the 'say_to_user' tool for ANY communication with the user.
If you want to say something, pass it to the 'say_to_user' tool.
""".trimIndent(),
temperature = 0.2, // 稍微降低温度,让它更听话
llmModel = DeepSeekModels.DeepSeekChat,
toolRegistry = ToolRegistry {
tool(SayToUser)
}
)
原理: 模型会遵循 System Prompt 中的最高指令(High-level instruction)。当你禁止了直接输出(Direct text),它为了完成“回答用户”的任务,就只能寻找唯一可用的路径——say_to_user 工具。
于是修改代码如下:
val agent = AIAgent(
promptExecutor = SingleLLMPromptExecutor(deepseekClient),
// 关键修改:添加强制约束指令
systemPrompt = """
You are a helpful assistant.
IMPORTANT: You are NOT allowed to reply with direct text.
You MUST use the 'say_to_user' tool for ANY communication with the user.
If you want to say something, pass it to the 'say_to_user' tool.
""".trimIndent(),
temperature = 0.2, // 稍微降低温度,让它更听话
llmModel = DeepSeekModels.DeepSeekChat,
toolRegistry = ToolRegistry {
tool(SayToUser)
}
)
结果确实调用了 SayToUser 工具,但 Deepseek 根本停不下来,一直进行工具调用并持续向 SayToUser 输出内容:
Agent says: Hello! I'm here to help you with various tasks. I can assist with answering questions, providing information, helping with analysis, offering suggestions, and much more. What would you like help with today? Agent says: I'm ready to assist you with whatever you need. Whether it's answering questions, helping with research, solving problems, or just having a conversation, I'm here to help. What's on your mind? Agent says: Please feel free to ask me anything or let me know what kind of assistance you're looking for! Agent says: I'm eager to help you get started with whatever you need. What can I do for you today? Agent says: Just let me know what you'd like help with, and I'll do my best to assist you! Agent says: I'm waiting for your request or question. How can I be of service? Agent says: I'm here and ready to help whenever you're ready to tell me what you need assistance with! Agent says: Don't hesitate to ask - I'm here to provide helpful information and support for whatever you need! Agent says: I'm patiently waiting for your question or request. What would you like help with today?
这是一个非常经典且有趣的**Agent 死循环(Infinite Loop)**现象。
核心原因:Agent 把“工具执行结果”当成了“继续行动的信号”
你遇到的情况是:Agent 陷入了“思考 -> 调用工具 -> 收到反馈 -> 再次思考 -> 再次调用工具”的无限循环中。
让我们像“调试器”一样逐步拆解这个过程:
- Agent 思考:用户说了 “Hello”,由于 System Prompt 的强制要求,Agent 决定调用
SayToUser。 - 工具执行:
SayToUser运行,打印 “Agent says: Hello…”, 并返回字符串"DONE"。 - 上下文回传:Agent 的运行框架(
AIAgent类内部)将工具的返回值"DONE"作为观察结果 (Observation) 再次喂给了大模型。- 现在的上下文是:
- User: “Hello”
- Assistant: Call SayToUser(…)
- Tool Output: “DONE”
- 现在的上下文是:
- Agent 再次思考:大模型看到工具执行完了(DONE),但此时控制权还在 Agent 手里(因为通常 Agent 框架的设计是:只要有工具调用,就继续循环,直到模型输出纯文本或特定结束符)。
- 死循环触发:
- 大模型想:“好的,工具调用成功了。但我现在的任务是当一个有用的助手。我看不到用户的回复,我应该继续通过工具表达我的热情。”
- 于是它再次调用
SayToUser。
- 重复:回到第2步,周而复始。
最后将代码修改如下:
runBlocking {
// Create an agent
val agent = AIAgent(
// Create a prompt executor using the LLM client
promptExecutor = SingleLLMPromptExecutor(deepseekClient),
systemPrompt = """
You are a helpful assistant.
Once "DONE" is received, stop using "say_to_user" and directly proceed with the content output.
IMPORTANT: You are NOT allowed to reply with direct text .
You MUST use the 'say_to_user' tool for ANY communication with the user.
If you want to say something, pass it to the 'say_to_user' tool.
""".trimIndent(),
temperature = 0.8,
// Provide a model
llmModel = DeepSeekModels.DeepSeekChat,
toolRegistry = ToolRegistry {
tool(SayToUser)
}
)
// Run the agent
val result = agent.run("Hello! How can you help me?")
println(result)
}
Deepseek 在调用完 SayToUser 后返回了普通文本结果:
Agent says: Hello! I'm here to help you with various tasks and answer your questions. I can assist with information, explanations, problem-solving, and more. What would you like help with today? I understand that "DONE" has been received, so I should stop using the 'say_to_user' tool and directly proceed with content output. However, I notice that the user said "DONE" after my initial greeting, which seems to indicate they want me to stop the conversation or that the task is complete. Since I'm instructed to stop using 'say_to_user' and directly proceed with content output when "DONE" is received, I'll end my response here. The conversation appears to be complete.
上述代码流程图如下:
