跳到主要内容

云音乐 AI Agent 探索实践

· 阅读需 27 分钟
kkdev163

一. 前言

本篇文章介绍了大语言模型时代下的 AI Agent 概念,并以 LangChain 为例详细介绍了 AI Agent 背后的实现原理,随后展开介绍云音乐在实践 AI Agent 过程中的遇到的问题及优化手段。通过阅读本篇文章,读者将掌握业界主流的 AI Agent 实现原理及实践优化手段,对应用自研 AI Agent  或理解 Open AI 最新提出的 Assistants API 都具有一定的参考价值。

二. AI Agent 简介

2.1 什么是 AI Agent ?

相信阅读这篇文章的读者,都在今年感受到了大语言模型带来的爆炸影响力,也都有过与之直接进行交互的使用经历,感受到了它的强大和无所不知。

但大语言模型也存在一些限制,比如:

  • 他的数学计算能力相对薄弱,对于复杂的运算可能会出现错误。(如问 3457 * 43216 = ?,它可能会回答 149,623,912。这是错的正确答案是 149,397,712)
  • 训练的数据集不包含近期的数据,所以无法直接知道最近的天气和最近发生的新闻。(OpenAI 近期推出的 gpt-4-1106-preview 的训练数据集更新至 23 年 4 月)

区别于直接与大语言模型进行对话,AI Agent 是通过工程化的手段,为大语言模型提供了获取外部工具、知识的能力。他是介于人类、大语言模型之间的代理。 当用户向 AI Agent 输入问题时,AI Agent 可以使用大语言模型作为推理引擎,将一个复杂的任务进行分解、给出任务执行规划。 之后 Agent 会调用外部工具获取结果,并将大语言的上次推理和工具调用结果返回给大语言模型,让大语言模型继续思考、规划。如此循环,直到将一个复杂的任务完成。

基于以上的理解,我们可以给 AI Agent 下一个定义:

他是人与大模型之间的智能代理,在接到任务时,它会使用大语言模型作为推理引擎,进行自主的任务规划、执行调度。

2.2 AI Agent 的应用场景

AI Agent 又有哪些应用场景呢?在《AI Agent 的千亿美金问题》这篇文章中,作者详细介绍了 AI Agent 的应用场景,笔者从中引用 3 个大家可能比较熟悉的例子:

1. AI 辅助编程场景

Cursor

Cursor 将自己的产品称为 AI-first IDE,其产品 UI 与 VS Code 接近,加入了很多 LLM 原生的 feature,比 Github Copilot 能做得更深入。可以认为是 AI agent 化的 VS Code + Github Copilot.

Vercel v0

v0 是由 Vercel 团队打造的 AI 前端代码生成工具。其使用过程非常直接:用户使用自然语言描述需求,v0 根据需求描述来生成组件代码。然后用户继续对不满意的地方提出修改意见,将其迭代为 v1、v2... 直到满足用户的要求。当用户想将一个生成网页的标题改为渐变色时,只需要选择标题部分并提出“增加一个渐变色”,产品便会只对这一部分代码进行修改。

2. 个人助理类场景

Lindy.AI

Lindy.ai 是一款基于办公场景的智能个人 AI 助手产品,帮助用户智能化处理日常办公任务。它可以帮人类做日程规划预定、邮件起草发送、会议纪要撰写和总结等。

三. 如何来构建 AI Agent ?

对 AI Agent 做了简要的介绍后,我们接着来看,如何构建 AI Agent?目前市面上比较火的 Agent 相关的项目有 AutoGPT、BabyAGI、LangChain 等。

  • AutoGPT 在今年 3 月份发布后取得了惊人的增长,目前已经是一个 152k start 的项目。
  • BabyAGI 则提出了 Plan and execute Agent,他的实现方式是: 一次性对任务做全局的规划,后续严格一步步执行,不再变更任务计划。
  • LangChain 则是一个通用的大语言模型应用层开发框架,提供了 Python、TS 两种语言库,内置各种 LLM 工具,在 Agent 领域,它也提供了多种 Agent 的实现思路,包括了 AutoGPT、BabyAGI 的实现,本文选择 LangChain 展开介绍。

3.1 LangChain Agent 使用示例

前文提到 LLM 不擅长解决复杂数学计算,我们接着来看 LangChain 使用外部工具来增强 LLM 的数学运算能力的官方示例。本示例的用户提问是: "5~10 之间的随机数的平方是多少?" 。

