直接从实战场景聊起吧。我前阵子接了一个内部智能体项目,任务听起来很简单:给用户提交的内容做一次准确度评估。一开始我直接让大模型“自由发挥”,输出一段话来说明它怎么判断。结果很快就被打脸了——我拿到了几十种不同风格的“判断文本”,有的写一大段,有的只写一个词,有的还把结论藏在第二段第三行。为了把这个自由文本消化进下游系统,我不得不写一堆脆弱的解析逻辑,正则、关键词匹配、JSON提取全上,还是防不住漏判和误判。后来我彻底换了个思路:不让它生成文本,只让它做判断。所有输出收敛到严格定义好的结构里,用类型安全的方式把事情办扎实。这篇文章就记录我这次“TypeSafe Agent Skill”接入的全过程,包括为什么这么选、怎么做、中间踩了哪些坑,希望对正在折腾Agent落地的你有参考价值。
我花了大概两天半重构这个能力,核心改动只有一件事:把“让模型写作文”改成“让模型做选择题+填结构化字段”。就这么一个简单的转换,后续下游的解析代码从一百多行缩到了不到十行,线上误判率也好了不止一个量级。这个经验让我觉得特别值得展开说说——因为它不是某个项目的偏方,而是所有想用LLM做判断、分类、审核、风险评估的人都绕不开的设计问题。
1. 项目思路拆解:判断型任务为什么不能用“文本生成”思路来做
首先要明白一个前提:大语言模型本质上是文本生成器,它按概率预测下一个token。但你跟它说“请判断这段内容是否合规”,如果你不约束输出格式,它给出的“判断”本质上仍然是一段文本。文本就有无限种合法的写法,这对程序来说是个灾难。
1.1 需求定位:我要的不是“一段话”,而是一个可执行的结论
在项目最开始,需求方说得很含糊:“让AI帮忙判断一下提交内容的有效性。”第一个版本我照着字面意思做,让模型输出判断结果文本,类似:
“这是一段可用的内容,因为它的格式正确,不过有些细节还需要补充。”
表面上看这件事完成了,但仔细想就会发现,下游根本没法直接用这句话。我需要的是:有效还是无效,若无效属于哪一类,置信度多少。这些是程序可以消费的数据点,而不是给人类看的一整句评语。
做工程的人应该都知道一句话——接口的返回值决定了一个系统的气质。如果你把LLM当一个“文本接口”来用,那你整个下游都是在跟不可控字符串搏斗。而如果你把它当成一个“结构化决策接口”来用,你就有机会获得和普通API一样稳定的返回。
1.2 设计取舍:用约束代替自由,用类型代替文本
我这次的核心设计原则可以归纳成三句话:
- 判断结果必须是枚举值,不能是自由字符串。有效、无效、需人工复核,这几种状态写死,模型只能从中选一个。
- 辅助字段必须结构化。比如不合规的原因分类,也定义成枚举;置信度用0到1的数值;唯一允许自由文本的字段只有“备注”,并且明确标注它不是程序逻辑依赖的一部分。
- 校验层必须做两次。模型输出的JSON先按定义好的Schema解析,解析完再对枚举字段做合法性检查,两项都过才算一次成功的判断。
这套思路执行下来,效果是立竿见影的。最明显的变化是:原来一周内我在日志里能翻出七种不同格式的“判断结果”,重构之后,所有返回值的结构完全一致,日志直接可以拿去做统计展示,几乎零成本。
1.3 为什么选择“Skill”的封装方式
这个判断能力不是孤立存在的,它要被多个上游场景调用。有的场景是内容审核,有的场景是问卷答案质量评估,有的场景是用户意图分类。我不希望每个场景都在自己的代码里写一段提示词、然后各自解析返回值,这样迟早会失控。
所以我把它封装成一个独立的Agent Skill。Skill在这套架构里不只是一个提示词模板,它包含:
- 明确的输入Schema;
- 明确的输出Schema;
- 预设的系统提示词;
- 后处理与校验逻辑;
- 一段给Agent看的“技能说明”(什么时候该调用它、传入什么参数、拿到什么结果)。
这样做的直接好处是:调用方不用关心这个判断是怎么实现的,他们只需要按照输入Schema传参,然后从输出Schema里取字段。整个判断逻辑的升级、提示词调优、校验规则变更,都可以收敛在这个Skill内部,不会影响上游调用方。
2. TypeSafe Skill 的核心设计:从Schema到代码的双重保障
TypeSafe这个词这几年在工程圈很热,但在LLM应用里真正做到的不多。大多数项目所谓的结构化输出,只是让模型返回一个JSON字符串,然后json.loads()一下,取不取得到字段全靠运气。TypeSafe意味着从运行时数据到代码类型是一一对应的,今天我要展开说的就是这个层面的设计。
2.1 输入Schema设计:把问题的边界预先画清楚
判断型Skill的输入不复杂,核心字段如下:
from pydantic import BaseModel, Field from typing import Literal, Optional class JudgmentInput(BaseModel): content: str = Field(..., description="待判断的内容,最长不超过2000字") scenario: Literal["content_review", "answer_eval", "intent_cls"] = Field( ..., description="业务场景标识,影响系统提示词的选用" ) require_reason: bool = Field( True, description="是否要求模型输出结构化判定理由" )你可能注意到我特意加了scenario这个字段。同一个Skill要支撑多个场景,最简单粗暴的做法是把所有提示词塞在一起让模型自己理解,但这样效果很差。更好的做法是显式声明当前是哪个场景,然后Skill内部根据这个字段切换不同的提示词子模板。
这背后的逻辑是:给模型的信息越精准,模型的判断越稳定。你给它一个明确的场景标签,比让它从一段冗长的说明里自行推断任务背景要可靠得多。这就是输入侧类型安全的意义——它不只是给程序看的,也是给模型看的。
2.2 输出Schema设计:把判断结果收敛成不可逃逸的结构
输出Schema是整个重构的核心,我最终定稿的版本是这样的:
class JudgmentOutput(BaseModel): verdict: Literal["valid", "invalid", "need_review"] = Field( ..., description="最终判断结论,三选一" ) invalid_type: Optional[Literal["spam", "low_quality", "off_topic", "format_error", "other"]] = Field( None, description="仅当verdict为invalid时必须指定,否则为null" ) confidence: float = Field( ..., ge=0.0, le=1.0, description="模型对判断结论的置信度,0到1之间" ) structured_reason: dict = Field( ..., description="结构化判定理由,包含具体违规点或问题定位" ) brief_note: str = Field( "", description="面向人工复核的简短备注,程序侧不应依赖此字段" )我重点解释几个设计上的细节:
第一,verdict必须是Literal类型。这是类型安全最直接的体现,它从Schema层面限制了模型的“表达自由”。模型只能在这三个值里做选择,不存在第四个选项。如果你让它返回自由字符串,那么“invalid”“Invalid”“INVALID”“无效”都会出现,程序侧就得写一堆归一化逻辑。
第二,invalid_type是有条件必填的。我用Optional修饰它,但在业务规则上要求:当verdict=invalid时,这个字段必须被填充。虽然Pydantic的Schema本身表达不了条件约束,但我之后会在校验层补一道程序级检查。这个“Schema + 程序逻辑”的双重保障,就是TypeSafe能在工程上落地的关键。
第三,structured_reason是一个dict而不是自由文本。很多人会问,为什么判断理由不直接用字符串呢?我一开始也这么写,后来发现一个问题:理由中蕴含的“违规点定位”对下游分析极有价值。比如内容审核场景里,我希望知道到底是哪一段话触发了“低质”判断。如果给一段自由文本,程序无法直接定位;如果定义成结构化字段,比如{"offensive_terms": ["词1", "词2"], "offending_snippet": "原句摘录"},程序就能直接提取并做展示或统计。
这也回答了一个核心问题:TypeSafe并不等于不要自然语言,而是把自然语言限制在它应该存在的位置。对于需要程序消费的字段,用严格类型;对于需要人看的字段,用文本。泾渭分明。
2.3 校验层实现:宁可拒绝,不可带病通过
有了Schema之后,还不能直接信任模型的输出。因为模型是通过文本方式输出JSON的,就算你要求“只输出JSON”,它也可能给出来一个结构合法但语义非法的结果。比如枚举值拼错了一个字母,程序如果直接用就会出问题。
所以我在校验层写了这么一段逻辑:
from pydantic import ValidationError def parse_judgment(raw_text: str) -> JudgmentOutput: # 先暴力抽取JSON片段 try: json_str = extract_json_fragment(raw_text) data = json.loads(json_str) except Exception: raise ValueError("模型输出中未找到合法JSON片段") # 再走Pydantic校验,类型不匹配直接抛异常 try: obj = JudgmentOutput(**data) except ValidationError as e: raise ValueError(f"模型输出Schema校验失败: {e}") # 最后补业务级条件校验 if obj.verdict == "invalid" and obj.invalid_type is None: raise ValueError("verdict为invalid时必须指定invalid_type") if obj.verdict != "invalid" and obj.invalid_type is not None: raise ValueError("verdict不为invalid时invalid_type必须为null") return obj两层校验缺一不可。第一层Pydantic解决“类型对不对”,第二层程序逻辑解决“业务规则符不符合”。我踩过的一个真实案例是:模型返回了{"verdict": "invalid", "invalid_type": null},Pydantic校验是能过的,因为invalid_type本来就是Optional字段,但业务上这个数据是残缺的。没有第二层校验,这条脏数据就会流到下游,而且极难排查。
3. 实操过程:一个TypeSafe Agent Skill的完整接入实录
理论说得再漂亮,不落地都是空谈。下面记录我第一次完整接入这个Skill的实操流程,包括每一步的具体操作、关键代码和当时踩到的问题。
3.1 第一步:注册Skill,梳理它的调用契约
接入Agent之前首先要做的不是写提示词,而是把“调用契约”定义清楚。我使用的是自定义的Skill注册机制,核心思路是给Agent一个函数级的抽象,让Agent可以像调用普通工具一样调用这个判断能力。
@agent_skill.register( name="type_safe_judgment", description="对给定内容做结构化判断,返回类型安全的判定结论。适用于内容审核、答案评估、意图分类等只做判断不生成文本的场景。", input_schema=JudgmentInput, output_schema=JudgmentOutput, ) def type_safe_judgment(req: JudgmentInput) -> JudgmentOutput: prompt = build_judgment_prompt(req) raw_text = llm_chat_completion(prompt, json_mode=True) return parse_judgment(raw_text)这段代码最关键的在于:对Agent侧暴露的是函数签名,而不是自然语言模板。Agent在决定是否调用这个Skill时,只看description和输入输出Schema。它不需要知道内部用了什么提示词,也不需要知道返回结果是怎么解析出来的。它只需要知道:传入JudgmentInput,拿到JudgmentOutput。
这种设计带来的工程收益非常直接:
- 单元测试可以mock整个Skill,而不用调真实模型;
- 如果将来判断逻辑从“调大模型”换成“调规则+模型混合”,调用方无感知;
- 多个Agent可以安全地复用同一个Skill,不担心各改各的提示词导致行为漂移。
3.2 第二步:设计系统提示词,引导模型“选”而不是“写”
TypeSafe Skill的提示词设计和传统的聊天式提示词有明显区别。传统方式会说“请判断这段内容是否有效”,然后期望模型给出回答;我的做法是告诉模型:你的输出将被程序解析,严禁输出与Schema无关的内容。
实际使用的系统提示词结构大概是这样的:
你是结构化内容判断引擎。你的职责是根据输入内容和场景,输出一个符合JSON Schema的判断结果。 你必须严格遵守以下规则: 1. verdict字段只能取"valid"、"invalid"、"need_review"中的一个,不允许出现其他值。 2. 当verdict为"invalid"时,invalid_type必须取枚举值之一,不得留空。 3. confidence必须是0到1之间的浮点数,1.0表示完全确定,0.0表示完全不确定。 4. structured_reason必须是JSON对象,描述判断依据,禁止使用自由文本段落。 5. 禁止在JSON之外输出任何解释、前言、后记。 6. 如果不确定,请选择need_review,不要强行给出valid或invalid。你会发现我把“如果不确定就选need_review”写进了提示词。这是非常重要的一步,因为我观察到一个现象:语言模型在被迫二选一时,即使信息不足也会硬选一个。这在实际业务里是很危险的——本来该人工复核的内容被机器自动放行了。增加need_review这个中间态,相当于给模型一个“认怂”的出口,这恰恰是提升判断可靠性的关键设计。
另外,我还在提示词里做了一次对比性的示例引导。给模型看两个极端样例:一个是“返回了自由文本,程序无法解析”,另一个是“返回了标准JSON,程序秒级消费”。这种对比通常在五轮内就能让模型稳定输出正确格式。总之一句话,提示词的作用不是“说服它写”,而是“约束它选”。
3.3 第三步:接入Agent,完成工具注册与路由
Skill定义好了之后,接入Agent这一步相对机械。我这里用的是比较通用的Agent工具注册机制——Agent有一个工具列表,每个工具都有名字、描述、参数说明、返回值说明。大模型根据用户请求内容决定调用哪个工具,然后把工具返回值作为生成回复的上下文。
接入时有几个容易踩坑的地方:
坑一:给Agent的Skill描述不能太“范”。我一开始写的描述是“对内容进行判断”,Agent经常在该调用的时候不调用,不该调用的时候乱调用。后来改成“当需要判定用户提交的内容是否有效、是否合规、属于哪一类意图时,调用此Skill”,精确了很多。这其实印证了一个经验:Agent的工具调用决策质量直接取决于工具描述的准确性,模糊的描述是智能体幻觉的主要来源之一。
坑二:输出Schema里的中文枚举值在Agent侧容易错乱。我早期版本直接用了中文枚举值,比如有效、无效、需复核,结果Agent在决定路由时,偶尔会把中文值当作实际输出写进回复。后来我改成英文枚举值作为实际数据,中文只作为description里的解释性文本。这样既保证了程序侧的类型安全,也不影响模型对业务含义的理解。
# Agent注册示例 agent.register_tool( name="type_safe_judgment", description="当需要判定用户提交的内容是否有效、是否合规、属于哪一类意图时,调用此Skill。", parameters=JudgmentInput.model_json_schema(), returns=JudgmentOutput.model_json_schema(), )3.4 第四步:完善日志埋点与可观测性
这一点值得单独拿出来讲。TypeSafe Skill的接入,带来最大的好处其实是可观测性大幅提升。因为返回值是结构化数据,我可以在日志里直接把verdict、confidence、invalid_type作为字段输出。这样在监控面板上,我可以实时看到三种结论的分布比例、平均置信度、各业务场景的误判率。
我在日志埋点上做了这样一件事:给每一次判断都生成一个唯一的judgment_id,并把原始模型输出、解析后的结构化结果、校验是否通过、耗时、token消耗全部关联到这一个ID下。
这样做的实际价值是——当你想要复盘某一次误判时,你可以用judgment_id把所有现场数据捞出来,而不是大海捞针地在日志里找“某一段疑似不正常的文本”。这在A/B测试提示词时尤其好用,一比就知道新的提示词到底改没改善判断质量。
4. 工程选型实录:为什么是TypeSafe,而不是其他方案
做这类结构化判断,市面上其实不止一种实现路径。我在动手之前认真评估了三种主流方案,这里把对比过程完整写出来,供你在选型时参考。
4.1 方案A:自由文本生成 + 程序解析(初始方案)
这个方案是大多数人第一反应的做法:让模型自由输出,然后用提示词或程序从文本里解析出结论。
优点:
- 实现成本最低,不需要任何额外约束
- 提示词写起来最简单
- 模型不容易因为格式约束而“发挥失常”
缺点:
- 解析逻辑极其脆弱,一个标点符号变化都可能破坏解析逻辑
- 结论类型不可枚举,下游统计困难
- 模型输出风格变化会导致长期的维护噩梦
我在项目一开始就是这套方案,实测下来的感受是:demo演示没问题,一上生产就各种打补丁。你会陷入“模型换了个措辞 -> 解析逻辑漏判 -> 用户在日志里发现异常 -> 你加班修解析逻辑”的循环。最致命的是,你永远无法保证这周修完下周不会再出同类问题。
4.2 方案B:提示词强制JSON输出 + 基础解析
这套方案往前走了一步,要求模型输出JSON,然后程序用json.loads()解析。
优点:
- 比纯自由文本可解析性大幅提升
- 实现成本依然较低,只多了一句“以JSON格式输出”
缺点:
- 没有Schema约束,程序端拿到了JSON也不清楚字段类型是否可靠
- 模型可能输出多余字段、漏掉必需字段、把枚举值写成自由文本
- 一旦上游改了JSON结构,下游解析代码必须同步改,容易出错
事实上,这个方案只解决了“可解析”的问题,没解决“可靠”的问题。它和生产级的TypeSafe之间隔着一条鸿沟。
4.3 方案C:TypeSafe结构化输出 + Pydantic校验(最终方案)
这是我现在最终落地的方案,核心思路是:用Schema约束模型输出边界,用双重校验确保数据质量。
优点:
- 输出类型与程序代码一一对应,编译器能帮你提前发现错误
- 枚举字段杜绝了“同义不同值”的脏数据问题
- 下游代码只依赖结构,不依赖任何文案措辞,稳定性极高
- 日志与监控天然结构化,统计与复盘成本极低
缺点:
- 需要多写一些Schema定义代码
- 提示词设计需要更精细,要引导模型严格遵循结构
- 对模型指令遵循能力有一定要求,部分较弱模型可能会在结构输出上出状况
最终选择这个方案的核心理由其实只有一条:它能把你从“无限可能性”里解放出来,让你的系统重新变得可预期。工程最怕的东西就是不确定性,LLM本身已经带来了足够多的不确定性,如果连返回结构都不可控,这个系统基本上无法交付给下游使用。
4.4 三种方案对比速查表
我整理了一张表,方便你直接拿去参考:
| 对比维度 | 自由文本生成 | 强制JSON输出 | TypeSafe结构化输出 |
|---|---|---|---|
| 开发成本 | 极低 | 低 | 中 |
| 解析稳定性 | 差 | 中 | 高 |
| 枚举值控制 | 不可控 | 半可控 | 完全可控 |
| 下游消费成本 | 高 | 中 | 极低 |
| 可观测性 | 差 | 中 | 好 |
| 依赖模型能力 | 低 | 中 | 较高 |
| 长期维护成本 | 高 | 中 | 低 |
如果你正在犹豫选哪种方案,我给你的建议是:先想清楚这个技能的输出值会不会被程序消费。如果会,直接上TypeSafe,不要走弯路。如果只是给一个临时脚本用、没有长期维护压力,那方案B也未尝不可。但一旦涉及生产环境、多团队协作或长期迭代,TypeSafe几乎是必选项。
5. 实操中遇到的坑与排查心得
接入过程当然不是一帆风顺的。我整理了自己实际踩过、也帮周围同行排查过的几类问题,每个都附上了应对思路。
5.1 模型“硬编”枚举值,置信度永远满格
这是我在压测时发现的第一个问题。某些业务场景下,模型好像“学坏了”,不管输入什么内容,verdict都是valid,confidence都是0.99。表面上看返回完全合规、类型完全安全,但实际判断质量等于零。
排查思路是这样的:我先把日志里的confidence分布拉出来看,发现大量聚集在0.99附近,这明显不正常。接着抽样检查了valid的比例,远高于人工标注的基线。最后定位到问题出在提示词示例上——我在示例中举的“正确”样例几乎都是valid的,模型学到的不是“判断规则”,而是“回答valid”。
修正方法很直接:
- 重构了示例样本,让三种结论在示例中均衡出现
- 在提示词里显式加了“请勿无理由给出valid;所有判断必须基于输入内容的实际证据”
- 对
confidence低于0.6但verdict为valid的案例,在监控里标黄人工抽检
这里面最值得说的是监控习惯。如果你不把confidence分布当做一个关键指标来盯,这类“模型偷懒”问题可能隐藏很久,直到业务方反馈误判漏判。有了结构化的日志,这一步排查才可以做得如此快速。
5.2 JSON片段紧贴文本,直接json.loads()失败
尽管提示词要求“只输出JSON”,但模型偶尔还是会多带一点前后缀,比如“根据判断结果:”或者“```json表示。这导致json.loads()`直接抛异常。
我排查时的第一个策略不是改解析代码,而是去看哪一种前后缀出现得最频繁。用一周日志统计下来,发现95%的前缀都是固定的两三种模式。于是我先写了一个清洗函数,把这些常见前后缀去掉再解析。这个方案的坏处是——如果模型升级或提示词调整后出现新的前后缀,清洗函数就会失效。
后来我换成了更稳健的方案:不按“去掉前后缀”的思路,而是按“提取第一个{到最后一个}”的暴力提取方案。这样无论前面写了什么废话,只要最终输出里有一块合法的JSON对象,就能提取出来解析。
def extract_json_fragment(raw_text: str) -> str: start = raw_text.find("{"") end = raw_text.rfind(""}") if start == -1 or end == -1 or start >= end: raise ValueError("文本中不存在JSON对象") return raw_text[start:end+1]这个方案的容忍度高得多,配合Pydantic校验,实际效果很稳定。如果你也遇到类似问题,我建议一步到位直接上这种提取式方案,不要在清洗逻辑上花太多时间。
5.3 图文混杂输入导致判断质量骤降
我们的场景里有些输入不纯是文本,可能包含图片或特殊符号。模型对这些混杂内容的判断质量明显下降,特别是当图片承载了关键信息时。
因为我们的核心诉求是“类型安全的判断”,所以做的第一个调整是在输入Schema里增加一个字段,显式标注是否有非文本内容:
has_multimodal_content: bool = Field( False, description="输入内容是否包含图片等非文本元素" )当这个字段为True时,提示词会切换到一个更保守的版本,明确提示模型“如果你无法完整理解非文本内容,请给出need_review结论,不要强行判断”。实际运行下来,这个策略大幅压低了“盲猜”造成的高置信度误判。
这里也印证了一个大原则:类型安全的输出必须以输入侧的充分显式化为前提。输入Schema里定义得越清楚,模型行为就越可控。你不告诉模型“这是多模态内容”,它就会默认按纯文本处理,然后自信地给出一个错误判断。
5.4 Skill返回正常,但Agent最终回复里出现“幻觉值”
这个问题比较隐蔽。Skill本身返回的JudgmentOutput完全合法,但Agent在拿到Skill的返回值后,会基于它生成给用户的最终回复。在这个过程中,Agent偶尔会“记错”工具返回里的字段值,比如把verdict=invalid说成valid,或者自己编造一个invalid_type值。
这个问题的根因不在Skill,而在Agent的“自由生成”环节。它会把工具返回当成一份资料、然后再组织语言,这个过程中它可能篡改事实。
我的处理方式有两个层面:
第一层,在Agent的系统提示词中加一条强约束:“工具返回的结构化字段是客观事实,最终回复必须严格引用,禁止修改或编造任何字段值。”
第二层,在Agent和用户之间增加一个“事实校验层”。该层拿到Agent生成的最终回复后,提取其中的关键结论,与Skill返回的JudgmentOutput做一致性比对。哪怕只是简单的字符串匹配,比如“Agent文本中包含‘有效’但JudgmentOutput.verdict为invalid”,都能拦下一大批幻觉问题。
这里想分享的心得是:TypeSafe的价值不应该止步于Skill内部,而应该延伸到Agent生成最终文本的环节。你只有把“判断结果”的权威数据源握在结构化字段里,才有可能反查和约束Agent的自由文本输出。没有这个权威数据源,你连介入的抓手都没有。
6. 一点来自实战的优化经验
接入稳定之后,我又做了几轮针对性的优化。这些优化不是必须的,但如果你想把判断质量再往上推一档,可以照着试试。
6.1 置信度分桶策略
我观察到原始置信度虽然是连续的,但模型的置信度并非严格校准的——有时0.7和0.9之间没有本质区别。因此我对置信度做了分桶处理,在输出Schema里同时保留confidence原始值和一个衍生字段:
confidence_level: Literal["low", "medium", "high"] = Field( ..., description="置信度分桶:low<0.6,0.6<=medium<0.85,high>=0.85" )这个分桶字段不是模型直接生成的,而是在后处理阶段基于confidence计算出来并写入最终返回。这样下游业务就可以用confidence_level来做粗粒度的策略分流,而不用自己定阈值。同时有效降低了业务侧对“连续数值”的不当依赖——很多业务方拿到0.72这个数,容易误以为是经过校准的概率,实际上它只是一个排序参考。
6.2 一轮离线评测集固化
为了保证每次调整提示词后判断质量不滑坡,我花了一个下午整理了二百条带人工标注评测集,覆盖正常内容、低质内容、垃圾内容、偏题内容、边界模糊内容等类型。
评测脚本的逻辑很简单:跑完一批测试,统计准确率、召回率、以及“关键误判率”(把invalid判成valid)。每次改动提示词或Schema之后,先在评测集上跑一遍,数值达标才允许合入主线。
这一步的价值怎么说都不为过——你如果不把判断质量量化,那么“优化提示词”就成了一场玄学博弈。有了评测集,每次改动是变好还是变坏,一目了然。这也是TypeSafe Skill能持续迭代而不退化的根基。
6.3 小样本批量测试技巧
开发调试过程中,最烦的就是跑一次调一次大模型接口,又慢又费token。我后来做了一个小技巧:在本地调试时,用“离线JSON文件”模拟模型返回,再走一遍完整的提示词构建、JSON提取、Schema校验、业务校验链路。这样大部分代码逻辑问题都可以在白盒情况下快速定位,只有真正需要验证提示词效果时才调用大模型接口。
这会显著压缩调试周期。我原先改一次提示词要跑三分钟真实接口,现在迭代校验逻辑一分钟能跑十几个用例。调试效率上了一个大台阶,强烈推荐给同样在封装Agent Skill的开发者。
最后说两句心里话
这次TypeSafe Agent Skill的接入,让我对“LLM应用工程化”这件事有了更具体的理解。很多人觉得大模型应用就是提示词工程,写几句漂亮的prompt就完事了。但在真实工程环境里,你真正需要的是把LLM的输出关进一个类型安全的笼子里——让该确定的东西100%确定,让该灵活的东西只在指定区域灵活。
至少对我来说,这个思路帮我把整个项目的返工成本降到最低,也让“AI判断”这件事从一个玄学问题变成了一个工程问题。万物皆可结构化,哪怕本质是文本生成器的大模型,只要你用对了约束方式,它也能成为一个稳定的结构化决策引擎。
这个内容后续还可以往两个方向扩展:一是把“判断理由”从结构化字段进一步升级为一个独立的可校验证据链,让每一次判断都有据可查;二是把置信度分桶与人工复核系统做联动,实现真正的“低置信度人工兜底”。如果你也在做类似的Agent能力封装,希望这份实录能帮你少走一些我走过的弯路。