【NLP笔记】LLM应用之AI Agent & LangChain实战

时间:2024-04-13 11:26:30

文章目录

  • AI Agent概述
  • LangChain实战
    • 构建prompt模版
    • LLM调用
      • 调用HuggingFace开源大模型(在线)
      • 调用HuggingFace开源大模型(本地)
      • 调用文心一言
    • Chains
      • Single Chain
      • Sequential Chain
        • Simple Sequential Chain
        • Complex Sequential Chain
      • Router Chain
      • Document Chain
    • Agents
      • 示例1:构建ReAct Agent
      • 示例2:构建RAG Agent

参考内容:

  • The Rise and Potential of Large Language Model Based Agents: A Survey
  • A Survey on Large Language Model based Autonomous Agents
  • 全面拆解!何为AI Agent?

AI Agent概述

在这里插入图片描述

随着大模型的发展,一系列基于LLM的研究也逐渐发展起来。单纯调用LLM会有以下问题:会产生幻觉、结果并不总是真实的、对时事的了解有限或一无所知、很难应对复杂的计算。AI Agent代表的是一个概念,人类负责设定目标、提供资源和监督结果,AI 完成任务拆分、工具选择、进度控制、实现目标后自主结束工作,通过联合各种不同的组件能力去提升大模型的应用效果,解决上述单纯调用LLM现存问题,也向着更智能化的方向发展。

AI Agent需要包含的能力包括但不限于:

  • 工具集合(Tools):Google搜索(获取最新信息)、Python REPL(执行代码)、Wolfram(进行复杂的计算)、外部API(如获取特定信息入LLM大模型的接口,可以调用各种不同的大模型进行推理)等。框架如LangChain则是提供一种通用的框架通过大语言模型的指令来轻松地实现这些工具的调用,起到链接的作用;
  • 工程化的方案(Planning):如上一篇笔记提到的CoT、GoT等问题拆解和解决方案,让智能体能够规划并解决目标问题;
  • 记忆体(Memory):记忆体指的是可以定义为用于获取、存储、保留以及随后检索信息的过程;基于prompt上下文的短期记忆学习(short-term memory)、基于存储常识文本数据库检索的长期记忆学习(long-term memory)。通常用于提升记忆能力的方法有三种:
    • 扩展 Backbone 架构的长度限制:针对 Transformers 固有的序列长度限制问题进行改进。
    • 总结记忆(Summarizing):对记忆进行摘要总结,增强代理从记忆中提取关键细节的能力。
    • 压缩记忆(Compressing):通过使用向量或适当的数据结构对记忆进行压缩,可以提高记忆检索效率。;
      在这里插入图片描述
      现有的AI Agent一览:
      在这里插入图片描述

AI Agent适用于回答较为开放,无统一标准但是有基准要求的问答系统的实现,能够解决一些单纯LLM推理存在的问题,是未来智能化场景的重要解决思路。

LangChain实战

# 安装langchain
pip3 install langchain

构建prompt模版

  • 基础prompt模版构建
# langchain版本0.1.13
from langchain_core.prompts import PromptTemplate

# 无变量prompt
no_var_prompt = PromptTemplate(input_variables=[], template="请介绍一下导演李安。")
print(no_var_prompt.format())
# 请介绍一下导演李安。

# 单个变量prompt
one_var_prompt = PromptTemplate(input_variables=["name"], template="请介绍一下{name}。")
print(one_var_prompt.format(name="李安"))
# 请介绍一下李安。

# 多个变量prompt
multi_var_prompt = PromptTemplate(input_variables=["name", "job"], template="请介绍一下{name},他的职业是个{job}。")
print(multi_var_prompt.format(name="李安", job="导演"))
# 请介绍一下李安,他的职业是个导演。

# langchain可根据prompt模版自动推断输入变量有哪些
prompt_template = PromptTemplate.from_template(template="请介绍一下{name},他的职业是个{job}。")
print(prompt_template.input_variables)
# ['job', 'name']
print(prompt_template.format(name="李安", job="导演"))
# 请介绍一下李安,他的职业是个导演。
  • 聊天prompt模版创建
# langchain版本0.1.13
from langchain.prompts import (
    ChatPromptTemplate,
    PromptTemplate,
    SystemMessagePromptTemplate,
    AIMessagePromptTemplate,
    HumanMessagePromptTemplate,
)
from langchain.schema import (
    AIMessage,
    HumanMessage,
    SystemMessage
)

