背景

ChatGPT的问世无疑在人工智能领域掀起了巨大的波澜。然而,随着时间的推移,人们逐渐发现ChatGPT更像是被束缚在一个容器中的大脑。因此,人们开始尝试赋予它更多的功能,就像给它装上四肢一样。这种尝试催生了LangChain、AutoGPT和BabyGPT等新的技术,它们在人工智能领域引起了广泛的关注,并逐步揭示了通用人工智能(AGI)的发展趋势。
这些新的技术不仅提供了更多的功能,而且还开启了新的可能性。例如,LangChain利用语言模型来生成和解析自然语言,AutoGPT则是一个自我监督的生成模型,可以生成各种类型的文本。这些技术的出现,使我们对通用人工智能的理解更加深入,也为未来的人工智能研究和应用开辟了新的道路。
基于这些技术或思想的RAG/Agent应用,也如雨后春笋般迅速崛起

现状

Autonomous Agent

Autonomous agent 指的是完全由 LLM 自驱的规划工作流、并完成任务的 Agent 产品。这一类典型的代表就是AutoGPT,而AutoGPT子发布之日起,在短短不到3个月的时间,star数量就超过了PyTorch,可见其影响力之大
image.png
但随着使用深入,发现其指导意义大于实用性,大部分问题并不能真的解决:

  • 效果不稳定:对于简单的问题,AUtoGPT是可以解决的,但是对于复杂问题,AutoGPT有效率不足10%,其原因可归结为两类

    • 缺少足够的信息推理:AutoGPT的推理对自身的依赖比较强,在缺乏足够的工具下,很容易陷入信息盲区;其次,AutoGPT的推理对上下文依赖比较强,一步推理出错,后面就很难纠正回来
    • 推理死循环:AutoGPT没有外部的介入,很容易对规划产生死循环
  • 生态较弱:AutoGPT的API大部分都是围绕文档和搜索展开的,第三方支持的API不多,其次,由于Token的限制,导致注册的API数量也不能太多

    Vertical Agent

    Vertical Agent 则是深入某个垂直领域,理解该领域专家的工作流。这一类的典型代表则是以LangChain框架为主的各类垂直领域Agent,这类Agent相对于Autonomous Agent的优势在于

  1. 领域知识:Agent 需要理解并掌握领域知识,以便于处理特定领域的问题。这可能包括专门的训练数据、领域专家的输入等。
  2. 工作流理解:每个垂直领域都有自己的工作流,Agent 应用开发者需要选择具体场景,理解真实用户的工作流程,并探索如何与 AI 协同
  3. 数据反馈:收集反馈数据的机制是非常重要的,当Agent推理出错的时候,可以通过Tool进行反馈以矫正整体的推理路线
  4. 多 Agent 协作:在某些情况下,可能需要多个 Agent 合作来解决问题。例如,一个 Agent 可能负责收集数据,另一个 Agent 负责分析数据,再有一个 Agent 负责做出决策。这就需要一个强大的协作机制来支持这些 Agents 的合作

    AI带来的思考

    我们每天被应急所缠绕,是否可以让llm来帮我们处理?应急的工作流程是什么,llm来做的话,他应该怎么做?
    因此,我们梳理了日常的应急大致流程
    image.png

在这里,我们可以看到流程大致如下

  1. 首先获取报错信息,定位跟报错相关的产品(Reason)
  2. 去查看相关产品的监控、相关机器的日志、相关机器的变更和配置(Action)
  3. 获取相关的数据和信息(Observation)
  4. 进一步去查询相关信息(Action)
  5. 进一步相关的数据和信息(Observation)
  6. 最终给出最终的结论

首先,在应急场景下,我们优先考虑Vertical Agent,而这套流程,也正好匹配了ReAct模型,而去应急的这个人就是一个Agent,那基于单Agent的React模型的LangChain框架就成了我们首先的对象

  1. Reason => LLM
  2. Action => Tool
  3. Observation => Tool执行结果

基于此,我们也明确了我们应急场景的探索模式
模式:
基于LangChain的ReAct模型,以GPT(LLM)作为大脑,进行推理,各种Tool来获取最新准确的数据,以最终定位问题所在
目的:

  1. 通过AI来加速获取问题排查相关的数据,加速人工排查过程
  2. 通过LLM大脑的初步推理,协助应急人员进行故障判断和决策

