☰
DeepSeek Harness 实战:从裸调接口到生产级封装
2026/10/23 8:20:09 网站建设 项目流程

DeepSeek Harness 这个词最近被问得特别多,问法几乎一模一样:模型我天天用,Harness 又是个啥,跟我直接开个对话框提问到底差在哪?先把话说透——Harness 不是某个新模型,也不是一个必须安装的软件包,它指的是你把模型能力接进自己业务时,围在模型外面那一整圈“驾驭层”:请求怎么发、超时怎么退、输出怎么验、成本怎么算、效果怎么评,全归它管。裸调一行接口调用跑个 Demo 没问题,一旦要接进生产流程,缺了这层壳,问题会像开闸一样涌出来。这篇写给我带过的几个项目里真实踩过的坑,也写给刚准备把 DeepSeek 系列模型接进自己工具链的朋友。全文覆盖四种使用途径的取舍逻辑、一套必装插件清单、从三十行到三百行的搭建过程,以及一份排查速查表。零基础的能照着抄,有经验的能对着捡漏。

1. 先把 Harness 这个词掰开,别被名字唬住

1.1 模型是发动机,Harness 是底盘加变速箱

我习惯用开车打比方。DeepSeek 的模型能力相当于发动机,扭矩多大、油耗多少,这是模型本身决定的。但你真要把车开上路,光有发动机不行,得有底盘、变速箱、刹车、仪表盘。Harness 就是这一整套东西:它负责把用户一句含糊的需求翻译成模型能吃的提示词,负责把模型吐出来的自由文本裁成程序能用的结构,负责在接口抽风时自动重试而不是让用户看到报错,负责记录每一次调用花了多少 token 以及这次回答到底好不好。

很多人对 Harness 的误解在于,以为它是个可以下载的成品。实际上它更像一份“工程约定”,你可以用几十行代码实现最简版,也可以用一整套开源框架拼出复杂版,核心判断标准只有一个:模型输出的不确定性,有没有被你的外层代码兜住。兜住了就是 Harness,兜不住就只是一次接口调用。这个区别在你只做个人玩具时几乎无感,但当你把模型接进订单处理、客服回复、代码审查这些有明确输入输出契约的环节时,差距立刻显形。

我在项目里给 Harness 划的边界很清晰:它不碰业务逻辑,只做五件事——请求编排、输出约束、异常兜底、成本记账、质量回归。任何超出这五件事的需求,都应该放到业务层去,别往 Harness 里塞。这个边界守住了,你的封装层才不会在三周之后变成一团谁都不敢改的泥巴。

1.2 裸调接口走不远:三个我亲历的翻车现场

第一个现场是输出格式。当时做一个简历信息抽取,提示词写了“请以 JSON 返回”,本地测了二十条全对,上线第二天开始零星报解析失败。排查发现模型偶尔会在 JSON 前后加一句“好的,以下是提取结果:”,或者把某个字段的值写成中文全角的引号。单看每次失败都不致命,但解析器一抛异常整条流水线就断。后来我们在外层加了强约束和“解析失败就带错误信息重试一次”的逻辑,失败率从百分之三降到千分之二以下。

第二个现场是重试放大。有个批量任务调了两万次接口,中途遇到限流,我图省事在循环里写了while True加重试。结果限流期间所有请求同时重试,把并发推得更高,反而更难恢复,最后整批任务跑了四个小时,账单比预估高出将近一倍。那次之后我强制要求所有重试必须带指数退避加随机抖动,且必须有次数上限。

第三个现场是效果漂移。同一个提示词,月初跑出来的分类准确率是百分之九十,月底掉到八十二。翻日志才发现,中间有人为了“让回答更自然”,把温度参数从 0.1 改成了 0.7,改动没有任何记录。这就是为什么我后来坚持把提示词、参数、模型版本三者打包成一个“配置指纹”,写进每一次调用的日志里。

提示:这三个坑的共同点是——它们都不是模型能力问题,全是外层工程缺失。判断你需不需要 Harness,看这一个问题就够了:模型出错时,你的系统是自动兜住还是直接把错误抛给用户。

