1. 动手之前先想清楚:这套接口到底适合什么场景
很多人第一次接触 OpenAI API,是被“调用大模型”这四个字吸引过来的,觉得只要拿到密钥、跑通一段示例代码,就等于掌握了。我最初也是这么想的,结果第一个上线的功能就被成本表和延迟曲线按在地上摩擦。所以这篇内容不打算从“安装 SDK”讲起,而是先把几个更根本的问题理清楚:这套接口能干什么、干不了什么、什么任务用它划算、什么任务用它纯属浪费。
一句话概括,OpenAI API 是把大模型能力以 HTTP 服务形式暴露出来的接口集合,你用代码把文本、图片、音频送进去,拿回模型生成的文本或结构化结果。它解决的核心问题是:让业务系统不必自己训练和部署模型,就能获得语言理解、生成、抽取、改写、推理这类能力。适合谁看?三类人:一是想把 AI 功能塞进现有产品的后端开发者,二是要快速验证想法的独立开发者或小团队,三是需要评估“这事到底值不值得接模型”的技术负责人。如果你只是想找个聊天窗口随便问问,那用现成的对话产品就够了,不必碰接口。
真正需要接口的场景,几乎都带三个特征:要嵌进自己的流程、要处理批量数据、要对结果做二次加工。比如把客服工单自动归类打标、把一堆非结构化简历抽成字段、把用户长评论压成摘要、给代码仓库做批量注释检查、把会议纪要转成待办清单。这些事情的共同点是输入量不小、格式不固定、结果要落到自己的数据库里,手工做不现实,规则引擎又覆盖不了语言的模糊性。反过来说,凡是能用正则、字典、SQL 精确解决的事情,就别上模型——我见过有团队用大模型判断“这行文本里有没有出现某个固定编号”,纯粹是把简单问题复杂化,延迟高、成本高、还不稳定。
1.1 模型谱系怎么挑:先摸上限,再压成本
接口里的 model 字段是整个调用里最影响结果和账单的一个参数。模型大致可以分成几层:能力最强的旗舰层、速度与成本平衡的通用层、便宜高效的轻量层,另外还有专门偏推理的型号和支持图像、音频的多模态型号。具体型号名字和价格变动很快,我不在这里写死,你以官方文档的模型列表页为准,但选择逻辑是不变的。
我的建议顺序是从上往下压,而不是从下往上试。先用能力最强的那一档把功能跑通,确认“这件事模型到底能不能做到、做到什么水平”,把提示词和输出格式调稳;然后换成便宜一档的模型,跑同一套测试用例,看效果掉多少。如果只掉一点点、业务能接受,就换;掉得厉害,那就老老实实付旗舰的钱,或者拆成“小模型做粗筛 + 大模型做精修”的两段式。
常见的错误是反过来:一上来就图便宜用小模型,结果效果不好,回头怀疑提示词写得不对,反复折腾两三天,最后换大模型一次通过。这两天的时间和情绪成本,远比省下来的那点调用费贵。还有一个常被忽略的点:轻量模型和旗舰模型对提示词的敏感度不一样,旗舰模型能容忍你写得含糊,轻量模型不行。所以在旗舰模型上验证通过的提示词,换小模型之后往往要重写一遍约束部分,这个工作量要提前算进去。
至于推理型模型,它适合的是多步推导、复杂条件判断、需要“先想再答”的任务,比如复杂的规则校验、长文档的逻辑一致性检查。代价是它会产生额外的推理 token,账单和延迟都会往上走,而且它对提示词的写法偏好也不一样(通常不需要你手把手给解题步骤)。如果你的任务是简单的分类或抽取,用它就属于杀鸡用牛刀。
1.2 先算账再写代码:token 与成本的心算方法
写第一行业务代码之前,我习惯先在纸上把账算一遍,因为模型的计费是按 token 来的,输入和输出分开算,输出通常比输入贵好几倍。不了解这个结构,很容易做出一个“功能很好但根本养不起”的东西。
token 不是字,也不是单词,是模型切分文本后的最小单元。粗略的经验值:英文大约 4 个字符对应 1 个 token,一个英文单词常常是 1 到 1.5 个 token;中文因为编码方式不同,一个字经常对应 1 到 2 个 token,标点和换行也占。这个比例只是拿来心算的,真要精确,用官方提供的分词工具跑一遍最稳。我一般在项目里写一个估算函数,把待处理文本丢进去算总数,再乘单价,先看个量级。
心算公式很简单:
| 项目 | 计算方式 |
|---|---|
| 单次输入 token | 系统提示 + 历史对话 + 用户输入 + 工具定义,全部加起来 |
| 单次输出 token | max_tokens 上限,实际按生成量结算 |
| 单次成本 | 输入 token × 输入单价 + 输出 token × 输出单价 |
| 日成本 | 单次成本 × 日调用量 |
举个例子:假设某个模型输入单价是每百万 token X 元,输出单价是每百万 token 4X 元。你的系统提示加工具定义固定占 800 token,用户输入平均 200 token,输出平均 300 token。那单次成本就是 (1000 × X + 300 × 4X) / 1,000,000,也就是 2200X 除以一百万。看着很小,但如果日调用量是 20 万次,一天就是 22 万 X 的千分之一……这里我不写具体数字,你自己把 X 代进去,通常会得出一个让人清醒的结论:固定提示词的重复消耗,是账单里最大的一块隐性支出。
这就是为什么后面我会专门讲提示词缓存和前缀复用:那 800 token 的系统提示,每次请求都重新计费,一年下来可能比你的服务器还贵。算清这笔账再动手,能避免很多返工。
2. 最小可运行骨架:第一次调用要把哪几件事做对
跑通第一个请求只需要几分钟,但把它写成“以后不用返工”的样子,需要做对几件事。我见过太多项目,第一版代码是直接从文档里复制示例,密钥硬编码在文件里,异常全部交给顶层 catch,结果上线第一周就出问题。这一章讲的是骨架,也就是之后所有功能都长在上面的那层地基。
先说依赖。Python 环境用官方 SDK 就够,安装一个包,创建客户端对象。现在的 SDK 推荐用客户端实例的方式调用,而不是早期那种全局配置,好处是你可以针对不同用途创建多个客户端,各自设置超时和重试策略。初始化时会自动读取环境变量里的密钥,所以代码里不要出现密钥字符串。
# 安装:pip install openai import os from openai import OpenAI client = OpenAI( api_key=os.environ["OPENAI_API_KEY"], timeout=30.0, # 单次请求超时,秒 max_retries=3, # SDK 内置重试次数 )调用方式上,目前主流仍是 chat completions 风格的接口,消息按角色组织;新版 SDK 里也有面向更通用输入输出的 responses 接口,字段组织方式不同。两者都能用,我的建议是:如果你的业务是纯文本对话式交互,chat 风格资料多、生态成熟;如果是多模态混合输入、或者想用内置工具,可以看看新接口。无论选哪个,先在一个小脚本里跑通,确认密钥、模型名、网络出口都没问题,再接进项目。
2.1 密钥管理:别把第一颗雷埋在地基里
密钥泄露是这类项目最常见也最致命的事故。我踩过一次:把测试脚本推到公开仓库,密钥写死在代码里,半小时后收到一封额度异常的告警邮件。从那以后我固定了几条规矩。
第一,密钥只放在环境变量或密钥管理服务里,代码里永远只有os.environ[...]。第二,本地开发用.env文件加载,但.env必须写进.gitignore,仓库里只留一个.env.example写变量名。第三,前端绝不直接调接口,所有请求走自己的后端中转,因为任何打进浏览器的密钥都等于公开。第四,给不同环境用不同的密钥,开发、测试、生产分开,这样出问题能单独吊销,也能分别看用量。第五,在控制台设置用量上限和告警阈值,这是最后一道保险,万一真泄露,损失有上限。
还有一个细节:日志里不要打印完整密钥,也不要打印完整的请求头。我习惯在日志里只保留密钥的后四位做标识,方便排查是哪把钥匙的问题。另外,团队协作时别用共享密钥,一人一把,出问题能定位到人,人走了直接吊销那一把,不影响其他人。
2.2 第一个请求:把消息结构理解透
chat 风格的消息是一个数组,每个元素有 role 和 content。role 有四种:system 用来放角色设定和全局规则,user 放用户这一轮输入,assistant 放模型之前的回复(多轮对话时回填),tool 放工具执行结果。这个数组就是模型的“全部记忆”,模型本身不保存任何上下文,每次请求你都要把需要的历史重新送一遍。这一点必须刻在脑子里,否则你会困惑“为什么它忘了我上一句说的话”。
resp = client.chat.completions.create( model="你的模型名", messages=[ {"role": "system", "content": "你是一名严谨的技术文档编辑。"}, {"role": "user", "content": "把下面这段话压缩成一句话:……"}, ], temperature=0.2, max_tokens=300, ) print(resp.choices[0].message.content)返回结构里几个字段值得记住:choices是候选结果数组,通常取第 0 个;finish_reason告诉你为什么停了,stop是正常结束,length是被 max_tokens 截断,tool_calls表示模型要调工具,content_filter表示被安全策略拦截。usage里有用量明细,输入多少、输出多少,这是你对账的唯一依据,务必落到日志里。我第一次做成本统计时没记 usage,后来只能靠估算,误差三成以上,返工重埋点非常痛苦。
2.3 参数逐个掰开:每个旋钮到底在调什么
参数不多,但每一个都值得说清楚,因为它们直接决定输出的稳定性和账单。
temperature控制随机性,取值通常 0 到 2。它本质上是在调整候选词的概率分布,值越低越倾向于选高概率词,输出越确定;值越高越敢选低概率词,越有“创意”。做抽取、分类、格式转换这类需要稳定复现的任务,我给 0 到 0.3;做文案生成、头脑风暴,给 0.7 到 1.0。注意它不是“越低越准确”,只是“越低越稳定”,低温度下模型仍然可能答错,只是每次错得一样。
top_p是另一种控制随机性的方式,叫核采样,只从累计概率达到 p 的那部分候选词里选。它和 temperature 一般不同时调,调一个就行。我个人的习惯是固定 temperature,把 top_p 留默认。
max_tokens是输出上限,也是最直接的省钱旋钮。它不设,模型可能一直说到自然结束,长输出就是长账单;设太小,答案会被拦腰截断,finish_reason会告诉你length。我的做法是按任务的输出长度分布,取一个略高于 P95 的值,既不容易截断,也不至于放开了乱写。注意推理型模型有个额外的推理 token 预算参数,不计入max_tokens,用这类模型时要单独留额度,否则会出现“输出没几个字但账单很贵”的情况。
stop是停止词,碰到指定字符串就停。这个在模板化输出里很好用,比如你只要模型填一段内容,遇到###就停,能省掉后面一堆废话。
response_format控制结构化输出,后面单独讲。seed用于尽量复现结果,但注意它只是“尽力而为”,不是绝对保证,别把线上逻辑建立在“同 seed 必得同结果”这个假设上。
3. 提示词与输出结构:让模型稳定吐出你要的东西
接口本身只是管道,真正决定效果的是你送进去的内容。这一章是我在实际项目里花时间最多的地方——把“模型偶尔能答对”变成“模型稳定答对”,靠的不是换模型,而是提示词结构和输出约束。
先说一个心态问题。很多人把提示词当咒语,到处收集“神奇提示词模板”,试来试去发现效果忽好忽坏。问题出在没有把提示词当代码看待。提示词是有结构的、可测试的、可版本管理的。我给团队的规矩是:提示词必须进版本控制,每次改动要有对照测试集,改完要跑一遍回归。这样你才知道自己是变好了还是仅仅“这次碰巧对了”。
3.1 分层写法:把提示词拆成五块
我现在写提示词基本固定成五块:角色定义、任务描述、输入数据、约束条件、输出格式。顺序也基本固定,因为模型对靠后的内容注意力更强,输出格式放最后效果最好。
角色定义一句话就够,比如“你是一名有十年经验的合同审阅律师”。它的作用是收窄模型的表达空间,让它调用相关的表达习惯。写太长、堆一堆形容词反而没用。任务描述要具体到“输入什么、输出什么、边界在哪”,避免“帮我分析一下”这种模糊指令。输入数据用明确的分隔符包起来,比如三引号或者 XML 标签,这一步非常关键——它能防止用户输入里的文字被模型误当成指令,也就是常说的提示词注入。约束条件写清楚不能做什么、拿不准怎么办(比如“信息不足时输出 null,不要猜测”)。输出格式放最后,写清楚字段、类型、示例。
你是一名简历信息抽取助手。 任务:从下方简历文本中抽取结构化信息。 输入: <resume> {resume_text} </resume> 约束: 1. 只依据文本内容抽取,不要推测。 2. 文本中未提及的字段,值设为 null。 3. 工作年限按整年计算,不足一年按 0 计。 输出格式:仅输出 JSON,不要解释,不要代码块标记。 {"name": string|null, "years": number|null, "skills": string[]}这个结构看起来朴素,但它解决了我遇到的大部分“输出格式乱掉”的问题。特别注意那句“不要代码块标记”,如果不写,模型经常会给你包一层 ```json,导致解析失败。这就是经验,文档里不会特意提醒你。
3.2 结构化输出:三种做法,各有各的代价
让模型吐 JSON 有三种常见做法,我按可靠性从低到高说。
第一种,纯提示词约束,然后自己解析。优点是兼容性最好,任何模型都能用;缺点是偶尔会有前后缀、注释、尾逗号,解析器要写得非常宽容。我的做法是解析前先做清洗:去掉代码块标记、截取第一个左花括号到最后一个右花括号之间的内容、把尾逗号替换掉,再json.loads。解析失败就重试一次,重试时把错误信息一起塞回去,告诉模型“上次输出不符合要求,请只输出合法 JSON”。这个重试机制能把成功率从九成拉到接近百分之百。
第二种,用接口的 JSON 模式。打开之后,模型保证输出是合法 JSON,但你必须同时在提示词里出现“JSON”这个词,否则会报错——这个坑我踩过,报错信息还算清楚,但第一次遇到会懵。它保证语法合法,不保证字段符合你的 schema,字段名写错、类型不对依然可能发生。
第三种,用结构化输出 / JSON Schema 严格模式。你把 schema 定义好传进去,模型输出会严格贴合 schema,字段名和类型都有保证,可选字段的处理也更可靠。这是目前做数据抽取最省心的方式,代价是 schema 有一定复杂度限制,不是所有模型都支持,而且第一次接入要花点时间把 schema 写对。
不管用哪种,我都坚持一条:模型输出只是候选,落库前必须校验。必填字段缺失、类型不对、枚举值越界、字符串超长,这些都要在代码里检查,不合格就重试或者进人工队列。把模型当“会犯错的实习生”,而不是“确定性函数”,这个心态能帮你省掉大量线上事故。
3.3 流式输出:首字延迟才是用户感知的关键
同步调用要等模型全部生成完才返回,一个 800 字的长回答可能要等十几秒,用户会以为页面卡死了。流式输出就是为了解决这个体验问题:它把结果一小块一小块推回来,你边收边渲染,用户看到字在动,等待焦虑立刻下降。
stream = client.chat.completions.create( model="你的模型名", messages=[{"role": "user", "content": "介绍一下这个方案的优缺点"}], stream=True, ) for chunk in stream: delta = chunk.choices[0].delta if delta and delta.content: print(delta.content, end="", flush=True)几个实操要点。第一,delta.content可能是None,尤其是第一个和最后一个 chunk,必须判空,否则线上会抛异常。第二,流的最后一个 chunk 会带finish_reason,如果你的业务需要统计用量,流式模式下要注意用量字段什么时候出现。第三,流式不省钱,输出 token 一分不少,它只优化体验。第四,如果前端用的是 HTTP 流式传输,记得关掉中间层的缓冲,否则用户还是看不到实时效果——这个坑我在网关配置上折腾了半天。第五,流式下的错误处理更麻烦,因为响应头可能已经发出去了,中途出错只能在前端提示“生成中断”,所以关键业务不要只靠流式,最好有完整重试路径。
4. 把接口用进真实业务:工具调用、批处理与容错
跑通单次调用只是开始,真实业务里你会遇到三个绕不开的问题:模型怎么获取外部信息、大量请求怎么并发、出错了怎么兜底。这一章讲的就是让接口真正能扛住业务的三块能力。
4.1 工具调用:让模型从“会聊天”变成“能办事”
模型本身不知道今天的库存、查不了数据库、发不了邮件。工具调用(也叫函数调用)就是给它装上手:你用 JSON Schema 描述几个可用函数,模型判断该用哪个、参数填什么,你执行完把结果回填,它再基于结果继续生成。整个闭环是:请求带 tools → 模型返回 tool_calls → 你执行 → 回填 role=tool 的消息 → 再请求 → 直到 finish_reason 为 stop。
tools = [{ "type": "function", "function": { "name": "get_order_status", "description": "根据订单号查询订单当前状态", "parameters": { "type": "object", "properties": {"order_id": {"type": "string", "description": "订单号,纯数字"}}, "required": ["order_id"], }, }, }]踩坑记录几条。第一,description写得好不好,直接决定模型选不选这个工具、参数填得对不对,它比函数名重要得多,要写清楚“什么时候用、什么时候不用”。第二,模型可能一次返回多个工具调用,要按数组遍历处理,别只取第一个。第三,参数必须校验,模型给的订单号可能是带空格的、带前缀的,甚至幻觉一个不存在的号码,执行层要做格式检查和存在性检查,查不到就把“未找到”作为结果回填,让模型自己组织回复。第四,工具执行必须有超时,否则一个慢查询会把整个会话拖死。第五,避免死循环,设置最大轮次,比如最多五轮工具调用,超过就强制收尾。
还有一条容易忽略:工具调用会让提示词变长(工具定义本身占 token),而且每轮都要重新发送。工具越多、schema 越复杂,固定成本越高。我一般控制在五个以内的核心工具,其余功能用“一个通用查询函数 + 参数说明”的方式合并。
4.2 并发与限流:别把 429 当成玄学
批量处理时最常见的报错是限流,返回码 429。很多人以为是随机抽风,其实它有明确含义:你单位时间内的请求数或 token 数超过了配额。处理方式不是傻等,而是指数退避 + 抖动。第一次失败等 1 秒,第二次 2 秒,第三次 4 秒,加一点随机浮动避免所有请求同时重试造成二次拥堵。
并发数也要控制住。开一百个线程同时打接口,看起来很猛,实际上大部分请求都在排队或者被拒。我的经验是先用信号量把并发压到一个保守值,比如 5 到 10,观察成功率,再慢慢往上加,找到一个成功率接近百分之百的最大并发。这个值跟你的配额、单请求 token 量都有关,没有通用答案,必须实测。
如果是离线的大批量任务,比如给十万条历史数据打标,别用同步接口硬跑,看看官方的批处理能力。它把请求打包成文件提交,异步处理,通常有几小时到一天的周转时间,换取更低的单价和更高的吞吐配额。代价是没有实时性,适合夜间任务、报表生成、历史数据回填这类场景。我用它跑过一轮数据清洗,成本比同步调用低不少,值得在项目规划阶段就考虑进去。
4.3 缓存与降级:把确定性留给系统,把不确定性关进笼子
模型调用有三个不确定性:可能慢、可能错、可能贵。工程上的应对是把这三件事都用兜底机制包起来。
缓存分两层。第一层是结果缓存:把“归一化后的输入 + 模型 + 关键参数”做成 key,命中就直接返回。归一化很重要,大小写、多余空格、标点差异都应该抹平,否则命中率低得可怜。常见的坑是把 temperature 高的生成任务也缓存,那会导致同一个输入永远返回同一个“创意”,用户会觉得呆板,所以缓存只用在确定性任务上。第二层是提示词前缀缓存:把稳定不变的部分(系统提示、工具定义、few-shot 示例)放在消息数组最前面,变动的内容放最后。很多模型服务商对相同前缀有缓存优惠,能显著降低重复计费,具体规则看官方文档。
降级策略我一般配三级。一级,主模型超时或报错,自动切到备用模型(通常是更快更便宜的型号),牺牲一点质量保可用。二级,接口彻底不可用,返回兜底文案或者把任务塞进队列稍后重试,不要让用户看到白屏。三级,对质量敏感的场景,模型置信度低(比如结构化输出校验失败两次)就直接转人工,别硬撑。这套东西写起来不复杂,但必须在第一版就有,否则第一次故障就会手忙脚乱。
5. 常见故障排查与成本优化实录
最后一章是我压箱底的部分,都是我或者身边同行真实撞过的墙。故障排查这件事,最有价值的不是“知道所有报错含义”,而是“有一套固定的排查顺序”,不至于看到报错就瞎改。
5.1 报错速查:先看状态码,再看错误类型
我整理了一张表,基本覆盖了日常九成的问题。遇到报错,先按这张表定位大类,再往细节看。
| 现象 | 常见原因 | 处理方式 |
|---|---|---|
| 401 未授权 | 密钥错误、过期、环境变量没加载 | 检查变量是否注入,密钥是否被吊销 |
| 403 禁止访问 | 密钥权限不足、模型未开通 | 确认该密钥有对应模型的调用权限 |
| 404 找不到 | 模型名拼错、接口路径变更 | 对照官方模型列表核对名称 |
| 400 请求错误 | 参数类型错、上下文超长、JSON 模式未提 JSON | 看错误详情字段,逐项核对 |
| 429 限流 | 请求数或 token 数超配额 | 指数退避重试,降低并发 |
| 500 / 503 | 服务端临时问题 | 退避重试,连续失败切备用方案 |
| finish_reason=length | 输出被 max_tokens 截断 | 提高上限或压缩提示词 |
| finish_reason=content_filter | 内容被安全策略拦截 | 检查输入,给用户友好提示 |
| 超时 | 请求过长、网络链路慢、并发过高 | 加超时、拆任务、改用流式 |
排查顺序我固定成四步:先看状态码定大类,再看错误消息里的具体字段,再确认是不是自己参数写错,最后才怀疑模型或服务端。这个顺序能避免大量无效折腾。有一次线上突然大面积超时,团队先怀疑模型服务抖动,查了半天,最后发现是我们自己的连接池配置太小,请求全堵在客户端。这类问题看错误分布就能判断:如果是自己这边的问题,报错通常集中在特定机器或特定时间段。
5.2 效果不稳定:按这四条线排查
“昨天还好好的,今天怎么答得乱七八糟”,这是最难查的一类问题,因为没有报错。我一般按四条线走。
第一条线,输入变了。用户输入里混进了奇怪字符、超长文本、多语言混杂,或者上游数据格式改了。做法是把出问题的请求原文完整记录下来,重放一遍。第二条线,提示词变了。有没有人改了系统提示没跑回归?有没有拼装逻辑在某个分支下漏了字段?第三条线,参数变了。温度是不是被调高了,max_tokens 是不是被压小了导致截断。第四条线,模型侧变化。模型版本更新、别名指向的版本切换,都可能导致行为变化。这条线的应对办法是:能用固定版本号就别用会漂移的别名,重要业务上线前跑一遍回归测试。
说到回归测试,这是我觉得投入产出比最高的一件事。建一个几十条的小测试集,覆盖典型输入和边界输入,每条写好期望的输出特征(不是逐字匹配,而是判断关键字段是否正确)。每次改提示词、换模型、调参数,跑一遍,通过率掉超过阈值就不上。这套东西半天能搭起来,能省掉无数次“上线才发现变差”的返工。
5.3 省钱的实操手法:从账单结构倒推
省钱不是一味换小模型,而是先看账单结构。一般会看到三块:固定提示词的重复消耗、长上下文的历史堆积、无效输出的浪费。对应三种手法。
针对固定提示词,做前缀复用和缓存,能缓存的稳定内容全部前置。针对历史堆积,多轮对话不要无限追加,做滑动窗口加摘要压缩,把早期对话压成一段短摘要,既省 token 又保留关键信息。针对无效输出,收紧 max_tokens,用 stop 词截断,提示词里明确“不要解释、不要重复问题”,这几句话能省掉不少输出 token——输出通常是输入的好几倍单价,这里省得最狠。
还有几个小技巧。用轻量模型做路由:先让便宜模型判断“这个问题简单还是复杂”,简单的它自己答,复杂的转给旗舰模型,整体成本能降不少。批处理任务走异步批接口。few-shot 示例从五个减到两个,往往效果差别不大但输入短了一截。还有一条经常被忽略:别在每次请求里重发整个知识库,用检索先召回相关片段,只送最相关的那几段进去,这一条能把输入 token 砍掉一个数量级。
最后分享一个个人的教训。我曾经为了“效果更好”把系统提示写到两千多字,塞了大量规则和示例,短期看效果确实稳,但跑了一个月账单吓人。后来把这些规则重新拆解,一部分变成结构化校验代码,一部分精简成三句话,效果只掉了一点点,成本降了大半。能用代码约束的,别用提示词约束,这句话我贴在工位上。
这个内容后续还能往几个方向扩:一是把评估体系做扎实,用人工标注建立基准集,做模型之间的横向对比;二是研究多模态输入,比如票据识别、图表理解;三是把调用链路做成可观测的,记录每次请求的输入输出、延迟、成本、成功率,做到问题可追溯。真把这套地基打好,后面加什么功能都是往上叠砖的事。