智能体开发-Langchain

573 阅读28分钟

LangChain 是一套专为 LLM 开发打造的开源框架,实现了 LLM 多种强大能力的利用,提供了 Chain、Agent、Tool 等多种封装工具,基于 LangChain 可以便捷开发应用程序,极大化发挥 LLM 潜能。目前,使用 LangChin 已经成为 LLM 开发的必备能力之一。

在这一部分,我们将对 LangChain 展开深入介绍,帮助学习者了解如何使用 LangChain,并基于 LangChain 开发完整的、具备强大能力的应用程序。通过学习本部分,您能够掌握如何使用 LangChain,打通大模型开发的快速通道,结合前面部分学习的基础能力,快速成为一名 LLM 开发者。

一 LangChain的诞生和发展

通过对LLM或大型语言模型给出提示(prompt),现在可以比以往更快地开发AI应用程序,但是一个应用程序可能需要进行多轮提示以及解析输出。

在此过程有很多胶水代码需要编写,基于此需求,哈里森·蔡斯 (Harrison Chase) 创建了LangChain,使开发过程变得更加丝滑。

LangChain开源社区快速发展,贡献者已达数百人,正以惊人的速度更新代码和功能。

二,课程基本内容

LangChain是用于构建大模型应用程序的开源框架,有Python和JavaScript两个不同版本的包(本课程是使用Java实现的版本)。LangChain基于模块化组合,有许多单独的组件,可以一起使用或单独使用。此外LangChain还拥有很多应用案例,帮助我们了解如何将这些模块化组件以链式方式组合,以形成更多端到端的应用程序 。

在本课程中,我们将介绍LandChain的常见组件。具体而言我们会讨论一下几个方面

模型(Models): 集成各种语言模型与向量模型。 提示(Prompts): 使模型执行操作的方式 索引(Indexes): 获取数据的方式,可以与模型结合使用 链式(Chains): 端到端功能实现 代理(Agents): 使用模型作为推理引擎

通过学习使用这些组件构建链式应用,你将可以快速上手 LangChain,开发出功能强大的语言模型程序。让我们开始探索LangChain的魅力吧!

第二章 模型,提示和输出解释器

本章我们将简要介绍关于 LLM 开发的一些重要概念:模型、提示与解释器。如果您已完整学习过前面两个部分的内容,对这三个概念不会陌生。但是,在 LangChain 的定义中,对这三个概念的定义与使用又与之前有着细微的差别。我们仍然推荐您认真阅读本章,以进一步深入了解 LLM 开发。 同时,如果您直接学习本部分的话,本章内容更是重要的基础。

我们首先向您演示直接调用 OpenAI 的场景,以充分说明为什么我们需要使用 LangChain。

一、直接调用OpenAI

1.1 计算1+1

我们来看一个简单的例子,直接使用通过 OpenAI 接口封装的函数get_completion来让模型告诉我们:1+1是什么?

    String prompt = "1+1是什么?";

    String result = this.getCompletion(prompt);

    log.info("test1:\n{}", result);
1+1等于2。

1.2 用普通话表达海盗邮件

在上述简单示例中,模型gpt-3.5-turbo为我们提供了关于1+1是什么的答案。而现在,我们进入一个更为丰富和复杂的场景。

设想一下,你是一家电商公司的员工。你们的客户中有一位名为海盗A的特殊顾客。他在你们的平台上购买了一个榨汁机,目的是为了制作美味的奶昔。但在制作过程中,由于某种原因,奶昔的盖子突然弹开,导致厨房的墙上洒满了奶昔。想象一下这名海盗的愤怒和挫败之情。他用充满海盗特色的英语方言,给你们的客服中心写了一封邮件。

    String customerEmail = "嗯呐,我现在可是火冒三丈,我那个搅拌机盖子竟然飞了出去,把我厨房的墙壁都溅上了果汁!\n" +
        "更糟糕的是,保修条款可不包括清理我厨房的费用。\n" +
        "伙计,赶紧给我过来!";

在处理来自多元文化背景的顾客时,我们的客服团队可能会遇到某些特殊的语言障碍。如上,我们收到了一名海盗客户的邮件,而他的表达方式对于我们的客服团队来说略显生涩。

为了解决这一挑战,我们设定了以下两个目标:

首先,我们希望模型能够将这封充满海盗方言的邮件翻译成普通话,这样客服团队就能更容易地理解其内容。 其次,在进行翻译时,我们期望模型能采用平和和尊重的语气,这不仅能确保信息准确传达,还能保持与顾客之间的和谐关系。 为了指导模型的输出,我们定义了一个文本表达风格标签,简称为style

 String style = "正式普通话用一个平静、尊敬、有礼貌的语调";

