原作者写的很多都太经典了,感觉都需要记下来,就会有大段的copy。
工具和工具箱
未来的AI Agent,应该就是以LLM为核心控制器的代理系统。而工具,则是代理身上延展出的三头六臂,是代理的武器,代理通过工具来与世界进行交互,控制并改造世界。
LangChain通过提供一个统一的框架来集成功能的具体实现。在这个框架中,每个功能都被封装成一个工具。每个工具都有自己的输入和输出,以及处理这些输入和生成输出的方法。
当代理接收到一个任务时,它会根据任务的类型和需求,通过大模型的推理,来选择合适的工具处理这个任务。这个选择过程可以基于各种策略,例如基于工具的性能,或者基于工具处理特定类型任务的能力。
一旦选择了合适的工具,LangChain就会将任务的输入传递给这个工具,然后工具会处理这些输入并生成输出。这个输出又经过大模型的推理,可以被用作其他工具的输入,或者作为最终结果,被返回给用户。
langchain支持的工具
langchain中的工具
RAG
RAG,全称为Retrieval-Augmented Generation,即检索增强生成,它结合了检索和生成的能力,为文本序列生成任务引入外部知识。RAG将传统的语言生成模型与大规模的外部知识库相结合,使模型在生成响应或文本时可以动态地从这些知识库中检索相关信息。这种结合方法旨在增强模型的生成能力,使其能够产生更为丰富、准确和有根据的内容,特别是在需要具体细节或外部事实支持的场合。
- 检索:对于给定的输入(问题),模型首先使用检索系统从大型文档集合中查找相关的文档或段落。这个检索系统通常基于密集向量搜索,例如ChromaDB、Faiss这样的向量数据库。
- 上下文编码:找到相关的文档或段落后,模型将它们与原始输入(问题)一起编码。
- 生成:使用编码的上下文信息,模型生成输出(答案)。这通常当然是通过大模型完成的。
特点:不仅仅依赖于训练数据中的信息,还可以从大型外部知识库中检索信息。这使得RAG模型特别适合处理在训练数据中未出现的问题。
文档加载
文本转换
加载文档后,下一个步骤是对文本进行转换,而最常见的文本转换就是把长文档分割成更小的块(或者是片,或者是节点),以适合模型的上下文窗口。LangChain 有许多内置的文档转换器,可以轻松地拆分、组合、过滤和以其他方式操作文档。
文本分割
原理
- 将文本分成小的、具有语义意义的块(通常是句子)。
- 开始将这些小块组合成一个更大的块,直到达到一定的大小。
- 一旦达到该大小,一个块就形成了,可以开始创建新文本块。这个新文本块和刚刚生成的块要有一些重叠,以保持块之间的上下文。
分割策略和参数
- 文本如何分割
- 块的大小
- 块之间重叠文本的长度
首先,就是LLM 的具体限制
GPT-3.5-turbo支持的上下文窗口为4096个令牌,这意味着输入令牌和生成的输出令牌的总和不能超过4096,否则会出错。为了保证不超过这个限制,我们可以预留约2000个令牌作为输入提示,留下约2000个令牌作为返回的消息。这样,如果你提取出了五个相关信息块,那么每个片的大小不应超过400个令牌。
此外,文本分割策略的选择和任务类型相关
- 需要细致查看文本的任务,最好使用较小的分块。例如,拼写检查、语法检查和文本分析可能需要识别文本中的单个单词或字符。垃圾邮件识别、查找剽窃和情感分析类任务,以及搜索引擎优化、主题建模中常用的关键字提取任务也属于这类细致任务。
- 需要全面了解文本的任务,则使用较大的分块。例如,机器翻译、文本摘要和问答任务需要理解文本的整体含义。而自然语言推理、问答和机器翻译需要识别文本中不同部分之间的关系。还有创意写作,都属于这种粗放型的任务。
最后,你也要考虑所分割的文本的性质
例如,如果文本结构很强,如代码或HTML,你可能想使用较大的块,如果文本结构较弱,如小说或新闻文章,你可能想使用较小的块。
其他形式的文本转换
- 过滤冗余的文档:使用 EmbeddingsRedundantFilter 工具可以识别相似的文档并过滤掉冗余信息。这意味着如果你有多份高度相似或几乎相同的文档,这个功能可以帮助识别并删除这些多余的副本,从而节省存储空间并提高检索效率。
- 翻译文档:通过与工具 doctran 进行集成,可以将文档从一种语言翻译成另一种语言。
- 提取元数据:通过与工具 doctran 进行集成,可以从文档内容中提取关键信息(如日期、作者、关键字等),并将其存储为元数据。元数据是描述文档属性或内容的数据,这有助于更有效地管理、分类和检索文档。
- 转换对话格式:通过与工具 doctran 进行集成,可以将对话式的文档内容转化为问答(Q/A)格式,从而更容易地提取和查询特定的信息或回答。这在处理如访谈、对话或其他交互式内容时非常有用。
文本嵌入
通过LLM来做嵌入(Embeddings),将文本转换为数值表示,使得计算机可以更容易地处理和比较文本。
时间消耗大
存储嵌入
为了减少时间消耗,对上一步的计算嵌入结果进行存储。
-
缓存存储:CacheBackedEmbeddings是一个支持缓存的嵌入式包装器,它可以将嵌入缓存在键值存储中。具体操作是:对文本进行哈希处理,并将此哈希值用作缓存的键。
不同的缓存策略
- InMemoryStore:在内存中缓存嵌入。主要用于单元测试或原型设计。如果需要长期存储嵌入,请勿使用此缓存。
- LocalFileStore:在本地文件系统中存储嵌入。适用于那些不想依赖外部数据库或存储解决方案的情况。
- RedisStore:在Redis数据库中缓存嵌入。当需要一个高速且可扩展的缓存解决方案时,这是一个很好的选择。
-
向量数据库
数据检索
Retriever,也就是检索器,是数据检索模块的核心入口,它通过非结构化查询返回相关的文档。
- 向量存储检索器:最常见的,主要支持向量检索。
- 其他检索工具
索引
索引是一种高效地管理和定位文档信息的方法,确保每个文档具有唯一标识并便于检索。
- 避免重复内容:确保你的向量存储中不会有冗余数据。
- 只更新更改的内容:能检测哪些内容已更新,避免不必要的重写。
- 省时省钱:不对未更改的内容重新计算嵌入,从而减少了计算资源的消耗。
- 优化搜索结果:减少重复和不相关的数据,从而提高搜索的准确性。
在进行索引时,Langchain的API 会对每个文档进行哈希处理,确保每个文档都有一个唯一的标识。这个哈希值不仅仅基于文档的内容,还考虑了文档的元数据。
一旦哈希完成,以下信息会被保存在记录管理器中:
- 文档哈希:基于文档内容和元数据计算出的唯一标识。
- 写入时间:记录文档何时被添加到向量存储中。
- 源 ID:这是一个元数据字段,表示文档的原始来源。
数据库
-
Chain查询
from langchain_experimental.sql import SQLDatabaseChain db_chain = SQLDatabaseChain.from_llm(llm, db, verbose=True) -
Agent查询
- 根据数据库的架构以及数据库的内容回答问题(例如检索特定表的描述)。
- 具有纠错能力,当执行生成的查询遇到错误时,能够捕获该错误,然后正确地重新生成并执行新的查询。
agent_executor = create_sql_agent( llm=llm, toolkit=SQLDatabaseToolkit(db=db, llm=llm), verbose=True, agent_type=AgentType.ZERO_SHOT_REACT_DESCRIPTION, )
回调函数
import asyncio
from typing import Any, Dict, List
from langchain.chat_models import ChatOpenAI
from langchain.schema import LLMResult, HumanMessage
from langchain.callbacks.base import AsyncCallbackHandler, BaseCallbackHandler
# 创建同步回调处理器
class MyFlowerShopSyncHandler(BaseCallbackHandler):
def on_llm_new_token(self, token: str, **kwargs) -> None:
print(f"获取花卉数据: token: {token}")
# 创建异步回调处理器
class MyFlowerShopAsyncHandler(AsyncCallbackHandler):
async def on_llm_start(
self, serialized: Dict[str, Any], prompts: List[str], **kwargs: Any
) -> None:
print("正在获取花卉数据...")
await asyncio.sleep(0.5) # 模拟异步操作
print("花卉数据获取完毕。提供建议...")
async def on_llm_end(self, response: LLMResult, **kwargs: Any) -> None:
print("整理花卉建议...")
await asyncio.sleep(0.5) # 模拟异步操作
print("祝你今天愉快!")
# 主要的异步函数
async def main():
flower_shop_chat = ChatOpenAI(
max_tokens=100,
streaming=True,
callbacks=[MyFlowerShopSyncHandler(), MyFlowerShopAsyncHandler()],
)
# 异步生成聊天回复
await flower_shop_chat.agenerate([[HumanMessage(content="哪种花卉最适合生日?只简单说3种,不超过50字")]])
# 运行主异步函数
asyncio.run(main())
感觉这个代码还挺有应用场景的?
在Python中,一个上下文管理器通常用于管理资源,如文件或网络连接,这些资源在使用前需要设置,在使用后需要清理。上下文管理器经常与with语句一起使用,以确保资源正确地设置和清理
get_openai_callback被设计用来监控与OpenAI交互的Token数量。当你进入该上下文时,它会通过监听器跟踪Token的使用。当你退出上下文时,它会清理监听器并提供一个Token的总数。通过这种方式,它充当了一个回调机制,允许你在特定事件发生时执行特定的操作或收集特定的信息。
with get_openai_callback() as cb:
# 第一天的对话
# 回合1
conversation("我姐姐明天要过生日,我需要一束生日花束。")
print("第一次对话后的记忆:", conversation.memory.buffer)
# 回合2
conversation("她喜欢粉色玫瑰,颜色是粉色的。")
print("第二次对话后的记忆:", conversation.memory.buffer)
# 回合3 (第二天的对话)
conversation("我又来了,还记得我昨天为什么要来买花吗?")
print("/n第三次对话后时提示:/n",conversation.prompt.template)
print("/n第三次对话后的记忆:/n", conversation.memory.buffer)
# 输出使用的tokens
print("\n总计使用的tokens:", cb.total_tokens)
import asyncio
# 进行更多的异步交互和token计数
async def additional_interactions():
with get_openai_callback() as cb:
await asyncio.gather(
*[llm.agenerate(["我姐姐喜欢什么颜色的花?"]) for _ in range(3)]
)
print("\n另外的交互中使用的tokens:", cb.total_tokens)
# 运行异步函数
asyncio.run(additional_interactions())
async def:这表示additional_interactions是一个异步函数。它可以使用await关键字在其中挂起执行,允许其他异步任务继续。await asyncio.gather(...):这是asyncio库提供的一个非常有用的方法,用于并发地运行多个异步任务。它会等待所有任务完成,然后继续执行。*[llm.agenerate(["我姐姐喜欢什么颜色的花?"]) for _ in range(3)]:这实际上是一个Python列表解析,它生成了3个 llm.agenerate(...)的异步调用。asyncio.gather将并发地运行这3个调用。