Bootstrap

突破LLM的边界:解析LLM的局限与LangChain初探

前言

img_21.png

在当今信息爆炸的时代,自然语言处理(NLP)技术的飞速发展为人们提供了前所未有的便利和智能体验。然而,随着大规模预训练语言模型(LLM)的兴起,开发者和消费者们也逐渐发现 LLM 的局限性,并由此诞生了许多解决方案。

本文将探讨LLM局限性的来源、影响以及如何使用LangChain作为一种解决方案,为克服相关难题。

LLM的局限性

LLM Tokens限制

img_5.png

如上图所示,最新的 GPT-3.5 模型的上下文窗口(Context Window)已经扩展到可以处理16,385个 tokens。那么这里的上下文窗口是指什么呢?

上下文窗口是指模型在处理输入文本时考虑的前后文信息的范围,这个窗口的大小通常以 token 的数量为单位。

这里的 token 可以是一个单词、一个子词或一个字符,具体取决于模型的分词方式。16,385 tokens 可以理解为在与 GPT 开启一次窗口会话时,你可以输入的最大文本量。

img_6.png

GPT-3.5 支持 16,385 tokens,而 GPT-4 取得了显著的进步,支持128,000 tokens,正如图中所示。

超过这个限制会导致什么情况呢?可能出现以下问题:

1、截断或切分:系统可能会选择截断或切分文本以适应模型的要求。这可能导致部分文本信息的丢失,尤其是在截断位置附近的信息。

2、信息缺失: 上下文窗口限制会使模型无法考虑到整个文本的上下文,因此在处理超长文本时,模型可能无法获取一些全局信息,从而影响对整体语境的理解。

3、性能下降:超过上下文窗口限制可能导致模型在处理任务时性能下降。

简而言之,如果对话记忆超过 tokens 的上限,它将会遗忘之前的对话,并导致响应速度减慢。这是目前 GPT 在需求较为复杂的任务中无法克服的缺陷。

最新的 GPT-4 模型已经支持128,000 tokens,这是一项显著的进步,相当于一部小说的文本量,也就是说,GPT 可以直接理解一部《哈利波特》的所有内容并回答相关问题。 然而,对于企业消费者来说,128,000 tokens 仍然有限,难以满足我对大型项目文档分析之类的要求。

企业用户的定制需求

假设我是一个普通用户,那么我可以使用 GPT-4 Turbo(128,000 tokens)解析一本《哈利波特》,然后对这本书的内容进行问答。虽然GPT的响应速度会变得很慢, 但我仍然可以使用。但如果我是一个企业用户,我需要LLM根据我的商品列表进行问答,在不考虑本地部署LLM模型的情况下,我很难使用GPT之类的模型来实现我的需求。

那么以GPT模型为例,假设你需要根据企业内部数据(例如:商品列表信息),让GPT进行问答,你会遇到以下问题和挑战:

1、上下文窗口tokens限制:这会限制你将企业信息作为语料输入到GPT。

2、语料类型单一:GPT 无法直接解析视频、音频、图片等文件。

3、数据安全和隐私:如果你的企业数据包含敏感信息,如客户信息、财务数据等,要确保在使用GPT时能够有效地保护数据的安全和隐私。

4、领域特定性:GPT是在大规模通用文本数据上预训练的,可能对于某些特定领域的企业内部数据理解能力有限。

5、数据准备:需要对企业内部数据进行预处理,将其转换成适合GPT输入的格式。这可能包括分词、标记化和其他数据清理工作,以确保模型能够正确理解和处理数据。

6、解释性:GPT等深度学习模型通常被认为是“黑盒”模型,其决策过程难以解释。对于企业决策中需要透明度和解释性的场景,可能需要考虑如何解释模型的输出。

7、用户反馈和迭代:直接使用GPT时,很难修复模型可能存在的偏见或错误,满足用户的定制需求。

8、问题多样性: GPT的性能可能会受到问题多样性的影响。在问答任务中,确保模型能够处理各种类型和形式的问题是一个挑战,有时可能需要更多的数据来覆盖不同的情况。

img_11.png

LoRA

基于上面的问题,延伸出一个概念:LoRA —— 大型语言模型的低秩自适应。这个名字挺唬人,但原理很简单。

img_16.png

