Cointime

扫码下载App
iOS & Android

解密LangChain:构建强大代理的秘密武器

作者:Kevin Hu. 编译:Cointime.com QDD

1. 概述

最近,我研究了 LangChain 项目,我对它如何在如此短的时间内成为一个功能强大且成熟的项目感到惊讶。它提供了许多创建自己的基于 LLM 的项目所需的基本工具,只需几行代码即可抽象出繁琐的步骤。

我喜欢该项目的发展方向,开发团队一直积极主动地将最新的 LLM 功能的新想法纳入项目中。

了解这个新项目的过程并不是一帆风顺的。它对于代码组织有自己的观点,对于如何为自己的项目进行黑客攻击,超出了教程的范围,可能不太直观。许多教程都解释了如何使用 LangChain 创建一个小应用程序,但并没有涵盖如何直观地理解抽象和设计选择。

因此,我主动记录了我在这个过程中的个人认知过程。通过这样做,我既可以澄清自己的理解,同时也可以为那些对通过黑客攻击 LangChain 获得乐趣和利益感兴趣的人提供帮助。

这篇博客文章将致力于对所有概念的整体理解。我发现首先理解与 LLM 直接交互的概念,尤其是核心 API 接口,非常有帮助。一旦你了解了所有 LangChain 的抽象概念,黑客攻击和扩展自己的实现就更加直观了。

我将介绍关于 Chain 和 Agents 的基本概念:

l   Chain(链)

l   Tool(工具)

l   Template(模板)

l   Agent(代理)

l   AgentExecutor(代理执行器)

在这篇博客中我不会提及的内容,将在另一篇博客或讨论中涉及:

l   嵌入

l   内存

l   文档加载器

l   向量存储

选择正确的概念,编程将自然而然地从设计中流淌;

选择错误的概念,编程将成为一系列令人讨厌的意外。

——麻省理工学院教授丹尼尔·杰克逊(Daniel Jackson)在他的书《软件抽象》中关于软件抽象的观点

2. 概念

2.1 链(Chains)

链是组织操作、扩展 LLM 功能和集成不同链操作的基本方式。你可以将它想象为一组"链"在一起的操作。

基本 Chain 类的接口如下所示:

class Chain:

    @property

    def input_keys(self) -> List[str]:

        ...

    @property

    def output_keys(self) -> List[str]:

        ...

    @abstractmethod

    def _call(self, input: Dict[str, Any], ...) -> Dict[str, str]:

        ...

    def __call__(self, input: Union[Dict[str, Any], Any]) -> Dict[str, Any]:

    # which is a wrapper around _call() and preprocesses input args

        ...

    def run(self, *kargs, **kwargs) -> str:

    # which is a wrapper around __call__()

        ...

一旦你理解了这个接口,扩展 Chain 就非常清晰了。你需要定义:

l   输入参数

l   输出参数

l   在调用 Chain 时要执行的操作,通过定义抽象的 _call() 方法(或者 _acall() 用于异步调用,但我暂时不涉及这些)。

call() 和 run() 方法实际上只是对处理输入参数的核心方法的包装。

有时可能会混淆同一个 Chain 有很多不同的调用方式。但是请思考:

l   _call() 是你作为开发者需要定义的基本功能。它有好的、经过预处理的输入参数。

l   call() 或 run() 是项目用户的接口,接受更灵活的输入。

通过这个接口,你可以通过在一个链中将它们链接在一起来扩展功能。前一个链的输出将成为下一个链的输入键。

这里的示例中查看更多内容

2.1.1 LLMChain

LLMChain 是一种特殊类型的链,它包装了底层的 LLM 生成引擎。它是最常用的链,用于直接使用和可扩展性。你可以扩展它以实现任何特殊功能,甚至可以将它们"链"起来以执行与 LLM 相关的一系列操作。

LLMChain 的接口非常简单。详见源代码

class LLMChain:

    prompt: BasePromptTemplate

    llm: BaseLanguageModel

    output_key: str

LLMChain 通过定义以下内容来扩展原始 Chain:

l   输入:与文本模板所需的相同输入。

l   输出:"text" 字段,即 LLM 生成的输出结果。

你可以直接调用它,或者用它来构建更专业的 Chains。

chain = LLMChain(llm=llm, template=template)

chain.run('LLM prompt')

详见"模板"部分了解 prompt 模板。

2.1.2 扩展和连接 Chains

你可以扩展 Chain 来完成任何需要输入并产生输出的任务。可以将其视为一个任务,你可以将其用于例如文本预处理,甚至解析等任何你认为在实现整个任务流程时有用的任务。

一个 Chain 并不一定需要涉及与 LLM 的交互。当你实现整个任务流水线时,它可以是任何你认为有用的任务。

