导读
LangChain是一个框架,用于开发由LLM驱动的应用程序。可以简单认为是LLM领域的Spring,以及开源版的ChatGPT插件系统。核心的2个功能为:
1)可以将 LLM 模型与外部数据源进行连接。
2)允许与 LLM 模型与环境进行交互,通过Agent使用工具。
图1.
LangChain核心组件
图2.
LangChain本身不提供LLM,提供通用的接口访问LLM,可以很方便的更换底层的LLM以及自定义自己的LLM。主要有2大类的Models:
1)LLM:将文本字符串作为输入并返回文本字符串的模型,类似OpenAI的text-davinci-003;
与模型交互的,基本上是通过给与Prompt的方式,LangChain通过PromptTemplate的方式方便构建以及复用Prompt。
from langchain import PromptTemplate prompt_template = '''作为一个资深编辑,请针对 >>> 和 <<< 中间的文本写一段摘要。 >>> {text} <<< ''' prompt = PromptTemplate(template=prompt_template, input_variables=["text"]) print(prompt.format_prompt(text="我爱北京天安门"))
索引和外部数据进行集成,用于从外部数据获取答案。如下图所示,主要的步骤有:
1)通过Document Loaders加载各种不同类型的数据源,
2)通过Text Splitters进行文本语义分割
3)通过Vectorstore进行非结构化数据的向量存储
4)通过Retriever进行文档数据检索
图3.
2.2.1 Document Loaders
如下图所示:LangChain目前支持结构化、非结构化以及公开以及私有的各种数据
图4.
2.2.2 Text Splitters
文本分割主要有2个考虑:
1)将语义相关的句子放在一块形成一个chunk。一般根据不同的文档类型定义不同的分隔符,或者可以选择通过模型进行分割。
from langchain.text_splitter import RecursiveCharacterTextSplitter text_splitter = RecursiveCharacterTextSplitter.from_tiktoken_encoder( model_name="gpt-3.5-turb allowed_special="all", separators=["nn", "n", "。", ","], chunk_size=7000, chunk_overlap=0 ) docs = text_splitter.create_documents(["文本在这里"]) print(docs)
2.2.3 Vectorstore
Embedding模型支持OpenAIEmbeddings、HuggingFaceEmbeddings等。通过HuggingFaceEmbeddings加载本地模型可以节省embedding的调用费用。
#通过cache_folder加载本地模型 embeddings = HuggingFaceEmbeddings(model_name="text2vec-base-chinese", cache_folder="本地模型地址") embeddings = embeddings_model.embed_documents( [ "我爱北京天安门!", "Hello world!" ] )
2.2.4 Retriever
Retriever接口用于根据非结构化的查询获取文档,一般情况下是文档存储在向量数据库中。可以调用 get_relevant_documents 方法来检索与查询相关的文档。
from langchain import FAISS from langchain.document_loaders import WebBaseLoader from langchain.embeddings import HuggingFaceEmbeddings from langchain.text_splitter import RecursiveCharacterTextSplitter loader = WebBaseLoader("https://in.m.jd.com/help/app/register_info.html") data = loader.load() text_splitter = RecursiveCharacterTextSplitter.from_tiktoken_encoder( model_name="gpt-3.5-turbo", allowed_special="all", separators=["nn", "n", "。", ","], chunk_size=800, chunk_overlap=0 ) docs = text_splitter.split_documents(data) #通过cache_folder设置自己的本地模型路径 embeddings = HuggingFaceEmbeddings(model_name="text2vec-base-chinese", cache_folder="models") vectorstore = FAISS.from_documents(docs, embeddings) result = vectorstore.as_retriever().get_relevant_documents("用户注册资格") print(result) print(len(result))
Langchain通过chain将各个组件进行链接,以及chain之间进行链接,用于简化复杂应用程序的实现。其中主要有LLMChain、Sequential Chain以及Route Chain。
2.3.1 LLMChain
图5.
类似下面的示例,给评论进行关键词提前以及情绪分析,通过LLMChain组合PromptTemplate、LLM以及OutputParser,可以很简单的实现一个之前通过依赖小模型不断需要调优的事情。
from langchain.chains import LLMChain from langchain.prompts import PromptTemplate from langchain.output_parsers import ResponseSchema, StructuredOutputParser from azure_chat_llm import llm #output parser keyword_schema = ResponseSchema(name="keyword", description="评论的关键词列表") emotion_schema = ResponseSchema(name="emotion", description="评论的情绪,正向为1,中性为0,负向为-1") response_schemas = [keyword_schema, emotion_schema] output_parser = StructuredOutputParser.from_response_schemas(response_schemas) format_instructions = output_parser.get_format_instructions() #prompt template prompt_template_txt = ''' 作为资深客服,请针对 >>> 和 <<< 中间的文本识别其中的关键词,以及包含的情绪是正向、负向还是中性。 >>> {text} <<< RESPONSE: {format_instructions} ''' prompt = PromptTemplate(template=prompt_template_txt, input_variables=["text"], partial_variables={"format_instructions": format_instructions}) #llmchain llm_chain = LLMChain(prompt=prompt, llm=llm) comment = "京东物流没的说,速度态度都是杠杠滴!这款路由器颜值贼高,怎么说呢,就是泰裤辣!这线条,这质感,这速度,嘎嘎快!以后妈妈再也不用担心家里的网速了!" result = llm_chain.run(comment) data = output_parser.parse(result) print(f"type={type(data)}, keyword={data['keyword']}, emotion={data['emotion']}")
输出:
图6.
2.3.2 Sequential Chain
SequentialChains是按预定义顺序执行的链。SimpleSequentialChain为顺序链的最简单形式,其中每个步骤都有一个单一的输入/输出,一个步骤的输出是下一个步骤的输入。SequentialChain 为顺序链更通用的形式,允许多个输入/输出。
from langchain.chains import LLMChain from langchain.prompts import PromptTemplate from langchain.chains import SimpleSequentialChain first_prompt = PromptTemplate.from_template( "翻译下面的内容到中文:" "nn{content}" ) # chain 1: 输入:Review 输出:英文的 Review chain_trans = LLMChain(llm=llm, prompt=first_prompt, output_key="content_zh") second_prompt = PromptTemplate.from_template( "一句话总结下面的内容:" "nn{content_zh}" ) chain_summary = LLMChain(llm=llm, prompt=second_prompt) overall_simple_chain = SimpleSequentialChain(chains=[chain_trans, chain_summary],verbose=True) content = '''In a blog post authored back in 2011, Marc Andreessen warned that, “Software is eating the world.” Over a decade later, we are witnessing the emergence of a new type of technology that’s consuming the world with even greater voracity: generative artificial intelligence (AI). This innovative AI includes a unique class of large language models (LLM), derived from a decade of groundbreaking research, that are capable of out-performing humans at certain tasks. And you don’t have to have a PhD in machine learning to build with LLMs—developers are already building software with LLMs with basic HTTP requests and natural language prompts. In this article, we’ll tell the story of GitHub’s work with LLMs to help other developers learn how to best make use of this technology. This post consists of two main sections: the first will describe at a high level how LLMs function and how to build LLM-based applications. The second will dig into an important example of an LLM-based application: GitHub Copilot code completions. Others have done an impressive job of cataloging our work from the outside. Now, we’re excited to share some of the thought processes that have led to the ongoing success of GitHub Copilot. ''' result = overall_simple_chain.run(content) print(f'result={result}')
输出:
图7.
2.3.3 Router Chain
RouterChain由两个组件组成:
1)路由器链本身,负责选择要调用的下一个链,主要有2种RouterChain,其中LLMRouterChain通过LLM进行路由决策,EmbeddingRouterChain 通过向量搜索的方式进行路由决策。
初始化RouterChain以及destination_chains完成后,通过MultiPromptChain将两者结合起来使用。
图8.
2.3.4 Documents Chain
2.3.4.1 Stuff
图9.
2.3.4.2 Refine
Refine这种方式能部分保留上下文,以及token的使用能控制在一定范围。
图10.
2.3.4.3 MapReduce
MapReduce的方式将每个document单独处理,可以并发进行调用。但是每个文档之间缺少上下文。
图11.
2.3.4.4 MapRerank
MapRerank和MapReduce类似,会大批量的调用LLM,每个document之间是独立处理。
图12.
1)ConversationSummaryMemory :以摘要的信息保存记录
2)ConversationBufferWindowMemory:以原始形式保存最新的n条记录
通过查看chain的prompt,可以发现{history}变量传递了从memory获取的会话上下文。下面的示例演示了Memory的使用方式,可以很明细看到,答案是从之前的问题里获取的。
from langchain.chains import ConversationChain from langchain.memory import ConversationBufferMemory from azure_chat_llm import llm memory = ConversationBufferMemory() conversation = ConversationChain(llm=llm, memory=memory, verbose=True) print(conversation.prompt) print(conversation.predict(input="我的姓名是tiger")) print(conversation.predict(input="1+1=?")) print(conversation.predict(input="我的姓名是什么"))
输出:
图13.
Agent字面含义就是代理,如果说LLM是大脑,Agent就是代理大脑使用工具Tools。目前的大模型一般都存在知识过时、逻辑计算能力低等问题,通过Agent访问工具,可以去解决这些问题。目前这个领域特别活跃,诞生了类似AutoGPT(https://github.com/Significant-Gravitas/AutoGPT)、BabyAGI(https://github.com/yoheinakajima/babyagi)、AgentGPT(https://github.com/reworkd/AgentGPT)等一些优秀的项目。传统使用LLM,需要给定Prompt一步一步的达成目标,通过Agent是给定目标,其会自动规划并达到目标。
2.5.1 Agent核心组件
Agent:代理,负责调用LLM以及决定下一步的Action。其中LLM的prompt必须包含agent_scratchpad变量,记录执行的中间过程。
Tools:工具,Agent可以调用的方法。LangChain已有很多内置的工具,也可以自定义工具。注意Tools的description属性,LLM会通过描述决定是否使用该工具。
ToolKits:工具集,为特定目的的工具集合。类似Office365、Gmail工具集等。
Agent Executor:Agent执行器,负责进行实际的执行。
2.5.2 Agent的类型
agent = initialize_agent(agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION, tools=tools, llm=llm, verbose=True) print(agent.agent.llm_chain.prompt.template)
该Agent为一个zero-shot-react-description类型的Agent,其中zero-shot表明只考虑当前的操作,不会记录以及参考之前的操作。react表明通过ReAct框架进行推理,description表明通过工具的description进行是否使用的决策。
2.5.3 自定义Tool
有多种方式可以自定义Tool,最简单的方式是通过@tool装饰器,将一个函数转为Tool。注意函数必须得有docString,其为Tool的描述。
from azure_chat_llm import llm from langchain.agents import load_tools, initialize_agent, tool from langchain.agents.agent_types import AgentType from datetime import date @tool def time(text: str) -> str: """ 返回今天的日期。 """ return str(date.today()) tools = load_tools(['llm-math'], llm=llm) tools.append(time) agent_math = initialize_agent(agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION, tools=tools, llm=llm, verbose=True) print(agent_math("计算45 * 54")) print(agent_math("今天是哪天?"))
输出为:
LangChain落地实践
1)通过Loader加载远程文档
2)通过Splitter基于Token进行文档拆分
3)加载summarize链,链类型为refine,迭代进行总结
from langchain.prompts import PromptTemplate from langchain.document_loaders import PlaywrightURLLoader from langchain.chains.summarize import load_summarize_chain from langchain.text_splitter import RecursiveCharacterTextSplitter from azure_chat_llm import llm loader = PlaywrightURLLoader(urls=["https://content.jr.jd.com/article/index.html?pageId=708258989"]) data = loader.load() text_splitter = RecursiveCharacterTextSplitter.from_tiktoken_encoder( model_name="gpt-3.5-turbo", allowed_special="all", separators=["nn", "n", "。", ","], chunk_size=7000, chunk_overlap=0 ) prompt_template = ''' 作为一个资深编辑,请针对 >>> 和 <<< 中间的文本写一段摘要。 >>> {text} <<< ''' refine_template = ''' 作为一个资深编辑,基于已有的一段摘要:{existing_answer},针对 >>> 和 <<< 中间的文本完善现有的摘要。 >>> {text} <<< ''' PROMPT = PromptTemplate(template=prompt_template, input_variables=["text"]) REFINE_PROMPT = PromptTemplate( template=refine_template, input_variables=["existing_answer", "text"] ) chain = load_summarize_chain(llm, chain_type="refine", question_prompt=PROMPT, refine_prompt=REFINE_PROMPT, verbose=False) docs = text_splitter.split_documents(data) result = chain.run(docs) print(result)
1)通过Loader加载远程文档
2)通过Splitter基于Token进行文档拆分
3)通过FAISS向量存储文档,embedding加载HuggingFace的text2vec-base-chinese模型
4)自定义QA的prompt,通过RetrievalQA回答相关的问题
from langchain.chains import RetrievalQA from langchain.document_loaders import WebBaseLoader from langchain.embeddings.huggingface import HuggingFaceEmbeddings from langchain.prompts import PromptTemplate from langchain.text_splitter import RecursiveCharacterTextSplitter from langchain.vectorstores import FAISS from azure_chat_llm import llm loader = WebBaseLoader("https://in.m.jd.com/help/app/register_info.html") data = loader.load() text_splitter = RecursiveCharacterTextSplitter.from_tiktoken_encoder( model_name="gpt-3.5-turbo", allowed_special="all", separators=["nn", "n", "。", ","], chunk_size=800, chunk_overlap=0 ) docs = text_splitter.split_documents(data) #设置自己的模型路径 embeddings = HuggingFaceEmbeddings(model_name="text2vec-base-chinese", cache_folder="model") vectorstore = FAISS.from_documents(docs, embeddings) template = """请使用下面提供的背景信息来回答最后的问题。 如果你不知道答案,请直接说不知道,不要试图凭空编造答案。 回答时最多使用三个句子,保持回答尽可能简洁。 回答结束时,请一定要说"谢谢你的提问!" {context} 问题: {question} 有用的回答:""" QA_CHAIN_PROMPT = PromptTemplate(input_variables=["context", "question"], template=template) qa_chain = RetrievalQA.from_chain_type(llm, retriever=vectorstore.as_retriever(), return_source_documents=True, chain_type_kwargs={"prompt": QA_CHAIN_PROMPT}) result = qa_chain({"query": "用户注册资格"}) print(result["result"]) print(len(result['source_documents']))
未来发展方向
个人认为,除了和业务结合落地LLM应用外,还有2个大的方向可以进一步去探索:
1)通过低代码的形式进一步降低LLM应用的开发门槛。类似langflow这样的可视化编排工具发展也很快;
2)打造更加强大的Agent。Agent之于大模型,个人觉得类似SQL之于DB,能大幅度提升LLM的应用场景。
参考资料
1、https://python.langchain.com/docs/get_started/introduction.html
2、https://github.com/liaokongVFX/LangChain-Chinese-Getting-Started-Guide
3、https://www.deeplearning.ai/short-courses/langchain-for-llm-application-development/
4、https://lilianweng.github.io/posts/2023-06-23-agent/
5、https://mp.weixin.qq.com/s/3coFhAdzr40tozn8f9Dc-w
6、https://github.com/langchain-ai/langchain
本文仅供学习!所有权归属原作者。侵删!文章来源: 京东技术 -杨虎 :http://mp.weixin.qq.com/s/bTRaRSpnpTKvQJIGtmYbtw
文章评论