两次LLM调用替代一次:信息提取成本降67%准确率升至100%
2026/9/6 3:08:40 网站建设 项目流程

单次 LLM 调用做信息提取,准确率不够高还烧钱;改成两次调用后,成本反而降了 67%,准确率从 72% 拉到 100%。这不是玄学,是拆分任务、缩小模型输出范围之后带来的直接收益。这篇就拆解这套“两次调用替代一次调用”的 LLM 提取优化方案,讲清楚为什么更便宜、为什么更准,并给出一套可以直接改用的部署和测试流程。

如果你在处理合同解析、发票信息抽取、简历结构化、知识库入库这类场景,并且已经在用 LLM 做字段提取,这篇文章值得收藏。

1. 核心能力速览

能力项说明
方案类型LLM 信息提取优化策略,两次调用协作完成提取
核心思路第一次调用做粗提取,第二次调用做校验修正
成本效果相比单次大模型全量提取,成本降低约 67%
准确率效果在材料对应测试中,从 72% 提升到 100%
适用任务结构化字段提取、实体抽取、文档解析、知识库入库
底层模型不限定,可接 API,也可接本地部署的 LLM
硬件要求视模型而定;本地小模型可 CPU 推理,大模型建议 GPU
是否支持 API支持,本质就是两次普通 LLM 接口调用
是否支持批量任务支持,可写成批处理脚本或任务队列
适合读者做 RAG、文档解析、数据清洗、自动化管线的开发者

这个方案的价值不在“模型”,而在“调用策略”。你不需要换更强的大模型,只需要把一次复杂提取任务拆成两次简单任务,让每次调用都专注于更小的范围,成本自然下降,准确率反而更容易控住。

2. 两次调用代替一次调用:为什么更便宜也更准

2.1 为什么“两次”反而比“一次”便宜

先说结论:两次调用的总成本低于一次调用,因为两次调用各自处理的 token 范围大幅缩小了。

单次调用的典型做法是这样的:把整篇文本塞给一个大模型,让它一次性输出所有目标字段。这个过程中,输入 token 可能占几百到几千,输出 token 因为要覆盖所有字段,也往往很长。更麻烦的是,模型为了“完整输出”,会把很多重话、补充说明、格式描述一起生成出来,这些都属于无效成本。

两次调用则把任务拆成两段。

第一次调用,用小模型或低成本的模型做“粗提取”。它只需要识别出字段所在的片段,输出一个较小范围的结构化结果,不需要输出长解释。这次调用输出 token 少,模型规模也可以小,成本很低。

第二次调用,把第一次的结果连同原文里对应的上下文片段交给一个校验模型,让它只做三件事:检查字段值是否合理、补缺漏字段、修正格式。这一次调用的输入是“原文局部片段 + 第一次提取结果”,输出只是修正后的差异部分,输出 token 同样远小于一次全量提取。

所以总 token 消耗比单次大模型全量提取少很多,模型单价也可能更低。从材料看,这套组合能把成本压到原来的三分之一左右,对应标题里“便宜 67%”。需要说明的是,这个比例和模型选择、文本长度、字段数量都有关,不能当固定结论用,但它下降的趋势是成立的。

2.2 为什么准确率反而更高

一次提取容易出错,是因为模型在长文本里同时要完成“理解全文 + 定位字段 + 生成结构化内容”三件事。任务复杂时,定位不准确、字段冲突、格式混乱都会发生,出错后又没有修复机制,最终准确率只能停在 72% 左右。

两次调用的设计天然解决这个问题:

  • 第一次调用的目标非常窄:只做定位和初提取,不追求一次写对全部字段。
  • 第二次调用的目标是修正:它能看到第一次的结果,能对着原文片段做针对性强校验。这就相当于给提取结果加了一层质检环节。
  • 第二次输出的是差异修正,范围小,模型即使能力不强,也容易把任务做对。

一百个字段里挑出一个错误字段,比从零生成一百个正确字段简单得多。准确率从 72% 到 100%,本质上是把“一次生成正确”变成了“生成一次 + 修正一次”,后者的成功率自然高于前者。