在"通用链"部分中查看示例:

在示例中,TransformChain 只是执行正则表达式转换以删除空格。你可以与其他 Chains 结合使用,使用 SequentialChain 将它们链接在一起,创建一个转换 -> 重写的转换流水线。

sequential_chain = SequentialChain(

   chains=[clean_extra_spaces_chain, style_paraphrase_chain],

   input_variables=['text', 'style'],

   output_variables=['final_output'])

链的概念

一旦你理解了 Chains,你就可以在 LangChain 中构建强大的链流水线(因此得名)。有一些链可以:

l   计算和运行数学运算。

l   对文本进行摘要。

l   将文本翻译成其他语言。

l   提供产品名称和口号。

l   ...

Github 上查看更多链的示例

2.2 模板(Template)

当我第一次接触 LangChain 时,我对 prompt 和模板的概念感到困惑。但实际上,这个想法非常简单。它与任何模板的想法相同:你定义一个模板文本,并用文本变量插值。

prompt 模板最常见的用例是创建 LLM 的输入大纲,你可以通过变量自定义输入。就是这样。就是这么简单。

Template 的一个常见用例是,如上所述,格式化最终的 LLM prompt。在 Agents 中非常有用,因为你可以对 LLM 进行多个查询,并且你可以在每个迭代中使用不同的中间步骤来定义 prompt。

让我们来看看代理的概念。

2.3 代理(Agent)

LLM 最强大的应用之一是工具使用。代理提供了一种选择工具箱以解决更开放和复杂的问题的抽象方法。

根据 LangChain 的官方文档:

有些应用程序需要基于用户输入灵活地调用 LLM 和其他工具的链式调用。代理接口为这样的应用程序提供了灵活性。代理可以使用一套工具,并根据用户输入决定使用哪些工具。代理可以使用多个工具,并将一个工具的输出作为下一个工具的输入。

详见:

l   https://python.langchain.com/docs/modules/agents/

l   https://archive.pinecone.io/learn/langchain-agents/

2.3.1 代理接口

class Agent:

    def plan(self):

        ...

    def aplan(self):

        ...

这就是代理的接口。

理解代理的第一步是摒弃复杂的工具使用等特性,仅关注接口。

代理是一种自动执行者,可以根据 LLM 输出的每个步骤制定"计划"。你可以添加更多功能,创建一个完整的、功能齐全的代理,可以为你执行操作,例如使用工具、构建提示模板、解析输出。

要创建自己的 LangChain 代理,你只需要关注制定计划(例如处理输入、创建提示、解析输出并返回输出)。

为了说明代理的接口,我创建了一个非常简单的虚拟代理的实现,它根据定义的工具执行动作恰好 3 次。

在这个示例中,计划是:使用给定的工具返回 3 次的 AgentAction,然后返回 AgentFinish。

class DummyAgent(BaseSingleActionAgent):

 # initiate other part of the code like input, output, etc. # ...

 tool: str = ''

 count: int = 3

 def plan(self,

    intermediate_steps: List[Tuple[AgentAction, str]],

    **kwargs: Any):

        if self.count <= 0:

            return AgentFinish({'output': 'Finished execution'},

                log='Action Finished: ')

    self.count -= 1

    return AgentAction(tool=self.tool,

        ool_input=kwargs['tool_input'],

        log='Agent Action: ')

(在 Gist 上查看代码片段。此外,我刚刚开始了一个小的副项目,专门研究代理。在 Github 上了解更多详情。)

2.3.2 工具(Tools)

工具是与其他环境交互的接口。该接口同样非常简单,具有 run 或异步的 arun。

工具可以是与 LLM 相关的任何外部操作,例如计算器、搜索引擎、SQL 执行、文档或数据加载器,或具有 API 的任何其他操作。它还可以是任何其他链!

它的接口同样简单。同样地,你只需要定义输入、输出和要运行的内容。

from langchain.tools.base import BaseTool

class ExampleTool(BaseTool):

    name = 'example'

    description = 'An example tool'

    def _run(self, query):

        return 'some run results'

    def _arun(self, query):

        return 'async run'

有时候这可能会让人困惑,因为它可以以不同的初始化方式使用:

# initializing by setting the name, description, and a callable functionmath_tool = Tool(

    name='Calculator',

    func=llm_math.run,

    description='Useful for when you need to answer questions about math.',

)

# or, initializing with a function call

Tool.from_function(

    name='Calculator',

    func=llm_math.run,

    description='Useful for when you need to answer questions about math',

)

但思想是相同的。记住,这只是通过设置名称、描述和 _run 步骤来调用函数创建工具的语法糖。