2. 四种使用途径怎么选,一张表看清差别

2.1 途径一:对话式客户端,零代码先把感觉摸出来

这条路径成本最低,打开就能用,适合三类人:还没想清楚要做啥的探索期、需要快速验证某个提示词效果的调优期、以及只想拿模型当日常助手的轻量用户。我自己的习惯是,任何新需求先用对话界面手搓十轮,看看模型在这个任务上的上限在哪,再决定要不要写代码。

具体怎么用出效率,有几个我摸索出来的动作。第一是把长提示词存成文本文件,每次粘贴进去,而不是每次重新手打,这样你能保证测试条件一致。第二是开两个会话窗口做 A/B,同一个问题用两种问法各问一遍,对比差异,这比事后看日志直观得多。第三是遇到复杂任务时,先用一句话让模型复述你的需求,确认它理解对了再往下走,能省掉大量返工。

这条路径的天花板也很明确:无法批量、无法嵌入、无法自动记录。一旦你的需求变成“每天处理三千条”或者“嵌入到我现有的表单提交动作里”,就必须换路。我见过不少团队在对话界面里手工处理了几百条数据才想起来该写脚本,那几百条的手工时间换算成人力成本,够写好三个版本的封装了。

2.2 途径二:接口直连,程序化集成的最短路径

这是绝大多数工程场景的主力路径。DeepSeek 的接口走的是业界通用的兼容协议,你手上现成的 SDK、现成的调用范式基本都能直接迁移,学习成本很低。典型写法就是指定服务地址、填入密钥、选好模型 ID,然后发一条对话消息过去。

这条路径的优势在于可控:超时你自己设、重试你自己写、并发你自己管、返回的用量统计你能拿到。劣势同样明显——可控意味着什么都得自己管。我见过有团队直接把密钥硬编码在前端代码里,打包上线之后被人扒出来刷了几万次调用。这不是模型的问题,是工程纪律的问题。密钥必须放服务端,前端一律走自己的后端转发,这条没有例外。

还有一点常被忽略:兼容协议不等于行为完全一致。不同模型对同一个参数的解释可能不同,比如某些模型对温度的取值范围、对系统消息的处理方式、对最大输出长度的默认值都有细微差别。我的做法是写一层薄薄的适配层,把模型相关的差异全部收敛到一个配置文件里,业务代码只认统一入参,换模型时只改配置不动业务。这个适配层大概一百行,但它能在你换模型或者换供应商时救你一次大重构。

2.3 途径三:本地权重部署,数据不出内网的场景

当你的输入包含不能外发的材料——合同条款、内部代码、用户隐私数据——把数据发到外部接口这件事本身就得先过合规评审。这时候本地部署开源权重就是唯一选择。常见做法是用推理框架把权重跑起来,对外暴露一个与通用协议兼容的端点,这样上层的 Harness 代码一行都不用改,只改服务地址就行。

这条路的技术门槛集中在三块:显存够不够、吞吐扛不扛得住、量化精度损不损。显存要按模型参数量和精度算,粗略估法是每十亿参数在十六位精度下约需两到三个单位的显存,再加上上下文缓存的开销。吞吐方面,单卡并发跑满之后延迟会明显上扬,我一般会把并发闸门设得比理论值低三成,留出余量。量化方面,八位量化在多数抽取类任务上几乎无损,四位量化在需要精细推理的任务上会掉点,建议用你自己的评测集实测过再决定。

还有一个隐形成本:本地部署意味着模型版本由你自己维护,安全更新、精度修复都得跟。我在一个项目里吃过亏——本地跑的权重比线上版本落后了两个月,同一个提示词在两边的表现差了将近十个百分点,团队花了三天才定位到是版本差异。所以本地部署一定要在配置文件里写死版本号,并且在日志里打出来。

2.4 途径四:编辑器与工作流插件,把模型塞进日常动线

第四条路最容易被忽视,但对个人效率提升往往最大。它指的不是某个具体产品,而是一类形态:把模型能力做成编辑器里的补全、终端里的命令、或者自动化流程里的一个节点。你在写代码时它帮你补测试,你在整理文档时它帮你做格式转换,你在处理表格时它帮你批量清洗。

