通过4个任务比较LangChain和LlamaIndex

本文涉及的产品
检索分析服务 Elasticsearch 版,2核4GB开发者规格 1个月
智能开放搜索 OpenSearch行业算法版,1GB 20LCU 1个月
实时数仓Hologres,5000CU*H 100GB 3个月
简介: 我们在本地使用大模型的时候,尤其是构建RAG应用的时候,一般会有2个成熟的框架可以使用
  • LangChain:用开发LLM的通用框架。
  • LlamaIndex:专门用于构建RAG系统的框架。

选择一个框架是对于项目的后续开发是非常重要的,因为如果后续更换框架是一个非常困难的事情,所以我们这里对这两个框架做一个简单的对比,这样对于选择会有一个初步的印象。

首先我们看看他们的Github表现和一些公开的信息:

从财务状况来看,LlamaIndex的融资规模接近LangChain,尽管他们的目标市场要小得多。这可能表明LlamaIndex的生存机会更大,因为资金比较宽裕。但是LangChain提供了更多的面向企业并且能够产生收入的产品(LangServe, LangSmith),所以可能LangChain的收入更高,这样看来LangChain会更好。。

上面是我们对企业资金方面的胡乱分析,仅供参考。下面让我们进入正题,在本文中我将使用两个框架并行完成一些基本任务。通过对比展示这些代码片段,我希望它能在你做出选择时有所帮助。

1、用本地LLM创建聊天机器人

第一个任务是制作一个聊天机器人,并且使用本地的LLM。

虽然是本地,但是我们让LLM在独立的推理服务器中运行,这样可以避免重复使用,2个框架直接使用同一服务即可。虽然LLM推理API有多种模式,但我们这里选择与OpenAI兼容的模式,这样如果切换成OpenAI的模型也不需要修改代码。

下面是LlamaIndex的方法:

 from llama_index.llms import ChatMessage, OpenAILike  

 llm = OpenAILike(  
     api_base="http://localhost:1234/v1",  
     timeout=600,  # secs  
     api_key="loremIpsum",  
     is_chat_model=True,  
     context_window=32768,  
 )  
 chat_history = [  
     ChatMessage(role="system", content="You are a bartender."),  
     ChatMessage(role="user", content="What do I enjoy drinking?"),  
 ]  
 output = llm.chat(chat_history)  
 print(output)

这是LangChain:

 from langchain.schema import HumanMessage, SystemMessage  
 from langchain_openai import ChatOpenAI  

 llm = ChatOpenAI(  
     openai_api_base="http://localhost:1234/v1",  
     request_timeout=600,  # secs, I guess.  
     openai_api_key="loremIpsum",  
     max_tokens=32768,  
 )  
 chat_history = [  
     SystemMessage(content="You are a bartender."),  
     HumanMessage(content="What do I enjoy drinking?"),  
 ]  
 print(llm(chat_history))

可以看到代码十分类似:

LangChain区分了聊天llm (ChatOpenAI)和llm (OpenAI),而LlamaIndex在构造函数中使用is_chat_model参数来进行区分。

LlamaIndex区分官方OpenAI端点和openaillike端点,而LangChain通过openai_api_base参数决定向何处发送请求。

LlamaIndex用role参数标记聊天消息,而LangChain使用单独的类。

2个框架基本没什么差别,我们继续

2、为本地文件构建RAG系统

我们构建一个简单的RAG系统:从本地的文本文件文件夹中读取文本。

以下是使用LlamaIndex文档的代码:

 from llama_index import ServiceContext, SimpleDirectoryReader, VectorStoreIndex

 service_context = ServiceContext.from_defaults(  
     embed_model="local",  
     llm=llm, # This should be the LLM initialized in the task above.
 )  
 documents = SimpleDirectoryReader(
     input_dir="mock_notebook/",
 ).load_data()  
 index = VectorStoreIndex.from_documents(  
     documents=documents,
     service_context=service_context,
 )
 engine = index.as_query_engine(  
     service_context=service_context,  
 )
 output = engine.query("What do I like to drink?")  
 print(output)

