如果模型只负责写一段文案,返回自由文本通常够用;如果下一步要创建工单、查询数据库或调用支付接口,“大概是这个意思”就远远不够了。
第 3 章把模型包装器、提示词模板和输出解析器放进同一个“模型 I/O”模块。这个划分让我意识到,模型调用也应该像普通接口一样有契约:输入有哪些字段,输出是什么结构,失败时由谁处理。
版本说明:书中主要介绍早期的 LLM、Chat Model 和 Output Parser API。当前 LangChain 还提供结构化输出等新方式,具体用法应对照所选模型与官方文档。
模型包装器统一的是入口,不是能力
不同模型服务的认证方式、参数名、消息格式和流式协议都可能不同。包装器可以把常见调用整理成相近接口,让上层代码不必直接处理每家的 HTTP 细节。
但“调用方式相近”不代表“能力完全相同”。有的模型支持工具调用和结构化输出,有的只返回文本;上下文长度、图片输入、流式事件和安全策略也可能不同。
所以切换模型时,我不会只做一次“能不能返回 Hello World”的测试,还会检查:
| 检查项 | 需要确认的内容 |
|---|---|
| 输入 | 支持哪些消息、附件和上下文长度 |
| 输出 | 是否支持 JSON、工具调用、流式响应 |
| 运行 | 超时、重试、限流和错误类型 |
| 质量 | 在自己的测试集上是否仍然稳定 |
| 成本 | 单次请求的延迟和 token 消耗 |
PromptTemplate 管理的不只是占位符
刚开始写 Prompt 时,我常直接使用字符串插值。变量一多,问题就来了:字段是否缺失?用户输入和系统规则是否混在一起?改了模板以后,线上使用的是哪一版?
模板化至少能让输入变量显式化:
from langchain_core.prompts import ChatPromptTemplate
prompt = ChatPromptTemplate.from_messages([
("system", "你负责判断反馈类型,只能依据给定内容。"),
("human", "用户反馈:{feedback}"),
])
真实项目里,我还会给 Prompt 增加版本号和测试样例。提示词不是藏在代码里的临时文案,它已经是应用逻辑的一部分。
输出解析器是在给不确定性收口
假设模型要把用户反馈分成“故障、建议、咨询”三类,并给出置信度。最脆弱的做法,是让模型返回一段话,再用字符串查找分类名。
更稳妥的做法是先定义数据结构:
from typing import Literal
from pydantic import BaseModel, Field
class FeedbackResult(BaseModel):
category: Literal["故障", "建议", "咨询"]
confidence: float = Field(ge=0, le=1)
reason: str
然后让模型按该结构输出,并在程序侧完成校验。即使模型声称返回了 JSON,也仍要处理缺字段、类型错误、非法枚举和额外说明文字。
我比较认可的一条流程是:
业务输入 -> 输入校验 -> 模板渲染 -> 模型调用
-> 结构化解析 -> 业务规则校验 -> 成功或明确失败
这里的重点不是追求“永不失败”,而是让失败发生在能被看见、能被处理的位置。
一个常见误区:解析成功不等于业务正确
例如模型返回:
{"category": "故障", "confidence": 0.99, "reason": "用户无法登录"}
它在格式上完全合法,但分类是否正确,仍然需要测试数据和业务规则验证。Pydantic 可以保证字段类型,不能替我们判断语义。
同样,模型生成了合法的订单号,也不代表订单真实存在;生成了合法日期,也不代表该时间可预约。结构校验之后,还要经过数据库、权限和业务状态检查。
Prompt 注入也属于输入边界
用户输入、网页内容和检索文档都属于外部数据。它们应该被清楚标记为“待处理内容”,不能和系统规则混成一段。
对查询、发信、删除、付款等高风险动作,模型输出最多只能成为候选参数。真正执行前,还需要白名单、权限校验、幂等控制,必要时让用户再次确认。
这章给我的启发
模型 I/O 看上去是基础模块,却决定了后面所有组件能不能稳定衔接。输入没有边界,Prompt 会越来越难维护;输出没有结构,Chain 只能靠脆弱的自然语言继续传递;错误没有分类,排查时只能猜是模型、模板还是数据出了问题。
我现在会把一次模型调用理解成一份完整协议:输入模式、模型能力、输出模式,以及失败策略。真正可复用的,是这份协议,而不是某一句“效果很好”的提示词。
文章摘自:https://www.cnblogs.com/hazy-star/p/22057049
