LangChain 基础入门:模型调用、Prompt 模板与链式编排

环境准备

1
pip install langchain langchain-community langchain-ollama dashscope chromadb

各依赖的作用如下:

  • langchain:LangChain 核心框架
  • langchain-community:社区维护的模型、工具、加载器等
  • langchain-ollama:调用本地 Ollama 模型
  • dashscope:调用通义千问、DashScope 相关能力
  • chromadb:向量数据库,常用于文档检索和记忆存储

如果你使用 OpenAI 或其他在线模型,还需要额外安装对应 SDK,并配置 API Key。
例如 OpenAI 通常需要设置环境变量 OPENAI_API_KEY

如果你调用的是通义千问,通常需要提前配置 DASHSCOPE_API_KEY,并注意网络代理问题;有些环境下开代理可能会导致请求失败。


LangChain 是什么

LangChain 可以理解为一个“LLM 应用开发框架”。
它帮我们把大模型相关的常见能力封装起来,比如:

  • Prompt 组织与模板化
  • 模型调用
  • 多轮对话管理
  • 记忆机制
  • 文档加载与检索
  • Agent / 工具调用
  • 链式调用(Chain / LCEL)

简单来说,LangChain 的目标不是“替你训练模型”,而是帮助你更方便地“把模型用起来”。

LangChain 主要支持三类模型:

  1. LLMs
    输入一段字符串,输出一段字符串。
  2. Chat Models
    输入一组消息,输出一条回复,更适合多轮对话。
  3. Embedding Models
    把文本转换成向量,用于语义检索、相似度搜索、RAG 等场景。

最基本的模型调用

直接调用文本模型

  • 下面是调用通义千问的示例:
1
2
3
4
5
6
7
8
from langchain_community.llms.tongyi import Tongyi

llm = Tongyi(
model="qwen-max",
)

res = llm.invoke("你好哈哈")
print(res)
  • 调用本地 Ollama 模型
    1
    2
    3
    4
    5
    6
    7
    8
    from langchain_ollama import OllamaLLM

    llm = OllamaLLM(
    model="qwen3:4b",
    )

    res = llm.invoke("你是谁,可以干什么?")
    print(res)

本地模型有时会比较吃 CPU 或内存,如果运行压力太大,可以做两类优化:

  • 换更小的模型
  • 限制输出长度,例如:
1
2
3
4
llm = OllamaLLM(
model="qwen3:4b",
num_predict=50,
)

流式输出

很多时候我们不想等模型一次性返回完,而是希望它边生成边输出。
这时可以使用 stream()

1
2
3
4
5
6
from langchain_community.llms.tongyi import Tongyi

llm = Tongyi(model="qwen-max")

for chunk in llm.stream("你是谁,可以干什么?"):
print(chunk, end="", flush=True)

流式输出的好处是:

  • 用户能更快看到响应
  • 体验更自然
  • 适合长文本生成

流式输出(SSE)的实现

模型在收到客户端发送过来的消息的时候,根据提示词+已经生成token预测下一个token,之后选择最好的token作为delta发送,客户端收到后拼接成结果直到结束


聊天模型

聊天模型和普通 LLM 最大的区别在于:
它接收的不是单纯字符串,而是消息列表

消息通常有三种角色:

  • SystemMessage:系统提示词,用来设定身份、风格、规则
  • HumanMessage:用户输入
  • AIMessage:助手历史回复

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
from langchain_community.llms.tongyi import Tongyi
from langchain_core.messages import AIMessage, SystemMessage, HumanMessage

llm = Tongyi(model="qwen-max")

messages = [
SystemMessage(content="你是一个小说续写助手,只能续写 100 字。"),
HumanMessage(content="小说:我是小米"),
AIMessage(content=""),
]

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

这里的 messages 需要是正确的消息对象,或者符合 LangChain 规范的消息格式。
规范的写法应该使用消息对象,或者 ("system", "...")("user", "...") 这种格式。


Embedding:把文本变成向量

Embedding 模型的作用是把文本映射成高维向量。
这样就可以计算文本之间的语义相似度,用于:

  • 语义检索
  • 文档问答
  • 推荐系统
  • 向量数据库检索(后续的RAG)

    示例:DashScope Embedding

1
2
3
4
5
6
7
8
9
from langchain_community.embeddings import DashScopeEmbeddings

embeddings = DashScopeEmbeddings()

print(embeddings.embed_query("我是小明,我今年21岁了"))
print(embeddings.embed_documents([
"小明",
"小绿"
]))

一般来说:

  • embed_query():把单条查询文本转成向量
  • embed_documents():把文档列表转成向量列表

模板

PromptsTemplate

PromptTemplate 适合简单场景。
它本质上就是一个带变量的字符串模板。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from langchain_core.prompts import PromptTemplate
from langchain_community.llms.tongyi import Tongyi

template = "你是{role},擅长{skill}。接下来请按照你的身份回答问题。"
prompt = PromptTemplate.from_template(template)

formatted_prompt = prompt.format(
role="登山者",
skill="攀岩"
)

print(formatted_prompt)

model = Tongyi(model="qwen-max")
for chunk in model.stream(formatted_prompt):
print(chunk, end="", flush=True)

适用场景

  • 单轮问答
  • 固定角色设定
  • 简单文本生成

ChatPromptTemplate:适合对话场景

如果做的是聊天机器人、多轮问答、带上下文的对话,
ChatPromptTemplate 会更合适。

