DL.ai 大模型系列:3.LangChain 开发 LLM 应用

1 简介

LangChain是用于构建大模型应用程序的开源框架

  • 基于模块化组合,可单独使用也可以以链式方式进行组合
  • 开源社区发展快速;有Python和JavaScript两个不同版本的包

2 提示和输出解释器

直接使用OpenAI的示例:

import os
import openai

# 获取环境变量 OPENAI_API_KEY
openai.api_key = os.environ['OPENAI_API_KEY']  

def get_completion(prompt, model="gpt-3.5-turbo"):
    messages = [{"role": "user", "content": prompt}]
    response = openai.ChatCompletion.create(
        model=model,
        messages=messages,
        temperature=0, 
    )
    return response.choices[0].message["content"]
get_completion("1+1是什么?")
# 1+1等于2。

通过LangChain使用OpenAI:

# !pip install -q --upgrade langchain
from langchain.chat_models import ChatOpenAI

chat = ChatOpenAI(temperature=0.0)

template_string = """把由三个反引号分隔的文本\
翻译成一种{style}风格。\
文本: ```{text}```""" # 构造可指定风格的提示模板
prompt_template = ChatPromptTemplate.from_template(template_string)

customer_style = """正式普通话 用一个平静、尊敬的语气"""

customer_email = """阿,我很生气,因为我的搅拌机盖掉了,把奶昔溅到了厨房的墙上!\
更糟糕的是,保修不包括打扫厨房的费用。我现在需要你的帮助,伙计!"""

customer_messages = prompt_template.format_messages(
                    style=customer_style, # 指定风格
                    text=customer_email) # 原始文本

customer_response = chat(customer_messages)
print(customer_response.content)
# 尊敬的伙计,我感到非常愤怒,因为我的搅拌机盖子不慎掉落,导致奶昔溅到了厨房的墙壁上!
# 更加令人糟心的是,保修并不包括厨房清洁的费用。现在,我需要你的帮助,请你给予援手!

问:为什么需要提示(Prompts)模版? 答:方便重复使用,以应用于较为复杂的场景

输出解释器(Output Pasers):指定输出格式后,模型的返回结果依然是str格式。此时便需要输出解释器对结果进行校验

customer_review = """\
这款吹叶机非常神奇。 它有四个设置:吹蜡烛、微风、风城、龙卷风。 \
两天后就到了,正好赶上我妻子的周年纪念礼物。 我想我的妻子会喜欢它到说不出话来。 \
到目前为止,我是唯一一个使用它的人,而且我一直每隔一天早上用它来清理草坪上的叶子。 \
它比其他吹叶机稍微贵一点,但我认为它的额外功能是值得的。
"""

review_template_2 = """\
对于以下文本,请从中提取以下信息::

礼物:该商品是作为礼物送给别人的吗?
如果是,则回答 是的;如果否或未知,则回答 不是。

交货天数:产品到达需要多少天? 如果没有找到该信息,则输出-1。

价钱:提取有关价值或价格的任何句子,并将它们输出为逗号分隔的 Python 列表。

文本: {text}

{format_instructions}
"""
prompt = ChatPromptTemplate.from_template(template=review_template_2)

from langchain.output_parsers import ResponseSchema
from langchain.output_parsers import StructuredOutputParser

gift_schema = ResponseSchema(name="礼物",
                             description="这件物品是作为礼物送给别人的吗?\
                            如果是,则回答 是的,如果否或未知,则回答 不是。")

delivery_days_schema = ResponseSchema(name="交货天数",
                                      description="产品需要多少天才能到达?\
                                      如果没有找到该信息,则输出-1。")

price_value_schema = ResponseSchema(name="价钱",
                                    description="提取有关价值或价格的任何句子,\
                                    并将它们输出为逗号分隔的 Python 列表")

response_schemas = [gift_schema, 
                    delivery_days_schema,
                    price_value_schema]
output_parser = StructuredOutputParser.from_response_schemas(response_schemas)
format_instructions = output_parser.get_format_instructions()
print(format_instructions)

messages = prompt.format_messages(text=customer_review, format_instructions=format_instructions)
response = chat(messages)
print(response.content)
# json { "礼物": false, "交货天数": "两天后", "价钱": "它比其他吹叶机稍微贵一点" }

