▐ 索引(Indexs)
索引可以让文档结构化,从而LLM可以直接更好的和文档交互;比如用于答疑,知识库等,LLM先从文档中获取答案。
LangChain在索引这块也提供了许多有用的函数和工具,方便我们从外部加载与检索不同的文档数据;
在数据索引这块,LangChain提供的主要工具:
- Document Loaders:从不同的数据源加载文档,当使用loader加载器读取到数据源后,数据源需要转换成 Document 对象后,后续才能进行使用。
- Text Splitters:实现文本分割, 我们每次不管是做把文本当作 prompt 发给 openai api ,还是还是使用 openai api embedding 功能都是有字符限制的。比如我们将一份300页的 pdf 发给 openai api,让他进行总结,他肯定会报超过最大 Token 错。所以这里就需要使用文本分割器去分割我们 loader 进来的 Document。
- VectorStores:把文档存储为向量结构, 因为数据相关性搜索其实是向量运算。所以,不管我们是使用 openai api embedding 功能还是直接通过向量数据库直接查询,都需要将我们的加载进来的数据 Document 进行向量化,才能进行向量运算搜索。转换成向量也很简单,只需要我们把数据存储到对应的向量数据库中即可完成向量的转换。
- Retrievers:用于检索文档的数据
图中的FAISS是一种向量存储的服务;
给一个案例,了解下不同工具的用法:
- 首先加载文档
- 然后分隔文档为不同区块:
- 然后转换为向量存储
- 将向量存储转换为检索器,交给LangChain,用于问答
import os from langchain.chains import RetrievalQA from langchain.document_loaders import TextLoader from langchain.embeddings import OpenAIEmbeddings from langchain.indexes import VectorstoreIndexCreator from langchain.text_splitter import CharacterTextSplitter from langchain.vectorstores import Chroma from langchain.llms import OpenAI # 设置代理 os.environ['HTTP_PROXY'] = 'socks5h://127.0.0.1:13659' os.environ['HTTPS_PROXY'] = 'socks5h://127.0.0.1:13659' # 创建文本加载器 loader = TextLoader('/Users/aihe/Downloads/demo.txt', encoding='utf8') # 加载文档 documents = loader.load() # 文本分块 text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0) texts = text_splitter.split_documents(documents) # 计算嵌入向量 embeddings = OpenAIEmbeddings() # 创建向量库 db = Chroma.from_documents(texts, embeddings) # 将向量库转换为检索器 retriever = db.as_retriever() # 创建检索问答系统 qa = RetrievalQA.from_chain_type(llm=OpenAI(), chain_type="stuff", retriever=retriever) # 运行问题答案检索 query = "如何申请租户?" print(qa.run(query)) print(qa.run("能否说明下你可以提供的功能?"))
▐ 存储(Memory)
默认情况下Agent和Chain都是无状态的,也就是用完之后不知道上次的对话内容是什么。每次的query都是独立的。
但是在有些应用中,记住上一次的会话内容是比较重要的,比如聊天,LangChain对于也提供了一些相关的工具类。
from langchain import ConversationChain, OpenAI from langchain.memory import ConversationBufferMemory memory = ConversationBufferMemory() memory.chat_memory.add_user_message("你好!") memory.chat_memory.add_ai_message("你好吗?") llm = OpenAI(temperature=0) chain = ConversationChain(llm=llm, verbose=True, memory=memory) chain.predict(input="最近怎么样!") print(chain.predict(input="感觉很不错,刚和AI做了场对话."))
▐ 链(Chains)
链可以让我们把多个组件组合成一个应用,比如我们创建一个链,这个链可以接受用户的输入,然后通过PromptTemplate格式化用户的输入为提示词,然后把这个提示词输入给LLM。
我们也可以把一些链组合在一起,构建更复杂的链。
一个简单的案例:
# 引入所需模块和类 from langchain.chains import LLMChain from langchain.chat_models import ChatOpenAI from langchain import PromptTemplate from langchain.prompts.chat import ( ChatPromptTemplate, # 引入对话模板类 HumanMessagePromptTemplate, # 引入人类消息模板类 ) # 创建人类消息模板类 human_message_prompt = HumanMessagePromptTemplate( prompt=PromptTemplate( template="给我一个制作{product}的好公司名字?", # 输入模板,其中product为占位符 input_variables=["product"], # 指定输入变量为product ) ) # 创建对话模板类 chat_prompt_template = ChatPromptTemplate.from_messages([human_message_prompt]) # 创建OpenAI聊天模型对象 chat = ChatOpenAI(temperature=0.9) # 创建LLMChain对象,将聊天模型和对话模板传入 chain = LLMChain(llm=chat, prompt=chat_prompt_template) # 运行LLMChain对象,并输出结果 print(chain.run("袜子"))
▐ 代理(Agents)
代理是使用LLM作为思考工具,决定当前要做什么。我们会给代理一系列的工具,代理根据我们的输入判断用哪些工具可以完成这个目标,然后不断的运行工具,来完成目标。
代理可以看做是增强版的Chain,不仅绑定模板、LLM,还可以给代理添加一些工具。
Agent是一个智能代理,它负责根据用户输入和应用场景,在一系列可用工具中选择合适的工具进行操作。Agent可以根据任务的复杂性,采用不同的策略来决定如何执行操作。
有两种类型的Agent:
- 动作代理(Action Agents):这种代理一次执行一个动作,然后根据结果决定下一步的操作。
- 计划-执行代理(Plan-and-Execute Agents):这种代理首先决定一系列要执行的操作,然后根据上面判断的列表逐个执行这些操作。
对于简单的任务,动作代理更为常见且易于实现。对于更复杂或长期运行的任务,计划-执行代理的初始规划步骤有助于维持长期目标并保持关注。但这会以更多调用和较高延迟为代价。这两种代理并非互斥,可以让动作代理负责执行计划-执行代理的计划。
Agent内部涉及的核心概念如下:
- 代理(Agent):这是应用程序主要逻辑。代理暴露一个接口,接受用户输入和代理已执行的操作列表,并返回AgentAction或AgentFinish。
- 工具(Tools):这是代理可以采取的动作。比如发起HTTP请求,发邮件,执行命令。
- 工具包(Toolkits):这些是为特定用例设计的一组工具。例如,为了让代理以最佳方式与SQL数据库交互,它可能需要一个执行查询的工具和另一个查看表格的工具。可以看做是工具的集合。
- 代理执行器(Agent Executor):这将代理与一系列工具包装在一起。它负责迭代运行代理,直到满足停止条件。
代理的执行流程:
一个案例:
# 引入所需模块和类 from langchain.agents import load_tools # 引入加载工具函数 from langchain.agents import initialize_agent # 引入初始化代理函数 from langchain.agents import AgentType # 引入代理类型类 from langchain.llms import OpenAI # 引入OpenAI语言模型类 import os # 引入os模块 # 创建OpenAI语言模型对象,设定temperature为0,即关闭随机性 llm = OpenAI(temperature=0) # 加载所需工具,包括serpapi和llm-math tools = load_tools(["serpapi", "llm-math"], llm=llm) # 初始化代理对象,设定代理类型为ZERO_SHOT_REACT_DESCRIPTION,输出详细信息 agent = initialize_agent(tools, llm, agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION, verbose=True) # 运行代理对象,向其提问特朗普的年龄和年龄除以2的结果 agent.run("特朗普今年多少岁? 他的年龄除以2是多少?")
- 代理初始化类型
上述代码中关于Agent有个初始化的阶段,agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION,代理类型决定了代理如何使用工具、处理输入以及与用户进行交互。从而为用户提供有针对性的服务。其中可以选择的类型如下:
initialize_agent(tools, llm, agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION, verbose=True)
- zero-shot-react-description:该代理使用ReAct框架仅根据工具的描述来确定要使用哪个工具,可以提供任意数量的工具。要求为每个工具提供一个描述。
- react-docstore:该代理使用ReAct框架与文档存储(docstore)进行交互。必须提供两个工具:一个搜索工具和一个查找工具(它们必须确切地命名为Search和Lookup)。搜索工具应该用于搜索文档,而查找工具应该在最近找到的文档中查找术语。该代理等同于原始的ReAct论文,特别是维基百科的示例。
- self-ask-with-search:该代理使用一个名为Intermediate Answer的单一工具。这个工具应该能够查找问题的事实性答案。这个代理等同于原始的自问自答(self-ask)与搜索论文,其中提供了作为工具的谷歌搜索API。
- conversational-react-description:该代理旨在用于对话设置中。提示让代理在对话中变得有帮助。它使用ReAct框架来决定使用哪个工具,并使用内存来记住之前的对话互动。
- structured-chat-zero-shot-react-description: 在对话中可以使用任意的工具,并且能够记住对话的上下文。
- Tools 工具
官方已经默认提供了一系列的工具箱,发Gmail邮件,数据库查询,JSON处理等;还有一些单个的工具列表,都可以在文档中看到:https://python.langchain.com/en/latest/modules/agents/tools/getting_started.html
我们通过一个自定义的工具,了解下工具怎么用,因为后面再使用LangChain的时候我们做的也就是不断的自定义工具。编写工具的时候,要准备:
- 名称
- 工具描述:说明你的工具是做什么的
- 参数结构:当前工具需要的入参是什么结构