LangChain Model I/O深度解析:从模型调用到输出解析

前言

这篇是尚硅谷 LangChain 系列的学习笔记,这次整理的是第二章 Model I/O(模型输入输出)。

你想想,不管你后面要做 Agent 还是 RAG,是不是都得先和大模型打交道?怎么把需求告诉模型、怎么拿到模型的回复、怎么把回复变成程序能用的数据——这些问题,Model I/O 都帮你解决了。

这一章内容比较多,包括模型调用、提示词模板、输出解析、本地模型部署。跟着敲了一遍代码之后,感觉对 LangChain 的理解又深了一层。

🏠个人主页:山沐与山


文章目录


一、初识 Model I/O:什么是模型输入输出?

Model I/OLangChain 框架的核心基础模块。说白了,它就是管理"如何和大模型对话"的。

你想想,和大模型打交道,是不是就这几件事?

  1. 格式化输入(Format):把你的需求整理成模型能理解的格式
  2. 调用模型(Predict):把请求发给模型,获取响应
  3. 解析输出(Parse):把模型返回的文本转换成程序能用的结构化数据

简单来说,就是:你想说什么 → 怎么说给模型听 → 模型回复了什么 → 怎么把回复变成你能用的数据

为什么需要 Model I/O

直接调用 OpenAI API 不行吗?确实可以,但是你会遇到这些问题:

  • 提示词写在代码里不好维护,改一次就要发版
  • 每次都要手动解析 JSON、列表等格式,累不累?
  • 换个模型(比如从 OpenAI 换到 Claude)要改一堆代码
  • 多轮对话的历史记录管理很麻烦

LangChainModel 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-4ClaudeQwen 这些你常用的模型,全是对话模型。

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")
为什么要用文件存储?
  1. 代码与提示词分离:提示词改动不需要改代码
  2. 多语言版本管理prompt_zh.yamlprompt_en.yaml
  3. 团队协作:产品经理可以直接编辑提示词文件
  4. 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 是一个本地大模型运行工具,可以在你的电脑上运行开源模型(比如 LLaMAQwenDeepSeek 等)。

为什么要用本地模型? 你想想,有些数据是敏感的(比如公司内部代码),传给第三方 API 你放心吗?

  1. 数据隐私:敏感数据不用传给第三方 API
  2. 成本优化:不需要付费调用 API
  3. 离线使用:没网也能用
  4. 定制化:可以微调模型

缺点

  • 需要较好的硬件(显卡)
  • 效果通常不如 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 是要花钱的,以下是省钱技巧:

  1. 使用小模型gpt-4o-minigpt-4 便宜 30 倍
  2. 控制输出长度:设置 max_tokens=500
  3. 精简提示词:去掉废话,直击重点
  4. 使用缓存:相同请求不要重复调用
  5. 考虑本地模型:敏感数据 + 高频调用场景

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,但它返回了一堆废话,或者格式不对。

原因分析

  1. 提示词不够明确,模型没理解你要什么格式
  2. 没有使用 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 ChatPromptTemplatePromptTemplate 该用哪个?

简单一句话: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 的内容确实挺多的,但梳理下来其实就是四大块:

核心内容回顾

  1. 模型调用

    • 三种模型:LLMs、Chat Models、Embeddings
    • 配置方式:环境变量 + .env 文件
    • 消息系统:SystemMessage、HumanMessage、AIMessage
    • 调用方法:invoke、stream、batch
  2. 提示词模板

    • PromptTemplate:基础模板(用于 LLMs)
    • ChatPromptTemplate:对话模板(用于 Chat Models)⭐
    • MessagesPlaceholder:动态插入历史消息
    • Few-Shot Learning:通过示例引导输出
    • 从文件加载:YAML/JSON 格式
  3. 输出解析器

    • StrOutputParser:提取字符串
    • JsonOutputParser:解析 JSON⭐
    • CommaSeparatedListOutputParser:解析列表
    • DatetimeOutputParser:解析日期
    • get_format_instructions():自动生成格式指令
  4. 本地模型

    • Ollama:本地运行开源模型
    • 数据隐私 + 成本优化
    • 完全兼容 LangChain API

重点总结

  • 推荐使用 ChatOpenAI:功能最全面
  • 配置用 .env 文件:避免密钥泄露
  • 消息历史需要手动管理:大模型本身无记忆
  • 提示词模板选 ChatPromptTemplate:支持多角色和历史
  • 输出解析器首选 JsonOutputParser:结构化数据最常用
  • LCEL 链式调用是核心prompt | model | parser

学习建议

Model I/OLangChain 的基础,后面的 ChainsMemoryAgents 都建立在这之上。怎么学?

  1. 敲代码:把示例代码都敲一遍,别光看
  2. 做实验:试试不同的提示词、不同的解析器
  3. 看文档LangChain 官方文档写得很详细
  4. 多思考:为什么这么设计?有什么优缺点?

下一章会学习 Chains(链),了解如何把多个组件串联起来完成复杂任务。继续加油!


热门专栏推荐

等等等还有许多优秀的合集在主页等着大家的光顾,感谢大家的支持

文章到这里就结束了,如果有什么疑问的地方请指出,诸佬们一起来评论区一起讨论😊
希望能和诸佬们一起努力,今后我们一起观看感谢您的阅读🙏
如果帮助到您不妨3连支持一下,创造不易您们的支持是我的动力🌟

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