补充材料:链式思考推理(ReAct)

  • LangChain通过Reasoning+Acting(简称ReAct)构建自治代理的框架
  • ReAct 通过将大语言模型LLM与外部工具集成来改进决策,该技术有时也称为增强语言模型 (ALM)
  • 关于ReAct的更多细节和演示可参阅OpenAI GPT 和 LangChain 中的 ReAct提示

3 储存 Memory

储存模块:将先前的对话嵌入到语言模型中的,使其具有连续对话的能力

常用的四种储存模块:

  • 对话缓存储存 (ConversationBufferMemory)
  • 对话缓存窗口储存 (ConversationBufferWindowMemory)
  • 对话令牌缓存储存 (ConversationTokenBufferMemory)
  • 对话摘要缓存储存 (ConversationSummaryBufferMemory)

LLM的海量参数中保留着模型的长期记忆,而存储模块有助于优化、延长模型的短期记忆

  1. 对话缓存储存:记录历史的所有对话记录
from langchain.chains import ConversationChain
from langchain.chat_models import ChatOpenAI
from langchain.memory import ConversationBufferMemory

llm = ChatOpenAI(temperature=0.0)  
memory = ConversationBufferMemory() # 新建一个空的对话缓存记忆
# 新建一个 ConversationChain Class 实例
# verbose参数设置为True时,程序会输出更详细的信息,以提供更多的调试或运行时信息。
# 相反,当将verbose参数设置为False时,程序会以更简洁的方式运行,只输出关键的信息。
conversation = ConversationChain(llm=llm, memory = memory, verbose=True )
conversation.predict(input="Hi, my name is Andrew")
conversation.predict(input="What is 1+1?")
conversation.predict(input="What is my name?")
# 'Your name is Andrew.'
print(memory.buffer) # 查看存储缓存
memory.save_context({"input": "Hi"}, {"output": "What's up"})  # 向缓存区添加指定对话的输入输出
print(memory.load_memory_variables({}))# 再次加载记忆变量
  1. 对话缓存窗口储存:记录历史的最近k次对话记录

只记录最近k次对话记录,一方面是减少缓存区,另一方面也是减少调用LLM的成本

from langchain.memory import ConversationBufferWindowMemory

llm = ChatOpenAI(temperature=0.0)
# k 为窗口参数,k=1表明只保留一个对话记忆
memory = ConversationBufferWindowMemory(k=1)
conversation = ConversationChain(llm=llm, memory=memory, verbose=False)
conversation.predict(input="Hi, my name is Andrew")
conversation.predict(input="What is 1+1?")
conversation.predict(input="What is my name?")
# "I'm sorry, but I don't have access to personal information."
  1. 对话Token缓存储存:限制保存的token最大数量
from langchain.llms import OpenAI
from langchain.memory import ConversationTokenBufferMemory

memory = ConversationTokenBufferMemory(llm=llm, max_token_limit=30)
memory.save_context({"input": "AI is what?!"}, {"output": "Amazing!"})
memory.save_context({"input": "Backpropagation is what?"}, {"output": "Beautiful!"})
memory.save_context({"input": "Chatbots are what?"}, {"output": "Charming!"})
memory.load_memory_variables({}) # 超过30 tokens的部分已被清洗

补充:

ChatGPT使用一种基于字节对编码(Byte Pair Encoding,BPE)的方法来进行tokenization(将输入文本拆分为token)。BPE是一种常见的tokenization技术,它将输入文本分割成较小的子词单元。

OpenAI在其官方GitHub上公开了一个最新的开源Python库 tiktoken,这个库主要是用来计算tokens数量的。相比较HuggingFace的tokenizer,其速度提升了好几倍。

具体token计算方式,特别是汉字和英文单词的token区别,具体课参考知乎文章

  1. 对话摘要缓存储存:保存所有历史对话的摘要
from langchain.chains import ConversationChain
from langchain.chat_models import ChatOpenAI
from langchain.memory import ConversationSummaryBufferMemory

# 创建一个长字符串
schedule = "There is a meeting at 8am with your product team. \
You will need your powerpoint presentation prepared. \
9am-12pm have time to work on your LangChain \
project which will go quickly because Langchain is such a powerful tool. \
At Noon, lunch at the italian resturant with a customer who is driving \
from over an hour away to meet you to understand the latest in AI. \
Be sure to bring your laptop to show the latest LLM demo."