image.png

LangChain简介

首先,我们先简单介绍一下LangChain

概念

LangChain 是一个开发由语言模型驱动的应用程序的框架。我们相信最强大和不同的应用程序不仅会通过 API 调用语言模型, 还会:

数据感知 : 将语言模型连接到其他数据源

具有代理性质 : 允许语言模型与其环境交互

LangChain 框架是基于以上原则设计的。

在早期GPT中,GPT的知识是有限的,类似于gpt3.5只到2021年,也没有从外部获取知识的能力(function call是在6月中旬才推出的)
LangChain的设计也就正如它的名字一样 lang(LLM) 和 chain (连接)组成,它的目的是将LLM与数据和代理进行连接,让LLM增加知识的同时,也能增加行为能力

ReAct

概念

LangChain如何将LLM与知识和行为进行连接的,这就是ReActk框架,通过Reasoning and Acting构建自治代理,简称ReAct

ReAct方式的作用就是协调LLM模型和外部的信息获取,与其他功能交互。如果说LLM模型是大脑,那ReAct框架就是这个大脑的手脚和五官。同时具备帮助LLM模型获取信息、输出内容与执行决策的能力。对于一个指定的任务目标,ReAct框架会自动补齐LLM应该具备的知识和相关信息,然后再让LLM模型做出决策,并执行LLM的决策。

一个ReAct流程里,关键是三个概念:
Thought:由LLM模型生成,是LLM产生行为和依据。可以根据LLM的思考,来衡量他要采取的行为是否合理。这是一个可用来判断本次决策是否合理的关键依据。相较于人类,thought的存在可以让LLM的决策变得更加有可解释性和可信度。
Act:Act是指LLM判断本次需要执行的具体行为。Act一般由两部分组成:行为和对象。用编程的说法就是API名称和对应的入参。LLM模型最大的优势是,可以根据Thought的判断,选择需要使用的API并生成需要填入API的参数。从而保证了ReAct框架在执行层面的可行性。
Obs:LLM框架对于外界输入的获取。它就像LLM的五官,将外界的反馈信息同步给LLM模型,协助LLM模型进一步的做分析或者决策。
一个完整的ReAct的行为,包涵以下几个流程:
1.输入目标:任务的起点。可以是用户的手动输入,也可以是依靠触发器(比如系统故障报警)。
2.LOOP:LLM模型开始分析问题需要的步骤(Thought),按步骤执行Act,根据观察到的信息(Obs),循环执行这个过程。直到判断任务目标达成。
3.Finish:任务最终执行成功,返回最终结果。

Demo

image.png

Agent

在langchain中,实现了ReAct框架的组件,就是Agent
image.png
Agent是基于大型语言模型(LLM)和工具(Tools)的ReAct实现

  • 大型语言模型(LLM):LLM是代理的核心,负责理解输入和生成输出。在LangChain中,LLM可以是任何能够理解和生成文本的模型,例如OpenAI的GPT-3。
  • 工具(Tools):工具是代理用来获取和处理信息的模块。例如,Serp API工具可以进行网络搜索,llm-math工具可以执行数学运算。工具的结果会被传递给LLM,LLM会使用这些结果来生成响应。
  • ReAct框架(React Framework):反应框架定义了代理如何处理输入和生成输出。在LangChain中,反应框架是一个函数,它接收输入和工具的结果,然后调用LLM来生成响应。反应框架可以根据需要定制,以实现特定的行为和功能。

Agent的工单流程是一个 Loop循环:

  1. Agent接收到用户的输入和可以使用的工具,将工具的描述一并发给LLM
  2. LLM判断是否需要使用工具,如果需要使用,则给出工具的入参
  3. Agent获取到LLM的输出后,解析需要调用的工具和参数,执行调用
  4. 将上一步工具的输出,作为新的输入和用户输入一起给到LLM
  5. LLM继续判断是否需要使用工具,如果需要的话,重复2-4步骤
  6. 知道LLM可以给出最终结果,Agent解析最终结果并返回给用户

Indexes

