Wisp Science高级用法:用 map_items 批量处理文献

查看原文

下载了 100 篇 PDF,要逐篇判断“是不是随机对照试验、样本量多少、是否符合纳入标准”。在一段对话里让 Agent 一篇篇读,读到二十来篇上下文就满了;拆成 100 段对话,又没法汇总。

这类“同一个任务重复 N 次”的工作,交给 map_items:一条指令、N 个条目,每个条目在自己独立的短上下文里处理,结果写成一张表。主对话只收到计数和简短清单,不会被全文淹没。

这篇教程用“筛选 100 篇文献”走一遍完整流程:怎么让 Agent 调用它、审批时看什么、结果在哪、怎么续跑和二次筛选。

本文示例中的文件名、运行编号、数字和回答均为示意,用于说明交互方式,不代表已经执行的筛选结果。

先弄清一件事:map_items 是 Agent 的工具,不是你要敲的命令。

你不需要手写 JSON。在项目对话里用自然语言描述批量任务,Agent 会自己组织参数并调用;你要做的是把任务说清楚,并在审批时确认一次。下文出现的 JSON 是 Agent 实际发出的调用,放在这里是为了让你看懂审批提示和工具卡片,也方便在 Agent 选错工具时直接点名“用 map_items”。

它和另外几种“分出去做”的方式分工不同:

场景 用什么
同一个任务重复很多次:筛 100 篇文献、从 50 个 PDF 抽同一张表 map_items
少数几个互不相同的任务,最多 8 个 delegate_tasks,或 Agent Workflow
一次开放式的调查 explore

准备:把条目放进项目,按需配置 TypeSafe 密钥。

  • 把文件放在项目目录下,例如 papers/。PDF、Office 文档、EPUB 和纯文本都在本机转成文字,这一步不消耗 token。
  • 扫描版 PDF 没有可提取的文字,暂不支持 OCR;这样的文件只会让它自己那一行失败。
  • 一次最多 1000 个条目。超过会直接报错,不会悄悄截断。
  • 只做信息抽取不需要额外配置。想让每一行自动得到“通过 / 剔除 / 不确定”的判定,需要在“设置 → 凭据”里填写 TypeSafe(Jev)的 API key;命令行则设置环境变量 TYPESAFE_API_KEY。没有密钥时,Agent 的工具里不会出现判定这一步。

第一步:把“对一个条目做什么”说清楚。

在项目对话里发送:

用 map_items 处理 papers/ 下所有 PDF。每篇提取:研究设计(rct、cohort、case-control 或 other)、样本量、主要结局指标。先只抽取,不做判定。

好的批量指令有三个特点:针对单个条目;自包含,处理条目的模型看不到你们之前聊过什么;字段固定。Agent 会把它整理成这样的调用:

{
  "items": { "glob": "papers/*.pdf" },
  "worker": {
    "instruction": "Read this paper. Report its study design, sample size and primary outcome.",
    "output_schema": {
      "type": "object",
      "required": ["design", "sample_size", "primary_outcome"],
      "properties": {
        "design": { "enum": ["rct", "cohort", "case-control", "other"] },
        "sample_size": { "type": "integer" },
        "primary_outcome": { "type": "string" }
      }
    }
  }
}

worker 是每个条目上的一次抽取,output_schema 规定一行结果长什么样。某一篇的回答不符合这个结构,那一行记为失败并保留原因,不会混进后面的汇总。

第二步:看懂审批提示,确认一次。

开始消耗 token 之前,Wisp 只问一次(项目的工具审批模式设为“完全放行”时不会询问)。提示大致如下:

map_items will process 100 item(s) — 100 item(s) from glob 'papers/*.pdf'.
Worker: model your-model-id — up to ~3150k input / 100k output tokens; stops and pauses at 3250k total.
Concurrency 4; pauses after 20 min per call (resume continues). Results: .wisp/map-runs/20261006-081530-a1b2c3

