WebDancer: Towards Autonomous Information Seeking Agency

Paper: arXiv:2505.22648(本文笔记以 arXiv v3, 2025-08-10 源码/PDF 为主;OpenReview 标注为 NeurIPS 2025 poster) Code: Alibaba-NLP/WebAgent → DeepResearch Code reference: main @ f72f75d8 (2026-02-27)

1. Motivation (研究动机)

WebDancer 关注的问题不是“给 LLM 接一个搜索 API”这么简单,而是如何把复杂、多步、真实网页环境中的信息寻求能力内化到开源模型里。论文把目标称为 autonomous information seeking agency:模型需要在不固定、部分可观测的网页环境中反复决定下一步是搜索、访问页面还是给出答案;每一步都要根据已有证据更新计划,而不是一次性检索后生成。这个设定对应 Deep Research 类产品的核心能力:面对开放问题时能拆解目标、搜集证据、判断网页是否有用、继续追问并最终合成答案。

现有路线有三个主要瓶颈。第一,纯 prompting 或工程化 agent framework 可以调用工具,但能力主要来自底座模型和提示词,模型并没有经过 end-to-end agentic training 来学习“何时搜索、搜什么、访问哪里、如何利用观察结果”。这类方法在强 reasoning model 上可能可用,但很难把行为稳定迁移到较小开源模型。第二,已有 SFT/RL 搜索-agent 数据通常偏浅,很多 QA 可以通过一两次检索解决;论文指出一些传统多跳数据集已经被模型刷到很高分,不能代表真实深度信息寻求。第三,真实网页环境动态变化,页面内容、搜索结果、工具失败和证据缺失都会导致训练和评测非平稳;如果没有高质量轨迹、冷启动和在线式 RL,模型容易只学会格式或浅层工具调用。

本文要解决的具体问题是:如何从数据和训练阶段两个角度,系统构造一个 ReAct 风格的 web agent,让它在 GAIA、WebWalkerQA、BrowseComp 这类更接近真实检索任务的 benchmark 上具备多轮工具使用与多步推理能力。这里“值得研究”的地方在于,一旦可行,开源模型就不必只靠 prompt wrapper 模仿 Deep Research,而可以通过合成 QA、轨迹采样、SFT 冷启动和 RL 优化逐步获得 agentic policy。这样的能力对研究助理、事实核验、网页问答和企业知识检索都很关键,因为任务难点往往不是最终生成,而是中间的信息获取路径是否正确、充分、可恢复。

2. Idea (核心思想)

核心洞察是:web agent 的能力可以拆成一个可训练的四阶段流水线,而不是把所有希望寄托在推理模型本身。WebDancer 先合成更深的 QA,再为 QA 采样 ReAct 轨迹,然后用轨迹做 supervised cold start,最后用 outcome-based RL 在真实工具环境中优化决策。这样 SFT 负责教会模型基本交互语法和 Thought-Action-Observation 节奏,RL 负责在动态网页环境中强化“哪些行动序列最终能答对”。

本文的关键创新不是某个单独工具,而是把数据构造、轨迹质量控制、agent 格式学习和 DAPO 式策略优化连成一条 end-to-end recipe。与 Search-o1、R1-Searcher 这类侧重“搜索何时触发”或“用 outcome reward 学搜索行为”的方法相比,WebDancer 更强调 agent 轨迹本身的训练数据质量:CrawlQA 和 E2HQA 提供长程问题,short/long CoT 轨迹提供不同思考模式,过滤漏斗保证轨迹可用,SFT 和 RL 分工处理冷启动与泛化。与 WebThinker 等强 reasoning agent 相比,WebDancer 的焦点是构建一个可以从 7B/32B 到 QwQ-32B 扩展的训练范式,并在同一 ReAct 框架下对比 vanilla ReAct 的增益。

一句话概括:WebDancer 把“自主搜寻信息”视为可监督、可强化的行为序列学习问题;它先制造足够难且可验证的网页 QA,再让模型学习合格轨迹,最后在真实 search/visit 环境中用最终答案奖励修正策略。

3. Method (方法)

3.1 总体框架与四阶段训练