如上图所示,LoRA的基本原理是冻结预训练好的模型权重参数,并额外增加一个旁路网络、一个降维矩阵A、一个升维矩阵B,用变量R来控制降维度。R越小,整体的 参数量就会越小。我们可以使用本地知识库来训练这个旁路网络,这样不仅微调的成本显著降低,而且还能获得和全模型微调类似的效果。

看起来使用LoRA微调技术可以满足我们的需求,而且相对于原模型,LoRA不需要存储优化器数据,所以参数量减少了很多。但即使这样, LoRA的训练在没有几块4090显卡的情况下仍然很困难。

那有没有更轻量级的解决方案呢?——有!

LlamaIndex 和 LangChain 就是其中的佼佼者。

LangChain

我们之前提到了一些更轻量级的解决方案,其中包括 LlamaIndex 和 LangChain。它们的原理和目标都不相同,LlamaIndex 专注于为 Prompt 准备数据,而 LangChain 的功能更为全面和广泛。

现在让我们详细了解一下 LangChain。

LangChain 是什么?

img_12.png

上图是官方对于 LangChain 的解释,"翻译"过来就是:

LangChain 是一个由 Python 开发的应用框架,用于帮助开发者利用大语言模型构建应用程序。它提供了一系列的工具和组件,使你能更简单地创建基于大语言 模型和聊天模型的应用。使用 LangChain,你能更方便地管理、扩展语言模型的交互,将多个组件链接到一起,并提供额外的资源,如 API 和数据库。

LangChain 能做什么?

img_15.png

如上图所示,LangChain 包含一个 Prompt 模板、一个模型输出解释器(OutputParser)和多个指令工具(llm-math、google-search、terminal)。

一个典型的使用场景是,当用户提出问题时,LangChain 会使用 Prompt 模板将问题格式化,然后调用 LLM 模型。模型会根据 Prompt 模板提供的信息返回回答, 然后由输出解析器解析输出。如果收到 LLM 模型发出的指令,就执行相应的工具以获取执行结果,再通过 Prompt 向模型请求,直到模型没有下一步指令则返回结果。

那么,既然是面向开发人员的框架,我们就具体看一下 LangChain 能做些什么。我们以 ChatGPT 为例,通过 Python 调用 LangChain。

Prompts 提示词

Prompts 是用于指导模型生成响应的关键词或短语,它们有助于模型理解上下文并生成相关且连贯的基于语言的输出,比如回答问题、完成句子或参与对话。

img_13.png

PromptTemplate 提示词模板

你可以使用此功能自定义一个对话模板,并使用占位符 ‘{}’ 替换需要动态处理的内容,以确保 LLM 模型能理解对话内容。

例如,假设有一个商品搜索功能,用户输入商品名称,然后 LLM 输出对应商品的信息,这时我们可以定义一个查询商品详情的对话模板,并用占位符 ‘{}’ 替换 需要动态变更的商品名称。

下面是一个例子:

# 用于为字符串提示创建 PromptTemplate 模板。
from langchain import PromptTemplate 

# 默认情况下, PromptTemplate 使用 Python 的 str.format 语法进行模板化;但是可以使用其他模板语法(例如, jinja2 )
prompt_template = PromptTemplate.from_template(
    "Tell me all about the {goodName}!"
)

prompt_template.format(goodName="MacBook Pro")

prompt_template.format 对于参数数量没有限制,你可以添加 0 个或多个参数。

此外,prompt_template 还提供了参数校验功能,参数变量将与模板字符串中存在的变量进行比较,如果不匹配,则会引发异常。

ChatPromptTemplate 对话提示模板

langchain.prompts 还支持对话模板的定制,用户可以根据模板的内容进行问答

代码示例如下:

from langchain.prompts import ChatPromptTemplate

# ChatPromptTemplate.from_messages 接受各种消息表示形式。
# 这里接收了一个动态的系统名称,以及用户输入的对话。
template = ChatPromptTemplate.from_messages([
    ("system", "You are a helpful AI bot. Your name is {name}."),
    ("human", "Hello, how are you doing?"),
    ("ai", "I'm doing well, thanks!"),
    ("human", "{user_input}"),
])

messages = template.format_messages(
    name="Bob",
    user_input="What is your name?"
)
messages

