OpenSeeker-v2: Pushing the Limits of Search Agents with Informative and High-Difficulty Trajectories

Paper: arXiv:2605.04036 Code: PolarSeeker/OpenSeeker Code reference: main @ 954a286a (2026-05-22)

1. Motivation (研究动机)

当前 frontier search agents 的主要瓶颈不是单纯“有没有浏览器工具”,而是强搜索能力的训练路线被工业级闭源管线垄断:论文把典型 recipe 概括为大规模 pre-training / continual pre-training (CPT) → SFT → RL,多阶段管线依赖大算力、私有数据与复杂训练基础设施,导致学术团队很难复现或迭代同等规模的 Deep Research agent。

OpenSeeker-v2 想回答一个更尖锐的问题:如果只使用标准 SFT,能不能通过更难、更信息密集的 ReAct trajectories,把约 30B 规模 search agent 推到接近甚至超过重型 CPT+SFT+RL 管线的水平?这与 OpenSeeker: Democratizing Frontier Search Agents by Fully Open-Sourcing Training Data 的延续关系很明确:v1 已经证明 open-source SFT baseline 可行,v2 进一步把注意力从“训练阶段复杂度”转回“trajectory 数据质量”。

这个问题值得研究的原因有三点。第一,如果 SFT-only 能成立,search-agent 研究的门槛会从昂贵 RL/CPT pipeline 转向可检查、可扩展的数据合成策略。第二,deep search 的核心能力是长程信息搜集、多跳证据聚合、工具调用规划;这些能力很可能更依赖训练轨迹中是否真的包含长 horizon 搜索,而不只是优化算法是否复杂。第三,OpenSeeker-v2 的结果为 academic/open-source 社区提供了一个强 baseline:用 10.6k 条高难轨迹训练的 30B ReAct agent,在四个 benchmark 上达到 46.0 / 58.1 / 34.6 / 78.0。

2. Idea (核心思想)

核心 insight 是:search agent 的能力上限可以被训练 trajectories 的信息密度和难度显著抬高;当轨迹本身迫使模型做多跳探索、使用更丰富工具、走更长工具调用链时,标准 SFT 也能学习到强长程搜索策略。论文并不是提出一个复杂 RL objective,而是把“训练样本是否足够难”当作主要杠杆。

OpenSeeker-v2 的关键创新很简单:在数据合成阶段扩大 source graph 的拓扑扩展半径,让问题天然需要跨多个节点聚合证据;扩大工具集合,让 trajectory 展示更多功能模式;再用 strict low-step filtering 删除太容易的轨迹,保证训练集有最低难度地板。最终只保留 10.6k 条样本,但这些样本平均工具调用步数达到 64.67,高于 OpenSeeker-v1 的 46.97 和 RedSearcher 的 36.01。

与 Tongyi DeepResearch、RedSearcher、WebSailor-V2-RL 这类重型或 RL-enhanced 路线相比,OpenSeeker-v2 的根本差别是把训练 recipe 从“多阶段优化”简化为“SFT on harder trajectories”。这并不说明 RL 对 search agent 没价值,而是说明在约 30B ReAct-based agent 这个 setting 下,数据合成质量本身已经能带来非常大的增益。

3. Method (方法)

3.1 Overall framework:SFT-only search agent from high-difficulty trajectories

论文没有给出传统意义上的 pipeline architecture diagram;Figure 1 是一个 performance overview,用来说明 OpenSeeker-v2 在同等模型规模和 ReAct paradigm 内的相对位置。方法本身可以拆成四步:从 source graph 合成更复杂 query;让 agent 在更大工具集合下产生 ReAct trajectory;按工具调用步数过滤掉简单样本;最后用标准 SFT 训练 Qwen3-30B-A3B-Thinking-2507 初始化的 search agent。

Figure 1 解读:这张图不是训练架构图,而是把 OpenSeeker-v2 放在四个 deep-search benchmark 的 leaderboard 语境中。它强调同一件事:OpenSeeker-v2 使用 SFT-only,却在 BrowseComp、BrowseComp-ZH、HLE 和 xbench 上超过多个约 30B 的 ReAct-based search agents,甚至超过使用 CPT+SFT+RL 的 Tongyi DeepResearch。读图时应把它理解为“数据质量路线”的结果证据,而不是一个额外模型模块。

直觉上,SFT 不是只能学习短 answer pattern;如果 demonstration trajectories 本身包含足够长的 search loop,模型会被迫模仿“先提出检索假设 → 调用工具 → 从 observation 中抽取证据 → 修正查询/继续访问 → 最后回答”的行为分布。OpenSeeker-v2 的设计就是把简单 lookup 样本从训练集中剔除,让 SFT gradient 主要来自长 horizon 的行为轨迹。