使用LangChain,代码会变得很长:

 from langchain_community.document_loaders import DirectoryLoader  

 # pip install "unstructured[md]"  
 loader = DirectoryLoader("mock_notebook/", glob="*.md")  
 docs = loader.load()  

 from langchain.text_splitter import RecursiveCharacterTextSplitter  

 text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)  
 splits = text_splitter.split_documents(docs)  

 from langchain_community.embeddings.fastembed import FastEmbedEmbeddings  
 from langchain_community.vectorstores import Chroma  

 vectorstore = Chroma.from_documents(documents=splits, embedding=FastEmbedEmbeddings())  
 retriever = vectorstore.as_retriever()  

 from langchain import hub  

 # pip install langchainhub  
 prompt = hub.pull("rlm/rag-prompt")  


 def format_docs(docs):  
     return "\n\n".join(doc.page_content for doc in docs)  


 from langchain_core.runnables import RunnablePassthrough  

 rag_chain = (  
     {"context": retriever | format_docs, "question": RunnablePassthrough()}  
     | prompt  
     | llm # This should be the LLM initialized in the task above.
 )  
 print(rag_chain.invoke("What do I like to drink?"))

这些代码片段清楚地说明了这两个框架的不同抽象级别。LlamaIndex用一个名为“query engines”的方法封装了RAG管道,而LangChain则需要更多的内部组件:包括用于检索文档的连接器、表示“基于X,请回答Y”的提示模板,以及他所谓的“chain”(如上面的LCEL所示)。

当使用LangChain构建时,必须确切地知道想要什么。比如调用from_documents的位置,这使得对于初学者来说是一个非常麻烦的事情,需要更多的学习曲线。

LlamaIndex可以无需显式选择矢量存储后端直接使用,而LangChain则需要显示指定这也需要更多的信息,因为我们不确定在选择数据库时是否做出了明智的决定。

虽然LangChain和LlamaIndex都提供类似于Hugging Face的云服务(即LangSmith Hub和LlamaHub),但是LangChain把它集成到了几乎所有的功能,我们使用pull只下载一个简短的文本模板,内容如下:

You are an assistant for question-answering tasks. Use the following pieces of retrieved context to answer the question. If you don’t know the answer, just say that you don’t know. Use three sentences maximum and keep the answer concise.Question: {question}Context: {context}Answer:**

这绝对是一种过度的做法。虽然这确实鼓励在社区中分享提示,但是这有必要吗。

3、支持RAG的聊天机器人

我们将上面两个简单的功能整合起来,这样我们可以获得一个可以和本地文件对话的真正的可用的简单应用。

使用LlamaIndex,就像将as_query_engine与as_chat_engine交换一样简单:

 engine = index.as_chat_engine()
 output = engine.chat("What do I like to drink?")  
 print(output) # "You enjoy drinking coffee."
 output = engine.chat("How do I brew it?")  
 print(output) # "You brew coffee with a Aeropress."

使用LangChain时,按照官方教程,让我们首先定义memory(负责管理聊天记录):

 # Everything above this line is the same as that of the last task.
 from langchain_core.runnables import RunnablePassthrough, RunnableLambda  
 from langchain_core.messages import get_buffer_string  
 from langchain_core.output_parsers import StrOutputParser  
 from operator import itemgetter  
 from langchain.memory import ConversationBufferMemory  
 from langchain.prompts.prompt import PromptTemplate  
 from langchain.schema import format_document  
 from langchain_core.prompts import ChatPromptTemplate  

 memory = ConversationBufferMemory(  
     return_messages=True, output_key="answer", input_key="question"  
 )

在LLM开始时,我们需要从memory中加载聊天历史记录。

 load_history_from_memory = RunnableLambda(memory.load_memory_variables) | itemgetter(  
     "history"  
 )  
 load_history_from_memory_and_carry_along = RunnablePassthrough.assign(  
     chat_history=load_history_from_memory  
 )

