LangChain 由入门到精通
作者:王珂
邮箱:49186456@qq.com
简介
英文官网:
https://www.langchain.com/
中文官网
https://www.langchain.com.cn/
LangChain是一个用于开发由大型语言模型(LLM)驱动的应用程序的框架。通过LangChain可以轻松的与大预言模型集成,完成文本生成、问答、翻译对话等任务。
本文档编写基于的 LangChain 版本
langchain==0.3.27
langchain-core:0.3.76
langchain-community:0.3.29
langchain-mcp-adapters:0.1.9
大模型显存计算器:
https://www.llamafactory.cn/tools/gpu-memory-estimation.html
算力购买:
https://www.autodl.com/
为什么要用LangChain
-
数据连接:Langchain 允许你将大型语言模型连接到你自己的数据源,比如数据库、PDF文件或其他文档。这意味着你可以使模型从你的私有数据中提取信息
-
行动执行:不仅可以提取信息,还可以帮助你根据这些信息执行特操作,如发送邮件。
LangChain的核心
- Models: 包装器允许你连接到的大语言模型,如GPT-4 或 Hugging Face, 也包括GLM提供的模型
- Prompt Templates: 动态将用户输入插入到模板,发给语言模型
- Chains: 将组件组合在一起,解决特定任务(执行任务的过程想工作流一样,一步一步执行),并构建完整的语言模型应用程序
- LCEL(LangChain Expression Language),用于解决工作流的编排问题,通过LCEL表达式,可以灵活自定义AI任务处理流程,即灵活自定义链(Chain)
- Embedding: 嵌入与向量存储 VectorStore是数据表示和检索的手段,为模型提供必要的理解基础
- RAG(Retrieval-Augmented Generation)
- 模型记忆(Memory):让大模型记住之前的对话内容,这种能力称为模型记忆
- Agents: 一种基于大模型的应用设计模式,利用大模型的自然语言理解和推理能力,根据用户的需求自动调用外部系统和设备共同完成任务
LangChain三个核心组件:
-
Components 为LLMs提供封装接口,模板提示和信息检索索引
-
Chains 将不同的组件组合起来解决特定的任务,比如在大量文档中查找信息
-
Agents 使LLMs能够与外部环境进行交互,例如通过API请求执行操作
LangChain框架组成

-
langchain-core: 基础抽象和LangChain表达式 (LCEL)。 -
langchain-community: 第三方集成。合作伙伴库(例如
langchain-openai、langchain-anthropic等):一些集成已进一步拆分为自己的轻量级库,仅依赖于langchain-core。 -
langchain: 组成应用程序认知架构的链、代理和检索策略。 -
LangGraph: 通过将步骤建模为图中的边和节点,构建强大且有状态的多参与者应用程序。与LangChain无缝集成,但也可以单独使用。
-
LangServe: 基于FastAPI将LangChain链部署为REST API。
-
LangSmith: 一个开发者平台,让您调试、测试、评估和监控LLM应用程序。
LangChain任务处理