示例:一个简单的医生助手

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
from langchain_community.llms.tongyi import Tongyi
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder

class Doctor:
def __init__(self, major: str = ""):
self.major = major
self.client = Tongyi(model="qwen-max")
self.messages = []

self.standard = ChatPromptTemplate.from_messages([
("system", "You are a doctor, mainly major in {major}, please answer user's question professionally."),
MessagesPlaceholder(variable_name="conversation")
])

self.chain = self.standard | self.client

def ask(self, msg: str):
self.messages.append(("user", msg))

prompt_value = self.standard.invoke({
"major": self.major,
"conversation": self.messages
})

stream = self.chain.stream(prompt_value.to_messages())
full_response = ""

for chunk in stream:
print(chunk, end="", flush=True)
full_response += chunk

print()
self.messages.append(("assistant", full_response))
return full_response

def get_history(self):
return self.messages


if __name__ == "__main__":
d = Doctor(major="口腔")
answer = d.ask("17岁孩子口腔牙齿很不整齐,影响形象怎么办")

print("\n对话历史:")
for role, content in d.get_history():
print(f"{role}: {content}")

这个例子的核心思想

  • system 负责设定身份和规则
  • conversation 保存历史消息
  • chain = prompt | model 形成调用链
  • stream() 可以边生成边输出

在做下优化,变成流式输出
如果对话轮次很多,直接把全部历史塞进 prompt 会越来越长。
这时可以用向量检索,从历史对话里找“最相关”的内容。

  • 将每一轮用户和助手消息存入 Chroma
  • 用户新提问时,先检索最相似的历史片段
  • 把这些片段重新组装成上下文
  • 再交给大模型生成回答

    • 示例代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
from langchain_community.llms.tongyi import Tongyi
from langchain_community.embeddings import DashScopeEmbeddings
from langchain_chroma import Chroma
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder

class Doctor:
def __init__(self, major: str = ""):
self.major = major
self.client = Tongyi(model="qwen-max")

self.embeddings = DashScopeEmbeddings(model="text-embedding-v1")
self.vectorstore = Chroma(
collection_name="chat_history",
embedding_function=self.embeddings,
persist_directory=None
)

self.messages = []

self.standard = ChatPromptTemplate.from_messages([
("system", "You are a doctor, mainly major in {major}, please answer user's question professionally."),
MessagesPlaceholder(variable_name="conversation")
])

self.chain = self.standard | self.client

def _add_message(self, role: str, content: str):
self.messages.append((role, content))
self.vectorstore.add_texts(
texts=[content],
metadatas=[{"role": role}]
)

def ask(self, msg: str):
docs = self.vectorstore.similarity_search(msg, k=10)

conversation = []
for doc in docs:
role = doc.metadata.get("role", "user")
conversation.append((role, doc.page_content))

conversation.append(("user", msg))

stream = self.chain.stream({
"major": self.major,
"conversation": conversation
})

full_response = ""
for chunk in stream:
print(chunk, end="", flush=True)
full_response += chunk
print()

self._add_message("user", msg)
self._add_message("assistant", full_response)

return full_response

def get_history(self):
return self.messages


if __name__ == "__main__":
d = Doctor(major="口腔")
d.ask("17岁孩子口腔牙齿很不整齐,影响形象怎么办")
d.ask("刚刚是不是有人问了你问题,那个孩子是几岁来着")
d.ask("总结问问题的人的特点")

print("\n对话历史:")
for role, content in d.get_history():
print(f"{role}: {content}")

这段代码的作用

它解决的是“长对话记忆”问题:

  • 不是把所有历史都拼进去
  • 而是只找和当前问题最相关的历史
  • 更省 token,也更适合长对话

后续可能出现历史过长有幻觉问题,可以另外起一个功能函数,整合历史记录
个人有两个想法:

  1. 便利所有的历史记录,先喂给AI / 想出一个数学公式 让其提取计算模型对话历史中拟合的向量中值,之后删除太过偏离的向量对应的消息
  2. 建图,例如对于一个医生agent,关键词比如role = doctor, major = 口腔,以这个role为中心建图,可以采取向量相似度或者消息重合度连边,对于在同一个图内的设置消息的“相似度”,设置相似度 p,选取相似度 <= p的消息作为前置prompts输入模型

FewShotPromptTemplate:少样本提示词

少样本提示词的核心思想是:
先给模型几个示例,让它模仿这些示例的回答方式,再处理新的问题。

比如你给模型 3 组“问题 + 回答”,它就会更容易学会你的风格。

  • 适用场景
  • 固定格式输出
  • 分类任务
  • 翻译风格统一
  • 模仿特定回答模板
特性 PromptTemplate ChatPromptTemplate FewShotPromptTemplate
输出类型 字符串 消息列表 字符串
适用模型 LLM ChatModel LLM / 组合式聊天模板
主要用途 单轮问答 多轮对话 少样本学习
消息结构 system / user / assistant 示例驱动
变量插值 支持 支持 支持
组合能力 一般 很强 可与其他模板组合

LCEL:链式编排

LangChain 的一个重要能力是链式组合,也叫 LCEL。
你可以把多个步骤串起来:

1
template1 | model | parser | template2 | model

常见用途:

  • 提示词生成
  • 模型调用
  • 输出解析
  • 二次加工
  • 多步任务流

例如:

1
2
chain = prompt | model
result = chain.invoke({"name": "小明"})

如果是流式输出:

1
2
for chunk in chain.stream({"name": "小明"}):
print(chunk, end="", flush=True)