跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI 生图工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
PythonAI算法

LLM 局限性解析与 LangChain 框架初探

探讨了大型语言模型(LLM)面临的上下文窗口限制、企业定制需求等局限性,并介绍了 LoRA 微调技术。重点讲解了 LangChain 框架的核心组件,包括提示词模板、LLM 调用、输出解释器、文档加载器(RAG)、向量数据库及 Agent 智能体。通过代码示例展示了如何利用 LangChain 构建检索增强生成应用和自动化任务流程,帮助开发者克服 LLM 原生缺陷,实现更灵活的企业级 AI 应用开发。

Stephaine Walsh发布于 2025/2/6更新于 2026/9/1054 浏览
LLM 局限性解析与 LangChain 框架初探

前言

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

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

LLM 的局限性

LLM Tokens 限制

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

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

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

LoRA

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

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

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

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

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

LangChain

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

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

LangChain 是什么?

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

LangChain 能做什么?

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

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

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

Prompts 提示词

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

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
提示词的其他扩展

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

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

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

最后,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}")
其他功能和扩展

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

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

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

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'
# TODO: 请在此处设置您的 OpenAI API Key
openai_api_key = "your_api_key_here"
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

# 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'}]
向量数据库

这张图是向量数据库的交互逻辑,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

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

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

这个概念是 Shunyu Yao 等人 2022 年提出的,ReAct – 也就是推理和行动,结合 LLMs 模型,我们可以称之为 LLM ReAct 范式,也就是:在大语言模型中结合推理和行动。

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 工具
import re
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)
结果展示

结语与展望

在当前大规模预训练语言模型(LLM)的时代,我们见证了自然语言处理(NLP)技术的飞速发展,为我们带来了前所未有的智能体验。然而,正如我们在本文中所讨论的,LLM 也面临着一些局限性,例如上下文窗口的 tokens 限制、企业用户的定制需求等挑战。

从 GPT-3.5 到 GPT-4 的不断升级,我们看到了 LLM 模型在上下文窗口 tokens 数量上的显著提升,为更复杂的任务提供了更强大的能力。然而,对于企业用户而言,仍然存在一些不容忽视的问题,包括数据安全、领域特定性、解释性等方面的考虑。

在解决这些问题的过程中,我们介绍了 LoRA(大型语言模型的低秩自适应)的概念,以及更轻量级的解决方案 LlamaIndex 和 LangChain。特别是 LangChain,作为一个面向开发者的应用框架,为我们提供了更灵活、全面的工具和组件,使我们能够更轻松地构建基于大语言模型和聊天模型的应用程序。

通过 LangChain,开发者能够更方便地管理和扩展语言模型的交互,将多个组件链接在一起,并通过丰富的资源如 API 和数据库提供更广泛的功能。LangChain 的出现为克服 LLM 的局限性,满足企业用户的定制需求提供了一种创新性的解决方案。

未来,随着技术的不断演进和创新,我们对自然语言处理的期望也将变得更加广阔。LangChain 等工具的应用无疑将在这一领域发挥越来越重要的作用。开发者应关注社区动态,持续学习新的最佳实践,以构建更高效、安全、可控的 AI 应用系统。