template = "您是将{input_language}翻译成{output_language}的得力助手。"
# 创建角色:系统的模板
system_message_prompt = SystemMessagePromptTemplate.from_template(template)
print(system_message_prompt.format(input_language="中文", output_language="英文").content)
# 您是将中文翻译成英文的得力助手。
human_template = "{text}"
# 创建角色:人类的模板
human_message_prompt = HumanMessagePromptTemplate.from_template(human_template)
print(human_message_prompt.format(text="请介绍一下导演李安。").content)
# 请介绍一下导演李安。

# 创建一个常规的模板
prompt = PromptTemplate(
    template="您是将{input_language}翻译成{output_language}的得力助手。",
    input_variables=["input_language", "output_language"],
)
# 再创建一个角色:系统的模板
system_message_prompt_2 = SystemMessagePromptTemplate(prompt=prompt)
print(system_message_prompt_2.format(input_language="中文", output_language="英文").content)
# 您是将中文翻译成英文的得力助手。

# 从一个或多个 MessagePromptTemplate 构建 ChatPromptTemplate。
chat_prompt = ChatPromptTemplate.from_messages([system_message_prompt, human_message_prompt])

# 从格式化消息中获取聊天完成信息
message = chat_prompt.format_prompt(
    input_language="中文", output_language="英文", text="请介绍一下导演李安。").to_messages()
print(message)
# [SystemMessage(content='您是将中文翻译成英文的得力助手。'), HumanMessage(content='请介绍一下导演李安。')]

LLM调用

调用HuggingFace开源大模型(在线)

# langchain版本0.1.13
import os
from langchain.prompts import PromptTemplate
from langchain_community.llms import HuggingFaceEndpoint
# huggingface的access token,获取地址:https://huggingface.co/settings/tokens
os.environ['HUGGINGFACEHUB_API_TOKEN'] = '你的access token'

# 创建prompt模版
template = """
任务:请介绍一下{name},TA的职业是{job}。
要求:请用中文回答。
"""
prompt = PromptTemplate.from_template(template=template)
prompt = prompt.format(name="李安", job="导演")
print(prompt)
# 任务:请介绍一下李安,TA的职业是导演。
# 要求:请用中文回答。

# 加载大模型,进行回答
# 可选用于文本对话的大模型列表:https://huggingface.co/models?pipeline_tag=text-generation&sort=downloads
llm = HuggingFaceEndpoint(repo_id="HuggingFaceH4/zephyr-7b-beta")
print(llm.invoke(prompt))
# 答案:李安,全名詹斌,是一位中国出生的香港电影导演、影视生产商和社交活动家。他是香港电影业的一位经典人物,多年来,
# 他的影片中包含了辉笛、《风云》、《倾城》等多部经典片,并被誉为“香港新时代”的代表作家。在过去的几十年里,
# 他的影片一直处于热门地位,并被多次荣賞,他的影片在全球范围内受到了广泛欢迎。李安是中国影业的领袖和榜样,
# 他的影片还被认为是在扮演了一些形势与时势的角色。除了在影业中贡献了一身作为,他还是社会和社区的好样本,
# 他的奉献精神和社会工作为社区和社会造成了巨大的贡献。

调用HuggingFace开源大模型(本地)

  • 下载模型至本地
    在这里插入图片描述
    在这里插入图片描述
GIT_LFS_SKIP_SMUDGE=1 git clone https://huggingface.co/google/mt5-base
# 注意LFS文件太大可能下载不了,可以先跳过,然后单独下载(链接中blob换成resolve),并放到相一致的文件夹中
# 也可以直接浏览器点击下载按钮下载
wget wget --header="Authorization: Bearer 你的token" https://huggingface.co/google/mt5-base/resolve/main/pytorch_model.bin
# ...
# *.h5和*.msgpack是tensorflow框架和flax框架产出的模型权重,pytorch只要*.bin文件
# 将模型部署在本地并进行调用
from typing import List, Optional
from langchain.llms.base import LLM
from transformers import AutoTokenizer, AutoModel
import json
import torch


