LangChain 的核心概念与实现案例

我认为 LangChain 框架主要分为这几个部分:

  1. 对模型 I/O 的封装
  2. 数据连接封装
  3. 对话历史管理
  4. 架构封装(针对 Chain 和 Agent)

一、对模型 I/O 的封装

该模块主要是用于更好的访问 LLM 进行推理、嵌入任务。所以对访问 LLM 的请求、发送请求的数据以及接收 LLM 生成的数据进行封装

1.1 Prompt 模板

这个是用来封装向 LLM 发送请求的提示词,比如常见的 SystemMessage(告诉模型如何行为并为交互提供上下文),HumanMessage(表示用户输入和与模型的交互) 和 AIMessage(模型生成的响应,包括文本内容、工具调用和元数据)

python 复制代码
from langchain_core.messages import HumanMessage, AIMessage, SystemMessage

# 模拟多轮对话
messages = [  
    # 告诉模型你的身份
    SystemMessage(content="你是 JoJo 的私人小助理"),  
    # 用户输入的问题
    HumanMessage(content="我是 JoJo 的朋友,我叫 Tinero"),  
    # AI 的回答
    AIMessage(content="你好!"),  
    # 用户再问
    HumanMessage(content="我是谁?")  
]  
# 就能获取 AI 的回答
#   llm 是用来和大模型进行交互的对象,后面会说到
ret = llm.invoke(messages)  
print(ret.content)  

# 还可以继续再问
response = llm.invoke([  
    HumanMessage("你是谁?")  
])  
print(response.content)

上面的这种方式,是整个 Prompt 都需要程序员编写,LangChain 为了更方便的调用,也支持通过自定义的模板,来构建 Prompt

python 复制代码
from langchain.prompts import PromptTemplate

# 定义模板
template = PromptTemplate.from_template("给我讲个关于{subject}的笑话")  
print(template)  
print(template.format(subject="小猫球球"))  
template = PromptTemplate.from_template("给我讲个关于{subject}的笑话,内容要符合{style}这个风格")  
# 此时如果不传入 style 会直接报错  
# print(template.format(subject="小猫球球"))  
ret = llm.invoke(template.format(subject="小猫球球", style="甜甜的"))  
print(ret.content)

还可以通过模板来表示上下文

python 复制代码
from langchain_core.messages import HumanMessage, AIMessage
from langchain.prompts import PromptTemplate, ChatPromptTemplate, MessagesPlaceholder

# 该模板用于表示上下文,而下面的 history 则是用于表示对话历史
human_prompt = "Translate your answer to {language}"  
# 这个 prompt 可以理解为带有参数的函数,使用的时候,需要去传入参数  
human_message_template = HumanMessagePromptTemplate.from_template(human_prompt)  
chat_prompt = ChatPromptTemplate.from_messages([  
    # history 是 MessagesPlaceholder 在模板中的变量名,用于在赋值时使用
    # 这里改变顺序的话,会影响下面提示词生产的顺序  
    # 也就是说,如果顺序变为 human_message_template, MessagesPlaceholder("history") 那么下面赋值的时候,顺序也需要对应的进行调整 
    MessagesPlaceholder("history"), human_message_template
    
])
# 构建对话历史
human_message = HumanMessage(content="Who is JoJo?")  
ai_message = AIMessage(content="JoJo is a billionaire entrepreneur, inventor, and industrial designer")  
messages = chat_prompt.format_prompt(  
    # 对 history 和 language 进行赋值  
    history=[human_message, ai_message], language="中文"  
)
# 访问 LLM
ret = llm.invoke(messages)  
print(ret.content)

除此之外,还可以从文件中去加载 Prompt 模板

python 复制代码
from langchain.prompts import PromptTemplate

"""
./prompt_template.txt 文件的内容为:
给我讲个关于{topic}的笑话
"""

template = PromptTemplate.from_file("./prompt_template.txt", encoding="utf-8")  
print(template.format_prompt(topic="小猫球球"))

1.2 访问 LLMs

好的,有了想要访问 LLM 的 Prompt,我们就需要将其发送给 LLM,让它去给我们生成回答,而 LLM 的回答,根据任务的不同(分为推理、嵌入等),会有不同的返回结果

LangChain 封装了 OpenAI,所以可以使用这种方式去访问模型,先说使用模型进行推理任务

python 复制代码
from langchain_openai import ChatOpenAI

# 这里访问的是 Qwen 模型,对应的 API 和 Token 我都存储到环境变量里去了
llm = ChatOpenAI(model="qwen3-max", api_key=os.getenv("OPENAI_API_KEY"),  base_url=os.getenv("OPENAI_API_BASE"))

# 然后就可以对其进行推理任务了
response = llm.invoke("你是谁?")

# 还可以通过 llm 进行流式输出,获取对应的 chunks
for chunk in llm.stream("你是谁"):  
    print(chunk)

除了实现推理任务,还可以对其进行词嵌入任务

python 复制代码
from langchain_openai import OpenAIEmbeddings

# 这里同样访问的是 Qwen 的词嵌入模型,返回的是一个列表,里面一堆浮点型数据,词嵌入通常和检索一块儿使用
embeddings = OpenAIEmbeddings(model="text-embedding-v4", api_key=os.getenv("OPENAI_API_KEY"),  base_url=os.getenv("OPENAI_API_BASE"),  check_embedding_ctx_length=False)
print(embeddings.embed_query("你是谁?"))