下一步我们需要做的是将customerEmailstyle结合起来构造我们的提示:prompt

    String prompt = "把由三个反引号分隔的文本\n" +
                "翻译成一种" + style + "风格。\n" +
                "文本: ```" + customerEmail + "```";

    log.info("prompt:\n{}", prompt);
把由三个反引号分隔的文本
翻译成一种正式普通话用一个平静、尊敬、有礼貌的语调风格。
文本: ```嗯呐,我现在可是火冒三丈,我那个搅拌机盖子竟然飞了出去,把我厨房的墙壁都溅上了果汁!
更糟糕的是,保修条款可不包括清理我厨房的费用。
伙计,赶紧给我过来!```

经过精心设计的prompt已经准备就绪。接下来,只需调用getCompletion方法,我们就可以获得期望的输出——那封原汁原味的海盗方言邮件,将被翻译成既平和又尊重的正式普通话表达。

        String result = this.getCompletion(prompt);

        log.info("test2:\n{}", result);
嗯呐,我现在非常生气,我的搅拌机盖子竟然飞了出去,把我厨房的墙壁都溅上了果汁!更糟糕的是,保修条款并不包括清理我厨房的费用。伙计,请你尽快过来

在进行语言风格转换之后,我们可以观察到明显的变化:原本的用词变得更为正式,那些带有极端情绪的表达得到了替代,并且文本中还加入了表示感激的词汇。

二、通过LangChain使用OpenAI

在前面的小节中,我们使用了封装好的函数getCompletion,利用 OpenAI 接口成功地对那封充满方言特色的邮件进行了翻译。得到一封采用平和且尊重的语气、并用标准普通话所写的邮件。接下来,我们将尝试使用 LangChain 解决该问题。

2.1 模型

现在让我们尝试使用LangChain来实现相同的功能。只要导入OpenAI的对话模型ChatOpenAI。 除去OpenAI以外还集成了其他对话模型,更多细节可以查看 java-Langchain 官方文档。

    import com.starcloud.ops.llm.langchain.core.model.chat.ChatOpenAI;

    ChatOpenAI chatOpenAI = new ChatOpenAI();
    chatOpenAI.setTemperature(0.0);

    log.info("test1:\n{}", result);
ChatOpenAI(model=gpt-3.5-turbo, messages=null, temperature=0.0, topP=1.0, n=1, stream=false, stop=null, maxTokens=500, presencePenalty=0.0, frequencyPenalty=0.0, functions=null)

上面的输出显示ChatOpenAI的默认模型为gpt-3.5-turbo

2.2 使用提示模版

在前面的例子中,我们通过f字符串把表达式的值style和customerEmail添加到prompt字符串内。

langchain提供了接口方便快速的构造和使用提示。

2.2.1 用普通话表达海盗邮件

现在我们来看看如何使用langchain来构造提示吧!

        String temp = "把由三个反引号分隔的文本\n" +
            "翻译成一种{style}风格。\n" +
            "文本: ```{text}```";

        //以human类型创建一个message
        HumanMessagePromptTemplate humanMessagePromptTemplate = HumanMessagePromptTemplate.fromTemplate(temp);

        //创建一个prompt模版
        ChatPromptTemplate chatPromptTemplate = ChatPromptTemplate.fromMessages(Arrays.asList(humanMessagePromptTemplate));

        //传入参数,生成最后完整的替换过变量的prompt
        ChatPromptValue chatPromptValue = chatPromptTemplate.formatPrompt(Arrays.asList(
        BaseVariable.newString("style", "正式普通话 \n" +
        "用一个平静、尊敬的语气"),
        BaseVariable.newString("text", "嗯呐,我现在可是火冒三丈,我那个搅拌机盖子竟然飞了出去,把我厨房的墙壁都溅上了果汁!\n" +
        "更糟糕的是,保修条款可不包括清理我厨房的费用。\n" +
        "伙计,赶紧给我过来!")
        ));

        log.info("test4:\n{}", chatPromptValue);
ChatPromptValue(messages=[BaseMessage(content=把由三个反引号分隔的文本
翻译成一种正式普通话
用一个平静、尊敬的语气风格。
文本: ```嗯呐,我现在可是火冒三丈,我那个搅拌机盖子竟然飞了出去,把我厨房的墙壁都溅上了果汁!
更糟糕的是,保修条款可不包括清理我厨房的费用。
伙计,赶紧给我过来!```, additionalArgs={})])

现在我们可以调用模型部分定义的chat模型来实现转换客户消息风格。

        ChatOpenAI chatOpenAI = new ChatOpenAI();
        chatOpenAI.setTemperature(0.0);

        String result = chatOpenAI.call(chatPromptValue);

        log.info("result:\n{}", result);