确认四件事:条目数对不对、用的是哪个模型、token 上限、结果写到哪里。上限按“每篇都读满 60000 个字符”估算,是保守的上界,不是预计花费;到达上限时运行会暂停,而不是截断。

条目清单在这一刻冻结。运行期间往 papers/ 里新增文件,不会被加进这次运行。

第三步:读结果。

运行结束后,Agent 收到一份简短报告,并据此向你汇报:

[map_items run 20261006-081530-a1b2c3: complete — 97 ok, 3 failed, 0 not yet processed, of 100]
failed (3): papers/scan_017.pdf (no extractable text (a scanned PDF? OCR is not supported)), ...
tokens (worker/reduce): 812k in / 21k out; 412s elapsed
rows: .wisp/map-runs/20261006-081530-a1b2c3/rows.jsonl
table: .wisp/map-runs/20261006-081530-a1b2c3/results.csv
continue: map_items {"resume": "20261006-081530-a1b2c3"} (retries the failed items)

完整结果保存在项目的 .wisp/map-runs/<运行编号>/ 下:

文件 内容
manifest.json 冻结的条目清单和参数、运行状态
rows.jsonl 逐条追加的记录;同一条目以最后一行为准
results.csv 每个条目一行:条目、状态、判定、抽取出的字段、各问题的概率、错误原因
reduce.md 汇总结果,只有要求了汇总才会生成

results.csv 可以直接在项目文件预览中打开。

第四步:需要筛选时,把判定标准一起交代。

抽取回答“这篇文献里写了什么”,判定回答“它符不符合要求”。配置了 TypeSafe 密钥后,可以在任务里把标准一并说出来:

用 map_items 筛选 papers/ 下所有 PDF。纳入标准:随机对照试验,且样本量不少于 100。每篇先提取研究设计和样本量,再判定是否纳入。

调用里会多出 decide:

"decide": {
  "questions": {
    "is_rct": { "type": "noul", "instructions": "Is `design` a randomized controlled trial?" },
    "large_enough": { "type": "noul", "instructions": "Is `sample_size` at least 100?" }
  }
}

noul 是“是 / 否”问题,问题里用反引号引用上一步抽取出的字段。每个问题都要写成回答“是”代表符合要求。Jev 对每个问题返回一个概率,判定规则是固定的:

判定 条件
pass 通过 每个是否问题的概率都不低于 pass_at,默认 0.8
reject 剔除 任何一个是否问题的概率不高于 reject_at,默认 0.2
uncertain 不确定 其余情况,包括概率缺失

判定只是标签,不会删除或移动任何文件。还有两点需要知道:

  • 数据会离开本机。 使用判定时,每个条目抽取出的那一行会发送给 TypeSafe;如果没有抽取这一步,发送的是条目开头的一段文字。审批提示会写明这一点。
  • 推荐“抽取 + 判定”一起用。 抽取读全文并整理出事实,判定只看这一行紧凑的结果。只用判定适合对标题和摘要做一次便宜的初筛。

第五步:处理“不确定”和没跑完的条目。

这几种情况都用一句话交代,Agent 会带上对应的参数:

你想做什么 可以这样说 调用里的关键参数
运行被停止、到了时间或 token 上限、有条目失败 “继续刚才那次 map_items 运行” {"resume": "<运行编号>"}
觉得阈值太严或太松 “把通过阈值改成 0.7,重新贴标签” resume 加上 decide.pass_at
只复核不确定的那些 “对上次不确定的文献再跑一遍,这次允许查阅项目里的补充材料” "items": {"run": "<运行编号>", "verdict": ["uncertain"]}

续跑会跳过已经完成的条目,并重试失败的条目。如果某一条只是判定这一步失败,已经付费得到的抽取结果会被复用,只重新询问 Jev。改阈值后重新贴标签不需要调用模型,因为每一行都保存了原始概率。

续跑只能调整并发、上限和阈值。要更换条目、抽取指令或判定问题,需要开始一次新的运行;用 items.run 可以把上一次的结果行作为新的输入。

按需加上汇总,或换一种条目来源。

