简介 LangChain
是一个基于大型语言模型(LLMs)的开源框架,旨在帮助开发者构建端到端的语言模型应用程序。他是基于Python语言的框架,我们今天来初步尝试一下。
基础概念 LangChain的核心组件 LangChain
框架的核心模块主要包括模型输入输出(Model I/O)、数据连接(Data Connection)、 链(Chains)、记忆(Memory)、代理(Agents)和回调(Callbacks)等六个模块。
模型输入输出模块
提供了语言模型和大语言模型的接口,可以将文本格式化为模型需要的输入(与大模型交互,向大模型发送文本,并接收返回的信息)。
数据连接模块
提供了文档加载器和文档转换器等工具,用于将非结构化文本转换为可处理的数据(也许称为文本加工模块更加符合)。
链模块
提供了各种类型的链,如基础链、路由链和顺序链等,用于组合和连接不同的功能。(比如需要查询外部接口、加工输入信息,我们可以用链将这些任务串起来)
记忆模块
用于在链之间存储和传递信息,实现对话的上下文感知能力。(将对话数据存储到nosql或者sql中)
代理模块
通过使用LLM来自动决策和执行动作,完成任务。(也许可以叫助理模块,我们将资源和需要执行的任务目标告诉程序,程序自动帮助我们实现任务)
回调模块
提供了连接到LLM申请的各个阶段的功能,用于日志记录、监控和流传输等任务。
文本 & 聊天消息 文本 与LLM交互的自然语言方式
聊天消息 类似文本,但指定了消息类型(系统、人类、人工智能)
系统(system):有用的背景消息,告诉人工智能该做什么
人类(human):用户的消息
人工智能(AI):人工智能响应的内容消息
提示词 & 提示词模版 提示词(Prompts) 提示词是引导语言模型生成特定输出的输入文本。在LangChain
中,提示词通过模板实现动态化和结构化,使开发者能够分离文本逻辑与数据,提高复用性和可维护性。
提示词模板(Prompt Templates) 模板是包含占位符的文本框架,运行时动态填充变量生成最终提示词。例如:
1 2 3 4 from langchain.prompts import PromptTemplatetemplate = "请为一家{industry}公司起一个名字。" prompt = PromptTemplate(input_variables=["industry" ], template=template) formatted_prompt = prompt.format (industry="人工智能" )
文档加载器 & 文本分割器 文档加载器(Document Loaders) 支持从多种来源加载文本(如PDF、CSV、数据库、网页爬虫等),例如:
1 2 3 from langchain.document_loaders import PyPDFLoaderloader = PyPDFLoader("example.pdf" ) documents = loader.load()
文本分割器(Text Splitters) 将长文本分割为适合模型处理的片段,保持语义连贯性:
1 2 3 from langchain.text_splitter import RecursiveCharacterTextSplittersplitter = RecursiveCharacterTextSplitter(chunk_size=1000 , chunk_overlap=200 ) splits = splitter.split_documents(documents)
示例选择器 简介 示例选择器的作用是从一组预定义的示例中,根据输入动态筛选最相关的子集,帮助语言模型更好地理解任务并生成准确回答。(对提示词做加工)
内置选择器类型
类型
选择策略
适用场景
LengthBasedExampleSelector
根据输入长度调整示例数量(长输入选较少示例,短输入选更多)
控制上下文窗口长度
MMRExampleSelector
基于最大边际相关性(Max Marginal Relevance),平衡相关性与多样性
信息检索、推荐系统
SemanticSimilarityExampleSelector
通过嵌入模型(如余弦相似度)选择语义最相似的示例
需要高语义匹配的任务
NGramOverlapExampleSelector
基于 N-gram 重叠度选择示例(如词或字符序列匹配)
文本匹配、翻译任务
输出解析器 核心功能 输出解析器用于将语言模型(LLM)生成的非结构化文本 转换为结构化数据 (如JSON、列表、Pydantic对象等),主要作用包括:
结构化转换 :将模型输出的文本解析为程序可处理的格式(如字典、列表、日期对象等)
数据验证 :通过Pydantic等工具确保输出符合预定义的字段类型和约束(如评分必须在1-10之间)
错误修复 :自动修复格式错误的输出(如无效JSON),通过OutputFixingParser
重新调用LLM修正
模型指导 :通过get_format_instructions()
生成格式说明,引导LLM输出符合预期的结构
类型
解析器类型
类名
核心功能
字符串解析器
StrOutputParser
提取模型输出的原始文本,不做额外处理
逗号分隔列表解析器
CommaSeparatedListOutputParser
将逗号分隔的文本转换为Python列表
JSON解析器
JsonOutputParser
将JSON格式文本转换为Python字典或列表,支持Pydantic验证
Pydantic解析器
PydanticOutputParser
基于Pydantic模型定义输出结构,支持嵌套字段和强类型验证
结构化输出解析器
StructuredOutputParser
通过ResponseSchema
自定义字段,生成结构化字典
日期时间解析器
DatetimeOutputParser
将文本解析为Python datetime
对象,支持多种时间格式
枚举解析器
EnumOutputParser
限制输出为预定义的枚举值(如颜色类型)
自动修复解析器
OutputFixingParser
包装其他解析器,在解析失败时调用LLM修复错误
XML解析器
XMLOutputParser
解析XML格式的输出为字典或对象
使用场景与最佳实践 使用场景
数据提取 :从模型回答中提取结构化字段(如电影信息、产品参数)
API集成 :将LLM输出转换为JSON格式,供其他系统调用
多语言支持 :结合翻译链处理跨语言输出
最佳实践
错误处理 :使用OutputFixingParser
或RetryWithErrorParser
增强鲁棒性
嵌套结构 :通过Pydantic模型定义复杂数据结构,并在提示中提供清晰示例
性能优化 :对高频解析结果缓存,减少LLM重复调用
环境搭建 langchain是一个基于python语言的框架,开发环境有多种选择,langchain官方推荐使用JupyterNotebook,但我使用的是Anaconda+Pycharm,Anaconda用来管理python环境,Pycharm是Jetbrain专门用于开发python的软件。
使用Jupyter 搭建环境 地址:https://www.devbean.net/2024/07/langchain-01-setup-jupyter/
使用Anaconda搭建环境 首先下载Anaconda并安装,然后修改.condarc
文件,配置镜像源,这里使用的是清华镜像源
1 2 3 4 5 6 7 8 9 10 channels: - defaults show_channel_urls: true default_channels: - https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main - https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/r - https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/msys2 custom_channels: conda-forge: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud pytorch: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud
然后进入Anaconda PowerShell Prompt
,输入命令创建环境
1 2 3 4 5 6 7 8 9 10 # 列出所有环境 conda env list # 创建虚拟环境 conda create -n langchain_env python=3.12 # 名称为‘langchain_env’ 使用的python版本为3.12 # 激活虚拟环境 conda activate langchain_env # 安装核心依赖 pip install langchain-core langchain-community langchain-openai langchain-deepseek # 安装requirements.txt文件中的依赖(可以使用requirements.txt文件来统一管理依赖) pip install -r requirements.txt
接着我们打开PyCharm,新建一个Python项目,我的叫‘langchain-demo’;
在项目的根目录下新建一个.env
文件,用来存放需要使用到的环境变量(配置信息);
1 2 3 4 ALIYUN_API_KEY="sk-xxx" ALIYUN_MODEL_NAME="text-embedding-v1" DEEPSEEK_API_KEY="sk-xxx" DEEPSEEK_MODEL="deepseek-chat"
另外新建一个requirements.txt
文件来存放项目的python依赖,使用命令“pip install -r requirements.txt”安装依赖;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 aiohappyeyeballs==2.6.1 aiohttp==3.12.12 aiosignal==1.3.2 annotated-types==0.6.0 anyio==4.7.0 attrs==25.3.0 black==23.12.1 certifi==2025.4.26 charset-normalizer==3.4.2 click==8.1.8 colorama==0.4.6 dataclasses-json==0.6.7 distro==1.9.0 fastapi>=0.115.0,<0.116.0 # 允许补丁更新,避免次要版本升级[2,8](@ref) frozenlist==1.7.0 greenlet==3.1.1 h11==0.16.0 httpcore==1.0.9 httptools==0.6.4 httpx==0.28.1 httpx-sse==0.4.0 idna==3.7 jiter==0.9.0 jsonpatch==1.33 jsonpointer==3.0.0 langchain==0.3.25 langchain-community==0.3.24 langchain-core==0.3.60 langchain-deepseek==0.1.3 langchain-openai==0.3.17 langchain-text-splitters==0.3.8 langgraph==0.4.8 langgraph-checkpoint==2.0.26 langgraph-prebuilt==0.2.2 langgraph-sdk==0.1.70 langsmith==0.3.42 marshmallow==3.26.1 multidict==6.4.4 mypy_extensions==1.1.0 numpy==2.3.0 openai==1.79.0 orjson==3.10.18 ormsgpack==1.10.0 packaging==24.2 propcache==0.3.2 pydantic==2.10.3 pydantic-settings==2.6.1 pydantic_core==2.27.1 PyMySQL==1.1.1 python-dotenv==1.1.0 python-multipart==0.0.20 PyYAML==6.0.2 regex==2024.11.6 requests==2.32.4 requests-toolbelt==1.0.0 setuptools==78.1.1 sniffio==1.3.0 SQLAlchemy==2.0.39 starlette==0.38.2 tenacity==9.1.2 tiktoken==0.9.0 tqdm==4.67.1 typing-inspect==0.9.0 typing_extensions==4.12.2 urllib3==2.4.0 uv==0.7.5 uvicorn==0.32.1 watchfiles==1.0.5 websockets==15.0.1 wheel==0.45.1 xxhash==3.5.0 yarl==1.20.1 zstandard==0.23.0
我们引入了uvicorn,可以使用命令”uvicorn app.main:app –reload“启动项目
基础教程 简单的交互 我们使用deepseek大模型,首先在deepseek开发平台注册并创建api_key,在项目中创建model/deepseekModel.py
,将大模型服务注册为一个对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import osfrom langchain_deepseek import ChatDeepSeekfrom dotenv import load_dotenvload_dotenv() DEEPSEEK_API_KEY = os.getenv("DEEPSEEK_API_KEY" ) model = ChatDeepSeek( model="deepseek-chat" , api_key=DEEPSEEK_API_KEY, temperature=0.5 , max_tokens=512 )
我们再创建一个helloworld.py
,再其中调用大模型服务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 from langchain_core.messages import SystemMessage, HumanMessagefrom langchain_core.output_parsers import StrOutputParserfrom model.deepseekModel import modelmessage = [ SystemMessage(content="你是一个翻译助手,请将下面的内容翻译为英文" ), HumanMessage(content="你好,langchain,今天过得怎么样?" ) ] response = model.invoke(message) print (response)parser = StrOutputParser() parser_response = parser.invoke(response) print ("提取到的文本信息:" + parser_response)chain = model | parser chain_response = chain.invoke(message) print ("链式调用获取到的信息:" + chain_response)
可以看到代码还是比较简单的,稍微解释一下链式调用,在java中有一种设计模式叫‘责任链’,简单来说就是将复杂的任务切割为相对简单的一个个业务模块,再由流程控制器调用;使用时就可以灵活的调用其中的某几个模块,业务执行时就像链条一样,按照串联顺序执行。
构建聊天机器人 刚才我们已经实现了与大模型的对话,但是多进行几次对话我们会发现两个不足的地方,第一,每次发送请求返回的延迟都有点高,造成这个问题的原因是现在AI大模型将文本全部生成后再返回给我们;第二,现在AI大模型记不住对话信息,所以每次发送请求都相当于第一句对话;
现在我们想进一步构建一个聊天机器人,弥补我们刚才说的两个问题;所以需要更进一步实现两个功能:流式访问和记忆对话;流式访问是指我们让AI大模型返回的信息采用流式返回(生成词语及返回,不需要全部生成后返回),这样就可以避免返回时间过长的问题;记忆对话是指我们将对话信息保存下来,并在下一次发送对话请求时将整个对话信息发送给AI大模型,这样AI大模型就可以像人类一样与我们进行交谈;代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 from langchain_core.output_parsers import StrOutputParserfrom langchain_core.prompts import ChatPromptTemplate, SystemMessagePromptTemplate, HumanMessagePromptTemplate, \ MessagesPlaceholder from langchain_community.chat_message_histories import ChatMessageHistoryfrom langchain_core.chat_history import BaseChatMessageHistoryfrom langchain_core.runnables import RunnableWithMessageHistoryfrom model.DeepseekModel import deepseek_modelmessageTemplate = ChatPromptTemplate.from_messages( [ SystemMessagePromptTemplate.from_template("你是一个对话助手,请用{language}友好回答问题" ), HumanMessagePromptTemplate.from_template("{text}" ), MessagesPlaceholder(variable_name="text" ) ] ) parser = StrOutputParser() chain = messageTemplate | deepseek_model | parser store = {} def get_session_history (session_id: str ) -> BaseChatMessageHistory: if session_id not in store: store[session_id] = ChatMessageHistory(); return store[session_id] chat_with_history = RunnableWithMessageHistory( chain, get_session_history, input_messages_key="text" ) config = {"configurable" : {"session_id" : "100" }} for res1 in chat_with_history.stream( { "language" : "中文" , "text" : "我的名字叫张三" }, config=config, ): print (res1, end="|" ) for res2 in chat_with_history.stream( { "language" : "中文" , "text" : "我的名字叫什么" }, config=config ): print (res2, end="|" )
构建向量存储和检索器 对于向量存储的概念,我们之前的文章打造私有知识库 中,我们已经介绍过向量存储的概念,现在来看看在langchain
中如何实现;首先,在python中引入阿里云百炼的向量模型,我们先在.evn
文件中添加api_key
和model_name,
1 2 ALIYUN_API_KEY="sk-xxx" ALIYUN_MODEL_NAME="text-embedding-v1"
再初始化一个阿里云百炼向量模型的对象
1 2 3 4 5 6 7 8 9 10 11 12 13 import osfrom dotenv import load_dotenvfrom langchain_community.embeddings import DashScopeEmbeddingsload_dotenv() aliyun_key = os.getenv("ALIYUN_API_KEY" ) model_name = os.getenv("ALIYUN_MODEL_NAME" , "text-embedding-v1" ) embeddings = DashScopeEmbeddings( dashscope_api_key=aliyun_key, model=model_name )
我们编写一个相似度查询的demo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 from langchain_chroma import Chromafrom langchain_core.documents import Documentfrom langchain_core.prompts import ChatPromptTemplatefrom langchain_core.runnables import RunnableLambda, RunnablePassthroughfrom langchain_core.output_parsers import StrOutputParserfrom langchain.globals import set_debug, get_debugfrom model.DeepseekModel import deepseek_modelset_debug(True ) debug_status = get_debug() print ("Debug mode:" , debug_status)documents = [ 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 model.AliyunBailianEmbeddings import embeddingsvectorstore = Chroma.from_documents( documents=documents, embedding=embeddings ) res1 = vectorstore.similarity_search("cat" ) print (res1)print ("========================" )res2 = vectorstore.similarity_search_with_score("cat" ) print (res2)print ("========================" )retriever = RunnableLambda(vectorstore.similarity_search).bind(k=1 ) res3 = retriever.batch(["猫" , "鲨鱼" ]) print (res3)print ("========================" )message = """ 仅使用提供的上下文回答下面的问题: {question} 上下文: {context} """ rag_prompt_template = ChatPromptTemplate.from_messages(("human" , message)) parser = StrOutputParser() chain = {"question" : RunnablePassthrough(), "context" : retriever} | rag_prompt_template | deepseek_model | parser res4 = chain.invoke("请为我介绍一下猫这种生物" ) print (res4)
可以看到,代码的基本逻辑就是将文档(docs)添加到向量存储(vectorstore)中,然后利用RunnableLambda构建一个检索器(retriever),将检索器添加到执行链(chain)中,这样执行就可以相似度检索(执行链会将用户对话向量化,与向量存储中的内容进行向量比较,返回最接近的内容)
构建对话式 RAG 我们前面介绍了向量存储,这里我们进一步实现对话式RAG;关于RAG的知识我们在文章langchain4j实现智能助手 中介绍过,现在我们看看在langchain中如何实现,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 import osimport bs4from langchain.chains import create_retrieval_chainfrom langchain.chains.combine_documents import create_stuff_documents_chainfrom langchain.chains.history_aware_retriever import create_history_aware_retrieverfrom langchain_chroma import Chromafrom langchain_community.chat_message_histories import ChatMessageHistoryfrom langchain_community.document_loaders import WebBaseLoaderfrom langchain_core.chat_history import BaseChatMessageHistoryfrom langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholderfrom langchain_core.runnables import RunnableWithMessageHistoryfrom langchain_text_splitters import RecursiveCharacterTextSplitterfrom model.AliyunBailianEmbeddings import embeddingsfrom model.DeepseekModel import deepseek_modelos.environ[ "USER_AGENT" ] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36" loader = WebBaseLoader( web_paths=("https://lilianweng.github.io/posts/2023-06-23-agent/" ,), bs_kwargs=dict ( parse_only=bs4.SoupStrainer( class_=("post-content" , "post-title" , "post-header" ) ) ), header_template={ "User-Agent" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36" }, ) docs = loader.load() text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000 , chunk_overlap=200 ) split_documents = text_splitter.split_documents(docs) vectorstore = Chroma.from_documents(documents=split_documents, embedding=embeddings) retriever = vectorstore.as_retriever() contextualize_q_system_prompt = ( "给定一个聊天历史记录和最新的用户问题," "可能会涉及聊天历史记录中的上下文," "制定一个独立的问题,可以在没有聊天历史记录的情况下理解。" "如果需要,重新构造问题,否则原样返回。" ) contextualize_q_prompt = ChatPromptTemplate.from_messages( [ ("system" , contextualize_q_system_prompt), MessagesPlaceholder("chat_history" ), ("human" , "{input}" ), ] ) history_aware_retriever = create_history_aware_retriever(deepseek_model, retriever, contextualize_q_prompt) chat_store = {} def get_session_history (session_id: str ) -> BaseChatMessageHistory: if session_id not in chat_store: chat_store[session_id] = ChatMessageHistory() return chat_store[session_id] system_prompt = ( "您是一个用于回答问题的助手。" "使用检索到的上下文来回答问题。" "如果不知道答案,请说不知道。" "最多使用三句话,保持回答简洁。" "\n\n" "{context}" ) human_prompt = ChatPromptTemplate.from_messages( [ ("system" , system_prompt), MessagesPlaceholder("chat_history" ), ("human" , "{input}" ), ] ) question_answer_chain = create_stuff_documents_chain(deepseek_model, human_prompt) rag_chain = create_retrieval_chain(history_aware_retriever, question_answer_chain) conversational_rag_chain = RunnableWithMessageHistory(rag_chain, get_session_history, input_messages_key="input" , output_messages_key="answer" , history_messages_key="chat_history" , ) message1 = {"input" : "什么是任务分解?" } config1 = { "configurable" : {"session_id" : "abc123" } } response1 = conversational_rag_chain.invoke(input =message1, config=config1) print (response1["answer" ])message2 = {"input" : "常见的做法有哪些?" } config2 = config = {"configurable" : {"session_id" : "abc123" }} response2 = conversational_rag_chain.invoke(input =message2, config=config2) print (response2["answer" ])
可以看到,我们的工作大致分为5个部分:1. 加载资源并将资源分块,利用资源创建向量存储和历史感知检索器; 2. 构建聊天对话存储 3. 构建提示词模版和提示词 4. 构建执行链 5. 调用执行链
构建在 SQL 数据上的问答系统 在上文’构建对话式RAG‘中我们使用的资源来自网络上的博客,而我们平时经常使用的数据库,也是一个重要的资源;我们来看看如何使用SQL数据库,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 from operator import itemgetterfrom langchain.chains.sql_database.query import create_sql_query_chainfrom langchain_community.tools import QuerySQLDataBaseToolfrom langchain_community.utilities import SQLDatabasefrom langchain_core.output_parsers import StrOutputParserfrom langchain_core.prompts import PromptTemplatefrom langchain_core.runnables import RunnablePassthroughfrom model.DeepseekModel import deepseek_modeldb = SQLDatabase.from_uri("sqlite:///D:/Data/chinook/chinook.db" ) sql_query_chain = create_sql_query_chain(deepseek_model, db) message = {"question" : "有多少名员工" } response1 = sql_query_chain.invoke(message) sql = "" ; if ": " in response1: sql = response1.split(": " , 1 )[1 ].strip() print (sql)print ("=========================" )class ExecuteTool (QuerySQLDataBaseTool ): def _run (self, query: str ) -> str : if query.startswith("SQLQuery:" ): query = query.split(":" , 1 )[1 ].strip() return super ()._run(query) execute_tool = ExecuteTool(db=db) chain = sql_query_chain | execute_tool response2 = chain.invoke(message) print (response2)print ("=========================" )answer_prompt = PromptTemplate.from_template( """给定以下用户问题,相应的 SQL 查询和 SQL 结果,回答用户问题。 问题:{question} SQL 查询:{query} SQL 结果:{result} 答案:""" ) chain = ( RunnablePassthrough.assign(query=sql_query_chain).assign( result=itemgetter("query" ) | execute_tool ) | answer_prompt | deepseek_model | StrOutputParser() ) response3 = chain.invoke({"question" : "有多少员工" }) print (response3)print ("=========================" )
可以看到,我们的代码大致分为3个部分:1. 引入数据库db后,利用其与AI大模型构建查询链,当我们输入问题,程序就能返回对应的SQL语句;2. 我们对返回的SQL语句进行格式处理后,构建SQL的执行器;3. 构建提示词;4. 将查询链、数据库执行器、提示词、AI大模型、文本处理器串联为最终的执行链;5. 调用执行链
进阶教程(如何使用代理) 代理(Agent) 单独来看,语言模型无法采取行动 - 它们只能输出文本。 LangChain
的一个重要用途是创建代理 。 代理是使用AI大模型作为推理引擎的系统,用于确定要采取的行动以及这些行动的输入应该是什么。 然后,可以将这些行动的结果反馈给代理,并确定是否需要更多行动,或者是否可以结束。我们来构建一个代理,该代理可以与多个不同的工具进行交互:一个是本地数据库,另一个是搜索引擎。我们可以使用tavily_search
,这是langchain
已经集成的一款搜索引擎,当然我们先申请其api_key
,将其填入.env
文件,程序就可以自动加载;实现agent的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 from dotenv import load_dotenvfrom langchain_community.tools import TavilySearchResultsfrom langgraph.prebuilt import chat_agent_executorfrom langchain_core.messages import HumanMessagefrom model.DeepseekModel import deepseek_modelload_dotenv() tavily_search = TavilySearchResults(max_results=1 ) tools = [tavily_search] model_bind_tavily = deepseek_model.bind_tools(tools) agent = chat_agent_executor.create_tool_calling_executor(deepseek_model, tools) config = {"configurable" : {"thread_id" : "abc123" }} for chunk in agent.stream({"messages" : [HumanMessage(content="昆明今天的天气如何?" )]}, config): print (chunk) print ("-----" )
我们将搜索引擎包装为数组,再将其与AI大模型绑定,然后将其传入agent的执行器中,再传入消息和id;执行程序时,代理就会自动使用搜索引擎搜索相关资源,再经过自然语言加工,返回经过加工的回答
利用代理构建对话式RAG 在上文’构建对话式 RAG‘中,我们利用链式 方法构建了一个对话式RAG ,现在既然我们学些了代理 (agent),我们来看看如何使用代理构建对话式RAG,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 import bs4from langchain.tools.retriever import create_retriever_toolfrom langchain_chroma import Chromafrom langchain_community.document_loaders import WebBaseLoaderfrom langchain_core.messages import HumanMessagefrom langchain_text_splitters import RecursiveCharacterTextSplitterfrom langgraph.checkpoint.memory import MemorySaverfrom langgraph.prebuilt import chat_agent_executorfrom model.AliyunBailianEmbeddings import embeddingsfrom model.DeepseekModel import deepseek_modelloader = WebBaseLoader( web_paths=("https://lilianweng.github.io/posts/2023-06-23-agent/" ,), bs_kwargs=dict ( parse_only=bs4.SoupStrainer( class_=("post-content" , "post-title" , "post-header" ) ) ), header_template={ "User-Agent" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36" }, ) docs = loader.load() text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000 , chunk_overlap=200 ) split_documents = text_splitter.split_documents(docs) vectorstore = Chroma.from_documents(documents=split_documents, embedding=embeddings) retriever = vectorstore.as_retriever() tool = create_retriever_tool( retriever, "blog_post_retriever" , "搜索并返回自主代理博客文章摘录。" , ) tools = [tool] config = {"configurable" : {"thread_id" : "abc123" }} agent_executor = chat_agent_executor.create_tool_calling_executor(deepseek_model, tools, checkpointer=MemorySaver()) for chunk in agent_executor.stream( {"messages" : [HumanMessage(content="什么是任务分解" )]}, config ): print (chunk) print ("-----" ) for chunk in agent_executor.stream( {"messages" : [HumanMessage(content="我刚才问的问题是什么" )]}, config ): print (chunk) print ("-----" )
可以看出,代理方式和链式方式最大的不同是在代理方式中我们不明确规定实现的逻辑,而是将获取资源的‘工具‘交给AI大模型,由AI大模型决定如何利用这些’工具‘来实现业务
利用代理构建在 SQL 数据上的问答系统 在上文’构建在 SQL 数据上的问答系统‘中,我们利用链式方式构建了一个以SQL为资源的问答系统,现在我们看看如何用agent实现,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 from langchain_community.agent_toolkits import SQLDatabaseToolkitfrom langchain_community.utilities import SQLDatabasefrom langchain_core.messages import HumanMessagefrom langchain_core.prompts import PromptTemplatefrom langgraph.prebuilt import chat_agent_executorfrom model.DeepseekModel import deepseek_modelsystem_prompt = PromptTemplate.from_template( """您是一个专门与SQL数据库交互的代理。 给定一个输入问题,创建一个语法正确的SQLite查询来运行,然后查看查询的结果并返回答案。 如果第一次查询不成功,最多尝试3次不同的查询方案。 除非用户指定他们希望获得的特定数量的示例,否则始终将查询限制在最多5个结果。 您可以按相关列对结果进行排序,以返回数据库中最有趣的示例。 永远不要查询特定表的所有列,只根据问题要求的相关列进行查询。 您可以使用与数据库交互的工具。 只使用以下工具。只使用以下工具返回的信息来构建您的最终答案。 在执行查询之前,您必须仔细检查您的查询。如果在执行查询时出现错误,请重新编写查询并重试。 不要对数据库进行任何DML语句(INSERT、UPDATE、DELETE、DROP等)。 首先,您应该始终查看数据库中的表,以了解您可以查询的内容。 不要跳过此步骤。 然后,您应该查询最相关表的模式。""" ) db = SQLDatabase.from_uri("sqlite:///D:/Data/chinook/chinook.db" ) toolkit = SQLDatabaseToolkit(db=db, llm=deepseek_model) tools = toolkit.get_tools() agent_executor = chat_agent_executor.create_tool_calling_executor(deepseek_model, tools, prompt=system_prompt) for s in agent_executor.stream( {"messages" : [HumanMessage(content="Which country's customers spent the most?" )]} ): print (s) print ("----" )
可以看到,我们没有规划具体的执行步骤,依旧是利用工具与AI大模型构建代理,然后将问题提交给代理处理
更多 关于更多的langchain使用方法,可以参考:https://www.aidoczh.com/langchain/v0.2/docs/introduction/