嗯呐,我现在非常生气,我的搅拌机盖子竟然飞了出去,把我厨房的墙壁都溅上了果汁!更糟糕的是,保修条款并不包括清理我厨房的费用。伙计,请你尽快过来!

2.2.2 用海盗方言回复邮件

到目前为止,我们已经实现了在前一部分的任务。接下来,我们更进一步,将客服人员回复的消息,转换为海盗风格英语,并确保消息比较有礼貌。 这里,我们可以继续使用起前面构造的的langchain提示模版,来获得我们回复消息提示。

2.2.3 为什么需要提示模版

在应用于比较复杂的场景时,提示可能会非常长并且包含涉及许多细节。使用提示模版,可以让我们更为方便地重复使用设计好的提示。英文版提示2.2.3 给出了作业的提示模版案例:学生们线上学习并提交作业,通过提示来实现对学生的提交的作业的评分。

此外,LangChain还提供了提示模版用于一些常用场景。比如自动摘要、问答、连接到SQL数据库、连接到不同的API。通过使用LangChain内置的提示模版,你可以快速建立自己的大模型应用,而不需要花时间去设计和构造提示。

2.3 输出解析器

2.3.1 不使用输出解释器提取客户评价中的信息

对于给定的评价customerReview, 我们希望提取信息,并按以下格式输出:

{
  "gift": False,
  "delivery_days": 5,
  "price_value": "pretty affordable!"
}

第三章 储存

在与语言模型交互时,你可能已经注意到一个关键问题:它们并不记忆你之前的交流内容,这在我们构建一些应用程序(如聊天机器人)的时候,带来了很大的挑战,使得对话似乎缺乏真正的连续性。因此,在本节中我们将介绍 LangChain 中的储存模块,即如何将先前的对话嵌入到语言模型中的,使其具有连续对话的能力。

当使用 LangChain 中的储存(Memory)模块时,它旨在保存、组织和跟踪整个对话的历史,从而为用户和模型之间的交互提供连续的上下文。

LangChain 提供了多种储存类型。其中,缓冲区储存允许保留最近的聊天消息,摘要储存则提供了对整个对话的摘要。实体储存则允许在多轮对话中保留有关特定实体的信息。这些记忆组件都是模块化的,可与其他组件组合使用,从而增强机器人的对话管理能力。储存模块可以通过简单的 API 调用来访问和更新,允许开发人员更轻松地实现对话历史记录的管理和维护。

此次课程主要介绍其中四种储存模块,其他模块可查看文档学习。

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

在 LangChain 中,储存指的是大语言模型(LLM)的短期记忆。为什么是短期记忆?那是因为LLM训练好之后 (获得了一些长期记忆),它的参数便不会因为用户的输入而发生改变。当用户与训练好的LLM进行对话时,LLM 会暂时记住用户的输入和它已经生成的输出,以便预测之后的输出,而模型输出完毕后,它便会“遗忘”之前用户的输入和它的输出。因此,之前的这些信息只能称作为 LLM 的短期记忆。

为了延长 LLM 短期记忆的保留时间,则需要借助一些外部储存方式来进行记忆,以便在用户与 LLM 对话中,LLM 能够尽可能的知道用户与它所进行的历史对话信息。开发出功能强大的语言模型程序。让我们开始探索LangChain的魅力吧!

一、对话缓存储存

1.1 初始化对话模型

让我们先来初始化对话模型。

        ChatOpenAI chatOpenAI = new ChatOpenAI();

        //这里我们将参数temperature设置为0.0,从而减少生成答案的随机性。
        chatOpenAI.setTemperature(0.0);

        ConversationBufferMemory memory = new ConversationBufferMemory();

        //新建一个 ConversationChain Class 实例
        ConversationChain chain = new ConversationChain(chatOpenAI, memory);

1.2 第一轮对话

当我们运行预测(predict)时,生成了一些提示,如下所见,他说“以下是人类和 AI 之间友好的对话,AI 健谈“等等,这实际上是 LangChain 生成的提示,以使系统进行希望和友好的对话,并且必须保存对话,并提示了当前已完成的模型链。

    chain.run("你好, 我叫皮皮鲁");
Human: 你好, 我叫皮皮鲁
AI: 你好,皮皮鲁!很高兴认识你。我是一个AI助手,可以回答你的问题和提供信息。有什么我可以帮助你的吗?

1.3 第二轮对话

当我们进行第二轮对话时,它会保留上面的提示

    chain.run("1+1等于多少?");
Human: 你好, 我叫皮皮鲁
AI: 你好,皮皮鲁!很高兴认识你。我是一个AI助手,可以回答你的问题和提供信息。有什么我可以帮助你的吗?
Human: 1+1等于多少?
AI:  1+1等于2。