这类集成的关键判断标准是“打断成本”。一个好的集成,你应该感觉不到它的存在——光标停在那里它就给出建议,按一个键就采纳。一个糟糕的集成,你需要切窗口、复制、粘贴、等待、再切回来,那还不如直接开对话界面。我在选工具时会专门测这一点:从产生需求到拿到结果,需要几次上下文切换。超过两次的基本可以放弃。

工作流形态的集成则要注意幂等性。自动化流程里同一个任务可能因为上游重跑而执行两次,如果你的模型调用有副作用(比如直接写库、直接发消息),重跑就会产生脏数据。我的做法是所有模型调用只产出结果不落库,落库动作交给下游的幂等写入逻辑,中间用任务 ID 做去重键。

2.5 四条路径的对照与混用策略

途径上手成本适合场景主要短板我的建议用法
对话式客户端极低需求探索、提示词调优、零散任务无法批量、无法嵌入任何新需求的第一步
接口直连中批量处理、业务系统集成什么都得自己管生产环境主力
本地部署高数据敏感、离线环境显存与运维成本合规硬约束才选
编辑器与工作流低到中个人效率、日常动线能力受插件边界限制和接口直连配合用

实际项目里这四条路很少单选。我现在的常见组合是:用对话式客户端做前期探索和提示词打磨,把定稿的提示词搬进代码仓库;生产流量走接口直连加自建 Harness;本地部署作为敏感数据的兜底通道,走同一套 Harness 代码,只切服务地址;日常开发时用编辑器集成提效。四条路共用一套提示词和一套评测集,这样切换成本最低,也不会出现“线上效果和本地测试不一致”的经典问题。

3. 必装清单:这七件东西少一件都难受

3.1 第一层:请求治理,先让调用不把自己搞死

这一层包含四件事:超时、重试、限流、并发闸门。超时是最基础的,但很多人只设了连接超时没设读取超时,结果遇到模型长时间不返回时线程就一直挂着。我的习惯是分开设:连接超时给短一点,读取超时按任务复杂度给,简单分类任务三十秒够,长文档生成给到一百二十秒。

重试必须是带退避的。指数退避的算法很简单:第 n 次重试等待 base 乘以 2 的 n 次方,再叠一个随机抖动。基础间隔设一秒,重试四次的话等待序列大致是一秒、两秒、四秒、八秒,加抖动后总耗时控制在十五秒以内。为什么要加抖动?因为限流往往是批量触发的,如果所有客户端都按同样的节奏重试,就会形成一波一波的冲击。抖动的作用是把这波冲击摊平。

限流和并发闸门解决的是不同问题。限流控制单位时间内的请求数,防止你超过服务方的配额;并发闸门控制同时在飞的请求数,防止你自己的资源被打爆。两个都要有,用信号量实现并发闸门,用令牌桶实现限流,加起来不到五十行代码。

注意:重试只对可恢复错误生效。参数错误、鉴权失败、内容被拒这类错误重试一百次也没用,只会放大账单。判断依据通常是状态码区间,四开头的多数不该重试,五开头和超时可以重试,但也要给总次数上限。

3.2 第二层:输出契约,把自由文本驯成结构化数据

模型输出的是自然语言,你的程序需要的是字段。中间这道转换就是输出契约层,它包含三件事:格式声明、解析校验、失败补救。格式声明靠提示词和结构化输出模式,现在多数接口都支持强制返回合法 JSON 的模式,能开就开,这是最省事的一步。

解析校验要落到具体的类型系统上。Python 里用数据模型类做校验是最顺手的做法:先按 JSON 解析,再喂给模型类做类型和范围检查,字段缺失、类型不对、枚举值越界全在这一步暴露。这一步的收益在于,它把“模型偶尔抽风”这种模糊问题变成了具体的错误类型,你能统计出到底是哪个字段最容易出问题。

失败补救的策略我一般设两级。第一级是带错误信息重试一次,把解析错误的具体内容拼进提示词,让模型自我修正,这一级能救回大半问题。第二级是降级到宽松解析,比如用正则兜底抽关键字段,或者直接返回空值并打标,让下游逻辑知道这条数据不可信。最忌讳的是解析失败就抛异常终止整批任务,两万条里有一条格式不对就全废,这在生产环境是不可接受的。