想在表格之外得到一段总结,直接说“最后汇总纳入的研究各用了哪些结局指标”。调用里会多出 reduce,它在全部条目完成后只读取结构化的结果行,不读全文;有判定时,默认只汇总通过和不确定的行。

条目不一定是文件夹里的文件:

来源 写法 说明
匹配的文件 {"glob": "papers/*.pdf"} 相对项目根目录
指定的文件 {"paths": ["a.pdf", "b.docx"]} 逐个列出
JSONL 文件 {"jsonl": "records.jsonl", "id_field": "pmid", "where": {"year": 2024}} 每行一个条目;where 按字段相等过滤
上一次运行 {"run": "<运行编号>", "verdict": ["uncertain"]} 二次处理

四种来源每次只能选一种。

在命令行里使用。

交互模式下的用法和桌面一致,审批会以 [y/n] 的形式出现在终端里:

export TYPESAFE_API_KEY="your-typesafe-key"   # 只在需要判定时设置
wisp-science

wisp-science run --output jsonl 无法应答审批,请求会被自动拒绝,因此批量任务不适合在这个输出模式下启动。模型环境变量的配置见 Wisp 命令行。

运行不符合预期时,先看这张表。

现象 先检查什么
Agent 一篇篇手动读文件,没有调用工具 在消息里直接点名“用 map_items”;确认任务确实是同一件事重复多次
调用里没有判定这一步 是否配置了 TypeSafe 密钥;命令行是否设置了 TYPESAFE_API_KEY
提示条目数超过上限 一次最多 1000 个,缩小匹配范围或分成几次运行
某些 PDF 失败,原因是没有可提取的文字 多为扫描版;先自行 OCR 成文本或带文字层的 PDF
很多行失败,原因是回答不符合结构 指令是否写清每个字段的含义和取值;结构是否过严
运行显示暂停 到了时间或 token 上限,或被手动停止;说“继续”即可续跑
运行很快以失败结束 连续 5 次服务错误会熔断,检查密钥、接口地址和网络后再续跑
长论文的中间部分没被读到 单个条目默认只送 60000 个字符,保留开头和结尾;可以要求调高 max_chars

第一次使用,建议先拿 5 到 10 篇试跑:确认字段抽得对、判定问题的方向没写反,再对全部文献运行。

全部参数与实现细节见 map_items 参考文档;与其他委派方式的关系见 Agent delegation。本文依据撰写时的项目实现整理,不同版本的提示文字可能略有差异。

You have downloaded 100 PDFs and need to decide, paper by paper, whether each is a randomized controlled trial, how large the sample is, and whether it meets your inclusion criteria. Ask the agent to read them one by one in a single conversation and the context fills up after twenty or so. Split the job into 100 conversations and there is nothing to summarize.

Work where the same task repeats N times belongs to map_items: one instruction, N items, each item handled in its own short-lived context, and the results written to a table. The main conversation receives only counts and short lists, never the full texts.

This tutorial walks through screening 100 papers: how to get the agent to call the tool, what to check at approval, where the results go, and how to resume a run or screen a second time.

File names, run ids, numbers, and replies in this article are illustrative. They show the interaction and are not the results of a screening that was run.

First, one thing to know: map_items is a tool the agent calls, not a command you type.

You do not write JSON by hand. Describe the batch task in plain language in a project conversation, and the agent builds the arguments and makes the call. Your part is to state the task clearly and confirm once at approval. The JSON below is what the agent sends. It is shown so that you can read the approval prompt and the tool card, and so that you can say "use map_items" when the agent picks a different tool.

It has a different job from the other ways of handing work off:

Situation Use
The same task repeated many times: screening 100 papers, extracting the same table from 50 PDFs map_items
A few different tasks, at most 8 delegate_tasks, or an Agent Workflow
One open-ended investigation explore

