LangChain:PromptTemplate 与 OutputParser——从堆字符串到结构化 IO

在很多使用大模型的项目里,第一步往往是「直接写一段 prompt 字符串」和「在一堆自然语言里抠出需要的数据」。
这一篇聚焦 LangChain 里最基础但非常耐用的两块:PromptTemplate 和 OutputParser,目标是搞清楚:它们分别解决了什么问题、如何配合使用,以及在工程实践中有哪些值得注意的边界。

最原始的写法通常是这样:

1const prompt = `
2你是一个助手,请帮我完成以下任务:
3${userQuestion}
4`;

看起来没什么问题,但随着工程变复杂,会遇到几类典型痛点:

  • 难以复用
    • 不同地方有类似的 prompt,只是变量略有不同,很容易 copy-paste 出好几份版本。
  • 难以维护
    • 想统一调整语气/风格/输出格式,需要满项目搜字符串;
    • 注入变量时容易遗漏或拼错占位位置。
  • 输出结构不稳定
    • 让模型「用 JSON 回复」,结果多打一逗号,全局 JSON.parse 就炸;
    • 不同调用方希望拿到结构化结果,但解析逻辑散落在各个调用点。

LangChain 在这一层做的事,是给「prompt 构造 + 输出解析」定义一个最小但清晰的抽象。

PromptTemplate 做的事情可以用一句话描述:

  • 把 prompt 变成「固定骨架 + 若干命名变量」,渲染时只关心填变量。

对应到常见需求上,有几个直接好处:

  • 模板本身可以独立 review / 管理;
  • 每个变量有清晰的名字,调用方更难填错顺序;
  • 不同模块可以共享模板,只传不同的变量。

工程里更推荐的写法是:

  • 把 prompt 模板单独放在某个模块/文件中;
  • 用 PromptTemplate 接受一个「参数对象」;
  • 在链路中复用这个模板,而不是到处写硬编码字符串。

从「心智模型」的角度,可以简单记成:
PromptTemplate 就是一种配置化、可复用的 prompt 函数签名。

大模型的输出,本质上是一串自然语言。
在常见的工程场景中,调用方更关心的却是「结构化结果」:

  • 一段 JSON;
  • 一个枚举(分类标签);
  • 一个列表(推荐项、步骤列表等)。

如果没有统一的解析层,每个地方都会出现类似这样的代码:

1const raw = await callModel(...);
2let parsed;
3try {
4  parsed = JSON.parse(extractJson(raw));
5} catch (e) {
6  // 各种兜底
7}

OutputParser 的目标就是:

  • 把「输出应该长什么样」的定义集中到一处,调用链路中只关心“这个模型调用的返回类型”。
  • 解析规则不再散落在各个业务函数里,方便统一优化;
  • 可以针对不同任务定制 Parser(结构化 JSON、打分、分类等);
  • 方便在链式调用中组合:上一个模型的 OutputParser 输出,直接作为下一个步骤的输入。

这在多步链路中尤其重要:
一旦中间某一步的输出结构不稳定,后续所有步骤的鲁棒性都会受到影响。

把这两个抽象合在一起看,会得到一个更工程化的视角:

  • PromptTemplate:定义了「输入参数 → prompt 文本」的映射;
  • OutputParser:定义了「模型输出文本 → 结构化结果」的映射。

合起来,相当于:

  • 给每一次模型调用定义了一个清晰的「函数签名」:输入是什么、输出期望是什么。

这对于:

  • 在不同链路中复用同一个调用;
  • 在测试环境中替换成 mock 结果;
  • 在监控系统中统计调用成功率与解析失败率;

都非常有帮助。

PromptTemplate 和 OutputParser 解决的,是工程层的组织问题,不会自动解决所有「模型不听话」的问题。
在实践中需要注意:

  • Prompt 仍然需要精心设计
    • 模板抽象只是让 prompt 更易管理,并不替代 prompt 设计本身;
    • 输出格式约束要写得足够明确,才能降低解析失败率。
  • Parser 需要防御性设计
    • 解析时需要考虑少字段、多字段、格式不完全符合预期的情况;
    • 对于重要链路,建议增加「解析失败重试」或「回退到更宽容解析」的逻辑。
  • 不要把所有逻辑都塞进 prompt
    • 适合放在 prompt 里的,是「让模型补齐的部分」;
    • 可以在代码里判断/处理的逻辑,仍然建议留在代码层面。

一个相对稳妥的做法是:

  • 把 PromptTemplate + OutputParser 看成「调用层」的职责;
  • 把业务规则、兜底策略放在调用层之外的业务模块中,保持分层清晰。

从工程视角看,PromptTemplate 和 OutputParser 虽然简单,却是后续所有 LangChain 设计的基础:

  • 先把「一次调用」变成一个有清晰输入输出的可复用单元;
  • 在这个基础上,再去讨论检索、工具调用、多步链路,才不会把逻辑全部堆回硬编码字符串上。

后面如果继续展开 LangChain 相关内容,可以在这个抽象之上再看:

  • RAG 场景中,如何用 Retriever + PromptTemplate 构造上下文;
  • 工具调用与 Agent 场景中,如何定义「调用结果」的结构化表示;
  • 在 Runnable / LCEL 链路中,让每一步的输入输出都保持清晰的签名。