# 使用对话摘要缓存记忆
llm = ChatOpenAI(temperature=0.0)
memory = ConversationSummaryBufferMemory(llm=llm, max_token_limit=100) 
memory.save_context({"input": "Hello"}, {"output": "What's up"})
memory.save_context({"input": "Not much, just hanging"}, {"output": "Cool"})
memory.save_context(
    {"input": "What is on the schedule today?"}, {"output": f"{schedule}"}
)
print(memory.load_memory_variables({})['history']) # 基于LLM萃取的对话摘要

4 模型链 Chains

链(Chains)通常将大语言模型(LLM)与提示(Prompt)结合在一起

链可以一次性接受多个输入;不同链可以组合在一起构造出更复杂的链

4.1 LLMChains

大语言模型链(LLMChain)是一个简单但非常强大的链,也是后面其他许多链的基础

from langchain.chat_models import ChatOpenAI 
from langchain.prompts import ChatPromptTemplate  
from langchain.chains import LLMChain   

prompt = ChatPromptTemplate.from_template(   
    "描述制造{product}的一个公司的最佳名称是什么?"
)
chain = LLMChain(llm=llm, prompt=prompt)
product = "大号床单套装"
chain.run(product)

4.2 顺序链

顺序链(SequentialChains):按预定义顺序执行其链接的链

简单顺序链:只有一个输入和输出

first_prompt = ChatPromptTemplate.from_template(   
    "描述制造{product}的一个公司的最好的名称是什么"
)
chain_one = LLMChain(llm=llm, prompt=first_prompt)

second_prompt = ChatPromptTemplate.from_template(   
    "写一个20字的描述对于下面这个\
    公司:{company_name}的"
)
chain_two = LLMChain(llm=llm, prompt=second_prompt)

overall_simple_chain = SimpleSequentialChain(chains=[chain_one, chain_two],verbose=True)
product = "大号床单套装"
overall_simple_chain.run(product)

普通顺序链:有多个输入和输出

# 子链1的prompt模板: 翻译成英语(把下面的review翻译成英语)
first_prompt = ChatPromptTemplate.from_template(
    "把下面的评论review翻译成英文:\n\n{Review}")
# chain 1: 输入:Review    输出:英文的 Review
chain_one = LLMChain(llm=llm, prompt=first_prompt, output_key="English_Review")

# 子链2的prompt模板: 用一句话总结下面的 review
second_prompt = ChatPromptTemplate.from_template(
    "请你用一句话来总结下面的评论review:\n\n{English_Review}")
# chain 2: 输入:英文的Review   输出:总结
chain_two = LLMChain(llm=llm, prompt=second_prompt, output_key="summary")

# 子链3的prompt模板: 下面review使用的什么语言
third_prompt = ChatPromptTemplate.from_template(
    "下面的评论review使用的什么语言:\n\n{Review}")
# chain 3: 输入:Review  输出:语言
chain_three = LLMChain(llm=llm, prompt=third_prompt, output_key="language")

# 子链4的prompt模板 4: 使用特定的语言对下面的总结写一个后续回复
fourth_prompt = ChatPromptTemplate.from_template(
"使用特定的语言对下面的总结写一个后续回复:\n\n总结: {summary}\n\n语言: {language}")
# chain 4: 输入: 总结, 语言    输出: 后续回复
chain_four = LLMChain(llm=llm,prompt=fourth_prompt,output_key="followup_message")

# 对四个子链进行组合
# 输入:review    输出:英文review,总结,后续回复 
overall_chain = SequentialChain(
    chains=[chain_one, chain_two, chain_three, chain_four],
    input_variables=["Review"],
    output_variables=["English_Review", "summary","followup_message"],
    verbose=True
)

import pandas as pd # 读取官方示例数据
df = pd.read_csv('data/Data.csv')
review = df.Review[5]
overall_chain(review)

4.3 路由链

路由链(Router Chain):根据输入内容将其路由到一条链

目标链(destination_chains):路由器链可以路由到的链