提示词的其他扩展
  1. ChatPromptTemplate.from_messages 还支持复杂对象的构建,例如:你可以传入一个 SystemMessage 对象,在这里配置 LLM 的角色。

  2. langchain.prompts 也支持用户自定义模板,在某些情况下,默认提示模板可能无法满足你的需求。例如,你可能希望创建一个提示模板, 其中包含语言模型的特定动态说明。在这种情况下,你可以创建自定义提示模板。

  3. langchain.prompts 支持带例子的提示词模板。有些情况下,我们需要给 LLM 一些例子,让 LLM 模型更好地理解我们的意图。

  4. 最后,langchain.prompts 也支持我们将多个提示组合使用(compose),以及将提示词序列号存储(load_prompt)。

LLM

大型语言模型(LLM)是 LangChain 的核心组件。LangChain 不提供自己的 LLM,而是提供了一个标准接口,用于与许多不同的 LLM 进行交互。

直接使用LLM模型
# 设置代理
import os
os.environ['http_proxy'] = 'http://127.0.0.1:10809'
os.environ['https_proxy'] = 'http://127.0.0.1:10809'

# 创建LLM模型
from langchain.llms import OpenAI
llm = OpenAI()

# 可以直接调用
llm("给我讲一个笑话")

也可以批量调用

# 批量调用15次
llm_result = llm.generate(["给我讲个笑话", "给我讲个诗词"]*15)

# 获取第一次结果
llm_result.generations[0]

异步调用LLM

因为LLM模型的调用是网络绑定的,异步调用 LLM 可以让程序在等待响应时做更多的事情。

下面是一个例子:

# 导入所需的模块
import time  # 用于计时
import asyncio  # 用于处理异步编程

from langchain.llms import OpenAI  # 从 langchain.llms 库导入 OpenAI 类

# 定义一个串行(同步)方式生成文本的函数
def generate_serially():
    llm = OpenAI(temperature=0.9)  # 创建 OpenAI 对象,并设置 temperature 参数为 0.9
    for _ in range(10):  # 循环10次
        resp = llm.generate(["Hello, how are you?"])  # 调用 generate 方法生成文本
        print(resp.generations[0][0].text)  # 打印生成的文本

# 定义一个异步生成文本的函数
async def async_generate(llm):
    resp = await llm.agenerate(["Hello, how are you?"])  # 异步调用 agenerate 方法生成文本
    print(resp.generations[0][0].text)  # 打印生成的文本

# 定义一个并发(异步)方式生成文本的函数
async def generate_concurrently():
    llm = OpenAI(temperature=0.9)  # 创建 OpenAI 对象,并设置 temperature 参数为 0.9
    tasks = [async_generate(llm) for _ in range(10)]  # 创建10个异步任务
    await asyncio.gather(*tasks)  # 使用 asyncio.gather 等待所有异步任务完成

# 记录当前时间点
s = time.perf_counter()

# 使用异步方式并发执行生成文本的任务
# 如果在 Jupyter 以外运行此代码,使用 asyncio.run(generate_concurrently())
await generate_concurrently()

# 计算并发执行所花费的时间
elapsed = time.perf_counter() - s
print("\033[1m" + f"Concurrent executed in {elapsed:0.2f} seconds." + "\033[0m")

LLM 缓存

LangChain 为 LLM 提供了一个可选的缓存层。这么做的原因有两个:

1、如果你经常多次请求相同的完成,它可以通过减少你对 LLM 提供程序进行的 API 调用次数来节省你的资金。

2、它可以减少你对 LLM 调用 API 的次数,来加速你的应用程序。

LangChain LLM 分为基于本地内存的缓存和服务器缓存,下面是两个例子:

本地缓存示例:

# 本地缓存

# 导入 langchain llm 组件
import langchain
from langchain.llms import OpenAI

# 计时器
import time
# 创建 llm
llm = OpenAI(model_name="text-davinci-002", n=2, best_of=2)

# 导入缓存组件
from langchain.cache import InMemoryCache

# 使用内存缓存
langchain.llm_cache = InMemoryCache()

# 记录开始时间
start_time = time.time()  

# 第一次调用不会走缓存,之后会从缓存获取数据
print(llm.predict("Tell me a joke"))

# 打印信息
end_time = time.time()  # 记录结束时间
elapsed_time = end_time - start_time  # 计算总时间
print(f"Predict method took {elapsed_time:.4f} seconds to execute.")