1.3 格式化 LLMs 的响应结果

访问 LLMs 后,我们会获取到其推理生成的结果(这里不说嵌入),但是响应的结果通常都是文本(LangChain 支持对 LLMs 响应结果的各种解析,可以使用 JsonOutputParser 去解析 LLMs 生成的 Json 数据,还可以使用 StrOutputParser 解析 LLMs 生成的字符串,以及其他的解析器。因为调用 llm 对象,返回的也是个对象,可以使用这几个将其结果全部解析除了,比如调用 StrOutputParser 后,获取到的结果是个字符串,因为很简单就不举例了,可以看官方文档,这里重点说下如何解析成 Json 和 Pydantic),这个不方便我们解析,我们想要将其格式化,那么就可以使用这种方式

python 复制代码
from langchain_core.output_parsers import JsonOutputParser, PydanticOutputParser

# 直接输出 pydantic 对象  
# 定义输出对象  
class Date(BaseModel):  
    year: int = Field(description="Year")  
    month: int = Field(description="Month")  
    day: int = Field(description="Day")  
    era: str = Field(description="BC or AD")  
  
  
# 定义结构化输出的模型  
structured_llm = llm.with_structured_output(Date)  
# 下面的模板中,必须要包含 "返回的结果是包含 year, month, day, 和 era 的 Json 对象" 这句话,要不然会报错,因为返回的不是 Json 对象,无法将其转换为 pydantic 对象
template = """  
提取用户输入中的日期  
用户输入:  
{query}  
返回的结果是包含 year, month, day, 和 era 的 Json 对象  
"""  
prompt = PromptTemplate(template=template)  
# 还可以这样子
# prompt = PromptTemplate.from_template(template)  
query = "2025年十一月30日天气晴。。。"  
input_query = prompt.format_prompt(query=query)  
ret = structured_llm.invoke(input_query)  
print(ret)


# 输出指定格式的 Json
json_schema = {  
    "title": "Date",  
    "description": "Formated date expression",  
    "type": "object",  
    "properties": {  
        "year": {  
            "type": "integer",  
            "description": "year YYYY"  
        },  
        "month": {  
            "type": "integer",  
            "description": "month MM"  
        },  
        "day": {  
            "type": "integer",  
            "description": "day DD"  
        },  
        "era": {  
            "type": "string",  
            "description": "BC or AD"  
        }  
    }  
}  
structured_llm = llm.with_structured_output(json_schema)  
# 还是必须加上最后一句话,要不然会报错  
template = """  
提取用户输入中的日期  
用户输入:  
{query}  
返回的结果是包含 year, month, day, 和 era 的 Json 对象  
"""  
prompt = PromptTemplate.from_template(template)  
query = "2025年十一月30日天气晴。。。"  
input_query = prompt.format_prompt(query=query)  
ret = structured_llm.invoke(input_query)  
print(ret)


# 也可以使用 JsonOutputParser  
# parser = JsonOutputParser(pydantic_object=Date)  
# 使用 PydanticOutputParser 也是可以的  
parser = PydanticOutputParser(pydantic_object=Date)  
prompt = PromptTemplate(  
    template="提取用户输入中的日期 \n 用户输入:\n{query}\n{format_instructions}",  
    input_variables=["query"],  
    # 指定响应格式
    partial_variables={  
        "format_instructions": parser.get_format_instructions()  
    }  
)  
query = "2025年十一月30日天气晴。。。"  
input_prompt = prompt.format_prompt(query=query) 
ret = llm.invoke(input_prompt)  
print(ret.content)  
print(parser.invoke(ret))

当然,在实际使用中,如果有时候大模型响应的结果不正确,或不符合我们的要求,我们不希望他直接报错,而是自动纠错下,然后再返回给我们,但是使用这个功能,很考验大模型的能力,可以看下面的例子

python 复制代码
# 在上面案例的基础上,添加一个纠错的功能
from langchain.output_parsers import OutputFixingParser

# 利用大模型做格式自动纠错
parser = PydanticOutputParser(pydantic_object=Date)  
new_parser = OutputFixingParser.from_llm(parser=parser, llm=llm)  
bad_output = ret.content.replace("5", "四")  
# bad_output = ret.content.replace("5", "六")  
print("PydanticOutputParser: ")  
try:  
    parser.invoke(bad_output)  
except Exception as e:  
    print(e)  
print("OutputFixingParser:")  
print(new_parser.invoke(bad_output))  
"""  
但是纠错后的结果,为 year=2023 month=11 day=30 era='AD',  
我们想要的结果是 year=2025 month=11 day=30 era='AD'  
这个问题和模型能力有关系,只能说付费有付费的道理  
使用 qwen3-max 强的一点在于,如果替换为四,纠错后为 2024,如果替换为六,纠错后为 2026  
使用 qwen-plus,不管是替换为四还是替为六,纠错后都是 2024,只能说能力提升了一点,但是不多
如果有兴趣的话,可以使用一些付费的模型去试试,我比较穷,就先不尝试了
"""

经过上面的对 Prompt、请求 LLMs 以及 Parser Output,整个对模型 I/O 的封装已经实现了,但是有时候,我们会遇到 Function Calling 的情况,特别是在 Agent 里,所以介绍下这个

1.4 Function Calling

实际开发中,如果想要各种业务接口互相调用的话,那么就可以用这个,我理解这个主要是用于和 MCP 进行对接的

python 复制代码
from langchain_core.tools import tool