3.3 第三层:可观测性,没有日志的优化都是瞎猜

这一层我觉得是整个 Harness 里性价比最高的部分,但也是最容易被跳过的。最少要记五样东西:请求的唯一标识、时间戳、模型 ID、输入输出 token 数、耗时。再往上加就记提示词版本号、参数快照、返回的结束原因、以及业务侧的任务 ID。

结束原因这一项特别值得单独说。它告诉你模型是正常结束、还是因为达到输出长度上限被截断、还是因为内容策略被拦。这三种情况在日志里长得几乎一样,但处理方式完全不同。我遇到过一次线上事故:某批任务的输出全部被截断,但因为下游有兜底逻辑,表面上没报错,只是结果质量莫名下降。翻了半天日志才在结束原因字段里看到全是长度截断,原因是有人把输出上限从两千改成了五百。

追踪链路也值得做。一次业务请求可能触发多次模型调用(先分类、再抽取、最后校验),如果每次调用都是独立日志,排查时就得靠时间戳拼图。给整条链路一个统一的追踪 ID,所有子调用都带上它,排错效率能提升一个数量级。

3.4 第四层:成本账本,钱是怎么漏的一点都不神秘

成本账本的本质是把 token 消耗和业务动作关联起来。只统计总消耗量意义不大,你需要知道“哪个功能最费钱”“哪个用户的调用最贵”“哪次改版之后成本涨了”。我的做法是在每次调用的日志里带上功能标识,然后按天做聚合,出一个简单的报表。

计算逻辑不复杂。输入 token 乘以输入单价,加上输出 token 乘以输出单价,就是单次成本。批量场景下再乘以调用次数。举个估算例子:一次抽取任务,提示词约八百字中文(大致折合五百到八百 token),输出约两百 token,如果每天跑五千次,日消耗的输入 token 就是两百五十万到四百万量级。这个数量级下,把提示词从八百字压到五百字,节省的比例非常可观。

压缩提示词是最便宜的优化,但要小心压过头。我压提示词的顺序是:先删掉重复的礼貌用语和解释性铺垫,再合并同义指令,然后把长示例换成短示例,最后才考虑精简规则本身。每压一轮都跑一遍评测集,确认准确率不掉再继续。经验是提示词能压掉三成左右而效果基本不变,再往下压就开始掉点了。

3.5 第五层:缓存与提示词版本管理

缓存放两层。第一层是精确缓存,把提示词加参数的哈希值当键,命中就直接返回,适合那些高频重复的固定问题。第二层是语义缓存,把历史问题的向量存下来,新问题进来先做相似度检索,超过阈值就复用答案。语义缓存要慎用,阈值设低了会返回似是而非的答案,我在分类任务上用过,阈值设到很高才敢上线。

提示词版本管理听起来像流程问题,实际上是技术问题。我的做法是把提示词写成文件放进代码仓库,文件头写清楚版本号、修改原因、修改人、以及对应的评测结果。代码里引用提示词时用版本号而不是路径,这样任何一次线上调用都能追溯到具体是哪个版本的提示词。这个习惯救过我一次:某次效果下滑,直接从日志里翻出版本号,diff 一下发现是有人删了一句关键的约束条件。

评测集要和提示词一起管。规模不用大,三十到五十条覆盖主要场景和边界情况就够,但必须固定。每次改提示词、换模型、调参数,都拿这套集子跑一遍,看准确率变化。我在一个分类项目里攒了六十条评测样本,两年下来拦住了至少五次“看起来是优化实际是退化”的改动。

4. 动手搭一个最小可用 Harness

4.1 目录结构与依赖选择

我不喜欢一上来就上框架,先手写一遍再决定要不要抽象。最小版本我按这个结构组织:一个配置目录放模型参数和提示词文件,一个客户端模块放调用封装,一个契约模块放输出校验,一个账本模块放成本统计,再加一个评测脚本。依赖只用最基础的几样:发请求的库、做数据校验的库、做日志的库,其余能不加就不加。