服务器缓存示例:

# 使用 SQLite 数据库缓存
# We can do the same thing with a SQLite cache
from langchain.cache import SQLiteCache
langchain.llm_cache = SQLiteCache(database_path=".langchain.db")


start_time = time.time()  # 记录开始时间
# The first time, it is not yet in cache, so it should take longer
print(llm.predict("用中文讲个笑话"))
end_time = time.time()  # 记录结束时间
elapsed_time = end_time - start_time  # 计算总时间
print(f"Predict method took {elapsed_time:.4f} seconds to execute.")

自定义大语言模型

上面我们已经看到了LangChain直接调用OpenAI接口的示例,下面我们来介绍一下我们如果有自己的大语言模型,该如何接入LangChain。

ChatGLM是清华大学团队推出的平民大模型,使用RTX3090单卡即可部署,代码库开源,可以作为目前大语言模型的平替。

我们使用LLMs模块封装ChatGLM,请求我们的模型服务,主要重构两个函数:

  • _call:模型调用的主要逻辑,输入用户字符串,输出模型生成的字符串;
  • _identifying_params:返回模型的描述信息,通常返回一个字典,字典中包括模型的主要参数;

下面是一个例子:

import time
import logging
import requests
from typing import Optional, List, Dict, Mapping, Any

import langchain
from langchain.llms.base import LLM
from langchain.cache import InMemoryCache

logging.basicConfig(level=logging.INFO)
# 启动llm的缓存
langchain.llm_cache = InMemoryCache()

# 继承自 LLM 的 CustomLLM 类
class ChatGLM(LLM):
  
    # 模型服务url
    url = "http://127.0.0.1:8595/chat"

    # 一个属性装饰器,用于获取 _llm_type 的值
    @property
    def _llm_type(self) -> str:
        return "chatglm"

    # 定义一个用户查询结构
    def _construct_query(self, prompt: str) -> Dict:
        """构造请求体
        """
        query = {
            "human_input": prompt
        }
        return query

    # 请求大语言模型
    @classmethod
    def _post(cls, url: str,
        query: Dict) -> Any:
        """POST请求
        """
        _headers = {"Content_Type": "application/json"}
        with requests.session() as sess:
            resp = sess.post(url, 
                json=query, 
                headers=_headers, 
                timeout=60)
        return resp

  
   # _call 方法用于处理某些操作,下面是处理用户输入
    def _call(self, prompt: str, 
        stop: Optional[List[str]] = None) -> str:
        """_call
        """
        # construct query
        query = self._construct_query(prompt=prompt)

        # post
        resp = self._post(url=self.url,
            query=query)
  
        if resp.status_code == 200:
            resp_json = resp.json()
            predictions = resp_json["response"]
            return predictions
        else:
            return "请求模型" 
  
    # 属性装饰器,用于获取 _identifying_params 的值
    @property
    def _identifying_params(self) -> Mapping[str, Any]:
        """Get the identifying parameters.
        """
        _param_dict = {
            "url": self.url
        }
        return _param_dict

if __name__ == "__main__":
    llm = ChatGLM()
    while True:
        human_input = input("Human: ")

        begin_time = time.time() * 1000
        # 请求模型
        response = llm(human_input, stop=["you"])
        end_time = time.time() * 1000
        used_time = round(end_time - begin_time, 3)
        logging.info(f"chatGLM process time: {used_time}ms")

        print(f"ChatGLM: {response}")

其他功能和扩展
  1. LLM序列化: LangChain提供了一个方便的方法,用于将LLM的配置序列化为JSON字符串,以便将其保存到磁盘上的文件中。

  2. 流式处理响应: 某些LLM提供流式处理响应。这意味着,你可以在响应可用时立即开始处理它,而不是等待整个响应返回。 在生成响应时向用户显示响应,或在生成响应时处理响应时可以使用此功能。

  3. 跟踪token使用情况: 通过langchain.callbacksget_openai_callback,可以获取你的问答使用tokens的数量。此外, 使用get_openai_callback还可以打印出具体的调用链路信息。

  4. FakeListLLM: 可以用于测试的假LLM对象。

输出解释器

output_parsers: 处理LLM输出的文本。你可以用它来格式化输出内容,例如:

  • LLM输出英文答案,我们可以用 output_parsers 将其转换成中文。
  • 输出内容包含敏感信息,我们可以将其过滤后输出
  • 需要提取LLM回答的内容,做进一步处理时,我们可以用 output_parsers 提取出想要的内容。