class MT5(LLM):
    max_new_tokens = 1000
    temperature = 0.1
    top_p = 1
    penalty_alpha = 0

    def __init__(self):
        super().__init__()

    @property
    def _llm_type(self) -> str:
        return "MT5"

    def _call(self, prompt: str, stop: Optional[List[str]] = None) -> str:
        # 调用本地部署大模型进行预测
        try:
            # 加载本地模型
            tokenizer = AutoTokenizer.from_pretrained("./local_models/google/mt5-small", trust_remote_code=True)
            model = AutoModel.from_pretrained("./local_models/google/mt5-small", trust_remote_code=True).half().cuda()
            # tokenize输入
            model_input = tokenizer.encode(prompt, return_tensors="pt").to("cuda")
            # 预测
            model.eval()
            # 模型参数在config.py文件中进行设置
            with torch.no_grad():
                response = tokenizer.decode(model.generate(model_input, max_length=1500, top_k=1)[0])
            return response
        except Exception as e:
            logging.error(f"调用模型失败,错误信息:{str(e)}")
            return "None"
# 调用本地部署大模型
llm = MT5()
print(llm('请介绍一下李安'))
也可以结合text-generation-webui,将模型部署在开发机的docker中,通过自定义端口访问开发机进行调用;

链接:https://github.com/oobabooga/text-generation-webui

调用文心一言

# langchain版本0.1.13
import os
from langchain.prompts import PromptTemplate
from typing import List, Optional

from langchain.llms.base import LLM
import requests
import json
import logging
# 需要购买文心相关服务获得对应的API调用授权码
from configs.constants import WENXIN_AK, WENXIN_SK

def get_access_token():
    """
    获取千帆access token
    """

    url = f"https://aip.baidubce.com/oauth/2.0/token?grant_type=client_" \
          f"credentials&client_id={WENXIN_AK}&client_secret={WENXIN_SK}"
    payload = ""
    headers = {
        'Content-Type': 'application/json',
        'Accept': 'application/json'
    }
    response = requests.request("POST", url, headers=headers, data=payload)
    access_token = json.loads(response.text).get("access_token")
    return access_token

class WenXin(LLM):
    """
    自定义文心一言类
    """

    temperature = 0.1
    top_p = 0.8
    penalty_score = 1.0
    model_name = "completions"
    # turbo、文心4.0、文心3.5
    models = ["eb-instant", "completions_pro", "completions"]

    @property
    def _llm_type(self) -> str:
        return self.model_name

    def _call(self, prompt: str, stop: Optional[List[str]] = None) -> str:
        url = "https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/{}?access_token={}".format(
            self.model_name, get_access_token())
        data = {
            "messages": [{"role": "user", "content": prompt}],
            "temperature": self.temperature,
            "top_p": self.top_p,
            "penalty_score": self.penalty_score,
        }
        headers = {'Content-Type': 'application/json'}
        response = requests.request("POST", url, headers=headers, data=json.dumps(data))
        if response.status_code == 200:
            return response.json()['result']
        logging.error(f"调用模型失败,错误信息:{response.text}")
        return "None"
# 创建prompt模版
template = """
问题:{country}的首都是哪里?
要求:请用中文回答。
{format_instructions}
"""
response_schemas = [
    ResponseSchema(name="answer", description="回答用户的答案"),
    ResponseSchema(name="score", description="答案的置信度得分,0~1范围内", type="float")
]
output_parser = StructuredOutputParser.from_response_schemas(response_schemas)
# 获取响应格式化的指令
format_instructions = output_parser.get_format_instructions()
prompt = PromptTemplate(
    template=template, input_variables=["country"], partial_variables={"format_instructions": format_instructions})
prompt = prompt.format(country="中国")
print(prompt)
# 问题:中国的首都是哪里?
# 要求:请用中文回答。
# The output should be a markdown code snippet formatted in the following schema, including the leading and trailing "```json" and "```":
#
# ```json
# {
# 	"answer": string  // 回答用户的答案
# 	"score": float  // 答案的置信度得分,0~1范围内
# }
# ```
llm = WenXin()
llm.model_name = "completions"
res = llm.invoke(prompt)
print(output_parser.parse(res))
# {'answer': '中国的首都是北京。', 'score': 1.0}
  • 其他开源LLM的部署与调用
    • 其他大模型的调用可以类似,结合docker和text-generation-webui可以进行开发机上的模型本地部署(模型下载至开发机),通过text-generation-webui开放接口调用开发机模型,有了接口就可以参考上述代码构造本地模型调用类,实现不同功能的LLM的本地部署&调用;
    • 可以连接huggingface等网站的机器,也可以结合tansformer包,在_call函数里实现对各种开源大模型的调用;

