【AI】LangChain Model I/O解析:从模型调用到输出解析
怎么把需求告诉模型、怎么拿到模型的回复、怎么把回复变成程序能用的数据——这些问题,Model I/O 都帮你解决了。
LangChain Model I/O深度解析:从模型调用到输出解析
前言
这篇是尚硅谷 LangChain 系列的学习笔记,这次整理的是第二章 Model I/O(模型输入输出)。
你想想,不管你后面要做 Agent 还是 RAG,是不是都得先和大模型打交道?怎么把需求告诉模型、怎么拿到模型的回复、怎么把回复变成程序能用的数据——这些问题,Model I/O 都帮你解决了。
这一章内容比较多,包括模型调用、提示词模板、输出解析、本地模型部署。跟着敲了一遍代码之后,感觉对 LangChain 的理解又深了一层。
🏠个人主页:山沐与山
文章目录
- 一、初识 Model I/O:什么是模型输入输出?
- 二、模型调用:从入门到精通
- 三、提示词模板:让 AI 按你的想法输出
- 四、输出解析器:结构化你的 AI 输出
- 五、本地模型:数据隐私与成本优化
- 六、实战技巧与最佳实践
- 七、常见问题
- 八、总结
- 热门专栏推荐
一、初识 Model I/O:什么是模型输入输出?
Model I/O 是 LangChain 框架的核心基础模块。说白了,它就是管理"如何和大模型对话"的。
你想想,和大模型打交道,是不是就这几件事?
- 格式化输入(Format):把你的需求整理成模型能理解的格式
- 调用模型(Predict):把请求发给模型,获取响应
- 解析输出(Parse):把模型返回的文本转换成程序能用的结构化数据
简单来说,就是:你想说什么 → 怎么说给模型听 → 模型回复了什么 → 怎么把回复变成你能用的数据。
为什么需要
Model I/O?直接调用
OpenAI API不行吗?确实可以,但是你会遇到这些问题:
- 提示词写在代码里不好维护,改一次就要发版
- 每次都要手动解析
JSON、列表等格式,累不累?- 换个模型(比如从
OpenAI换到Claude)要改一堆代码- 多轮对话的历史记录管理很麻烦
LangChain的Model I/O就是来解决这些问题的。
二、模型调用:从入门到精通
2.1 三种模型类型
LangChain 把模型分成三类,每种用途不一样,别搞混了:
(1)非对话模型(LLMs)
最传统的文本生成模型,输入一段文本,返回一段文本。
from langchain_openai import OpenAI
llm = OpenAI()
response = llm.invoke("写一首关于秋天的诗")
print(response) # 直接返回字符串
特点:
- 输入输出都是字符串
- 没有角色概念(system/user/assistant)
- 适合简单的文本生成任务
(2)对话模型(Chat Models)⭐
现在主流的模型都是这种,支持多轮对话和角色设定。为什么说它主流?因为 GPT-4、Claude、Qwen 这些你常用的模型,全是对话模型。
from langchain_openai import ChatOpenAI
from langchain_core.messages import SystemMessage, HumanMessage
chat_model = ChatOpenAI(model="gpt-4o-mini")
messages = [
SystemMessage(content="你是一个幽默的 AI 助手,名字叫小智"),
HumanMessage(content="你好,我是陆小千")
]
response = chat_model.invoke(messages)
print(response.content) # AIMessage 对象
特点:
- 支持多种消息类型(System、Human、AI)
- 可以设定 AI 的行为规则
- 返回的是
AIMessage对象,包含更多元数据 - 推荐使用,因为功能更强大
(3)嵌入模型(Embedding Models)
把文本转换成向量,用于语义搜索、RAG 等场景。
from langchain_openai import OpenAIEmbeddings
embeddings_model = OpenAIEmbeddings(model="text-embedding-ada-002")
vector = embeddings_model.embed_query('你好,我是陆小千')
print(len(vector)) # 1536 维向量
特点:
- 输入文本,返回向量
- 主要用于 RAG、相似度计算等场景
- 不同模型返回的向量维度不同
重点:实际项目中 90% 的情况都用 ChatOpenAI,因为它功能最全面!
2.2 配置方式最佳实践
配置 API 密钥有三种方式,但推荐第三种:
❌ 方式1:硬编码(不推荐)
llm = ChatOpenAI(
api_key="sk-xxxxxxxx", # 密钥暴露在代码里
base_url="https://api.openai.com/v1",
model="gpt-4o-mini"
)
问题:密钥容易泄露,上传到 GitHub 就完蛋了。
⚠️ 方式2:环境变量(可用)
import os
os.environ['OPENAI_API_KEY'] = "sk-xxxxxxxx"
os.environ['OPENAI_BASE_URL'] = "https://api.openai.com/v1"
llm = ChatOpenAI(model="gpt-4o-mini")
问题:密钥还是在代码里,只是换了个地方。
✅ 方式3:配置文件(最佳实践)
创建 .env 文件:
OPENAI_API_KEY="sk-xxxxxxxx"
OPENAI_BASE_URL="https://api.openai.com/v1"
代码中加载:
from dotenv import load_dotenv
import os
load_dotenv() # 自动加载 .env 文件
llm = ChatOpenAI(
api_key=os.getenv("OPENAI_API_KEY"),
base_url=os.getenv("OPENAI_BASE_URL"),
model="gpt-4o-mini"
)
优点:
- 密钥不会出现在代码里
.env文件可以加入.gitignore- 不同环境(开发/测试/生产)可以用不同的配置文件
小提示:记得在
.gitignore里加上.env,别把密钥传到 GitHub 了!
2.3 消息系统详解
对话模型最重要的就是消息系统,LangChain 定义了四种消息类型:
from langchain_core.messages import (
SystemMessage, # 系统消息:设定 AI 的行为规则
HumanMessage, # 人类消息:用户的输入
AIMessage, # AI 消息:模型的回复
ToolMessage # 工具消息:函数调用的结果
)
SystemMessage:设定 AI 的"人设"
SystemMessage(content="你是魔幻手机里的「傻妞」,说话风格活泼可爱,经常用「主人」称呼对方")
作用:
- 定义 AI 的角色和说话风格
- 设定任务的规则和约束
- 整个对话过程中一直生效
HumanMessage:用户输入
HumanMessage(content="你好,我是陆小千")
AIMessage:AI 的历史回复
AIMessage(content="主人你好呀!傻妞很高兴见到你~")
重要:大模型本身是无记忆的!每次调用都是独立的。什么意思?就是你问完第一个问题,模型回答了,然后你问第二个问题,模型压根不记得你之前问过什么。
想要实现多轮对话,必须手动传递历史消息:
# 第一轮对话
messages = [
SystemMessage(content="你是小智"),
HumanMessage(content="人工智能英文怎么说?")
]
response1 = chat_model.invoke(messages)
# AI 回复:AI
# 第二轮对话(需要带上历史)
messages.append(AIMessage(content=response1.content)) # 加上 AI 的回复
messages.append(HumanMessage(content="你叫什么名字")) # 新问题
response2 = chat_model.invoke(messages)
# AI 能回答:我叫小智(因为历史消息里有)
划重点:
- 大模型本身没有记忆功能
- 想要多轮对话,必须每次把完整的历史消息传给模型
- 历史越长,Token 消耗越多,成本越高
- 后面会学到 LangChain 的 Memory 组件,可以自动管理历史
2.4 调用方法对比
LangChain 提供了多种调用方式,适用于不同场景:
(1)invoke():标准调用
response = chat_model.invoke(messages)
print(response.content)
特点:
- 阻塞式调用,等待完整响应
- 返回完整的
AIMessage对象 - 最常用的方式
(2)stream():流式输出 ⭐
for chunk in chat_model.stream(messages):
print(chunk.content, end="", flush=True)
特点:
- 逐字输出,用户体验更好
- 类似 ChatGPT 的打字效果
- 推荐在用户界面中使用
对比示例:
# invoke:等 5 秒,突然出现一大段文字
response = chat_model.invoke("写一篇 500 字的文章")
print(response.content) # 等待... 突然全出来
# stream:边生成边显示,体验更好
for chunk in chat_model.stream("写一篇 500 字的文章"):
print(chunk.content, end="", flush=True) # 一个字一个字蹦出来
(3)batch():批量处理
messages_list = [
[HumanMessage(content="1+1=?")],
[HumanMessage(content="2+2=?")],
[HumanMessage(content="3+3=?")]
]
responses = chat_model.batch(messages_list)
for response in responses:
print(response.content)
特点:
- 一次处理多个请求
- 提高并发效率
- 适合批量数据处理
(4)异步方法
import asyncio
async def async_call():
response = await chat_model.ainvoke(messages)
print(response.content)
asyncio.run(async_call())
异步方法:ainvoke()、astream()、abatch()
三、提示词模板:让 AI 按你的想法输出
写提示词是个技术活。但是把提示词硬编码在代码里,改起来太麻烦了——每次调整一个字,就要改代码、发版、部署。有没有更好的办法?
LangChain 的提示词模板就是来解决这个问题的。
3.1 PromptTemplate 基础
PromptTemplate 是最基础的模板,用于非对话模型(LLMs)。
创建模板
from langchain_core.prompts import PromptTemplate
# 方式1:构造方法
prompt = PromptTemplate(
template="你是一个{role},你的名字叫 {name}",
input_variables=["role", "name"]
)
# 方式2:from_template()【推荐】
prompt = PromptTemplate.from_template(
"你是一个{role},你的名字叫{name}"
)
变量占位符:用 {变量名} 表示可替换的部分。
填充变量
# format() - 返回字符串
text = prompt.format(role="AI助手", name="小智")
print(text) # "你是一个AI助手,你的名字叫小智"
# invoke() - 返回 PromptValue(可与其他组件链接)
prompt_value = prompt.invoke({"role": "AI助手", "name": "小智"})
区别:
format():直接返回字符串,适合查看结果invoke():返回PromptValue对象,适合在 LCEL 链中使用
部分提示词模板(重点)
有时候某些变量是固定的,可以预先填充:
prompt = PromptTemplate.from_template(
"你是{role},名字是{name},介绍{product}",
partial_variables={"role": "魔幻手机机器人"} # 预填充
)
# 后续只需填充 name 和 product
text = prompt.format(name="傻妞", product="iPhone 16")
使用场景:
- 多个地方用到相同的前缀
- 动态生成某些固定参数(比如当前时间)
组合提示词
prompt = (
PromptTemplate.from_template("Tell me a joke about {topic}")
+ ", make it funny"
+ "\n\nand in {language}"
)
text = prompt.format(topic="程序员", language="中文")
# "Tell me a joke about 程序员, make it funny\n\nand in 中文"
3.2 ChatPromptTemplate 进阶
ChatPromptTemplate 用于对话模型,支持多角色消息。
创建模板
from langchain_core.prompts import ChatPromptTemplate
# 推荐方式
chat_prompt = ChatPromptTemplate.from_messages([
("system", "你是AI助手,名字叫{name}"),
("user", "我的问题是{question}"),
])
消息格式:(角色, 内容),角色可以是:
"system"→ SystemMessage"user"/"human"→ HumanMessage"assistant"/"ai"→ AIMessage
调用方法对比
# invoke() - 返回 ChatPromptValue(用于 LCEL 链)
chat_prompt_value = chat_prompt.invoke({
"name": "小智",
"question": "1.8和1.11谁大?"
})
# format() - 返回格式化字符串
text = chat_prompt.format(name="小智", question="1.8和1.11谁大?")
# format_messages() - 返回消息列表
messages = chat_prompt.format_messages(name="小智", question="1.8和1.11谁大?")
# [SystemMessage(...), HumanMessage(...)]
使用场景:
invoke():在 LCEL 链中使用format_messages():需要查看具体消息列表format():调试时查看文本格式
MessagePlaceholder:动态插入消息(重点)
这个很重要,用于插入不确定数量的消息,比如对话历史:
from langchain_core.prompts import MessagesPlaceholder
prompt = ChatPromptTemplate.from_messages([
("system", "你是助手,名字叫{name}"),
MessagesPlaceholder(variable_name="history"), # 对话历史占位符
("human", "{question}")
])
# 使用时传入历史消息列表
messages = prompt.invoke({
"name": "小智",
"history": [
HumanMessage(content="1+2*3 = ?"),
AIMessage(content="1+2*3=7")
],
"question": "我刚才问的问题是什么?"
})
结果:
System: 你是助手,名字叫小智
Human: 1+2*3 = ?
AI: 1+2*3=7
Human: 我刚才问的问题是什么?
为什么需要 MessagePlaceholder?
因为对话历史的消息数量是动态的,可能是 0 条,也可能是 100 条。用
MessagePlaceholder可以灵活地插入任意数量的消息,这对实现多轮对话至关重要。
嵌套模板
模板可以嵌套使用,适合复杂的提示词结构:
# 定义子模板
greeting_prompt = ChatPromptTemplate.from_messages([
("system", "你是{role}"),
("human", "你好")
])
# 嵌套到主模板
main_prompt = ChatPromptTemplate.from_messages([
greeting_prompt,
("human", "{question}")
])
3.3 Few-Shot Learning 少样本学习
Few-Shot Learning(少样本学习)是一种通过提供示例来引导模型输出的技术。
说白了就是:给几个例子,让 AI 照着学。
为什么需要 Few-Shot?
你让模型理解一个新规则,光靠描述可能说不清楚。看这个例子:
# 没有示例
response = chat_model.invoke("2🦜2")
# 模型可能回答:这是什么意思?
# 有示例
messages = [
HumanMessage(content="2🦜2"),
AIMessage(content="4"),
HumanMessage(content="2🦜3"),
AIMessage(content="8"),
HumanMessage(content="2🦜4") # 现在模型知道规则了
]
# 模型回答:16
适用场景:
- 需要特定格式输出(JSON、表格等)
- 教模型理解特殊符号或语法
- 提高特定任务的准确性
FewShotPromptTemplate(用于 PromptTemplate)
from langchain_core.prompts import FewShotPromptTemplate, PromptTemplate
# 定义示例格式
example_prompt = PromptTemplate.from_template("input:{input}\noutput:{output}\n")
# 提供示例
examples = [
{"input": "北京天气怎么样", "output": "北京市"},
{"input": "南京下雨吗", "output": "南京市"},
{"input": "武汉热吗", "output": "武汉市"}
]
# 创建少样本模板
few_shot_prompt = FewShotPromptTemplate(
example_prompt=example_prompt,
examples=examples,
suffix="input:{input}\noutput:\n",
input_variables=["input"],
)
# 使用
response = chat_model.invoke(few_shot_prompt.invoke({"input": "上海天气怎么样"}))
print(response.content) # 输出:上海市
原理:模型从示例中学到了"提取城市名"的规则。
FewShotChatMessagePromptTemplate(用于 ChatPromptTemplate)
from langchain_core.prompts import FewShotChatMessagePromptTemplate
examples = [
{"input": "2🦜2", "output": "4"},
{"input": "2🦜3", "output": "8"},
]
example_prompt = ChatPromptTemplate.from_messages([
('human', '{input} 是多少?'),
('ai', '{output}')
])
few_shot_prompt = FewShotChatMessagePromptTemplate(
examples=examples,
example_prompt=example_prompt,
)
final_prompt = ChatPromptTemplate.from_messages([
('system', '你是数学奇才'),
few_shot_prompt,
('human', '{input}'),
])
示例选择器:动态选择最相关的示例
如果示例很多(比如 100 条),全部传给模型会浪费 Token。可以用示例选择器根据语义相似度动态选择最相关的几条:
from langchain_core.example_selectors import SemanticSimilarityExampleSelector
from langchain_community.vectorstores import FAISS
from langchain_openai import OpenAIEmbeddings
examples = [
{"input": "高兴", "output": "悲伤"},
{"input": "高", "output": "矮"},
{"input": "阳光", "output": "阴暗"},
{"input": "炎热", "output": "寒冷"},
# ... 假设有 100 条示例
]
# 创建示例选择器
example_selector = SemanticSimilarityExampleSelector.from_examples(
examples,
OpenAIEmbeddings(model="text-embedding-ada-002"),
FAISS,
k=2 # 只选择最相似的 2 条
)
# 使用选择器
similar_prompt = FewShotPromptTemplate(
example_selector=example_selector, # 用选择器替代固定示例
example_prompt=example_prompt,
prefix="给出每个词组的反义词",
suffix="Input: {word}\nOutput:",
input_variables=["word"],
)
# 查询"开心"时,会自动选择"高兴→悲伤"等相关示例
response = similar_prompt.invoke({"word": "开心"})
优点:
- 节省 Token,只传递最相关的示例
- 示例库可以很大,按需选择
- 提高任务准确率
3.4 从文件加载提示词
把提示词写在代码里不好维护,LangChain 支持从外部文件加载。
YAML 格式
创建 prompt.yaml:
_type: prompt
input_variables: ["name", "what"]
template: "请给{name}讲一个关于{what}的故事"
加载并使用:
from langchain_core.prompts import load_prompt
prompt = load_prompt("prompt.yaml", encoding="utf-8")
text = prompt.format(name="年轻人", what="滑稽")
JSON 格式
创建 prompt.json:
{
"_type": "prompt",
"input_variables": ["name", "what"],
"template": "请给{name}讲一个{what}的故事。"
}
加载方式一样:
prompt = load_prompt("prompt.json", encoding="utf-8")
为什么要用文件存储?
- 代码与提示词分离:提示词改动不需要改代码
- 多语言版本管理:
prompt_zh.yaml、prompt_en.yaml - 团队协作:产品经理可以直接编辑提示词文件
- A/B 测试:方便测试不同版本的提示词效果
实际项目建议:
- 简单的提示词可以写在代码里
- 复杂的、经常改动的提示词用文件存储
- 用 Git 管理提示词版本,方便回滚
四、输出解析器:结构化你的 AI 输出
4.1 为什么需要输出解析器
大模型返回的是什么?文本。但你程序里需要的是什么?结构化数据。
看这个例子:
# 模型返回的文本
response = "人工智能的英文是 Artificial Intelligence,简称 AI"
# 但你实际需要的是这样的数据
data = {
"question": "人工智能英文怎么说?",
"answer": "Artificial Intelligence"
}
每次都手动写正则表达式去解析?太累了,而且容易出错。LangChain 提供了多种输出解析器来自动完成这个工作。
4.2 常用解析器详解
(1)StrOutputParser:字符串解析器
最简单的解析器,提取 AIMessage 中的文本内容:
from langchain_core.output_parsers import StrOutputParser
parser = StrOutputParser()
response = chat_model.invoke("什么是爱情?") # AIMessage 对象
text = parser.invoke(response) # 提取为字符串
print(text)
使用场景:
- 只需要文本内容,不需要其他元数据
- 在 LCEL 链中简化输出
LCEL 链中使用:
chain = chat_prompt | chat_model | StrOutputParser()
result = chain.invoke({"question": "什么是爱情?"})
print(result) # 直接是字符串,不是 AIMessage
(2)JsonOutputParser:JSON 解析器 ⭐
把模型输出解析为 JSON 对象,这个最常用!
方式1:手动指定返回 JSON
from langchain_core.output_parsers import JsonOutputParser
parser = JsonOutputParser()
response = chat_model.invoke(
"人工智能用英文怎么说?返回JSON格式,包含q和a字段"
)
data = parser.invoke(response)
print(data) # {'q': '人工智能用英文怎么说?', 'a': 'Artificial Intelligence'}
问题:每次都要在提示词里说"返回 JSON 格式",很麻烦。
方式2:使用 get_format_instructions()【推荐】
parser = JsonOutputParser()
prompt_template = PromptTemplate.from_template(
"回答问题\n{format_instructions}\n问题:{question}",
partial_variables={
"format_instructions": parser.get_format_instructions()
}
)
chain = prompt_template | chat_model | parser
result = chain.invoke({"question": "告诉我一个笑话"})
print(result) # 自动解析为字典
get_format_instructions() 做了什么?
它会生成类似这样的指令:
The output should be formatted as a JSON instance that conforms to the JSON schema below.
{
"properties": {
"setup": {"type": "string"},
"punchline": {"type": "string"}
},
"required": ["setup", "punchline"]
}
模型看到这个指令,就知道要返回什么格式了。
(3)CommaSeparatedListOutputParser:列表解析器
把逗号分隔的文本解析为列表:
from langchain_core.output_parsers import CommaSeparatedListOutputParser
parser = CommaSeparatedListOutputParser()
prompt = PromptTemplate.from_template(
"生成5个关于{text}的列表.\n{format_instructions}",
partial_variables={"format_instructions": parser.get_format_instructions()}
)
chain = prompt | chat_model | parser
result = chain.invoke({"text": "电影"})
print(result)
# ['科幻电影', '动作电影', '爱情电影', '恐怖电影', '喜剧电影']
使用场景:
- 生成标签、关键词
- 生成选项列表
- 批量数据提取
(4)DatetimeOutputParser:日期解析器
把日期字符串解析为 datetime 对象:
from langchain.output_parsers import DatetimeOutputParser
parser = DatetimeOutputParser()
prompt = ChatPromptTemplate.from_messages([
("system", "按照用户要求回答,{format_instructions}"),
("user", "{request}")
])
chain = prompt | chat_model | parser
response = chain.invoke({
"request": "请告诉我2024年奥运会的开幕日期?",
"format_instructions": parser.get_format_instructions()
})
print(response) # 2024-07-26 00:00:00
print(type(response)) # <class 'datetime.datetime'>
使用场景:
- 时间相关的查询
- 日程安排
- 事件提取
(5)XMLOutputParser:XML 解析器
适合需要层级结构的数据:
from langchain_core.output_parsers import XMLOutputParser
parser = XMLOutputParser()
prompt_template = PromptTemplate.from_template(
"用户问题{question}\n{format_instructions}",
partial_variables={"format_instructions": parser.get_format_instructions()}
)
chain = prompt_template | chat_model | parser
result = chain.invoke({"question": "生成周星驰的电影记录"})
print(result)
# 返回解析后的字典结构
输出解析器选择指南
| 需求 | 推荐解析器 | 使用场景 |
|---|---|---|
| 简单文本 | StrOutputParser |
只需要纯文本,不需要结构化 |
| 结构化数据 | JsonOutputParser |
API 调用、数据存储、前端展示 |
| 列表数据 | CommaSeparatedListOutputParser |
标签、关键词、选项 |
| 时间数据 | DatetimeOutputParser |
日程、事件、时间查询 |
| 层级结构 | XMLOutputParser |
复杂的嵌套数据 |
实战建议:
- 90% 的情况用
JsonOutputParser就够了- 一定要用
get_format_instructions(),别手动写提示词- 复杂格式可以用 Pydantic 定义 schema(后面会学)
五、本地模型:数据隐私与成本优化
5.1 Ollama 简介
Ollama 是一个本地大模型运行工具,可以在你的电脑上运行开源模型(比如 LLaMA、Qwen、DeepSeek 等)。
为什么要用本地模型? 你想想,有些数据是敏感的(比如公司内部代码),传给第三方 API 你放心吗?
- 数据隐私:敏感数据不用传给第三方 API
- 成本优化:不需要付费调用 API
- 离线使用:没网也能用
- 定制化:可以微调模型
缺点:
- 需要较好的硬件(显卡)
- 效果通常不如 GPT-4
- 需要自己下载和管理模型
5.2 快速上手
步骤1:安装 Ollama
访问 https://ollama.com 下载安装包。
步骤2:下载模型
# 下载 DeepSeek 7B 模型
ollama run deepseek-r1:7b
# 下载其他模型
ollama run llama2
ollama run qwen
步骤3:在 LangChain 中调用
from langchain_ollama import ChatOllama
from langchain_core.messages import HumanMessage
# 调用本地模型
llm = ChatOllama(model="deepseek-r1:7b")
messages = [HumanMessage(content="你好,请问2.2和2.18谁大?")]
response = llm.invoke(messages)
print(response.content)
完全兼容:用法和 ChatOpenAI 一模一样,切换成本几乎为零。
六、实战技巧与最佳实践
通过这一章的学习,总结一些实战经验:
1. LCEL 链式调用
LCEL(LangChain Expression Language)是 LangChain 的核心语法,贯穿整个框架:
chain = prompt_template | chat_model | output_parser
result = chain.invoke({"input": "..."})
优点:
- 代码简洁,逻辑清晰
- 自动处理数据流转
- 便于调试和优化
实战建议:
- 简单任务用单链
- 复杂任务可以拆分成多个链
- 善用
RunnablePassthrough传递数据
2. 提示词管理策略
| 场景 | 推荐方式 | 理由 |
|---|---|---|
| 简单固定提示词 | 代码中硬编码 | 方便快捷 |
| 经常变动的提示词 | YAML/JSON 文件 | 便于修改和版本控制 |
| 多语言版本 | 文件 + 配置系统 | 支持国际化 |
| 复杂嵌套提示词 | 模板组合 | 代码复用性高 |
3. 成本控制技巧
调用 API 是要花钱的,以下是省钱技巧:
- 使用小模型:
gpt-4o-mini比gpt-4便宜 30 倍 - 控制输出长度:设置
max_tokens=500 - 精简提示词:去掉废话,直击重点
- 使用缓存:相同请求不要重复调用
- 考虑本地模型:敏感数据 + 高频调用场景
Token 计算公式:
总消耗 = 输入 Token + 输出 Token
成本 = 输入 Token * 输入单价 + 输出 Token * 输出单价
节省示例:
# ❌ 浪费 Token
prompt = """
你是一个专业的 AI 助手,拥有丰富的知识和经验。
你需要帮助用户回答问题,回答要详细、准确、有条理。
请用中文回答,语气要友好。
现在用户问你:什么是 AI?
"""
# ✅ 精简版
prompt = "用一句话解释:什么是 AI?"
4. 调试技巧
(1)查看提示词实际内容
messages = chat_prompt.format_messages(name="小智", question="...")
print(messages) # 查看最终发给模型的内容
(2)打印中间结果
chain = (
prompt_template
| chat_model
| (lambda x: print(f"模型输出:{x.content}") or x) # 打印日志
| parser
)
(3)使用 LangSmith
LangChain 官方的调试工具,可以查看每一步的执行情况(需要注册)。
5. 两种模板体系对比
| 特性 | PromptTemplate | ChatPromptTemplate |
|---|---|---|
| 适用模型 | LLMs(非对话模型) | Chat Models(对话模型) |
| 输入格式 | 字符串 | 消息列表 |
| 返回格式 | 字符串 | 消息对象 |
| 角色支持 | 无 | system/user/assistant |
| 历史记忆 | 不支持 | 支持(MessagePlaceholder) |
| 推荐度 | ⭐⭐ | ⭐⭐⭐⭐⭐ |
结论:99% 的情况用
ChatPromptTemplate就对了!
6. 多轮对话实现模式
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
# 定义模板
prompt = ChatPromptTemplate.from_messages([
("system", "你是助手{name}"),
MessagesPlaceholder(variable_name="history"),
("human", "{input}")
])
# 初始化历史
history = []
# 第一轮
response1 = chat_model.invoke(prompt.invoke({
"name": "小智",
"history": history,
"input": "1+2*3=?"
}))
history.append(HumanMessage(content="1+2*3=?"))
history.append(AIMessage(content=response1.content))
# 第二轮(带历史)
response2 = chat_model.invoke(prompt.invoke({
"name": "小智",
"history": history,
"input": "我刚才问了什么?"
}))
注意事项:
- 每次都要手动更新
history - 历史越长,Token 消耗越多
- 可以设置最大历史长度(比如只保留最近 10 条)
- 后面的 Memory 组件会自动管理历史
七、常见问题
学 Model I/O 过程中,这几个问题经常被问到:
7.1 模型返回的内容不符合预期怎么办?
问题描述:让模型返回 JSON,但它返回了一堆废话,或者格式不对。
原因分析:
- 提示词不够明确,模型没理解你要什么格式
- 没有使用
get_format_instructions()生成格式指令
解决方案:
# ❌ 错误做法:提示词太模糊
response = chat_model.invoke("告诉我今天的天气,返回JSON")
# ✅ 正确做法:使用解析器的格式指令
parser = JsonOutputParser()
prompt = PromptTemplate.from_template(
"{question}\n{format_instructions}",
partial_variables={"format_instructions": parser.get_format_instructions()}
)
chain = prompt | chat_model | parser
7.2 ChatPromptTemplate 和 PromptTemplate 该用哪个?
简单一句话:99% 的情况用 ChatPromptTemplate。
为什么?因为现在主流模型都是对话模型(Chat Models),只有对话模型才支持设定角色(system/user/assistant)、才能更好地管理多轮对话。
只有在你确定要用非对话模型(比如老版本的 text-davinci-003)时,才用 PromptTemplate。
7.3 多轮对话记忆问题怎么解决?
问题描述:模型"记不住"之前聊过什么,每次都像新对话。
原因分析:大模型本身没有记忆功能,每次调用都是独立的。
解决方案:
# 方法1:手动管理历史(本章讲的)
history = []
history.append(HumanMessage(content="问题"))
history.append(AIMessage(content="回答"))
# 方法2:使用 MessagesPlaceholder(本章讲的)
prompt = ChatPromptTemplate.from_messages([
("system", "你是助手"),
MessagesPlaceholder(variable_name="history"),
("human", "{input}")
])
# 方法3:使用 Memory 组件(后面章节会讲)
# 这是最优雅的方案,LangChain 帮你自动管理
7.4 API 调用费用太高怎么办?
| 优化方法 | 效果 | 实现难度 |
|---|---|---|
使用小模型(gpt-4o-mini) |
便宜 30 倍 | ⭐ |
限制 max_tokens |
减少输出 Token | ⭐ |
| 精简提示词 | 减少输入 Token | ⭐⭐ |
使用本地模型(Ollama) |
完全免费 | ⭐⭐⭐ |
| 缓存相同请求 | 避免重复调用 | ⭐⭐⭐ |
八、总结
这一章 Model I/O 的内容确实挺多的,但梳理下来其实就是四大块:
核心内容回顾
-
模型调用
- 三种模型:LLMs、Chat Models、Embeddings
- 配置方式:环境变量 +
.env文件 - 消息系统:SystemMessage、HumanMessage、AIMessage
- 调用方法:invoke、stream、batch
-
提示词模板
PromptTemplate:基础模板(用于 LLMs)ChatPromptTemplate:对话模板(用于 Chat Models)⭐MessagesPlaceholder:动态插入历史消息- Few-Shot Learning:通过示例引导输出
- 从文件加载:YAML/JSON 格式
-
输出解析器
StrOutputParser:提取字符串JsonOutputParser:解析 JSON⭐CommaSeparatedListOutputParser:解析列表DatetimeOutputParser:解析日期get_format_instructions():自动生成格式指令
-
本地模型
- Ollama:本地运行开源模型
- 数据隐私 + 成本优化
- 完全兼容 LangChain API
重点总结
- 推荐使用 ChatOpenAI:功能最全面
- 配置用 .env 文件:避免密钥泄露
- 消息历史需要手动管理:大模型本身无记忆
- 提示词模板选 ChatPromptTemplate:支持多角色和历史
- 输出解析器首选 JsonOutputParser:结构化数据最常用
- LCEL 链式调用是核心:
prompt | model | parser
学习建议
Model I/O 是 LangChain 的基础,后面的 Chains、Memory、Agents 都建立在这之上。怎么学?
- 敲代码:把示例代码都敲一遍,别光看
- 做实验:试试不同的提示词、不同的解析器
- 看文档:
LangChain官方文档写得很详细 - 多思考:为什么这么设计?有什么优缺点?
下一章会学习 Chains(链),了解如何把多个组件串联起来完成复杂任务。继续加油!
热门专栏推荐
- Agent小册
- 服务器部署
- Java基础合集
- Python基础合集
- Go基础合集
- 大数据合集
- 前端小册
- 数据库合集
- Redis 合集
- Spring 全家桶
- 微服务全家桶
- 数据结构与算法合集
- 设计模式小册
- 消息队列合集
等等等还有许多优秀的合集在主页等着大家的光顾,感谢大家的支持
文章到这里就结束了,如果有什么疑问的地方请指出,诸佬们一起来评论区一起讨论😊
希望能和诸佬们一起努力,今后我们一起观看感谢您的阅读🙏
如果帮助到您不妨3连支持一下,创造不易您们的支持是我的动力🌟
更多推荐


所有评论(0)