下面是一个示例:

#这段代码的主要目的是使用一个预训练的语言模型从OpenAI来生成并验证一个笑话。
# 导入必要的模块和类
from langchain.prompts import PromptTemplate, ChatPromptTemplate, HumanMessagePromptTemplate
from langchain.llms import OpenAI
from langchain.chat_models import ChatOpenAI
from langchain.output_parsers import PydanticOutputParser
from pydantic import BaseModel, Field, validator
from typing import List

# 定义模型名称和温度(影响模型的随机性)
model_name = 'text-davinci-003'
temperature = 0.0

# 初始化OpenAI模型
model = OpenAI(model_name=model_name, temperature=temperature)

# 定义想要的数据结构,这里是一个笑话的结构,包含设置和冷笑话
class Joke(BaseModel):
    setup: str = Field(description="question to set up a joke")  # 笑话的设置部分
    punchline: str = Field(description="answer to resolve the joke")  # 笑话的冷笑话部分

    # 使用Pydantic添加自定义验证逻辑,确保设置部分以问号结束
    @validator('setup')
    def question_ends_with_question_mark(cls, field):
        if field[-1] != '?':
            raise ValueError("Badly formed question!")
        return field

# 设置一个解析器,并将指令注入到提示模板中
parser = PydanticOutputParser(pydantic_object=Joke)

# 定义提示模板
prompt = PromptTemplate(
    template="Answer the user query.\n{format_instructions}\n{query}\n",
    input_variables=["query"],
    partial_variables={"format_instructions": parser.get_format_instructions()}
)

# 定义一个查询,目的是提示语言模型填充上述数据结构
joke_query = "给我用中文讲个笑话."

# 格式化提示
_input = prompt.format_prompt(query=joke_query)

# 使用模型生成输出
output = model(_input.to_string())

# 使用解析器解析输出
parser.parse(output)


文档加载器:检索增强生成 (RAG)

当你开发的LLM应用需要根据用户特定的数据做交互,而这些数据又不存在于LLM模型的训练集时,可以使用LangChain的文档加载器(document_loaders), 将一段固定内容,或者csv、pdf等文件在执行生成传递给LLM,让LLM对文档进行分析,实现简单的文档交互功能。

下面是一段加载文档、分析文档、文档拆分,最后接入LLM,让LLM做文档评估的代码:

# 初始化导入,导入嵌入、存储和检索模块
from langchain.embeddings.openai import OpenAIEmbeddings
from langchain.vectorstores import FAISS
from langchain.chains import RetrievalQA

# 模型和文档加载器
from langchain import OpenAI
from langchain.document_loaders import TextLoader

# 文档评估
from langchain.evaluation.qa import QAEvalChain

# LLM使用gpt-3.5-turbo-16k,model_name='gpt-3.5-turbo'
llm = OpenAI(temperature=0, openai_api_key=openai_api_key)

# 加载一份文档
loader = TextLoader('data/falcon.txt', encoding="utf-8")
doc = loader.load()

# 输出文档分析的结果
print(f"You have {len(doc)} document")
print(f"You have {len(doc[0].page_content)} characters in that document")

# 对文档进行拆分,并获取拆分后的docs
text_splitter = RecursiveCharacterTextSplitter(chunk_size=800, chunk_overlap=400)
docs = text_splitter.split_documents(doc)

# 获取字符总数,以便稍后查看平均值
num_total_characters = sum([len(x.page_content) for x in docs])

# 对docs做分析、打印
print(f"Now you have {len(docs)} documents that have an average of {num_total_characters / len(docs):,.0f} characters (smaller pieces)")

# 创建嵌入模块(embeddings)和文档库,用于检索
embeddings = OpenAIEmbeddings(openai_api_key=openai_api_key)
docsearch = FAISS.from_documents(docs, embeddings)

# 制作检索链
chain = RetrievalQA.from_chain_type(llm=llm, chain_type="stuff", retriever=docsearch.as_retriever(), input_key="question")