Figure 2 解读:图中把训练拆成 SFT 与 RL 两个大阶段,但其前置依赖是 QA 构造和轨迹采样。左侧 SFT 使用已经转成 ReAct 格式的 short-CoT 与 long-CoT 轨迹,让模型先学会用 Thought、Action、Observation、Answer 的结构进行多轮交互;右侧 RL 使用未进入 SFT 的 QA,在真实工具调用环境中 rollout,并用 DAPO 根据最终格式和答案正确性更新 policy。这个设计的直觉是:如果直接 RL,模型早期会把大量探索浪费在格式错误、工具参数错误或无效动作上;先 SFT 冷启动可以把搜索/访问/回答的动作空间约束到合理区域,再让 RL 优化长期收益。

论文将 pipeline 抽象成四步:Step I 构造复杂 web QA;Step II 对 QA 采样高质量 agent trajectories;Step III 对 ReAct 轨迹做 supervised fine-tuning;Step IV 对剩余 QA 做 agent RL。模型执行时的一个 ReAct round 被表示为 是 Thought, 是 Action, 是 Observation。动作可分成 action name 与参数 ,论文中的动作集合是 。search 返回 Top-10 标题与摘要片段;visit 返回由 summarizer model 生成的 evidence 与 summary;answer 结束循环。

历史轨迹写作: 在第 步,agent 依据 产生 。这一定义很重要,因为 WebDancer 不只是训练最终答案,而是在模型生成 token 中区分自主决策部分与外部工具反馈部分;后者会进入上下文,但在 SFT 和 RL loss 中不作为模型要学习生成的目标。

3.2 深度 QA 构造:CrawlQA 与 E2HQA

Figure 1 解读:上半部分是 CrawlQA:从 arXiv、GitHub、Wiki 等知识站点的 root URL 出发,模拟人类浏览点击子链接,收集页面内容,再让 GPT-4o 按 COUNT、MULTI-HOP、INTERSECTION 等类型生成 QA。下半部分是 E2HQA:从 SimpleQA 风格的实体答案问题开始,反复选取当前问题中的实体,围绕该实体搜索新内容,再用 LLM 将新内容改写成替换原实体的子问题,从而让一个简单问题逐步变成需要多步检索的问题。两条管线分别覆盖“从网页内容合成问题”和“从简单问题递归加深”的数据来源。

CrawlQA 的价值在于规模化和网页真实性。它不是从已有 benchmark 中抽难题,而是从真实网站结构里收集 root/subpage 信息,让问题更容易涉及真实网页跳转和页面证据。E2HQA 的价值在于可控地增加推理深度。若当前问题 中有实体 ,系统先通过搜索得到与 相关的信息 ,再由 LLM 生成替换表达 ,用 替换问题中的 得到 。因为答案保持不变,构造过程既提高检索难度,又保留可验证性。论文实际收集了 40K 个 E2HQA 与 60K 个 CrawlQA 样本,用于后续轨迹生成和 RL。

3.3 轨迹采样与过滤

轨迹采样基于 ReAct。short CoT 轨迹直接使用 GPT-4o 在 ReAct 框架下采集;long CoT 轨迹使用 QwQ-Plus:每一步只给历史 actions 与 observations,让 LRM 决定下一步动作,但不把此前 thought 输入给它,因为 QwQ-Plus 未必在训练中见过多步 thought 串接;生成出的中间 reasoning_content 则保留为当前 step 的 Thought,作为监督信号。每个 QA 最多进行 次 rejection sampling。

过滤采用三层漏斗。第一层 validity control 丢弃不符合 ReAct 格式或长上下文下不遵守指令的样本。第二层 correctness verification 只保留最终答案正确的轨迹,论文按照 Humanity/BrowseComp 风格使用 GPT-4o 做准确性判断。第三层 quality assessment 先用规则去掉动作数过多、幻觉或严重重复的轨迹,再用 prompt 检查 Information Non-redundancy、Goal Alignment、Logical Reasoning and Accuracy。这个漏斗的意义在于:SFT 阶段如果喂入格式错误或冗余轨迹,模型会学习到错误的工具调用习惯;RL 阶段如果保留无效 QA,也会让 outcome reward 变得噪声很大。

3.4 Agent SFT:只学习模型自主部分