3.2 Scaling graph size:用更大的 source subgraph 生成多跳问题

论文把源信息表示为图: 对每个 seed node ,原始 pipeline 会围绕 seed 构造一个局部子图。OpenSeeker-v2 把 expansion budget 从 提高到 ,其中 ,得到更大的 evidence subgraph: 随后 query generator 在扩展后的证据上下文上采样问题: 这个组件的输入是 source graph 与 seed node,输出是更大、更丰富的 evidence subgraph 以及由它条件生成的问题 。作者需要它,是因为一个小局部图容易产生单点事实查询;扩大拓扑上下文会增加可行 reasoning paths 的数量和多样性,使问题更可能需要多跳证据聚合。如果移除这一步,后续 trajectory 即使很长也可能只是重复浅层搜索,而不是由任务结构强制出的长程探索。

def synthesize_query_from_expanded_graph(source_graph, seed_node, K, query_generator):
    """Paper-level pseudocode; data-synthesis code is not released in PolarSeeker/OpenSeeker."""
    evidence_subgraph = expand_graph(source_graph, seed_node, budget=K)
    query = query_generator.sample(condition=evidence_subgraph)
    return query, evidence_subgraph

3.3 Expanding the tool set:让 ReAct trajectory 展示更多功能模式

给定合成问题 ,OpenSeeker-v2 让 agent 在扩展工具集合 下生成多步 ReAct-style trajectory: 其中 是第 次工具调用, 是工具返回 observation, 是 action 前的 reasoning trace, 是最终回答前的 reasoning, 是答案。这个组件的输入是 query 与工具集合 ,输出是带 reasoning / action / observation 交替结构的 trajectory

released code 中,v2 推理侧的工具集合由 tools_visit + tools_e2b 组成:searchvisitcreate_sandboxrun_commandrun_python_codeupload_file_from_local_to_sandboxdownload_file_from_internet_to_sandbox。这支持普通网页搜索/访问,也支持在 E2B sandbox 内执行轻量 shell/Python 或文件搬运。它对应论文里的“broader functionality”,但注意:repo 只给出推理/评测工具接口,没有公开生成训练 trajectory 的完整数据合成代码。

def execute_tool_call(tool_name: str, tool_args: dict, filter_huggingface: bool = True) -> str:
    """Mirrors src/llm_tool_openseeker_v2.py::_execute_tool at main@954a286a."""
    if tool_name == "search":
        return Search({"filter_huggingface": filter_huggingface}).call(tool_args)
    if tool_name in ("visit", "visit_summary"):
        return Visit().call(tool_args)
 
    validate_e2b_tool_args(tool_name, tool_args)
    if tool_name == "create_sandbox":
        return create_sandbox(timeout=int(tool_args.get("timeout", 600)))
    if tool_name == "run_command":
        return run_command(tool_args["command"], tool_args["sandbox_id"])
    if tool_name == "run_python_code":
        return run_python_code(tool_args["code_block"], tool_args["sandbox_id"])
    if tool_name == "upload_file_from_local_to_sandbox":
        return upload_file_from_local_to_sandbox(
            tool_args["sandbox_id"], tool_args["local_file_path"], tool_args.get("sandbox_file_path", "/home/user")
        )
    if tool_name == "download_file_from_internet_to_sandbox":
        return download_file_from_internet_to_sandbox(
            tool_args["sandbox_id"], tool_args["url"], tool_args.get("sandbox_file_path", "/home/user")
        )
    return "Unknown tool or call tool with incorrect format."

3.4 Strict low-step filtering:删除“太容易”的 trajectory

为了避免训练集被浅层 lookup 任务污染,论文定义了低步数过滤: 这里 是 trajectory 的工具调用步数, 是预设最小工具调用阈值。输入是 raw synthesized dataset ,输出是 filtered dataset 。它优化的不是一个 differentiable loss,而是训练数据分布:过滤后 SFT 看到的 demonstration 更偏向长程、多工具、多证据链。如果移除它,模型会从大量短轨迹中学习到“快速直接答复”或“浅检索即停止”的策略,削弱长 horizon search 习惯。

def strict_low_step_filter(raw_dataset, t_min: int):
    """Paper-level pseudocode; released repo does not expose the trajectory-filtering script."""
    kept = []
    for query, trajectory in raw_dataset:
        tool_steps = count_tool_calls(trajectory)
        if tool_steps >= t_min:
            kept.append((query, trajectory))
    return kept

