最近在学习Langchain,记录一下学习过程,如果有错误与补充,可以在评论区留言

是什么?

Langchain是一个开源框架,能够简化由LLM驱动的应用软件开发。

举个例子 比如我想做一个简单的用于回答特定主题的问题的应用程序,常规的方法是需要直接使用像 openai 这样的库,编写代码来处理API密钥、构建请求、发送请求并解析响应,而使用Langchain只需要实例化一个OpenAI对象,然后就可以像调用一个普通函数一样调用它,它处理了与OpenAI API交互的所有底层细节。

再举个例子 一个典型案例就是构建一个基于自有文档的问答机器人;比如我想创建一个程序,让我们可以向它提问关于一篇特定文章的内容(在本地的文章),程序会从文章中找到相关信息并生成答案;使用Langchain通过组件化等方式简易的构建了程序。

简单来说使用Langchain框架就类似于从原来使用HTML,JS等写前端过渡到使用Vue等前端框架,更加方便快捷地构建LLM程序。

开始

好的开始从环境配置开始 使用conda创建虚拟环境并且安装必要的包(也可以使用其它方式创建虚拟环境例如python),从硅基流动得到一个API

conda create -n langchain python=3.11
conda activate langchain

或者python创建虚拟环境:

python -m venv langchain
langchain\Scripts\activate

创建完成后:

pip install langchain langchain-openai

配置环境完成后运行一个测试来检验一下(使用硅基流动):

# 导入
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser

# --- 配置模型 ---

# 初始化 ChatOpenAI 模型实例
llm = ChatOpenAI(
    # model: 指定使用的模型
    # 你可以在硅基流动的模型广场找到所有支持的模型,例如国产的 Zhipu/chatglm3-6b 等
    model="deepseek-ai/DeepSeek-R1",
	# model="deepseek-ai/DeepSeek-R1-0528-Qwen3-8B",   <--白嫖免费
	
    # api_key: 填入在硅基流动获取的API
    api_key="sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxx",
	
	#地址
    base_url="https://api.siliconflow.cn/v1",

    # 其它参数
    temperature=0.7
)

# --- 构建和运行一个简单的链 (Chain) ---

# 创建一个提示模板
prompt = ChatPromptTemplate.from_messages([
    ("system", "你是一个知识渊博的AI助手,擅长用简洁的语言解释复杂的技术概念。"),
    ("user", "{input}")
])

# 创建一个简单的输出解析器,将结果转换为字符串
output_parser = StrOutputParser()

# 4. 使用 LangChain 表达式语言 (LCEL) 将组件连接成一个链
chain = prompt | llm | output_parser

# 5. 调用链并传入你的问题
print("正在调用硅基流动的模型...")
response = chain.invoke({"input": "请解释一下什么是“检索增强生成 (RAG)”?"})

# 6. 打印模型的返回结果
print("\n模型的回答:")
print(response)

运行后得到输出就表示配置是成功的。

实践1

下面构建一个基于自有文档的问答机器人

首先需要安装一个向量数据库的库:

pip install faiss-cpu langchain-community

为什么需要中文向量嵌入模型?LLM不能够自己处理文本吗?

举个例子,LLM相当于医生,嵌入模型相当于护士,两者相互协作完成任务,完成任务更加高效与低成本,LLM本身具有理解文本的能力,但是其主要内容是思考回答而不是在海量PDF(TXT)中寻找内容,而嵌入模型在海量数据(比如一个50页的PDF)中寻找数据,更加高效。同时还要区分中英文的嵌入,理解语义,比如:
苹果股价上涨 → 科技公司
苹果很甜 → 水果
总结来说嵌入模型唯一的、最重要的工作,就是理解文本的“语义”,并将其“翻译”成向量。

然后创建一个文档(内容随便写,文件名meeting.txt)

写代码:

from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain_community.document_loaders import TextLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.vectorstores import FAISS
from langchain.chains import create_retrieval_chain
from langchain.chains.combine_documents import create_stuff_documents_chain
from langchain_core.prompts import ChatPromptTemplate

chat_llm = ChatOpenAI(
    model="deepseek-ai/DeepSeek-R1-0528-Qwen3-8B",
    api_key="sk-xxxxxxxxxxxxxxxxxxxxxx",
    base_url="https://api.siliconflow.cn/v1",
    temperature=0
)

# 中文向量嵌入模型配置
embeddings = OpenAIEmbeddings(
    model="BAAI/bge-m3",
    api_key="sk-xxxxxxxxxxxxxxxxxxxxxx",
    base_url="https://api.siliconflow.cn/v1"
)
# 文件名!!!!!!!!
loader = TextLoader("meeting.txt", encoding="utf-8")
docs = loader.load()