from langchain.chains.router import MultiPromptChain  #导入多提示链
from langchain.chains.router.llm_router import LLMRouterChain,RouterOutputParser
from langchain.prompts import PromptTemplate

llm = ChatOpenAI(temperature=0)
####### 1. 定义提示适用于不同场景下的提示模板
#第一个提示适合回答物理问题
physics_template = """你是一个非常聪明的物理专家。 \
你擅长用一种简洁并且易于理解的方式去回答问题。\
当你不知道问题的答案时,你承认你不知道.这是一个问题:{input}"""
#第二个提示适合回答数学问题
math_template = """你是一个非常优秀的数学家。 \
你擅长回答数学问题。 你之所以如此优秀, \
是因为你能够将棘手的问题分解为组成部分,\
回答组成部分,然后将它们组合在一起,回答更广泛的问题。
这是一个问题:{input}"""
#第三个适合回答历史问题
history_template = """你是以为非常优秀的历史学家。 \
你对一系列历史时期的人物、事件和背景有着极好的学识和理解\
你有能力思考、反思、辩证、讨论和评估过去。\
你尊重历史证据,并有能力利用它来支持你的解释和判断。
这是一个问题:{input}"""
#第四个适合回答计算机问题
computerscience_template = """ 你是一个成功的计算机科学专家。\
你有创造力、协作精神、前瞻性思维、自信、解决问题的能力、\
对理论和算法的理解以及出色的沟通技巧。你非常擅长回答编程问题。\
你之所以如此优秀,是因为你知道如何通过以机器可以轻松解释的命令式步骤描述解决方案来解决问题,\
并且你知道如何选择在时间复杂性和空间复杂性之间取得良好平衡的解决方案。
这是一个输入:{input}"""
####### 2. 对提示模版进行命名和描述
prompt_infos = [
    {
        "名字": "物理学", 
        "描述": "擅长回答关于物理学的问题", 
        "提示模板": physics_template
    },
    {
        "名字": "数学", 
        "描述": "擅长回答数学问题", 
        "提示模板": math_template
    },
    {
        "名字": "历史", 
        "描述": "擅长回答历史问题", 
        "提示模板": history_template
    },
    {
        "名字": "计算机科学", 
        "描述": "擅长回答计算机科学问题", 
        "提示模板": computerscience_template
    }
]
####### 3. 基于提示模版信息创建相应可达的目标链
destination_chains = {}
for p_info in prompt_infos:
    name = p_info["名字"]
    prompt_template = p_info["提示模板"]
    prompt = ChatPromptTemplate.from_template(template=prompt_template)
    chain = LLMChain(llm=llm, prompt=prompt)
    destination_chains[name] = chain  

destinations = [f"{p['名字']}: {p['描述']}" for p in prompt_infos]
destinations_str = "\n".join(destinations)
####### 4. 创建默认目标链,以作为无可达目标链时的备选
default_prompt = ChatPromptTemplate.from_template("{input}")
default_chain = LLMChain(llm=llm, prompt=default_prompt)

####### 5. 定义不同链之间的多提示路由模板
MULTI_PROMPT_ROUTER_TEMPLATE = """给语言模型一个原始文本输入,\
让其选择最适合输入的模型提示。系统将为您提供可用提示的名称以及最适合改提示的描述。\
如果你认为修改原始输入最终会导致语言模型做出更好的响应,你也可以修改原始输入。

<< 格式 >>
返回一个带有JSON对象的markdown代码片段,该JSON对象的格式如下:
`json
{{{{
    "destination": 字符串 \ 使用的提示名字或者使用 "DEFAULT"
    "next_inputs": 字符串 \ 原始输入的改进版本
}}}}
`

记住:“destination”必须是下面指定的候选提示名称之一,\
或者如果输入不太适合任何候选提示,则可以是 “DEFAULT” 。
记住:如果您认为不需要任何修改,则 “next_inputs” 可以只是原始输入。

<< 候选提示 >>
{destinations}

<< 输入 >>
{{input}}

<< 输出 (记得要包含 ```json)>>

样例:
<< 输入 >>
"什么是黑体辐射?"
<< 输出 >>
`json
{{{{
    "destination": 字符串 \ 使用的提示名字或者使用 "DEFAULT"
    "next_inputs": 字符串 \ 原始输入的改进版本
}}}}
`"""