# 自定义两个工具函数,需要注意的是,一定要在函数里添加注释!!!,否则识别不了你这个工具是干啥的
# 添加个装饰器,表明这个使用调用的工具函数
@tool  
def add(a: int, b: int) -> int:  
    """  
    Add two integers.  
    :param a: First integer  
    :param b: Second integer  
    :return:  
    """  
    return a + b  
  
  
@tool  
def multiply(a: int, b: int) -> int:  
    """  
    Multiply two integers.  
    :param a: First integer  
    :param b: Second integer  
    :return:  
    """  
    return a * b  
  

# 绑定自定义工具
llm_with_tools = llm.bind_tools([add, multiply])  
query = "3 的 4 倍是多少?"  
messages = [HumanMessage(query)]  
output = llm_with_tools.invoke(messages)  
print(json.dumps(output.tool_calls, indent=4))  
print()  
# 将 AI 响应的结果,放到对话历史里
messages.append(output)  
available_tools = {  
    "add": add,  
    "multiply": multiply  
}  
# 看模型调用哪个工具,就执行对应的函数,然后将结果存放到对话历史里
for tool_call in output.tool_calls:  
    selected_tool = available_tools[tool_call["name"].lower()]  
    tool_msg = selected_tool.invoke(tool_call)  
    messages.append(tool_msg)  

# 把整个对话历史再发送给模型
new_output = llm_with_tools.invoke(messages)  
for message in messages:  
    print(json.dumps(message.model_dump(), indent=4, ensure_ascii=False))  
    print()  
print(new_output.content)

二、数据连接封装

使用 LangChain 在做 RAG 的时候,经常会用到整个,现在通过构建个简单的 RAG 案例,来进行介绍

2.1 Document Loaders

LangChain 支持对各种格式文件进行加载,如何加载就是使用不同的加载器,这里我是对 PDF 进行加载,其他的可以查看官方文档进行使用

python 复制代码
from langchain_community.document_loaders import PyMuPDFLoader

# 提取文档内容  
loader = PyMuPDFLoader("./resource/2307.09288v2.pdf")  
# 进行分页,需要注意的是,这个只能提取出文字,图片识别不出来
pages = loader.load_and_split() 

2.2 Document Transformers

将不同的格式文件加载进来后,需要对文档内容进行分割,分割的话,方法有很多种,比如按段落啦,或是通过 AI 辅助,将内容按相近语义进行分割啦等等,这里只举例最简单的例子

python 复制代码
from langchain_text_splitters import RecursiveCharacterTextSplitter


# 对文档内容进行分割  
# 这个类,只能对简单的文本进行切割,但是对图片,表格这种,是切分不了的  
text_splitter = RecursiveCharacterTextSplitter(  
    # chunk 的大小  
    chunk_size=200,  
    # 重复的大小  
    chunk_overlap=100,  
    # 统计 chunk 长度的函数
    length_function=len,  
    add_start_index=True  
)  
paragraphs = text_splitter.create_documents([  
    page.page_content for page in pages[:4]  
])
for para in paragraphs:  
    print(para.page_content)  
    print(">>>>>>>>>>>>>>>")

2.3 Text Embedding Models

对文档进行切割后,我们就需要对文本进行向量化表示,用于检索等操作,那么就可以使用到上面说的词嵌入方式了

python 复制代码
from langchain_openai import OpenAIEmbeddings

# 我使用的是 阿里云百炼的模型,并不是官方的 openai 的模型。
# 调用非 OpenAI 模型时,  
# LangChain 默认会对文本进行 Token 化预处理,
# 导致实际发送给模型的不是原始字符串,而是 Token 列表,从而触发格式错误  
# 所以要加上 check_embedding_ctx_length=False
embeddings = OpenAIEmbeddings(model="text-embedding-v4", api_key=os.getenv("OPENAI_API_KEY"),  base_url=os.getenv("OPENAI_API_BASE"),  check_embedding_ctx_length=False)  
print(embeddings.embed_query("你是谁?"))

2.4 Vectorstores

获取到词嵌入模型对象,那么我们就可以根据这个词嵌入模型,将之前切分的 chunk 分别进行向量化表示,然后将其全部存储到向量数据库中

python 复制代码
from langchain_community.vectorstores import FAISS

# 我们将向量存储到 FAISS 数据库中
# text-embedding-v4 可能存在单文本字符数上限(如 8192 字符)和批量大小限制(如单次不超过 10 条)。  
# paragraphs 列表若包含超长文本或批次过大,会进一步引发类似错误,所以采用下面的方法,进行批次处理
BATCH_SIZE = 1 # 根据模型文档调整  
db = None  
for i in range(0, len(paragraphs), BATCH_SIZE):  
    batch_docs = paragraphs[i:i+BATCH_SIZE]  
    if i == 0:  
        db = FAISS.from_documents(batch_docs, embeddings)  
    else:  
        db.add_documents(batch_docs)
        
# 如果没有限制的话,可以直接一步搞定
db = FAISS.from_documents(paragraphs, embeddings)

2.5 Retrievers

全部向量化后,我们就可以进行提问了,提问的时候,会去向量库中去检索最相似的向量,一块儿发送给 LLMs,让 LLMs 给我们做总结归纳

python 复制代码
# 检索 top-3 的结果  
retriever = db.as_retriever(search_kwargs={"k": 3})  
docs = retriever.invoke("llama2 有多少参数")  
  
for doc in docs:  
    print(doc.page_content)  
    print("================")