SFT 阶段把完整轨迹转成 multi-turn chatml:tool responses 作为 user messages,模型的 thought/action 作为 assistant messages。论文定义 task context 和执行轨迹 ,其中 。loss 为: 这里 会 mask 掉 observation token,使训练只惩罚模型自己应该生成的 thought、action 和 answer,而不要求模型“背诵”搜索引擎或网页返回的内容。这一点是 agentic world modelling 的关键:观察结果必须进入上下文以影响下一步决策,但不能被当成模型要预测的目标,否则模型会学会编造 observation,而不是学会依据真实 observation 决策。

3.5 Agent RL:DAPO + outcome reward

RL 阶段在 SFT policy 基础上运行工具调用 rollout。对于每个 QA ,DAPO 从旧策略 采样 个候选 agentic executions ,论文实现细节给出的 rollout number 是 16。优化目标是 clipped policy objective: 并带有动态采样约束: 也就是说,同一 prompt 下如果所有样本都对或都错,就不提供有效相对学习信号;DAPO 会过采样并过滤掉这些 accuracy 为 1 或 0 的 prompt。优势估计为组内标准化: 奖励由格式和答案两部分组成: 是二值项,只有输出严格符合格式且 JSON tool calls 有效才为 1; 也是二值项,由 judge model 判断最终答案是否正确。实现细节中 基于 Qwen-72B-Instruct,reward prompt 参考 Humanity/BrowseComp 评测,RL rollout temperature=1.0、top_p=1.0。这个奖励设计反映了作者的取舍:格式奖励权重小,因为 SFT 已经解决大部分格式问题;答案奖励权重大,因为最终目标是信息寻求任务是否完成。

3.6 Released code 对应的推理实现与差异

Code reference: main @ f72f75d8 (2026-02-27) — pseudocode and mapping based on this commit

Paper ConceptSource FileKey Class/Function
ReAct multi-turn execution loopinference/react_agent.pyMultiTurnReactAgent._run, custom_call_tool
Tool registry for inferenceinference/react_agent.pyTOOL_CLASS, TOOL_MAP
Search actioninference/tool_search.pySearch.call, google_search_with_serp
Visit action and evidence summaryinference/tool_visit.pyVisit.call, readpage_jina, call_server
Demo agent wrapperWebAgent/WebDancer/demos/agents/search_agent.pySearchAgent._run
WebDancer demo launchWebAgent/WebDancer/scripts/run_demo.shexports GOOGLE_SEARCH_KEY, JINA_API_KEY, DASHSCOPE_API_KEY
SGLang servingWebAgent/WebDancer/scripts/deploy_model.shsglang.launch_server --tp 4 --port 8004
Public sample data formatWebAgent/WebDancer/datasets/sample_qa.jsonl, sample_traj.jsonlQA keys and chatml trajectory keys

论文公式与 released code 实现差异:arXiv 论文完整描述了 SFT、DAPO、reward、rollout 与训练配置,但官方 GitHub 在 main@f72f75d8 中主要释放的是 WebDancer demo/inference、sample QA/trajectory 和 DeepResearch/WebAgent 生态代码;我未在该 commit 中找到 WebDancer 专用的 DAPO/verl 训练脚本、SFT launch config 或 reward training implementation。因此上面的训练伪代码基于论文公式和实现细节,推理伪代码与 mapping 则锚定到 released code。另一个小差异是:论文中的 search 参数写作 queryfilter_year,而 released inference/tool_search.pySearch.parameters 只要求数组字段 queryvisit 的 released 参数是 urlgoal,而论文写作 goalurl_link

Released inference loop 的结构如下,直接对应 inference/react_agent.py

def run_react_inference(agent, item, planning_port):
    question = item["question"]
    messages = [
        {"role": "system", "content": SYSTEM_PROMPT},
        {"role": "user", "content": question},
    ]
    for step in range(MAX_LLM_CALL_PER_RUN):  # env default: 100
        content = agent.call_server(messages, planning_port)
        messages.append({"role": "assistant", "content": content.strip()})
 
        if "<tool_call>" in content and "</tool_call>" in content:
            tool_call = json5.loads(extract_between(content, "<tool_call>", "</tool_call>"))
            tool_name = tool_call.get("name", "")
            tool_args = tool_call.get("arguments", {})
            observation = TOOL_MAP[tool_name].call(tool_args)
            messages.append({"role": "user", "content": observation})
            continue
 
        if "<answer>" in content and "</answer>" in content:
            return extract_between(content, "<answer>", "</answer>")
 
        if count_tokens(messages) > 110 * 1024:
            messages[-1]["content"] = FORCE_FINAL_ANSWER_PROMPT
            return agent.call_server(messages, planning_port)
 
    return "No answer found."