Chains

Single Chain

# langchain版本0.1.13
import os
from langchain.chains import LLMChain
from langchain.prompts import PromptTemplate
from langchain_community.llms import HuggingFaceEndpoint

# huggingface的access token,获取地址:https://huggingface.co/settings/tokens
os.environ['HUGGINGFACEHUB_API_TOKEN'] = '你的huggingface access token'

template = """
问题:请介绍一下{name}, TA是一个{job}。
要求:请用中文回答。
"""
prompt = PromptTemplate(input_variables=["name", "job"], template=template)
llm = HuggingFaceEndpoint(repo_id="HuggingFaceH4/zephyr-7b-beta")
single_chain = LLMChain(prompt=prompt, llm=llm)
print(single_chain.invoke(input={"name": "李安", "job": "导演"}))
# {'name': '李安', 'job': '导演', 'text': '答案:李安是一个盛世界影业的权威导演和作家。他曾创造了多个影片,包括《流浪地球》和《红颜》等,这些影片在国内和国外都非常受欢迎。他的作品经历了非常多的艰苦和困难,但他一直坚持自己的独特风格和创造力,并在整个行业中具有显著的影响力。在他的作品中,我们可以看到他的深刻思考和社会观察,并且经常发现到令人吸引的视觉巧妙和技巧。所以,我可以说,李安是一个非常有趣和具有创造力的人,他的作品绝对值得观看和学习。'}

Sequential Chain

Simple Sequential Chain

Simple Sequential Chain是顺序执行的链式结构,按顺序连接多个处理组件,输入输出只能有1个token。
在这里插入图片描述

# langchain版本0.1.13
import os
from langchain.chains import LLMChain, SimpleSequentialChain
from langchain.prompts import PromptTemplate, ChatPromptTemplate
from langchain_community.llms import HuggingFaceEndpoint

# huggingface的access token,获取地址:https://huggingface.co/settings/tokens
os.environ['HUGGINGFACEHUB_API_TOKEN'] = '你的huggingface access token'
llm = HuggingFaceEndpoint(repo_id="HuggingFaceH4/zephyr-7b-beta")
# 通过顺序链解决实际问题(每个链只能有单个token输入)
# 目标是:设定你要开一家店,给这个店取个名字,再看看能够研发哪些产品。
prompt1 = ChatPromptTemplate.from_template(
    template="问题:你要开一家{store},取什么名字比较好?要求:请用中文回答。"
)
# 定义第一条链,且需要指定输出token
chain1 = LLMChain(prompt=prompt1, llm=llm, output_key="store_name")

prompt2 = ChatPromptTemplate.from_template(
    template="问题:店名为{store_name},该店面向的是高端市场,你能研发哪些产品?要求:请用中文回答。"
)
chain2 = LLMChain(prompt=prompt2, llm=llm)
# 将多个链串联形成顺序链
seq_chain = SimpleSequentialChain(chains=[chain1, chain2], verbose=True)
# 调用顺序链,传入输入参数
print(seq_chain.invoke(input="咖啡店"))
# > Entering new SimpleSequentialChain chain...
# AI: 答案:名字应该简单易记,能够反映咖啡店的风貌和品味。建议取名字中含有咖啡或者巧克力的词语,例如:明月咖啡店、好日子巧克力咖啡、锅巴咖啡、柔情咖啡、巧茗咖啡等等。最终选择应该根据商户自身的风格和特点进行决定。
# AI: 高端市场对咖啡的要求是更加专业和精细的,因此,我们可以研发以下产品:
# 1. 特定来源和采茗技术的咖啡:例如,来自俄勃利亚或埃塞崽亚的咖啡,采茗技术包括烘熬、冲泡和培芽等等。
# 2. 巧克力咖啡:这是通过合成巧克力和咖啡的技术,可以提供更加奢华和复杂的咖啡体验。
# 3. 高端咖啡配料:例如,牛奶、奶油、蜂蜀芽等等。
# 4. 咖啡面条:这是通过将咖啡 grounds 加入面条机的技术,可以提供更加独特和奢华的咖啡体验。
# 5. 咖啡甜点:例如,巧克力咖啡毡蛋、咖啡巧克力曲奇等等。
# 最终的产品和咖啡配料应该根据商户自身的特色和目标进行决定。
# > Finished chain.
# {'input': '咖啡店', 'output': '\n\nAI: 高端市场对咖啡的要求是更加专业和精细的,因此,我们可以研发以下产品:\n\n1. 特定来源和采茗技术的咖啡:例如,来自俄勃利亚或埃塞崽亚的咖啡,采茗技术包括烘熬、冲泡和培芽等等。\n2. 巧克力咖啡:这是通过合成巧克力和咖啡的技术,可以提供更加奢华和复杂的咖啡体验。\n3. 高端咖啡配料:例如,牛奶、奶油、蜂蜀芽等等。\n4. 咖啡面条:这是通过将咖啡 grounds 加入面条机的技术,可以提供更加独特和奢华的咖啡体验。\n5. 咖啡甜点:例如,巧克力咖啡毡蛋、咖啡巧克力曲奇等等。\n\n最终的产品和咖啡配料应该根据商户自身的特色和目标进行决定。'}
Complex Sequential Chain