####### 6. 创建完整的路由器模板,适用许多不同类型的目标
router_template = MULTI_PROMPT_ROUTER_TEMPLATE.format(
    destinations=destinations_str
)
router_prompt = PromptTemplate(
    template=router_template,
    input_variables=["input"],
    output_parser=RouterOutputParser(),
)
router_chain = LLMRouterChain.from_llm(llm, router_prompt)
####### 7. 创建整体链路——多提示链
chain = MultiPromptChain(router_chain=router_chain,    [#l路由链路](/tag/llu-you-lian-lu.html)
                         destination_chains=destination_chains,   [#目标链路](/tag/mu-biao-lian-lu.html)
                         default_chain=default_chain,      [#默认链路](/tag/mo-ren-lian-lu.html)
                         verbose=True)
####### 7. 提问与测试
chain.run("什么是黑体辐射?") # 链向物理链路
chain.run("你知道李白是谁嘛?") # 链向历史链路
chain.run("2 + 2 等于多少") # 链向数学链路
chain.run("为什么我们身体里的每个细胞都包含DNA?") # 链向默认链路

5 基于文档的问答

使用大语言模型来构建一个能够回答关于给定文档和文档集合的问答系统

5.1 直接创建向量存储

from langchain.chains import RetrievalQA #检索QA链,在文档上进行检索
from langchain.chat_models import ChatOpenAI [#openai模型](/tag/openaimo-xing.html)
from langchain.document_loaders import CSVLoader #文档加载器,采用csv格式存储
from langchain.vectorstores import DocArrayInMemorySearch [#向量存储](/tag/xiang-liang-cun-chu.html)
from IPython.display import display, Markdown [#在jupyter显示信息的工具](/tag/zai-jupyterxian-shi-xin-xi-de-gong-ju.html)
from langchain.indexes import VectorstoreIndexCreator [#导入向量存储索引创建器](/tag/dao-ru-xiang-liang-cun-chu-suo-yin-chuang-jian-qi.html)
import pandas as pd

file = 'data/OutdoorClothingCatalog_1000.csv'
loader = CSVLoader(file_path=file)
data = pd.read_csv(file,usecols=[1, 2])

# 创建指定向量存储类;创建完成后,从加载器中调用, 通过文档加载器列表加载
index = VectorstoreIndexCreator(vectorstore_cls=DocArrayInMemorySearch).from_loaders([loader])

query ="Please list all your shirts with sun protection \
in a table in markdown and summarize each one."
response = index.query(query) # 使用索引查询创建一个响应,并传入这个查询
display(Markdown(response)) # 查看查询返回的内容

5.2 结合表征模型和向量存储

文本表征(Embeddings)是对文本语义的向量表征,相似内容的文本具有相似的表征向量

向量数据库(Vector Database)用来存储文档的文本块

结合表征模型和向量存储的LLMs问答过程:

  • 对于给定的文档,我们首先将其分成较小的文本块(chunks)
  • 然后获取每个小文本块的文本表征,并将这些表征储存在向量数据库中
  • 储存过程中还会创建索引(index),用来查找与传入查询(Query)最相关的文本片段
  • 将Query与向量数据库中所有向量进行比较后,选择并返回最相似的n个文本块
  • 根据这些找到的相似文本块构建提示,并输入到语言模型,从而得到最终答案
# 创建一个文档加载器,通过csv格式加载
loader = CSVLoader(file_path=file)
docs = loader.load()
print('doc0:',docs[0]) # 每个文档对应于CSV中的一行数据

from langchain.embeddings import OpenAIEmbeddings
embeddings = OpenAIEmbeddings() [#OpenAIEmbedding类初始化](/tag/openaiembeddinglei-chu-shi-hua.html)
# 因为文档比较短了,所以这里不需要进行任何分块,可以直接进行向量表征
# 使用初始化OpenAIEmbedding实例上的查询方法embed_query为文本创建向量表征
embed = embeddings.embed_query("Hi my name is Harrison")
print(len(embed)) # 查看得到向量表征的长度:1536

# 将刚才创建文本向量表征(embeddings)存储在向量存储(vector store)中
# 使用DocArrayInMemorySearch类的from_documents方法来实现
# 该方法接受文档列表以及向量表征模型作为输入
db = DocArrayInMemorySearch.from_documents(docs, embeddings)
query = "Please suggest a shirt with sunblocking"
# 使用上面的向量存储来查找与传入查询类似的文本,得到一个相似文档列表
docs = db.similarity_search(query)

retriever = db.as_retriever() # 基于向量储存,创建检索器
# 导入大语言模型, 这里使用默认模型gpt-3.5-turbo会出现504服务器超时,因此使用gpt-3.5-turbo-0301
llm = ChatOpenAI(model_name="gpt-3.5-turbo-0301",temperature = 0.0) 
qdocs = "".join([docs[i].page_content for i in range(len(docs))]) # 合并获得的相似文档内容

query =  "Please list all your shirts with sun protection in a table in markdown \
and summarize each one."# 以Markdown表格的方式列出所有具有防晒功能的衬衫并总结

# 方法一:直接在合并的相似文档内容后加上问题(question)输入到 `llm.call_as_llm`中
response = llm.call_as_llm(f"{qdocs} Question: {query}") 
display(Markdown(response))

# 方法二:创建一个检索QA链,对检索到的文档进行问题回答
qa_stuff = RetrievalQA.from_chain_type(
    llm=llm, chain_type="stuff", retriever=retriever, verbose=True)
response = qa_stuff.run(query) # 在查询上运行链
display(Markdown(response)) # 两种方法的结果是一样的

更多检索式问答实践可参阅6_course/深度学习/DeepLearning.ai 大模型系列教程/DL.ai 大模型系列:4.LangChain 处理个人数据#6 问答

6 评估 Evaluation

from langchain.chains import RetrievalQA #检索QA链,在文档上进行检索
from langchain.chat_models import ChatOpenAI [#openai模型](/tag/openaimo-xing.html)
from langchain.document_loaders import CSVLoader #文档加载器,采用csv格式存储
from langchain.indexes import VectorstoreIndexCreator [#导入向量存储索引创建器](/tag/dao-ru-xiang-liang-cun-chu-suo-yin-chuang-jian-qi.html)
from langchain.vectorstores import DocArrayInMemorySearch [#向量存储](/tag/xiang-liang-cun-chu.html)

file = 'OutdoorClothingCatalog_1000.csv'
loader = CSVLoader(file_path=file)
data = loader.load()
# 将指定向量存储类,创建完成后,我们将从加载器中调用,通过文档记载器列表加载
index = VectorstoreIndexCreator(
    vectorstore_cls=DocArrayInMemorySearch
).from_loaders([loader])
# 通过指定语言模型、链类型、检索器和日志详细程度来创建检索QA链
llm = ChatOpenAI(temperature = 0.0)
qa = RetrievalQA.from_chain_type(
    llm=llm, chain_type="stuff", retriever=index.vectorstore.as_retriever(), 
    verbose=True,chain_type_kwargs = {"document_separator": "<<<<>>>>>"}
)
# 创建测试用例数据
examples = [
    {
        "query": "高清电视机怎么进行护理?",
        "answer": "使用干布清洁。"
    },
    {
        "query": "旅行背包有内外袋吗?",
        "answer": "有。"
    }
]
from langchain.evaluation.qa import QAGenerateChain 
# 导入QA生成链,它将接收文档,并从每个文档中创建一个问题答案对
example_gen_chain = QAGenerateChain.from_llm(ChatOpenAI())
# 通过传递chat open AI语言模型来创建这个链
new_examples = example_gen_chain.apply_and_parse(
    [{"doc": t} for t in data[:5]]
) # 创建许多例子
examples += new_examples
qa.run(examples[0]["query"]) #

如果使用中文,需要继续QAGenerateChain类,并将PROMPT添加上“请使用中文输出”

如果希望输出更多的调试信息,可import langchain并配置langchain.debug = True

predictions = qa.apply(examples) # 为所有不同的示例创建预测
from langchain.evaluation.qa import QAEvalChain # 导入QA问题回答,评估链
llm = ChatOpenAI(temperature=0)
eval_chain = QAEvalChain.from_llm(llm) # 通过调用chatGPT进行评估
graded_outputs = eval_chain.evaluate(examples, predictions) # 调用evaluate进行评估

# 传入示例和预测,得到一堆分级输出,循环遍历它们打印答案
for i, eg in enumerate(examples):
    print(f"Example {i}:")
    print("Question: " + predictions[i]['query'])
    print("Real Answer: " + predictions[i]['answer'])
    print("Predicted Answer: " + predictions[i]['result'])
    print("Predicted Grade: " + graded_outputs[i]['text'])
    print()

# Example 1: 
# Question: 旅行背包有内外袋吗? 
# Real Answer: 有。 
# Predicted Answer: 是的,旅行背包有多个实用的内外袋,可以轻松装下您的必需品。
# Predicted Grade: CORRECT

正确的答案会有相似的语义,但可能有不同的变体,借助LLM能够实现更合理的评估

7 代理 Agent

LLMs可以通过代理(Agent)的方式借助外部工具以增强模型的输出效果

  • llm-math 工具:结合语言模型和计算器用以进行数学计算
  • wikipedia工具:通过API连接到wikipedia进行搜索查询
  • PythonREPLTool工具:使用Python交互式环境工具(REPLTool)
from langchain.agents import load_tools, initialize_agent
from langchain.tools.python.tool import PythonREPLTool

llm = ChatOpenAI(temperature=0)
tools = load_tools(["llm-math","wikipedia"], llm=llm #第一步初始化的模型)

agent= initialize_agent(
    tools, llm, # 加载工具, 初始化模型
    agent=AgentType.CHAT_ZERO_SHOT_REACT_DESCRIPTION,  [#代理类型](/tag/dai-li-lei-xing.html)
    handle_parsing_errors=True, [#处理解析错误](/tag/chu-li-jie-xi-cuo-wu.html)
    verbose = True [#输出中间步骤](/tag/shu-chu-zhong-jian-bu-zou.html)
)

agent("计算300的25%,思考过程请使用中文。")
# {'input': '计算300的25%,请用中文', 'output': '300的25%是75.0'}

LLMs借助代理(Agent)的思考和问题解决过程:

  1. 模型对于接下来需要做什么,给出思考(Thought)

    <p style="font-family:verdana; font-size:12px;color:green"> <strong>思考</strong>:我们需要计算300的25%,这个过程中需要用到乘法和除法。</p>

  2. 模型基于思考采取行动(Action), 因为使用的工具不同,Action的输出也和之前有所不同

    <p style="font-family:verdana; font-size:12px;color:green"> <strong>行动</strong>: 使用计算器(calculator),输入300*0.25</p>

  3. 模型得到观察(Observation)

    <p style="font-family:verdana; font-size:12px;color:green"><strong>观察</strong>:答案: 75.0</p>

  4. 基于观察,模型对于接下来需要做什么,给出思考(Thought)

    <p style="font-family:verdana; font-size:12px;color:green"> <strong>思考</strong>: 我们的问题有了答案 </p>

  5. 给出最终答案(Final Answer)

    <p style="font-family:verdana; font-size:12px;color:green"> <strong>最终答案</strong>: 75.0 </p>

  6. 以字典的形式给出最终答案。

扩展:自定义工具

# 导入tool函数装饰器
from langchain.agents import tool
from datetime import date

@tool
def time(text: str) -> str:
    """
    返回今天的日期,用于任何需要知道今天日期的问题。\
    输入应该总是一个空字符串,\
    这个函数将总是返回今天的日期,任何日期计算应该在这个函数之外进行。
    """
    return str(date.today())

agent= initialize_agent(
    tools=[time], llm=llm, # 时间工具, 初始化的模型
    agent=AgentType.CHAT_ZERO_SHOT_REACT_DESCRIPTION,  [#代理类型](/tag/dai-li-lei-xing.html)
    handle_parsing_errors=True, [#处理解析错误](/tag/chu-li-jie-xi-cuo-wu.html)
    verbose = True [#输出中间步骤](/tag/shu-chu-zhong-jian-bu-zou.html)
)
agent("今天的日期是?")

8 总结

本次简短课程涵盖了一系列LangChain的应用实践,包括处理顾客评论和基于文档回答问题,以及通过LLM判断何时求助外部工具 (如网站) 来回答复杂问题。

其他可能应用:基于CSV文件回答问题、查询sql数据库、与api交互等等

往年同期文章