# 将文档分割成小块,以便进行有效的检索
text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)
splits = text_splitter.split_documents(docs)

# --- 创建向量数据库 ---
print("正在创建向量数据库,请稍候...")
vectorstore = FAISS.from_documents(documents=splits, embedding=embeddings)
print("向量数据库创建完成!")

# --- 创建 RAG 链 (Chain) ---
retriever = vectorstore.as_retriever()

# 创建一个提示模板,指导模型如何利用检索到的上下文来回答问题
prompt = ChatPromptTemplate.from_template("""请你仅根据下面提供的上下文来回答问题。如果你在上下文中找不到答案,就说你不知道。

<context>
{context}
</context>

问题: {input}
""")

# 创建一个文档处理链,它负责将检索到的文档块填充到提示中
document_chain = create_stuff_documents_chain(chat_llm, prompt)

# 将检索器和文档链结合,创建完整的 RAG 链
retrieval_chain = create_retrieval_chain(retriever, document_chain)

# 提问
question = "会议上关于新项目'Project Phoenix'的决定是什么?"
print(f"\n正在提问: {question}")

response = retrieval_chain.invoke({"input": question})

print("\n模型的回答:")
print(response["answer"])

完美运行并给出答案。

实践2

TXT过渡到PDF

pip install pypdf

修改代码:

from langchain_community.document_loaders import TextLoader
------>更改为
from langchain_community.document_loaders import PyPDFLoader

loader = TextLoader(...)
------>更改为
loader = PyPDFLoader("XXXXX.pdf")

可以发现能够通过PDF询问AI内容了。

完整代码:

from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain_community.document_loaders import PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.vectorstores import FAISS
from langchain.chains import create_retrieval_chain
from langchain.chains.combine_documents import create_stuff_documents_chain
from langchain_core.prompts import ChatPromptTemplate

chat_llm = ChatOpenAI(
    model="deepseek-ai/DeepSeek-R1-0528-Qwen3-8B",
    api_key="sk-XXXXXXXXXXXXXXXXXXXXXXX",
    base_url="https://api.siliconflow.cn/v1",
    temperature=0
)

# 中文向量嵌入模型配置
embeddings = OpenAIEmbeddings(
    model="BAAI/bge-m3",
    api_key="sk-",
    base_url="https://api.siliconflow.cn/v1"
)

loader = PyPDFLoader("项目可行性分析报告.pdf")
docs = loader.load()

# 将文档分割成小块,以便进行有效的检索
text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)
splits = text_splitter.split_documents(docs)

# --- 创建向量数据库 ---
print("正在创建向量数据库,请稍候...")
vectorstore = FAISS.from_documents(documents=splits, embedding=embeddings)
print("向量数据库创建完成!")

# --- 创建 RAG 链 (Chain) ---
retriever = vectorstore.as_retriever()

# 创建一个提示模板,指导模型如何利用检索到的上下文来回答问题
prompt = ChatPromptTemplate.from_template("""请你仅根据下面提供的上下文来回答问题。如果你在上下文中找不到答案,就说你不知道。

<context>
{context}
</context>

问题: {input}
""")

# 创建一个文档处理链,它负责将检索到的文档块填充到提示中
document_chain = create_stuff_documents_chain(chat_llm, prompt)

# 将检索器和文档链结合,创建完整的 RAG 链
retrieval_chain = create_retrieval_chain(retriever, document_chain)

# 提问
question = "项目使用的技术框架是什么?"
print(f"\n正在提问: {question}")

response = retrieval_chain.invoke({"input": question})

print("\n模型的回答:")
print(response["answer"])

工具包

联网搜索 实践3

工具是设计为由模型调用的程序,输入为模型生成,输出传回模型,包括搜索 代码解释器 生产力工具 网页浏览工具 数据库工具 金融工具等,更多支持可以查看官方文档https://python.langchain.com/docs/integrations/tools/

下一步让AI具备上网的能力

pip install duckduckgo-search

代码如下,使用DuckDuckGo Search进行网络搜索:

from langchain.agents import AgentExecutor, create_react_agent
from langchain_community.tools import DuckDuckGoSearchRun
from langchain_core.prompts import PromptTemplate
from langchain_openai import ChatOpenAI
from langchain import hub