# 最后,我们向LLM输入问题和回答,让LLM根据文档内容做评估,LLM会将我的回答(answer)与LLM的结果(result)进行比较。
question_answers = [
    {'question': "Falcon是哪个国家研发的", 'answer': '阿拉伯联合酋长国'},
    {'question': "爱丁堡大学博士生符尧觉得Falcon不会比LLaMA好", 'answer': '是的'}
]

# 使用chain.apply加载question_answers
predictions = chain.apply(question_answers)

# 输出结果
predictions

#
# 下面是predictions的输出:
#
# [{'question': 'Falcon是哪个国家研发的',
#  'answer': '阿拉伯联合酋长国',
#  'result': ' Falcon是阿联酋阿布扎比的技术创新研究所(TII)开发的。'},
# {'question': '爱丁堡大学博士生符尧觉得Falcon不会比LLaMA 好',
#  'answer': '是的',
#  'result': ' 是的,爱丁堡大学博士生符尧在推特上表示:「Falcon真的比LLaMA好吗?简而言之:可能不会。」'}]
#
# result就是LLM根据我们的文档给出的评估结果。
#

# 最后启动评估链
eval_chain = QAEvalChain.from_llm(llm)

# 我们让LLM将我的真实答案(answer)与LLM的结果(result)进行比较, 让LLM自我评分。
graded_outputs = eval_chain.evaluate(question_answers,
                                     predictions,
                                     question_key="question",
                                     prediction_key="result",
                                     answer_key='answer')

# 查看结果                       
graded_outputs

#
# 下面是graded_outputs的结果
#
# [{'text': ' CORRECT'}, {'text': ' CORRECT'}]
#


向量数据库

img_14.png

这张图是向量数据库的交互逻辑,LangChain可以对接不同的向量数据库产品,让向量数据库负责数据的检索,对接LLM模型,给出更加快速精确的答案。

在上面的document_loaders代码中,有一段from langchain.vectorstores import FAISS代码,这里的FAISS就是一种向量数据库。

那么我们应该如何使用向量数据库呢?以Chromedb为例,我们需要做如下几件事:

  1. 准备环境: 向量数据库也是数据库,它需要单独安装。pip install chromadb

  2. 准备本地数据: Chromedb支持doc、txt、pdf等格式的数据。

  3. 将本地数据切片、向量化,然后入库存储: 数据切片工具例如我们上面提到的RecursiveCharacterTextSplitter,向量化工具有很多,列入OpenAI的text-embedding-ada-002等。当然,不管是切片工具还是向量化工具,类型是很多种的,需要根据自身需要场景来使用。

  4. 配置LangChain、LLM和Chromedb,就可以使用关键字对定制数据进行检索、提问了。

下面是一段使用LangChain调用Chromedb的示例:

import argparse
import os

from langchain import PromptTemplate
from langchain.chains import RetrievalQA
from langchain.llms import OpenAI, openai
from dotenv import load_dotenv
from langchain.embeddings import HuggingFaceEmbeddings, OpenAIEmbeddings
from langchain.vectorstores import Chroma

# 这里我们使用LLM是ChatGLM
from ChatGLM import ChatGLM

# 加载向量数据库配置文件
load_dotenv("config.env")
embeddings_model_name = os.environ.get("EMBEDDINGS_MODEL_NAME")
persist_directory = os.environ.get('PERSIST_DIRECTORY')
target_source_chunks = int(os.environ.get('TARGET_SOURCE_CHUNKS', 4))
# openai.api_key = os.getenv("OPENAI_API_KEY")
from constants import CHROMA_SETTINGS

if __name__ == '__main__':

    # 嵌入向量(embeddings)模型
    embeddings = HuggingFaceEmbeddings(model_name=embeddings_model_name)
    # 向量数据库
    db = Chroma(persist_directory=persist_directory, embedding_function=embeddings, client_settings=CHROMA_SETTINGS)
    retriever = db.as_retriever(search_kwargs={"k": target_source_chunks})

    # llm = OpenAI(model_name="text-ada-001", n=2, best_of=2)
    llm = ChatGLM()

    # 提示模板
    prompt_template = """基于以下已知信息,简洁和专业的来回答用户的问题。
    如果无法从中得到答案,请说 "根据已知信息无法回答该问题" 或 "没有提供足够的相关信息",不允许在答案中添加编造成分,答案请使用中文。
    已知内容:
    {context}
    问题:
    {question}"""

    promptA = PromptTemplate(template=prompt_template, input_variables=["context", "question"])
    chain_type_kwargs = {"prompt": promptA}
    
    # 使用RetrievalQA(检索增强)
    qa = RetrievalQA.from_chain_type(llm=llm, retriever=retriever, chain_type="stuff",
                                     chain_type_kwargs=chain_type_kwargs, return_source_documents=True)
    
    # 交互,输入问题给出答案
    while True:
        query = input("\n请输入问题: ")
        if query == "exit":
            break

        res = qa(query)
        answer, docs = res['result'], res['source_documents']

        print("\n\n> 问题:")
        print(query)
        print("\n> 回答:")
        print(answer)

        for document in docs:
            print("\n> " + document.metadata["source"] + ":")


