mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-09-24 19:08:10 +00:00
fix(core): 插件拒绝工具时把 ctx.Response 的理由透给模型
before_toolcall 的 ctx.Response 是插件写的**拒绝理由**,但工具结果被写死成 「工具 X 已被插件拒绝」,理由从不到达模型——模型于是不知道能不能重试, 会反复重试被拒的调用。抽出 denialResultText 并在有理由时原样透出。
This commit is contained in:
@ -671,7 +671,7 @@ func (a *Agent) stepToolBegin(f *TaskFrame) stepOutcome {
|
||||
f.StageCtx.ToolCalls = []sdk.ToolCall{sdkTC}
|
||||
f.StageCtx.ToolResults = nil
|
||||
if a.runStage(sdk.StageBeforeToolcall, f.StageCtx) {
|
||||
result := fmt.Sprintf("工具 %s 已被插件拒绝", tc.Name)
|
||||
result := denialResultText(f.StageCtx, tc.Name)
|
||||
f.Msgs = append(f.Msgs, agentAPI.Message{Role: "assistant", ToolCalls: []agentAPI.ToolCall{tc}})
|
||||
f.Msgs = append(f.Msgs, agentAPI.Message{Role: "tool", ToolCallID: tc.ID, Content: result})
|
||||
a.publishEvent(events.EventToolCall, map[string]interface{}{
|
||||
@ -702,6 +702,20 @@ func (a *Agent) stepToolBegin(f *TaskFrame) stepOutcome {
|
||||
return outcomeContinue
|
||||
}
|
||||
|
||||
// denialResultText 返回「工具被插件拒绝」时交给模型的工具结果。
|
||||
//
|
||||
// 插件在 before_toolcall 里用 ctx.Response 写的是**拒绝理由**(为什么被拒、
|
||||
// 能不能重试)。此前这里一律丢成通用文案,模型看不到原因就会反复重试同一个
|
||||
// 调用——权限门精心写的"请不要重试"等于白写。有理由就用理由。
|
||||
func denialResultText(ctx *sdk.StageContext, toolName string) string {
|
||||
if ctx != nil && ctx.Response != nil {
|
||||
if reason := strings.TrimSpace(*ctx.Response); reason != "" {
|
||||
return reason
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf("工具 %s 已被插件拒绝", toolName)
|
||||
}
|
||||
|
||||
// stepToolExec 执行工具。**临界区**:见设计文档 §4.3。
|
||||
func (a *Agent) stepToolExec(f *TaskFrame) stepOutcome {
|
||||
result := a.executeToolCall(f.CurTool, f.OutputChannel)
|
||||
|
||||
@ -5,6 +5,7 @@ import (
|
||||
"testing"
|
||||
|
||||
agentAPI "gitcode.com/JianFeeeee/HomeAgent/internal/agent/api"
|
||||
sdk "gitcode.com/JianFeeeee/homeagent-sdk/sdk"
|
||||
)
|
||||
|
||||
// appendPlaceholder 复刻 process() 循环顶部的补位逻辑。
|
||||
@ -115,3 +116,23 @@ func TestIsOutputDeliveryTool(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 插件在 before_toolcall 里给出的拒绝理由必须原样进入工具结果。
|
||||
// 若被通用文案覆盖,模型不知道「为什么被拒 / 能不能重试」,会反复重试同一个调用。
|
||||
func TestDenialReasonReachesModel(t *testing.T) {
|
||||
reason := "QQ 权限策略拒绝私人资源工具 calendar_list;请不要重试"
|
||||
ctx := &sdk.StageContext{Response: &reason}
|
||||
if got := denialResultText(ctx, "calendar_list"); got != reason {
|
||||
t.Fatalf("拒绝理由被丢弃,实际: %q", got)
|
||||
}
|
||||
|
||||
// 插件没给理由时退回通用文案(保持既有行为)。
|
||||
if got := denialResultText(&sdk.StageContext{}, "calendar_list"); !strings.Contains(got, "已被插件拒绝") {
|
||||
t.Fatalf("无理由时应退回通用文案,实际: %q", got)
|
||||
}
|
||||
// 空白理由不算理由。
|
||||
blank := " "
|
||||
if got := denialResultText(&sdk.StageContext{Response: &blank}, "x"); !strings.Contains(got, "已被插件拒绝") {
|
||||
t.Fatalf("空白理由应退回通用文案,实际: %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user