1.4 第三轮对话

为了验证他是否记忆了前面的对话内容,我们让他回答前面已经说过的内容(我的名字),可以看到他确实输出了正确的名字,因此这个对话链随着往下进行会越来越长。

    chain.run("我叫什么名字?");
Human: 你好, 我叫皮皮鲁
AI: 你好,皮皮鲁!很高兴认识你。我是一个AI助手,可以回答你的问题和提供信息。有什么我可以帮助你的吗?
Human: 1+1等于多少?
AI:  1+1等于2。
Human: 我叫什么名字?
AI: 你叫皮皮鲁。

1.5 查看储存缓存

储存缓存(buffer),即储存了当前为止所有的对话信息

        String buffer = memory.getBuffer();

        log.info("buffer: {}", buffer);
Human: 你好, 我叫皮皮鲁
AI: 你好,皮皮鲁!很高兴认识你。我是一个AI助手,可以回答你的问题和提供信息。有什么我可以帮助你的吗?
Human: 1+1等于多少?
AI: 1+1等于2。
Human: 我叫什么名字?
AI: 你叫皮皮鲁。

也可以通过 loadMemoryVariables 打印缓存中的历史消息。这里的{}是一个空字典,有一些更高级的功能,使用户可以使用更复杂的输入,具体可以通过 LangChain 的官方文档查询更高级的用法。

    log.info("loadMemoryVariables: {}", memory.loadMemoryVariables());
loadMemoryVariables: [BaseVariable(type=null, field=history, defaultValue=null, value=Human: 你好, 我叫皮皮鲁
AI: 你好,皮皮鲁!很高兴认识你。我是一个AI助手,可以回答你的问题和提供信息。有什么我可以帮助你的吗?
Human: 1+1等于多少?
AI: 1+1等于2Human: 我叫什么名字?
AI: 你叫皮皮鲁。, options=null)]

1.6 直接添加内容到储存缓存

我们可以使用saveContext来直接添加内容到buffer中。

        ConversationBufferMemory memory = new ConversationBufferMemory();

        memory.saveContext(BaseVariable.newString("input", "你好,我叫皮皮鲁"), BaseVariable.newString("output", "你好啊,我叫鲁西西"));

        log.info("loadMemoryVariables: {}", memory.loadMemoryVariables());
[BaseVariable(type=null, field=history, defaultValue=null, value=Human: 你好,我叫皮皮鲁
AI: 你好啊,我叫鲁西西, options=null)]

继续添加新的内容

       memory.saveContext(BaseVariable.newString("input", "很高兴和你成为朋友!"), BaseVariable.newString("output", "是的,让我们一起去冒险吧!"));

       log.info("loadMemoryVariables: {}", memory.loadMemoryVariables());
[BaseVariable(type=null, field=history, defaultValue=null, value=Human: 你好,我叫皮皮鲁
AI: 你好啊,我叫鲁西西
Human: 很高兴和你成为朋友!
AI: 是的,让我们一起去冒险吧!, options=null)]

可以看到对话历史都保存下来了!

当我们在使用大型语言模型进行聊天对话时,大型语言模型本身实际上是无状态的。语言模型本身并不记得到目前为止的历史对话。每次调用API结点都是独立的。储存(Memory)可以储存到目前为止的所有术语或对话,并将其输入或附加上下文到LLM中用于生成输出。如此看起来就好像它在进行下一轮对话的时候,记得之前说过什么。

二、对话缓存窗口储存

随着对话变得越来越长,所需的内存量也变得非常长。将大量的tokens发送到LLM的成本,也会变得更加昂贵,这也就是为什么API的调用费用,通常是基于它需要处理的tokens数量而收费的。

针对以上问题,LangChain也提供了几种方便的储存方式来保存历史对话。其中,对话缓存窗口储存只保留一个窗口大小的对话。它只使用最近的n次交互。这可以用于保持最近交互的滑动窗口,以便缓冲区不会过大。

2.1 添加两轮对话到窗口储存

我们先来尝试一下使用 ConversationBufferWindowMemory 来实现交互的滑动窗口,并设置k=1,表示只保留一个对话记忆。接下来我们手动添加两轮对话到窗口储存中,然后查看储存的对话。

        ConversationBufferWindowMemory memory = new ConversationBufferWindowMemory(1);

        memory.saveContext(BaseVariable.newString("input", "你好,我叫皮皮鲁"), BaseVariable.newString("output", "你好啊,我叫鲁西西"));


        memory.saveContext(BaseVariable.newString("input", "很高兴和你成为朋友!"), BaseVariable.newString("output", "是的,让我们一起去冒险吧!"));

        log.info("loadMemoryVariables: {}", memory.loadMemoryVariables());