2.3 这套思路的适用范围

从材料看,这个方案主要针对的是“从非结构化文本中提取结构化字段”的任务。典型特征是:输出字段固定、字段值来自原文、允许做核对验证。

如果你的任务符合这三条,它的收益最明显。如果任务是创意写作、开放问答、总结等没有固定答案的场景,就不能直接套用。

3. 适用场景与使用边界

3.1 适合谁用

这套方案适合以下人群:

  • 正在做文档解析的开发者,需要从 PDF、Word、扫描件中提取合同字段、发票字段、简历字段。
  • 做知识库入库的工程师,需要把非结构化文本批量转换成结构化条目。
  • 使用 LLM 做数据清洗的数据工程师,需要从大量文本中抽实体、抽关系、抽属性。
  • 在 RAG 链路里需要做召回后命中断言抽取的开发者。

3.2 能解决什么问题

第一个问题是成本。大模型按 token 计费,输出 token 往往比输入 token 更贵。减少输出 token 是控制成本最直接的手段。两次调用把每次输出都控制在小范围,整体输出量下降。

第二个问题是准确率。单次提取如果出错,往往只能加大模型、加提示词、再做多轮重试,每轮都是一次全量调用的成本。两次调用用一次低成本校验来解决,不依赖无限堆 prompt 技巧。

第三个问题是稳定性。第二次调用可以把输出格式强制规整到统一 schema,即使第一次输出乱,第二次也能修正。

4. 环境准备与前置条件

这套方案不是一个独立软件,而是调用 LLM 的一种方式,所以环境准备重点是模型访问和运行脚本。

4.1 模型选择

你可以选择两种路线:

  • API 路线:使用 OpenAI、Anthropic、国内大模型厂商的 API,只需要申请 key,不需要显卡。
  • 本地部署路线:使用 Qwen、Llama、DeepSeek 等开源模型,用 Ollama、vLLM 或 Transformers 加载,需要 Python 环境和 CUDA 环境。

如果你已经有可用的 LLM 服务,不管 API 还是本地,都可以直接跑通方案。建议第一次用小参数模型,减少资源压力。

4.2 通用环境清单

项目要求
操作系统Windows / Linux / macOS 均可
Python3.9 及以上
依赖库requests 或 openai 等 SDK
LLM 服务API 或本地 Ollama/vLLM 服务
网络API 路线需可访问对应服务;本地模型无需
磁盘本地模型按大小需要数 GB 到数十 GB
显存本地大模型建议 8G 以上,小模型可 CPU 运行

如果走本地路线,先把模型服务起起来。以 Ollama 为例:

# 安装 Ollama 后拉取一个小模型,示例模型名需要替换 ollama pull qwen2.5:7b ollama serve

这里强调一下,模型名、服务端口、请求格式要以你实际使用的框架为准。下面代码里的地址和模型名都是占位符。

5. 两步提取方案设计与核心实现

我用一个通用 Python 示例来说明完整流程。案例目标:从一段简历文本中提取“姓名、工作年限、技能列表、最近一份工作公司”四个字段。

5.1 第一次调用:粗提取

第一次调用只做一件事:把可能包含目标字段的原文片段找出来,并给出初步值。

import requests LLM_URL = "http://127.0.0.1:11434/api/generate" MODEL_NAME = "qwen2.5:7b" def first_extract(text): prompt = f""" 你是一个信息提取助手。请从下面的文本中提取字段:姓名、工作年限、技能列表、最近一份工作公司。 只输出 JSON,不要解释。 文本: {text} """ payload = { "model": MODEL_NAME, "prompt": prompt, "stream": False, "format": "json" } resp = requests.post(LLM_URL, json=payload, timeout=120) return resp.json()["response"]

这一步的要点是:只要求输出 JSON,不要求模型做任何额外解释。如果模型输出格式不对,可以配合正则提取 JSON 片段,或者在 prompt 中给一个示例格式。