Prepare: put the items in the project, and add a TypeSafe key if you need one.

  • Put the files under the project directory, for example in papers/. PDFs, Office documents, EPUB, and plain text are converted to text on your machine, which spends no tokens.
  • A scanned PDF has no extractable text, and OCR is not supported yet. Such a file fails only its own row.
  • One run takes at most 1000 items. More than that is an error, never a silent cut.
  • Extraction alone needs no extra setup. To have each row labelled pass, reject, or uncertain, enter a TypeSafe (Jev) API key under Settings → Credentials, or set the TYPESAFE_API_KEY environment variable for the CLI. Without a key, the decision step is not offered to the agent at all.

Step 1: say what to do with one item.

Send this in a project conversation:

Use map_items on every PDF in papers/. For each paper, extract the study design (rct, cohort, case-control, or other), the sample size, and the primary outcome. Extract only, with no decision yet.

A good batch instruction is about one item, is self-contained because the model handling an item cannot see your earlier conversation, and has fixed fields. The agent turns it into a call like this:

{
  "items": { "glob": "papers/*.pdf" },
  "worker": {
    "instruction": "Read this paper. Report its study design, sample size and primary outcome.",
    "output_schema": {
      "type": "object",
      "required": ["design", "sample_size", "primary_outcome"],
      "properties": {
        "design": { "enum": ["rct", "cohort", "case-control", "other"] },
        "sample_size": { "type": "integer" },
        "primary_outcome": { "type": "string" }
      }
    }
  }
}

worker is one extraction per item, and output_schema defines what a result row looks like. If the reply for one paper does not match it, that row is recorded as failed with its reason and never reaches the summary step.

Step 2: read the approval prompt and confirm once.