配置与代码分离这件事必须从第一天做起。我见过太多项目把模型 ID、温度、输出上限散落在十几处代码里,改一个参数要全局搜索半小时。把这些全部收进一个配置文件,代码里统一读配置,是唯一能长期维护的做法。配置文件里至少要包含:服务地址、模型 ID、温度、最大输出长度、超时秒数、重试次数、并发上限。

密钥的处理单独说一句。放在环境变量里,不写进配置文件,更不写进代码仓库。配置里只写环境变量的名字,运行时去读。这个习惯看起来小题大做,但在多人协作的项目里,它是唯一能防止密钥泄露的可靠做法。

4.2 核心代码:带重试、计费、日志的调用封装

下面这段是我现在项目里在用的简化版,去掉业务定制之后大概就是这个骨架。

# harness/client.py import os, time, random, hashlib, logging from dataclasses import dataclass, field from typing import Optional logger = logging.getLogger("harness") RETRYABLE_STATUS = {429, 500, 502, 503, 504} @dataclass class CallRecord: trace_id: str feature: str model: str prompt_version: str prompt_tokens: int = 0 completion_tokens: int = 0 latency_ms: int = 0 finish_reason: str = "" retries: int = 0 cached: bool = False class HarnessClient: def __init__(self, cfg, transport): self.cfg = cfg # 模型参数配置 self.transport = transport # 实际发请求的函数,便于测试替换 self.semaphore = __import__("threading").Semaphore(cfg.max_concurrency) def _backoff(self, attempt: int) -> float: base = self.cfg.retry_base delay = min(base * (2 ** attempt), self.cfg.retry_cap) return delay + random.uniform(0, delay * 0.3) def call(self, messages, feature, prompt_version, temperature=None, max_tokens=None) -> tuple[str, CallRecord]: cfg = self.cfg rec = CallRecord( trace_id=hashlib.md5(str(time.time_ns()).encode()).hexdigest()[:12], feature=feature, model=cfg.model, prompt_version=prompt_version, ) payload = { "model": cfg.model, "messages": messages, "temperature": cfg.temperature if temperature is None else temperature, "max_tokens": cfg.max_tokens if max_tokens is None else max_tokens, } last_err: Optional[Exception] = None for attempt in range(cfg.max_retry): started = time.monotonic() try: with self.semaphore: resp = self.transport(payload, timeout=cfg.timeout) rec.latency_ms = int((time.monotonic() - started) * 1000) rec.retries = attempt usage = resp.get("usage", {}) rec.prompt_tokens = usage.get("prompt_tokens", 0) rec.completion_tokens = usage.get("completion_tokens", 0) rec.finish_reason = resp["choices"][0].get("finish_reason", "") if rec.finish_reason == "length": logger.warning("输出被截断 trace=%s feature=%s", rec.trace_id, feature) logger.info("call done %s", rec) return resp["choices"][0]["message"]["content"], rec except Exception as e: status = getattr(e, "status_code", None) last_err = e logger.warning("call failed attempt=%d status=%s trace=%s", attempt, status, rec.trace_id) if status is not None and status not in RETRYABLE_STATUS: raise if attempt == cfg.max_retry - 1: raise time.sleep(self._backoff(attempt)) raise last_err

这段代码里有几个设计取舍值得说明。第一,transport是注入的,好处是测试时可以塞一个假函数进去,不用真的发请求,这对写单元测试非常关键。第二,信号量包在transport外层,保证并发控制对重试同样生效——我见过把信号量放在循环外面的写法,重试时会绕过并发限制。第三,截断告警单独打出来,因为这个信号太容易淹没在普通日志里。

4.3 输出契约:把 JSON 校验做成一道闸门

模型返回文本之后,第一件事不是直接用,而是过校验。用数据模型类定义期望结构,字段名、类型、取值范围、必填与否全部声明清楚,解析失败就带着错误信息重试一次。