在LangChain中,构建RAG应用的,另一个重要组件,就是这个Indexes了
由于gpt的token限制,我们并不是每次都能把文档完全传给gpt,然后再让gpt回答的,那就需要我们对文档进行处理,而indexes就是做这个的

先举个🌰
我们有一篇文档(鲁迅的《藤野先生》),我们期望gpt根据这个文档回答一个问题(当然,这篇文档的token数量不多,交给gpt完全没问题,这里我们假设超出了gpt的token)
Q: 鲁迅是在哪里学医的
从我们人类的角度出发,要回答这个问题,我们需要首先扫一下文档,定位到答案所在的位置,然后根据答案我们给出回答

  1. 定位答案所在位置

      我就往仙台的医学专门学校去。从东京出发,不久便到一处驿站,写道:日暮里。不知怎地,我到现在还记得这名目。其次却只记得水户了,这是明的遗民朱舜水先生客死的地方。仙台是一个市镇,并不大;冬天冷得利害;还没有中国的学生。
  2. 给出答案

    仙台

以上的流程,就是indexes的工作逻辑
image.png
存储

  1. 用户输入文档,对应藤野先生这篇文章
  2. 对文档进行切割,按照某种格式/字符数量等切割方式,切割成chunk
  3. 对每个chunk进行向量化
  4. 将向量化的结果及chunk存储到向量库(chromadb/fassis等)

查询

  1. 用户输入一段query
  2. 根据query,去向量库匹配相关信息
  3. 拿到相关信息后,组装prompt,扔给llm
  4. llm根据已知信息,给出最终答案

    应急场景实践

    ChatGPT有自己的局限性

  5. 实时性:ChatGPT的知识是基于其训练数据集的,而这些数据集通常截止于特定的日期。因此,它对于在该日期之后发生的事件或最新的科技、文化发展等缺乏了解。
  6. 准确性:ChatGPT的回答是基于训练数据的,这些数据可能过时或者不准确,同时ChatGPT对于训练数据外的提问,并不能给出答案
  7. 推理能力局限性:尽管ChatGPT在处理语言和生成连贯文本方面表现出色,但它的理解能力并不等同于人类的理解。它可能无法准确把握复杂的概念、隐喻或深层含义。

但是,LangChain的玩法给了我们一条解决的思路

  1. 实时性:通过提供各种tool供gpt进行交互来获取实时的信息
  2. 准确性:无需gpt提供数据,我们提供数据给gpt,通过gpt的推理能力来完成回答
  3. 推理能力局限性:对于一些特殊的概念,/host等概念,通过prompt提示工程来完成,类似于host,告诉gpt是 \w+-[\w+-]\w+-\w+ 这种形式,而不需要fine-tuning来局限

接下来,就是我们依托以ReAct框架、langchaingo和ChatGPT做的在应急场景方向的实践

框架及功能完善

我们这里选用了开源版本的langchaingo进行二开
选用langchaingo的原因是因为,个人对go更加熟悉,而python则接触较少

当然,我们对langchaingo增加场景所需要的一些能力和扩展

  1. BUG修复:这在使用过程中,逐步踩坑逐步优化;常见的如:向量化后没有score,outparser不能正确解析等
  2. 域内ChatGPT中枢支持: 针对蚂蚁中枢的ChatGPT,我们实现了其接入;并且针对同步限流问题,我们优化为了异步模式,限流的情况近乎降为0
  3. 强化MarkDown分词切割: 域内文档均以markdown为主,所以我们实现了更优的markdown切割方案,在向量化中,优化后的markdown切割方案明显比原始版本的markdown切割方案要优,效果大大提升
  4. OpenAPI3.0 tool支持: 由于考虑tool的开发成本和域内现有的api,一个个将api手动转化为tool的工作量过大,所以我们整合了openapi3.0的规范,支持将 openapi3.0 schema自动生成对应的tool,用户无需再写tool,只需要将接口导出为yaml文件,即可自动转化为对应的tool,这对扩展能力有了很大的提升,开发效率也大大提高
  5. m3e-large支持: openai的embedding对中文效果还是欠缺一点,所以在框架中,我们实现了m3e本地embedding的支持;当然,由于gpu资源的问题,我们还是使用openai的embedding
  6. 应急专属Agent: 我们实现了针对应急场景专用的Agent,增强了Output的解析能力和 Agent对向量库的访问能力,以在ReAct中,强化检索和感知能力
  7. COS相似度算法: 对于日志的查询,给一个关键字,可能会查出上百条记录,而这些记录可能就是trace 时间不同,内容基本都差不多,所以,我们基于COS相似度算法,将查询的日志信息进行压缩
  8. 提供精度搜索工具: 对于日志解析,错误码解析等高精度场景,我们提供了精度搜索的能力,而避免了向量搜索的不准确性影响最终准确率