然后要求LLM用上下文来丰富我们的提问

 rephrase_the_question = (  
     {  
         "question": itemgetter("question"),  
         "chat_history": lambda x: get_buffer_string(x["chat_history"]),  
     }  
     | PromptTemplate.from_template(  
         """You're a personal assistant to the user.  
 Here's your conversation with the user so far:  
 {chat_history}  
 Now the user asked: {question}  
 To answer this question, you need to look up from their notes about """  
     )  
     | llm  
     | StrOutputParser()  
 )

但是我们不能只是将两者连接起来,因为话题可能在谈话过程中发生了变化,这使得聊天记录中的大多数语义信息无关紧要。

然后就是运行RAG。

 retrieve_documents = {  
     "docs": itemgetter("standalone_question") | retriever,  
     "question": itemgetter("standalone_question"),  
 }

对提问进行回答:

 rephrase_the_question = (  
     {  
         "question": itemgetter("question"),  
         "chat_history": lambda x: get_buffer_string(x["chat_history"]),  
     }  
     | PromptTemplate.from_template(  
         """You're a personal assistant to the user.  
 Here's your conversation with the user so far:  
 {chat_history}  
 Now the user asked: {question}  
 To answer this question, you need to look up from their notes about """  
     )  
     | llm  
     | StrOutputParser()  
 )

得到最终响应后将其附加到聊天历史记录。

 final_chain = (  
     load_history_from_memory_and_carry_along  
     | {"standalone_question": rephrase_the_question}  
     | retrieve_documents  
     | compose_the_final_answer  
 )  
 # Demo.
 inputs = {"question": "What do I like to drink?"}  
 output = final_chain.invoke(inputs)  
 memory.save_context(inputs, {"answer": output.content})  
 print(output) # "You enjoy drinking coffee."
 inputs = {"question": "How do I brew it?"}  
 output = final_chain.invoke(inputs)  
 memory.save_context(inputs, {"answer": output.content})  
 print(output) # "You brew coffee with a Aeropress."

这是一个非常复杂的过程,我们通过这个过程可以了解了很多关于llm驱动的应用程是如何构建的。特别是调用了LLM几次,让它假设不同的角色:查询生成器、总结检索到的文档的人,对话的参与者。这对于学习来说是非常有帮助的,但是对于应用是不是有些复杂了。

4、Agent

RAG管道可以被认为是一个工具。而LLM可以访问多个工具,比如给它提供搜索、百科查询、天气预报等。通过这种方式聊天机器人可以回答关于它直接知识之外的问题。

工具也不一定要提供信息,还可以进行其他操作,例如下购物订单,回复电子邮件等。

LLM有了这些工具,就需要决定使用哪些工具,以及以什么顺序使用。而使用这些工具LLM角色被称为“代理”。

有多种方式可以为LLM提供代理。最具模型泛型的方法是ReAct范式。

在LlamaIndex中使用方法如下

 from llama_index.tools import ToolMetadata  
 from llama_index.tools.query_engine import QueryEngineTool  

 notes_query_engine_tool = QueryEngineTool(  
     query_engine=notes_query_engine,  
     metadata=ToolMetadata(  
         name="look_up_notes",  
         description="Gives information about the user.",  
     ),  
 )  
 from llama_index.agent import ReActAgent  

 agent = ReActAgent.from_tools(  
     tools=[notes_query_engine_tool],  
     llm=llm,  
     service_context=service_context,  
 )  
 output = agent.chat("What do I like to drink?")  
 print(output) # "You enjoy drinking coffee."
 output = agent.chat("How do I brew it?")  
 print(output) # "You can use a drip coffee maker, French press, pour-over, or espresso machine."

对于我们的后续问题“how do I brew coffee”,代理的回答与它仅仅是一个查询引擎时不同。这是因为代理可以自己决定是否查看我们本地笔记。如果他们有足够的信心来回答这个问题,代理可能会选择不使用任何工具。如果LLM发现他无法回答这个问题,则会使用RAG搜索我们本地的文件(我们的查询引擎的其职责是从索引中查找文档,所以他肯定会选择这个)。

