diff --git a/internal/multiagent/eino_single_runner.go b/internal/multiagent/eino_single_runner.go index cb8a352a..0dd18efc 100644 --- a/internal/multiagent/eino_single_runner.go +++ b/internal/multiagent/eino_single_runner.go @@ -168,11 +168,29 @@ func RunEinoSingleChatModelAgent( }, EmitInternalEvents: true, } + ins := injectToolNamesOnlyInstruction(ctx, ag.EinoSingleAgentSystemInstruction(), mainTools) + if logger != nil { + names := collectToolNames(ctx, mainTools) + mountedNames := collectToolNames(ctx, mainToolsForCfg) + hasToolSearch := false + for _, n := range names { + if strings.EqualFold(strings.TrimSpace(n), "tool_search") { + hasToolSearch = true + break + } + } + logger.Info("eino tool-name injection", + zap.String("scope", "eino_single"), + zap.Int("tool_names", len(names)), + zap.Int("mounted_tool_names", len(mountedNames)), + zap.Bool("has_tool_search", hasToolSearch), + ) + } chatCfg := &adk.ChatModelAgentConfig{ Name: einoSingleAgentName, Description: "Eino ADK ChatModelAgent with MCP tools for authorized security testing.", - Instruction: ag.EinoSingleAgentSystemInstruction(), + Instruction: ins, Model: mainModel, ToolsConfig: mainToolsCfg, MaxIterations: maxIter, diff --git a/internal/multiagent/eino_tool_name_injection.go b/internal/multiagent/eino_tool_name_injection.go new file mode 100644 index 00000000..88bb6dfb --- /dev/null +++ b/internal/multiagent/eino_tool_name_injection.go @@ -0,0 +1,73 @@ +package multiagent + +import ( + "context" + "strings" + + "github.com/cloudwego/eino/components/tool" +) + +// injectToolNamesOnlyInstruction prepends a compact tool-name-only section into +// the system instruction so the model can reference current callable names. +func injectToolNamesOnlyInstruction(ctx context.Context, instruction string, tools []tool.BaseTool) string { + names := collectToolNames(ctx, tools) + if len(names) == 0 { + return strings.TrimSpace(instruction) + } + hasToolSearch := false + for _, n := range names { + if strings.EqualFold(strings.TrimSpace(n), "tool_search") { + hasToolSearch = true + break + } + } + + var sb strings.Builder + sb.WriteString("以下是当前会话中可调用的工具名称列表(仅名称,无参数定义):\n") + for _, name := range names { + sb.WriteString("- ") + sb.WriteString(name) + sb.WriteByte('\n') + } + sb.WriteString("\n使用规则:\n") + sb.WriteString("1) 上述仅为名称列表,不包含参数定义。\n") + if hasToolSearch { + sb.WriteString("2) 在调用具体工具前,应先使用 tool_search 查看工具详情与参数要求,再发起调用。\n") + } else { + sb.WriteString("2) 调用具体工具前,请先确认该工具的参数要求;不确定时先澄清再调用。\n") + } + sb.WriteString("3) 不要臆造不存在的工具名。\n\n") + if s := strings.TrimSpace(instruction); s != "" { + sb.WriteString(s) + } + return sb.String() +} + +func collectToolNames(ctx context.Context, tools []tool.BaseTool) []string { + if len(tools) == 0 { + return nil + } + seen := make(map[string]struct{}, len(tools)) + out := make([]string, 0, len(tools)) + for _, t := range tools { + if t == nil { + continue + } + info, err := t.Info(ctx) + if err != nil || info == nil { + continue + } + name := strings.TrimSpace(info.Name) + if name == "" { + continue + } + key := strings.ToLower(name) + if _, ok := seen[key]; ok { + continue + } + seen[key] = struct{}{} + out = append(out, name) + } + return out +} + diff --git a/internal/multiagent/runner.go b/internal/multiagent/runner.go index 9878f351..79437567 100644 --- a/internal/multiagent/runner.go +++ b/internal/multiagent/runner.go @@ -261,10 +261,29 @@ func RunDeepAgent( subHandlers = append(subHandlers, teleMw) } + subInstrFinal := injectToolNamesOnlyInstruction(ctx, instr, subTools) + if logger != nil { + subNames := collectToolNames(ctx, subTools) + mountedNames := collectToolNames(ctx, subToolsForCfg) + hasToolSearch := false + for _, n := range subNames { + if strings.EqualFold(strings.TrimSpace(n), "tool_search") { + hasToolSearch = true + break + } + } + logger.Info("eino tool-name injection", + zap.String("scope", "sub_agent"), + zap.String("agent", id), + zap.Int("tool_names", len(subNames)), + zap.Int("mounted_tool_names", len(mountedNames)), + zap.Bool("has_tool_search", hasToolSearch), + ) + } sa, err := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{ Name: id, Description: desc, - Instruction: instr, + Instruction: subInstrFinal, Model: subModel, ToolsConfig: adk.ToolsConfig{ ToolsNodeConfig: compose.ToolsNodeConfig{ @@ -316,6 +335,25 @@ func RunDeepAgent( orchDescription = d } } + orchInstruction = injectToolNamesOnlyInstruction(ctx, orchInstruction, mainTools) + if logger != nil { + mainNames := collectToolNames(ctx, mainTools) + mountedNames := collectToolNames(ctx, mainToolsForCfg) + hasToolSearch := false + for _, n := range mainNames { + if strings.EqualFold(strings.TrimSpace(n), "tool_search") { + hasToolSearch = true + break + } + } + logger.Info("eino tool-name injection", + zap.String("scope", "orchestrator"), + zap.String("orchestration", orchMode), + zap.Int("tool_names", len(mainNames)), + zap.Int("mounted_tool_names", len(mountedNames)), + zap.Bool("has_tool_search", hasToolSearch), + ) + } supInstr := strings.TrimSpace(orchInstruction) if orchMode == "supervisor" {