5.2 第二次调用:校验修正

第二次调用输入第一次的结果,同时只返回修正后的差异部分。

def second_validate(text, first_result): prompt = f""" 请校验下面这个信息提取结果,找出错误或缺失的字段。 原文: {text} 第一次提取结果: {first_result} 要求: 1. 对照原文核对每个字段。 2. 如果有错误,给出正确值。 3. 如果有缺失字段,补上正确值。 4. 如果都正确,返回一个空的 JSON 对象:{} 5. 只输出 JSON,不要解释。 """ payload = { "model": MODEL_NAME, "prompt": prompt, "stream": False, "format": "json" } resp = requests.post(LLM_URL, json=payload, timeout=120) return resp.json()["response"]

第二次调用的 prompt 做了几件关键事:

  • 明确告诉模型要“对照原文核对”。
  • 要求“错误则修正,缺失则补全,正确则输出空对象”。
  • 限制输出为 JSON。

这样第二次调用的输出 token 会非常少。绝大多数情况下,输出就是一个空 JSON 或只包含少数修改字段,成本自然可控。

5.3 合并最终结果

拿到两次调用的结果后,在代码里合并,而不是让模型再合并:

import json def merge_result(first_result, second_result): first = json.loads(first_result) second = json.loads(second_result) # 第二次结果就是修正或补漏的内容,覆盖到第一次结果上 merged = {**first, **second} return merged full_text = "张三,5年Python开发经验,精通FastAPI、Docker,目前在某某科技有限公司担任后端工程师。" first = first_extract(full_text) second = second_validate(full_text, first) result = merge_result(first, second) print(result)

这里要注意:合并动作放在代码里做,不要再让 LLM 做一次合并。合并是确定性逻辑,不该让模型参与;重复调用只会增加成本和延迟。

5.4 成本估算参考

假设文本 1000 token,四个字段固定输出:

  • 一次大模型全量提取:输入 1000 token,输出 300 token,输出很多是解释和格式内容。
  • 两次小模型提取:第一次输入 1000 token,输出 120 token;第二次输入 300 token,输出 30 token。总量和单价都下降明显。

这里只是一个估算方式。实际成本你需要按模型单价、token 长度、字段数量重新算,但“单次全量输出 > 粗提取输出 + 校验修正输出”的规律是稳定的。

6. 功能测试与效果验证

这个方案不是装完就跑,而是需要一个评测集来确认它是否在你的数据上真的有效。

6.1 测试目的

验证两点:

  • 准确率是否比单次调用提升。
  • 成本是否比单次调用下降。

6.2 构造评测集

从一个更小的样本开始。准备 20 到 30 条真实文本,人工标注出正确结果。注意:这组数据必须是你自己业务里的真实样本,不要用测试生成的无意义文本。

6.3 测试流程

对于每条样本,分别执行单次调用和两次调用两种策略,记录结果、token 消耗、耗时。

# 可以用一个简单的目录结构来组织测试 data/ input/ # 原始文本 labels/ # 人工标注结果 run_single/ # 单次调用输出 run_two/ # 两次调用输出 cost_logs/ # 每次调用的 token 统计

6.4 判断准确率

准确率的标准要提前定义好。常见做法是字段级准确率:抽取结果与标注结果完全一致才算该字段正确。

def field_accuracy(pred, label, fields): correct = 0 for field in fields: value = pred.get(field) expected = label.get(field) if isinstance(value, str): value = value.strip() if isinstance(expected, str): expected = expected.strip() if value == expected: correct += 1 return correct / len(fields)

字段级准确率的好处是可量化、可比较。材料中 72% 到 100% 的提升,应该是在类似字段级标准下得到的,你要在自己的数据上用同一标准验证。

6.5 对比维度

对比项单次调用两次调用
字段准确率记录记录
输入 token 总和记录记录
输出 token 总和记录记录
总耗时记录记录
失败率记录记录

