你有没有遇到过这种情况:为了从一份长文档里抽取结构化字段,你把全文一次性丢给大模型,让它直接输出 JSON。结果模型要么漏字段,要么把原文里的数字改错,甚至格式根本不合法。回头一看账单,输入 token 高得吓人,准确率却卡在 70% 上下。
前阵子我看到一个很有意思的案例:同样是做信息抽取,把「一次大模型调用」改成「两次小任务调用」,成本反而降了 67%,抽取准确率从 72% 提升到了近乎 100%。
第一反应可能觉得离谱——调用次数翻倍,成本怎么会下降?准确率怎么会上升?
这篇文章就把这件事讲透:为什么一次调用又贵又不准,两次调用的设计思路是什么,以及如何用 Python 实现一个可运行的「粗提取 + 精校验」抽取流程。本文不讨论 fp16、fp32、bf16 这类推理数值精度问题,只聚焦业务层面的 extraction accuracy。
如果你正在做文档解析、简历抽取、合同关键信息提取,或者想优化 LLM 调用的成本,这篇文章值得读完。
1. 为什么单次调用又贵又不准
1.1 长文本抽取的真实困境
先还原一个最常见的场景。
你手里有一份简历、一份合同,或者一份发票扫描件转出来的文本。你需要从中抽取姓名、公司、邮箱、金额、日期这些结构化字段。最直接的做法就是写一个 prompt,把整份文本塞进去,要求大模型输出一个 JSON。
从下面的文本中提取字段,只输出 JSON: {"name": "", "company": "", "email": "", "amount": ""} 文本: ...这种方法在短文本上表现还行,但一旦文本变长,问题就来了。
第一个问题是模型「一心多用」容易出错。抽取本身需要定位、理解、去重、格式化,在几千甚至上万 token 的长文本里,模型要同时完成「找到姓名」「找到公司」「保证 JSON 合法」多个子任务。任务越复杂,出错的概率越高。
第二个问题是上下文越长,注意力越分散。大模型在超长上下文里并不会线性地记住所有细节,中间部分的字段被遗漏是常见现象。尤其是当答案分散在文本不同位置时,单次调用很难稳定地把所有字段都捞全。
第三个问题是成本被 few-shot 放大了。为了提升准确率,你会在 prompt 里加示例、加字段说明、加输出格式约束。这些内容每一轮都要重复计费。文本越长,few-shot 越多,输入 token 就越高。
1.2 单次调用的隐性成本
很多人只盯着输出 token 的价格,忽略了输入 token 才是大头。
一次长文本抽取调用,假设输入 8000 token,输出 500 token。如果使用的是高性能大模型,费用主要花在输入侧。而为了「一次搞定」,你又不得不在 prompt 里塞入大量指令和示例,进一步推高输入成本。
更隐蔽的是失败重试成本。抽取结果一旦字段缺失或 JSON 格式非法,你就要重新调用一次。一次变两次、两次变三次,实际成本远远超过账单上看到的第一次调用费用。
1.3 准确率上不去的三个原因
把准确率问题拆开看,通常有三个原因:
一是模型没有真正理解「要什么」。字段定义模糊,抽取目标不清晰。
二是错误不可校验。模型输出一个错误字段时,你没有任何机制发现它错了。
三是定位和格式化混在一个任务里。让模型既做信息定位又做 JSON 格式化,等于同时要求它做两件不同复杂度的事,后者会把前者的结果带偏。
所以说,单次调用的问题不是模型能力不够,而是任务形态不合理。
2. 两次调用为什么能赢:粗提取 + 精校验
2.1 核心思想是任务分解
两次调用的本质,是把「一次复杂的抽取任务」拆成「一次粗提取 + 一次精校验」。
第一次调用负责粗提取。它的任务是在长文本里定位与目标字段相关的候选片段,不需要输出最终 JSON,只需要把原文中相关的句子或段落原样捞出来。这一步可以用便宜的小模型完成。
第二次调用负责精校验。它不再面对整篇长文本,而是只看到第一次调用返回的候选片段。任务非常简单:根据候选片段,结合字段定义,输出严格 JSON。
如果你理解 Agent 的工作方式,会发现这就是一种极简的编排思想:先检索定位相关信息,再基于限定范围做精准决策。
2.2 成本为什么反而降低
这是整个方案最反直觉的地方:调用次数变多,为什么总成本反而更低?
关键在于,第二次调用的输入被第一次调用「裁剪」了。
单次调用时,大模型要处理完整长文本,假设输入 8000 token。两次调用时,第二次输入可能只需要候选片段,假设只有 800 到 1200 token。第一次调用虽然也处理完整文本,但使用的是便宜的小模型。最终总成本可能低于单次使用大模型处理完整文本的成本。
用一个简化的成本公式说明:
单次调用成本 = C_big × (prompt_full + completion_full) 两次调用成本 = C_small × (prompt_full + completion_coarse) + C_big × (prompt_focused + completion_final)其中 C_small 远小于 C_big,prompt_focused 远小于 prompt_full,那么即使调用两次,总成本也可能更低。
原文案例中出现 67% 的成本下降,本质上就是这个原因:虽然调用次数翻倍,但「贵模型处理的 token 量」大幅缩水,便宜模型承担了大部分粗活。
2.3 准确率为什么反而提升
成本降低可以用 token 计算解释,准确率提升则来自两点。
第一是上下文聚焦。第二次调用只看到候选片段,没有无关文本干扰,模型可以更专注地完成字段精确提取。这好比审稿人不需要读完整本书,只需要看被标记出来的几页。
第二是错误有了二次修正机会。第一次调用可能多捞了一些无关内容,第二次调用会做一次「校对」。即使第一次粗提取漏掉某个字段,你也可以在精校验阶段加入规则,要求模型对缺失字段再次确认。
在原文案例的测试集上,单次调用的准确率是 72%,两次调用之后提升到了 100%。这里的 100% 是指特定测试集上的表现,不代表任何场景都能达到。它说明的是:任务拆分带来的收益,有时候比换更强的模型更明显。
2.4 单次调用与两次调用的对比
| 对比维度 | 单次调用 | 两次调用(粗提取 + 精校验) |
|---|---|---|
| 任务复杂度 | 定位 + 理解 + 格式化混合 | 任务拆分,每步目标单一 |
| 大模型输入量 | 完整长文本 + few-shot | 聚焦后的候选片段 |
| 成本结构 | 贵模型处理全部文本 | 便宜模型做粗活,贵模型做精活 |
| 准确率稳定性 | 长文本下容易漏字段 | 上下文聚焦,错误可二次修正 |
| 延迟 | 一次调用 | 两次顺序调用,略有增加 |
| 失败恢复 | 失败后整篇重跑 | 可只针对缺失字段局部重试 |
看到这里你会发现,两次调用真正的优势不在于「调用了两次」,而在于「把错误分散到小范围内,把成本集中到小模型上」。
3. 适用场景与边界
3.1 适合什么场景
最典型的场景是长文本结构化抽取。比如:
- 简历信息抽取:姓名、城市、公司、职位、技能、邮箱。
- 合同关键信息提取:甲方、乙方、金额、日期、付款条件。
- 发票或票据识别:发票号、金额、税率、销售方。
- 政策文档和公告解析:标题、发布时间、正文要点。
这些场景有几个共同点:文本较长、字段分散、错误容忍度低,且抽取结果需要被下游系统使用。任何一次字段错误都可能导致业务异常,因此「二次校验」带来的准确率提升非常值钱。
3.2 不适合什么场景
短文本抽取不适合用这套方案。如果输入只有一两句话,一次调用完全能胜任,两次调用只会增加延迟和复杂度。
延迟敏感的场景也要谨慎。两次调用是顺序执行,总延迟等于两次调用耗时之和。在实时对话、在线检索这类要求快速响应的场景里,二次调用的收益可能被延迟成本抵消。
另外,如果一个小模型已经能在你的文本上达到 95% 以上的准确率,那也没必要强行拆成两次。拆分的收益曲线不是线性的,它更适合「准确率卡在中间水平」的尴尬阶段。
3.3 与 RAG 的关系
有人会问:第一次调用定位候选片段,这不就是 RAG 里的检索吗?
不完全一样。RAG 通常用向量检索召回候选段落,再把候选段落送给大模型生成答案。本文说的第一步是让 LLM 自己定位候选片段,相当于一个轻量的语义路由或预筛过程。它不需要提前建立向量索引,对于字段定义经常变化的场景更灵活。
如果项目里已经使用了 RAG,完全可以把「粗提取 + 精校验」作为其中一环。第一次调用负责从检索结果中过滤与字段相关的片段,第二次调用负责精确抽取。这两者是互补关系,不是替代关系。
4. 环境准备
动手之前,先把运行环境准备好。本示例使用 Python 3.9+,依赖 openai 库调用兼容 OpenAI 协议的模型服务。
4.1 安装依赖
pip install openai python-dotenv这里使用 openai 库,是因为目前绝大多数国内模型服务都提供 OpenAI 兼容接口。你只需要修改 base_url 和 model 名称,就能切换到实际使用的模型,不需要额外安装专用 SDK。
4.2 配置 API Key
建议把 API Key 放在环境变量中,不要硬编码到代码里。项目根目录创建一个.env文件:
LLM_API_KEY=your-api-key LLM_BASE_URL=https://api.example.com/v1 SMALL_MODEL=your-small-model BIG_MODEL=your-big-model其中:
LLM_API_KEY:你的模型服务 API Key。LLM_BASE_URL:兼容 OpenAI 协议的网关地址。SMALL_MODEL:粗提取使用的小模型,建议选择速度快、单价低的模型。BIG_MODEL:精校验使用的大模型,建议选择指令理解能力更强的模型。
具体的模型名称以你的服务商文档为准。本文示例用SMALL_MODEL和BIG_MODEL两个变量代替,避免写死某一家的模型名。
5. 完整代码实现
下面代码演示一套完整的「粗提取 + 精校验」流程,并提供一个单次调用的 baseline 作为对比。
5.1 准备测试文本
先用一份模拟简历作为测试输入。实际项目中,这段文本可能来自 PDF 解析、OCR 或数据库字段。
# 文件路径:demo_text.py RESUME_TEXT = """ 张三,男,1991年6月出生,现居杭州。 2014年毕业于浙江理工大学,计算机科学与技术专业。 毕业后加入杭州某中小型互联网公司,从事Java后端开发,主要参与电商交易系统的订单模块研发。 在之后五年里,他先后接触过Spring Boot、MySQL、Redis、Kafka等技术,并在2019年晋升为技术小组组长。 2020年跳槽到一家在线教育公司,开始负责研发团队的日常管理和核心架构设计。 他熟悉微服务治理、分布式事务和容器化部署,同时有较强的跨部门沟通能力。 目前所在公司为杭州某某科技有限公司,担任后端技术负责人。 联系方式:zhangsan@example.com。 最近三年,他主导过多个中大型项目的落地,重点方向包括高并发接口优化、数据迁移和链路追踪。 个人兴趣包括开源项目、技术写作和长跑。 """ FIELDS = ["name", "city", "company", "email", "position", "tech_stack"]这里设计了 6 个抽取字段。tech_stack需要从文本中的技术名词中汇总成数组,适合展示「候选片段 + 精校验」的好处。
5.2 基础工具函数
封装一个统一的 LLM 调用函数,方便记录 token 用量,同时加一个简单的 JSON 解析容错函数。
# 文件路径:llm_client.py import json import os from openai import OpenAI from demo_text import RESUME_TEXT, FIELDS client = OpenAI( api_key=os.getenv("LLM_API_KEY", "your-api-key"), base_url=os.getenv("LLM_BASE_URL", "https://api.example.com/v1"), ) SMALL_MODEL = os.getenv("SMALL_MODEL", "your-small-model") BIG_MODEL = os.getenv("BIG_MODEL", "your-big-model") def call_llm(model: str, system_prompt: str, user_prompt: str, temperature: float = 0.0): """调用大模型,返回文本内容和 token 用量。""" response = client.chat.completions.create( model=model, messages=[ {"role": "system", "content": system_prompt}, {"role": "user", "content": user_prompt}, ], temperature=temperature, ) content = response.choices[0].message.content usage = { "prompt_tokens": response.usage.prompt_tokens, "completion_tokens": response.usage.completion_tokens, } return content, usage def parse_json_loose(text: str): """容错解析模型输出的 JSON。""" text = text.strip() if text.startswith("```"): text = text.strip("`") text = text.replace("json", "", 1) start = text.find("{") end = text.rfind("}") if start == -1 or end == -1: return None try: return json.loads(text[start:end + 1]) except json.JSONDecodeError: return None5.3 第一次调用:粗提取候选片段
第一次调用的目标不是输出最终字段,而是定位与目标字段相关的原文片段。
# 文件路径:stage1_coarse_extract.py COARSE_SYSTEM_PROMPT = """ 你是信息抽取助手。请从给定的文本中,定位与目标字段相关的原文片段。 目标字段:{fields} 规则: 1. 只要某个句子或短语与目标字段相关,就把原文原样抄录下来。 2. 不要改写原文,不要翻译,不要补充文本里没有的信息。 3. 输出 JSON 数组,每个元素包含 field、quote、reason 三个字段。 4. 如果某个字段在文本中没有出现,quote 填 null。 只输出 JSON。 """ def stage1_coarse_extract(text: str): fields_str = "、".join(FIELDS) user_prompt = f"目标字段:{fields_str}\n\n文本:\n{text}" content, usage = call_llm( SMALL_MODEL, COARSE_SYSTEM_PROMPT.format(fields=fields_str), user_prompt, ) quotes = parse_json_loose(content) return quotes, usage这一步允许模型多捞一些内容,不需要太精确。因为后面还有第二次调用做精校验。
5.4 第二次调用:精校验输出
第二次调用只接收候选片段,输出严格 JSON。
# 文件路径:stage2_precise_extract.py PRECISE_SYSTEM_PROMPT = """ 你是信息抽取专家。请根据候选片段,从原文中精确抽取以下字段: - name:姓名 - city:所在城市 - company:当前公司 - email:邮箱 - position:当前职位 - tech_stack:技术栈列表 规则: 1. 只能使用候选片段中出现的原文信息。 2. tech_stack 输出字符串数组;没有的内容填空数组。 3. 不要凭空补充字段值。 4. 只输出一个 JSON 对象。 """ def stage2_precise_extract(quotes: list, usage_coarse: dict): quote_block = "\n".join( f"[{item.get('field')}] {item.get('quote')}" for item in quotes ) user_prompt = f"候选片段:\n{quote_block}" content, usage = call_llm( BIG_MODEL, PRECISE_SYSTEM_PROMPT, user_prompt, ) result = parse_json_loose(content) return result, usage第二次调用的输入只有第一次返回的候选片段,通常在 800 到 1500 token 之间,远小于原文的完整长度。
5.5 单次调用 baseline
作为对照组,再写一个单次调用版本。它直接把整篇文本和字段定义一起交给大模型。
# 文件路径:baseline_single_pass.py SINGLE_PASS_SYSTEM_PROMPT = """ 你是信息抽取专家。请从文本中直接抽取以下字段,并输出 JSON: - name:姓名 - city:所在城市 - company:当前公司 - email:邮箱 - position:当前职位 - tech_stack:技术栈列表 规则: 1. 只能使用原文中出现的信息。 2. tech_stack 输出字符串数组。 3. 不要输出 JSON 之外的任何内容。 """ def single_pass_extract(text: str): content, usage = call_llm( BIG_MODEL, SINGLE_PASS_SYSTEM_PROMPT, f"文本:\n{text}", ) result = parse_json_loose(content) return result, usage5.6 成本估算与主流程
代码中给出一个示例价格模型,只为演示成本比例。实际使用时请替换为你的模型服务商价格。
# 文件路径:main.py PRICE_PER_TOKEN = { "small_input": 0.0000002, "small_output": 0.0000008, "big_input": 0.000002, "big_output": 0.000008, } def estimate_cost(usage, is_small: bool): if is_small: input_price = PRICE_PER_TOKEN["small_input"] output_price = PRICE_PER_TOKEN["small_output"] else: input_price = PRICE_PER_TOKEN["big_input"] output_price = PRICE_PER_TOKEN["big_output"] return ( usage["prompt_tokens"] * input_price + usage["completion_tokens"] * output_price ) def run_pipeline(): # 单次调用 single_result, single_usage = single_pass_extract(RESUME_TEXT) single_cost = estimate_cost(single_usage, is_small=False) # 两次调用 quotes, coarse_usage = stage1_coarse_extract(RESUME_TEXT) final_result, precise_usage = stage2_precise_extract(quotes, coarse_usage) coarse_cost = estimate_cost(coarse_usage, is_small=True) precise_cost = estimate_cost(precise_usage, is_small=False) two_stage_cost = coarse_cost + precise_cost print("【单次调用结果】") print(single_result) print("prompt_tokens:", single_usage["prompt_tokens"], "completion_tokens:", single_usage["completion_tokens"]) print("cost:", round(single_cost, 6)) print("\n【两次调用结果】") print("粗提取候选:") for item in quotes: print(" ", item) print("精校验结果:") print(final_result) print("粗提取 prompt_tokens:", coarse_usage["prompt_tokens"], "completion_tokens:", coarse_usage["completion_tokens"]) print("精校验 prompt_tokens:", precise_usage["prompt_tokens"], "completion_tokens:", precise_usage["completion_tokens"]) print("cost:", round(two_stage_cost, 6)) if __name__ == "__main__": run_pipeline()这套代码的核心逻辑很清晰:先让小模型在海量文本中圈出重点,再让大模型只看重点做精确输出。两次调用的收益不是来自某个神秘的模型能力,而是来自「大模型处理的信息量大幅缩小」。
6. 运行结果与效果验证
6.1 运行方式
在项目根目录执行:
python main.py运行成功后,你会看到单次调用和两次调用的结果对比、token 用量和估算成本。下面是一个模拟输出,实际数值会因模型服务商和文本内容不同而变化。
6.2 输出对比
【单次调用结果】 {"name": "张三", "city": "杭州", "company": "杭州某某科技有限公司", "email": "zhangsan@example.com", "position": "后端技术负责人", "tech_stack": ["Java", "Spring Boot", "MySQL"]} prompt_tokens: 8500, completion_tokens: 450 cost: 0.0206 【两次调用结果】 粗提取候选: [{'field': 'name', 'quote': '张三,男,1991年6月出生,现居杭州。', 'reason': '姓名信息'} {'field': 'tech_stack', 'quote': '先后接触过Spring Boot、MySQL、Redis、Kafka等技术'}, ...] 精校验结果: {"name": "张三", "city": "杭州", "company": "杭州某某科技有限公司", "email": "zhangsan@example.com", "position": "后端技术负责人", "tech_stack": ["Java", "Spring Boot", "MySQL", "Redis", "Kafka", "微服务", "分布式事务", "Docker"]} 粗提取 prompt_tokens: 8500, completion_tokens: 800 精校验 prompt_tokens: 1200, completion_tokens: 420 cost: 0.0101注意,这段输出是示意性的。核心观察点是两处:
第一,第二次调用的「贵模型」输入 token 从单次调用的 8500 降到了 1200 左右,这是成本下降的关键。
第二,精校验结果往往能补全单次调用漏掉的技能点。单次调用可能只输出两三个技能就结束了,而精校验因为只看候选片段,更不容易遗漏。
6.3 如何判断方案是否有效
不要只看一次运行的结果。正确的验证方式是对同一批测试样本分别运行两套流程,按字段计算准确率。
# 文件路径:evaluate.py GROUND_TRUTH = { "name": "张三", "city": "杭州", "company": "杭州某某科技有限公司", "email": "zhangsan@example.com", "position": "后端技术负责人", "tech_stack": ["Java", "Spring Boot", "MySQL", "Redis", "Kafka", "微服务", "分布式事务", "Docker"], } def evaluate_one(actual: dict): if not actual: return 0.0 correct = 0 for key, expect in GROUND_TRUTH.items(): if actual.get(key) == expect: correct += 1 return correct / len(GROUND_TRUTH)在一个固定测试集上分别统计两套流程的平均字段准确率,你大概率会看到类似趋势:单次调用在 70% 到 80% 之间波动,两次调用能稳定在 90% 以上。这不是模型能力突变了,而是任务拆分减少了单次模型输出的复杂度。
6.4 失败时先看哪里
如果运行失败,第一步看模型返回的 content 是否包含完整 JSON,第二步看 parse_json_loose 是否解析成功。大模型偶尔会在 JSON 前后加解释文字,容错解析函数已经帮你处理了多余前缀后缀。如果 candidate 列表为空,优先检查第一步的粗提取 prompt 是否限制了「只能输出 JSON」之外的内容。
7. 常见问题与排查思路
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 第二次校验后准确率反而更差 | 粗提取返回的候选片段本身噪声太多或丢失关键信息 | 打印粗提取返回的 quotes,对比原文 | 调整粗提取 prompt,允许模型多捞内容,宁可多不可漏 |
| 粗提取阶段漏掉关键片段 | 小模型指令理解能力有限 | 检查粗提取输出的 reason 字段 | 降低输出约束,增加示例;或换更强的小模型 |
| 模型输出不是合法 JSON | 温度过高或 prompt 约束不足 | 查看原始返回内容 | 温度设为 0;在 prompt 末尾加上「只输出 JSON」 |
| 成本并没有明显下降 | 粗提取输出过长,导致精校验输入仍很大 | 打印两次调用各自的 prompt_tokens | 限制粗提取 quote 长度;只保留与字段直接相关的句子 |
| 长文本超出模型上下文窗口 | 原文过长,小模型也装不下 | 查看模型上下文限制 | 先做文本切块,对每块做粗提取,再汇总候选片段做精校验 |
| 两次调用延迟太高 | 两个模型都偏慢 | 在日志中记录每次调用耗时 | 小模型选低延迟型号;精校验只处理候选片段,通常不会太慢 |
| tech_stack 这类列表字段总是漏项 | 列表字段分散在多个句子中,粗提取只捞了第一处 | 检查 quotes 中 tech_stack 条目数量 | 粗提取规则中明确「列出所有相关片段,不要合并去重」 |
8. 最佳实践与工程建议
8.1 粗提取 prompt 宁可多捞,不可漏掉
粗提取是整条链路的漏斗入口。如果粗提取漏了,精校验就永远看不到正确的原文。所以粗提取阶段的 prompt 应该允许模型多返回一些上下文,不要急着做字段级精确过滤。把「精确」这个任务交给第二次调用。
8.2 小模型和大模型的选择
小模型不是越便宜越好。如果小模型粗提取质量太差,精校验拿不到足够的候选信息,准确率会直线下降。建议先用一个小模型做一轮粗提取,打印 quotes 看看覆盖率,再决定是否升级。
大模型的选择标准也不是参数越多越好,而是指令跟随能力要强。精校验阶段其实是一个「基于小范围文本做结构化输出」的任务,大模型只需在这个范围内发挥优势。
8.3 对列表类字段做特殊处理
像技能、人员名单这类列表字段,值分散在多个句子中。粗提取阶段容易只捞到第一处。建议在字段定义中明确说明「该字段需要列出所有相关片段」,或者在精校验 prompt 中提示「检查候选片段中是否还有遗漏的同类信息」。
8.4 成本监控和日志记录
每次调用都要记录 prompt_tokens、completion_tokens、模型名称、耗时。成本优化不是一次性工作,而是持续的过程。字段变化、文本长度变化都会影响 token 消耗。如果没有监控,很难发现哪一次 prompt 调整让成本翻倍了。
8.5 评测集先行
不要靠肉眼判断「好像准确了」。准备一个 30 到 50 条的固定测试集,包含各种边界情况。每次修改 prompt、切换模型后,先在测试集上跑一遍,对比字段准确率和成本变化。在这个方案里,评测集就像刹车系统,防止你越调越偏。
8.6 注意合规与数据安全
身份证号、手机号、邮箱、公司内部信息都属于敏感数据。调用云端模型服务前,确认数据脱敏策略和合规要求。优先使用机构批准或私有化部署的模型服务。API Key 不要提交到代码仓库,用环境变量或密钥管理服务管理。
9. 总结
「两次 LLM 调用反而更便宜更准」这件事,拆开看逻辑并不复杂:一次调用让模型同时干太多事,任务复杂度和 token 成本都在膨胀;拆成「粗提取 + 精校验」后,便宜模型承担文本定位的繁重工作,贵模型只做小范围内的精确抽取,成本和准确率都得到优化。
这篇文章讲清楚了单次调用为什么卡在 70% 左右、两次调用的成本模型为什么反而更低、以及完整代码怎么落地。如果你是做信息抽取相关项目,建议先在自己的测试集上跑一遍这个流程,对比单次调用和两次调用的准确率与成本曲线。
下一步值得深入的方向包括:将这套思路扩展到 RAG 的答案精炼环节,或者在粗提取阶段引入置信度机制,只有低置信度字段才触发第二次调用。成本优化和准确率优化永远不只是换模型那么简单,任务拆分的思路在任何阶段都不过时。