框架仓库地址

so,talk is cheap, show me the code
域内langchaingo二开版本:https://github.com/tyloafer/langchaingo

架构

image.png
我们将整个应急场景构建了三层

  1. Agent层:

    1. agent主要是跟llm进行交互,通过ReAct的模型,来明确llm需要的数据,通过选择对应的工具来提供不同阶段所需的数据,保证推理的有效性和真实性
    2. 定义和优化agent的prompt,确保llm的所具备的能力和推理行为符合预期
  2. Tool层:

    1. 实现了对antmonitor、shell、zappinfo、paas变更信息等工具,以从各个平台上获取llm推理过程中所需要的真实准确的数据
    2. 定义和优化prompt,保证工具的使用方式和能力可被llm正确感知
  3. 知识库层:我们使用优化后的md splitter来对文档进行切割,切割后的文档使用openai embedding(后续会有改变)进行向量计算,并最终持久化存储到ChromaDB里面,作为agent的知识储备

    流程图示

image.png

场景区分

我们将具体场景划分为应急场景和业务问题定位场景

应急场景

根据历史故障,总结出来的复盘文档,runbook,进行文档总结和向量化,当一个报错信息输入的时候,llm会根据上下文总结出来用户的场景,并精确搜索出来应急流程,根据文档的思路和自身的推理能力来运行

业务问题定位场景

业务定位更多和业务自身是有关联的,类似于我们尝试的具体场景

  1. 消息中心分区消费延迟
  2. 消息中心投递失败
  3. 消息中心投递链路分析-订阅端,投递耗时等

对于这种场景,我们更多的是让llm去理解我们的业务设计和架构,提供所需的工具,由llm去自推理的完成定位的过程

问题及挑战

框架和设计完成了,并不代表就能真正的在这个场景中发挥效用,在适配应急场景的过程中,我们也面临着各种问题和挑战

领域知识不完备

由于我们是使用ReAct框架实现的Agent应用,并没有对LLM进行fine-tuning,导致LLM对域内的概念并不清楚;例如:zone/LDC/host这些,LLM在这上面的推理能力接近为0
因此,

  1. 我们沉淀了runbook去引导LLM的推理,沉淀了业务结构设计文档,去帮助llm理解产品

在这里,我们对runbook再度进行总结压缩和修正,以提高LLM识别的能力

同时对于业务文档,我们通过llm自动取总结,人工交互反馈以提高最终文档的简洁和准确性

  1. 我们对一些概念名词做了Prompt的矫正

🌰:
Host: format: (\w+-[\w+-]\w+-\w+)
DBPart: the input mush be like xxxx@xxx@xxx

  1. 我们在Tool里面,对LLM输入进行了容错处理

🌰:
"test-xx-xxx" => test-xx-xxx // 去除多余符号
"test-xx-xxx,test-xx-xxy" => ["test-xx-xxx","test-xx-xxy"] // 对,分割切割成数组
"test-xx-xxx.doamin.com" => "test-xx-xxx" // 去除多余后缀信息

  1. 工具的描述依旧很重要

工具描述不清晰,则会导致LLM并不会选用这个工具,而导致工具无效
我们对当前提供的各类工具的Prompt进行不断地测试及修正,最终得到了不错的效果
🌰:
Host Search: useful for when you need to get more information / status about host, the input should be like \w+-\w+-\w+ or \d+.\d+.\d+.\d+

基于这些对域内概念和Tool的Prompt描述和容错处理,LLM对我们所需的这些名词、Tool选择的识别和处理成功率提升了80%以上

向量搜索匹配度不达标

我们考虑一下下面的情况