Complex Sequential Chain是更加复杂的串联链式结构,它可以有多个输出。

# langchain版本0.1.13
import os
from langchain.chains import LLMChain, SimpleSequentialChain
from langchain.prompts import PromptTemplate, ChatPromptTemplate
from langchain_community.llms import HuggingFaceEndpoint# huggingface的access token,获取地址:https://huggingface.co/settings/tokens
os.environ['HUGGINGFACEHUB_API_TOKEN'] = '你的huggingface access token'
llm = HuggingFaceEndpoint(repo_id="HuggingFaceH4/zephyr-7b-beta")# 目标是:设定你要开一家店,给这个店取个名字,再看看能够研发哪些产品。
prompt1 = ChatPromptTemplate.from_template(template="问题:你要开一家{store},取什么名字比较好?要求:请用中文回答。")
chain1 = LLMChain(prompt=prompt1, llm=llm, output_key="store_name")
prompt2 = ChatPromptTemplate.from_template(
   template="问题:你开的店名为{store_name},请给出店的定位?要求:请用中文回答。"
)
chain2 = LLMChain(prompt=prompt2, llm=llm, output_key="concept")
prompt3 = ChatPromptTemplate.from_template(
   template="问题:你的店定位是{concept},请确定几款研发的产品?要求:请用中文回答。"
)
chain3 = LLMChain(prompt=prompt3, llm=llm, output_key="products")
seq_chain = SequentialChain(
   chains=[chain1, chain2, chain3], input_variables=["store"],
   output_variables=["store_name", "concept", "products"],
   verbose=True
)
res = seq_chain.invoke(input="咖啡店")
print(res)

Router Chain

设定多个chain,每个chain负责回答自己擅长的问题,有两种实现方式,一种是通过大模型进行识别,再进一步分配任务(LLMRouter),另一种是通过向量数据库进行相似度匹配,然后再进行任务分发(EmbeddingRouter):
在这里插入图片描述

# langchain版本0.1.13
import os
from langchain.chains import LLMChain, SimpleSequentialChain, LLMRouterChain, MultiPromptChain
from langchain.prompts import PromptTemplate, ChatPromptTemplate
from langchain.chains.router.llm_router import RouterOutputParser
from langchain_community.llms import HuggingFaceEndpoint
from langchain.chains.router.embedding_router import EmbeddingRouterChain
from langchain.embeddings import CohereEmbeddings
from langchain.vectorstores import Chroma
# huggingface的access token,获取地址:https://huggingface.co/settings/tokens
os.environ['HUGGINGFACEHUB_API_TOKEN'] = '你的huggingface access token'
llm = HuggingFaceEndpoint(repo_id="HuggingFaceH4/zephyr-7b-beta")
cooker_template = """你是一位技术非常好的厨师。
你很擅长回答美食、烹饪等相关的问题。
这里有一个问题:
{input}
"""

conductor_template = """你是一位技术很牛的导演。\
你很擅长回答拍摄、电影等相关的问题。
这里有一个问题:
{input}"""

prompt_infos = [
    {
        "name": "cooker",
        "description": "擅长回答美食、烹饪等相关的问题",
        "prompt_template": cooker_template
    },
    {
        "name": "conductor",
        "description": "擅长回答拍摄、电影等相关的问题",
        "prompt_template": conductor_template
    },
]

combined_chains = {}
for prompt_info in prompt_infos:
    name = prompt_info["name"]
    template = prompt_info["prompt_template"]