Agent

img_18.png

设想这么一种情况,你从朋友那里听说最近有一部叫做《奥本海默》的电影很火,你被吸引力了想去了解一下这部电影,接下来你拿出手机点开搜索引擎搜索“奥本海默” 几个关键字,你得到了很多信息,这些信息最终帮助你决定是否去电影院观看这部电影。

现在我们格局大一点,可以将上面的场景描述为:在人类从事一项需要多个步骤的任务时,步骤和步骤之间,或者说动作和动作之间,往往会有一个推理过程。

LLM ReAct 在 LangChain 中的实践就是 Agent。

LangChain Agent 可以穿插到 LangChain 的执行流程中,运行大体流程: 1用户给出一个任务(Prompt) -> 2思考(Thought) -> 3行动(Action) -> 4观察(Observation), 然后循环执行上述 2-4 的流程,直到大模型认为找到最终答案为止。

我们在文章的一开始将" LangChain 能做什么?" 时就说过: “当收到 LLM 模型发出的指令,就执行相应的工具以获取执行结果,再通过 Prompt 向模型请求,直到模型没有下一步指令则返回结果。” 这个功能就是通过Agent实现。

Agent 的使用

在 LangChain 中,使用 Agent 之前需要定义好工具(BaseTool),并添加描述(description)告知大模型在什么情况下来使用这个工具。

基于我们一开始描述的场景,我们来实现这么一个功能:在用户搜索电影相关问题时,让LLM调用搜索工具检索信息,然后反馈给用户答案。 下面是代码示例:

定义Agent工具
# 定义Agent工具
from langchain.tools import BaseTool, DuckDuckGoSearchRun

# 搜索工具
class SearchTool(BaseTool):
    name = "Search"
    # 告诉LLM在什么情况下使用这个工具
    description = "当问电影相关问题时候,使用这个工具"
    return_direct = False  # 直接返回结果

    def _run(self, query: str) -> str:
        print("\n正在调用搜索引擎执行查询: " + query)
        # LangChain 内置搜索引擎
        search = DuckDuckGoSearchRun()
        return search.run(query)
 


定义结果解析类
from typing import Dict, Union, Any, List

from langchain.output_parsers.json import parse_json_markdown
from langchain.agents.conversational_chat.prompt import FORMAT_INSTRUCTIONS
from langchain.agents import AgentExecutor, AgentOutputParser
from langchain.schema import AgentAction, AgentFinish

# 自定义解析类
class CustomOutputParser(AgentOutputParser):

    def get_format_instructions(self) -> str:
        return FORMAT_INSTRUCTIONS

    def parse(self, text: str) -> Union[AgentAction, AgentFinish]:
        print(text)
        cleaned_output = text.strip()
        # 定义匹配正则
        action_pattern = r'"action":\s*"([^"]*)"'
        action_input_pattern = r'"action_input":\s*"([^"]*)"'
        # 提取出匹配到的action值
        action = re.search(action_pattern, cleaned_output)
        action_input = re.search(action_input_pattern, cleaned_output)
        if action:
            action_value = action.group(1)
        if action_input:
            action_input_value = action_input.group(1)
        
        # 如果遇到'Final Answer',则判断为本次提问的最终答案了
        if action_value and action_input_value:
            if action_value == "Final Answer":
                return AgentFinish({"output": action_input_value}, text)
            else:
                return AgentAction(action_value, action_input_value, text)

        # 如果声明的正则未匹配到,则用json格式进行匹配
        response = parse_json_markdown(text)
        
        action_value = response["action"]
        action_input_value = response["action_input"]
        if action_value == "Final Answer":
            return AgentFinish({"output": action_input_value}, text)
        else:
            return AgentAction(action_value, action_input_value, text)