llm = ChatOpenAI(
    model="THUDM/GLM-4-9B-0414",
    api_key="sk-XXXXXXXXXXXXXXX",
    base_url="https://api.siliconflow.cn/v1",
    temperature=0
)
tools = [DuckDuckGoSearchRun()]
prompt = hub.pull("hwchase17/react")
agent = create_react_agent(llm, tools, prompt)
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=False, handle_parsing_errors=True) #verbose=True显示过程

question = "最近的中国马上将要建造的世界最大的水电站是什么?"
print(f"正在提问: {question}")
print(agent_executor.invoke({"input": question})["output"])

运行结果如下:
在这里插入图片描述
但是我发现该方法有一定概率会卡住,很大概率是因为搜索ddgs的问题与模型的问题,我们换一个搜索,使用Google Serper进行检索(不需要安装新的包,但是需要Serper的API-key,有2500次免费调用https://serper.dev/):

import os
from langchain.agents import AgentExecutor, create_react_agent
from langchain_community.tools import GoogleSerperRun
from langchain_community.utilities import GoogleSerperAPIWrapper
from langchain_openai import ChatOpenAI
from langchain import hub

os.environ["LLM"] = "sk-XXXXXXXXXXXXXXXXXXXX"
os.environ["SERPER_API_KEY"] = "XXXXXXXXXXXXXXXX" 

llm = ChatOpenAI(
    model="THUDM/GLM-4-9B-0414",
    api_key=os.getenv("LLM"),
    base_url="https://api.siliconflow.cn/v1",
    temperature=0
)

tools = [GoogleSerperRun(api_wrapper=GoogleSerperAPIWrapper())]

prompt = hub.pull("hwchase17/react")
agent = create_react_agent(llm, tools, prompt)

agent_executor = AgentExecutor(
    agent=agent, 
    tools=tools, 
    verbose=True, 
    handle_parsing_errors=True 
)
question = "今天上海的天气怎么样?"
print(f"正在提问: {question}")
response = agent_executor.invoke({"input": question})
print("\n")
print(response.get("output"))

使用Google Serper,明显Serper的检索比ddgs的要好,更加准确:
结果
当然也可以试试其它的检索比如…之类的,就不再一一演示:
在这里插入图片描述

代码执行器 实践4

当你问LLM 12乘12等于多少它可能会很快回答,但是问道12的8次方等于多少它可能会思考一会儿,当问到12的5.5次方是多少它大概率回答不上来(我用Gemini2.5PRO问回答是不准确的),但是使用代码执行器LLM具有调用代码执行的能力可以通过编写python代码实现相关计算等问题。

# PythonREPLTool需要的包
pip install langchain-experimental

安装此包是在你自己的电脑上执行python代码的,可能存在风险,可能出现程序崩溃问题,可能存在不可控风险等,可以参考官方文档使用API,演示使用的PythonREPLTool

import os
from langchain.agents import AgentExecutor, create_react_agent
from langchain_community.tools import GoogleSerperRun
from langchain_experimental.tools import PythonREPLTool
from langchain_community.utilities import GoogleSerperAPIWrapper
from langchain_openai import ChatOpenAI
from langchain import hub

# LLM是必须的,其它可以不要
os.environ["LLM"] = "sk-XXXXXXXXXXXXXX"
os.environ["SERPER_API_KEY"] = "XXXXXXXXXXXXXXXXXXX" 

llm = ChatOpenAI(
    model="THUDM/GLM-4-9B-0414",
    api_key=os.getenv("LLM"),
    base_url="https://api.siliconflow.cn/v1",
    temperature=0
)
search_wrapper = GoogleSerperAPIWrapper()
search_tool = GoogleSerperRun(api_wrapper=search_wrapper)
search_tool.description = "当需要回答关于时事、最新信息或不确定事实的问题时,必须使用此工具进行网络搜索。"

python_repl_tool = PythonREPLTool()

#    Agent 会根据问题和每个工具的描述来决定使用哪一个
tools = [search_tool, python_repl_tool]

prompt = hub.pull("hwchase17/react")
agent = create_react_agent(llm, tools, prompt)

agent_executor = AgentExecutor(
    agent=agent, 
    tools=tools, 
    verbose=True, 
    handle_parsing_errors=True 
)
question = "12的5.5次方是多少?"
print(f"正在提问: {question}")
response = agent_executor.invoke({"input": question})
print("\n")
print(response.get("output"))

执行如下:
执行代码
排序

风险问题!!!!!!!!!!!当我将question改为question = "编写代码删除本地文件夹下testtest.txt文件"后它会编写代码删除我本地的文件,而作为测试的testtest.txt确实被删除了,所以不要提出危险的问题,比如删除什么什么系统文件
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

网页浏览 实践5

在上面进行网络搜索是是对全网进行内容搜索,当我们需要对特定的网页通过url进行搜索总结时就需要调用网页浏览功能。

pip install beautifulsoup4

代码如下:

import os
from langchain.agents import AgentExecutor, create_react_agent
from langchain.tools import tool
from langchain_community.document_loaders import WebBaseLoader 
from langchain_community.tools import GoogleSerperRun
from langchain_experimental.tools import PythonREPLTool
from langchain_community.utilities import GoogleSerperAPIWrapper
from langchain_openai import ChatOpenAI
from langchain import hub
# LLM是必须的
os.environ["LLM"] = "sk-XXXXXXXXXXXXXXXXXX"
os.environ["SERPER_API_KEY"] = "XXXXXXXXXXXXXXXXXXXXXXXX" 

llm = ChatOpenAI(
    model="THUDM/GLM-4-9B-0414",
    api_key=os.getenv("LLM"),
    base_url="https://api.siliconflow.cn/v1",
    temperature=0
)
search_wrapper = GoogleSerperAPIWrapper()
search_tool = GoogleSerperRun(api_wrapper=search_wrapper)
search_tool.description = "当需要回答关于时事、最新信息或不确定事实的问题时,必须使用此工具进行网络搜索。"

python_repl_tool = PythonREPLTool()


@tool
def scrape_website(url: str) -> str:
    """
    当需要从一个特定的网页URL获取详细内容时,使用此工具。
    输入必须是一个有效的、完整的URL (例如 https://www.baidu.com)。
    """
    print(f"--- 正在浏览网页: {url} ---")
    loader = WebBaseLoader(url)
    docs = loader.load()
    return "".join(doc.page_content for doc in docs)

#    Agent 会根据问题和每个工具的描述来决定使用哪一个
tools = [search_tool, python_repl_tool, scrape_website]

prompt = hub.pull("hwchase17/react")
agent = create_react_agent(llm, tools, prompt)

agent_executor = AgentExecutor(
    agent=agent, 
    tools=tools, 
    verbose=False, 
    handle_parsing_errors=True 
)
question = "看看https://zh.stardewvalleywiki.com/mediawiki/index.php?title=Stardew_Valley_Wiki&variant=zh这个网站是干什么的?"
print(f"正在提问: {question}")
response = agent_executor.invoke({"input": question})
print("\n")
print(response.get("output"))

运行:
run

数据库连接 实践6

当AI能够连接数据库并对数据库进行操作时,其应用于真实商业场景就更近一步了。
提示:下面代码出现报错是十分正常的现象,特别是使用的硅基流动免费模型,模型不能够按照准确的格式返回内容导致报错,解决方案是使用参数更大的模型比如deepseek-v3,deepseek-r1等,笔者实测使用免费模型均会报错;同时Agent可能会一次性执行多个由分号 (;) 分隔的 SQL 语句,但是数据库的连接设置不允许这样做,从而出现报错,建议一个问题一个问题的问

注意LLM可能会编写SQL语句进行删库等操作,但是笔者实测发出删库指令被禁止执行了,原因是由于内置的安全协议限制,但还是需要防范,备份数据

# 安装数据库连接依赖
pip install mysql-connector-python

代码如下:

import os
from langchain_community.agent_toolkits.sql.base import create_sql_agent
from langchain_community.utilities.sql_database import SQLDatabase
from langchain_community.agent_toolkits.sql.toolkit import SQLDatabaseToolkit
from langchain_openai import ChatOpenAI

os.environ["LLM"] = "sk-XXXXXXXXXXXXXXXXXXXXXXXXXXXX"

llm = ChatOpenAI(
    model="deepseek-ai/DeepSeek-V3",
    api_key=os.getenv("LLM"),
    base_url="https://api.siliconflow.cn/v1",
    temperature=0
)

# 这里我以本地的MySQL为例
db_user = "root"
db_password = "123456"
db_host = "localhost"
db_name = "aitest"

# 构建 MySQL 的连接 URI
db_uri = f"mysql+mysqlconnector://{db_user}:{db_password}@{db_host}/{db_name}"


print(f"正在尝试连接到数据库: {db_name} ...")
try:
    db = SQLDatabase.from_uri(db_uri)
    print("数据库连接成功!")
except Exception as e:
    print(f"数据库连接失败: {e}")
    exit()


# 创建 SQL 工具箱
toolkit = SQLDatabaseToolkit(db=db, llm=llm)
tools = toolkit.get_tools()

agent_executor = create_sql_agent(
    llm=llm,
    toolkit=toolkit,
    verbose=True,
    handle_parsing_errors=True
)

question = "这个数据库一共有几张表多少数据?"
print(f"正在提问: {question}")
response = agent_executor.invoke({"input": question})
print("\n")
print(response.get("output"))

结果:
查询
在这里插入图片描述
在这里插入图片描述
关于langchain工具就介绍到这里,这里只介绍了几种常用工具,实际上官网上还列出超过200个工具(AI告诉我的The webpage https://python.langchain.com/docs/integrations/tools/ lists **over 200 tools** across various categories such as search, code interpreters, productivity, web browsing, databases, and finance. The exact count may vary as the page is updated, but it includes a comprehensive collection of integrations.),详细可以参阅官网,关于使用工具出现的报错,很大概率是LLM的问题,使用参数量更大的模型会更加智能,也更不容易出现报错。

在使用任何工具之前,有固定的模式:
安装 (Install): 使用 pip 安装提供该工具的库,部分库可能已经内置在langchain。
导入 (Import): 从相应的 langchain_community.tools 或其他模块中导入工具类。
实例化 (Instantiate): 创建工具的实例,并将其放入一个列表中,作为“工具箱”。
创建并运行 Agent: 将工具箱交给 AgentExecutor,让它来决定何时使用。

持久化对话(记忆)实践7

当前与LLM对话是没有记忆的,每个问题都是独立的,没有上下文的,我们需要添加持久化对话功能。
下面提供交互的python代码,结合了之前的工具:

import os
from langchain.agents import AgentExecutor, create_react_agent
from langchain.tools import tool
from langchain_community.document_loaders import WebBaseLoader 
from langchain_community.tools import GoogleSerperRun
from langchain_experimental.tools import PythonREPLTool
from langchain_community.utilities import GoogleSerperAPIWrapper
from langchain_openai import ChatOpenAI
from langchain import hub
from langchain.memory import ConversationBufferMemory


os.environ["LLM"] = "sk-XXXXXXXXXXXXXXXXXXXXXXXXX"
os.environ["SERPER_API_KEY"] = "XXXXXXXXXXXXXXXXXXXXXXXXXXXX" 

llm = ChatOpenAI(
    model="Qwen/Qwen3-8B", 
    api_key=os.getenv("LLM"),
    base_url="https://api.siliconflow.cn/v1",
    temperature=0
)

search_wrapper = GoogleSerperAPIWrapper()
search_tool = GoogleSerperRun(api_wrapper=search_wrapper)
search_tool.description = "当需要回答关于时事、最新信息或不确定事实的问题时,必须使用此工具进行网络搜索。"
python_repl_tool = PythonREPLTool()
@tool
def scrape_website(url: str) -> str:
    """当需要从一个特定的网页URL获取详细内容时,使用此工具。"""
    print(f"--- 正在浏览网页: {url} ---")
    loader = WebBaseLoader(url)
    docs = loader.load()
    return "".join(doc.page_content for doc in docs)
tools = [search_tool, python_repl_tool, scrape_website]


# --- 创建带记忆的 Agent 和执行器 ---
prompt = hub.pull("hwchase17/react-chat")
memory = ConversationBufferMemory(memory_key="chat_history")
agent = create_react_agent(llm, tools, prompt)
agent_executor = AgentExecutor(
    agent=agent, 
    tools=tools, 
    memory=memory,
    verbose=True,
    handle_parsing_errors=True 
)

print("\n--- 可以开始对话了 ---")
print("--- (输入 'exit', 'quit' 或 '再见' 来结束对话) ---")

while True:
    user_input = input("\n输入: ")

    if user_input.lower() in ["exit", "quit", "再见"]:
        print("再见!")
        break

    response = agent_executor.invoke({"input": user_input})
    print(f"\n回答: {response.get('output')}")

在这里插入图片描述
在这里插入图片描述
上面代码更改了Prompt(提示),那么hwchase17/react-chathwchase17/react区别在那里?
hwchase17/react-chat包含了{chat_history}的占位符,使LLM在对话时不仅能够看到问题,也能够看到聊天历史,从而理解上下文。

工具集成

由于一个一个文件写API-key太麻烦,所以创建了一个.env文件放在同目录下,.env文件内容如下,更换API就好:

# 需要先 pip install python-dotenv

LLM=sk-XXXXXXXXXXXXXXXXXXXXXX
SERPER_API_KEY=XXXXXXXXXXXXXXXXXXXXXXX

现在将上述工具进行集成:

import os
import re
import json
from pathlib import Path

from dotenv import load_dotenv
load_dotenv()

from langchain.agents import AgentExecutor, create_react_agent, Tool
from langchain.tools import tool
from langchain import hub
from langchain.memory import ConversationBufferMemory
from langchain_openai import ChatOpenAI, OpenAIEmbeddings

from langchain_community.document_loaders import PyPDFLoader, WebBaseLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.vectorstores import FAISS
from langchain.chains import create_retrieval_chain, create_sql_query_chain
from langchain.chains.combine_documents import create_stuff_documents_chain
from langchain_core.prompts import ChatPromptTemplate
from langchain_community.utilities import SQLDatabase, GoogleSerperAPIWrapper
from langchain_community.agent_toolkits import SQLDatabaseToolkit
from langchain_experimental.tools import PythonREPLTool
from langchain_community.tools import GoogleSerperRun

LLM = ChatOpenAI(
    # model="THUDM/GLM-4-9B-0414",
    model="deepseek-ai/DeepSeek-V3",
    api_key=os.getenv("LLM"),
    base_url="https://api.siliconflow.cn/v1",
    temperature=0
)
embeddings = OpenAIEmbeddings(
    model="BAAI/bge-m3",
    api_key=os.getenv("LLM"),
    base_url="https://api.siliconflow.cn/v1"
)
db = None
try:
    print("\n--- 正在尝试连接数据库... ---")
    db_uri = f"mysql+mysqlconnector://root:123456@localhost/学生选课"
    db = SQLDatabase.from_uri(db_uri)
    print("数据库连接成功!")
except Exception as e:
    print(f"数据库连接失败: {e}\n")


@tool
def rag_document_analyzer(user_input: str) -> str:
    """
    当需要基于一个指定的PDF文档来回答问题时,使用此工具。
    输入应该是一个字符串,其中清晰地包含你的问题以及用引号括起来的文件路径。
    例如: "在 '项目可行性分析报告.pdf' 文件中,风险章节提到了什么?"
    """
    match = re.search(r"['\"]([^'\"]+\.pdf)['\"]", user_input, re.IGNORECASE)
    if not match:
        return "错误: 未能在你的问题中找到有效的PDF文件名。请将文件名包含在单引号或双引号中,例如 'my_report.pdf'。"
    
    file_path = match.group(1)
    query = user_input

    try:
        print(f"\n--- 正在加载和处理PDF: {file_path} ---")
        loader = PyPDFLoader(file_path)
        docs = loader.load()
        text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=100)
        splits = text_splitter.split_documents(docs)
        vectorstore = FAISS.from_documents(documents=splits, embedding=embeddings)
        retriever = vectorstore.as_retriever()
        
        rag_prompt = ChatPromptTemplate.from_template("请仅根据上下文回答问题:\n上下文: {context}\n问题: {input}")
        document_chain = create_stuff_documents_chain(LLM, rag_prompt)
        rag_chain = create_retrieval_chain(retriever, document_chain)
        response = rag_chain.invoke({"input": query})
        return response.get("answer", "无法在文档中找到答案。")
    except Exception as e:
        return f"处理文档 '{file_path}' 时发生错误: {e}"

@tool
def scrape_website_tool(url: str) -> str:
    """当需要从一个特定的网页URL获取详细、完整的文本内容时,使用此工具。输入必须是一个有效的URL。"""
    print(f"\n--- 正在抓取网页: {url} ---")
    try:
        loader = WebBaseLoader(url)
        content = loader.load()[0].page_content
        return f"网页内容抓取成功:\n{content[:2500]}..."
    except Exception as e:
        return f"抓取网页失败: {e}"

# --- 组装工具列表 ---
tools = [rag_document_analyzer, scrape_website_tool, PythonREPLTool()]

if db:
    sql_toolkit = SQLDatabaseToolkit(db=db, llm=LLM)
    sql_tools = sql_toolkit.get_tools()
    for t in sql_tools:
        if t.name == "sql_db_query":
            t.description = "一个只读工具,用于执行单一的 SQL SELECT 查询来回答问题。只能用于查询数据,不能修改数据。"
            break
    
    def run_sql_command(command: str) -> str:
        """
        一个包装函数,用于智能处理 Agent 的输入并执行 SQL 命令。
        """
        # 检查输入是否像一个 JSON 字符串
        if command.strip().startswith("{"):
            try:
                # 尝试解析 JSON
                data = json.loads(command)
                # 提取 'command' 或 'query' 字段
                sql_query = data.get("command", data.get("query", ""))
                if not sql_query:
                    return "错误: 输入的 JSON 中没有找到 'command' 或 'query' 字段。"
                print(f"\n--- 从JSON中提取到SQL: {sql_query} ---")
                return db.run(sql_query)
            except json.JSONDecodeError:
                # 如果解析失败,就当作普通字符串处理
                print(f"\n--- 输入像JSON但解析失败,将作为纯文本执行: {command} ---")
                return db.run(command)
        else:
            # 如果不是 JSON,直接执行
            return db.run(command)
    sql_run_tool = Tool(
        name="sql_db_run_command",
        func=run_sql_command,
        description="一个强大的管理员工具,用于执行可能修改数据库的 SQL 命令(如 ALTER TABLE, UPDATE, INSERT, DELETE)或一次执行多条SQL语句。只有在绝对必要时才使用。"
    )
    tools.extend(sql_tools)
    tools.append(sql_run_tool)

# --- 添加网络搜索工具 ---
try:
    google_search_tool = Tool(
        name="google_search",
        func=GoogleSerperAPIWrapper().run,
        description="当需要快速回答时事、简单事实或任何其他工具都无法处理的通用问题时,使用此工具。这是一个最终的后备选项。"
    )
    tools.append(google_search_tool)
except Exception as e:
    print(f"网络搜索工具初始化失败: {e}\n")


print(f"\n--- 初始化完成 ---")
prompt = hub.pull("hwchase17/react-chat")
memory = ConversationBufferMemory(memory_key="chat_history", return_messages=True, output_key='output')
agent = create_react_agent(LLM, tools, prompt)
agent_executor = AgentExecutor(
    agent=agent, 
    tools=tools, 
    memory=memory,
    verbose=True,
    handle_parsing_errors=True,
    max_iterations=10
)

def main():
    print("--- (输入 'exit' 来结束对话) ---\n")
    while True:
        try:
            user_input = input("输入: ")
            if user_input.lower() in ["exit", "quit"]:
                break
            response = agent_executor.invoke({"input": user_input})
            print(f"\n输出: {response.get('output')}")

        except Exception as e:
            print(f"\n发生了一个错误: {e}")

if __name__ == "__main__":
    main()

测试结果:
1
2
3
4
5
6
7

输出解析器

LLM的回答通常是一段话的,非结构化的,而一个优秀的LLM程序需要精准的数据格式(例如JSON等),使用输出解析器将规范LLM的回答,其指导模型如何输出、解析验证模型输出。

示例的meeting.txt

会议主题:2025年第三季度产品路线图规划
参会人员:张三、李四、王五
会议日期:2025年7月23日
核心决定:
1.  产品A:决定于8月15日上线新功能“智能分析报告”。该功能由李四团队负责。
2.  产品B:市场反馈不佳,一致同意在9月底前逐步下线,并引导用户迁移至产品A。
3.  新项目“Project Phoenix”:正式启动,王五被任命为项目负责人,初步预算为50万元。目标是在年底前完成原型开发。
pip install pydantic
import os
import datetime
from typing import List, Optional
from pydantic import BaseModel, Field
from langchain_openai import ChatOpenAI
from langchain_core.prompts import PromptTemplate
from langchain.output_parsers import PydanticOutputParser

llm = ChatOpenAI(
	# 出现报错大概率是模型问题
    model="deepseek-ai/DeepSeek-R1-0528-Qwen3-8B",
    api_key="sk-XXXXXXXXXXXXXXXXXXXXXXX",
    base_url="https://api.siliconflow.cn/v1",
    temperature=0
)

# 定义数据结构
class Decision(BaseModel):
    """定义单个核心决定的数据结构"""
    item_name: str = Field(description="涉及的产品或项目名称,例如 '产品A' 或 'Project Phoenix'")
    details: str = Field(description="关于该项目的具体决定内容摘要")
    owner: Optional[str] = Field(description="该事项的负责人,如果没有明确指定则为空", default=None)
    due_date: Optional[str] = Field(description="相关的截止日期,例如 '8月15日' 或 '9月底前'", default=None)
    budget: Optional[float] = Field(description="相关的预算金额(仅抽取出数字部分),如果没有则为空", default=None)

class MeetingInfo(BaseModel):
    """定义整个会议纪要的顶层数据结构"""
    topic: str = Field(description="会议的核心主题")
    attendees: List[str] = Field(description="所有参会人员的姓名列表")
    date: datetime.date = Field(description="会议举行的日期,格式为 YYYY-MM-DD")
    decisions: List[Decision] = Field(description="会议中做出的所有核心决定的列表")


# ==============================================================================
# 创建一个知道如何解析上述“蓝图”的解析器
# ==============================================================================

parser = PydanticOutputParser(pydantic_object=MeetingInfo)

# ==============================================================================
# 构建一个包含“格式化指令”的提示模板
# ==============================================================================

prompt_template = """
你是一个专业的会议助理AI。请仔细阅读下面提供的会议纪要原文。
你的任务是根据原文内容,严格、准确地提取出所有关键信息。

{format_instructions}

会议纪要原文:
```{meeting_minutes}```

请直接输出符合上述格式的JSON对象,不要包含任何其他解释性文字。
"""

prompt = PromptTemplate(
    template=prompt_template,
    input_variables=["meeting_minutes"],
    partial_variables={"format_instructions": parser.get_format_instructions()}
)

# 创建链
chain = prompt | llm | parser

# 读取 meeting.txt 文件内容
with open('meeting.txt', 'r', encoding='utf-8') as f:
    meeting_content = f.read()
print("--- 已成功读取文件 ---")


# 执行链,并传入会议纪要内容
print("\n--- 正在调用 LLM 进行智能信息提取,请稍候... ---")
parsed_output = chain.invoke({"meeting_minutes": meeting_content})


# 输出提取结果
print("\n --- 信息提取成功!--- \n")

# `parsed_output` 现在是一个 MeetingInfo 对象
print("提取出的数据类型:", type(parsed_output))
print("-" * 20)

# 我们可以像操作任何 Python 对象一样访问它的属性
print(f"会议主题: {parsed_output.topic}")
print(f"参会人员: {parsed_output.attendees}")
print(f"会议日期: {parsed_output.date}")
print("-" * 20)

print("核心决定详情:")
for i, decision in enumerate(parsed_output.decisions):
    print(f"  决定 {i+1}:")
    print(f"    项目: {decision.item_name}")
    print(f"    内容: {decision.details}")
    print(f"    负责人: {decision.owner}")
    print(f"    截止日期: {decision.due_date}")
    print(f"    预算: {decision.budget}")
print("-" * 20)

print("\n--- 转换成标准 JSON 格式 ---")
json_output = parsed_output.model_dump_json(indent=2)
print(json_output)

运行结果:
在这里插入图片描述
在这里插入图片描述
接下来我们可以直接利用JSON数据进行接下来操作

LangSmith

LangSmithLangChain 官方推出的一个用于调试、追踪、评估和监控 LLM 应用的平台。它是将原型项目推向生产环境的必备工具。

https://smith.langchain.com/官网上注册一个账号,获取一个API:
1
2
3
再将复制的这段代码放在.env中(将最后一行OPENAI_API_KEY删除),此时你的.env应该长这样:
env
然后照常运行你的代码,进行几轮对话,就能够在网页上看到你的测试情况,包含各条运行记录,运行的各个输出以及出错的位置,帮助更好分析程序情况,解决错误:
在这里插入图片描述
可以在平台上进行相关的优化等,查看问题出在那里,在Playground进行调试(需要OPENAI的API)等,最主要的还是观察输入输出状况,然后优化 Prompt 以降低成本和延迟这些…其中还包括自动化评估(类似于测试等来优化流程等),这属于软件中后期的工作了,就适当略过。

LangGraph

到现在,已经掌握了LangChain,但是现在的 Agent,使用的是 ReAct 模式。它就像一个黑盒,给它工具,它在内部思考,然后自己决定调用哪个工具。但当流程变得复杂时,就很难精确控制它的行为。
而LangGraph 把这种隐式的、黑盒的决策过程,变成了显式的、由你设计的图。你将亲自绘制智能体的“思考地图”,规定它在什么情况下该走向哪条路。这带来了前所未有的可靠性和可控性。
由于是面向初学者的LangChain学习指南,LangGraph属于更加进阶的内容,这里就不再阐述LangGraph了,感兴趣的可以参考官方https://www.langchain.com/langgraph

结语

虽然 LangChain 是目前最知名、生态最成熟的框架,但 AI Agent 领域百花齐放,许多优秀的框架和工具正从不同角度切入,也看到了许许多多的程序从用AI构建程序到构建AI的程序,但是这些框架的能力依然由大语言模型(LLM)所决定。从2023初始(当时笔者还在上高三),我的朋友非常兴奋的告诉我什么ChatGPT能够很智能的回答问题,到现在2025年,Grok4、Gemini2.5Pro,LLM的迭代速度十分迅速,我依然相信在不久的将来AI将会取代大部分简单重复的工作而且成为复杂工作的强大助力,这种改变对“捧着电脑上班”的人群尤为明显。

Logo

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

更多推荐