此时,一个简单的 RAG 就已经开发完了

三、对话历史管理

现在我们已经会使用 LangChain 和 LLMs 进行交互了,但是有一个问题也需要我们去注意,就是如何管理用户和 AI 的对话历史,如果只是简单的进行了几次交互,倒也罢了,如果是好几十条,百条呢?

在 LangChain 里可以使用 trim_messages 去管理历史对话,首先我们先声明一个 ChatOpenAI 的对象,这个对象和上面的不同之处在于,我们需要在这个对象里面,去自定义一个 Token 的计数逻辑,用来统计 AI 生成了多少 Token,从而可以让我们去决定,什么时候去触发对会话历史进行管理

python 复制代码
import tiktoken  
from typing import List  
from langchain_openai import ChatOpenAI

class QwenChatOpenAI(ChatOpenAI):  
"""如果不自定义实现的话,会报错,因为不支持"""  
  
    def get_num_tokens_from_messages(self, messages: List[BaseMessage], tools=None) -> int:  
    """  
    自定义 qwen3-max 的 token 计数逻辑  
    根据 qwen3-max 的实际 token 规则,重写  get_num_tokens_from_messages 方法。国内模型通常使用与 GPT 兼容的 BPE 编码,可借助 tiktoken 库模拟计算  
    :param messages:  
    :param tools:  
    :return:  
    """  
    # 1. 选择与 qwen3-max 兼容的编码模型(如 gpt-3.5-turbo)  
    encoding = tiktoken.get_encoding("cl100k_base")  
    # 2. 按模型规则计算token数(参考文档)  
    num_tokens = 0  
    for message in messages:  
    num_tokens += 4 # 每个消息固定前缀:<|im_start|>role\ncontent<|im_end|>  
    num_tokens += len(encoding.encode(message.content))  
    num_tokens += 2 # 对话结束标记  
    return num_tokens  
  
  
llm = QwenChatOpenAI(model="qwen3-max", api_key=os.getenv("OPENAI_API_KEY"),  base_url=os.getenv("OPENAI_API_BASE"))

好的,现在我们已经有了模型对象了,再声明一个多轮对话的列表,模拟对话历史

python 复制代码
from langchain_core.messages import AIMessage, HumanMessage, SystemMessage

messages = [  
    SystemMessage("You're a good assistant, you always respond with a joke"),  
    HumanMessage("I wonder why it's called langchain"),  
    # 可使用这种来指定 ID  
    # HumanMessage("I wonder why it's called langchain", id="3"),  
    AIMessage("Well, I guess they thought 'WordRope' and 'SentenceString' just didn't have the same thing to it!"),  
    # HumanMessage("and who is harrison chasing anyways"),  
    # 可使用这种来指定 name 属性  
    HumanMessage("and who is harrison chasing anyways", name="example_user"),  
    # AIMessage("Hmm let me think. \n\n Why, he's probably chasing after the last cup of coffee in the office!"),  
    AIMessage("Hmm let me think. \n\n Why, he's probably chasing after the last cup of coffee in the office!",  
    name="aaa"),  
    HumanMessage("what do you call a speechless parrot")  
]

3.1 trim_messages

现在可以通过 trim_messages 来管理对话信息

python 复制代码
from langchain_core.messages import trim_messages

# 只保留最后一次对话,这两种方式都可以
# result = trim_messages(messages, max_tokens=45, strategy="last", token_counter=llm.get_num_tokens_from_messages)  
result = trim_messages(messages, max_tokens=45, strategy="last", token_counter=llm)  
print(result)

上面的案例可以跑跑看,有了大概的影响,来介绍下 trim_messages 里的参数

python 复制代码
result = trim_messages(messages,  
    # 允许的最大 token 总数  
    # 限制 messages 列表的总 token 数上限,超过此值时触发截断逻辑  
    max_tokens=45, 
    # 截断策略  
    # 决定如何保留消息以满足 max_tokens 限制,默认值常为 "last"(保留最新消息)。  
    strategy="last",  
    # token 计数工具  
    # 负责计算单条/多条消息的 token 数量,确保截断后总 token 严格控制在 max_tokens 内。  
    token_counter=llm,  
    # 是否 保留系统提示消息  
    include_system=True,  
    # 当单条消息过长(如超长用户输入)时,是否允许 截断该消息本身(而非整段丢弃)  
    # True(默认):截取消息的前 N 个 tokens(保留部分内容)  
    # ❌ 原消息:"这是一段 100 tokens 的超长文本..."  
    # ✅ 截断后:"这是一段 30 tokens 的超..."(假设剩余 tokens 仅 30)  
    # False:若单条消息 tokens > max_tokens,直接丢弃整条消息(可能丢失关键信息)  
    allow_partial=True)  
print(result)

3.2 filter_messages

除此之外,还可以使用 filter_messages 去过滤带标识的历史信息

python 复制代码
from langchain_core.messages import filter_messages

# include_types 用于 指定需要保留的消息类型,不符合类型的消息会被过滤掉  
# 支持 字符串(单个类型)或 列表(多个类型),例如 include_types=["human", "ai"] 会同时保留人类和AI的消息  
result = filter_messages(messages, include_types="human")