Search tool 的 released 行为可以概括为:

def search_call(params):
    query = params["query"]
    queries = [query] if isinstance(query, str) else query
    snippets = []
    for q in queries:
        result = serper_google_search(q, top_k=10)
        snippets.append(format_titles_links_and_snippets(result))
    return "\n=======\n".join(snippets)

Visit tool 的 released 行为可以概括为:

def visit_call(params):
    urls = params["url"]
    goal = params["goal"]
    urls = [urls] if isinstance(urls, str) else urls
    outputs = []
    for url in urls:
        page = jina_readpage(url)
        summary_json = call_summarizer(EXTRACTOR_PROMPT.format(webpage_content=page, goal=goal))
        outputs.append(
            f"The useful information in {url} for user goal {goal} as follows:\n\n"
            f"Evidence in page:\n{summary_json['evidence']}\n\n"
            f"Summary:\n{summary_json['summary']}\n\n"
        )
    return "\n=======\n".join(outputs)

训练侧的 paper-level 伪代码如下;注意它反映论文算法,不代表 released repo 中存在同名训练函数:

def build_e2hqa(question, answer, search_engine, llm, num_refinements):
    for _ in range(num_refinements):
        entity = llm.select_entity(question)
        content = search_engine.search(entity)
        replacement = llm.rewrite_entity_as_subquestion(content, entity)
        question = question.replace(entity, replacement)
    return {"question": question, "answer": answer, "tag": "e2hqa"}
def rejection_sample_trajectories(qas, actor, judge, quality_checker, max_trials=5):
    kept = []
    for qa in qas:
        for _ in range(max_trials):
            traj = actor.rollout_react(qa["question"])
            if not valid_react_format(traj):
                continue
            if not judge.is_correct(traj.final_answer, qa["answer"]):
                continue
            if not quality_checker.pass_all(traj, criteria=[
                "information_non_redundancy",
                "goal_alignment",
                "logical_reasoning_and_accuracy",
            ]):
                continue
            kept.append({"qa": qa, "trajectory": traj})
            break
    return kept
def sft_loss(policy, trajectory, task_context):
    loss_terms = []
    for token_span in trajectory.spans:  # thought/action/observation/answer spans
        if token_span.kind == "observation":
            continue  # mask external feedback
        logprob = policy.logprob(token_span.tokens, context=task_context + trajectory.prefix_before(token_span))
        loss_terms.append(-logprob.mean())
    return torch.stack(loss_terms).mean()
def dapo_agent_rl_step(policy, old_policy, qa_batch, tools, judge, group_size=16):
    all_losses = []
    for q, answer in qa_batch:
        executions = [rollout_with_tools(old_policy, q, tools) for _ in range(group_size)]
        rewards = torch.tensor([
            0.1 * valid_format(e) + 0.9 * judge.is_correct(e.final_answer, answer)
            for e in executions
        ], dtype=torch.float32)
        if rewards.sum() == 0 or rewards.sum() == group_size:
            continue  # dynamic sampling: remove all-wrong/all-right prompts
        advantages = (rewards - rewards.mean()) / (rewards.std() + 1e-6)
        for execution, adv in zip(executions, advantages):
            for token in execution.model_generated_tokens():
                ratio = policy.prob(token) / old_policy.prob(token)
                clipped = torch.clamp(ratio, 1 - eps_low, 1 + eps_high)
                all_losses.append(-torch.minimum(ratio * adv, clipped * adv))
    return torch.stack(all_losses).mean()

4. Experimental Setup (实验设置)

训练数据。 论文收集 40K 个 E2HQA 与 60K 个 CrawlQA 样本,再通过 QwQ 或 GPT-4o 生成轨迹并做多阶段过滤。最终 SFT 数据统计为:Short-CoT 7,678 条,平均 action count 4.56,平均 thinking length 510.03 tokens;Long-CoT 6,550 条,平均 action count 2.31,平均 thinking length 1599.39 tokens。作者还使用 MuSiQue、Bamboogle、PopQA、2Wiki、HotpotQA 等 open-only QA 数据,并通过简单 RAG 过滤去掉过易问题。