[BaseVariable(type=null, field=history, defaultValue=null, value=Human: 很高兴和你成为朋友!
AI: 是的,让我们一起去冒险吧!, options=null)]

通过结果,我们可以看到窗口储存中只有最后一轮的聊天记录。

2.2 在对话链中应用窗口储存

接下来,让我们来看看如何在ConversationChain中运用ConversationBufferWindowMemory吧!

         ChatOpenAI chatOpenAI = new ChatOpenAI();
        //这里我们将参数temperature设置为0.0,从而减少生成答案的随机性。
        chatOpenAI.setTemperature(0.0);

        ConversationBufferWindowMemory memory = new ConversationBufferWindowMemory(1);

        //新建一个 ConversationChain Class 实例
        ConversationChain chain = new ConversationChain(chatOpenAI, memory);


        log.info("第一轮");
        chain.run("你好, 我叫皮皮鲁");

        log.info("第二轮");
        chain.run("1+1等于多少?");

        log.info("第三轮");
        chain.run("我叫什么名字?");
第一轮
Current conversation:

Human: 你好, 我叫皮皮鲁
AI:

第二轮
Current conversation:
AI: 你好,皮皮鲁!很高兴认识你。我是一个AI助手,可以回答你的问题和提供帮助。有什么我可以帮你的吗?
Human: 1+1等于多少?
AI:

第三轮
Current conversation:
AI: 1+1等于2。
Human: 我叫什么名字?
AI:

注意此处!由于这里用的是一个窗口的记忆,因此只能保存一轮的历史消息,因此AI并不能知道你第一轮对话中提到的名字,他最多只能记住上一轮(第二轮)的对话信息

三、对话字符缓存储存

使用对话字符缓存记忆,内存将限制保存的token数量。如果字符数量超出指定数目,它会切掉这个对话的早期部分 以保留与最近的交流相对应的字符数量,但不超过字符限制。

添加对话到Token缓存储存,限制token数量,进行测试

        ChatOpenAI chatOpenAI = new ChatOpenAI();
        //这里我们将参数temperature设置为0.0,从而减少生成答案的随机性。
        chatOpenAI.setTemperature(0.0);

        ConversationTokenBufferMemory memory = new ConversationTokenBufferMemory(chatOpenAI, 30);

        memory.saveContext(BaseVariable.newString("input", "朝辞白帝彩云间,"), BaseVariable.newString("output", "千里江陵一日还。"));

        memory.saveContext(BaseVariable.newString("input", "两岸猿声啼不住,"), BaseVariable.newString("output", "轻舟已过万重山。"));


        log.info("loadMemoryVariables: {}", memory.loadMemoryVariables());
[BaseVariable(type=null, field=history, defaultValue=null, value=Human: 两岸猿声啼不住,
AI: 轻舟已过万重山。, options=null)]

ChatGPT 使用一种基于字节对编码(Byte Pair Encoding,BPE)的方法来进行 tokenization (将输入文本拆分为token)。BPE 是一种常见的 tokenization 技术,它将输入文本分割成较小的子词单元。 OpenAI 在其官方 GitHub 上公开了一个最新的开源 Python 库 tiktoken(github.com/openai/tikt…%EF%BC%8C%E8%BF%99%E4%B8%AA%E5%BA%93%E4%B8%BB%E8%A6%81%E6%98%AF%E7%94%A8%E6%9D%A5%E8%AE%A1%E7%AE%97) tokens 数量的。相比较 HuggingFace 的 tokenizer ,其速度提升了好几倍。 具体 token 计算方式,特别是汉字和英文单词的 token 区别,具体可参考知乎文章(www.zhihu.com/question/59…%E3%80%82)

四、对话摘要缓存储存

对话摘要缓存储存,使用 LLM 对到目前为止历史对话自动总结摘要,并将其保存下来。

4.1 使用对话摘要缓存储存