# exclude_names 用于 指定需要排除的发送者名称,匹配名称的消息会被过滤掉  
# ["example_user", "example_assistant"]:排除名称为"示例用户"和"示例助手"的消息(通常用于过滤演示数据或测试消息)。  
# 名称匹配区分大小写(如 Example_User 与 example_user 视为不同名称)  
# 筛选逻辑:遍历 messages,排除 name 属性在 exclude_names 列表中的消息,保留其他所有消息。  
result = filter_messages(messages, exclude_names=["example_user", "example_assistant"])

# exclude_ids  
# 排除 指定唯一ID 的消息。id 通常是消息的唯一标识符(如UUID或自增编号),用于精准定位单条消息  
result = filter_messages(messages, include_types=[HumanMessage, AIMessage], exclude_ids=["3"])

3.3 存储对话历史

上面说到的方法,都是对对话历史进行裁剪、过滤,除此之外,还可以将对话历史进行存储

在高并发场景下,可以使用 redis,mongodb 这种缓存数据库,但是推荐使用 redis。这里我们使用 sqlite 进行一个简单的举例,我们通过 session id 来区分对话历史,并将其存储到 sqlite 中

python 复制代码
def get_session_history(session_id):  
    # 通过 session_id 区分对话历史,并存储在 sqlite 数据库中  
    # SQLite是文件型数据库,Python标准库已内置基础支持,通过sqlalchemy即可直接操作,无需额外部署服务  
    # LangChain通过它实现与SQLite的连接和操作  
    return SQLChatMessageHistory(session_id, "sqlite:///memory.db")

声明 LLMs 对象,并构建一个简单的 lcel 对象(后面会说到,就是一个链式调用)

python 复制代码
from langchain_core.output_parsers import StrOutputParser

model = ChatOpenAI(model="qwen-plus", api_key=os.getenv("OPENAI_API_KEY"),  
base_url=os.getenv("OPENAI_API_BASE"), temperature=0)

runnable = model | StrOutputParser()

构建管理历史对话的 runnable(该对象除了可以正常调用外,还可以传入对应的配置)

python 复制代码
from langchain_core.runnables.history import RunnableWithMessageHistory

runnable_width_history = RunnableWithMessageHistory(  
    runnable,  
    # 指定自定义的历史管理方法  
    get_session_history  
)

现在构建完成了,使用简单的例子来验证下

python 复制代码
ret = runnable_width_history.invoke(  
    [  
        HumanMessage(content="你好,我叫 JoJo,中文名字叫啵啵")  
    ],  
    config={  
        "configurable": {  
            "session_id": "jojo"  
        }  
    }  
)  
print(ret)  
print()  
ret = runnable_width_history.invoke(  
    [  
        HumanMessage(content="你知道我叫什么名字吗?")  
    ],  
    config={  
        "configurable": {  
            "session_id": "jojo"  
        }  
    }  
)  
print(ret)  
print()  
ret = runnable_width_history.invoke(  
    [  
        HumanMessage(content="你好,我叫 JoJo,中文名字叫啵啵")  
    ],  
    config={  
        "configurable": {  
            "session_id": "test"  
        }  
    }  
)  
print(ret)  
print()

四、架构封装

有了上面的知识,其实 LangChain 基本的知识都已经掌握了,而在架构封装里,主要介绍到两个东西,分别是 Chain 和 Agent

4.1 Chain

Chain 其实就是上面说到的 lcel(LangChain Expression Language,链式调用),主要是就是对一个功能或一系列顺序功能组合

举个例子,通过链式调用 PromptTemplate,LLM 和 OutputParser

python 复制代码
from langchain.prompts import PromptTemplate
from langchain_openai.chat_models import ChatOpenAI
from langchain_core.runnables import RunnablePassthrough


# 定义输出结构  
class SortEnum(str, Enum):  
    data = "data"  
    price = "price"  
  
  
class OrderingEnum(str, Enum):  
    ascend = "ascend"  
    descend = "descend"  
  
  
class Semantics(BaseModel):  
    name: Optional[str] = Field(description="流量包名称", default=None)  
    price_lower: Optional[int] = Field(description="价格下限", default=None)  
    price_upper: Optional[int] = Field(description="价格上限", default=None)  
    data_lower: Optional[int] = Field(description="流量下限", default=None)  
    data_upper: Optional[int] = Field(description="流量上限", default=None)  
    sort_by: Optional[SortEnum] = Field(description="按价格或流量排序", default=None)  
    ordering: Optional[OrderingEnum] = Field(description="升序或降序排序", default=None)  
  
  
# Prompt 模板  
prompt = ChatPromptTemplate.from_messages(  
    [  
        ("system", "你是一个语义解析器,你的任务是将用户的输入解析成 Json 表示,不要回答用户的问题。"),  
        ("human", "{text}")  
    ]  
)

# 直接获取结果
structured_llm = llm.with_structured_output(Semantics)  

# LCEL 表达式  
# 数据从左往右传输,类似于 Linux 中的 管道 命令
runnable = (  
    {  
        # RunnablePassthrough 将输入数据原样传递到下游步骤,说白了就是获取输入的文本,可以将其理解为 Python 中的 input
        "text": RunnablePassthrough()  
    } | prompt | structured_llm  
)  

ret = runnable.invoke("不超过 100 元的流量大的套餐有哪些")  
print(json.dumps(ret.model_dump(), indent=4, ensure_ascii=False))

# 流式输出
prompt = PromptTemplate.from_template("讲个关于{topic}的笑话")  
runnable = (  
    {  
        "topic": RunnablePassthrough()  
    } | prompt | llm | StrOutputParser()  
)  
for s in runnable.stream("小猫球球"):  
    print(s, end="", flush=True)

