LangChain:PromptTemplate 与 OutputParser——从堆字符串到结构化 IO
在很多使用大模型的项目里,第一步往往是「直接写一段 prompt 字符串」和「在一堆自然语言里抠出需要的数据」。
这一篇聚焦 LangChain 里最基础但非常耐用的两块:PromptTemplate 和 OutputParser,目标是搞清楚:它们分别解决了什么问题、如何配合使用,以及在工程实践中有哪些值得注意的边界。
1. 直接堆字符串有什么问题?
最原始的写法通常是这样:
1const prompt = `
2你是一个助手,请帮我完成以下任务:
3${userQuestion}
4`;
看起来没什么问题,但随着工程变复杂,会遇到几类典型痛点:
- 难以复用
- 不同地方有类似的 prompt,只是变量略有不同,很容易 copy-paste 出好几份版本。
- 难以维护
- 想统一调整语气/风格/输出格式,需要满项目搜字符串;
- 注入变量时容易遗漏或拼错占位位置。
- 输出结构不稳定
- 让模型「用 JSON 回复」,结果多打一逗号,全局
JSON.parse就炸; - 不同调用方希望拿到结构化结果,但解析逻辑散落在各个调用点。
- 让模型「用 JSON 回复」,结果多打一逗号,全局
LangChain 在这一层做的事,是给「prompt 构造 + 输出解析」定义一个最小但清晰的抽象。
2. PromptTemplate:把 prompt 拆成模板 + 变量
2.1 抽象出的核心概念
PromptTemplate 做的事情可以用一句话描述:
- 把 prompt 变成「固定骨架 + 若干命名变量」,渲染时只关心填变量。
对应到常见需求上,有几个直接好处:
- 模板本身可以独立 review / 管理;
- 每个变量有清晰的名字,调用方更难填错顺序;
- 不同模块可以共享模板,只传不同的变量。
2.2 工程视角下的使用方式
工程里更推荐的写法是:
- 把 prompt 模板单独放在某个模块/文件中;
- 用 PromptTemplate 接受一个「参数对象」;
- 在链路中复用这个模板,而不是到处写硬编码字符串。
从「心智模型」的角度,可以简单记成:
PromptTemplate 就是一种配置化、可复用的 prompt 函数签名。
3. OutputParser:把不稳定输出变成稳定结构
3.1 为什么要单独有一个 Parser?
大模型的输出,本质上是一串自然语言。
在常见的工程场景中,调用方更关心的却是「结构化结果」:
- 一段 JSON;
- 一个枚举(分类标签);
- 一个列表(推荐项、步骤列表等)。
如果没有统一的解析层,每个地方都会出现类似这样的代码:
1const raw = await callModel(...);
2let parsed;
3try {
4 parsed = JSON.parse(extractJson(raw));
5} catch (e) {
6 // 各种兜底
7}
OutputParser 的目标就是:
- 把「输出应该长什么样」的定义集中到一处,调用链路中只关心“这个模型调用的返回类型”。
3.2 工程上的几个直接收益
- 解析规则不再散落在各个业务函数里,方便统一优化;
- 可以针对不同任务定制 Parser(结构化 JSON、打分、分类等);
- 方便在链式调用中组合:上一个模型的 OutputParser 输出,直接作为下一个步骤的输入。
这在多步链路中尤其重要:
一旦中间某一步的输出结构不稳定,后续所有步骤的鲁棒性都会受到影响。
4. PromptTemplate + OutputParser:把一次调用看成「有签名的函数」
把这两个抽象合在一起看,会得到一个更工程化的视角:
- PromptTemplate:定义了「输入参数 → prompt 文本」的映射;
- OutputParser:定义了「模型输出文本 → 结构化结果」的映射。
合起来,相当于:
- 给每一次模型调用定义了一个清晰的「函数签名」:输入是什么、输出期望是什么。
这对于:
- 在不同链路中复用同一个调用;
- 在测试环境中替换成 mock 结果;
- 在监控系统中统计调用成功率与解析失败率;
都非常有帮助。
5. 使用这些抽象时需要注意的边界
PromptTemplate 和 OutputParser 解决的,是工程层的组织问题,不会自动解决所有「模型不听话」的问题。
在实践中需要注意:
- Prompt 仍然需要精心设计
- 模板抽象只是让 prompt 更易管理,并不替代 prompt 设计本身;
- 输出格式约束要写得足够明确,才能降低解析失败率。
- Parser 需要防御性设计
- 解析时需要考虑少字段、多字段、格式不完全符合预期的情况;
- 对于重要链路,建议增加「解析失败重试」或「回退到更宽容解析」的逻辑。
- 不要把所有逻辑都塞进 prompt
- 适合放在 prompt 里的,是「让模型补齐的部分」;
- 可以在代码里判断/处理的逻辑,仍然建议留在代码层面。
一个相对稳妥的做法是:
- 把 PromptTemplate + OutputParser 看成「调用层」的职责;
- 把业务规则、兜底策略放在调用层之外的业务模块中,保持分层清晰。
6. 小结:为什么先从这两个模块讲 LangChain?
从工程视角看,PromptTemplate 和 OutputParser 虽然简单,却是后续所有 LangChain 设计的基础:
- 先把「一次调用」变成一个有清晰输入输出的可复用单元;
- 在这个基础上,再去讨论检索、工具调用、多步链路,才不会把逻辑全部堆回硬编码字符串上。
后面如果继续展开 LangChain 相关内容,可以在这个抽象之上再看:
- RAG 场景中,如何用 Retriever + PromptTemplate 构造上下文;
- 工具调用与 Agent 场景中,如何定义「调用结果」的结构化表示;
- 在 Runnable / LCEL 链路中,让每一步的输入输出都保持清晰的签名。