# harness/contract.py import json from pydantic import BaseModel, Field, ValidationError class ResumeInfo(BaseModel): name: str = Field(min_length=1, max_length=40) years: int = Field(ge=0, le=60) skills: list[str] = Field(default_factory=list, max_length=30) def extract(content: str, client, prompt_version: str): messages = build_messages(content) # 你的提示词组装逻辑 text, rec = client.call(messages, "resume_extract", prompt_version) try: return ResumeInfo.model_validate_json(text), rec except (ValidationError, json.JSONDecodeError) as e: fix = messages + [ {"role": "assistant", "content": text}, {"role": "user", "content": f"上面的输出解析失败:{e}。请只返回修正后的合法 JSON,不要任何解释。"}, ] text2, rec2 = client.call(fix, "resume_extract_fix", prompt_version) return ResumeInfo.model_validate_json(text2), rec2

这里的重试和网络层的重试是两回事,必须分开计数。网络重试是“请求没成功”,解析重试是“请求成功了但结果不合格”,两者的监控指标要分开看。我一般把解析重试率控制在一成以内,超过这个比例说明提示词该重写了,别指望靠重试救。

校验的松紧度也需要拿捏。约束写得太死,模型稍微换个表达就判失败,重试率飙升;写得太松,脏数据就漏到下游。我的经验是只对关键字段做强约束,对可选字段用宽松类型加默认值。像上面例子里的技能列表,允许为空,长度上限设宽一点,避免因为多列了几项就整条报废。

4.4 加一层缓存,把重复的钱省下来

缓存实现最简单的就是字典加文件落盘,键用提示词、参数、模型 ID 三者拼接后的哈希值。写的时候注意两点:一是把模型 ID 和提示词版本号也拼进键里,否则换了模型或改了提示词还在命中旧缓存,那是最坑的 bug;二是给缓存加过期时间,我一般设七天,再长就容易命中过时的业务数据。

命中率是最值得盯的指标。如果你的业务里有大量重复请求(比如每天处理同一批固定格式的表单),命中率能到三成以上,效果立竿见影。如果命中率长期低于百分之五,说明你的场景本身不适合精确缓存,别硬上,转去优化提示词长度更划算。

语义缓存要单独说一句。它的收益诱人,但风险也大。我的做法是先在小范围灰度,把相似度阈值设得非常高,并且记录所有“被缓存命中”的请求和原始请求,人工抽查一批看看是否合理。抽查通过率不到百分之九十五就别开。分类、意图识别这类任务相对安全,开放式问答慎用。

4.5 参数怎么定:几个关键值的取值逻辑

温度是最需要按键任务定的参数。抽取、分类、格式转换这类要求确定性输出的任务,我一般给零到零点二;改写、润色、生成文案给零点六到零点八;头脑风暴、起名字这类发散任务才给到一点零以上。同一套系统里不同任务用不同温度是完全正常的,别图省事全局设一个值。

输出长度上限要按任务留余量。经验算法是:先不设上限跑二十条样本,统计实际输出长度的最大值,然后把这个最大值乘以一点五作为上限。留一点五倍是因为模型偶尔会在末尾多说两句,卡得太死就会触发截断。前面说过,截断在日志里不明显,但会实打实降低结果质量。

并发上限的决定因素是下游能承受多少。如果你的接口有明确配额,按配额的八成设。如果配额不明确,从个位数开始压测,每次翻倍,观察延迟曲线,延迟开始明显上扬的那个点的前一档就是合理值。我在一个项目里从二开始测,测到十六时延迟翻了三倍,最后定在八,跑了一个月很稳。

超时值按任务分档。简单分类三十秒,中等长度的生成六十秒,长文档处理一百二十秒。设得太短会导致大量无谓的重试,设得太长会让故障暴露变慢。还有个细节:读超时应该略大于你预期的最大生成时间,别和连接超时设成一样。

5. 踩坑实录与排查速查表

5.1 现象、原因、定位路径的对照表