目录

  1. 前言
  2. LLM 的局限性
  3. LLM Tokens 限制
  4. 企业用户的定制需求
  5. LoRA
  6. LangChain
  7. LangChain 是什么?
  8. LangChain 能做什么?
  9. Prompts 提示词
  10. PromptTemplate 提示词模板
  11. 用于为字符串提示创建 PromptTemplate 模板。
  12. 默认情况下,PromptTemplate 使用 Python 的 str.format 语法进行模板化;但是可以使用其他模板语法(例如,jinja2)
  13. ChatPromptTemplate 对话提示模板
  14. ChatPromptTemplate.from_messages 接受各种消息表示形式。
  15. 这里接收了一个动态的系统名称,以及用户输入的对话。
  16. 提示词的其他扩展
  17. LLM
  18. 直接使用 LLM 模型
  19. 设置代理
  20. 创建 LLM 模型
  21. 可以直接调用
  22. 批量调用 15 次
  23. 获取第一次结果
  24. 异步调用 LLM
  25. 导入所需的模块
  26. 定义一个串行(同步)方式生成文本的函数
  27. 定义一个异步生成文本的函数
  28. 定义一个并发(异步)方式生成文本的函数
  29. 记录当前时间点
  30. 使用异步方式并发执行生成文本的任务
  31. 如果在 Jupyter 以外运行此代码,使用 asyncio.run(generate_concurrently())
  32. 计算并发执行所花费的时间
  33. LLM 缓存
  34. 本地缓存
  35. 导入 langchain llm 组件
  36. 计时器
  37. 创建 llm
  38. 导入缓存组件
  39. 使用内存缓存
  40. 记录开始时间
  41. 第一次调用不会走缓存,之后会从缓存获取数据
  42. 打印信息
  43. 使用 SQLite 数据库缓存
  44. We can do the same thing with a SQLite cache
  45. The first time, it is not yet in cache, so it should take longer
  46. 自定义大语言模型
  47. 启动 llm 的缓存
  48. 继承自 LLM 的 CustomLLM 类
  49. _call 方法用于处理某些操作,下面是处理用户输入
  50. 其他功能和扩展
  51. 输出解释器
  52. 导入必要的模块和类
  53. 定义模型名称和温度(影响模型的随机性)
  54. 初始化 OpenAI 模型
  55. 定义想要的数据结构,这里是一个笑话的结构,包含设置和冷笑话
  56. 设置一个解析器,并将指令注入到提示模板中
  57. 定义提示模板
  58. 定义一个查询,目的是提示语言模型填充上述数据结构
  59. 格式化提示
  60. 使用模型生成输出
  61. 使用解析器解析输出
  62. 文档加载器:检索增强生成(RAG)
  63. 初始化导入,导入嵌入、存储和检索模块
  64. 模型和文档加载器
  65. 文档评估
  66. LLM 使用 gpt-3.5-turbo-16k,model_name='gpt-3.5-turbo'
  67. TODO: 请在此处设置您的 OpenAI API Key
  68. 加载一份文档
  69. 输出文档分析的结果
  70. 对文档进行拆分,并获取拆分后的 docs
  71. 获取字符总数,以便稍后查看平均值
  72. 对 docs 做分析、打印
  73. 创建嵌入模块(embeddings)和文档库,用于检索
  74. 制作检索链
  75. 最后,我们向 LLM 输入问题和回答,让 LLM 根据文档内容做评估,LLM 会将我的回答(answer)与 LLM 的结果(result)进行比较。
  76. 使用 chain.apply 加载 question_answers
  77. 输出结果
  78. result 就是 LLM 根据我们的文档给出的评估结果。
  79. 最后启动评估链
  80. 我们让 LLM 将我的真实答案(answer)与 LLM 的结果(result)进行比较,让 LLM 自我评分。
  81. 查看结果
  82. graded_outputs 的结果
  83. [{'text': ' CORRECT'}, {'text': ' CORRECT'}]
  84. 向量数据库
  85. 这里我们使用 LLM 是 ChatGLM
  86. 加载向量数据库配置文件
  87. openai.apikey = os.getenv("OPENAIAPI_KEY")
  88. Agent
  89. Agent 的使用
  90. 定义 Agent 工具
  91. 定义 Agent 工具
  92. 搜索工具
  93. 定义结果解析类
  94. 自定义解析类
  95. 初始化 Agent
  96. 初始化大模型实例,可以是本地部署的,也可是是 ChatGPT
  97. llm = ChatGLM(endpoint_url="http://你本地的实例地址")
  98. 初始化工具
  99. 初始化对话存储,保存上下文
  100. 配置 agent
  101. 调用 Agent
  102. 结果展示
  103. 结语与展望

更多推荐文章

查看全部
  • Python 基础语法与面向对象编程入门指南
  • 数据结构:堆的原理与实现
  • VirtualBox 创建虚拟机并安装 Ubuntu 系统指南
  • 大模型训练核心概念:预训练、后训练与持续训练详解
  • Face Analysis WebUI 使用 Gradio share 开启远程临时链接调试
  • 网络安全职业发展路径与技能规划指南
  • Spatial Joy 2025 全球 AR&AI 开发大赛参赛指南与资源解析
  • Python 入门:30 天零基础学习规划(每日 1 小时)
  • 深入理解 Sentinel:分布式系统的流量守卫者
  • 第二届人工智能、虚拟现实与交互设计国际学术会议(AIVRID 2026)
  • 用三省六部做 AI Agent 协作:Edict 架构拆解
  • 基于 WebUploader 与 PHP 的飞行日志分片上传可靠性方案
  • C++ 基于 Boost 的正倒排索引搜索引擎数据清洗详解
  • AIGC 音乐制作全流程指南:从旋律生成到人声合成
  • 双指针算法实战:快乐数与盛最多水的容器
  • AI 绘画提示词工程:从基础原理到高效实践
  • Anaconda 安装与 Python 环境配置详解
  • 程序员面试实战:HR 沟通技巧与核心技术考点解析
  • Python 学习路线与核心知识点详解
  • Home Assistant 界面美化:用 lovelace-soft-ui 做出更顺眼的控制面板

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • RSA密钥对生成器

    生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online

  • Mermaid 预览与可视化编辑

    基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online

  • 随机西班牙地址生成器

    随机生成西班牙地址(支持马德里、加泰罗尼亚、安达卢西亚、瓦伦西亚筛选),支持数量快捷选择、显示全部与下载。 在线工具,随机西班牙地址生成器在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • curl 转代码

    解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online