3.5 SFT objective:论文只说明 standard SFT,没有给完整训练配置

论文正文只写 OpenSeeker-v2 “trains the search agent with a standard SFT objective over the filtered dataset”,没有展开 token-level loss、learning rate、batch size、训练步数、训练硬件或 optimizer。为了理解,可把它视作在 filtered trajectories 上做标准 next-token negative log-likelihood: 但这条公式是 standard SFT 的解释性写法,不是论文显式给出的新公式。可复现性上要注意:released repo 当前公开的是模型运行和评测 harness,不包含 SFT training script、数据合成脚本或超参配置。因此所有训练细节中只有论文明确写出的数值可作为事实:10.6k training samples、Qwen3-30B-A3B-Thinking-2507 初始化、30B total / 3B activated、256k context、最多 200 次 tool calls、SFT-only、no RL、no additional hyperparameter tuning。

3.6 Released-code inference loop:v2 agent 如何跑 ReAct 工具调用

released code 的核心是 src/llm_tool_openseeker_v2.py::call_llm_with_toolsolve_query_with_tools。它用 OpenAI-compatible /v1/completions endpoint 调用本地/远端 SGLang-served model,把 system/user/tool messages 渲染进 chat template;每轮从模型输出里解析 <tool_calls_begin> ... <tool_call>...</tool_call> 形式的 JSON 工具调用;执行工具并把 observation 追加回 messages;直到模型产生 <answer> 或达到 tool_count_max

def solve_query_with_tools(query: str, model_endpoint: str, tool_count_max: int = 200, max_tokens: int = 32768):
    """Code-grounded pseudocode from src/llm_tool_openseeker_v2.py and eval/generate_answer_v2.py."""
    messages = [
        {"role": "system", "content": "You are a tool-augmented QA agent..."},
        {"role": "user", "content": query},
    ]
    tool_count = 0
    trace = []
 
    while True:
        prompt = render_chat_template(messages, tools=tools_visit + tools_e2b)
        completion = stream_completion(model_endpoint, prompt, max_tokens=max_tokens)
        reasoning, content = split_think_and_content(completion)
        cleaned_text, tool_calls, parse_error = parse_tool_calls_from_text(content)
        messages.append({"role": "assistant", "content": completion})
        trace.append({"type": "model_message", "reasoning": reasoning, "tool_calls": tool_calls})
 
        if has_answer_tag(content):
            break
        if not tool_calls:
            tool_count += 1
            messages.append({"role": "tool", "name": "unknown", "content": parse_error})
            if tool_count >= tool_count_max:
                break
            continue
 
        for call in tool_calls:
            output = execute_tool_call(call["function"]["name"], call["function"]["arguments"])
            messages.append({"role": "tool", "name": call["function"]["name"], "content": output})
            trace.append({"type": "tool_response", "content": output})
            tool_count += 1
            if tool_count >= tool_count_max:
                disable_further_tools_and_force_final_answer()
                break
 
    return {"answer": extract_last_answer(messages), "tool_calls": tool_count, "trace": trace}

3.7 Evaluation harness:批量生成 answer 并记录 full trajectory

eval/generate_answer_v2.py 负责从 JSONL dataset 读取 query,默认给 query 追加 \boxed{} 最终答案格式要求;并发调用 solve_query_with_tools;把 final_responsetool_callselapsed_secondscontext_charscontext_est_tokensfinish_reasonfull_trajtrace 写入 result_tool{tool_count_max}.jsonl。默认 tool_count_max=200max_worker=60filter_huggingface=True,这与论文“最多 200 tool calls、mask Hugging Face links 避免泄漏”的评测描述一致。

async def generate_answers_v2(dataset_path, out_dir, max_worker: int = 60, tool_count_max: int = 200):
    """Code-grounded pseudocode from eval/generate_answer_v2.py at main@954a286a."""
    examples = read_jsonl(dataset_path)
    examples = append_box_instruction(examples)
    result_path = out_dir / f"result_tool{tool_count_max}.jsonl"
    semaphore = asyncio.Semaphore(max_worker)
 
    async def process_one(example):
        async with semaphore:
            result = await asyncio.to_thread(
                solve_query_with_tools,
                example["query"],
                max_tokens=32768,
                tool_count_max=tool_count_max,
                filter_huggingface=True,
                return_full_traj=True,
            )
            row = dict(example)
            row.update({
                "final_response": result["answer"],
                "tool_calls": result["tool_calls"],
                "full_traj": result["full_traj"],
                "trace": result["trace"],
            })
            append_jsonl(result_path, row)
 
    await asyncio.gather(*(process_one(x) for x in examples))

