From 762f778e1ecfd7c453cfc9159f756320b5bd3aee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=85=AC=E6=98=8E?= <83812544+Ed1s0nZ@users.noreply.github.com> Date: Wed, 13 May 2026 12:05:12 +0800 Subject: [PATCH] Add files via upload --- internal/multiagent/eino_middleware.go | 19 +++++++----- internal/multiagent/eino_single_runner.go | 13 ++------ .../multiagent/eino_tool_name_injection.go | 31 ++++++++++++------- internal/multiagent/runner.go | 26 ++++------------ 4 files changed, 40 insertions(+), 49 deletions(-) diff --git a/internal/multiagent/eino_middleware.go b/internal/multiagent/eino_middleware.go index 53591fae..062faf6b 100644 --- a/internal/multiagent/eino_middleware.go +++ b/internal/multiagent/eino_middleware.go @@ -161,6 +161,8 @@ func buildReductionMiddleware(ctx context.Context, mw config.MultiAgentEinoMiddl } // prependEinoMiddlewares returns handlers to prepend (outermost first) and optionally replaces tools when tool_search is used. +// toolSearchActive is true when the toolsearch middleware was mounted (dynamic tools split off); callers should pass this to +// injectToolNamesOnlyInstruction — tool_search is not part of the pre-middleware tools list, so name-scanning alone cannot detect it. func prependEinoMiddlewares( ctx context.Context, mw *config.MultiAgentEinoMiddlewareConfig, @@ -170,16 +172,16 @@ func prependEinoMiddlewares( skillsRoot string, conversationID string, logger *zap.Logger, -) (outTools []tool.BaseTool, extraHandlers []adk.ChatModelAgentMiddleware, err error) { +) (outTools []tool.BaseTool, extraHandlers []adk.ChatModelAgentMiddleware, toolSearchActive bool, err error) { if mw == nil { - return tools, nil, nil + return tools, nil, false, nil } outTools = tools if mw.PatchToolCallsEffective() { patchMW, perr := patchtoolcalls.New(ctx, &patchtoolcalls.Config{}) if perr != nil { - return nil, nil, fmt.Errorf("patchtoolcalls: %w", perr) + return nil, nil, false, fmt.Errorf("patchtoolcalls: %w", perr) } extraHandlers = append(extraHandlers, patchMW) } @@ -190,7 +192,7 @@ func prependEinoMiddlewares( } else { redMW, rerr := buildReductionMiddleware(ctx, *mw, conversationID, einoLoc, logger) if rerr != nil { - return nil, nil, rerr + return nil, nil, false, rerr } extraHandlers = append(extraHandlers, redMW) } @@ -209,10 +211,11 @@ func prependEinoMiddlewares( if split && len(dynamic) > 0 { ts, terr := toolsearch.New(ctx, &toolsearch.Config{DynamicTools: dynamic}) if terr != nil { - return nil, nil, fmt.Errorf("toolsearch: %w", terr) + return nil, nil, false, fmt.Errorf("toolsearch: %w", terr) } extraHandlers = append(extraHandlers, ts) outTools = static + toolSearchActive = true if logger != nil { logger.Info("eino middleware: tool_search enabled", zap.Int("static_tools", len(static)), @@ -233,12 +236,12 @@ func prependEinoMiddlewares( } baseDir := filepath.Join(skillsRoot, rel, sanitizeEinoPathSegment(conversationID)) if mk := os.MkdirAll(baseDir, 0o755); mk != nil { - return nil, nil, fmt.Errorf("plantask mkdir: %w", mk) + return nil, nil, toolSearchActive, fmt.Errorf("plantask mkdir: %w", mk) } ptBE := &localPlantaskBackend{Local: einoLoc} pt, perr := plantask.New(ctx, &plantask.Config{Backend: ptBE, BaseDir: baseDir}) if perr != nil { - return nil, nil, fmt.Errorf("plantask: %w", perr) + return nil, nil, toolSearchActive, fmt.Errorf("plantask: %w", perr) } extraHandlers = append(extraHandlers, pt) if logger != nil { @@ -247,7 +250,7 @@ func prependEinoMiddlewares( } } - return outTools, extraHandlers, nil + return outTools, extraHandlers, toolSearchActive, nil } func deepExtrasFromConfig(ma *config.MultiAgentConfig) (outputKey string, retry *adk.ModelRetryConfig, taskDesc func(context.Context, []adk.Agent) (string, error)) { diff --git a/internal/multiagent/eino_single_runner.go b/internal/multiagent/eino_single_runner.go index 2c32a1a8..388d6379 100644 --- a/internal/multiagent/eino_single_runner.go +++ b/internal/multiagent/eino_single_runner.go @@ -96,7 +96,7 @@ func RunEinoSingleChatModelAgent( return nil, err } - mainToolsForCfg, mainOrchestratorPre, err := prependEinoMiddlewares(ctx, &ma.EinoMiddleware, einoMWMain, mainTools, einoLoc, skillsRoot, conversationID, logger) + mainToolsForCfg, mainOrchestratorPre, singleToolSearchActive, err := prependEinoMiddlewares(ctx, &ma.EinoMiddleware, einoMWMain, mainTools, einoLoc, skillsRoot, conversationID, logger) if err != nil { return nil, fmt.Errorf("eino single eino 中间件: %w", err) } @@ -178,22 +178,15 @@ func RunEinoSingleChatModelAgent( }, EmitInternalEvents: true, } - ins := injectToolNamesOnlyInstruction(ctx, ag.EinoSingleAgentSystemInstruction(), mainTools) + ins := injectToolNamesOnlyInstruction(ctx, ag.EinoSingleAgentSystemInstruction(), mainTools, singleToolSearchActive) 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), + zap.Bool("tool_search_middleware", singleToolSearchActive), ) } diff --git a/internal/multiagent/eino_tool_name_injection.go b/internal/multiagent/eino_tool_name_injection.go index 88bb6dfb..2e0fe9f8 100644 --- a/internal/multiagent/eino_tool_name_injection.go +++ b/internal/multiagent/eino_tool_name_injection.go @@ -9,34 +9,43 @@ import ( // 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 { +// toolSearchMiddlewareActive must be true when prependEinoMiddlewares mounted toolsearch (dynamic tools); do not infer this +// by scanning tool names — tool_search is injected by middleware and is usually absent from the pre-split tools list. +func injectToolNamesOnlyInstruction(ctx context.Context, instruction string, tools []tool.BaseTool, toolSearchMiddlewareActive bool) 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 + hasToolSearch := toolSearchMiddlewareActive + if !hasToolSearch { + for _, n := range names { + if strings.EqualFold(strings.TrimSpace(n), "tool_search") { + hasToolSearch = true + break + } } } var sb strings.Builder - sb.WriteString("以下是当前会话中可调用的工具名称列表(仅名称,无参数定义):\n") + sb.WriteString("以下是当前会话绑定的工具名称索引(仅名称,无参数 JSON Schema)。\n") + sb.WriteString("说明:若启用了 tool_search,则列表里可能含「非常驻」工具——它们不一定出现在当前轮次下发给模型的工具定义中;在未看到该工具的完整 schema 前,禁止凭名称臆测参数。\n") for _, name := range names { sb.WriteString("- ") sb.WriteString(name) sb.WriteByte('\n') } sb.WriteString("\n使用规则:\n") - sb.WriteString("1) 上述仅为名称列表,不包含参数定义。\n") + sb.WriteString("1) 上表仅为名称索引,不含参数定义。禁止猜测参数名、类型、枚举取值或是否必填。\n") if hasToolSearch { - sb.WriteString("2) 在调用具体工具前,应先使用 tool_search 查看工具详情与参数要求,再发起调用。\n") + sb.WriteString("【强制 / 最高优先级】本会话已启用 tool_search(动态工具池)。凡名称索引里出现、但你在「当前请求所附 tools 定义」中看不到其完整参数 schema 的工具,一律必须先调用 tool_search;为省 token 或赶进度而跳过 tool_search、直接调用业务工具,属于明确禁止的错误流程。\n") + sb.WriteString("2) 默认策略:只要对目标工具的参数定义有任何不确定,就先 tool_search;宁可多一次 tool_search,也不要在未见 schema 时盲调业务工具。\n") + sb.WriteString("3) 调用顺序:先 tool_search(唯一必填参数 regex_pattern:按工具名匹配的正则,如子串 nuclei 或 ^exact_tool_name$)→ 在后续轮次确认目标工具已出现在 tools 列表且已阅读其 schema → 再发起对该工具的真实调用。\n") + sb.WriteString("4) tool_search 的返回仅为匹配到的工具名列表;schema 在解锁后的下一轮才会下发。禁止在 schema 未出现时编造 JSON 参数。\n") + sb.WriteString("5) 不要臆造不存在的工具名。\n\n") } else { - sb.WriteString("2) 调用具体工具前,请先确认该工具的参数要求;不确定时先澄清再调用。\n") + sb.WriteString("2) 调用具体工具前,请先确认该工具的参数要求(以当前请求中的工具定义为准);不确定时先澄清再调用。\n") + sb.WriteString("3) 不要臆造不存在的工具名。\n\n") } - sb.WriteString("3) 不要臆造不存在的工具名。\n\n") if s := strings.TrimSpace(instruction); s != "" { sb.WriteString(s) } diff --git a/internal/multiagent/runner.go b/internal/multiagent/runner.go index 313d58bb..65ea5c36 100644 --- a/internal/multiagent/runner.go +++ b/internal/multiagent/runner.go @@ -223,7 +223,7 @@ func RunDeepAgent( return nil, fmt.Errorf("子代理 %q 工具: %w", id, err) } - subToolsForCfg, subPre, err := prependEinoMiddlewares(ctx, &ma.EinoMiddleware, einoMWSub, subTools, einoLoc, skillsRoot, conversationID, logger) + subToolsForCfg, subPre, subToolSearchActive, err := prependEinoMiddlewares(ctx, &ma.EinoMiddleware, einoMWSub, subTools, einoLoc, skillsRoot, conversationID, logger) if err != nil { return nil, fmt.Errorf("子代理 %q eino 中间件: %w", id, err) } @@ -260,23 +260,16 @@ func RunDeepAgent( subHandlers = append(subHandlers, teleMw) } - subInstrFinal := injectToolNamesOnlyInstruction(ctx, instr, subTools) + subInstrFinal := injectToolNamesOnlyInstruction(ctx, instr, subTools, subToolSearchActive) 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), + zap.Bool("tool_search_middleware", subToolSearchActive), ) } sa, err := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{ @@ -341,28 +334,21 @@ func RunDeepAgent( if err != nil { return nil, err } - mainToolsForCfg, mainOrchestratorPre, err := prependEinoMiddlewares(ctx, &ma.EinoMiddleware, einoMWMain, mainTools, einoLoc, skillsRoot, conversationID, logger) + mainToolsForCfg, mainOrchestratorPre, mainToolSearchActive, err := prependEinoMiddlewares(ctx, &ma.EinoMiddleware, einoMWMain, mainTools, einoLoc, skillsRoot, conversationID, logger) if err != nil { return nil, err } - orchInstruction = injectToolNamesOnlyInstruction(ctx, orchInstruction, mainTools) + orchInstruction = injectToolNamesOnlyInstruction(ctx, orchInstruction, mainTools, mainToolSearchActive) 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), + zap.Bool("tool_search_middleware", mainToolSearchActive), ) }