☰
LangChain 从入门到实战(03):别再用字符串拼提示词——多消息、few-shot 与结构化输出
2026/10/6 12:36:20 网站建设 项目流程

LangChain 从入门到实战(03):别再用字符串拼提示词——多消息、few-shot 与结构化输出

上一篇我们用三个最小件跑通了第一次真实调用,也埋了根线:from_template生成的是单条用户消息。这一篇把这根线拆开——ChatPromptTemplate真正能上生产的三种写法:多角色消息(system/human)、few-shot 少样本示例、绑定结构化输出(JSON)。让提示词从「一段写死的字符串」变成「一个可被管理、复用、进生产的组件」。

老规矩,本篇代码都能直接跑,并附在每段后的「跑起来你会看到」。

一、单条消息的局限:你需要「系统」这个角色

上一篇的from_template只能生成一条用户消息。但真实生产里,一段合格的提示词往往是「先立规矩 + 再提问」:

  • system 消息:设定模型的身份、语气、边界(「你是资深 Java 后端架构师,只回答与代码相关的问题」);
  • human 消息:用户真正的问题。

from_messages就是干这个的,它能按顺序放多个角色:

fromlangchain_core.promptsimportChatPromptTemplate ckpt=ChatPromptTemplate.from_messages([("system","你是资深{role}工程师。回答要精炼,不超过3条。"),("human","{question}"),])chain=(ckpt|llm|parser)# llm、parser 沿用第02篇print(chain.invoke({"role":"Java 后端","question":"@Transactional 失效的三种场景?"}))

跑起来你会看到:先有身份设定,再答,方向不会跑偏。把「立规矩的 system」和「用户输入 human」分开管,是生产提示词的第一条分水岭。

二、few-shot:给模型「照着写」的范本

有时光靠 system 描述一件事不够,模型还是不知道你要的格式。最有效的办法是给几个「输入 → 正确输出」的样例,让模型照着写——few-shot,少样本示例。

fromlangchain_core.promptsimportFewShotChatMessagePromptTemplate,ChatPromptTemplate examples=[{"question":"1+1 等于几?","answer":"2"},{"question":"2*3 等于几?","answer":"6"},]few=FewShotChatMessagePromptTemplate(example_prompt=ChatPromptTemplate.from_messages([("human","{question}"),("ai","{answer}"),]),examples=examples,)final=ChatPromptTemplate.from_messages([("system","你是一个只回数字的算术助手。"),few,("human","{question}"),])result=final.invoke({"question":"4*5 等于几?"})print(result)

跑起来你会看到最后一条 human 是用户提问,前面带着两组示例做「锚定」。few-shot 的价值是「用节奏带动模型对齐格式」,它比千言万语更管用——模型直接抄格式,几乎没有发挥空间。

三、结构化输出:让链直接吐出 JSON(不进生产的关键一步)

前面两步都是「输入侧」。要到生产,输出侧必须结构化。用JsonOutputParser,模型就直接返回一个合规 JSON:

fromlangchain_core.promptsimportChatPromptTemplatefromlangchain_core.output_parsersimportJsonOutputParser parser_json=JsonOutputParser()prompt=ChatPromptTemplate.from_messages([("system","你是货币换算助手。"),("human","把金额 {amount} usd 转成人民币,四舍五入到元。\n{format_instructions}"),])# 把 JSON 格式约束注入成模板变量 {format_instructions}prompt=prompt.partial(format_instructions=parser_json.get_format_instructions())chain=prompt|llm|parser_json result=chain.invoke({"amount":12.5})print(result)# 直接就是 dict,例:{'amount_cny': 90}

这里的两个关键点:①partial必须在模板里留{format_instructions}这个占位符,get_format_instructions()生成的「请按下面 JSON 结构返回」约束才注得进去;②invoke传入的键要和模板里的变量名严格对齐(这里是amount),多传、少传或拼错都会报 KeyError。

JsonOutputParser内部已经帮你做了json.loads,所以你拿到的直接是 Pythondict,不再是需要re去抠的一堆字符串——这在 Java 侧相当于你在服务里直接拿到Map<String, Object>而不是自己去 parse 文本。

对照 Java 视角:这个输出「强类型化」的过程,就是String→Map再 → 「Pydantic Model」的演进。想再进一步,用PydanticOutputParser能直接锚定到一个class上去——而v0.3+ 更主流也更少踩坑的写法是llm.with_structured_output(PydanticModel),一行就把「让模型按 schema 输出 + 反序列化成对象」两步合并了,我们第 05 篇会专门展开。

四、工程化对照(Java 视角)

  1. 模板即配置:from_messages把提示词和业务代码分离,像 Java 把 SQL / 文案外置到资源文件,改文案不用动代码。
  2. few-shot 即「样例驱动开发」:调试模型行为,本质是「加样例」而不是「改逻辑」,跟对着测试用例改实现一个思路。
  3. 结构化输出即「对账」:JsonOutputParser保证下游是强类型,少一层「文本→对象」的反序列化==少一类 parser 崩溃。

小结

能力解决的问题白话
from_messages(多角色)提示词只能「一条」把「规矩」和「提问」分开
few-shot少样本模型不知道你要什么格式给 2~3 个例子,让它「照抄格式」
JsonOutputParser输出是一坨没法处理的文本直接反射成 JSON / dict

下一篇预告:这一篇我们把提示词打磨成组件,下一关是「模型」——第04篇《模型适配:一套代码,接所有 Provider》——把 API Key、base_url、模型名这些「会变的东西」做成可配置,让 LangChain 链能在一个项目里同时换 OpenAI、Zhipu、DeepSeek 多个供应商而业务代码零改动。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询