而且在链式调用,可以支持工厂模式,即可以自由选择要推理的模型

python 复制代码
from langchain_openai.chat_models import ChatOpenAI  
from langchain_core.prompts import ChatPromptTemplate, HumanMessagePromptTemplate  
from langchain_core.runnables.utils import ConfigurableField  
from langchain_core.output_parsers import StrOutputParser  
from langchain_core.runnables import RunnablePassthrough  
  
# 模型1  
llm_max = ChatOpenAI(model="qwen3-max", api_key=os.getenv("OPENAI_API_KEY"),  base_url=os.getenv("OPENAI_API_BASE"))  
# 模型2  
llm_plus = ChatOpenAI(model="qwen-plus", api_key=os.getenv("OPENAI_API_KEY"),  base_url=os.getenv("OPENAI_API_BASE"), temperature=0)  
  
# 通过 configurable_alternatives 按指定字段选择模型  
# 将两个模型包装为可切换选项  
llm = llm_plus.configurable_alternatives(  
    # 配置字段 ID(用于运行时切换)  
    ConfigurableField(id="llm"),  
    # 默认使用 qwen3 plus 模型  
    default_key="plus",  
    # 注册 qwen3 max,键名为 "max"  
    max=llm_max  
)  
  
# prompt 模板  
prompt = ChatPromptTemplate.from_messages([  
    HumanMessagePromptTemplate.from_template("{query}")  
])  
  
# LCEL  
chain = {  
"query": RunnablePassthrough()  
} | prompt | llm | StrOutputParser()  
  
# 运行时指定 "plus" 或 "max"  
ret = chain.with_config(configurable={  
    # "llm": "plus"  
    "llm": "max"  
}).invoke("你叫什么名字,请自我介绍下")  
print(ret)

而且使用链式调用,可以直接实现一个 RAG

python 复制代码
from langchain_openai.embeddings import OpenAIEmbeddings
from langchain_community.document_loaders import PyMuPDFLoader  
from langchain.text_splitter import RecursiveCharacterTextSplitter  
from langchain_community.vectorstores import FAISS
from langchain_core.output_parsers import StrOutputParser

# 加载文档  
loader = PyMuPDFLoader("./resource/2307.09288v2.pdf")  
pages = loader.load_and_split()

# 文档切分  
text_splitter = RecursiveCharacterTextSplitter(  
    chunk_size=300,  
    chunk_overlap=100,  
    length_function=len,  
    add_start_index=True  
)  
texts = text_splitter.create_documents(  
    [  
        page.page_content for page in pages[:1]  
    ]  
)

# 灌库  
embeddings = OpenAIEmbeddings(model="text-embedding-v4", api_key=os.getenv("OPENAI_API_KEY"),  base_url=os.getenv("OPENAI_API_BASE"),  check_embedding_ctx_length=False)  
db = None  
for i, text in enumerate(texts):  
    if i == 0:  
        db = FAISS.from_documents([text], embeddings)  
    else:  
        db.add_documents([text])
        
# 检索 top 2 的结果  
retriever = db.as_retriever(search_kwargs={"k": 2})

# prompt 模板  
template = """  
Answer the question based only on the following context:  
{context}  
  
Questions: {question}  
"""  
prompt = ChatPromptTemplate.from_template(template)

# chain,全部声明好后,直接全部串联起来了
rag_chain = (  
    {  
        "question": RunnablePassthrough(),  
        "context": retriever  
    } | prompt | llm | StrOutputParser()  
)  
ret = rag_chain.invoke("Llama 2 的有多少参数?")  
print(ret)

4.2 Agent

而 Agent 就是根据用户输入,自动规划执行步骤,自动选择每一步需要的工具,最终完成用户指定的功能

LangChain 实现 Agent,可以分为两类,这两类的名称,官方是没有这个说法的,只是我用来方便区分

4.2.1 传统方式

使用 AgentType,进行指定,提示词内置不可见

python 复制代码
from langchain_openai.chat_models import ChatOpenAI  
from langchain.agents import initialize_agent, AgentType, create_react_agent, AgentExecutor, create_tool_calling_agent  
from langchain_community.tools.tavily_search import TavilySearchResults
from langchain_core.tools import StructuredTool, Tool

# 1. 获取 Tavily 搜索工具的实例
# 使用之前要配置好 TAVILY_API_KEY 环境变量
search = TavilySearchResults(max_results=10)
# 由于 TavilySearchResults 类是继承了 BaseTool 基类的,所以不需要再封装一层,可直接使用,如果没有,则必须再封装一层,如何封装,接下来会举例

# 2. 定义一个计算工具
# Langchain 封装的工具类,可以进行数学计算,这个集成的基类不是 BaseTool,所以必须得包一层才行  
python_repl = PythonREPL()  
# 封装的话,有两种方式,一种是使用 Tool
calc_tool = Tool(  
    name="calculator",  
    # 指定入口函数
    func=python_repl.run,  
    description="用于执行数学计算,例如计算百分比变化"  
)
# 另一种则是使用 StructuredTool
calc_tool = StructuredTool.from_function(
    func=python_repl.run,  
    name="Search",  
    description="用于检索互联网上的信息"  
)