# 鲁迅的生平简介
(1881年9月25日 - 1936年10月19日),原名周树人,字豫才,是中国现代文学史上具有重要影响的文学家、思想家和革命家

# 鲁迅的早年经历
出生在浙江绍兴,家境贫寒。他在青年时期先后留学于日本,学习医学。这段经历对他后来的文学和思想发展产生了深远影响。

# 鲁迅的作品简介
- 《狂人日记》: 小说以第一人称的方式叙述,揭示主人公在封建社会无法容忍的压力下,逐渐走向疯狂的心理历程。

- 《阿Q正传》: 这部小说通过对主人公阿Q的描写,深刻反映了封建社会的虚伪和无情。阿Q的形象成为中国文学中一个极具代表性的形象,体现了中国传统文化中的某些弊端。

- 《呐喊》: 多篇揭示社会黑暗、揭露人性丑恶的短篇小说,如《药》、《祝福》等。这些作品以强烈的激进主义和独特的文学风格引起了广泛关注。

- 《彷徨》: 包括《阻止》、《风波》等短篇小说。作品通过对个体在社会冲突中的彷徨、痛苦的描写,反映了那个时代的社会动荡。

- 《朝花夕拾》: 其中的散文以纪实手法,回忆鲁迅的童年、青年时期以及对社会变革的思考。作品以真实的历史记忆和个人经历,展现了作者对中国社会变革的关切。

当我们使用常规的markdown切割的时候,大概率会把 标题和内容切分开,形成不同的chunk来进行向量化(当然:如果将切割的字数无限制放大,也是可以满足需求的,这就违背了最开始切割的意图了)
当我们进行query: 帮我简单介绍一下鲁迅`鲁迅有哪些作品`时,就不能很好的关联上对应的信息了
而针对markdwon来说(或者部分同学的使用习惯而言),header里面是会包含一些关键信息的,包括且不限于 内容的简介

那这种切割方式,就很难得到完美的答案
所以,第一步,我们会对markdown分档进行拆分

支持,将header和内容分开,同时保存内容所属header的信息,header切分可以无限,但常规也就5个层级

然后 我们对content内容进行常规的文本切割**RecursiveCharacterTextSplitter** 设置一定的buf,尽量避免在切割时,上下两段chunk不能很好的链接
对切割好的content的chunk,我们会再将当前content上面挂的所有header 信息存储到metadata里面或连接到chunk上面,以保证向量搜索的可靠性

通过这种方式,对于上文切割,大致效果如下

chunk1:
# 鲁迅的生平简介
(1881年9月25日 - 1936年10月19日),原名周树人,字豫才,是中国现代文学史上具有重要影响的文学家、思想家和革命家

chunk2:
# 鲁迅的早年经历
出生在浙江绍兴,家境贫寒。他在青年时期先后留学于日本,学习医学。这段经历对他后来的文学和思想发展产生了深远影响。

chunk3:
# 鲁迅的作品简介
- 《狂人日记》: 小说以第一人称的方式叙述,揭示主人公在封建社会无法容忍的压力下,逐渐走向疯狂的心理历程。

chunk4:
# 鲁迅的作品简介
- 《阿Q正传》: 这部小说通过对主人公阿Q的描写,深刻反映了封建社会的虚伪和无情。阿Q的形象成为中国文学中一个极具代表性的形象,体现了中国传统文化中的某些弊端。

chunk5:
# 鲁迅的作品简介
- 《呐喊》: 多篇揭示社会黑暗、揭露人性丑恶的短篇小说,如《药》、《祝福》等。这些作品以强烈的激进主义和独特的文学风格引起了广泛关注。

chunk5:
# 鲁迅的作品简介
- 《彷徨》: 包括《阻止》、《风波》等短篇小说。作品通过对个体在社会冲突中的彷徨、痛苦的描写,反映了那个时代的社会动荡。

chunk6:
# 鲁迅的作品简介
- 《朝花夕拾》: 其中的散文以纪实手法,回忆鲁迅的童年、青年时期以及对社会变革的思考。作品以真实的历史记忆和个人经历,展现了作者对中国社会变革的关切。

而搜索的效果也大大提升

精确搜索