我们创建了一个长字符串,其中包含某人的日程安排。

        String text = "在八点你和你的产品团队有一个会议。\n" +
                "你需要做一个PPT。\n" +
                "上午9点到12点你需要忙于LangChain。\n" +
                "Langchain是一个有用的工具,因此你的项目进展的非常快。\\n" +
                "中午,在意大利餐厅与一位开车来的顾客共进午餐\n" +
                "走了一个多小时的路程与你见面,只为了解最新的 AI。\n" +
                "确保你带了笔记本电脑可以展示最新的 LLM 样例.";

        ChatOpenAI chatOpenAI = new ChatOpenAI();
        //这里我们将参数temperature设置为0.0,从而减少生成答案的随机性。
        chatOpenAI.setTemperature(0.0);

        ConversationSummaryBufferMemory memory = new ConversationSummaryBufferMemory(chatOpenAI, 100);

        memory.saveContext(BaseVariable.newString("input", "你好,我叫皮皮鲁"), BaseVariable.newString("output", "你好啊,我叫鲁西西"));

        memory.saveContext(BaseVariable.newString("input", "很高兴和你成为朋友!"), BaseVariable.newString("output", "是的,让我们一起去冒险吧!"));

        memory.saveContext(BaseVariable.newString("input", "今天的日程安排是什么?"), BaseVariable.newString("output", text));


        log.info("getMovingSummaryBuffer: {}", memory.getMovingSummaryBuffer());
The human and AI introduce themselves and become friends. They plan to go on an adventure together. The human asks about their schedule for the day. The AI informs the human that they have a meeting with their product team at 8 am and need to prepare a PowerPoint presentation. From 9 am to 12 pm, they will be busy with LangChain, a useful tool that is helping their project progress quickly. At noon, they will have lunch with a customer who has driven for over an hour just to learn about the latest AI. The AI advises the human to bring their laptop to showcase the latest LLM samples.

4.2 基于对话摘要缓存储存的对话链

基于上面的对话摘要缓存储存,我们新建一个对话链。

        //新建一个 ConversationChain Class 实例
        ConversationChain chain = new ConversationChain(chatOpenAI, memory);

        log.info("result :{}", chain.run("展示什么样的样例最好呢?"));
        
        log.info("getMovingSummaryBuffer: {}", memory.getMovingSummaryBuffer());
[[BaseMessage(content=The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know.
Current conversation:
System: The human and AI introduce themselves and become friends. They plan to go on an adventure together. The human asks about their schedule for the day. The AI informs the human that they have a meeting with their product team at 8 am and need to prepare a PowerPoint presentation. From 9 am to 12 pm, they will be busy with LangChain, a useful tool that is helping their project progress quickly. At noon, they will have lunch with a customer who has driven for over an hour just to learn about the latest AI. The AI advises the human to bring their laptop to showcase the latest LLM samples.
Human: 展示什么样的样例最好呢?
AI:, additionalArgs={})]]

展示一些最新的语言模型样例可能是一个不错的选择。你可以展示一些自然语言处理的应用,比如文本生成、情感分析、机器翻译等。另外,你也可以展示一些有趣的对话交互,让客户亲身体验语言模型的能力。总之,选择一些能够展示语言模型强大功能的样例会给客户留下深刻的印象。

getMovingSummaryBuffer: The human and AI introduce themselves and become friends. They plan to go on an adventure together. The human asks about their schedule for the day. The AI informs the human that they have a meeting with their product team at 8 am and need to prepare a PowerPoint presentation. From 9 am to 12 pm, they will be busy with LangChain, a useful tool that is helping their project progress quickly. At noon, they will have lunch with a customer who has driven for over an hour just to learn about the latest AI. The AI advises the human to bring their laptop to showcase the latest LLM samples. The human asks what kind of samples would be best to showcase. The AI suggests showcasing some of the latest language model samples, such as text generation, sentiment analysis, machine translation, etc. Additionally, they can also demonstrate some interesting conversational interactions to let the customer experience the capabilities of the language model. In summary, choosing samples that showcase the powerful features of the language model will leave a lasting impression on the customer.

通过对比上一次输出,发现摘要记录更新了,添加了最新一次对话的内容总结。

第四章 模型链

链(Chains)通常将大语言模型(LLM)与提示(Prompt)结合在一起,基于此,我们可以对文本或数据进行一系列操作。链(Chains)可以一次性接受多个输入。例如,我们可以创建一个链,该链接受用户输入,使用提示模板对其进行格式化,然后将格式化的响应传递给 LLM 。我们可以通过将多个链组合在一起,或者通过将链与其他组件组合在一起来构建更复杂的链。

一、大语言模型链

大语言模型链(LLMChain)是一个简单但非常强大的链,也是后面我们将要介绍的许多链的基础。

1.1 初始化语言模型

        ChatOpenAI chatOpenAI=new ChatOpenAI();

        //这里我们将参数temperature设置为0.0,从而减少生成答案的随机性。
        chatOpenAI.setTemperature(0.0);

1.2 初始化提示模版

初始化提示,这个提示将接受一个名为product的变量。该prompt将要求LLM生成一个描述制造该产品的公司的最佳名称



1.3 构建大语言模型链

将大语言模型(LLM)和提示(Prompt)组合成链。这个大语言模型链非常简单,可以让我们以一种顺序的方式去通过运行提示并且结合到大语言模型中。

 LLMChain chain=new LLMChain<>(chatOpenAI,chatPromptTemplate);