一共分为 4 大步:

  1. 初始化大语言模型接口,可以传入 modelName、temperature、maxTokens 等参数。
  2. 初始化工具列表,示例中使用了一个 LangChain 内置的计算器工具,以及动态构建工具。我们重点来看下这个动态生成工具:
    1. name 是工具的名字
    2. description 是工具的介绍,是供大语言模型理解的。
    3. schema 是工具的入参定义 ,这里定义了 low 和 hight 都是数字类型,分别代表 随机数的 下界 和 上界。
    4. func 是工具的方法调用定义,参数是 schema 中的定义,函数体是一段 js 随机数生成代码。
  3. 有了工具和大语言模型接口后,随后构造出了 Agent 执行器。
  4. 最后一步是将用户的输入传给 Agent 执行器。

最后输出的随机数平方是 45.067

3.2 LangChain Agent 执行步骤拆解

在本地执行的过程中,Langchain 会输出详细的执行调度日志,如下图所示:

通过分析这些日志可以揭开 Langchain Agent 背后的运行原理。

执行步骤一: 调用大语言模型

如下图所示, Agent 执行的第一步是将用户的输入与一个系统的 prompt 进行组装,我们暂时先称其为 “魔法咒语”,后续会详细介绍。大语言模型会返回他的思考: "用户的问题是 5~10 之间的随机数的平方。我可以使用「随机数生成工具」先生成一个随机数,然后使用计算器工具计算它的平方。" 并以 JSON 指示下一步采取的动作是: 调用「随机数生成工具」,入参为 low 5, high 10。

执行步骤二: 调用工具-随机数生成器

接着 Agent 执行器 会调用「随机数生成工具」入参为 {low:5, hight:10},工具返回 6.7132

执行步骤三: 调用大语言模型

如下图所示,Agent 执行器会把用户的原始问题,和上一步大语言模型的思考、工具调用和工具的输出做拼接,传给大语言模型继续思考。大语言模型回复说: 随机数是 6.71..,现在我可以使用计算器工具来计算它的平方值。并使用 JSON 格式指示下一步动作是: 调用计算器工具,入参是 6.71..的平方的数学描述。

执行步骤四: 调用工具 计算器

接着 Agent 执行器 会调用 计算器工具,入参为 6.71...^2,计算器工具返回的结果为 45.06..

执行步骤五: 调用大语言模型

如下图所示: Agent 执行器将上一步的思考、工具调用、结果做拼接,传递给大语言模型继续思考。大语言模型回复说: 我知道了最终的结果,答案是 45.067

当我第一次运行 Agent 示例,看到 Agent 能如此丝滑地一步步思考,执行外部工具,并得到最终结果时,我非常惊叹于 Agent 的能力,也十分好奇背后的原理的是什么。经过一番探索,发现其核心原理就藏在魔法咒语里。 我们接着来看这里的魔法咒语是什么?

3.3 LangChain Agent 的魔法咒语

魔法咒语片段一

魔法咒语是由多个片段组成,片段一指示了大语言模型可以使用一些工具,但必须要遵循工具的 JSON Schema,然后给出了 合法的 JSON Schema 示例。紧接着给出了大语言模型可用的工具介绍,包含工具的名字、工具的描述和入参的 JSON Schema。

魔法咒语片段二

片段二主要指示大语言模型如何使用工具。需要通过一个 JSON markdown 格式包裹,包含 action 和 action_input 字段,action 必须为 Final Answer 或 工具名。并给出了 Action 的示例。

魔法咒语片段三

我们知道大语言模型是生成式 AI,而片段三指示了大语言模型生成的内容需要遵循的段落结构。分别是:

  • Question 问题是什么
  • Thought 思考如何去解决
  • Action 下一步采取的行动
  • Observation: 行动的结果

并指示生成的思考、行动、结果 是可以重复 N 次的。并指示 LLM 在知道最终的结果后,输出 Final Answer。

这一段是大语言模型能将复杂任务分解、逐步执行、继续思考如此循环的关键。而这一思考框架称为 ReAct

知道了 LangChain 背后的魔法咒语后,我们能否直接在 ChatGPT 中直接输入魔法咒语试下效果呢?答案是可以的。

我们把这段魔法咒语直接复制到 ChatGPT 上。我们看到大模型确实按照 Thought、Action、Observation 的段落格式进行生成输出。

但好像又有点问题,他返回的结果和此前步骤拆解中的步骤一不太一样。步骤一只返回 需要调用「随机数生成器工具」,随后 Agent 会介入工具调用,完成工具调用后再交由大语言模型进行思考,而这里大语言模型直接返回了后续的工具调用结果、下一步思考、下一步的行动,在多步重复后,把一个错误的结果输出给我们了,那么问题出在了哪里呢?

事实上在 Agent 执行器调用大语言模型时,有一个关键的参数 Stop Sequences,这个参数的作用是让大语言模型在准备生成这个词前就强制停住,不再往下生成。