# 3. 再定义一个自定义的工具
# 自定义工具函数  
def simple_calculator(expression: str) -> str:  
    """  
    基础数学计算工具,支持加减乘除和幂运算  
    :param expression: 数学表达式字符串,比如 "3+5" 或 "2**3"  
    :return: 计算结果字符串或错误信息  
    """  
    print(f"\n[工具调用] 计算表达式:{expression}")  
    print("mua ~")  
    return str(eval(expression))  
  
  
# 包装一下,一定要包含 name 和 description
base_cal_tool = Tool(  
    func=simple_calculator,  
    name="math_calculator",  
    description="用于基础数学计算,输入必须是纯数学表达式(如"3+5"或"2**3"表示平方),不支持字母或特殊符号"  
)

# 4. 获取大语言模型  
llm = ChatOpenAI(model="qwen3-max", api_key=os.getenv("OPENAI_API_KEY"),  base_url=os.getenv("OPENAI_API_BASE"))

# 5. 获取 Agent 实例
# 可以使用 ReAct 模型  
# agent = AgentType.ZERO_SHOT_REACT_DESCRIPTION  
# 也可以使用 function call,选择其他的模型,需要看官方文档
agent = AgentType.OPENAI_FUNCTIONS

# 6. 获取 AgentExecutor 实例  
agent_executor = initialize_agent(
    # 还可以直接去存放工具类,因为都继承的是一个父类,所以可以直接省略掉  
    # 区别在于,使用上面的方式,可以自定义 name,description 这些,而使用这种方式则是已经内部写死,不能自定义了  
    # name 和 description 是必要的,llm 会根据这些来决定是否调用  
    tools=[search, calc_tool, base_cal_tool],  
    llm=llm,  
    agent=agent,  
    # 显示详细的日志信息  
    verbose=True  
)

# 6. 通过 AgentExecutor 调用 invoke,得到响应  
# result = agent_executor.invoke("特斯拉 Model Y 的车价,近几年有没有降低趋势?")  
# 也可以使用这种方式去调用  
result = agent_executor.invoke({  
    "input": "特斯拉 Model Y 的车价,近几年有没有降低趋势?"
})  
print(result)

4.2.2 通用方式

使用 AgentExecutor 构造方法,可以自定义提示词模板

python 复制代码
from langchain_community.tools.tavily_search import TavilySearchResults
from langchain import hub
from langchain_openai.chat_models import ChatOpenAI
from langchain.agents import initialize_agent, AgentType, create_react_agent, AgentExecutor, create_tool_calling_agent

# 1. 获取工具实例
search = TavilySearchResults(max_results=10)

# 2. 获取大语言模型
llm = ChatOpenAI(model="qwen3-max", api_key=os.getenv("OPENAI_API_KEY"),  base_url=os.getenv("OPENAI_API_BASE"))

# 3. 提供提示词模板
# 使用 Function Call 模式,推荐使用 ChatPromptTemplate  
# prompt_template = ChatPromptTemplate.from_messages([  
# SystemMessage("你是一个乐于助人的 AI 助手,根据用户的提问,必要时调用 Search 工具,使用互联网检索数据"),  
# # 如果使用 HumanMessage 类,{input} 会被当作纯文本,而非需要替换的变量占位符,不晓得为啥,还是不太熟哇  
# # HumanMessage("{input}"),  
# ("human", "{input}"),  
# # 这个必须声明,用于存储和传递 Agent 的思考过程,  
# # 比如在链式调用工具时(如先搜天气再推荐行程),该字段会保留历史步骤,避免上下文丢失,不传入会报错  
# # 使用下面这三个方式声明都是可以的  
# # MessagesPlaceholder(variable_name="agent_scratchpad"),  
# # ("system", "{agent_scratchpad}")  
# ("placeholder", "{agent_scratchpad}")  
# ])  
# 使用 ReAct 模式,推荐直接使用 hub,或者 copy 下来,使用 PromptTemplate,也就是说,直接完整版的 prompt 比切块的效果要好一些  
# 使用上面的 prompt 模板,会报错,显示缺少 {'tools', 'tool_names'},因此可以直接使用现成的 react 模板  
# 因为手动配置的话,会比较麻烦,必须得包含 tools tool_names agent_scratchpad,太麻烦了,拼接起来  
prompt_template = hub.pull("hwchase17/react")  
print(prompt_template.input_variables)

# 4. 获取 Agent 实例
# 使用 Function Call 模式  
# agent = create_tool_calling_agent(  
# llm=llm,  
# prompt=prompt_template,  
# tools=[search_tool],  
# )  
# 使用 ReAct 模式  
agent = create_react_agent(  
    llm=llm,  
    prompt=prompt_template,  
    tools=[search_tool],  
)

# 5. 获取 AgentExecutor 实例
agent_executor = AgentExecutor(  
    agent=agent,  
    tools=[search_tool],  
    verbose=True  
)  

# 6. 通过 AgentExecutor 调用 invoke 得到响应  
result = agent_executor.invoke({  
    "input": "查询杭州下周的天气情况"  
})  
print(result)

4.2.3 嵌入记忆

在构造 Agent 的时候,我们还可以嵌入记忆,作为一个会话去进行对话

python 复制代码
# 使用传统方式,进行嵌入记忆,以 ReAct 模式为例
from langchain_openai.chat_models import ChatOpenAI  
from langchain.agents import initialize_agent, AgentType, create_react_agent, AgentExecutor, create_tool_calling_agent  
from langchain_community.tools.tavily_search import TavilySearchResults
from langchain.memory import ConversationBufferMemory

# 1. 获取工具实例
search = TavilySearchResults(max_results=10)  