我们简单理解一下向量搜索,如果我们把每一个单词看作向量,king减queen之差与man与woman之差应该是近似的,都代表着性别的差异。

27bf2639-b729-4ac0-9b09-3a94707f72f1

而我们在实际的某些场景中,对精度要求比较高;类似于,我有三个日志文件格式的定义

  1. a.log
  2. b.log
  3. c.log

在向量化后,他们的相似度都是很高的,那我在搜索 a.log 的时候,就有概率返回的是 b.log的格式,这在应用过程中是坚决不允许的

所以,精确搜索就是保证搜索的高精度的

对于精确搜索,我们的处理策略是:

  1. 限制llm的输入范围,在tool的使用规范里面,明确限定input可选的列表
  2. 基于数据库去进行查询,对llm的属于进行容错处理后,再到数据库里面搜索准确的解决
  3. 当然上一步的容错,能解决掉95%的问题,对于剩余的5%的可能性,就重新返回给llm,让他进行自检

生态问题

虽然框架实现了各个平台的基本能力,但是能力还是远远不够的,其次,人为实现这些工具耗时耗力
我们需要一种能快速扩充生态的方式
所以我们思考了一下,既然域内已经接了openapi,我们是不是可以以openapi作为我们的中转层,来降低我们的开发量

基于此,我们实现对openapi schema的解析和请求构建

根据解析出来的信息,我们将desc和 query header body 参数组装成 api的prompt,来实现api转tool的泛化能力
再将llm输入的input信息,反解析并构建请求,获取相应返回给agent来进行推理

Tool返回的信息数据量过大

这一问题主要集中在日志搜索过程中,Agent是以关键字来搜索日志的,并不一定每次都用trace,那就会存在一个问题,同一个报错时刻都在发生,可能就会搜索出来上百乃至上千条的日志信息
在第一阶段,我们尝试将日志搜索范围降低,按照时间排序,筛选出来最近的100条,但这种解决方案很容易遗漏关键信息
基于此,我们尝试了第二阶段处理方案-COS相似度算法
对于以下的文本信息

Error: failed to connect to 11.11.11.11 123456678
Error: failed to connect to 11.11.11.11 123456688
Error: failed to connect to 11.11.11.11 123456698
Error: failed to connect to 11.11.11.11 123456679
Error: failed to connect to 11.11.11.11 123456670
Error: failed to connect to 11.11.11.11 123456671
Error: failed to connect to 11.11.11.11 123456672
Error: failed to connect to 11.11.11.11 123456673
Error: failed to connect to 11.11.11.11 123456674
Error: failed to connect to 11.11.11.11 123456675

经过相似度算法压缩(这里假设设置的阈值为0.8)之后
我们就可以得到一下的文本

Error: failed to connect to 11.11.11.11 123456675

而这个结果跟我们人为经验也是一致的,同时避免了关键信息流式的可能性

openai embedding对中文的支持较弱

embeding这块也是poc效果的一个对比,我们发现 m3e-larger 的效果还是不错的,在中文场景下,并不比openai text-davince-002 弱
所以,在 内部的langchaingo 中,我们增加了对m3e的支持,有兴趣有资源的同学可以尝试一下

最终效果

这里就不展示了,有数据规范嫌疑

未来展望

在当前的实验中,我们简单梳理了一个产品的文档,就可以去定位排查这个产品的50%的场景,那随着这个文档的完善,LLM对这个产品越来越熟悉,那是不是就可以解决这个产品剩余的问题场景了?基于这种思路,我们将这种方式扩散到整个中间件的产品,mesh层、注册中心、配置中心、定时任务层面,每个产品实现一个Agent,在出现问题的时候,让Agent之间去进行沟通,协作,是不是就可以实现中间件所有定位应急场景了?

举个例子:

mesh侧产生了报错,mesh Agent定位出来,这个报错跟注册中心有关,然后把问题和可能的原因交给注册中心Agent,注册中心Agent根据自身知识进行排查,最终解决了问题,并返回给最终用户

上述其实是讲了我们中间件团队的玩法,我们将这个扩散到整个人类社会是不是也可以这样玩

这就是Multi Agent的最终愿景,我们也在持续探索中

未来已来,而我们正当时

参考文档


tyloafer
814 声望228 粉丝