LangChain 由入门到精通
作者:王珂
邮箱:49186456@qq.com
文章目录
- LangChain 由入门到精通
- 简介
- 一、LangChain环境搭建
- 1.1 集成大模型提供商
- 1.1.1 集成Ollama
- 1.2 LangChain安装
- 二、LangChain开发
- 2.1 提示词工程
- 2.2 示例集
- 三、LangChain LCEL 工作流编排
- 3.1 LCEL
- 3.2 Runable interface
- 3.3 Stream(流)
- 3.4 Stream events(事件流)
- 四、LangChain历史消息管理
- 4.1 带历史消息的runnable
- 4.2 给历史消息添加用户标识
- 4.3 消息持久化
- 4.4 裁切消息
- 4.5 记忆总结
- 五、多模态输入
- 六、加载器
- 6.1 TextLoader
- 7.1 WebBaseLoader
- 七、自定义工具
- 7.1 创建工具
- 7.1.1 @tool装饰器
- 7.1.2 StructuredTool
- 7.1.3 继承BaseTool
- 7.1.4 官方提供的工具
- 7.1.5 工具包
- 八、开发代理Agent
- 九、向量相数据库
- 9.1 Chroma
- 9.1.1 创建向量数据库
- 9.1.1 从网页爬取数据
- 9.1.2 加载向量数据库
- 9.2 Faiss
- 十、LangChain整合数据库
- 十一、LangServe
- 11.1 LangServe环境搭建
- 十二、LangGraph
- 12.1 LangGraph基本原理
- 十三、LangSmish
简介
LangChain是一个用于开发由大型语言模型(LLM)驱动的应用程序的框架。通过LangChain可以轻松的与大预言模型集成,完成文本生成、问答、翻译对话等任务。
为什么要用LongChain
-
数据连接:Langchain 允许你将大型语言模型连接到你自己的数据源,比如数据库、PDF文件或其他文档。这意味着你可以使模型从你的私有数据中提取信息
-
行动执行:不仅可以提取信息,还可以帮助你根据这些信息执行特操作,如发送邮件。
LangChain的核心
-
Models: 包装器允许你连接到的大语言模型,如GPT-4 或 Hugging Face, 也包括GLM提供的模型
-
Prompt Templates: 动态将用户输入插入到模板,发给语言模型
-
Chains: 将组件组合在一起,解决特定任务(执行任务的过程想工作流一样,一步一步执行),并构建完整的语言模型应用程序
-
Agents: 一种基于大模型的应用设计模式,利用大模型的自然语言理解和推理能力,根据用户的需求自动调用外部系统和设备共同完成任务
-
Embedding: 嵌入与向量存储 VectorStore是数据表示和检索的手段,为模型提供必要的理解基础
-
LCEL(LangChain Expression Language),用于解决工作流的编排问题,通过LCEL表达式,可以灵活自定义AI任务处理流程,即灵活自定义链(Chain)
-
RAG(Retrieval Augmented Generic)
-
模型记忆(Memory):让大模型记住之前的对话内容,这种能力称为模型记忆
LangChain三个核心组件:
-
Components 为LLMs提供封装接口,模板提示和信息检索索引
-
Chains 将不同的组件组合起来解决特定的任务,比如在大量文档中查找信息
-
Agents 使LLMs能够与外部环境进行交互,例如通过API请求执行操作
LangChain框架组成
-
LangSmith 开发平台,是个云服务,支持 Langchain debug、任务监控
-
LangServe 基于 FastAPI 可以将 Langchain 定义的链(Chain),发布成为 REST API
-
Templates
-
LangChain库(Libraries)
-
LangChain 主要包括链(chain)、代理(agent)和检索策略
-
LangChain-Community 第三方集成,主要包括 langchain 集成的第三方组件
-
langChain-Core 基础抽象和 LangChain 表达语言
-
LangChain任务处理
LangChain对大模型封装主要分为LLM和Chat Model两种类型
-
LLM: 问答模式,模型一个文本输入,返回一个文本输出
-
Chat Model: 对话模型,接收一组对话消息,返回对话消息,类似聊天消息
核心概念
-
LLMs
基础模型:LangChain封装的基础模型,模型接收一个输入,然后返回一个文本结果
-
Chat Models
聊天模型(或对话模型),接收一组对话消息,然后返回对话消息,类似聊天一样
-
Message
聊天模型的消息内容,包括SystemMessage, HumanMessage, AIMessage, FunctionMessage, ToolMessage等
-
Prompts
LangChain封装了一组专门用于提示词(Prompts)的管理工具类,方便格式化提示词(prompts)内容
-
Output Parsers 输出解析器
大模型 (llm)返回的文本内容之后,可以使用专门的输出解析器对文本内容进行格式化,例如解析json、或者将 llm 输出的内容转成 python 对象
-
Retrievers
为方便将私有数据导入到大模型,提高模型问题回答的质量,LangChain封装了检索框架Retrievers,方便加载文档数据,切割文档数据,存储和检索文档数据
-
Vector stores 向量存储
-
Agents
智能体(agents),通常指以大模型做为决策引擎,根据用户输入的任务,自动调用外部系统和硬件设备,共同完成用户的任务,是一种以大模型为核心的设计模式
一、LangChain环境搭建
1.1 集成大模型提供商
本文选用ollama部署本地大模型,已经部署的大模型如下:
NAME ID SIZE MODIFIED
qwen2.5:0.5b a8b0c5157701 397 MB 14 hours ago
shaw/dmeta-embedding-zh:latest 55960d8a3a42 408 MB 9 days ago
1.1.1 集成Ollama
- 安装依赖包
pip install langchain-ollama
- 导入包
LLMs
from langchain_ollama.llms import OllamaLLM
ChatModels
from langchain_ollama.chat_models import ChatOllama
1.2 LangChain安装
LangChain目前支持Python和TypeScript语言
pip install langchain
如果需要用到OpenAI的模型,还需要安装
pip install langchain-openai
二、LangChain开发
2.1 提示词工程
Prompt Templates
提示词模板 Prompt Templates,大预言模型以文本做为输入,这个文本叫做提示词(prompt)。为了便于提示词的管理,可以通过提示词模板进行维护,类似于短信模板、邮件模板。
发给大模型的提示词可以是一段对话,一段示例等都可以
提示词模板的组成
-
发给大语言模型的指令
-
一组回答示例,以提醒AI以什么格式返回请求
-
发给语言模型的问题
创建提示词模板
-
简单提示词模板
from langchain_core.prompts import PromptTemplatetemplate = PromptTemplate.from_template("请帮我搜查一下关于{content}的内容") template.format(content="如何多挣钱")
-
消息提示词模板
聊天模型以聊天消息做为输入,聊天消息列表的消息内容通过提示词模板进行管理,每个消息都与角色相关联
OpenAI的聊天模型,给聊天消息定义了三种角色:
-
系统消息 system:通常用来给AI进行身份描述
-
人类消息 human:指你发给AI的内容
-
助手消息 Assistant:指当前消息是AI回答的内容
from langchain_core.prompts import ChatPromptTemplatechat_template = ChatPromptTemplate.from_messages([("system", "你是Python资深工程师"),("user", "{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资深工程师"),("user", "{input}"), ])
-
消息对象格式(推荐)
chat_template = ChatPromptTemplate.from_messages([SystemMessage(content="你是Python资深工程师"),HumanMessage(content="{input}") ])
-
MessagePlaceholder
负责在特定位置添加消息列表。如果希望用户传入一个消息列表,将其插入到特定位置,此时就会用到。
有两种写法:
-
字符串形式
chat_template = ChatPromptTemplate.from_messages([("system", "你是Python资深工程师"),("placeholder", "{msgs}"), ])
这将生成两条消息,第一条是系统消息,第二条是我们传入的 HumanMessage。
-
对象形式
chat_template = ChatPromptTemplate.from_messages([("system", "你是Python资深工程师"),# 通过MessagesPlaceholder可以传入一组消息MessagesPlaceholder("msgs") ]) chat_template.invoke({"msgs", [HumanMessage(content="hi")]})
2.2 示例集
提示词追加示例
提示词中包含交互样本示例的作用是为了帮助模型更好的理解用户意图,从而更好的回答问题或执行任务。
小样本提示模板是指使用一组少量的示例来指导模型处理新的输入。这些示例可以用来训练模型,以便模型可以更好的理解和回答类似的问题。
创建示例集
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])
使用示例选择器
将示例提供给ExampleSelector
向量相似度匹配
TODO
三、LangChain LCEL 工作流编排
3.1 LCEL
LCEL(LangChain Expression Language)是一种强大的工作流编排工具,可以从基本组件构建复杂任务链条(chain),并支持诸如流式处理、并行处理和日志记录等开箱即用的功能。
-
流式调用
-
异步支持
使用LCEL表达式构建的链可以使用同步API和异步API进行调用。
-
优化并行执行
-
重试和回退
-
访问中间结果
-
输入和输出模式
输入和输出模式为每个LCEL链提供了从链的结构推断出Pydantic和JSON Schema模式。
3.2 Runable interface
为了尽可能简化创建自定义链的过程,实现了一个"Runnable"协议。许多LangChain组件都实现了Runnalbe协议,包括聊天模型,LLMs,输出解析器、检索器、提示词模板。自定义链有三个标准接口:
标准接口
-
invoke 同步调用,对输入调用链
-
batch对invoke的批量调用,对输入列表调用链
-
stream 流式调用,返回响应的数据块
异步方法,应该与asyncio和await语法以实现并发:
异步接口
-
ainvoke
-
abatch
-
astream 异步返回响应数据库
-
astream_log
-
astream_events
输入类型 和 输出类型
组件 | 输入类型 | 输出类型 |
---|---|---|
提示词 | 字典 | 提示值 |
聊天模型 | 单个字符串,聊天消息列表,提示值 | 聊天消息 |
LLM | 单个字符串,聊天消息列表,提示值 | 字符串 |
输出解析器 | LLM 或 聊天模型输出 | 取决于解析器 |
检索器 | 单个字符串 | 文档列表 |
工具 | 单个字符串或字典,取决于工具 | 取决于工具 |
所有可运行对象都公开输入和输出模型,以检查输入和输出
-
input_schema: 从可运行对象结构自动生成输入Pydantic模型
-
output_schema: 从可运行对象结构自动生成输出Pydataic模型
流式运行对于使用LLM的应用程序对最终用户具有响应性至关重要。重要的LangChain原语,如聊天模型,输出解析器,提示模板,检索器和代理都实现了LangChain Runnable接口。该接口提供了两种通用的流式内容方法:
-
同步stream和异步astream: 流失传输链中的最终输出和默认实现
-
异步astream_events和异步astream_logs: 可以从链中流式传输中间步骤和最终输出的方式
3.3 Stream(流)
所有 Runnable 对象都实现了一个名为stream 的同步方法和一个名为 astream的异步方法。这些方法旨在以块的形式流式传输最终输出,尽快返回每个块。只有在程序中的所有步骤都知道如何处理输入流时,才能进行流式传输;即逐个处理输入块,并产生相应的输出块。这种处理的复杂性可以有所不同,从简单的任务,如发出 LLM 生成的令牌,到更具挑战性的任务,如在整个JSON完成之前流式传输JSON 结果的部分。开始探索流式传输的最佳方法是从 LLM 应用程序中最重要的组件开始–LLM 本身!
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 = 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("请给我解释一下什么是{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格式化输出。
import asynciofrom langchain_core.output_parsers import JsonOutputParser
from langchain_core.prompts import PromptTemplate
from langchain_ollama import ChatOllamachat_model = ChatOllama(base_url='http://192.168.1.17:11434',model='qwen:0.5b',temperature=0.8,num_predict=256
)prompt_template = PromptTemplate.from_template("""用JSON的格式输出{num}个学生的成绩单,最外层的标签是students,每一个学生用student标签包裹,每个student包含name, subject, score。""")json_parser = JsonOutputParser()chain = prompt_template | chat_model | json_parserasync def async_stream():async for chunk in chain.astream({"num": 3}):print(chunk, flush=True)asyncio.run(async_stream())
3.4 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 = ChatOllama(base_url='http://192.168.1.17:11434',model='qwen:0.5b',temperature=0.8,num_predict=256
)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': 'qwen:0.5b', '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_parserasync 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())
四、LangChain历史消息管理
跟聊天大模型进行对话,可以将对话的历史消息记录下来,在每次对话的过程中都带上历史消息,这样就使大模型有了记忆功能。
4.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 = ChatOllama(base_url='http://192.168.1.17:11434',model='qwen:0.5b',temperature=0.5,num_predict=256
)chat_prompt_template = ChatPromptTemplate.from_messages([SystemMessage(content="你是一个Python的资深编程专家,掌握了所有Python3的语法,能回答所有跟Python相关的编程问题.请用{language}回答我的问题"),MessagesPlaceholder(variable_name="history"),HumanMessage(content="{input}"),
])chain = chat_prompt_template | chat_model | StrOutputParser()# 创建一个带历史会话记录的runnable
history_runnable = RunnableWithMessageHistory(chain,get_session_history,input_messsage_key="input",history_message_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"}}
)
4.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"}}
)
4.3 消息持久化
- 配置redis环境
pip install --quiet redis
- 安装依赖包
pip install langchain-community
- 持久化消息
chain = chat_prompt_template | chat_modelREDIS_URL = 'redis://:redis@192.168.1.17:6379/1'def get_message_history(session_id: str) -> RedisChatMessageHistory:return RedisChatMessageHistory(session_id, url=REDIS_URL)
4.4 裁切消息
如果每次和大模型对话都带上所有的历史消息,时间长了,历史消息就会非常庞大,因此要对历史消息进行裁剪。
TODO
4.5 记忆总结
TODO
五、多模态输入
六、加载器
使用langchain的加载器,需要安装langchain-community包
pip install langchain-community
6.1 TextLoader
文本加载器加载普遍文档
embedding_model = OllamaEmbeddings(base_url="http://192.168.1.17:11434",model="shaw/dmeta-embedding-zh"
)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)
7.1 WebBaseLoader
七、自定义工具
在构建代理时,需要为其提供一个工具列表,以便代理可以使用这些工具。
工具的组成
属性 | 类型 | 描述 |
---|---|---|
name | str | 工具的名称,必须唯一 |
description | str | 对工具的描述。LLM或Agent使用描述做为上下文 |
args_schema | Pydantic BaseModel | 可选但建议,用于提供更多信息(如few-shot)或验证预期参数 |
return_direct | boolean | 仅对代理相关。当为True时 |
创建工具的方式
-
使用@tool装饰器
@tool def multiply(a: int, b: int) -> int:""" 两个数相乘 """return a * b
-
使用Structured.from_function类方法
-
通过集成BaseTool
7.1 创建工具
7.1.1 @tool装饰器
使用函数名做为工具的名称(但可以使用第一个参数自定义),使用函数的文档做为工具的描述。
基本工具
@tool
def multiply(a: int, b: int) -> int:""" 两个数相乘 """return a * b
指定args_schema
@tool("add_tool", args_schema=CalculatorInput)
def add(a: int, b: int) -> int:""" 两个数相(异步方式) """return a + b
7.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 * basync def amultiply(a: int, b: int) -> int:"""Multiply two numbers together."""return a * basync 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 * bcalculator_tool = StructuredTool.from_function(func=multiply,# 如果设置为True,则返回ToolException的文本,False会抛出ToolExceptionhandle_tool_error=True
)print(calculator_tool.invoke({"a": 2, "b": 3}))
或者直接使用hanle_tool_error=“错误描述”
calculator_tool = StructuredTool.from_function(func=multiply,# 如果设置为True,则返回ToolException的文本,False会抛出ToolExceptionhandle_tool_error="自定义错误"
)
使用自定义函数处理异常
def error_process(error: ToolException):print(f'这里是自定义异常处理函数,错误信息{error.args[0]}')calculator_tool = StructuredTool.from_function(func=multiply,# 如果设置为True,则返回ToolException的文本,False会抛出ToolExceptionhandle_tool_error=error_process
)
7.1.3 继承BaseTool
class Tool(BaseTool):pass
7.1.4 官方提供的工具
lanchain官方提供的工具
https://python.langchain.com/v0.2/docs/integrations/tools/
维基百科工具
# Wikipedia工具
api_wrapper = WikipediaAPIWrapper(top_k_results=1, doc_content_chars_max=1000)
tool = WikipediaQueryRun(api_wrapper=api_wrapper)
print(tool.invoke({"query": "langchain"}))
对维基百科工具拓展
TODO
7.1.5 工具包
工具包将工具整合在一起,以执行特定的任务。所有工具包都公开了get_tools方法返回一个工具列表。
使用工具包
# 初始化工具包
toolkit = ExampleTookit(...)
# 获取工具包列表
tools = toolkit.get_tools()
sqlite工具包
db = SQLDatabase.from_uri("sqlite:///langchain.db")
toolkit = SQLDatabaseToolkit(db=db, llm=ChatOllama(base_url="..."))
tools = toolkit.get_tools()
八、开发代理Agent
语言模型本身无法执行动作,只能输出文本。代理(Agent)是使用大语言模型做为推理引擎来确定要执行什么操作,以及这些操作的输入是什么。操作的结果可以反馈到代理中,以便代理决定是否需要更多的操作或者是否可以结束
Agent对命令进行规划,然后将其拆分为若干个任务,再去调用工具完成任务。
需求描述
我们现在要让大模型帮我们计算,要么是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 = ChatOllama(base_url="http://192.168.1.17:11434",model="qwen2.5:0.5b",temperature=0.01,num_predict=256
)# 加法方法
def add(a: int, b: int) -> int:"""计算加法"""print("执行加法运算")return a + b# 乘法方法
def multiply(a: int, b: int) -> int:"""计算乘法"""print("执行乘法运算")return a * b
2. 再定义两个工具 add_tool, multiply_tool```pythonclass 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}
九、向量相数据库
9.1 Chroma
9.1.1 创建向量数据库
用Chroma构造函数创建向量数据库
vector_store = Chroma(persist_directory=cls.default_persist_directory,embedding_function=embedding_model)
从documents创建向量数据库
vector_store = Chroma.from_documents(documents, embedding)
9.1.1 从网页爬取数据
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 documentsembedding = 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 允许将用户的问题之后传递给prompt和model
pre_prompt = {'question': RunnablePassthrough(), 'context': retriever}
# 使用chain链接
chain = pre_prompt | chat_prompt_template | chat_modelprint(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}
9.1.2 加载向量数据库
加载已经存在的向量数据库
vectorstore = Chroma(persist_directory=persist_dir, embedding_function=embedding)
9.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 向量存储
vector_store = FAISS.from_documents(docs, embeddings)
十、LangChain整合数据库
十一、LangServe
LangChain提供Web API服务能力,帮助开发着将可运行的链部署成为REST API。
它集成了FastAPI,并使用Pydantic进行数据验证。
Pydantic
Pydantic 是一个在 Python中用于数据验证和解析的第三方库,现在是Python中使用广泛的数据验证库。
-
它利用声明式的方式定义数据模型和Python类型提示的强大功能来执行数据验证和序列化,使您的代码更可靠、更可读、更简洁且更易于调试。
-
它还可以从模型生成 JSON 架构,提供了自动生成文档等功能,从而轻松与其他工具集成。
-
它提供了一个客户端,可用于调用部署在服务器上的可运行对象。
11.1 LangServe环境搭建
- 安装客户端库和服务端库
pip install langserve[all]
或者
pip install --upgrade "langserve[all]"
如果只安装服务端库
pip install langserve[server]
如果只安装客户端库
pip install langserve[client]
- 安装LangChain CLI
如果使用LangChain CLI快速启动LangServe项目,需要安装库
pip install langchain-cli
- 安装poetry
poetry是项目的包管理工具(类似Java项目中的Maven)。使用pipx安装poetry,因此先要安装pipx
安装pipx
pip install pipx
安装poetry
pipx install poetry
将poetry加入到环境变量
# 安装后需要重启pycharm
pipx ensurepath
- 使用langchain cli命令创建项目
查看langchain命令
D:\work\PycharmProject>langchainUsage: langchain [OPTIONS] COMMAND [ARGS]...┌─ Options ───────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ --version -v Print the current CLI version. │
│ --help Show this message and exit. │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
┌─ Commands ──────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ app Manage LangChain apps │
│ integration Develop integration packages for LangChain. │
│ migrate Migrate langchain to the most recent version. │
│ serve Start the LangServe app, whether it's a template or an app. │
│ template Develop installable templates. │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
创建名为demo-langchain-serve的项目
langchain app new myapp
安装依赖包
先进入myapp项目目录
cd myapp
再安装依赖包
poetry add langchain
- 添加一个路由
编辑app目录下的server.py,添加一个路由。
这里使用Ollama做为模型提供方,添加一个ollama的服务调用服务
chat_model = ChatOllama(base_url="http://192.168.1.17:11434",model="qwen:0.5b",temperature=0.7,num_predict=1024
)chat_prompt_template = ChatPromptTemplate.from_template("请解释一下什么是{content}")json_output_parser = JsonOutputParser()chain = chat_prompt_template | chat_model | json_output_parser# Edit this to add the chain you want to add
add_routes(app, chain, path="/chat")
可以测试使用Postman来调用这个接口
- 启动项目
poetry run langchain serve --port=8000
十二、LangGraph
12.1 LangGraph基本原理
十三、LangSmish
是一个用于构建生产级 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)