1.4 运行大语言模型链

因此,如果我们有一个名为"Queen Size Sheet Set"的产品,我们可以通过使用chain.run将其通过这个链运行


//通过BaseVariable 去实现替换模版内容中的占位符,这里是{product}
  chain.run(Arrays.asList(BaseVariable.newString("product","大号床单套装")));

豪华床纺

您可以输入任何产品描述,然后查看链将输出什么结果。

二、 顺序链

顺序链(SequentialChains)是按预定义顺序执行其链接的链,支持多个多个输入或多个输出。

接下来我们将创建一系列的链,然后一个接一个使用他们

2.1 创建四个子链


        ChatOpenAI chatOpenAI = new ChatOpenAI();

        //这里我们将参数temperature设置为0.0,从而减少生成答案的随机性。
        chatOpenAI.setTemperature(0.0);

        //以human类型创建一个message
        HumanMessagePromptTemplate humanMessagePromptTemplate = HumanMessagePromptTemplate.fromTemplate("把下面的评论review翻译成英文:\n\n{Review}");
        //创建一个prompt模版
        ChatPromptTemplate chatPromptTemplate = ChatPromptTemplate.fromMessages(Arrays.asList(humanMessagePromptTemplate));

        LLMChain chain1 = new LLMChain<>(chatOpenAI, chatPromptTemplate, "English_Review");


        //以human类型创建一个message
        HumanMessagePromptTemplate humanMessagePromptTemplate2 = HumanMessagePromptTemplate.fromTemplate("请你用一句话来总结下面的评论review:\n{English_Review}");
        //创建一个prompt模版
        ChatPromptTemplate chatPromptTemplate2 = ChatPromptTemplate.fromMessages(Arrays.asList(humanMessagePromptTemplate2));

        LLMChain chain2 = new LLMChain<>(chatOpenAI, chatPromptTemplate2, "summary");


        //以human类型创建一个message
        HumanMessagePromptTemplate humanMessagePromptTemplate3 = HumanMessagePromptTemplate.fromTemplate("下面的评论review使用的什么语言:\n{Review}");
        //创建一个prompt模版
        ChatPromptTemplate chatPromptTemplate3 = ChatPromptTemplate.fromMessages(Arrays.asList(humanMessagePromptTemplate3));

        LLMChain chain3 = new LLMChain<>(chatOpenAI, chatPromptTemplate3, "language");


        //以human类型创建一个message
        HumanMessagePromptTemplate humanMessagePromptTemplate4 = HumanMessagePromptTemplate.fromTemplate("使用特定的语言对下面的总结写一个后续回复:\n总结: {summary}\n语言: {language}");
        //创建一个prompt模版
        ChatPromptTemplate chatPromptTemplate4 = ChatPromptTemplate.fromMessages(Arrays.asList(humanMessagePromptTemplate4));

        LLMChain chain4 = new LLMChain<>(chatOpenAI, chatPromptTemplate4, "followup_message");
        

2.2 对四个子链进行组合

        //输入:review
        //输出:英文review,总结,后续回复
        SequentialChain sequentialChain = new SequentialChain(Arrays.asList(chain1, chain2, chain3, chain4), Arrays.asList("Review"), Arrays.asList("English_Review", "summary", "followup_message"));

让我们选择一篇评论并通过整个链传递它,可以发现,原始review是法语,可以把英文review看做是一种翻译,接下来是根据英文review得到的总结,最后输出的是用法语原文进行的续写信息。

        String review = "Je trouve le goût médiocre. La mousse ne tient pas, c'est bizarre. J'achète les mêmes dans le commerce et le goût est bien meilleur... Vieux lot ou contrefaçon !?";
        String result = sequentialChain.run(Arrays.asList(BaseVariable.newString("Review", review)));
SequentialChain result:
{
    summary = The reviewer finds the taste mediocre, with poor foam retention, suspecting the possibility of an old batch or counterfeit product compared to the ones bought in stores., 
    English_Review = I find the taste mediocre.The foam doesn 't hold, it's strange.I buy the same ones in stores and the taste is much better...Old batch or counterfeit! ? , 
    language = 这段评论使用的是法语。, 
    followup_message = 回复 : Le critique trouve le goût moyen, avec une mauvaise rétention de la mousse, soupçonnant la possibilité d 'un lot ancien ou d'un produit contrefait par rapport à ceux achetés en magasin.Je vais contacter le vendeur pour clarifier la situation et demander un remplacement ou un remboursement si nécessaire.
}

第五章 代理