Wisp asks once before any tokens are spent (it does not ask when the project's tool approval mode is "Full bypass"). The prompt looks roughly like this:

map_items will process 100 item(s) — 100 item(s) from glob 'papers/*.pdf'.
Worker: model your-model-id — up to ~3150k input / 100k output tokens; stops and pauses at 3250k total.
Concurrency 4; pauses after 20 min per call (resume continues). Results: .wisp/map-runs/20261006-081530-a1b2c3

Check four things: the item count, the model, the token ceiling, and where the results go. The ceiling assumes every paper fills all 60000 characters, so it is a conservative upper bound and not the expected spend. When the ceiling is reached the run pauses. It does not truncate.

The item list is frozen at this point. Files added to papers/ during the run are not picked up by it.

Step 3: read the results.

When the run ends, the agent receives a short report and tells you what it says:

[map_items run 20261006-081530-a1b2c3: complete — 97 ok, 3 failed, 0 not yet processed, of 100]
failed (3): papers/scan_017.pdf (no extractable text (a scanned PDF? OCR is not supported)), ...
tokens (worker/reduce): 812k in / 21k out; 412s elapsed
rows: .wisp/map-runs/20261006-081530-a1b2c3/rows.jsonl
table: .wisp/map-runs/20261006-081530-a1b2c3/results.csv
continue: map_items {"resume": "20261006-081530-a1b2c3"} (retries the failed items)

The full results are saved in the project under .wisp/map-runs/<run id>/:

File Contents
manifest.json The frozen item list and arguments, and the run status
rows.jsonl Records appended one at a time; the last line for an item wins
results.csv One row per item: item, status, verdict, extracted fields, the probability for each question, and the error
reduce.md The summary, written only when you asked for one

results.csv opens directly in the project file preview.

Step 4: when you are screening, give the criteria along with the task.

Extraction answers "what does this paper say". The decision answers "does it meet the requirement". With a TypeSafe key configured, state the criteria in the same request:

Use map_items to screen every PDF in papers/. Inclusion criteria: a randomized controlled trial with a sample size of at least 100. For each paper, extract the study design and sample size, then decide whether to include it.

The call gains a decide block:

"decide": {
  "questions": {
    "is_rct": { "type": "noul", "instructions": "Is `design` a randomized controlled trial?" },
    "large_enough": { "type": "noul", "instructions": "Is `sample_size` at least 100?" }
  }
}

noul is a yes/no question, and backticks in the question refer to fields extracted in the previous step. Phrase every question so that yes means the item meets the requirement. Jev returns one probability per question, and the rule that turns them into a verdict is fixed:

Verdict Condition
pass Every yes/no probability is at least pass_at, 0.8 by default
reject Any yes/no probability is at most reject_at, 0.2 by default
uncertain Everything else, including a missing probability

Verdicts are labels. No file is deleted or moved. Two more things to know:

  • Data leaves your machine. With a decision step, the extracted row for each item is sent to TypeSafe. Without an extraction step, the start of the item's text is sent instead. The approval prompt says so.
  • Extraction plus decision is the recommended setup. Extraction reads the full text and collects the facts, and the decision judges that one compact row. A decision step on its own suits a cheap first pass over titles and abstracts.

Step 5: handle uncertain items and items that did not finish.

Each of these takes one sentence, and the agent adds the matching argument:

What you want What to say Key argument in the call
The run was stopped, reached its time or token ceiling, or had failed items "Continue that map_items run" {"resume": "<run id>"}
The thresholds feel too strict or too loose "Change the pass threshold to 0.7 and relabel" resume plus decide.pass_at
Review only the uncertain ones "Run the uncertain papers again, and this time allow reading the supplementary files in the project" "items": {"run": "<run id>", "verdict": ["uncertain"]}

A resume skips finished items and retries failed ones. If only the decision step failed for an item, the extraction you already paid for is reused and only Jev is asked again. Relabelling after a threshold change needs no model call, because each row stores the raw probabilities.

A resume can change only concurrency, limits, and thresholds. To change the items, the extraction instruction, or the questions, start a new run. items.run feeds the rows of the earlier run in as the new input.

Add a summary, or use another item source, when you need to.

For a written summary on top of the table, ask for it directly: "then summarize which outcomes the included studies used." The call gains a reduce block. It runs after every item is done and reads only the structured rows, never the full texts. When there is a decision step, it summarizes pass and uncertain rows by default.

Items do not have to be files in a folder:

Source Form Notes
Matching files {"glob": "papers/*.pdf"} Relative to the project root
Named files {"paths": ["a.pdf", "b.docx"]} Listed one by one
A JSONL file {"jsonl": "records.jsonl", "id_field": "pmid", "where": {"year": 2024}} One item per line; where filters by field equality
An earlier run {"run": "<run id>", "verdict": ["uncertain"]} A second pass

A call takes exactly one of the four.

Using it from the CLI.

Interactive mode works the same way as the desktop, and the approval appears in the terminal as a [y/n] question:

export TYPESAFE_API_KEY="your-typesafe-key"   # only when you need the decision step
wisp-science

wisp-science run --output jsonl cannot answer an approval, so the request is refused automatically and a batch run should not be started in that output mode. For the model environment variables, see Wisp CLI.

When a run does not go as expected, start with this table.

Symptom Check first
The agent reads the files one by one and never calls the tool Name it in your message: "use map_items". Confirm the task really is one thing repeated many times
The call has no decision step Whether a TypeSafe key is configured; for the CLI, whether TYPESAFE_API_KEY is set
The item count is over the limit One run takes at most 1000. Narrow the pattern or split the work into several runs
Some PDFs fail with no extractable text Usually scanned copies. Run OCR yourself first, into text or a PDF with a text layer
Many rows fail because the reply does not match the structure Whether the instruction defines each field and its allowed values; whether the structure is too strict
The run reports paused It reached the time or token ceiling, or was stopped by hand. Say "continue" to resume
The run ends quickly as failed Five service errors in a row trip a circuit breaker. Check the key, endpoint, and network, then resume
The middle of a long paper was not read One item sends 60000 characters by default, keeping the start and the end. Ask for a higher max_chars

For a first attempt, try 5 to 10 papers: confirm that the fields come out right and that no decision question is phrased the wrong way round, then run it on everything.

All arguments and implementation details are in the map_items reference. For how it relates to the other ways of delegating, see Agent delegation. This article reflects the project implementation at the time of writing, and prompt wording may differ slightly between versions.

返回教程目录