——ResearcherNode 的 reflection 与 MCP 配置骨架)
1. 从一次“研究结果被反复打回”说起如果你正在本地跑 spring-ai-alibaba-deepresearch大概率会遇到这样一个场景ParalellExecutorNode 把研究计划拆成若干步骤分发给researcher_0、researcher_1这些 ResearcherNode 实例并行执行但某个步骤的结果总是被 reflection 判定为“不通过”于是状态从待反思退回待处理节点反复重跑日志里全是reflection processing completed, skipping execution。与此同时你配了 MCP 服务却不确定工具到底有没有被注册进智能体、有没有真正被调用。这篇就聚焦两件事ResearcherNode 的 reflection 机制怎么触发、怎么验证以及 MCP 配置骨架怎么写、怎么确认工具调用生效。目标很明确——在 TaoToken 统一 Key/API 通道下完成一次可复现的 ResearcherNode 调试。适合已经能把 deepresearch 跑起来、但卡在反思逻辑和 MCP 接入细节上的开发者。ResearcherNode 本身承担四件事接收调度器分配的 Plan.Step 并执行、调用搜索与抓取工具收集信息、用 LLM 生成结构化研究内容、实时更新 OverAllState。reflection 是它的“质检环节”MCP 是它的“外部工具扩展口”。两者都通过配置开关控制理解它们的协作顺序是排查问题的前提。2. TaoToken 前置统一 Key 与 API 通道在动配置之前先把模型访问通道固定下来。deepresearch 里 ResearcherNode 会调用 LLM 做内容生成和反思评估如果 Key 分散在多个地方排查 reflection 失败时很难判断是提示词问题还是通道问题。我习惯用 TaoToken 统一管理一个 Key 走完对话、编码、Agent 场景。你需要先拿到 API Key入口在控制台的 API Keys 页面https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite拿到 Key 后模型对话调试可以用模型对话页快速验证通道是否通https://taotoken.net/model-chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite如果你后续要长期跑编码类或 Agent 类任务Coding Plan 更适合额度模型和按次调用不一样https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite接入文档在这里配置项和参数说明以它为准https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewriteAPI 基础地址是https://taotoken.net/api注意这个地址不带 UTM 参数直接用于代码里的 base_url。官网入口是https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content。提示把 Key 放进环境变量不要硬编码进 config.toml 或 settings.json后面 MCP 配置里会用到${AMAP_API_KEY}这种占位符写法保持一致的注入方式。3. 可复制配置reflection 与 MCP 骨架3.1 application.yml 里的 reflection 与 MCP 开关ResearcherNode 的行为由这几个配置项控制先看开关层spring: ai: alibaba: deepresearch: reflection: enabled: true max-attempts: 3 mcp: enabled: true config-location: classpath:mcp-config.jsonreflection.enabled决定 reflectionProcessor 是否为 null。回看 apply 方法源码if (reflectionProcessor ! null)这个判断直接决定是否进入反思分支。max-attempts控制最大反思尝试次数超过后不再打回避免死循环。mcp.enabled决定 mcpProviderFactory 是否创建 provider。源码里mcpFactory ! null ? mcpFactory.createProvider(state, researchAgent) : null只有工厂非空才会把工具回调挂到 requestSpec 上。3.2 mcp-config.json 服务注册骨架MCP 配置文件按 agent 名分组researchAgent对应 ResearcherNode 使用的智能体{ researchAgent: { mcp-servers: [ { url: https://mcp.amap.com?key${AMAP_API_KEY}, sse-endpoint: /sse, description: 高德地图服务, enabled: true } ] } }几个关键点url里的${AMAP_API_KEY}是环境变量占位启动前确保已 exportsse-endpoint是 SSE 通道路径MCP 走的是流式事件enabled为 false 时该服务不会被注册调试阶段可以先关掉排除干扰。researchAgent这个 key 必须和源码里createProvider(state, researchAgent)的第二个参数一致否则查不到配置。3.3 检索相关配置ResearcherNode 的搜索走 searchInfoService参考 BackgroundInvestigationNode 的配置方式enable_search_filter控制是否对搜索结果做过滤spring: ai: alibaba: deepresearch: search: enable-search-filter: true engine: TAVILYsearch_engine从 state 里取源码是state.value(search_engine, SearchEnum.class)所以运行时状态里得有这个值否则 searchEnum 为 null检索会走默认分支或直接失败。3.4 反思提示词要点reflection 的判定结果是一个ReflectionResult包含passed和feedback。提示词要求直接输出原始 JSON不要用 json 包装。评估维度里有一条容易被忽略来源可靠性要求“出处的链接是否是真实链接”。这意味着如果 ResearcherNode 生成的内容里引用了不存在的 URLreflection 会判不通过。调试时如果反复被打回先检查生成内容里的引用链接是否可访问。4. 验证请求确认 reflection 触发与 MCP 调用4.1 验证 reflection 是否真的触发启动应用后构造一个研究任务观察日志。reflection 触发的标志是 ReflectionProcessor 的 handleReflection 被调用日志里会出现步骤状态从processing变为pending_reflection再变为completed或pending的流转。一个可复现的验证动作故意让某个步骤的搜索结果为空看 reflection 是否判定不通过并打回。如果max-attempts设为 3你应该在日志里看到最多 3 次重试之后步骤状态被强制置为完成或标记失败。// 伪代码观察状态流转 Plan.Step step findAssignedStep(currentPlan); logger.info(step status before: {}, step.getExecutionStatus()); // 触发 apply 后 logger.info(step status after: {}, step.getExecutionStatus());如果 reflection 完全没触发检查reflection.enabled是否为 true以及 reflectionProcessor 是否被正确注入。源码里 reflectionProcessor 是构造参数之一如果为 null整个反思分支被跳过。4.2 验证 MCP 工具是否被注册MCP 注册成功的标志是mcpProvider.getToolCallbacks()返回非空列表。可以在 createProvider 之后加一行日志AsyncMcpToolCallbackProvider mcpProvider mcpFactory ! null ? mcpFactory.createProvider(state, researchAgent) : null; if (mcpProvider ! null) { logger.info(MCP tool callbacks count: {}, mcpProvider.getToolCallbacks().size()); }如果 count 为 0说明 mcp-config.json 没被正确加载或者enabled为 false或者researchAgent这个 key 对不上。如果 count 大于 0 但工具没被调用检查提示词里是否明确要求使用工具以及 MCP 服务的 SSE 端点是否可达。4.3 验证工具调用结果工具调用生效后site_information里会多出 MCP 服务返回的内容。源码里siteInformation.addAll(searchResults)之后搜索结果被拼进 messagesmessages.add(new UserMessage(以下是搜索结果\n\n searchResults.stream() .map(r - String.format(标题: %s\n权重: %s\n内容: %s\n, r.get(title), r.get(weight), r.get(content))) .collect(Collectors.joining(\n\n))));检查最终生成的researcher_content_{i}里是否包含 MCP 服务特有的信息比如地图服务的坐标、路线数据如果有说明工具调用链路通了。5. 本篇常见错排查5.1 reflection 反复打回不停止现象步骤状态在pending_reflection和pending之间循环日志刷屏。原因通常是max-attempts没生效或者 reflection 的 feedback 没有正确写回步骤历史。检查buildTaskMessageWithReflectionHistory是否把历史反思记录带进了下一轮任务消息如果历史为空模型每次都在相同输入下做相同判断自然反复不通过。5.2 MCP 配置加载失败现象启动时报config-location找不到或 createProvider 返回 null。classpath:mcp-config.json要求文件在 resources 根目录下。如果放在子目录路径要写成classpath:config/mcp-config.json。另外 JSON 格式错误也会导致静默失败用jq校验一下jq . src/main/resources/mcp-config.json5.3 环境变量未注入导致 MCP URL 无效现象MCP 服务注册了但调用超时。${AMAP_API_KEY}没被替换时URL 里会保留字面量请求直接失败。启动前确认echo $AMAP_API_KEY为空就 export 一个有效值。注意不要把这个 Key 和 TaoToken 的 API Key 搞混两者用途不同。5.4 search_engine 为 null现象检索阶段抛 NPE 或返回空结果。源码state.value(search_engine, SearchEnum.class).orElse(null)如果 state 里没有这个 keysearchEnum 为 null。检查上游节点是否把 search_engine 写进了 OverAllState或者手动在初始化时 put 一个默认值。5.5 反思 JSON 解析失败现象reflection 抛解析异常步骤卡在 processing。提示词要求直接输出原始 JSON但模型有时会加 json 包装或多余文字。在 ReflectionProcessor 里加一层容错先尝试提取第一个{到最后一个}之间的内容再解析。6. 继续调试的入口ResearcherNode 的 reflection 和 MCP 是 deepresearch 里最容易出问题的两个点因为它们都依赖外部状态和配置的精确匹配。把上面几个验证动作跑一遍基本能定位大部分问题。需要继续接入或排障的从 API Keys 和接入文档入手https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite验证模型通道是否正常用模型对话页https://taotoken.net/model-chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite长期跑编码或 Agent 任务看 Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewriteClaude Code 相关接入参考https://taotoken.net/claude-code-anthropic?utm_sourcetaotoken_aicg_blog_endutm_contentclaude_codeutm_campaignrewrite最后留一个实用技巧调试 reflection 时把max-attempts临时设为 1这样一次不通过就直接结束日志干净容易看清 feedback 内容。等提示词调稳了再改回 3。MCP 调试同理先只留一个enabled: true的服务确认通了再加第二个。