现象大概率原因先看哪里处理动作
输出被莫名截断输出上限太小日志里的结束原因字段按实际长度统计值的一点五倍重设
JSON 解析零星失败模型加了说明文字 / 温度偏高失败样本的原始返回开结构化输出模式,温度降到零点二以下
批量任务越来越慢重试风暴或并发过高重试次数的分布加指数退避与抖动,收紧并发闸门
账单比预估高很多重试放大 + 上下文重复携带按功能聚合的 token 消耗分别治理,压缩提示词
同一问题答案差异大温度高且未固定随机种子参数快照关键任务把温度压到零附近
长文档回答质量骤降上下文超限被静默截断输入 token 是否顶到上限改成分块处理加结果汇总
首字延迟明显未开流式输出请求参数交互场景一律开流式
换模型后效果下滑参数语义差异未适配适配层配置按新模型重跑评测集再上线

5.2 成本失控的五个隐形漏斗

第一个漏斗是重试放大。每一次失败重试都是一次完整计费,如果重试率是百分之十,你的成本就凭空多了百分之十。更糟的是失败往往发生在长提示词上,那部分 token 最贵。治理办法很简单:把重试率当成核心指标监控,超过百分之五就得查原因。

第二个漏斗是上下文重复携带。多轮对话场景里,很多人每一轮都把完整历史发过去,聊到第十轮时输入 token 已经是第一轮的好几倍。治理办法是做历史压缩:保留最近几轮原文,更早的轮次摘要成一段话,或者只保留关键实体。这一项优化下来,长对话场景的成本能降一半以上。

第三个漏斗是提示词膨胀。项目迭代几轮之后,提示词里塞满了各种“补充说明”“特殊情况处理”“请务必注意”,很多条目其实互相重复甚至矛盾。我每季度会做一次提示词体检,逐条问“删掉这条会不会影响评测结果”,删完重跑评测集,通常能砍掉两到三成。

第四个漏斗是缓存失效。改了一个参数导致所有缓存键变化,命中率瞬间归零,但因为没人盯这个指标,团队完全无感。所以缓存命中率必须进日报。

第五个漏斗是模型选型错配。用最强的模型去做最简单的分类,成本可能是合适模型的好几倍。我的做法是按任务分级:简单任务用轻量模型,复杂任务才上大模型,中间加一层路由判断。路由本身可以用规则实现,不必再调一次模型。

5.3 效果忽好忽坏的三个根因

第一个根因是输入分布漂移。你的评测集是三个月前攒的,业务场景已经变了,模型在新输入上的表现自然下滑。治理办法是定期从线上抽样补充进评测集,保持评测集和真实分布同步。我一般每月补十条。

第二个根因是提示词被悄悄改动。前面提过,解法是版本化管理加配置指纹。这件事只有零次和无数次,只要团队超过两个人,就必须做。

第三个根因是上下文污染。多轮对话里,前面某轮的错误回答会被后续轮次当成事实继续推理,越错越远。解法是在关键节点做一次“事实核对”,把已确认的信息单独抽成一个结构化的状态对象,每轮都把它注入提示词,而不是依赖模型自己从历史里回忆。

提示:排查顺序永远是先看日志再看代码。我见过太多人凭直觉改代码,改了三版问题还在,最后一看日志发现是上游传了空字符串进来。日志里没有的信息,就是你没观测到的信息,没观测到的问题基本定位不了。

6. 几条我自己踩出来的经验

提示词一定要进代码仓库,这条我重复多少遍都不嫌多。散落在聊天记录、文档、便签里的提示词,等于没有版本控制,出了问题无从回溯。

日志和账本要在写第一行业务代码之前就做好。事后补日志的代价,是你永远缺那一段最关键的历史数据。

评测集哪怕只有三十条,也比拍脑袋强。它的作用不是证明你做得对,而是在你改坏的时候拦住你。

流式输出对体验的影响被严重低估。同样的总耗时,开了流式用户感觉快了一倍,交互类场景没有理由不开。

别指望一次调用解决所有问题。把复杂任务拆成“分类、抽取、校验”三个小步骤,每步用简单提示词,总成本和总延迟往往比一次巨型调用更低,出错也更好定位。

最后一条,也是我花时间最久才接受的:Harness 的价值不在写得多漂亮,而在它能不能让你在凌晨两点被告警叫醒时,五分钟内从日志里看出问题出在哪一层。朝着这个目标去设计,方向就不会偏。

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

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

立即咨询