
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. 动手搭一个最小可用 Harness4.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, temperatureNone, max_tokensNone) - tuple[str, CallRecord]: cfg self.cfg rec CallRecord( trace_idhashlib.md5(str(time.time_ns()).encode()).hexdigest()[:12], featurefeature, modelcfg.model, prompt_versionprompt_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, timeoutcfg.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_length1, max_length40) years: int Field(ge0, le60) skills: list[str] Field(default_factorylist, max_length30) 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 的价值不在写得多漂亮而在它能不能让你在凌晨两点被告警叫醒时五分钟内从日志里看出问题出在哪一层。朝着这个目标去设计方向就不会偏。