大型语言模型(LLMs)非常强大,但它们缺乏“最笨”的计算机程序可以轻松处理的特定能力。LLM 对逻辑推理、计算和检索外部信息的能力较弱,这与最简单的计算机程序形成对比。例如,语言模型无法准确回答简单的计算问题,还有当询问最近发生的事件时,其回答也可能过时或错误,因为无法主动获取最新信息。这是由于当前语言模型仅依赖预训练数据,与外界“断开”。要克服这一缺陷,LangChain框架提出了“代理”( Agent)的解决方案。

代理作为语言模型的外部模块,可提供计算、逻辑、检索等功能的支持,使语言模型获得异常强大的推理和获取信息的超能力。

在本章中,我们将详细介绍代理的工作机制、种类、以及如何在LangChain中将其与语言模型配合,构建功能更全面、智能程度更高的应用程序。代理机制极大扩展了语言模型的边界,是当前提升其智能的重要途径之一。让我们开始学习如何通过代理释放语言模型的最大潜力。

一、使用LangChain内置工具llm-math和wikipedia

要使用代理 (Agents) ,我们需要三样东西:

  • 一个基本的 LLM
  • 我们将要进行交互的工具 Tools
  • 一个控制交互的代理 (Agents) 。

首先,让我们新建一个基本的 LLM

        ChatOpenAI chatOpenAI = new ChatOpenAI();

//这里我们将参数temperature设置为0.0,从而减少生成答案的随机性。
chatOpenAI.setTemperature(0.0);

接下来,初始化工具 Tool ,我们可以创建自定义工具 Tool 或加载预构建工具 Tool。无论哪种情况,工具 Tool 都是一个给定工具 名称 name 和 描述 description 的 实用链。

CalculatorTool 一个算数计算工具

        List<BaseTool> tools=LoadTools.loadTools(Arrays.asList(CalculatorTool.class),chatOpenAI);

       

现在我们有了 LLM 和工具,最后让我们初始化一个简单的代理 (Agents) :


 //实例化一个代理
 OpenAIFunctionsAgent baseSingleActionAgent=OpenAIFunctionsAgent.fromLLMAndTools(chatOpenAI,tools);

 //示例化一个代理执行器
 AgentExecutor agentExecutor=AgentExecutor.fromAgentAndTools(tools,chatOpenAI,baseSingleActionAgent,baseSingleActionAgent.getCallbackManager());

注意这里并没有用`CHAT_ZERO_SHOT_REACT_DESCRIPTION`(REACT方式) 去实现Agent,因为实际生产上使用REACT方式结果更不稳定,现在效果最好的还是GPT4.0 Functions API.

上面其实分两个步骤:

  1. 生成一个具体的Agent实现类
  2. 用户Agent执行器做最终执行

让我们使用下这个Agent,使用代理回答数学问题 计算300的25%


 agentExecutor.run("计算300的25%");

onToolStart: CalculatorTool, {"query":"300 * 0.25"}, false, {}
CalculatorTool: 300 * 0.25
onChainEnd: class com.starcloud.ops.llm.langchain.core.agent.base.AgentExecutor, {output=30025%等于75。}, {}

二、 定义自己的工具并在代理中使用

在本节,我们将创建和使用自定义时间工具。LangChian tool 函数装饰器可以应用用于任何函数,将函数转化为LangChain 工具,使其成为代理可调用的工具。我们需要给函数加上非常详细的文档字符串, 使得代理知道在什么情况下、如何使用该函数/工具。 比如下面的函数time,我们加上了详细的文档字符串。


    //自定义工具
    @Data
    public static class MySelfTool extends BaseTool<MySelfTool.Request, String> {

        private String name = "MySelfTool";

        private String description = "返回今天的日期,用于任何需要知道今天日期的问题, 输入应该总是一个空字符串,这个函数将总是返回今天的日期,任何日期计算应该在这个函数之外进行。";

        @Override
        protected String _run(Request input) {
            
            return DateUtil.now();
        }


        @Data
        public static class Request {

            private String query;

        }
    }


        MySelfTool mySelfTool = new MySelfTool();
        
        //放入实例化后的工具
        List<BaseTool> tools = LoadTools.loadToolsInstance(Arrays.asList(mySelfTool), chatOpenAI);

        OpenAIFunctionsAgent baseSingleActionAgent = OpenAIFunctionsAgent.fromLLMAndTools(chatOpenAI, tools);

        AgentExecutor agentExecutor = AgentExecutor.fromAgentAndTools(tools, chatOpenAI, baseSingleActionAgent, baseSingleActionAgent.getCallbackManager());

        agentExecutor.run("今天的日期是?");
onChainEnd: class com.starcloud.ops.llm.langchain.core.agent.base.AgentExecutor, {output=今天的日期是2023101日。}, {}