工具的函数可以是 API 调用(例如计算器、搜索、加载文本等),也可以是调用其他链。它非常灵活,你可以重用链甚至将代理作为工具函数。因此,在这种方式下,一个代理可以调用其他代理。

2.3.3 代理执行器(AgentExecutor)

为了了解代理是如何产生以及 LLM 任务执行的一些基本思想,可以参阅我之前在了解 LLM 推理方面发现有用的论文列表的另一篇博客

AgentExecutor 也是一个 Chain:它具有与 Chain 完全相同的简单接口:输入、输出和动作,即将与代理相关的一切内容包装在一起。

LangChain 库提供了许多语法糖来"初始化代理"。但记住,它不是返回一个代理,而是返回一个 AgentExecutor,它具有"Chain"的接口。

from langchain.agents import initialize_agent

zero_shot_agent = initialize_agent(

    agent="zero-shot-react-description",

    tools=tools,

    llm=llm,

    verbose=True,

    max_iterations=3

)

Agent 类抽象了代理行为的最关键部分:它如何根据输入和中间结果"计划"每个步骤,以及它如何决定采取什么行动,或者是否完成代理执行。

AgentExecutor 的 _call() 实现将所有这些内容封装在一起:

l   通过将参数传递给它来初始化代理。

l   读取代理的输出。

l   运行工具箱中的操作。

l   通过提供输出来完成执行。

l   其他基础设施代码,如超时、迭代限制、输出流等。

代理执行器的工作流程

这些繁琐的工作都在 AgentExecutor 中实现,这样我们就可以专注于有趣的部分,即实际的规划。

通常我们忽略这些繁琐的工作,只关注有趣的部分,例如创建一个根据给定工具执行任务的 ReAct 代理。

是的,AgentExecutor 是一个 Chain,因此它可以与其他 Chains 或作为其他代理的工具一起使用。

在上面提到的 Pinecone 教程中查看另一个示例:

# initializing by setting the name, description, and a callable function

math_tool = Tool(

    name='Calculator',

    func=llm_math.run,

    description='Useful for when you need to answer questions about math.',

)

llm_math 是一个 AgentExecutor 类,它包装了 "llm_math" 代理,它是一个 Chain,其 run() 接口是调用代理的函数。

清楚了吗?

LangChain 已经提供了丰富的代理库,可以执行一些有趣的工作,例如读取 CSV 数据、管理文件、调用 API 等。

请参阅这里

3. 将所有内容结合起来:ZeroshotAgent

一旦你理解了所有这些组件,你可以将它们组合在一起,创建自己的代理。

实现背后有两篇论文。我在之前的博客中也提到了它们:

l   MRKL Systems:一种将大型语言模型、外部知识源和离散推理相结合的模块化神经符号架构,描述了将 LLM 与外部工具结合起来的思想。

l   ReAct:在语言模型中协同推理和行动,描述了如何通过格式化提示使 LLM 与外部工具进行推理和思维链推理。

LangChain 实现了 ZeroShotAgent

1) 创建一个提示以指导工作流程,创建几个样本遵循以下模式:

l   问题:

l   思考:

l   动作:

l   动作输入:

l   观察结果:(使用工具,Thought + Action + Observation 循环可能发生 N 次)

l   最终答案:

2) 为代理创建工具

3) 解析来自 Thought 的 LLM 输出,例如要使用的工具,是否为最终答案。

4) 调用工具并创建"观察结果"。

5) 基于输出创建另一个提示,然后再次将其提供给 LLM。

6) 重复此过程直到获得最终答案。输出。

如果你认为这对你有帮助,我会继续探索并写下我对 LangChain 和 NLP + LLM 的其他发现。希望这对你的理解有所帮助!

评论

所有评论