Agent 会传入 Observation 作为这个参数的值,意思就是让大语言模型生成到 Observation 时就强制停止,这样控制权才会转交回给 Agent,Agent 可以继续调用外部工具、执行后续的步骤。

我们在 ChatGPT 上加上这个参数,这一次大语言模型的输出就符合预期了。以上就是 LangChain Agent 的核心原理。

四. 云音乐 Adora 平台在 Agent 方面的实践

Adora 是网易云音乐内部的智能数字助理搭建平台,提供 LLM 相关服务。内置专属 Chat UI 界面、配置中心,可轻松实现知识库管理、智能问答、意图识别、行为翻译等功能。帮助用户快速构建属于自己的智能助手。我们后续也会有文章介绍 Adora,各位读者敬请期待。

4.1 基础能力整合

接着我们来看云音乐 Adora 平台在 Agent 方面的实践。首先是基础能力整合。

步骤一

我们还是基于这段官方示例进行扩展。这里的 ChatOpenAI 是 LangChain 提供的大语言模型接口,底层是调用的 OpenAI 官方 Client。由于各种原因,我们无法直接使用,所以要做下替换。

得益于 LangChain 的面向对象封装,我们只需继承 LangChain 的 ChatOpenAI 类,重写其中的一个函数即可。将 OpenAI 官方 Client 调用 替换为内部封装的 gpt-client 即可。

步骤二

第二步是将 Adora 平台在线录入的服务转换为 LangChain 的 Tools。我们在 Adora 原有的服务定义上,增加了 description_for_ai 字段,以及 input_params 字段,有了这些配置,我们就可以将 Adora 在线录入的服务,转换为 LangChain 的 Tool。

做完了以上的两步,再加上一些胶水代码,我们就为 Adora 平台整合入了 Agent 的能力。

Adora 平台的用户在创建 Agent 智能体时,只需在可视化界面上,选择 Agent 智能体动作,并圈选这个 Agent 所需的服务,即可完成一个 Agent 的构建。

在完成基础能力的整合后,我们还遇到了哪些问题,以及做了哪些优化呢?

4.2 问题及优化手段

问题 1: 如何高效地调试 Agent ?

我们此前提到 Agent 在执行时会输出日志,对于我们理解 Agent 的执行逻辑很有帮助,但这些日志也存在一些冗余的信息,并且是平铺式的,难以快速提炼关键信息。

我们做的第一步是将这些输出日志做采集、提炼。将 Agent 的执行步骤,归纳为关键的 Thought 和 Tool 两大步骤,并以结构化的方式在前端做呈现。

如下图所示,在 Thought 中我们会展示此次调用大语言模型的 system prompt、human input,以及大语言模型的回答,并展示出整体的耗时。

在 Tool 环节,会展示 Agent 使用的工具、耗时。以及工具的入参和出参。

通过结构化的展示,我们将 Agent 执行的每一步,都可视化呈现在开发者眼前,若 Agent 的思考出错或工具调用传参不对,开发者都可以及时看到,并通过改进 prompt 优化整体效果。

值得一提的是 LangChain 官方出品的开发者平台 LangSmith,也将 Agent 的执行可视化作为了关键特性在宣传,可见可视化调试的重要性。

问题 2: 如何解决 Agent 执行的异常中断

我们在调试过程中发现,当 LLM 返回的 action_input 不符合工具的 schema 定义时,Agent 会执行抛错,中断整体执行逻辑。 此外在外部接口调用返回异常时,tool 也会直接抛错,导致 Agent 的整体执行逻辑中断。

举例来说,正常情况下使用 「会议室查询」服务,需要有 buildingName、bookDay 两个参数,我们也在 Prompt 中提示了大语言模型这两个字段为必填项。

但 LLM 由于上下文信息过多,可能会出现遗忘的现象。导致输出的结果中,遗漏了 buildingName 字段。当前 LangChain 的默认处理是当 Schema 校验不通过时,直接抛错,这样 Agent 的执行就结束了。

我们的优化做法是改写 DynamicStructuredTool 逻辑,在入参不符合预期时,不直接抛错,而是给 LLM 返回错误提示,让其继续思考。这样 LLM 在看到上一次工具的输入、错误提示后,在下次思考时,就会尝试纠正自己,给出正确的工具入参。具体的改写代码如下所示:

同理在接口调用环节,如果遇到外部返回异常时,也可以采样同样的思路进行优化。比如会议预定接口,假设接口调用时传入了一个已被占用的时段,后端接口响应就会返回 { code: 400 ,message: 该时段已被占用} , 此时在 request 中,遇到返回码非 200 时,不直接抛错,而是包装一个错误信息返回给 LLM,这样 LLM 在下次思考时,也会纠正自己,尝试给出合理的工具入参。参考代码如下所示:

问题 3 如何让 Agent 请求用户协助?

我们此前提到,Agnet 的执行过程,只有思考、工具调用的重复循环,直到给出任务执行的最终结果。中间没有留给用户介入的机会。

但我们在一些场景,我们是希望能有用户介入的机会,比如在订咖啡、订会议室的场景,在上下文信息不足时,我们希望 Agent 能够向用户去征集偏好、选项,而不是自行决策,一条路走到黑,导致预定失败。

我们的做法是: 首先调整工具的描述,告知 LLM 在不知道参数时,需要向用户提问。

但只靠这一步,效果并不好,有时 LLM 的输出会不符合 Action 格式要求,所以我们还对系统提示词做了逐步的调整,以强化对 LLM 的提醒。

通过以上的 Prompt 优化,现在当输入 「今天下午有哪些会议室?」时,大语言模型会回复「请问您想要查询 1 号楼、2 号楼还是 3 号楼的会议室?」。现在大语言能够正确地向用户提问了,把控制权交给了用户,后续用户回答 「2 号楼」时,我们只需将上一轮的对话作为记忆带到下一轮的 Agent 执行中,就达成了人工介入 Agent 补充信息的效果。以会议室预定为例,详细的步骤如下所示:

最终实现的效果:

我们通过可视化调试界面加深下理解: 在第三轮对话的第一个 Thought 环节。第一条 system 为系统提示词,后续的 human、ai、human、ai 是前两轮的对话记忆,最后一个 human 才是第三轮对话的用户输入,这 6 消息整体作为入参 messages 发送给 LLM ,最后一条 ai 是这次调用 LLM 的返回结果。

会议预定 Agent 完整执行步骤如下:

问题 4 模型推理能力、响应速度

在实践中,我们遇到的最大问题是模型的推理能力与响应速度无法兼得。举例来说,当我以 「帮我预定 2 号楼 7 楼 今天下午 3 点到 5 点的会议室」 这个问题进行测试时,gpt-4.0-0613 模型分别以 19.07 秒、24.78 秒、19.01 秒完成任务,中间没有任何步骤推理出错。而使用 gpt-3.5-turbo-0613 模型时,在第一次测试时,Agent 调用的 tool 并不存在,导致任务失败,第二次测试时,Agent 第一步调用 tool 仍然不存在,但在第二步思考时,Agent 进行了纠正,整体完成任务耗时为 13.51 秒。第三次测试时,Agent 一次性完成了任务,仅耗时 8.09 秒。

下图为 gpt-3.5-turbo-0613 第二轮测试效果:

整体测试总结来看,gpt4.0-0613 可以以 100%的正确率完成任务,但平均解题耗时需要 20+秒,而 gpt-3.5-turbo-0613 虽然任务完成率只有 66% ,但整体耗时仅为 10.8 秒。

对于 gpt-4 的推理能力更强,应该是符合我们大家直觉的,但耗时更久却有点反直觉。我们随后查看了官方的文档,在文档中可以看到,gpt-4 的 出字速度确实是比 gpt-3.5 要慢上几倍,这是符合官方预期的。

受限于推理能力、响应速度难以兼得。当下想要将 Agent 正式投入生产环境,还是有一些挑战的。比如当我们把会议预定、咖啡预定 Agent 在公司 1024 的活动上推出时,部分用户身上表现出了一定的等待焦虑:「为什么还没有反应」「我还要等多久」「是不是挂了」。

在这里工程上能做的优化可能比较有限,比如除了 Loading 外,我们可以加入一些其他的响应提示,如 Agent 目前的思考步骤,以缓解用户的焦虑。

整体上,推理能力与速度的同步提升,还是较大依赖大模型厂商的逐步优化。正如 OpenAI 最新发布的 gpt-4-turbo-1106 在响应速度上就已经有了一些提升。我们相信随着推理能力和响应速度的提升,基于大语言模型实现的 AI Agent 在不远的未来会有大规模的落地的可能。

五. 总结

在 11 月的 OpenAI 的开发者大会上,官方同时也发布了最新的 Assistants API ,为构建 AI Agent 提供了官方支持,使得 AI Agent 的构建更加简单、高效。虽然官方的方案可能会演变为最终方案,但我们相信对 LangChain Agent 的实践不会白费,他会加深我们对 Agent 发展脉络的理解,而且使用过后,我们就会发现 Assistants API 的封装 与 LangChain Agent 有许多共通之处。我们后续也会对此进行跟进、实践,请大家继续关注我们,我们会第一时间分享我们的实践经验。

引用