LangChain 调大模型:模板拼接 + invoke / stream / batch

20 阅读8分钟

LangChain 调大模型:模板拼接 + invoke / stream / batch

读完你会什么

跟着本文走一遍,你能掌握 LangChain 调大模型的完整链路:

  1. 拼消息 — 用 ChatPromptTemplate 模板拼接(本文重点,生产最常用
  2. 调模型 — 用 invoke / stream / batch 三种方式发出请求(日常必会前两个

文末附有完整可运行脚本,复制即可跑通全文示例。

记忆口诀:先 from_messages 建模板 → format_messages 填变量 → llm.invoke(messages) 调模型。要做聊天 UI 就把 invoke 换成 stream

注意先申请大模型的秘钥和配置环境


一、模板拼接(重点掌握)

为什么要用模板

直接手写 SystemMessage + HumanMessage 能跑,但问题一多、变量一多,字符串拼接很快变得难维护。

模板的做法:在 Prompt 里留 {变量名} 占位,运行时填值,自动生成消息列表。

# ❌ 手写拼接:难维护
f"你是{role},请回答:{question}"

# ✅ 模板:结构清晰、可复用
("system", "你是{role}"),
("human", "{question}"),

聊天模型用 ChatPromptTemplate(产出 SystemMessage / HumanMessage 等)。老式纯文本补全的 PromptTemplate 本文不涉及。

标准写法:from_messages + format_messages

日常只用这一套,记住 三步

import os
from langchain.chat_models import init_chat_model
from langchain_core.prompts import ChatPromptTemplate

os.environ["OPENAI_API_KEY"] = os.getenv("SILICON_KEY")
os.environ["OPENAI_BASE_URL"] = os.getenv("SILICON_BASE_URL")

llm = init_chat_model("openai:deepseek-ai/DeepSeek-V3")

# ① 创建模板
prompt = ChatPromptTemplate.from_messages([
    ("system", "你是{role},回答要简洁"),
    ("human", "{question}"),
])

# ② 填值 → 消息列表
messages = prompt.format_messages(role="数学老师", question="什么是余数?")

# ③ 调模型
answer = llm.invoke(messages)
print(answer.content)

输出(示例,模型每次略有不同):

余数是整数除法后剩下的部分。例如 7÷3=2……1,这里的 1 就是余数。
步骤做什么产出
from_messages定义 Prompt 结构模板对象 prompt
format_messages填入 {role}{question}消息列表 messages
llm.invoke发给大模型AIMessage 回复

元组第一个位置常用 "system" / "human" / "ai",对应系统指令、用户输入、模型示例回复。

占位符语法同 Python f-string{变量名}。模板里要写 literal 花括号用 {{ / }}

其他模板写法(遇到再查)
写法适用场景
from_template("{question}")只有一条 human 消息
MessagesPlaceholder("history")多轮聊天,历史条数不固定
prompt.partial(role="...")角色固定,只换 {question}

prompt.invoke({...})format_messages 效果相同,用在chain的链式调用里,后续再说;直接调模型时 format_messages 更直观


二、三种调用方式(总览)

模板拼好 messages 之后,用 llm.xxx(...) 发出。LangChain 提供三种同步调用方式,第一个参数的写法规则相同,区别只在「怎么等结果」。

方法等什么返回什么要不要学
invoke全文生成完1 个 AIMessage⭐ 必学
stream边生成边返回多个 AIMessageChunk⭐ 必学
batch多个问题并行跑N 个 AIMessage特定场景再学
典型场景用哪个
脚本调试、后端 API、只要最终结果invoke
Web / App 聊天、打字机效果stream
批量翻译 / 摘要、离线跑几百条batch

同一批 messages换方法名即可,输入不用改

llm.invoke(messages)              # 同步,一次拿全文
llm.stream(messages)              # 流式,边生成边输出
llm.batch([messages1, messages2]) # 批量,两个独立问题
异步版本(FastAPI 等场景,了解即可)

ainvoke / astream / abatch 与上表一一对应,用于 async/await 项目,本文不展开。

简单再说下模板外挂,Prompt 写在 Python 里能跑,进项目后一般要外挂——把文案抽到代码外面,改 Prompt 不用改业务逻辑。多用txt和json、yaml文件。比较简单看下示例就会了

prompt = ChatPromptTemplate.from_messages([
    # ("system", "你是{role},回答要简洁"),
    # ("system", Path("./prompts/template.txt")..read_text(encoding="utf-8").strip()),
    ("system", load_prompt('./prompts/template.json').template),

    ("human", "{question}"),
])

template.txt

你是{role},回答要简洁

template.json

{
    "_type": "prompt",
    "input_variables": [
        "role"
    ],
    "template": "你是{role},回答要简洁"
}

第一个参数怎么传

关键区别:invoke / stream 是一维,batch 是二维。

方法维度含义
invoke / stream一维一个聊天框
batch二维多个聊天框,各聊各的
invoke / stream:  [消息, 消息, 消息]1 条回复
batch:           [[框1…], [框2…], …]    → N 条回复

一维示例 — 一个聊天框,消息之间有上下文:

messages = [
    ("system", "你是数学老师"),
    ("human", "什么是余数?"),
    ("ai", "除法剩下的部分……"),
    ("human", "再举个例子"),
]
llm.invoke(messages)   # 1 个框 → 1 条回复

二维示例 — 多个聊天框,互不相干:

llm.batch([
    [("system", "你是诗人"), ("human", "写首诗")],
    [("system", "你是程序员"), ("human", "解释变量")],
    "1+1等于几?",
])
# 3 个框 → 3 条回复
一维参数的五种写法

batch 里每个聊天框也适用以下任意一种。

#写法示例什么时候用
1字符串llm.invoke("问题")调试、单条提问
2模板填值llm.invoke(messages)⭐ 生产默认
3Message 对象llm.invoke([SystemMessage(...), HumanMessage(...)])封装层、类型要明确
4元组llm.invoke([("system", "..."), ("human", "...")])和模板风格一致
5字典llm.invoke([{"role": "user", "content": "..."}])对齐 OpenAI API 格式
# 1. 字符串 — 自动当 HumanMessage
llm.invoke("小学生怎么学余数?")

# 2. 模板填值 — 第一节的标准链路
messages = prompt.format_messages(role="数学老师", question="什么是余数?")
llm.invoke(messages)

# 3. Message 对象 — 最显式
from langchain_core.messages import SystemMessage, HumanMessage, AIMessage
llm.invoke([
    SystemMessage(content="你是数学老师"),
    HumanMessage(content="什么是余数?"),
    AIMessage(content="除法剩下的部分……"),
    HumanMessage(content="再举个例子"),
])

# 4. 元组 — 和 from_messages 写法一样
llm.invoke([("system", "你是助手"), ("human", "你好")])

# 5. 字典 — 和 OpenAI JSON 一致
llm.invoke([{"role": "system", "content": "你是助手"}, {"role": "user", "content": "你好"}])

写法 3 / 4 / 5 本质相同,LangChain 都会转成 SystemMessage / HumanMessage / AIMessage新手优先记 1(调试)和 2(生产)。


三、invoke — 同步拿全文

场景:脚本、后端 API、不需要打字机效果。

# 最简
llm.invoke("用一句话介绍你自己").content

# 配合模板(推荐)
messages = prompt.format_messages(role="数学老师", question="什么是余数?")
answer = llm.invoke(messages)
print(answer.content)

输出:

# 最简 → '我是 DeepSeek,由深度求索打造的 AI 助手……'
# 模板 → '余数是除法运算后剩下的部分,例如 7÷3=2 余 1……'

invoke = 给一个输入,同步等待,返回完整结果。

返回值AIMessage,取文本用 .content

response = llm.invoke("你好")
response.content            # 回复文本
response.response_metadata  # token 用量等

四、stream — 流式打字机

场景:Web / App 聊天窗口,边生成边展示。

第一个参数与 invoke 相同(一维、一个聊天框),区别在怎么读返回值

for chunk in llm.stream("用一句话介绍你自己"):
    print(chunk.content, end="", flush=True)

输出(逐字打印,不换行直到结束):

我是 DeepSeek,由深度求索公司打造的 AI 助手,乐意为你解答问题!

配合模板:

messages = prompt.format_messages(role="数学老师", question="什么是余数?")

for chunk in llm.stream(messages):
    print(chunk.content, end="", flush=True)

输出:

余数就是除法里除不尽剩下的那部分。比如 10÷3=31……

返回值:每次迭代一个 AIMessageChunk,同样用 .content 取文本。部分 chunk 可能为空,打印前建议判断:

full_text = ""
for chunk in llm.stream(messages):
    if chunk.content:
        full_text += chunk.content
        print(chunk.content, end="", flush=True)

五、batch — 批量多个问题

场景:批量翻译、批量摘要、离线一次跑很多条。日常开发用得少,需要时再查本节。

第一个参数是二维的:外层 list 的每个元素 = 一个独立聊天框 = 一次 invoke

answers = llm.batch([
    "1+1等于几?",
    "北京是哪个国家的首都?",
    "用一句话介绍 Python",
])

for i, ans in enumerate(answers):
    print(f"Q{i + 1}: {ans.content}")

输出:

Q1: 1+1 等于 2。
Q2: 北京是中华人民共和国的首都。
Q3: Python 是一门简洁易读、用途广泛的编程语言。

每个框也可以是一组 messages:

answers = llm.batch([
    [("system", "你是诗人"), ("human", "写一首短诗")],
    [("system", "你是程序员"), ("human", "解释变量")],
])

配合模板 — 每个问题各填一次值:

questions = ["什么是余数?", "什么是分数?", "什么是小数?"]
message_lists = [prompt.format_messages(role="数学老师", question=q) for q in questions]
answers = llm.batch(message_lists)

LangChain 默认用线程池并行执行,返回 list[AIMessage],顺序与输入一一对应。


小结

拼消息(重点)                    调模型(按场景选)
─────────────────                ─────────────────
ChatPromptTemplate               invoke  → 同步,拿全文     ⭐ 必学
  .from_messages([...])          stream  → 流式,打字机     ⭐ 必学
  .format_messages(...)  →       batch   → 批量,多框并行    特定场景
    messages
方法参数维度返回一句话
invoke一维(一个聊天框)1 个 AIMessage等全文,一次拿完
stream一维(一个聊天框)多个 AIMessageChunk边生成边输出
batch二维(多个聊天框)N 个 AIMessage多个独立问题并行

新手路径:先把第一节三步链路跑通 → 会用 invoke → 做聊天 UI 时换 stream → 遇到批处理再看 batch


附录:完整可运行脚本

前文有怎么在系统上配置硅基流动的秘钥和baseUrl

复制保存为 demo.py,配置好环境变量后执行 python demo.py

环境准备(写入 ~/.zshrc.env):

export SILICON_KEY="sk-xxx"
export SILICON_BASE_URL="https://api.siliconflow.cn/v1"

依赖安装

pip install langchain langchain-openai

完整代码


import os
import sys

from langchain.chat_models import init_chat_model
from langchain_core.prompts import ChatPromptTemplate

api_key = os.getenv("SILICON_KEY")
base_url = os.getenv("SILICON_BASE_URL", "https://api.siliconflow.cn/v1")

if not api_key:
    sys.exit("缺少 SILICON_KEY,请配置环境变量")

os.environ["OPENAI_API_KEY"] = api_key
os.environ["OPENAI_BASE_URL"] = base_url

# 本机若开了系统代理,访问国内 API 时建议 trust_env=False

llm = init_chat_model(
    "openai:deepseek-ai/DeepSeek-V3",
)

# ── 1. 模板拼接 + invoke ──
prompt = ChatPromptTemplate.from_messages([
    ("system", "你是{role},回答要简洁"),
    ("human", "{question}"),
])
messages = prompt.format_messages(role="数学老师", question="什么是余数?")

print("【invoke】")
answer = llm.invoke(messages)
print(answer.content)

# ── 2. stream ──
print("\n【stream】")
for chunk in llm.stream("用一句话介绍你自己"):
    print(chunk.content, end="", flush=True)
print()

# ── 3. batch ──
print("\n【batch】")
answers = llm.batch([
    "1+1等于几?",
    "北京是哪个国家的首都?",
])
for i, ans in enumerate(answers):
    print(f"Q{i + 1}: {ans.content}")

运行结果(示例)

【invoke】
余数是整数除法后剩下的部分。例如 7÷3=2……1,这里的 1 就是余数。

【stream】
我是 DeepSeek,由深度求索公司打造的 AI 助手,乐意为你解答问题!

【batch】
Q1: 1+1 等于 2。
Q2: 北京是中华人民共和国的首都。

实际输出因模型随机性会略有不同,看到类似结构即表示跑通。