评测数据。 主实验使用 GAIA 与 WebWalkerQA。GAIA 用于评估通用 AI assistant 的复杂信息检索能力;WebWalkerQA 更聚焦深度网页信息检索。论文使用 GAIA text-only validation split 的 103 个问题,以及 WebWalkerQA test set 的 680 个问题。更难 benchmark 包括 BrowseComp (English) 与 BrowseComp-zh。

评测指标。 主表采用 LLM-as-Judge 的 Pass@1。分析部分还使用 Pass@3 与 Cons@3:Cons@3 对三次独立回答的正确数量给分,一次正确为 ,两次为 ,三次为 。BrowseComp 表用 x/y 报告 Pass@1/Pass@3。因为网页环境动态,作者还做了 temperature 稳定性分析与 memorization stress test。

基线。 No Agency 包括 Qwen2.5-7B/32B/72B-Instruct、QwQ-32B、DeepSeek-R1-671B、GPT-4o,以及部分 RAG 设置。Closed-source agentic baseline 是 OpenAI Deep Research。Open-source agentic baseline 包括 Search-o1、R1-Searcher、WebThinker-Base、WebThinker-RL、Simple DeepSearcher;作者还在同一 ReAct framework 下比较 vanilla ReAct 与 WebDancer。

训练/推理配置。 数据构造时 rejection sampling 次数 ;summarizer model 是 Qwen-2.5-72B;系统基于 Qwen-Agents 的 ReAct framework。judge model 基于 Qwen-72B-Instruct。RL 使用 verl 支持算法与 rollouts,rollout number=16。推理参数为 temperature=0.6、top_p=0.95;LRM repetition penalty=1.1,LLM repetition penalty=1.0;RL rollout temperature=1.0、top_p=1.0。实验硬件是 32 nodes × 8 NVIDIA H20 (96GB)。Qwen-7B 与 Qwen-32B 使用 Short-CoT 数据训练;QwQ-32B 使用 Long-CoT 数据训练。

5. Experimental Results (实验结果)

5.1 主结果:WebDancer 在同一 ReAct 框架内显著优于 vanilla ReAct

主表最关键的是同一 backbone 下 vanilla ReAct 与 WebDancer 的对照。Qwen-2.5-7B 上,GAIA Avg 从 18.4 提升到 31.0(+12.6),WebWalkerQA Avg 从 24.2 提升到 36.0(+11.8)。Qwen-2.5-32B 上,GAIA Avg 从 31.0 到 40.7(+9.7),WebWalkerQA Avg 从 31.9 到 38.4(+6.5)。QwQ-32B 上,GAIA Avg 从 37.8 到 51.5(+13.7),WebWalkerQA Avg 从 24.1 到 47.9(+23.8)。这说明增益不是来自 ReAct wrapper 本身,而是来自本文的数据、SFT 和 RL 训练。

更横向地看,OpenAI Deep Research 在 GAIA Avg 上仍最高,为 67.4;但在开源或可复现 agentic 框架中,WebDancer-QwQ-32B 达到 GAIA Avg 51.5、WebWalkerQA Avg 47.9,高于 WebThinker-RL 的 48.5/46.5,也高于 GPT-4o vanilla ReAct 的 34.6/33.8。作者据此认为,轻量 ReAct 框架只要配合系统训练,也能把开源模型推到强 agentic baseline 水平。

5.2 更难 benchmark:BrowseComp 与 BrowseComp-zh

BrowseComp 表显示,WebDancer 在需要复杂信息寻求的 English/Chinese BrowseComp 上也有稳定优势。GPT-4o 不带 browsing 时 En=0.6、Zh=6.2;GPT-4o 带 browsing 时 En=1.9,中文未报告;QwQ-32B no browsing 在 Zh=11.1。WebDancer with browsing 达到 En=3.8/7.9、Zh=18.0/31.5(表中以 Pass@1/Pass@3 形式呈现)。这些绝对数值不高,但说明任务本身非常难;WebDancer 的意义在于它把开源 agent 从“几乎答不出”推进到可测的正收益区间。

5.3 细粒度分析与数据效率