output_parser = CustomOutputParser()



初始化Agent
from langchain.memory import ConversationBufferMemory
from langchain.agents.conversational_chat.base import ConversationalChatAgent 
from langchain.agents import AgentExecutor, AgentOutputParser

SYSTEM_MESSAGE_PREFIX = """尽可能用中文回答以下问题。您可以使用以下工具"""

# 初始化大模型实例,可以是本地部署的,也可是是ChatGPT
# llm = ChatGLM(endpoint_url="http://你本地的实例地址")
llm = ChatOpenAI(openai_api_key="sk-xxx", model_name='gpt-3.5-turbo', request_timeout=60)
# 初始化工具
tools = [CalculatorTool(), SearchTool()]
# 初始化对话存储,保存上下文
memory = ConversationBufferMemory(memory_key="chat_history", return_messages=True)
# 配置agent
chat_agent = ConversationalChatAgent.from_llm_and_tools(
    system_message=SYSTEM_MESSAGE_PREFIX, # 指定提示词前缀
    llm=llm, tools=tools, memory=memory, 
    verbose=True, # 是否打印调试日志,方便查看每个环节执行情况
    output_parser=output_parser # 
)
agent = AgentExecutor.from_agent_and_tools(
    agent=chat_agent, tools=tools, memory=memory, verbose=True,
    max_iterations=3 # 设置大模型循环最大次数,防止无限循环
)


调用Agent
agent.run(prompt)


结果展示

img_17.png

如何学习AI大模型?

我在一线互联网企业工作十余年里,指导过不少同行后辈。帮助很多人得到了学习和成长。

我意识到有很多经验和知识值得分享给大家,也可以通过我们的能力和经验解答大家在人工智能学习中的很多困惑,所以在工作繁忙的情况下还是坚持各种整理和分享。但苦于知识传播途径有限,很多互联网行业朋友无法获得正确的资料得到学习提升,故此将并将重要的AI大模型资料包括AI大模型入门学习思维导图、精品AI大模型学习书籍手册、视频教程、实战学习等录播视频免费分享出来。

在这里插入图片描述

第一阶段: 从大模型系统设计入手,讲解大模型的主要方法;

第二阶段: 在通过大模型提示词工程从Prompts角度入手更好发挥模型的作用;

第三阶段: 大模型平台应用开发借助阿里云PAI平台构建电商领域虚拟试衣系统;

第四阶段: 大模型知识库应用开发以LangChain框架为例,构建物流行业咨询智能问答系统;

第五阶段: 大模型微调开发借助以大健康、新零售、新媒体领域构建适合当前领域大模型;

第六阶段: 以SD多模态大模型为主,搭建了文生图小程序案例;

第七阶段: 以大模型平台应用与开发为主,通过星火大模型,文心大模型等成熟大模型构建大模型行业应用。

在这里插入图片描述

👉学会后的收获:👈
• 基于大模型全栈工程实现(前端、后端、产品经理、设计、数据分析等),通过这门课可获得不同能力;

• 能够利用大模型解决相关实际项目需求: 大数据时代,越来越多的企业和机构需要处理海量数据,利用大模型技术可以更好地处理这些数据,提高数据分析和决策的准确性。因此,掌握大模型应用开发技能,可以让程序员更好地应对实际项目需求;

• 基于大模型和企业数据AI应用开发,实现大模型理论、掌握GPU算力、硬件、LangChain开发框架和项目实战技能, 学会Fine-tuning垂直训练大模型(数据准备、数据蒸馏、大模型部署)一站式掌握;

• 能够完成时下热门大模型垂直领域模型训练能力,提高程序员的编码能力: 大模型应用开发需要掌握机器学习算法、深度学习框架等技术,这些技术的掌握可以提高程序员的编码能力和分析能力,让程序员更加熟练地编写高质量的代码。

在这里插入图片描述

1.AI大模型学习路线图
2.100套AI大模型商业化落地方案
3.100集大模型视频教程
4.200本大模型PDF书籍
5.LLM面试题合集
6.AI产品经理资源合集

👉获取方式:
😝有需要的小伙伴,可以保存图片到wx扫描二v码免费领取【保证100%免费】🆓

在这里插入图片描述

;