LangChain对大模型封装主要分为LLM和Chat Model两种类型
-
LLM: 问答模式,模型一个文本输入,返回一个文本输出
-
Chat Model: 对话模型,接收一组对话消息,返回对话消息,类似聊天消息
核心概念
-
LLMs
基础模型:LangChain封装的基础模型,模型接收一个文本输入,然后返回一个文本输出
-
Chat Models
聊天模型(或对话模型),接收一组对话消息,然后返回对话消息,类似聊天一样
-
Message
聊天模型的消息内容,包括SystemMessage, HumanMessage, AIMessage, ToolMessage, FunctionMessage等
消息对象包含
- content 消息内容
- name 可选,消息作者
- response_metadata 可选,元数据字典(例如,通常由模型提供者为 AIMessages 提供)
-
Prompts
LangChain封装了一组专门用于提示词(Prompts)的管理工具类,方便格式化提示词(prompts)内容
-
Output Parsers 输出解析器
大模型 (llm)返回的文本内容之后,可以使用专门的输出解析器对文本内容进行格式化,例如解析json、或者将 llm 输出的内容转成 python 对象
-
Retrievers
为方便将私有数据导入到大模型,提高模型问题回答的质量,LangChain封装了检索框架Retrievers,方便加载文档数据,切割文档数据,存储和检索文档数据
-
Vector stores 向量存储
为支持私有数据的语义相似搜索,langchain 支持多种向量数据库。
-
Agents
智能体(agents),通常指以大模型做为决策引擎,根据用户输入的任务,自动调用外部系统和硬件设备,共同完成用户的任务,是一种以大模型为核心的设计模式
一、LangChain环境搭建
1.1 LangChain安装
LangChain目前支持Python和TypeScript语言
pip install langchain
如果用到OpenAI的模型,还需要安装
pip install langchain-openai
如果用到第三方,还需要安装 langchain-community
pip install langchain-community
二、LangChain开发
2.1 提示词模板
Prompt Templates
提示词模板 Prompt Templates,大预言模型以文本做为输入,这个文本叫做提示词(prompt)。为了便于提示词的管理,可以通过提示词模板进行维护,类似于短信模板、邮件模板。
发给大模型的提示词可以是一段对话,一段示例等都可以
提示词模板的组成
-
发给大语言模型的指令
-
一组回答示例,以提醒AI以什么格式返回请求
-
发给语言模型的问题
2.1.1 简单提示词模板
简单提示词模板
from langchain_core.prompts import PromptTemplate
simple_template = PromptTemplate.from_template("用Python写一个使用{content}的示例代码")
print(simple_template.format(content='python装饰器'))
print(simple_template.invoke({"content": "python装饰器"}))
用Python写一个使用python装饰器的示例代码
text=‘用Python写一个使用python装饰器的示例代码’
简单提示词模板的拼接
prompt = (
PromptTemplate.from_template("请帮我搜查一下关于{content}的方法")
+ ", 要求:1、需要举例说明"
+ ", 要求:2、用中文回答"
)
2.1.2 聊天提示词模板
聊天模型以聊天消息做为输入,聊天消息列表的消息内容通过提示词模板进行管理,每个消息都与角色相关联
OpenAI的聊天模型,给聊天消息定义了三种角色:
-
系统消息 system:通常用来给AI进行身份描述
-
人类消息 human:指你发给AI的内容
-
助手消息 Assistant:指当前消息是AI回答的内容
# 聊天提示词模板
chat_template = ChatPromptTemplate.from_messages([
("system", "你是一个编程助手"),
("human", "解释一下Python中的{content}基本知识")
])
prompt_text = chat_template.format(content='async')
print(prompt_text)
prompt_value = chat_template.invoke({'content': 'async'})
print(prompt_value)
System: 你是一个编程助手
Human: 解释一下Python中的async基本知识messages=[SystemMessage(content=‘你是一个编程助手’, additional_kwargs={}, response_metadata={}), HumanMessage(content=‘解释一下Python中的async基本知识’, additional_kwargs={}, response_metadata={})]
from langchain_core.prompts import ChatPromptTemplate
chat_template = ChatPromptTemplate.from_messages([
("system", "你是Python资深工程师"),
("human", "{input}"),
])
messages = chat_template.format_messages(input="请解释一下Python中的箭头函数怎么用?")
langchain将提示词分抽象为消息,分为SystemMessage, HumanMessage,最后将消息转换成报文(如JSON报文)传递给大模型
[SystemMessage(content=‘你是Python资深工程师’, additional_kwargs={}, response_metadata={}), HumanMessage(content=‘请解释一下Python中的箭头函数怎么用?’, additional_kwargs={}, response_metadata={})]
聊天提示词模板两种格式:
-
字符串格式
chat_template = ChatPromptTemplate.from_messages([ ("system", "你是Python资深工程师"), ("human", "解释一下Python中的{content}基本知识"), # {content}是一个占位符 ]) prompt_value = chat_template.invoke({'content': 'async'}) print(prompt_value)提示词模板是一个runnable对象
-
聊天对象格式
chat_template = ChatPromptTemplate.from_messages([ SystemMessage( content="你是Python资深工程师" ), HumanMessage( content="{input}" ) ])
MessagePlaceholder
负责在特定位置添加消息列表。如果希望用户传入一个消息列表,将其插入到特定位置,此时就会用到。
有两种写法:
-
字符串形式
chat_template = ChatPromptTemplate.from_messages([ ("system", "你是Python资深工程师"), ("placeholder", "{chat_history}"), ("human", "请问今天是几号?") ]) chat_history = [{"role":"human", "content": "今天是2025年09月16日"}] response = chat_template.invoke({"chat_history": chat_history}) print(response)messages=[SystemMessage(content=‘你是Python资深工程师’, additional_kwargs={}, response_metadata={}), HumanMessage(content=‘今天是2025年09月16日’, additional_kwargs={}, response_metadata={}), HumanMessage(content=‘请问今天是几号?’, additional_kwargs={}, response_metadata={})]
-
对象形式
chat_template = ChatPromptTemplate.from_messages([ ("system", "你是Python资深工程师"), MessagesPlaceholder("chat_history"), ("human", "请问今天是几号?") ]) chat_history = [HumanMessage(content="今天是2025年09月16日")] response = chat_template.invoke({"chat_history": chat_history}) print(response)messages=[SystemMessage(content=‘你是Python资深工程师’, additional_kwargs={}, response_metadata={}), HumanMessage(content=‘今天是2025年09月16日’, additional_kwargs={}, response_metadata={}), HumanMessage(content=‘请问今天是几号?’, additional_kwargs={}, response_metadata={})]
提示词添加样例
提示词添加示例(Few-shot prompt templates),提示词中包含交互样本的作用是为了帮助大模型更好的理解用户的意图,从而更好的回答问题或执行任务。小样本提示词模板是指使用一组少量的示例来指导模型处理新的输入。这些示例可以用来训练模型,以便模型可以更好的理解和回答类似的问题。
Q: 什么是蝙蝠侠?
A: 蝙蝠侠是一个虚构的漫画人物。
Q: 什么是语言模型?
A: 语言模型是通过语言进行推理、交互的模型。
......
以上示例是告诉模型,根据Q是问题,A是答案,按照这种格式进行问答交互。
示例1:
from langchain_core.prompts import PromptTemplate, FewShotPromptTemplate
examples = [
{
"question": "中国历史上哪一个朝代统治的时间最长?",
"answer": """
中国历史上统治时间最长的朝代是周朝,享国约791年(前1046年—前256年),分为西周(275年)和东周(516年)。
尽管东周时期诸侯割据,但周王室作为天下共主的法统地位延续至战国末期,远超其他统一或分裂政权。
""",
},
{
"question": "中国历史上哪一个朝代统治的时间最长短?",
"answer": """
中国历史上统治时间最短的朝代是秦朝,它于公元前221年统一六国建立,至公元前207年灭亡,国祚仅15年。
秦朝虽开创中央集权制度,但因严刑峻法、滥用民力,最终在农民起义中迅速覆灭。
"""
}
]
example_prompt = PromptTemplate(input_variables=["question", "answer"], template="问题:{question}\\n{answer}")
prompt = FewShotPromptTemplate(
examples=examples,
example_prompt=example_prompt,
input_variables=["question", "answer"],
suffix="问题:{input}"
)
content = prompt.format(input="中国哪一个朝代皇帝最多?")
print(content)
result = prompt.invoke({"input": "中国哪一个朝代皇帝最多?"})
print(result)
问题:中国历史上哪一个朝代统治的时间最长?\n
中国历史上统治时间最长的朝代是周朝,享国约791年(前1046年—前256年),分为西周(275年)和东周(516年)。
尽管东周时期诸侯割据,但周王室作为天下共主的法统地位延续至战国末期,远超其他统一或分裂政权。问题:中国历史上哪一个朝代统治的时间最长短?\n
中国历史上统治时间最短的朝代是秦朝,它于公元前221年统一六国建立,至公元前207年灭亡,国祚仅15年。
秦朝虽开创中央集权制度,但因严刑峻法、滥用民力,最终在农民起义中迅速覆灭。问题:中国哪一个朝代皇帝最多?
text=‘问题:中国历史上哪一个朝代统治的时间最长?\n\n 中国历史上统治时间最长的朝代是周朝,享国约791年(前1046年—前256年),分为西周(275年)和东周(516年)。\n 尽管东周时期诸侯割据,但周王室作为天下共主的法统地位延续至战国末期,远超其他统一或分裂政权。\n \n\n问题:中国历史上哪一个朝代统治的时间最长短?\n\n 中国历史上统治时间最短的朝代是秦朝,它于公元前221年统一六国建立,至公元前207年灭亡,国祚仅15年。\n 秦朝虽开创中央集权制度,但因严刑峻法、滥用民力,最终在农民起义中迅速覆灭。\n \n\n问题:中国哪一个朝代皇帝最多?’
定义一个 example 的示例数组,里面包含了一组回答样例
聊天模型添加样例
# 定义样例
examples = [
{
"question": "2 😀 2",
"answer": "4"
},
{
"question": "2 😀 3",
"answer": "6"
},
{
"question": "3 😀 3",
"answer": "9"
}
]
# 定义基本模板(注意,要和样例进行对应)
example_prompt = ChatPromptTemplate.from_messages([
("human", "{question}"),
("ai", "{answer}")
])
# 包含示例的提示词模板
few_shot_prompt = FewShotChatMessagePromptTemplate(
examples=examples,
example_prompt=example_prompt
)
# 最终的模板
final_prompt = ChatPromptTemplate.from_messages([
("system", "你是一个AI智能助手"),
few_shot_prompt,
MessagesPlaceholder("input")
])
chain = final_prompt | chat_openai
result = chain.invoke({"input": [HumanMessage(content="4 😀 8 的结果是多少?")]})
print(result)
32
(因为 “😀” 在这里代表乘法运算,4 × 8 = 32
2.1.3 ICL提示词模板
In-context Learning (ICL) 提示词模板
做为一种新的自然语言处理范式逐渐展露头角。ICL的核心思想是:通过提供少量示例做为上下文,让大模型直接从中学习并做出预测。这一方法不仅省去了传统监督学习中繁琐的训练过程,还为大模型的应用开辟了新的可能性。
-
提供示例
examples = [ { "question": "穆罕默德·阿里和艾论·图灵谁活得更久?", "answer": """ 是否需要后续问题:是。 后续问题:穆罕默德·阿里去世时多大? 中间答案:穆罕默德·阿里去世时74岁。 后续问题:艾伦·图灵去世时多大? 中间答案:艾伦·图灵去世时41岁。 所以最终答案是:穆罕默德·阿里 """ }, { "question": "《大白鲨》和《007:大战皇家赌场》的导演是否来自同一个国家?", "answer": """ 是否需要后续问题:是。 后续问题:《大白鲨》的导演是谁? 中间答案:《大白鲨》的导演是史蒂文·斯皮尔伯格。 后续问题:史蒂文·斯皮尔伯格来自哪里? 中间答案:美国。 后续问题:《007:大战皇家赌场》的导演是谁? 中间答案:《007:大战皇家赌场》的导演是马丁·坎贝尔。 后续问题:马丁·坎贝尔来自哪里? 中间答案:新西兰。 所以最终答案是:否 """ } ] -
提示词模板
base_prompt = PromptTemplate.from_template("问题:{question}\n{answer}") -
FewShotPromptTemplate
final_prompt = FewShotPromptTemplate( examples=examples, example_prompt=base_prompt, suffix="问题:{input}", input_variables=["input"] ) -
执行
chain = final_prompt | chatOpenAI result = chain.invoke({"input": "巴伦·特朗普的父亲是谁?"}) print(result)content=‘是否需要后续问题:是。 \n后续问题:巴伦·特朗普的母亲是谁? \n中间答案:巴伦·特朗普的母亲是梅拉尼娅·特朗普。 \n后续问题:巴伦·特朗普的父亲是谁? \n中间答案:巴伦·特朗普的父亲是唐纳德·特朗普。 \n所以最终答案是:唐纳德·特朗普’ additional_kwargs={‘refusal’: None} response_metadata={‘token_usage’: {‘completion_tokens’: 73, ‘prompt_tokens’: 296, ‘total_tokens’: 369, ‘completion_tokens_details’: None, ‘prompt_tokens_details’: {‘audio_tokens’: None, ‘cached_tokens’: 0}}, ‘model_name’: ‘qwen-plus’, ‘system_fingerprint’: None, ‘id’: ‘chatcmpl-e8820173-4ffb-9064-9298-26cf0bd95a35’, ‘service_tier’: None, ‘finish_reason’: ‘stop’, ‘logprobs’: None} id=‘run–167e85e6-71b4-498d-b2e5-70c219ae6de7-0’ usage_metadata={‘input_tokens’: 296, ‘output_tokens’: 73, ‘total_tokens’: 369, ‘input_token_details’: {‘cache_read’: 0}, ‘output_token_details’: {}}
创建示例集
examples = [
{
"question": "什么动物的寿命最长",
"answer": "乌龟的寿命最长"
},
{
"question": "乌龟可以活多次时间",
"answer": "乌龟可以活上百年"
}
]
example_prompt = PromptTemplate(
input_variables=["question", "answer"],
template="问题: {question}\\n{answer}"
)
prompt = FewShotPromptTemplate(
examples=examples,
example_prompt=example_prompt,
suffix="问题:{input}",
input_variables=["input"]
)
prompt.format(input='大象可以活多久?')
示例
问题: 什么动物的寿命最长\n乌龟的寿命最长
问题: 乌龟可以活多次时间\n乌龟可以活上百年
问题:大象可以活多久?
创建小样本示例的格式化程序
examples = [
{
"question": "什么动物的寿命最长",
"answer": "乌龟的寿命最长"
},
{
"question": "乌龟可以活多长时间",
"answer": "乌龟可以活上百年"
}
]
example_prompt = PromptTemplate(input_variables=["question", "answer"], template="问题: {question}\\n{answer}")
example_prompt.format(**examples[0])
示例选择器
ExampleSelectors
TODO
2.2 输出解析器
输出解析器,负责获取模型的输出并将其转换成更适合下游任务的格式。在使用大模型生成结构化数据或规范化聊天模型和大语言模型的输出时非常有用。
-
字符串输出解析器 StrOutputParser()
chain = final_prompt | chat_openai | StrOutputParser() -
工具调用方法 bind_tools()
import os from dotenv import load_dotenv from langchain_openai import ChatOpenAI from pydantic import BaseModel, Field load_dotenv() class ResponseFormatter(BaseModel): answer: str = Field(description="对用户问题的回答"), followup_question: str = Field(description="用户可能提出的后续问题") chat_openai = ChatOpenAI( base_url=os.getenv("DEEPSEEK_BASE_URL"), api_key=os.getenv("DEEPSEEK_API_KEY"), model="deepseek-chat", ) runnable = chat_openai.bind_tools([ResponseFormatter]) resp = runnable.invoke("细胞的动力源是什么?") print(resp) resp.pretty_print()content=‘’ additional_kwargs={‘tool_calls’: [{‘id’: ‘call_0_709ff06c-beec-4bff-90b1-dfc90ff739d2’, ‘function’: {‘arguments’: ‘{“answer”:“细胞的动力源主要是线粒体(mitochondria),它被称为细胞的“动力工厂”。线粒体通过有氧呼吸过程产生三磷酸腺苷(ATP),这是细胞进行各种生命活动所需的直接能量来源。”,“followup_question”:“线粒体是如何产生ATP的?”}’, ‘name’: ‘ResponseFormatter’}, ‘type’: ‘function’, ‘index’: 0}], ‘refusal’: None} response_metadata={‘token_usage’: {‘completion_tokens’: 77, ‘prompt_tokens’: 129, ‘total_tokens’: 206, ‘completion_tokens_details’: None, ‘prompt_tokens_details’: {‘audio_tokens’: None, ‘cached_tokens’: 128}, ‘prompt_cache_hit_tokens’: 128, ‘prompt_cache_miss_tokens’: 1}, ‘model_name’: ‘deepseek-chat’, ‘system_fingerprint’: ‘fp_8802369eaa_prod0623_fp8_kvcache’, ‘id’: ‘0ec8f11a-d625-4312-9e30-57dd270283c9’, ‘service_tier’: None, ‘finish_reason’: ‘tool_calls’, ‘logprobs’: None} id=‘run–2d40f486-2373-426f-97ab-0a76f7a1f9bd-0’ tool_calls=[{‘name’: ‘ResponseFormatter’, ‘args’: {‘answer’: ‘细胞的动力源主要是线粒体(mitochondria),它被称为细胞的“动力工厂”。线粒体通过有氧呼吸过程产生三磷酸腺苷(ATP),这是细胞进行各种生命活动所需的直接能量来源。’, ‘followup_question’: ‘线粒体是如何产生ATP的?’}, ‘id’: ‘call_0_709ff06c-beec-4bff-90b1-dfc90ff739d2’, ‘type’: ‘tool_call’}] usage_metadata={‘input_tokens’: 129, ‘output_tokens’: 77, ‘total_tokens’: 206, ‘input_token_details’: {‘cache_read’: 128}, ‘output_token_details’: {}}
================================== Ai Message ==================================
Tool Calls:
ResponseFormatter (call_0_709ff06c-beec-4bff-90b1-dfc90ff739d2)
Call ID: call_0_709ff06c-beec-4bff-90b1-dfc90ff739d2
Args:
answer: 细胞的动力源主要是线粒体(mitochondria),它被称为细胞的“动力工厂”。线粒体通过有氧呼吸过程产生三磷酸腺苷(ATP),这是细胞进行各种生命活动所需的直接能量来源。
followup_question: 线粒体是如何产生ATP的? -
with_structured_output()
一些Langchain聊天模型支持 .with_structured_output()方法。该方法只需要一个模式作为输入,并返回一个字典或Pydantic对像。通常这个
方法仅在支持下面描述的更高级方法的模型上存在, 并将在内部使用其中一种。它负责导入合适的输出解析器并将模式格式化为模型所需的正确格式。import os from dotenv import load_dotenv from typing import Optional from pydantic import BaseModel, Field from langchain_openai import ChatOpenAI from langchain.prompts import PromptTemplate load_dotenv(verbose=True) chat_openai = ChatOpenAI( api_key=os.getenv("DASHSCOPE_API_KEY"), base_url=os.getenv("DASHSCOPE_BASE_URL"), model='qwen-plus', model_kwargs={ "response_format": {"type": "json_object"} } ) class Joke(BaseModel): setup: str = Field(description="笑话的开头部分") # date: str = Field(description="通知日期 2025年07月13日") # author: Optional[str] = Field(description="编写人: 王珂") punchline: str = Field(description="笑话的笑点/包袱") # publisher: str = Field(description="发布人:王珂") rating: Optional[int] = Field(description="笑话的有趣评分程度,范围1到10") prompt_template = PromptTemplate.from_template("帮我生成一个关于{topic}的笑话") runnable = chat_openai.with_structured_output(Joke) chain = prompt_template | runnable result = chain.invoke({"topic": "猫"}) # 阿里百炼的千文和 deepseek 执行报错 print(result)
2.3 自定义输出
2.3.1 自定义输出 JSON
如何解析JSON输出
虽然一些模型提供商支持内置的方法返回结构化输出,但并非所有都支持。可以使用输出解析器指定格式的输出
-
SimpleJsonOutputParser()一些模型,例如
OpenAI, 支撑一种称为 JSON 模式的功能,通常通过配置启用。启动时,JSON 模式将限制模型的输出始终为某种有效的 JSON。chat_openai = OpenAI(model='gpt-4') prompt = ChatPromptTemplate.from_template( "尽你所能回答用户问题。" "你必须始终输出一个包含 'answer' 和 'followup_question' 键的JSON对象。其中 'answer' 代表用户回答的问题,'followup_question' 代表根据回答衍生出的新问题。" "{question}" ) chain = prompt | chat_openai | SimpleJsonOutputParser() resp = chain.invoke({"question": "细胞的动力源是什么"}) print(resp){‘answer’: ‘细胞的动力源主要是三磷酸腺苷(ATP)。ATP是一种高能分子,通过细胞呼吸(如线粒体中的有氧呼吸或细胞质中的无氧呼吸)产生,为各种细胞活动(如物质运输、肌肉收缩和生化合成)提供能量。’, ‘followup_question’: ‘线粒体是如何具体产生ATP的?这个过程涉及哪些关键步骤和分子?’}
-
JsonOutputParser是一个内置选项,用于提示并解析 JSON 输出from langchain_core.output_parsers import JsonOutputParser from langchain_core.prompts import PromptTemplate from pydantic import BaseModel, Field chat_openai = OpenAI(model='gpt-4') class Article(BaseModel): title: str = Field(description="文章的标题") content: str = Field(description="文章的内容") json_output = JsonOutputParser(pydantic_object=Article) # 提示词模板对象, # template:提示词模板 # {format_instructions}:格式化指令占位符,它通过 JsonOutputParser() 输出解析器调用方法 get_format_instructions() 得到 # {query}:用户问题的占位符,通过用户的输入得到 prompt = PromptTemplate( template="回答用户的问题。\n{format_instructions}\n{query}\n", input_variables=["query"], partial_variables={"format_instructions": json_output.get_format_instructions()}, ) print(prompt.format(query="写一篇关于转基因油的文章"))生成的提示词如下:
回答用户的问题。
The output should be formatted as a JSON instance that conforms to the JSON schema below.As an example, for the schema {“properties”: {“foo”: {“title”: “Foo”, “description”: “a list of strings”, “type”: “array”, “items”: {“type”: “string”}}}, “required”: [“foo”]}
the object {“foo”: [“bar”, “baz”]} is a well-formatted instance of the schema. The object {“properties”: {“foo”: [“bar”, “baz”]}} is not well-formatted.Here is the output schema:
{"properties": {"title": {"description": "文章的标题", "title": "Title", "type": "string"}, "content": {"description": "文章的内容", "title": "Content", "type": "string"}}, "required": ["title", "content"]}写一篇关于转基因油的文章
进行格式化输出
chain = prompt | llm_deepseek | json_output response = chain.invoke({"query": "写一篇关于转基因油的文章"}) print(response){‘title’: ‘转基因食用油:科学视角下的安全性与争议’, ‘content’: ‘转基因油是指从通过基因工程技术改良的油料作物(如转基因大豆、菜籽、玉米等)中提炼的食用油。这类作物通常被赋予抗虫害、抗除草剂或提高出油率等特性。\n\n科学共识表明,目前市场上批准的转基因食用油与传统食用油在营养成分和安全性上无本质差异。世界卫生组织(WHO)和联合国粮农组织(FAO)等机构基于大量研究指出,经严格审批的转基因食品不会对人类健康产生额外风险。转基因油在生产过程中需经过精炼处理,其中可能存在的转基因蛋白和DNA片段已被有效去除,最终产品主要成分为甘油三酯,与非转基因油无异。\n\n然而,转基因油仍面临公众认知争议。反对观点主要集中于生态影响(如基因漂移对生物多样性的潜在影响)和伦理争议。多国实行强制性转基因标识制度,以保障消费者知情权。\n\n从农业经济角度看,转基因技术可提高油料作物产量,降低农药使用量,有助于应对全球粮食安全挑战。未来需继续加强转基因作物的长期生态监测和公众科普,实现技术进步与风险管理的平衡。’}
2.3.2 自定义输出 XML
使用 XMLOutputParser 来提示模型生成 XML 输出,然后将输出解析为可用的格式.
因为用到的了 defusedxml,因此需要先安装
pip install defusedxml
示例:
from langchain_core.output_parsers import XMLOutputParser
from langchain_core.prompts import PromptTemplate
chat_openai = ChatOpenAI(model='gpt-4')
xml_parser = XMLOutputParser(tags=['movies', 'movie', 'name', 'publish-date'])
# 提示词模板对象,
# template:提示词模板
# {format_instructions}:格式化指令占位符,它通过 XMLOutputParser() 输出解析器调用方法 get_format_instructions() 得到
# {query}:用户问题的占位符,通过用户的输入得到
prompt = PromptTemplate(
template="回答用户的问题。\n{format_instructions}\n{query}\n",
partial_variables={"format_instructions": xml_parser.get_format_instructions()},
input_variables=["query"]
)
print(prompt.format(query="世界电影票房最高的电影列表,按照时间由近到远排序"))
回答用户的问题。
The output should be formatted as a XML file.
- Output should conform to the tags below.
- If tags are not given, make them on your own.
- Remember to always open and close all the tags.
As an example, for the tags [“foo”, “bar”, “baz”]:
- String “
” is a well-formatted instance of the schema.- String “
” is a badly-formatted instance.- String “
” is a badly-formatted instance.Here are the output tags:
['movies', 'movie', 'name', 'publish-date']世界电影票房最高的电影列表,按照时间由近到远排序
调用大模型输出
chain = prompt | chat_openai
response = chain.invoke({"query": "世界电影票房最高的电影列表,按照时间由近到远排序"})
print(response.content)
<?xml version="1.0" encoding="UTF-8"?> Avatar: The Way of Water 2022 Spider-Man: No Way Home 2021 Avengers: Endgame 2019 Avatar 2009 Titanic 1997
2.3.2 自定义输出 YAML
from langchain.output_parsers import YamlOutputParser
from langchain_core.prompts import PromptTemplate
from pydantic import BaseModel, Field
chat_openai = ChatOpenAI(model='gpt-4')
class Article(BaseModel):
title: str = Field(description="文章的标题")
content: str = Field(description="文章的内容")
yaml_output = YamlOutputParser(pydantic_object=Article)
# 提示词模板对象,
# template:提示词模板
# {format_instructions}:格式化指令占位符,它通过 YamlOutputParser() 输出解析器调用方法 get_format_instructions() 得到
# {query}:用户问题的占位符,通过用户的输入得到
prompt = PromptTemplate(
template="回答用户的问题。\n{format_instructions}\n{query}\n",
partial_variables={"format_instructions": yaml_output.get_format_instructions()},
input_variables=["query"]
)
# print(prompt.format(query="写一篇关于转基因油的文章"))
chain = prompt | chat_openai
response = chain.invoke({"query": "写一篇关于转基因油的文章"})
print(response.content)
title: 转基因油:科学视角下的安全性与争议
content: 转基因油是从经过基因工程技术改良的油料作物(如大豆、玉米、油菜)中提取的食用油。通过修改作物基因,可增强其抗病虫害能力、提高出油率或改善脂肪酸组成。目前主流科学机构(如世界卫生组织、美国食品药品监督管理局)认为经批准的转基因油与传统油品同样安全,因其成分与常规油无本质差异,且加工过程中转基因蛋白质和DNA已被去除。然而,公众对转基因食品的长期生态影响和健康风险仍存争议,部分国家和地区要求强制标识。消费者在选择时可根据自身认知和偏好,通过产品标签识别来源。
三、LangChain历史消息管理
跟聊天大模型进行对话,可以将对话的历史消息记录下来,在每次对话的过程中都带上历史消息,这样就使大模型有了记忆功能。
LangChain 中有四种类型的消息:
- SystemMessage 系统提示词
- HumanMessage 用户输入的消息
- AIMessage 大模型回答的消息
- ToolMessage 工具调用的消息
3.1 带历史消息的runnable
LangChain中提供了BaseChatMessageHistory对象来保持历史消息
# 定义一个字典chat_history_store保存历史消息,此时历史消息保存在内存中
chat_history_store = {}
def get_session_history(session_id: str) -> BaseChatMessageHistory:
""" 根据session_id获取历史消息 """
if session_id not in chat_history_store:
chat_history_store[session_id] = InMemoryChatMessageHistory()
return chat_history_store[session_id]
创建带历史消息的runnable
chat_model = ChatOpenAI(model='gpt-4')
chat_prompt_template = ChatPromptTemplate.from_messages([
SystemMessage(content="你是一个Python的资深编程专家,掌握了所有Python3的语法,能回答所有跟Python相关的编程问题.请用{language}回答我的问题"),
MessagesPlaceholder(variable_name="history"),
('human', '{input}')
])
chain = chat_prompt_template | chat_model | StrOutputParser()
# 创建一个带历史会话记录的runnable
history_runnable = RunnableWithMessageHistory(
chain,
get_session_history,
input_messages_key="input",
history_messages_key="history"
)
# 执行runnable
response = history_runnable.invoke(
{"language": "中文", "input": "Python的装饰器如何使用?"},
config={"configurable": {"session_id": "1"}}
)
print(response)
print('-' * 64)
# 执行runnable
response = history_runnable.invoke(
{"language": "中文", "input": "请解释的再详细一些"},
config={"configurable": {"session_id": "1"}}
)
3.2 给历史消息添加用户标识
每一个用户和大模型对话时,历史对话应该和用户绑定的。
# 定义一个字典chat_history_store保存历史消息,此时历史消息保存在内存中
chat_history_store = {}
def get_session_history(user_id: str, session_id: str) -> BaseChatMessageHistory:
""" 根据user_id, session_id获取历史消息 """
if (user_id, session_id) not in chat_history_store:
chat_history_store[(user_id, session_id)] = InMemoryChatMessageHistory()
return chat_history_store[(user_id, session_id)]
创建带用户标识和历史会话的runnable
# 创建一个带历史会话记录的runnable
history_runnable = RunnableWithMessageHistory(
chain,
get_session_history,
input_messsage_key="input",
history_message_key="history",
history_factory_config=[
ConfigurableFieldSpec(
id="user_id",
name="USER ID",
annotation="str",
default="1",
description="用户唯一标识",
is_shared=True
),
ConfigurableFieldSpec(
id="session_id",
name="SESSION ID",
annotation="str",
default="1",
description="会话唯一标识",
is_shared=True
)
]
)
# 执行runnable
response = history_runnable.invoke(
{"language": "中文", "input": "Python的装饰器如何使用?"},
config={"configurable": {"user_id": "wk", session_id": "1"}}
)
3.3 消息持久化
3.3.1 持久化到内存
# 定义一个字典 chat_history_store 保存历史消息,此时历史消息保存在内存中
chat_history_store = {}
def get_session_history(session_id: str) -> BaseChatMessageHistory:
""" 根据session_id获取历史消息 """
if session_id not in chat_history_store:
chat_history_store[session_id] = InMemoryChatMessageHistory()
return chat_history_store[session_id]
3.3.2 持久化到 Redis
-
配置redis环境
pip install --quiet redis -
安装依赖包
pip install langchain-community -
持久化消息
chain = chat_prompt_template | chat_model REDIS_URL = 'redis://:redis@192.168.1.17:6379/1' def get_message_history(session_id: str) -> RedisChatMessageHistory: return RedisChatMessageHistory(session_id, url=REDIS_URL)
3.3.3 持久化到关系数据库
使用 SQLite 数据库
def get_session_history_from_sqlite(session_id: str):
return SQLChatMessageHistory(
session_id=session_id,
connection_string="sqlite:///chatbot.db",
)
MySql 数据库
Python 中一个流行的 ORM 框架叫:sqlalchemy
3.4 裁切消息
如果每次和大模型对话都带上所有的历史消息,时间长了,历史消息就会非常庞大,因此要对历史消息进行裁剪。
# 保留最后两条对话记录
new_messages = sql_chat_message_history.messages[-2:]
sql_chat_message_history.messages.clear()
sql_chat_message_history.messages.append(summary_result)
for message in new_messages:
sql_chat_message_history.messages.append(message)
四、LangChain 表达式 LCEL
4.1 LCEL
LCEL(LangChain Expression Language) 其实就是用 “|” 运算符连接 LangChain 应用的各个组件。是一种声明式的方法连接 LangChain 组件。
-
流式调用
-
异步支持
使用LCEL表达式构建的链可以使用同步API和异步API进行调用。
-
优化并行执行
-
重试和回退
-
访问中间结果
-
输入和输出模式
输入和输出模式为每个LCEL链提供了从链的结构推断出Pydantic和JSON Schema模式。
4.2 Runable 协议
LangChain组件词模板、聊天模型、LLMs,输出解析器、检索器等实现了Runnable协议。
自定义组件实现Runnable协议:
def square(x: int) -> int:
return x ** 2
# 实现 runnable 协议
r_square = RunnableLambda(square)
标准方法
-
invoke() 同步调用,对输入调用链
# 调用 invoke() 方法 result = r_square.invoke(2) print(result)输出:4
-
batch() 对invoke的批量调用,对输入列表调用链
# 调用 batch() 方法 result = r_square.batch([2, 3, 4]) print(result)输出:4, 9, 16
-
stream() 流式调用,返回响应的数据块
def split_str(content: str): """ 生成器方法 """ for i in content.split(' '): yield i # 实现 runnable 协议 r_split_str = RunnableLambda(split_str) result = r_split_str.stream("I am a chinese.") # 返回一个生成器 for chunk in result: print(chunk)输出:
I
am
a
chinese
.
异步方法
异步方法,应该与asyncio和await语法以实现并发:
-
ainvoke()
-
abatch()
-
astream() 异步返回响应数据库
-
astream_log() 异步返回中间步骤,以及最终响应
-
astream_events() : beta 流式传输链中发生的事件(在 langchain-core 0.1.14 中引入)
输入类型 和 输出类型
| 组件 | 输入类型 | 输出类型 |
|---|---|---|
| 提示词 | 字典 | 提示值 |
| 聊天模型 | 单个字符串,聊天消息列表,提示值 | 聊天消息 |
| LLM | 单个字符串,聊天消息列表,提示值 | 字符串 |
| 输出解析器 | LLM 或 聊天模型输出 | 取决于解析器 |
| 检索器 | 单个字符串 | 文档列表 |
| 工具 | 单个字符串或字典,取决于工具 | 取决于工具 |
所有可运行对象都公开输入和输出模型,以检查输入和输出
-
input_schema: 从可运行对象结构自动生成输入Pydantic模型 -
output_schema: 从可运行对象结构自动生成输出Pydataic模型
流式运行对于使用LLM的应用程序对最终用户具有响应性至关重要。重要的LangChain原语,如聊天模型,输出解析器,提示模板,检索器和代理都实现了LangChain Runnable接口。该接口提供了两种通用的流式内容方法:
-
同步stream和异步astream: 流失传输链中的最终输出和默认实现
-
异步astream_events和异步astream_logs: 可以从链中流式传输中间步骤和最终输出的方式
4.3 流式输出
4.3.1 Stream(流)
所有 Runnable 对象都实现了一个名为stream 的同步方法和一个名为 astream的异步方法。这些方法旨在以块的形式流式传输最终输出,尽快返回每个块。只有在程序中的所有步骤都知道如何处理输入流时,才能进行流式传输;即逐个处理输入块,并产生相应的输出块。这种处理的复杂性可以有所不同,从简单的任务,如发出 LLM 生成的令牌,到更具挑战性的任务,如在整个JSON完成之前流式传输JSON 结果的部分。开始探索流式传输的最佳方法是从 LLM 应用程序中最重要的组件开始–LLM 本身!
chat_model = ChatOpenAI(model="gpt-4")
for chunk in chat_model.stream("你是谁?"):
"""chunk实际上是AIMessageChunk(content='', id=...),并且消息块chunk是可以叠加的 """
print(chunk.content, end=' ', flush=True)
我是 来自 阿里 云 的大 规模 语言 模型 , 我 叫 通 义 千 问 。
Chain和流式传输
链将任务的各个步骤链接起来,使用LangChain的表达式语言LCEL构建链。例如,将提示词、模型和解析器通过链起来。使用LCEL创建的链可以自动实现stream和astram,从而实现对最终输出的流式传输。事实上,使用LCEL创建的链实现了整个Runnable接口。
# 定义对话模型
chat_model = ChatOpenAI(model="gpt-4")
# 定义提示词模板
chat_prompt_template = ChatPromptTemplate.from_template("请给我解释一下什么是{content}")
# 定义输出解析器
str_parser = StrOutputParser()
# 定义链
chain = chat_prompt_template | chat_model | str_parser
# 定义异步函数
async def async_stream():
async for chunk in chain.astream({"content": "病毒"}):
print(chunk, end='', flush=True)
# 运行异步函数
asyncio.run(async_stream())
输出流JSON格式化
使用JsonOutputParser既可以对流式和非流式结果进行Json格式化输出。
chat_model = ChatOpenAI(model="gpt-4")
prompt_template = PromptTemplate.from_template("""
用JSON的格式输出{num}个学生的成绩单,最外层的标签是students,
每一个学生用student标签包裹,每个student包含name, subject, score。
""")
json_parser = JsonOutputParser()
chain = prompt_template | chat_model | json_parser
async def async_stream():
async for chunk in chain.astream({"num": 3}):
print(chunk, flush=True)
asyncio.run(async_stream())
4.3.2 Stream events(事件流)
langchain是一种事件驱动。
当流式传输正确实现时,对于可运行项的输入直到输出流完全消耗后才会知道。
事件
| 事件 | 名称 | 块 | 输入 | 输出 |
|---|---|---|---|---|
| on_chat_model_start | [模型名称] | {“messages”:[ [SystemMessage, HumanMessage] ]} | ||
| on_chat_model_end | [模型名称] | {“messages”:[ [SystemMessage, HumanMessage] ]} | AIMessageChunk(content=‘hello world’) | |
| on_llm_start | [模型名称] | {‘input’: ‘hello’} | ||
| on_llm_stream | [模型名称] | ‘hello’ | ||
| on_llm_end | [模型名称] | {‘input’: ‘human’} | ||
| on_chain_start | format_docs | |||
| on_chain_stream | format_docs | ‘hello world !, goodbye world !’ | ||
| on_chain_end | format_docs | |||
| on_tool_start | some_tool | |||
| on_tool_end | ||||
| on_retriever_start | {‘query’: ‘hello’} | |||
| on_retriever_end | {‘query’: ‘hello’} | [Document(…), …] | ||
| on_prompt_start | ||||
| on_prompt_end | ChatPromptValue(message: [SystemMessage, …]) |
查看chat_model_astream_events的事件
chat_model = ChatOpenAI(model='gpt-4')
async def async_stream():
async for event in chat_model.astream_events('病毒', version='v2'):
print(event)
asyncio.run(async_stream())
触发事件流如下
{'event': 'on_chat_model_start', 'data': {'input': '病毒'}, 'name': 'ChatOllama', 'tags': [], 'run_id': '181c4ce7-4750-4849-b5d6-903badd27923', 'metadata': {'ls_provider': 'ollama', 'ls_model_name': 'qwen:0.5b', 'ls_model_type': 'chat', 'ls_temperature': 0.8}, 'parent_ids': []}
{'event': 'on_chat_model_stream', 'run_id': '181c4ce7-4750-4849-b5d6-903badd27923', 'name': 'ChatOllama', 'tags': [], 'metadata': {'ls_provider': 'ollama', 'ls_model_name': 'gpt-4', 'ls_model_type': 'chat', 'ls_temperature': 0.8}, 'data': {'chunk': AIMessageChunk(content='病毒', additional_kwargs={}, response_metadata={}, id='run-181c4ce7-4750-4849-b5d6-903badd27923')}, 'parent_ids': []}
......
{'event': 'on_chat_model_stream', 'run_id': '181c4ce7-4750-4849-b5d6-903badd27923', 'name': 'ChatOllama', 'tags': [], 'metadata': {'ls_provider': 'ollama', 'ls_model_name': 'qwen:0.5b', 'ls_model_type': 'chat', 'ls_temperature': 0.8}, 'data': {'chunk': AIMessageChunk(content='', additional_kwargs={}, response_metadata={'model': 'qwen:0.5b', 'created_at': '2025-01-23T06:34:10.498514659Z', 'done': True, 'done_reason': 'stop', 'total_duration': 6560301289, 'load_duration': 2331005746, 'prompt_eval_count': 9, 'prompt_eval_duration': 86000000, 'eval_count': 100, 'eval_duration': 4140000000, 'message': Message(role='assistant', content='', images=None, tool_calls=None)}, id='run-181c4ce7-4750-4849-b5d6-903badd27923', usage_metadata={'input_tokens': 9, 'output_tokens': 100, 'total_tokens': 109})}, 'parent_ids': []}
{'event': 'on_chat_model_end', 'data': {'output': AIMessageChunk(content='病毒是一种特殊的微生物,它们具有自我复制的能力。病毒的传播方式多种多样,例如通过呼吸道飞沫传播、血液传播等。\n\n病毒对人类健康的影响主要体现在以下几个方面:\n\n1. 疾病传播:病毒可以通过空气、水、食物等方式进行传播。\n\n2. 传染病控制:通过科学合理的防治机制,有效控制各种传染病的发生和流行。\n\n3. 传染病教育:通过教育手段提高公众的健康素养,防止疾病的发生和流行。', additional_kwargs={}, response_metadata={'model': 'qwen:0.5b', 'created_at': '2025-01-23T06:34:10.498514659Z', 'done': True, 'done_reason': 'stop', 'total_duration': 6560301289, 'load_duration': 2331005746, 'prompt_eval_count': 9, 'prompt_eval_duration': 86000000, 'eval_count': 100, 'eval_duration': 4140000000, 'message': Message(role='assistant', content='', images=None, tool_calls=None)}, id='run-181c4ce7-4750-4849-b5d6-903badd27923', usage_metadata={'input_tokens': 9, 'output_tokens': 100, 'total_tokens': 109})}, 'run_id': '181c4ce7-4750-4849-b5d6-903badd27923', 'name': 'ChatOllama', 'tags': [], 'metadata': {'ls_provider': 'ollama', 'ls_model_name': 'qwen:0.5b', 'ls_model_type': 'chat', 'ls_temperature': 0.8}, 'parent_ids': []}
查看chain.astream_events()的事件
chat_model = ChatOllama(
base_url='http://192.168.1.17:11434',
model='qwen:0.5b',
temperature=0.8,
num_predict=256
)
chat_prompt_template = ChatPromptTemplate.from_template("""
用JSON的格式输出{num}个学生的成绩单,最外层的标签是students,
每一个学生用student标签包裹,每个student包含name, subject, score。
""")
json_output_parser = JsonOutputParser()
chain = chat_prompt_template | chat_model | json_output_parser
async def async_stream():
async for event in chain.astream_events({"num": 3}, version='v2'):
print(event)
asyncio.run(async_stream())
触发事件流如下
D:\opt\Python\Python310\python.exe D:\work\PycharmProject\demo-langchain\ollama\demo-ollama-stream-event.py
{'event': 'on_chain_start', 'data': {'input': {'num': 3}}, 'name': 'RunnableSequence', 'tags': [], 'run_id': '0205bf78-ebc2-4416-8540-335aa79e9f20', 'metadata': {}, 'parent_ids': []}
{'event': 'on_prompt_start', 'data': {'input': {'num': 3}}, 'name': 'ChatPromptTemplate', 'tags': ['seq:step:1'], 'run_id': '0ee313d4-416e-4f89-9acf-2a5d00b78961', 'metadata': {}, 'parent_ids': ['0205bf78-ebc2-4416-8540-335aa79e9f20']}
{'event': 'on_prompt_end', 'data': {'output': ChatPromptValue(messages=[HumanMessage(content='\n 用JSON的格式输出3个学生的成绩单,最外层的标签是students,\n 每一个学生用student标签包裹,每个student包含name, subject, score。\n ', additional_kwargs={}, response_metadata={})]), 'input': {'num': 3}}, 'run_id': '0ee313d4-416e-4f89-9acf-2a5d00b78961', 'name': 'ChatPromptTemplate', 'tags': ['seq:step:1'], 'metadata': {}, 'parent_ids': ['0205bf78-ebc2-4416-8540-335aa79e9f20']}
{'event': 'on_chat_model_start', 'data': {'input': {'messages': [[HumanMessage(content='\n 用JSON的格式输出3个学生的成绩单,最外层的标签是students,\n 每一个学生用student标签包裹,每个student包含name, subject, score。\n ', additional_kwargs={}, response_metadata={})]]}}, 'name': 'ChatOllama', 'tags': ['seq:step:2'], 'run_id': 'a8621d51-de51-4b93-835b-218c8805741d', 'metadata': {'ls_provider': 'ollama', 'ls_model_name': 'qwen:0.5b', 'ls_model_type': 'chat', 'ls_temperature': 0.8}, 'parent_ids': ['0205bf78-ebc2-4416-8540-335aa79e9f20']}
{'event': 'on_chat_model_stream', 'data': {'chunk': AIMessageChunk(content='{\n', additional_kwargs={}, response_metadata={}, id='run-a8621d51-de51-4b93-835b-218c8805741d')}, 'run_id': 'a8621d51-de51-4b93-835b-218c8805741d', 'name': 'ChatOllama', 'tags': ['seq:step:2'], 'metadata': {'ls_provider': 'ollama', 'ls_model_name': 'qwen:0.5b', 'ls_model_type': 'chat', 'ls_temperature': 0.8}, 'parent_ids': ['0205bf78-ebc2-4416-8540-335aa79e9f20']}
{'event': 'on_parser_start', 'data': {}, 'name': 'JsonOutputParser', 'tags': ['seq:step:3'], 'run_id': 'db06ef0f-bfa4-4f56-ba8d-25e955462912', 'metadata': {}, 'parent_ids': ['0205bf78-ebc2-4416-8540-335aa79e9f20']}
{'event': 'on_parser_stream', 'run_id': 'db06ef0f-bfa4-4f56-ba8d-25e955462912', 'name': 'JsonOutputParser', 'tags': ['seq:step:3'], 'metadata': {}, 'data': {'chunk': {}}, 'parent_ids': ['0205bf78-ebc2-4416-8540-335aa79e9f20']}
{'event': 'on_chain_stream', 'run_id': '0205bf78-ebc2-4416-8540-335aa79e9f20', 'name': 'RunnableSequence', 'tags': [], 'metadata': {}, 'data': {'chunk': {}}, 'parent_ids': []}
{'event': 'on_chat_model_stream', 'data': {'chunk': AIMessageChunk(content=' ', additional_kwargs={}, response_metadata={}, id='run-a8621d51-de51-4b93-835b-218c8805741d')}, 'run_id': 'a8621d51-de51-4b93-835b-218c8805741d', 'name': 'ChatOllama', 'tags': ['seq:step:2'], 'metadata': {'ls_provider': 'ollama', 'ls_model_name': 'qwen:0.5b', 'ls_model_type': 'chat', 'ls_temperature': 0.8}, 'parent_ids': ['0205bf78-ebc2-4416-8540-335aa79e9f20']}
{'event': 'on_chat_model_stream', 'data': {'chunk': AIMessageChunk(content=' "', additional_kwargs={}, response_metadata={}, id='run-a8621d51-de51-4b93-835b-218c8805741d')}, 'run_id': 'a8621d51-de51-4b93-835b-218c8805741d', 'name': 'ChatOllama', 'tags': ['seq:step:2'], 'metadata': {'ls_provider': 'ollama', 'ls_model_name': 'qwen:0.5b', 'ls_model_type': 'chat', 'ls_temperature': 0.8}, 'parent_ids': ['0205bf78-ebc2-4416-8540-335aa79e9f20']}
{'event': 'on_chat_model_stream', 'data': {'chunk': AIMessageChunk(content='students', additional_kwargs={}, response_metadata={}, id='run-a8621d51-de51-4b93-835b-218c8805741d')}, 'run_id': 'a8621d51-de51-4b93-835b-218c8805741d', 'name': 'ChatOllama', 'tags': ['seq:step:2'], 'metadata': {'ls_provider': 'ollama', 'ls_model_name': 'qwen:0.5b', 'ls_model_type': 'chat', 'ls_temperature': 0.8}, 'parent_ids': ['0205bf78-ebc2-4416-8540-335aa79e9f20']}
{'event': 'on_chat_model_stream', 'data': {'chunk': AIMessageChunk(content='":', additional_kwargs={}, response_metadata={}, id='run-a8621d51-de51-4b93-835b-218c8805741d')}, 'run_id': 'a8621d51-de51-4b93-835b-218c8805741d', 'name': 'ChatOllama', 'tags': ['seq:step:2'], 'metadata': {'ls_provider': 'ollama', 'ls_model_name': 'qwen:0.5b', 'ls_model_type': 'chat', 'ls_temperature': 0.8}, 'parent_ids': ['0205bf78-ebc2-4416-8540-335aa79e9f20']}
{'event': 'on_chat_model_stream', 'data': {'chunk': AIMessageChunk(content=' [\n', additional_kwargs={}, response_metadata={}, id='run-a8621d51-de51-4b93-835b-218c8805741d')}, 'run_id': 'a8621d51-de51-4b93-835b-218c8805741d', 'name': 'ChatOllama', 'tags': ['seq:step:2'], 'metadata': {'ls_provider': 'ollama', 'ls_model_name': 'qwen:0.5b', 'ls_model_type': 'chat', 'ls_temperature': 0.8}, 'parent_ids': ['0205bf78-ebc2-4416-8540-335aa79e9f20']}
{'event': 'on_parser_stream', 'run_id': 'db06ef0f-bfa4-4f56-ba8d-25e955462912', 'name': 'JsonOutputParser', 'tags': ['seq:step:3'], 'metadata': {}, 'data': {'chunk': {'students': []}}, 'parent_ids': ['0205bf78-ebc2-4416-8540-335aa79e9f20']}
...
{'event': 'on_chat_model_stream', 'data': {'chunk': AIMessageChunk(content='', additional_kwargs={}, response_metadata={'model': 'qwen:0.5b', 'created_at': '2025-01-23T06:46:12.065341232Z', 'done': True, 'done_reason': 'length', 'total_duration': 11462394384, 'load_duration': 1572176970, 'prompt_eval_count': 51, 'prompt_eval_duration': 209000000, 'eval_count': 256, 'eval_duration': 9677000000, 'message': Message(role='assistant', content='', images=None, tool_calls=None)}, id='run-a8621d51-de51-4b93-835b-218c8805741d', usage_metadata={'input_tokens': 51, 'output_tokens': 256, 'total_tokens': 307})}, 'run_id': 'a8621d51-de51-4b93-835b-218c8805741d', 'name': 'ChatOllama', 'tags': ['seq:step:2'], 'metadata': {'ls_provider': 'ollama', 'ls_model_name': 'qwen:0.5b', 'ls_model_type': 'chat', 'ls_temperature': 0.8}, 'parent_ids': ['0205bf78-ebc2-4416-8540-335aa79e9f20']}
{'event': 'on_chat_model_end', 'data': {'output': AIMessageChunk(content='{\n "students": [\n {\n "name": "John Doe",\n "subject": "Math",\n "score": 90\n },\n {\n "name": "Jane Smith",\n "subject": "English",\n "score": 85\n },\n {\n "name": "Bob Brown",\n "subject": "History",\n "score": 72\n }\n ],\n "grades": [\n {\n "name": "Average",\n "score": 40\n },\n {\n "name": "Lowest",\n "score": 55\n },\n {\n "name": "Median",\n "score": 62.5\n }\n ],\n "studentgroups": [\n {\n "group": "A",\n "students": [\n {\n "name": "John Doe",\n "subject": "Math",\n "score": 90\n },\n {\n "name": "Jane Smith",\n "subject": "English",\n "score": 85\n }\n ],\n "grades": [\n {\n "name": "Average",\n "score": 40\n },\n', additional_kwargs={}, response_metadata={'model': 'qwen:0.5b', 'created_at': '2025-01-23T06:46:12.065341232Z', 'done': True, 'done_reason': 'length', 'total_duration': 11462394384, 'load_duration': 1572176970, 'prompt_eval_count': 51, 'prompt_eval_duration': 209000000, 'eval_count': 256, 'eval_duration': 9677000000, 'message': Message(role='assistant', content='', images=None, tool_calls=None)}, id='run-a8621d51-de51-4b93-835b-218c8805741d', usage_metadata={'input_tokens': 51, 'output_tokens': 256, 'total_tokens': 307}), 'input': {'messages': [[HumanMessage(content='\n 用JSON的格式输出3个学生的成绩单,最外层的标签是students,\n 每一个学生用student标签包裹,每个student包含name, subject, score。\n ', additional_kwargs={}, response_metadata={})]]}}, 'run_id': 'a8621d51-de51-4b93-835b-218c8805741d', 'name': 'ChatOllama', 'tags': ['seq:step:2'], 'metadata': {'ls_provider': 'ollama', 'ls_model_name': 'qwen:0.5b', 'ls_model_type': 'chat', 'ls_temperature': 0.8}, 'parent_ids': ['0205bf78-ebc2-4416-8540-335aa79e9f20']}
{'event': 'on_parser_end', 'data': {'output': {'students': [{'name': 'John Doe', 'subject': 'Math', 'score': 90}, {'name': 'Jane Smith', 'subject': 'English', 'score': 85}, {'name': 'Bob Brown', 'subject': 'History', 'score': 72}], 'grades': [{'name': 'Average', 'score': 40}, {'name': 'Lowest', 'score': 55}, {'name': 'Median', 'score': 62.5}], 'studentgroups': [{'group': 'A', 'students': [{'name': 'John Doe', 'subject': 'Math', 'score': 90}, {'name': 'Jane Smith', 'subject': 'English', 'score': 85}], 'grades': [{'name': 'Average', 'score': 40}]}]}, 'input': AIMessageChunk(content='{\n "students": [\n {\n "name": "John Doe",\n "subject": "Math",\n "score": 90\n },\n {\n "name": "Jane Smith",\n "subject": "English",\n "score": 85\n },\n {\n "name": "Bob Brown",\n "subject": "History",\n "score": 72\n }\n ],\n "grades": [\n {\n "name": "Average",\n "score": 40\n },\n {\n "name": "Lowest",\n "score": 55\n },\n {\n "name": "Median",\n "score": 62.5\n }\n ],\n "studentgroups": [\n {\n "group": "A",\n "students": [\n {\n "name": "John Doe",\n "subject": "Math",\n "score": 90\n },\n {\n "name": "Jane Smith",\n "subject": "English",\n "score": 85\n }\n ],\n "grades": [\n {\n "name": "Average",\n "score": 40\n },\n', additional_kwargs={}, response_metadata={'model': 'qwen:0.5b', 'created_at': '2025-01-23T06:46:12.065341232Z', 'done': True, 'done_reason': 'length', 'total_duration': 11462394384, 'load_duration': 1572176970, 'prompt_eval_count': 51, 'prompt_eval_duration': 209000000, 'eval_count': 256, 'eval_duration': 9677000000, 'message': Message(role='assistant', content='', images=None, tool_calls=None)}, id='run-a8621d51-de51-4b93-835b-218c8805741d', usage_metadata={'input_tokens': 51, 'output_tokens': 256, 'total_tokens': 307})}, 'run_id': 'db06ef0f-bfa4-4f56-ba8d-25e955462912', 'name': 'JsonOutputParser', 'tags': ['seq:step:3'], 'metadata': {}, 'parent_ids': ['0205bf78-ebc2-4416-8540-335aa79e9f20']}
{'event': 'on_chain_end', 'data': {'output': {'students': [{'name': 'John Doe', 'subject': 'Math', 'score': 90}, {'name': 'Jane Smith', 'subject': 'English', 'score': 85}, {'name': 'Bob Brown', 'subject': 'History', 'score': 72}], 'grades': [{'name': 'Average', 'score': 40}, {'name': 'Lowest', 'score': 55}, {'name': 'Median', 'score': 62.5}], 'studentgroups': [{'group': 'A', 'students': [{'name': 'John Doe', 'subject': 'Math', 'score': 90}, {'name': 'Jane Smith', 'subject': 'English', 'score': 85}], 'grades': [{'name': 'Average', 'score': 40}]}]}}, 'run_id': '0205bf78-ebc2-4416-8540-335aa79e9f20', 'name': 'RunnableSequence', 'tags': [], 'metadata': {}, 'parent_ids': []}
async 同步和异步调用
async def tast1():
async for chunk in chat_model.astream('...'):
...
async def task2():
async for chunk in chat_model.astream('...'):
async def main():
''' 同步调用 '''
await task1()
await task2()
# 异步调用
await asyncio.gather(task1(), task2())
# 运行主函数
asyncio.run(main())
4.4 组合链
组合链是通过符号 “|” 将 runnable 组件链起来
串行链
from langchain_core.runnables import RunnableLambda
def double_int(x: int) -> int:
return x * 2
def square_int(x: int) -> int:
return x ** 2
r_double_int = RunnableLambda(double_int)
r_square_int = RunnableLambda(square_int)
chain = r_double_int | r_square_int
result = chain.invoke(2)
print(result)
输出:16
并行链
from langchain_core.runnables import RunnableLambda, RunnableParallel
def double_int(x: int) -> int:
return x * 2
def square_int(x: int) -> int:
return x ** 2
r_double_int = RunnableLambda(double_int)
r_square_int = RunnableLambda(square_int)
# 并行链(key1是键,值是:r_double_int;key2是键,值是:r_square_int)
chain = RunnableParallel(key1=r_double_int, key2=r_square_int)
result = chain.invoke(3, config={'max_concurrency': 100})
print(result)
输出:{‘key1’: 6, ‘key2’: 9}
打印链的图像描述
# 打印链的图像描述(需要安装 grandalf,pip install grandalf)
chain.get_graph().print_ascii()
+--------------------------+
| Parallel<key1,key2>Input |
+--------------------------+
*** ***
** **
** **
+------------+ +------------+
| double_int | | square_int |
+------------+ +------------+
*** ***
** **
** **
+---------------------------+
| Parallel<key1,key2>Output |
+---------------------------+
合并并处理中间数据
# 合并出来中间数据
r1 = RunnableLambda(lambda x: {'key1': x})
r2 = RunnableLambda(lambda y: y['key1'] + 10)
chain_1 = r1 | r2
result = chain_1.invoke(2)
print(result) # 输出: 12
RunnablePassthrough 传入一个字典数据。RunnablePassthrough.assign(key=value)给字典添加额外的键,。
chain_2 = r1 | RunnablePassthrough.assign(key2=r2)
result = chain_2.invoke(3)
print(result) # 输出:{'key1': 3, 'key2': 13}
RunnablePassthrough.pick() 选择只需要的数据
chain_3 = r1 | RunnablePassthrough.assign(key2=r2) | RunnablePassthrough().pick('key2')
result = chain_3.invoke(3)
print(result) # 输出:13
4.5 后备选型
后备选项是一种在紧急情况下使用的替代方案。
def double_int(x):
print('in double int')
return x + 2
def double_fallback(x):
print('in double_fallback')
return int(x) + 2
r_double_fallback = RunnableLambda(double_fallback)
chain_4 = r_double_int.with_fallbacks([r_double_fallback]) # 先执行 r_double_int,如果报错执行后备的 r_double_fallback
result = chain_4.invoke('2')
print(result)
输出:
in double int
in double_fallback
4
4. 6 动态构建链路
4.7 生命周期管理
def on_start(runner: Run):
"""当节点启动的时候自动执行"""
print(f'节点启动的时间:{runner.start_time}')
def on_end(runner: Run):
"""当节点结束的时候自动执行"""
print(f'节点启动的时间:{runner.end_time}')
r1 = RunnableLambda(double_int)
chain = r1.with_listeners(on_start=on_start, on_end=on_end)
result = chain.invoke(2)
print(result)
五、多模态输入
LangChain 支撑多模态输入和自定义输出。
5.1 多模态输入
5.1.1 图片输入
最长支持的传入图片的方式是将其做为字节串输入。
-
传入图片地址
from langchain_core.messages import HumanMessage lim_dashscope = ChatOpenAI( base_url=os.getenv("DASHSCOPE_BASE_URL"), api_key=os.getenv("DASHSCOPE_API_KEY"), model="qwen-vl-max", ) image_url = 'http://gips3.baidu.com/it/u=3886271102,3123389489&fm=3028&app=3028&f=JPEG&fmt=auto?w=1280&h=960' human_message = HumanMessage( content=[ {"type": "text", "text": "请用中文表示这张图片"}, {"type": "image_url", "image_url": image_url}, ] ) response = lim_dashscope.invoke([human_message]) print(response)content=‘这张图片展示了一位身穿优雅白色礼服的女性,她的长发呈波浪状披散在肩上,发型精致,带有复古风格。她佩戴着精美的耳环,面容端庄,眼神深邃,整体气质高贵典雅。背景是一个装饰华丽的室内环境,灯光柔和,悬挂着多盏云朵形状的吊灯,营造出温馨而浪漫的氛围。场景可能是一个高档餐厅或宴会厅,桌椅整齐排列,细节丰富。右下角有“AI创作”的标识,表明这是一幅由人工智能生成的艺术作品。’ additional_kwargs={‘refusal’: None} response_metadata={‘token_usage’: {‘completion_tokens’: 122, ‘prompt_tokens’: 1216, ‘total_tokens’: 1338, ‘completion_tokens_details’: None, ‘prompt_tokens_details’: None}, ‘model_name’: ‘qwen-vl-max’, ‘system_fingerprint’: None, ‘id’: ‘chatcmpl-4b1d1875-8439-4078-8a62-a6b8f88bdfcf’, ‘service_tier’: None, ‘finish_reason’: ‘stop’, ‘logprobs’: None} id=‘run–49fe0930-f7f0-4929-8080-baa56f550eac-0’ usage_metadata={‘input_tokens’: 1216, ‘output_tokens’: 122, ‘total_tokens’: 1338, ‘input_token_details’: {}, ‘output_token_details’: {}}
-
传入 base64 编码的图片
import httpx import base64 from langchain_core.messages import HumanMessage from lm.llm import lim_dashscope image_url = 'http://gips3.baidu.com/it/u=3886271102,3123389489&fm=3028&app=3028&f=JPEG&fmt=auto?w=1280&h=960' image_data = base64.b64encode(httpx.get(image_url).content).decode("utf-8") human_message = HumanMessage( content=[ {"type": "text", "text": "请用中文表示这张图片"}, {"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{image_data}"}}, ] ) response = lim_dashscope.invoke([human_message]) print(response)content=‘这张图片展示了一位穿着白色礼服的女性。她的头发是深棕色的,卷曲且梳理得非常精致,显得优雅而高贵。她佩戴着一对精美的耳环,表情温柔,目光直视前方,给人一种宁静和自信的感觉。\n\n背景是一个室内场景,装饰华丽,灯光柔和,营造出一种温馨而浪漫的氛围。背景中可以看到一些模糊的吊灯和家具,显示出这是一个高档的场所,可能是餐厅或宴会厅。\n\n整体画面色调温暖,细节丰富,给人一种梦幻般的感觉。右下角有“AI创作”的标志,表明这是一幅由人工智能生成的艺术作品。’ additional_kwargs={‘refusal’: None} response_metadata={‘token_usage’: {‘completion_tokens’: 132, ‘prompt_tokens’: 1246, ‘total_tokens’: 1378, ‘completion_tokens_details’: None, ‘prompt_tokens_details’: None}, ‘model_name’: ‘qwen-vl-max’, ‘system_fingerprint’: None, ‘id’: ‘chatcmpl-ce6cc316-97a0-4f10-9428-95e8f89d7319’, ‘service_tier’: None, ‘finish_reason’: ‘stop’, ‘logprobs’: None} id=‘run–10f8b695-b7a2-4b3f-8bf9-8d371655f4d7-0’ usage_metadata={‘input_tokens’: 1246, ‘output_tokens’: 132, ‘total_tokens’: 1378, ‘input_token_details’: {}, ‘output_token_details’: {}}
-
传入多张图片
image_url = 'http://gips3.baidu.com/it/u=3886271102,3123389489&fm=3028&app=3028&f=JPEG&fmt=auto?w=1280&h=960' image_data = base64.b64encode(httpx.get(image_url).content).decode("utf-8") human_message = HumanMessage( content=[ {"type": "text", "text": "这两张图片一样吗?"}, {"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{image_data}"}}, {"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{image_data}"}}, ] ) response = lim_dashscope.invoke([human_message]) print(response)content=‘是的,这两张图片完全一样。\n\n从视觉上看,它们在人物外貌、发型、服装、背景、灯光效果以及整体构图上都没有任何差异。甚至连右下角的“AI创作”水印也完全一致。这说明两幅图像实际上是同一张图片的重复展示。\n\n可能的原因是上传时误操作或系统重复加载了同一文件。’ additional_kwargs={‘refusal’: None} response_metadata={‘token_usage’: {‘completion_tokens’: 84, ‘prompt_tokens’: 2418, ‘total_tokens’: 2502, ‘completion_tokens_details’: None, ‘prompt_tokens_details’: None}, ‘model_name’: ‘qwen-vl-max’, ‘system_fingerprint’: None, ‘id’: ‘chatcmpl-329248d5-ea4c-4493-8a79-64e0f4d673d9’, ‘service_tier’: None, ‘finish_reason’: ‘stop’, ‘logprobs’: None} id=‘run–26b29fa5-080e-40ff-b36b-4d98c28f2f74-0’ usage_metadata={‘input_tokens’: 2418, ‘output_tokens’: 84, ‘total_tokens’: 2502, ‘input_token_details’: {}, ‘output_token_details’: {}}
六、工具
在构建代理时,需要为其提供一个工具列表,以便代理可以使用这些工具。
工具的组成
| 属性 | 类型 | 描述 |
|---|---|---|
| name | str | 工具的名称,必须唯一 |
| description | str | 对工具的描述。LLM或Agent使用描述做为上下文 |
| args_schema | Pydantic BaseModel | 可选但建议,用于提供更多信息(如few-shot)或验证预期参数 |
| return_direct | boolean | 仅对代理相关。当为True时,在调用工具后,会将结果直接返回给用户 |
6.1 创建工具
创建工具有以下几种方式:
- 使用@tool装饰器
- 使用Structured.from_function类方法
- 通过继成BaseTool类
6.1.1 @tool装饰器
使用函数名做为工具的名称(但可以使用第一个参数自定义),使用函数的文档做为工具的描述。
基本工具
from langchain_core.tools import tool
@tool
def multiply(a: int, b: int) -> int:
"""两个数相乘
Args:
a: 第一个输入的数字
b: 第二个输入的数字
Returns:
返回两个数的运算结果
"""
return a * b
print(f"工具名称:{multiply.name}")
print(f"工具描述:{multiply.description}")
print(f"工具参数:{multiply.args}")
print(f"工具schema: {multiply.args_schema.model_json_schema()}")
指定 args_schema
class CalculatorInput(BaseModel):
a: int = Field(description="第一个数")
b: int = Field(description="第二个数")
@tool("add_tool", args_schema=CalculatorInput)
def add(a: int, b: int) -> int:
""" 两个数相(异步方式) """
return a + b
给模型绑定工具
from langchain_openai.chat_models import ChatOpenAI
chat_openai = ChatOpenAI(model='gpt-4')
tools = [multiply]
chat_openai_with_tools = llm_deepseek.bind_tools(tools) # 给大模型绑定工具
tool_call = chat_openai_with_tools.invoke("3 * 3 = ?")
print(tool_call.tool_calls)
6.1.2 StructuredTool
StructuredTool.from_function类方法提供了比@tool装饰器更多的可配置性。适用于需要动态创建工具的场景。
class CalculatorInput(BaseModel):
a: int = Field(description="第一个数")
b: int = Field(description="第二个数")
# 定义工具
multiply_tool = StructuredTool.from_function(
func=multiply,
name="Calculator",
description="计算两个数据的乘积",
args_schema=CalculatorInput,
return_direct=True,
# coroutine=异步方法
)
同一个工具定义了两个相同的方法,一个同步用于同步调用,一个用于异步调用
def multiply(a: int, b: int) -> int:
"""Multiply two numbers together."""
return a * b
async def amultiply(a: int, b: int) -> int:
"""Multiply two numbers together."""
return a * b
async def main():
calculator_tool = StructuredTool.from_function(func=multiply, coroutine=amultiply)
print(calculator_tool.invoke(input={"a": 2, "b": 3}))
print(await calculator_tool.ainvoke(input={"a": 2, "b": 3}))
asyncio.run(main())
处理工具的错误
在 工具内抛出ToolException,使用handle_tool_error指定错误的处理函数。
def multiply(a: int, b: int) -> int:
"""Multiply two numbers together."""
if a == 2:
raise ToolException("a can not be 2")
return a * b
calculator_tool = StructuredTool.from_function(
func=multiply,
# 如果设置为True,则返回ToolException的文本,False会抛出ToolException
handle_tool_error=True
)
print(calculator_tool.invoke({"a": 2, "b": 3}))
或者直接使用hanle_tool_error=“错误描述”
calculator_tool = StructuredTool.from_function(
func=multiply,
# 如果设置为True,则返回ToolException的文本,False会抛出ToolException
handle_tool_error="自定义错误"
)
使用自定义函数处理异常
def error_process(error: ToolException):
print(f'这里是自定义异常处理函数,错误信息{error.args[0]}')
calculator_tool = StructuredTool.from_function(
func=multiply,
# 如果设置为True,则返回ToolException的文本,False会抛出ToolException
handle_tool_error=error_process
)
6.1.3 继承BaseTool
可以通过继承类 BaseTool 来自定义工具。
class Tool(BaseTool):
pass
6.2 官方工具
6.2.1 lanchain 官方工具
https://python.langchain.com/v0.2/docs/integrations/tools/
-
维基百科工具
安装依赖
pip install -qU wikipedia工具调用
# Wikipedia工具 api_wrapper = WikipediaAPIWrapper(top_k_results=1, doc_content_chars_max=1000) tool = WikipediaQueryRun(api_wrapper=api_wrapper) print(tool.invoke({"query": "日本人的祖先是谁?"}))
6.2.2 lanchain 内置工具包
工具包将工具整合在一起,以执行特定的任务。所有工具包都公开了get_tools方法返回一个工具列表。
使用工具包
# 初始化工具包
toolkit = ExampleTookit(...)
# 获取工具包列表
tools = toolkit.get_tools()
sqlite 工具包
from langchain.agents import AgentType
from langchain_community.agent_toolkits import SQLDatabaseToolkit, create_sql_agent
from langchain_community.utilities import SQLDatabase
from lm.llm import llm_deepseek
db = SQLDatabase.from_uri("sqlite:///langchain.db")
toolkit = SQLDatabaseToolkit(db=db, llm=llm_deepseek)
tools = toolkit.get_tools()
agent_executor = create_sql_agent(
llm=llm_deepseek,
toolkit=toolkit,
verbose=False,
agent_executor=AgentType.OPENAI_FUNCTIONS,
)
result = agent_executor.invoke("描述一下这张表的结构:full_llm_cache")
print(result)
七、智能体 Agent
语言模型本身无法执行动作,只能输出文本。代理(Agent)是使用大语言模型做为推理引擎来确定要执行什么操作,以及这些操作的输入是什么。操作的结果可以反馈到代理中,以便代理决定是否需要更多的操作或者是否可以结束。
Agent对命令进行规划,然后将其拆分为若干个任务,再去调用工具完成任务。
翁荔提出著名的Agent开发公式(Agent=大模型+记忆+主动规划+工具使用)
7.1 创建智能体
需求描述
我们现在要让大模型帮我们计算,要么是a, b两个数据的相加,要么是a, b两个数据相乘的结果。
-
首先定义大模型和两个方法:加法add, 乘法multiply
from langchain.agents import create_tool_calling_agent, AgentExecutor from langchain_core.prompts import ChatPromptTemplate from langchain_ollama.chat_models import ChatOllama from langchain_core.tools import StructuredTool from pydantic import BaseModel, Field # 定义模型 chat_model = ChatOpenAI(model='gpt-4') # 加法方法 def add(a: int, b: int) -> int: """计算加法""" print("执行加法运算") return a + b # 乘法方法 def multiply(a: int, b: int) -> int: """计算乘法""" print("执行乘法运算") return a * b -
再定义两个工具 add_tool, multiply_tool
class AddInputSchema(BaseModel): a: int = Field(description="第一个数") b: int = Field(description="第二个数") # 加法工具 add_tool = StructuredTool.from_function( name="add", func=add, description="计算加法", args_schema=AddInputSchema, return_direct=True, ) class MultiplyInputSchema(BaseModel): a: int = Field(description="第一个数") b: int = Field(description="第二个数") # 乘法工具 multiply_tool = StructuredTool.from_function( name="multiply", func=multiply, description="计算乘法", args_schema=MultiplyInputSchema, return_direct=True, ) -
测试一下模型是否能够根据输入能够解析出要调用的工具
# 工具集 tools = [add_tool, multiply_tool] # 模型绑定工具 model_with_tools = chat_model.bind_tools(tools=tools) # 模型通过推理决定是否要调用工具 # response = model_with_tools.invoke([HumanMessage(content='计算加法:{"a": 1, "b": 2}')]) # response = model_with_tools.invoke(input='计算乘法,{"a": 1, "b": 2}') print(f'模型返回的结果:{response.content}') print(f'工具返回的结果:{response.tool_calls}') -
创建模型的代理,执行工具调用
# 创建提示模板,包含 agent_scratchpad 变量 prompt_template = ChatPromptTemplate.from_template( "我想要做这个操作:{action}。\n{agent_scratchpad}" ) # 创建代理 agent = create_tool_calling_agent( llm=chat_model, tools=tools, prompt=prompt_template ) # 创建代理执行器 agent_executor = AgentExecutor(agent=agent, tools=tools) # 执行 result = agent_executor.invoke({"action": "计算乘法{'a': 3, 'b': 6}"}) print(result)执行结果:
执行乘法运算
{‘action’: “计算乘法{‘a’: 3, ‘b’: 6}”, ‘output’: 18}
7.2 智能体添加记忆
TODO
7.3 智能体添加会话
TODO
八、MCP
8.1 MCP Server 端开发
-
安装 langchain 提供的 mcp 依赖包
# 安装之后,默认还会安装 mcp 的包 pip install langchain-mcp-adapters # 但是,实际企业用 fastmcp 的比较多;我们选择用 fastmcp, 因此还要再安装 fastmcp pip install fastmcp -
定义 MCP Server 和 MCP 服务器
from datetime import datetime from fastmcp import FastMCP server = FastMCP() @server.tool() def get_current_date(): """获取当前的日期""" print(f'执行了工具get_current_date()') return datetime.today().now().strftime("%Y/%m/%d %H:%M:%S") if __name__ == '__main__': server.run(transport="sse", host="0.0.0.0", port=8000)
8.2 MCP Client 端开发
-
安装依赖包
# 安装之后,默认还会安装 mcp 的包 pip install langchain-mcp-adapters -
客户端
import asyncio from langchain_mcp_adapters.client import MultiServerMCPClient async def main(): # 创建带有多个 MCP Server 的 MCP Client mcp_client = MultiServerMCPClient( { "calculator": { "url": "http://localhost:8000/sse", "transport": "sse", } } ) # 获取所有工具 tools = await mcp_client.get_tools() for tool in tools: print(tool) # 获取提示词,每次只能获取一个 translate_prompt = await mcp_client.get_prompt(server_name="calculator", prompt_name="translate", arguments={'content': 'good'}) # 获取所有资源 resources = await mcp_client.get_resources(server_name="calculator") if __name__ == '__main__': asyncio.run(main())
九、RAG
Retrieval Argumented Generation 检索增强生成。
核心原理
RAG 主要分为两个阶段:
-
索引阶段
-
加载(Load)
首先需要加载数据。通过文档加载器 Document Loaders 完成。
-
切片(Split)
接下来用文本切割器 Text splitters 将大型文本(Documents)切分成一个个小块(chunks)。
-
存储(Store)
再下来将小块进行
向量化并存储
-
-
检索阶段
-
检索(Retrieve)
根据用户的输入,使用检索器 Retriever 从存储中检索相关的文本片段。
-
生成(Generate)
使用大模型对检索到的问题来生成答案。
-
9.1 加载器
官方加载器 API 文档:
https://python.langchain.com/api_reference/community/document_loaders.html
使用langchain的加载器,需要安装langchain-community包。
pip install langchain-community
9.1.1 文本加载器
使用文本加载器TextLoader加载文本文档
loader = TextLoader(file_path="./新春走基层.txt", encoding="utf-8")
source_documents = loader.load()
# print(source_documents)
splitter = RecursiveCharacterTextSplitter(
chunk_size=500,
chunk_overlap=50
)
slice_documents = splitter.split_documents(documents=source_documents)
print(slice_documents)
9.1.2 Markdown 加载器
markdown 加载器使用UnstructuredMarkdownLoader
-
安装依赖
uv add unstructured -
下载 nltk_data
9.1.3 PDF 加载器
9.1.4 Word 加载器
9.1.5 WebBaseLoader
from langchain_community.document_loaders import WebBaseLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain.globals import set_verbose
set_verbose()
# 定义Web加载器
loader = WebBaseLoader('https://news.qq.com/rain/a/20250127A00HJS00')
# 获取加载数据
source_documents = loader.load()
# print(documents)
# 使用文本分割器对数据进行分割
slice_documents = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200).split_documents(source_documents)
print(slice_documents)
9.2 向量相数据库
9.2.1 Chroma
官方文档:
https://docs.trychroma.com/
Chroma 英文单词的含义是:(色彩的)浓度,色度。是一个以人工智能为基础的开源的向量数据库,它专为大规模语义搜索和相似度查找而设计。
向量数据库通常用于处理高维空间中的数据,例如自然语言处理(NLP)中的词嵌入向量、图像处理中的特征向量等。Chroma通过提供高效的向量索引和查询功能,支持快速查找与查询向量最相似的数据项。
Chroma的主要特点包括:
-
高性能
Chroma 利用 FAISS(Facebook AI Similarity Search)库来提供高效的相似度搜索,支持多种索引策略,如Flat, IVF(Inverted File with Vectors),HNSW(Hierarchical Navigable Small World Graph)等,以适应不同的查询性能和内存需求。
-
易于使用
Chroma 提供了一个简洁的Python API,使得集成到现有的Python应用程序中变得非常容易。它支持多种数据格式,如NumPy数组、Pandas DataFrame等。
-
灵活的数据结构
支持存储任意数量的向量和与之关联的元数据。用户可以灵活地添加、更新和删除向量及其元数据。
-
支持大规模数据
通过使用有效的索引技术和分布式存储(如Milvus),Chroma 能够处理大规模的向量数据集。
-
可扩展性
Chroma 可以通过简单的配置支持水平扩展,以适应不断增长的数据需求。
Chroma 可以以多种模式运行。以下是每种模式的示例,均与 LangChain 集成使用:
in-memory在 Python 脚本或 Jupyter 笔记中in-memory with persistance在脚本或笔记中保存/加载到磁盘in a docker container做为在本地机器或云中运行的服务器
可以进行的操作:
.add.get.update.upsert.delete.peek.query
简单使用
-
安装依赖
uv add langchain-chroma -
创建向量数据库
用Chroma构造函数创建向量数据库
vector_store = Chroma(persist_directory=cls.default_persist_directory, embedding_function=embedding_model)
从documents创建向量数据库
vector_store = Chroma.from_documents(documents, embedding)
- 从网页爬取数据
LangChain支持从网页、向量数据库或其它来源检索数据。
Chroma
Chroma是一个开源的向量数据库,主要有以下几个功能:
-
向量存储
Chroma能够有效地存储高维向量数据
-
相似度搜索
支持向量的相似度搜索,能够快速的找出与给定向量相似的向量数据。常见的相似度度量方法如:余弦相似度。
-
元数据管理
除了存储向量数据,Chroma还允许为每个向量关联元数据。这些元数据包含向量的额外信息,如文本来源,图像拍摄时间等
处理步骤
-
安装依赖
pip install langchain-chroma -
准备数据
这里假定数据存储在data模块的documents中
from langchain_core.documents import Document # 测试数据 documents = [ # 每一个Document代表一个文档 Document( page_content="狗是伟大的伴侣,以其忠诚和友好而闻名", metadata={"source": "哺乳动物宠物文档"} ), Document( page_content="猫是独立的宠物,通常喜欢自己的空间。", metadata={"source": "哺乳动物宠物文档"} ), Document( page_content="金鱼是初学者的流行宠物,需要相对简单的护理。", metadata={"source": "鱼类宠物文档"} ), Document( page_content="鹦鹉是聪明的鸟类,能够模仿人类的语言。", metadata={"source": "鸟类宠物文档"} ), Document( page_content="兔子是社交动物,需要足够的空间跳跃。", metadata={"source": "哺乳动物宠物文档"} ) ] -
创建向量空间,进行检索
from langchain_chroma import Chroma from langchain_ollama import OllamaEmbeddings from data import documents embedding = OllamaEmbeddings( base_url="http://192.168.1.17:11434", model="shaw/dmeta-embedding-zh:latest" ) # 创建向量空间(from_documents表示从文档创建一个向量数据库) vector_store = Chroma.from_documents( documents=documents, embedding=embedding, persist_directory="./chroma_db", ) # 相似度搜索 # vector_store.similarity_search("猫的特定", k=3) # 查询相似度以及分数(分数越低,相似度越高) print(vector_store.similarity_search_with_score("猫的特点"))结果如下
[ (Document(id='80bd3c1a-2e4e-493f-9a8d-9c0679d20d1d', metadata={'source': '哺乳动物宠物文档'}, page_content='猫是独立的宠物,通常喜欢自己的空间。'), 0.6414466358530113), (Document(id='adae7716-dbad-42a9-9efc-9f8e2766acf0', metadata={'source': '鱼类宠物文档'}, page_content='金鱼是初学者的流行宠物,需要相对简单的护理。'), 1.0955419781402238), (Document(id='b981d450-d9c7-4086-9ef7-73da8a5b8d77', metadata={'source': '哺乳动物宠物文档'}, page_content='兔子是社交动物,需要足够的空间跳跃。'), 1.1622589850758573), (Document(id='6331af8d-e35b-4f1c-8b5d-dd39916d3695', metadata={'source': '哺乳动物宠物文档'}, page_content='狗是伟大的伴侣,以其忠诚和友好而闻名'), 1.164206507630021) ]定义RunnableSerializable接口检索
# 定义一个实现Runnable的检索器,以便可以加入chain retriever = RunnableLambda(vector_store.similarity_search).bind(k=1) # 调用检索器 print(retriever.invoke("猫的特点")) # 批量调用 print(retriever.batch(["猫的特点", "狗的特点"]))说明:创建retriver的另一个方法retriever = vector_store.as_retriever().bind(k=1)结果如下
[Document(id='2a7325c0-7ff6-4d19-90d2-613a546ed1aa', metadata={'source': '哺乳动物宠物文档'}, page_content='猫是独立的宠物,通常喜欢自己的空间。')] [[Document(id='2a7325c0-7ff6-4d19-90d2-613a546ed1aa', metadata={'source': '哺乳动物宠物文档'}, page_content='猫是独立的宠物,通常喜欢自己的空间。')], [Document(id='6331af8d-e35b-4f1c-8b5d-dd39916d3695', metadata={'source': '哺乳动物宠物文档'}, page_content='狗是伟大的伴侣,以其忠诚和友好而闻名')]] -
与大预言模型整合
# 创建提示词模板 chat_prompt_template = ChatPromptTemplate.from_messages( [ ("human", """ 使用提供的上下文仅回答这个问题: {question} 上下文: {context} """) ] ) chat_model = ChatOllama( base_url="http://192.168.1.17:11434", model="qwen2.5:0.5b", temperature=0.1, max_tokens=512 ) # RunnablePassthrough 默认会将输入数据原样传递到下游l pre_prompt = {'question': RunnablePassthrough(), 'context': retriever} # 使用chain链接 chain = pre_prompt | chat_prompt_template | chat_model print(chain.invoke(input="请介绍一下猫"))创建chain的另一种方式
# 定义提示词模板 chat_prompt_template = ChatPromptTemplate.from_messages( [ ("system", """ 你是一个专注于回答问题的助手。使用下面提供的内容来回答提出的问题,如果你不知道就回答:很抱歉,我没有检索到您要的答案。 {context} """), # MessagesPlaceholder("chat_history"), ("human", "{input}") ] ) # 定义prompt和model chain chat_chain = create_stuff_documents_chain(chat_model, chat_prompt_template) # 定义retrieval chain chain = create_retrieval_chain(retriever, chat_chain) print(chain.invoke({'input': '请介绍一下猫?'}))
结果
```python
content='猫是一种常见的宠物,它们通常是独立的个体,并且喜欢自己的空间。' additional_kwargs={} response_metadata={'model': 'qwen2.5:0.5b', 'created_at': '2025-01-28T04:00:57.150148626Z', 'done': True, 'done_reason': 'stop', 'total_duration': 1130989582, 'load_duration': 19552926, 'prompt_eval_count': 111, 'prompt_eval_duration': 459000000, 'eval_count': 17, 'eval_duration': 640000000, 'message': Message(role='assistant', content='', images=None, tool_calls=None)} id='run-270effc1-e8fd-4d23-8f82-b49fd17a9be9-0' usage_metadata={'input_tokens': 111, 'output_tokens': 17, 'total_tokens': 128}
- 加载向量数据库
加载已经存在的向量数据库
vectorstore = Chroma(persist_directory=persist_dir, embedding_function=embedding)
9.2.2 Faiss
FAISS(Facebook AI Similarity Search)是由 Facebook(现 Meta)开发的一个用于高效相似性搜索和密集向量聚类的开源库,在处理大规模向量数据时具有显著优势,具有以下特点
-
高效利用硬件资源:它支持在 CPU 和 GPU 上运行,能够充分利用硬件的并行计算能力
-
多种索引类型:
FAISS 提供了多种不同的索引类型,如 IndexFlatL2(基于欧几里得距离的暴力搜索索引)、IndexHNSWFlat(基于 Hierarchical Navigable Small World 图的索引,适用于近似搜索)、IndexIVFFlat(基于倒排文件的索引,结合了聚类和暴力搜索)等
安装cpu版本
pip install faiss-cpu
安装GPU版本
pip install faiss-gpu
创建Faiss索引
首先,我们需要创建一个 Faiss 索引。Faiss 支持多种索引类型,不同的索引类型适用于不同的应用场景。最常见的索引类型包括:
- Flat:精确的暴力索引
- IVF(Inverted File):更高效的近似索引,适合中到大规模数据
- HNSW(Hierarchical Navigable Small World):高效的近似索引,适合大规模数据
创建一个 Flat 索引
import faiss
import numpy as np
# 创建一个随机的 10000 条 128 维向量
d = 128 # 向量维度
nb = 10000 # 向量数量
np.random.seed(1234)
xb = np.random.random((nb, d)).astype('float32')
# 创建一个 Faiss 索引
index = faiss.IndexFlatL2(d) # 使用 L2 距离度量创建 Flat 索引
# 向索引中添加向量
index.add(xb)
print("Number of vectors in the index:", index.ntotal)
创建一个 IVF 索引
# 创建一个 IVF 索引(适用于更大规模数据集)
nlist = 100 # 索引分桶的数量
quantizer = faiss.IndexFlatL2(d) # 使用 Flat 索引作为量化器
index_ivf = faiss.IndexIVFFlat(quantizer, d, nlist, faiss.METRIC_L2)
# 训练索引(需要一个大部分数据的子集来训练索引)
index_ivf.train(xb)
# 向索引中添加数据
index_ivf.add(xb)
print("Number of vectors in IVF index:", index_ivf.ntotal)
创建一个 HNSW 索引
# 创建一个 HNSW 索引
index_hnsw = faiss.IndexHNSWFlat(d, 32) # 32 是 HNSW 图的连接数
# 向索引中添加向量
index_hnsw.add(xb)
print("Number of vectors in HNSW index:", index_hnsw.ntotal)
相似度搜索
假设你有一个查询向量,并且你想查找与之最相似的向量。
-
搜索最近向量
# 创建一个随机查询向量 xq = np.random.random((5, d)).astype('float32') # 5 个查询向量 # 执行最近邻搜索,返回最相似的 5 个向量 k = 5 # 查找最相似的 5 个向量 D, I = index.search(xq, k) # D 是距离,I 是索引 print("Distances of the nearest neighbors:\n", D) print("Indices of the nearest neighbors:\n", I)D是距离矩阵,I是索引矩阵,分别表示每个查询向量与最相似的k个向量之间的距离和对应的索引。
搜索时的优化
在使用 IVF 或 HNSW 时,你可能需要指定更多的搜索参数来优化检索效果,比如 nprobe(在 IVF 中控制搜索的分桶数量),或 efSearch(在 HNSW 中控制搜索效率)
-
IVF
# 在 IVF 索引中执行搜索 index_ivf.nprobe = 10 # 搜索时考虑的桶的数量 D_ivf, I_ivf = index_ivf.search(xq, k) print("Distances of the nearest neighbors (IVF):\n", D_ivf) print("Indices of the nearest neighbors (IVF):\n", I_ivf) -
HNSW
# 在 HNSW 索引中执行搜索 index_hnsw.hnsw_efSearch = 32 # 增加搜索时的图搜索精度 D_hnsw, I_hnsw = index_hnsw.search(xq, k) print("Distances of the nearest neighbors (HNSW):\n", D_hnsw) print("Indices of the nearest neighbors (HNSW):\n", I_hnsw)
保存索引
faiss.write_index(index, "index_flat.index")
加载索引
index_loaded = faiss.read_index("index_flat.index")
print("Loaded index has", index_loaded.ntotal, "vectors.")
使用GPU加速
如果你有 GPU,Faiss 支持在 GPU 上加速向量搜索。首先,你需要安装 GPU 版本的 Faiss,然后将索引移动到 GPU:
import faiss
res = faiss.StandardGpuResources() # GPU 资源管理
gpu_index = faiss.index_cpu_to_gpu(res, 0, index) # 将索引移动到 GPU
# 搜索时使用 GPU 加速
D_gpu, I_gpu = gpu_index.search(xq, k)
langchain api 示例
from lm.em import em_dashscope_embedding
from langchain_community.document_loaders import WebBaseLoader
from langchain_community.vectorstores import FAISS
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain.globals import set_verbose
set_verbose(True)
# 定义Web加载器
loader = WebBaseLoader('https://news.qq.com/rain/a/20250127A00HJS00')
# 获取加载数据
documents = loader.load()
# print(documents)
# 使用文本分割器对数据进行分割
docs = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200).split_documents(documents)
# 将文档向量化
vector = FAISS.from_documents(docs, em_dashscope_embedding)
# 定义检索器
retriever = vector.as_retriever()
# result = retriever.get_relevant_documents('2025年春节联欢晚会都有哪些节目?')
result = retriever.invoke('2025年春节联欢晚会都有哪些节目?')
print(result)
十、LangSmith
是一个用于构建生产级 LLM 应用程序的平台,它提供了调试、测试、评估和监控基于任何 LLM 框架构建的链和智能代理的功能,并能与 LangChain 无缝集成。
-
调试与测试
-
评估应用效果
-
监控应用性能
-
数据管理与分析
-
团队协作
-
可扩展性与维护性
LangSmith是LangChain的一个子产品,是一个大模型应用开发平台。它提供了从原型到生产的全流程工具和服务,帮助开发者构建、测试、评估和监控基于LangChain或其他 LLM 框架的应用程序。
获取LangSmish的API key
登录地址,并注册
https://smith.langchain.com
要启动LangSmith需要配置环境变量
-
windows环境
# 配置LangSmith 监控开关,true开启,false关闭 setx LANGCHAIN_TRACING_V2 "true" # 配置 LangSmith api key,修改为你自己 setx LANGCHAIN_API_KEY "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"
Verbose 详细日志打印
from langchain.globals import set_verbose
# 打印详细日志
set_verbose(True)
Debug 调试日志打印
from langchain.globals import set debug
# 打印调试日志
set_debug(True)
1014

被折叠的 条评论
为什么被折叠?