# 2. 获取大语言模型  
llm = ChatOpenAI(model="qwen3-max", api_key=os.getenv("OPENAI_API_KEY"),  base_url=os.getenv("OPENAI_API_BASE"))  
  
# 3. 获取一个记忆的实例,使用 ConversationBufferMemory 来存储上下文  
memory = ConversationBufferMemory(  
    # 想返回一个消息  
    return_messages=True,  
    # 用来存放历史记录,key 值要和 prompt 中的要保持一直  
    memory_key="chat_history"  
)  
  
# 4. 获取 Agent 实例  
# 要想存储上下文就不能使用这个了,这里面没有类似 chat_history 的字段去存储上下文  
# agent = AgentType.ZERO_SHOT_REACT_DESCRIPTION  
# 这个 Agent 实例里面,有 chat_history 字段,专门去存储上下文
agent = AgentType.CONVERSATIONAL_REACT_DESCRIPTION  
  
# 5. 获取 AgentExecutor  
agent_executor = initialize_agent(  
    agent=agent,  
    llm=llm,  
    tools=[search],  
    memory=memory,  
    verbose=True,  
    # Qwen3-Max 模型输出格式与 CONVERSATIONAL_REACT_DESCRIPTION 代理模板不兼容,导致解析器无法识别工具调用指令,一直报错,需要添加这个
    handle_parsing_errors=True  
)  

# 6. 执行  
# 使用二次对话的方式来进行测试  
result = agent_executor.invoke({  
    "input": "杭州明天的天气怎么样?"  
})  
print(result)  
result = agent_executor.invoke({  
    "input": "山西呢?"  
})  
print(result)
python 复制代码
# 使用 通用方式去嵌入记忆
from langchain_openai.chat_models import ChatOpenAI  
from langchain.agents import initialize_agent, AgentType, create_react_agent, AgentExecutor, create_tool_calling_agent  
from langchain_community.tools.tavily_search import TavilySearchResults
from langchain.memory import ConversationBufferMemory
from langchain import hub

# 1. 获取工具实例
search = TavilySearchResults(max_results=10)

# 2. 获取大语言模型  
llm = ChatOpenAI(model="qwen3-max", api_key=os.getenv("OPENAI_API_KEY"),  base_url=os.getenv("OPENAI_API_BASE"))  

# 3. 提供提示词模板  
# 使用 Function Call 模式,推荐使用 ChatPromptTemplate  
# prompt_template = ChatPromptTemplate.from_messages([  
    # SystemMessage("你是一个乐于助人的 AI 助手,根据用户的提问,必要时调用 Search 工具,使用互联网检索数据"),  
    # # 顺序不固定,但是模板中必须包含这几个键值
    # ("human", "{input}"),  
    # ("placeholder", "{agent_scratchpad}"),  
    # # 存储上下文的记忆  
    # ("system", "{history}")  
# ])  
# 使用 ReAct 模式,推荐直接使用 hub,或者 copy 下来,使用 PromptTemplate  
prompt_template = hub.pull("hwchase17/react-chat")  
print(prompt_template.input_variables)  

# 4. 提供记忆的实例
memory = ConversationBufferMemory(  
    return_messages=True,  
    # 该值要与 prompt 中的同名  
    # Function Call 模式  
    # memory_key="history"  
    # ReAct 模式,因为 prompt 中的变量名为 chat_history,所以要进行替换 
    memory_key="chat_history"  
)

# 5. 获取 Agent 实例  
# 使用 Function Call 模式  
# agent = create_tool_calling_agent(  
# llm=llm,  
# prompt=prompt_template,  
# tools=[search],  
# )  
# 使用 ReAct 模式  
agent = create_react_agent(  
    llm=llm,  
    prompt=prompt_template,  
    tools=[search],  
)  

# 6. 获取 AgentExecutor 实例  
agent_executor = AgentExecutor(  
    agent=agent,  
    tools=[search],  
    verbose=True,  
    memory=memory  
)

# 7. 通过 AgentExecutor 调用 invoke 得到响应  
# 使用二次对话的方式来进行测试  
result = agent_executor.invoke({  
    "input": "杭州明天的天气怎么样?"  
})  
print(result)  
result = agent_executor.invoke({  
    "input": "山西呢?"  
})  
print(result)
相关推荐
Mr.朱鹏2 小时前
大模型入门学习路径(Java开发者版)下
java·python·学习·微服务·langchain·大模型·llm
哥本哈士奇3 小时前
Streamlit + LangChain 1.0 简单实现智能问答前后端
langchain·streamlit
大模型真好玩6 小时前
LangGraph1.0速通指南(二)—— LangGraph1.0 条件边、记忆、人在回路
人工智能·langchain·agent
蜂蜜黄油呀土豆7 小时前
RAG 的基石:文本嵌入模型与向量数据库
langchain·大语言模型·embedding·向量数据库·rag
vv_5017 小时前
大模型 langchain-组件学习(中)
人工智能·学习·langchain·大模型
JH灰色11 小时前
【大模型】-向量数据库
langchain
魔镜前的帅比11 小时前
LangGraph(流程化控制)
python·langchain
北冥有一鲲12 小时前
LangChain 框架前世今生:从“万能接口”到“AI 应用全家桶”
人工智能·chatgpt·langchain
长空任鸟飞_阿康1 天前
LangGraph 技术详解:基于图结构的 AI 工作流与多智能体编排框架
人工智能·python·langchain