3.8 论文公式与 released code 实现差异

论文公式与 released code 实现差异:论文的核心方法是 training-data synthesis 与 SFT training,而 PolarSeeker/OpenSeekermain@954a286a 主要公开了模型 serving、tool-augmented inference、evaluation scripts 和工具接口;没有公开 Expand(\mathcal{G}, v_{\mathrm{seed}}, K)、low-step filtering、SFT training loop、学习率/batch/optimizer 等训练配置。另一个小差异是 run_openseeker.sh 默认 MODEL_PATH="path/to/OpenSeeker-v1-30B-SFT",README 的 v2 使用说明要求用户手动改成 OpenSeeker-v2-30B-SFT

Code reference: main @ 954a286a (2026-05-22) — pseudocode and mapping based on this commit

Paper ConceptSource FileKey Class/Function
OpenSeeker-v2 tool-augmented ReAct inferencesrc/llm_tool_openseeker_v2.pycall_llm_with_tool, solve_query_with_tools, _parse_tool_calls_from_text
Expanded tool set for v2 inferencesrc/llm_tool_openseeker_v2.pytools_visit, tools_e2b, tools_all = tools_visit + tools_e2b, _execute_tool
Web search toolsrc/tools/search.pySearch.call, Serper/Tavily dispatch, Hugging Face filtering
Web visit / summarization toolsrc/tools/visit.pyVisit.call, readpage_jina, summary API call
Sandbox/code toolssrc/tools/e2b_sandbox_tools.pycreate_sandbox, run_command, run_python_code, upload/download helpers
v2 batch answer generation and metricseval/generate_answer_v2.pymain, process_one, compute_metrics, result JSONL/log paths
Model serving configrun_openseeker.shSGLang launch, CONTEXT_LENGTH=256000, default GPU_IDS="0,1,2,3", MAX_RUNNING_REQUESTS=2000
Data synthesis / graph expansion / low-step filtering / SFT trainingNot released in repoPaper-only formulas and descriptions; no source implementation found in this repo

4. Experimental Setup (实验设置)

Training data and model

  • Training data: OpenSeeker-v2 使用 10.6k 条 high-difficulty synthesized trajectories;OpenSeeker-v1 对照为 11.7k 条。论文没有披露原始 source graph 的规模、 / 的具体取值、、过滤前样本数或各工具类型比例。
  • Base model: Qwen3-30B-A3B-Thinking-2507,30B total parameters,inference 时 3B activated parameters。
  • Context / tool budget: 256k context window;每条 trajectory 最多允许 200 次 tool calls。released repo 的 run_openseeker.sh 也设置 CONTEXT_LENGTH=256000eval/generate_answer_v2.py 默认 tool_count_max=200
  • Training recipe: SFT-only;论文明确说 without RL or additional hyperparameter tuning,但没有给 learning rate、batch size、训练步数、训练硬件或 optimizer。released repo 没有 training config,因此这些数字不能从代码补全。

Evaluation datasets / benchmarks

论文评测四个 deep-search benchmark:BrowseComp、BrowseComp-ZH、Humanity’s Last Exam (HLE)、xbench-DeepSearch。正文第 2.2 节写成 “five challenging agentic benchmarks”,但随后只列出四个名字;表格和摘要也都是四个指标,因此这里按四个 benchmark 记录。论文没有给出各 benchmark 的 evaluation sample count,只说明它们覆盖 diverse deep research tasks,并在调用 web search tools 时 mask Hugging Face 相关链接以避免泄漏。

Baselines

主要对比对象是同规模 ReAct-based search agents:WebSailor-V2-30B-SFT/RL、WebLeaper-30B-SFT/RL、Tongyi DeepResearch、RedSearcher-30B、OpenSeeker-v1-30B-SFT。论文也列出闭源 proprietary models(Claude-4-Opus、Claude-4.5-Sonnet、Gemini-3-pro、OpenAI-o3、OpenAI Deep Research、GPT-5-High)和更大 open-source models(DeepSeek-V3.1/V3.2-671B、GLM-4.6/4.7-357B、Minimax-M2-230B)作为 broader reference。

Metrics

表格中的数值是各 benchmark 的 percent score / accuracy-style score,越高越好。# Samples 指训练数据样本数;Training 指训练阶段组合(CPT、SFT、RL);Academic 表示是否由纯学术团队完成;BC-ZH 是 BrowseComp-ZH。