代理是LangChain高级API:

 from langchain.agents import AgentExecutor, Tool, create_react_agent  

 tools = [  
     Tool(  
         name="look_up_notes",  
         func=rag_chain.invoke,  
         description="Gives information about the user.",  
     ),
 ]
 react_prompt = hub.pull("hwchase17/react-chat")  
 agent = create_react_agent(llm, tools, react_prompt)  
 agent_executor = AgentExecutor.from_agent_and_tools(agent=agent, tools=tools)  

 result = agent_executor.invoke(  
     {"input": "What do I like to drink?", "chat_history": ""}  
 )  
 print(result) # "You enjoy drinking coffee."
 result = agent_executor.invoke(  
     {  
         "input": "How do I brew it?",  
         "chat_history": "Human: What do I like to drink?\nAI: You enjoy drinking coffee.",  
     }
 )
 print(result) # "You can use a drip coffee maker, French press, pour-over, or espresso machine."

尽管我们仍然需要手动管理聊天记录,但与创建RAG相比,创建代理要容易得多。create_react_agent和AgentExecutor整合了底层的大部分工作。

总结

LlamaIndex和LangChain是构建LLM应用程序的两个框架。LlamaIndex专注于RAG用例,LangChain得到了更广泛的应用。我们可以看到,如果是和RAG相关的用例,LlamaIndex会方便很多,可以说是首选。

但是如果你的应用需要一些非RAG的功能,可能LangChain是一个更好的选择。

https://avoid.overfit.cn/post/94eb3dd8122346d393b059e0f9142335

相关实践学习
阿里云百炼xAnalyticDB PostgreSQL构建AIGC应用
通过该实验体验在阿里云百炼中构建企业专属知识库构建及应用全流程。同时体验使用ADB-PG向量检索引擎提供专属安全存储,保障企业数据隐私安全。
AnalyticDB PostgreSQL 企业智能数据中台:一站式管理数据服务资产
企业在数据仓库之上可构建丰富的数据服务用以支持数据应用及业务场景;ADB PG推出全新企业智能数据平台,用以帮助用户一站式的管理企业数据服务资产,包括创建, 管理,探索, 监控等; 助力企业在现有平台之上快速构建起数据服务资产体系
目录
相关文章
|
7月前
|
人工智能 机器人 API
使用 OpenAI、LangChain 和 LlamaIndex 构建 Knowledge
使用 OpenAI、LangChain 和 LlamaIndex 构建 Knowledge
1049 0
|
7月前
|
Shell Android开发
Android系统 adb shell push/pull 禁止特定文件
Android系统 adb shell push/pull 禁止特定文件
610 1
|
7月前
|
Android开发 Python
Python封装ADB获取Android设备wifi地址的方法
Python封装ADB获取Android设备wifi地址的方法
169 0
|
开发工具 Android开发
Mac 安卓(Android) 配置adb路径
Mac 安卓(Android) 配置adb路径
887 0
|
4月前
|
Shell Linux 开发工具
"开发者的救星:揭秘如何用adb神器征服Android设备,开启高效调试之旅!"
【8月更文挑战第20天】Android Debug Bridge (adb) 是 Android 开发者必备工具,用于实现计算机与 Android 设备间通讯,执行调试及命令操作。adb 提供了丰富的命令行接口,覆盖从基础设备管理到复杂系统操作的需求。本文详细介绍 adb 的安装配置流程,并列举实用命令示例,包括设备连接管理、应用安装调试、文件系统访问等基础功能,以及端口转发、日志查看等高级技巧。此外,还提供了常见问题的故障排除指南,帮助开发者快速解决问题。掌握 adb 将极大提升 Android 开发效率,助力项目顺利推进。
121 0
|
7月前
|
Shell Android开发
ADB更改Android设备屏幕显示方向
ADB更改Android设备屏幕显示方向
382 5
|
7月前
|
Java Android开发
Android 对adb命令的拦截
Android 对adb命令的拦截
111 2
|
6月前
|
Shell 开发工具 Android开发
|
7月前
|
存储 安全 Shell
Android系统 adb shell auth授权使用
Android系统 adb shell auth授权使用
575 2
|
7月前
|
网络协议 Shell Android开发
Android 深入学习ADB调试原理(1)
Android 深入学习ADB调试原理(1)
329 1