推荐阅读

  • 跨境洗黑钱集团清洗8800万港元8人被捕

    香港警方商业罪案调查科于2023年11月锁定一个跨境洗黑钱集团,调查发现集团于2023年9月至2024年3月期间,招揽内地人到香港开设傀儡银行户口,透过不同类型骗案,如电话骗案、裸聊骗案、投资骗案、求职骗案等去诈骗受害人。受害人根据骗徒指示,将骗款存入犯罪集团控制的傀儡户口,之后集团会从傀儡户口以现金方式提取骗款,并到加密货币场外交易所(OTC)购买加密货币,同时又会在海外加密货币平台上以虚假身份开设户口,并存入由骗款所购买的加密货币,再转移至多个加密货币钱包,以清洗犯罪得益。 警方又指集团利用72个本地开立的银行傀儡户口,清洗超过8800万港元犯罪得益,其中670万港元是与48宗骗案有关。至昨日,警方于全港拘捕7男1女,年龄介乎26至51岁,涉嫌串谋洗黑钱,他们分别报称救生员、摄影师、电话程式员、销售员及无业,其中6人为骨干成员,2人为傀儡户口持有人。

  • 纽约Sharp Alpha Advisors筹集2500万美元早期软件公司基金

    纽约市的风险投资公司Sharp Alpha Advisors已经为其第二个基金筹集了2500万美元,该基金的主要投资领域是体育、游戏和娱乐行业的早期软件公司。该基金计划在15家初创公司中每家投资100万至200万美元,投资对象包括面向体育博彩、幻想体育、流媒体平台和视频游戏的技术公司。该基金最近投资了总部位于伦敦的技术初创公司C15 Studio,该公司运营和分发F1和One Championship的流媒体频道。Sharp Alpha基金会创始人Danzig声称,该基金会的投资者包括一家大型美国金融公司、美国职业体育俱乐部的所有者、家族办公室、基金和与体育、游戏和娱乐产业相关的上市公司。然而,Danzig拒绝透露任何具体的个人或公司名称。该基金是Fund I的后续基金,Fund I在2021年筹集了1000万美元,主要来自高净值人士和家族办公室。Sharp Alpha在第一期基金中进行了20项投资,平均交易规模约为25万美元。

  • Vitalik提出以太坊改进提案EIP-7706,旨在为calldata添加一个独立的gas类型

    以太坊创始人Vitalik Buterin提出一个新的以太坊改进提案(EIP-7706),旨在为calldata添加一个独立的gas类型,并将三种gas类型整合到一个协调的系统中。Buterin希望这一提案能够使关于“多维度gas”概念的讨论变得更加具体。

  • Coinbase:故障现已完全排除

    加密货币交易所Coinbase表示,故障现已完全排除。

  • 菲律宾央行批准Coins.ph试点菲律宾比索支持的稳定币PHPC

    菲律宾央行在监管沙盒下批准Coins.ph试点一种以菲律宾比索为后盾的稳定币。该试点将评估稳定币在“实际应用中”的效益。Coins.ph表示,菲律宾央行已批准其试点名为PHPC的菲律宾比索稳定币。该项目将在BSP的监管沙盒框架下进行,由Coins.ph在菲律宾银行账户中持有的现金和现金等价物支持。Coins.ph计划将稳定币整合到向菲律宾汇款的国家的汇款平台中。

  • Ripple提交寻求封存并修订部分SEC诉讼文件的动议

    5月14日消息,Ripple在5月13日的最后期限前提交了一项动议,要求对救济动议的简报和某些证据进行狭义的修订。该公司表示,其密封请求是合理的,并与法院在诉讼的简易判决阶段批准的密封请求一致。在周一提交动议之前,Ripple的律师与美国金融监管机构进行了磋商。美国SEC表示,其接受Ripple的一些请求,并可能对其他请求提出异议。 根据文件,Ripple试图编辑若公开披露可能对公司、无辜第三方和Ripple员工造成伤害的信息,寻求对包含其审计财务报表和相关文件进行狭义的修订。

  • 俄当局拟对在住宅公寓内运营的加密货币矿工处以高额罚款

    俄罗斯当局已提议对在住宅物业中运营的加密货币矿工嫌疑人处以巨额罚款。当局还可能考虑对《行政违法法典》(Code of Administrative Offenses)进行修订,对滥用电力者追究责任。

  • 5月14日午间要闻速递

    1.前SEC主席:Coinbase的"缺乏规范清晰性"论点是非常缺乏说服力的

  • TheoriqAI完成620万美元Super-Seed轮融资,Hack VC领投

    5月14日消息,模块化AI代理基础层TheoriqAI在X平台发文宣布完成620万美元Super-Seed轮融资,Hack VC领投,Foresight Ventures、HTX Ventures、Figment Capital、HASH CIB、Inception Capital、Antalpha Ventures、NewTribe Capital、Stateless Ventures、Bitscale Capital、Construct Ventures、Hypersphere、IOSG Ventures、LongHash Ventures、HashKey Capital、SNZ Holding、Chainlink等参投。

  • 比特币上每日蚀刻的新符文数量已降至250个以下

    过去六天,比特币上每日蚀刻的新符文数量已降至250个以下,周一蚀刻了157个符文,较4月底的峰值下降了99%。根据RUNES创建的Dune Analytics仪表板,在4月26日至30日期间,平均每天蚀刻14,700个新符文,其中4月26日蚀刻了创纪录的23,061个符文。 自Runes于4月20日推出以来,已向比特币矿工支付了总计450万美元的交易费,每天约为189美元。迄今为止,比特币上已刻有超过91,200个符文。