5. Experimental Results (实验结果)

5.1 Main benchmark numbers

Model# SamplesTrainingAcademicBrowseCompBC-ZHHLExbench
Claude-4-Opus??×18.837.4--
Claude-4.5-Sonnet??×24.142.432.0-
Gemini-3-pro??×37.866.845.8-
OpenAI-o3??×49.168.720.265.0
OpenAI Deep Research??×51.542.926.6-
GPT-5-High??×54.963.041.7-
DeepSeek-V3.1-671B??×30.049.229.871.2
DeepSeek-V3.2-671B??×51.465.040.8-
GLM-4.6-357B??×45.149.530.4-
GLM-4.7-357B??×52.066.642.8-
Minimax-M2-230B??×44.048.5--
WebSailor-V2-30B-SFT?SFT×24.428.323.961.7
WebSailor-V2-30B-RL?SFT + RL×35.344.130.673.7
WebLeaper-30B-SFT15kSFT×27.7--66.0
WebLeaper-30B-RL?RL×38.8--72.0
Tongyi DeepResearch?CPT + SFT + RL×43.446.732.975.0
RedSearcher-30B?CPT + SFT + RL×42.149.834.3-
OpenSeeker-v1-30B-SFT11.7kSFT29.548.4-74.0
OpenSeeker-v2-30B-SFT10.6kSFT46.058.134.678.0

最重要的结论是:在约 30B ReAct-based search agents 里,OpenSeeker-v2 用 SFT-only 达到最强整体表现。相对 Tongyi DeepResearch,OpenSeeker-v2 在 BrowseComp 上高 2.6 分(46.0 vs 43.4),在 BrowseComp-ZH 上高 11.4 分(58.1 vs 46.7),在 HLE 上高 1.7 分(34.6 vs 32.9),在 xbench 上高 3.0 分(78.0 vs 75.0)。相对 RedSearcher-30B,OpenSeeker-v2 在 BrowseComp 上高 3.9 分(46.0 vs 42.1),在 BrowseComp-ZH 上高 8.3 分(58.1 vs 49.8),在 HLE 上高 0.3 分(34.6 vs 34.3)。

5.2 Scaling from OpenSeeker-v1 to v2

OpenSeeker-v2 相比 OpenSeeker-v1 的增益很大,而且训练样本数还略少:BrowseComp 从 29.5 提升到 46.0(+16.5),BrowseComp-ZH 从 48.4 提升到 58.1(+9.7),xbench 从 74.0 提升到 78.0(+4.0)。这支持作者的判断:OpenSeeker 框架在 SFT setting 下尚未饱和,继续提升 synthesized QA tasks 的难度与信息丰富度仍然可能带来可观收益。

Figure 2 解读:这张图比较 RedSearcher、OpenSeeker-v1 和 OpenSeeker-v2 训练数据中的工具调用步数分布。OpenSeeker-v2 的平均 tool calls 是 64.67,高于 OpenSeeker-v1 的 46.97 和 RedSearcher 的 36.01;因此 v2 不只是“样本更少但更干净”,而是把训练分布推向更长、更难的搜索过程。这个结果与低步数过滤的设计相互印证:过滤短轨迹后,模型在 SFT 中看到的 demonstration 更集中于长程信息寻找。

5.3 Ablation / component evidence

论文没有给出标准 ablation table,例如单独移除 graph scaling、tool expansion 或 low-step filtering 后的分数变化。因此组件贡献主要由两个间接证据支持:第一,v2 对 v1 在同一模型规模和 SFT-only recipe 下显著提升;第二,Figure 2 显示 v2 数据的平均轨迹长度显著更高。这个 evidence 足以支持“更难 trajectory 有帮助”的方向性结论,但不能精确量化三个 modifications 各自贡献多少。

5.4 Limitations and caveats

作者没有单列 limitations section。读者需要注意的可复现性 caveats 是:论文没有披露 、数据合成 prompts、过滤前后样本量、SFT 超参、硬件和训练脚本;released code 也没有这些训练/数据构造部分,只提供模型 serving、工具调用和评测 harness。因此,这篇报告更像一个强 empirical recipe / technical report,而不是完整可复现实验包。

总体结论是:OpenSeeker-v2 用 10.6k 条高难信息密集 trajectories 证明,约 30B ReAct search agent 不一定必须依赖 CPT+SFT+RL 才能达到强性能;在这个任务族里,trajectory 难度、信息密度和长程工具调用分布本身就是非常强的 scaling lever。