Figure 3 解读:该图按 Pass@1、Pass@3、Cons@3 展开 GAIA 细分结果,用来观察 RL 与 SFT 在不同 backbone 和不同难度上的影响。论文文字总结为:对 non-reasoning models,RL 显著提升 Pass@3 与 Cons@3,且 RL 后 Pass@1 已接近 SFT baseline 的 Pass@3,说明 RL 能让模型更高效地采到正确轨迹;对 LRMs,Pass@1/Pass@3/Cons@3 的增益更边际,但 consistency 有可见提升,作者推测原因是 LRM 轨迹过长导致 sparse reward 信号较弱。

Figure 4 解读:该图对应数据质量和数据来源的 ablation。论文指出,高质量轨迹对 agent SFT 很关键;CrawlQA 与 E2HQA 经过 trajectory rejection sampling 和过滤后,Final 数据在低数据量区间优于只用 open-source、crawl-only 或 e2h-only 的版本。直观原因是,long-CoT 容易在没有真实 observation 支撑时模拟网页内容,从而产生 hallucination;过滤漏斗虽然减少数量,但能提高监督信号密度。

作者报告最佳模型在 GAIA 上达到 Pass@3=64.1%,在 WebWalkerQA 上达到 Pass@3=62.0%。另外,冷启动 SFT 是必要的:如果只用单一 RL 设置训练 QwQ,GAIA Pass@3 只有 5%,说明多步多工具任务首先需要 instruction-following 与工具格式基础。随着 RL training steps 增加,Pass@3 与 Cons@3 持续改善,说明 outcome reward 对多轮决策确实提供了有效优化信号。

5.4 RL、行为演化与环境稳定性

Figure 5a–5c 解读:左图展示 DAPO training steps 增加时 GAIA 表现上升,支持“RL 不是只修格式,而是在强化多步决策”;中图展示 SFT/RL 后 action count 与 reasoning length 的变化,说明模型不只是更愿意给答案,而是在学会产生更长的推理与更复杂的行动序列;右图比较 temperature 对 Pass@1/Pass@3 的影响,作者认为 decoding variability 不能解释主要波动,真实网页环境本身的非平稳性才是 agent 部署难点。

CoT transfer 表给出一个重要负结果:reasoning model 产生的 long-CoT 不容易无损迁移到 instruction model。Qwen2.5-7B short-CoT 为 Pass@3=33.98、Cons@3=22.33、invalid=0.65%,long-CoT 为 35.92、21.00、invalid=21.36%;Qwen2.5-32B short-CoT 为 42.72、24.33、4.20%,long-CoT 为 45.63、30.00、13.59%;QwQ-32B short-CoT 为 44.66、28.33、0.97%,long-CoT 为 58.25、39.66、13.27%。结论是:对 QwQ 这种 reasoning model,long-CoT 明显有益;对非 reasoning instruction model,long-CoT 虽可提高部分 Pass@3,但 invalid rate 明显上升,常表现为重复或超过上下文。

论文还做了 memorization stress test:在 69 条 GAIA development 中已正确采样的轨迹上 fine-tune Qwen-7B 10 epochs,再用 greedy decoding 评测同一集合,也只有 37.4%。这说明 agentic task 的难点不是简单记住轨迹;真实环境和多步工具交互会让行为稳定性远低于静态 supervised QA。

5.5 局限与实践启示

第一,WebDancer 仍依赖大量合成数据、GPT-4o/QwQ-Plus 采样、Qwen-72B judge 和真实网页工具,因此成本与可复现性不是小问题。第二,answer reward 需要 LLM-as-Judge,而 judge 自身可能有偏差;对于开放答案和网页证据变化,二值 reward 会比较粗糙。第三,LRM 的长轨迹导致 sparse reward,论文也观察到 RL 对 LRM 的边际提升小于 non-reasoning models。第四,官方 released code 截至 main@f72f75d8 没有完整训练脚本,外部复现者能直接验证的是 demo/inference 与样例数据格式,而不是完整 DAPO 训练。

实践上,本文最有用的启示是分阶段训练:先解决 QA 深度与轨迹质量,再 SFT 冷启动,最后 RL 优化最终成功率。对于要构建企业或科研 deep-search agent 的工程团队,不能只做 prompt+tool;需要设计可验证 QA、明确工具 observation 是否参与 loss、记录每次 tool call 的有效性,并将动态网页环境下的失败视为训练信号而不是单纯噪声。