如果两次调用的准确率没有提升,优先检查第一次提取的 prompt 是否把要求写清楚了,以及第二次校验是否真的拿到了原文上下文。

6.6 判断成功与否

  • 如果准确率提升超过 5 个百分点,且成本没有上升,方案成功。
  • 如果准确率提升但成本上升,说明第二次调用模型的输出太啰嗦,需要继续压缩 prompt。
  • 如果准确率没有提升,说明任务本身不适合拆成两步,回到单次调用再优化提示词。

7. 接口 API 与批量任务

如果要把这个方案接到业务系统里,可以直接封装成 API,或者用批量脚本处理目录文件。

7.1 封装成 API 服务

用 FastAPI 做一个轻量示例:

# requirements.txt: fastapi uvicorn requests from fastapi import FastAPI, Request from pydantic import BaseModel app = FastAPI() class ExtractRequest(BaseModel): text: str @app.post("/extract") def extract(req: ExtractRequest): first = first_extract(req.text) second = second_validate(req.text, first) return {"result": merge_result(first, second)} # 启动命令: # uvicorn app:app --host 127.0.0.1 --port 8000

把第五节的三个函数导入进来,接口就可以直接跑通。注意这边的目标不是工程脚手架,而是说明“两次调用”逻辑可以完全嵌在业务 API 后面,对调用方透明。

7.2 批量任务设计

批量处理时,把文件列表读进来逐条处理,并在每步做日志记录:

import time import json def run_batch(input_dir, output_dir, log_dir): import os files = os.listdir(input_dir) for f in files: with open(os.path.join(input_dir, f), "r", encoding="utf-8") as fr: text = fr.read() start = time.time() try: first = first_extract(text) second = second_validate(text, first) result = merge_result(first, second) with open(os.path.join(output_dir, f + ".json"), "w", encoding="utf-8") as fw: json.dump(result, fw, ensure_ascii=False, indent=2) except Exception as e: with open(os.path.join(log_dir, f + ".err"), "w", encoding="utf-8") as ferr: ferr.write(str(e)) cost_time = time.time() - start print(f"{f}: {cost_time:.2f}s")

批量任务需要关注失败率。如果某个文件第二次调用输出不是合法 JSON,可以做最多三次重试,每次重试把第一次结果加上“上次输出不是合法 JSON”的错误信息重新发给校验模型。

7.3 接口调用示例

如果是 curl 调用上面封装好的 API:

curl -X POST http://127.0.0.1:8000/extract \ -H "Content-Type: application/json" \ -d '{"text": "张三,5年Python开发经验,精通FastAPI、Docker,目前在某某科技有限公司担任后端工程师。"}'

返回结构大致如下,实际字段以你的 schema 为准:

{ "result": { "姓名": "张三", "工作年限": "5年", "技能列表": ["Python", "FastAPI", "Docker"], "最近一份工作公司": "某某科技有限公司" } }

8. 资源占用与性能观察

8.1 显存占用

如果使用本地模型,显存占用取决于模型大小。一个 7B 量化模型通常需要 6G 到 8G 显存,CPU 推理也能跑但速度慢。如果在两台机器上分别部署第一次和第二次调用所需模型,可以让两者并行处理不同文件,吞吐量更高。

显存占用要按你实际部署的模型来看。7B、14B、70B 的差距很大,不要拿某个模型的数值直接套用到别的模型。

8.2 Token 消耗观察

观察 token 消耗是这套方案落地最关键的步骤。要在每次调用前后记录:

  • 输入 token 数。
  • 输出 token 数。

通过对比单次调用的 token 总量,可以快速算出降本幅度。建议把日志写到 CSV 文件里:

timestamp,strategy,input_tokens,output_tokens,cost_estimate,status 2025-01-01 10:00:00,two_call,1300,150,0.003,success

后续可以用 pandas 或 Excel 直接统计。

8.3 影响性能和成本的因素

  • 输入文本长度:第一次调用的主要成本来源。
  • 字段数量越多,第一次输出 token 越长,成本越高。
  • 第二次调用输入不要用全文,只保留第一次结果和定位到的原文片段,否则成本会被拉高。

