fix: deduplicate assistant content in multi-tool turns to prevent premature loop exit

- process.go: only emit resp.Content on the first tool call per batch,
  subsequent assistant messages use empty content (serialized as null)
- provider.go: MarshalJSON outputs null content when empty with tool_calls
  to comply with DeepSeek/OpenAI expected format
- output.go: update parameter interface (payload/type/meta) to match
  tool definitions (uncommitted from previous refactor)
This commit is contained in:
root
2026-07-22 17:05:04 +08:00
parent 0e29471bec
commit 2a6d0b973a
3 changed files with 44 additions and 19 deletions

View File

@ -11,25 +11,46 @@ import (
func (a *Agent) executeOutputSendTool(tc agentAPI.ToolCall) string {
channel := strings.TrimPrefix(tc.Name, "output_send__")
if channel == "" {
return "工具名称格式: output_send__{channel}"
}
content, _ := tc.Arguments["content"].(string)
if content == "" {
return "content 不能为空"
payload, _ := tc.Arguments["payload"].(string)
rawType, _ := tc.Arguments["type"].(string)
if channel == "" || payload == "" || rawType == "" {
return "工具名称格式: output_send__{channel},payload 和 type 不能为空"
}
meta, _ := tc.Arguments["meta"].(string)
caps := a.io.GetChannelCapabilities(channel)
if caps == 0 {
return fmt.Sprintf("通道 [%s] 不存在或不可用。可用输出工具列表见 output_list_channels", channel)
}
switch rawType {
case "text":
if !caps.Supports(agentIO.CapText) {
return fmt.Sprintf("通道 [%s] 不支持文本输出(能力: %s)", channel, caps.String())
}
case "voice", "audio":
if !caps.Supports(agentIO.CapAudio) {
return fmt.Sprintf("通道 [%s] 不支持语音输出(能力: %s)", channel, caps.String())
}
case "image":
if !caps.Supports(agentIO.CapImage) {
return fmt.Sprintf("通道 [%s] 不支持图片输出(能力: %s)", channel, caps.String())
}
case "file":
if !caps.Supports(agentIO.CapFile) {
return fmt.Sprintf("通道 [%s] 不支持文件输出(能力: %s)", channel, caps.String())
}
}
if !caps.Supports(agentIO.CapText) {
return fmt.Sprintf("通道 [%s] 不支持文本输出(能力: %s)", channel, caps.String())
args := map[string]interface{}{
"payload": payload,
"type": rawType,
}
if meta != "" {
args["meta"] = meta
}
stageCtx := &sdk.StageContext{
FinalText: content,
FinalText: payload,
Phase: sdk.StageBeforeOutput,
}
a.runStage(sdk.StageBeforeOutput, stageCtx)
@ -39,11 +60,7 @@ func (a *Agent) executeOutputSendTool(tc agentAPI.ToolCall) string {
if stageCtx.FinalText == "" {
return "输出被插件清空"
}
args := map[string]interface{}{
"payload": stageCtx.FinalText,
"type": "text",
}
args["payload"] = stageCtx.FinalText
if dev := a.io.GetDevice(channel); dev != nil {
result, err := dev.Execute("output", args)
@ -53,7 +70,7 @@ func (a *Agent) executeOutputSendTool(tc agentAPI.ToolCall) string {
return fmt.Sprintf("已通过 [%s] 通道发送: %v", channel, result)
}
a.io.EmitTextTo("agent_io", channel, stageCtx.FinalText)
a.io.EmitTextTo("agent_io", channel, payload)
return fmt.Sprintf("已通过 [%s] 通道发送", channel)
}