为了让第二次调用更省 token,可以在第一次 prompt 里要求模型同时输出“字段对应的原文片段”,第二次就只用这个片段做校验,不用整篇文本。

8.4 如何降低延迟

延迟方面,两次调用意味着平均多出一倍的网络往返或推理时间。如果两次调用都使用同一服务,可以尝试:

  • 第一次用小模型,第二次用小模型或同模型,减少单次推理时间。
  • 如果 API 允许并发,可以多线程处理不同样本。
  • 本地服务可以用 vLLM 提升吞吐。

9. 常见问题与排查方法

问题现象可能原因排查方式解决方案
第二次调用输出为空 JSON,但字段仍有明显错误校验 prompt 没有引导模型发现错误查看第一次结果和人工标注对比在第二次 prompt 中增加“逐字段对照原文”的明确要求
成本没有明显下降第二次调用输入仍使用了整篇原文检查第二次调用输入的 token 数第二次只传入第一次定位到的原文片段
模型输出不是合法 JSON模型能力不足或 prompt 未给示例查看原始输出内容加 JSON 示例,或用代码做格式修复
批量任务中途卡住某条文本太长,请求超时查看日志中的 timeout 报错增加 timeout 时间,或按长度分片处理
第一次提取结果为空文本格式与 prompt 假设不符打印第一次 prompt 和输入文本检查文本是否可以正常读取,调整 prompt 描述
本地模型推理速度很慢模型较大或 CPU 推理观察 CPU/GPU 占用换小模型、量化模型,或加 GPU
准确率提升不明显任务不适合两步拆分,或第二次校验未生效对比两次结果与标注的差异分布先诊断错误类型,再决定是否继续使用该策略

10. 最佳实践与使用建议

第一次先用小数据集跑通全流程,不要直接上全量数据。30 条样本足够判断方案是否有效,跑一次也就几分钟。

第二次调用的 prompt 要重点设计。它决定了整个方案的准确率上限。建议把第二次调用的 prompt 写成类似“代码 review 任务”的语气:正在解决问题的模型,不需要重写所有逻辑,只需要检查改动点。这个角色设定能有效减少模型乱发挥的情况。

字段个数要克制。一次提取 20 个字段以上时,第一次调用的输出 token 会显著膨胀。如果字段很多,考虑按业务域拆成多个独立提取任务,各自做两次调用。

合并逻辑必须放在代码里。让第三次 LLM 调用去合并结果,既增加成本,又引入新的不确定性。

成本估算不要只看模型单价,还要看输出 token 的实际长度。有些模型输出冗长解释,即使单价低,总 cost 也不低。通过 token 日志持续监控是唯一可靠的方式。

涉及人脸、声音、肖像、版权素材的数据,在进入任何 LLM 调用链路之前,必须先确认数据来源合法、处理授权清晰,尤其是批量解析合同、简历、发票这类包含个人敏感信息的场景。测试环境建议用脱敏数据,生产环境要控制数据访问范围。

11. 总结与下一步

这套“两次 LLM 调用代替一次调用”的方案,核心价值有两个:一是用更细的任务拆分降低输出 token,从而压成本;二是用一次低成本的校验环节提升准确率。它不依赖某个特定模型,API 路线和本地部署路线都能跑。

如果你现在面对的是长文本、固定字段、重复性高的提取任务,建议先做三件事:

  1. 准备 30 条带标注的真实样本。
  2. 分别跑单次调用和两次调用。
  3. 对比字段准确率和 token 消耗。

最容易踩的坑也提前说清楚:第二次调用千万不要把整篇原文又丢进去,那样成本不会有明显下降;字段一定要固定,输出格式一定要在代码里校验。

后续如果想继续优化,可以在这个框架上叠加更多工程手段,比如基于规则的预筛、多模型投票、结果缓存、字段置信度打分。先把“两次调用”这条基线跑通,后面的优化都会更可控。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询