作者:炯思、玦离
大模型出现伊始,我们就在SREWorks开源社区征集相关的实验案例。
玦离同学提供了面向大数据HDFS集群的智能体案例,非常好地完成了运维诊断的目标。于是基于这一系列的实验和探索,就有了本篇文章。
读者思路:
- 介绍什么是智能体
- 基于智能体的运维诊断工程框架
- HDFS集群智能体诊断实战
- 智能体工程框架进阶思路
一、初识智能体
智能体是什么
当前在大模型的推理应用场景,有RAG(Retrieval-Augmented Generation)和 Agent 两个热门的方向,本文将会重点阐述Agent这个方向的的应用,RAG相关的应用会出现在后续的系列中。
RAG: Retrieval-Augmented Generation 结合信息检索和文本生成的大模型工程。
Agent: 能自主执行任务的大模型工程。
RAG工程拓扑示意
Agent工程拓扑示意
"智能体"这个词其实是从英文Agent翻译过来的。Agent我们很熟悉,比如UserAgent:我们在访问网站时候,后端服务会鉴别我们用了哪个操作系统哪款浏览器,靠的就是这个字段。所以agent这个词在传统语境中,被翻译成代理更为合适:上网的用户是没有区分的,但是用户的设备有区分,这些设备都是他们的上网代理。
在这里就出现了一个问题:在中文语境下,当我们看到"代理"这个词之后,大家天然就会觉得代理是无状态的,感觉代理只是在透传背后操作者的指令或意图,体现不出Agent的主观能动性;而英文语境下,Agent则具有更强主观能动性。于是在中文的人工智能的语境下,我们将Agent变成了另外一个词:智能体。个体都具备有主观能动性,这个词很精准。大家可以反复斟酌一下这个主观能动性,后面我们还会提到。
从智能辅助到智能体
当前很多大模型相关比较常见的应用是Copilot,Copilot一词是飞机上的副驾驶的意思。在实际使用过程中,我们就会发现如果仅仅是智能辅助,补全一些代码,与我们的期望还有一些距离。我们的日常工作环境不仅仅只有代码,还会包含很多的工具、平台、流程;而很多机械重复的场景,基本上都已经有工具,智能辅助又较难帮助提效。
因此,作为智能辅助,如果要能够帮助到我们的日常工作,它的操作平面是工具、平台、流程更为合理:但我们是需要智能辅助帮我们写变更流程吗?是需要智能辅助帮我们审计风险吗?
其实到这个程度,智能辅助就已经不再是辅助了,应该被称作是智能体Agent了。
智能体和智能辅助的最大区别是就是主和辅的区别:
- 智能辅助只是辅助,根据指令来行动,并且给予执行反馈或者串联一些复杂的执行。
- 智能体具备主观能动性,可以根据需求去选择工具使用,进而达成目标。
二、基于智能体的运维诊断工程框架
在前两篇文章中,我们已经通过langchain进行了一些简单的应用,在本篇中,我们将基于langchain去构建一个能够用于运维诊断的智能体工程框架。针对不了解langchain的读者,我们这边简要介绍一下:langchain是一个旨在探索、开发和推广使用大语言模型进行编程、创造和自动化工程工具。
我们这里的诊断目标以完全开源的HDFS集群为例。
首先我们来分析一下我们日常运维诊断的场景:
- 收到一些问题反馈:流量下跌、某些功能使用异常。
- 排查这些功能相关的日志:找找日志中是否存在报错信息,通过报错的线索继续排查。
- 登录机器或实例排查:根据日志中的报错线索,登录对应运维实体用命令确认根因或者找到进一步排查的线索。
有些情况下,步骤2~3也会反复出现,并且3也不一定Linux OS,也可能是其他类型的运维对象。
langchain是一个能够给大模型加装调用工具的框架,其核心原理就是 ReAct(Reasoning and Acting),这部分原理我们在第一篇中已经讲述,在这里就不再展开了。
我们这里讲讲这个运维诊断的场景,我们需要如何基于langchain构建这个智能体。
首先根据我们刚刚场景的分析,我们可以知道需要一个查日志的工具、一个能够执行命令的工具。于是我们构建了下面这些工具:
名称 |
用途 |
|
在任意机器上执行shell命令 |
|
获取当前集群的namenode列表 |
|
在HDFS集群中执行touchz判断集群是否可写 |
|
获取指定namenode的日志,最近30行(不超过2000字符,超过则截断) |
|
指定机器执行df命令查看磁盘容量并返回 |
为了方便读者深入探索,诊断工具代码开源如下:
https://github.com/alibaba/sreworks-ext/blob/master/aibond/cases/hdfs-analyse/hdfscluster.py
这个时候有些同学就会有疑问了,这都有exec_command能执行任何命令了,再给一些namenode_log
、get_local_disk_free
这样的命令不是画蛇添足吗?事实上,在实际使用过程中,我们发现工具给得越具体,最终效果越好。这个特点大模型其实和我们人是一样的:大部分运维问题都能用Linux命令解决,但为什么还要封装运维工具?不就是为了少去记几个命令,使得运维专家能专注于关键问题,不用去思考刚刚输入的运维命令是不是少了个符号。
如果打开代码仔细看的读者,可能会发现一个细节:我们的这些tool和langchain的原生tool并不完全相同。我们在langchain框架之上引入了一层class的概念,使得tool不再是普通的function,而是可以做class实例化的function。
我们在第一篇文章中,提到过这样的面向对象的AI编程方法,之前大家读到的时候可能还不太有体感,当前的这个场景我可以举个例子,这种面向对象方式编程的优越性。
HDFS集群的每个Node通常不能直接登录,需要先登录到跳板机上,然后再登录到每台机器上。这就使得每次函数调用都必须包含跳板机的IP,比如查询日志的函数就会变成
namenode_log(gateway_host, host)
这时候大家平时编程时有体会,如果一个函数中有两个类似格式的入参变量,如果没有IDE提示辅助,很容易会搞错变量的位置。
同样的问题一样会发生在大模型上,所以面向对象的函数调用同样对大模型有友好,如果函数变成面向对象的写法,就能增加大模型调用函数的成功率:
cluster = new HDFSCluster(gateway_host) cluster.namenode_log(host)
三、HDFS集群智能体诊断实战
构建完成简单的智能体工程框架后之后,我们就要用来试试它是不是能真正地帮我们解决问题:我们先人为构造一个故障,然后看智能体能否分析出这个故障。
本次实战系列中的实验的3节点HDFS集群为开源大数据平台 E-MapReduce购买后一键搭建获得:
节点名称 |
内网 IP |
公网 IP |
硬盘信息 |
master-1-3 |
172.16.0.250 |
47.*.25.211 |
系统盘:80GB*1 数据盘:80GB*1 |
master-1-2 |
172.16.0.249 |
47.*.18.251 |
系统盘:80GB*1 数据盘:80GB*1 |
master-1-1 |
172.16.0.246 |
47.*.10.121 |
系统盘:80GB*1 数据盘:80GB*1 |
为了方便读者同样能够复现本次实战内容,我们同样把故障注入工具的代码也进行了开源:
https://github.com/alibaba/sreworks-ext/blob/master/aibond/cases/hdfs-analyse/fault_injector.py
这个故障注入工具能够利用fallocate命令将硬盘打满,使得文件系统无法正常读写。
基础实验
为了证明智能体能够真正分析出问题,我们来构造这样的3个基础实验:
- 集群正常运行,向智能体提问:这个集群正常吗?
- 集群中注入硬盘打满故障,向智能体提问:这个集群正常吗?
- 集群从硬盘打满故障中恢复,向智能体提问:这个集群正常吗?
这三个实验的提问prompt均相同,不存在额外信息提示,但集群现场完全不同,我们来看看智能体能否分析出来。
from aibond import AI from hdfscluster import HDFSCluster from langchain import OpenAI ai = AI() resp = ai.run("当前这个集群正常吗?", llm=OpenAI(temperature=0.2, model_name="gpt-4"), tools=[HDFSCluster("47.93.25.211")], verbose=True) print("=== resp ===") print(resp)
实验1 正常HDFS集群诊断
正常HDFS集群诊断
当前这个集群是正常的。
实验2 硬盘写满的故障集群诊断
我们利用故障注入工具将所有节点的/mnt/disk1
目录打满。我们再次运行诊断,向智能体发问,看看他的返回。
注入硬盘写满故障后的第一次诊断
当前集群存在问题,namenode在master-1-1.c-e4814c274586e7b4.cn-beijing.emr.aliyuncs.com的节点上反复关闭和启动,这不是正常的行为。
这里非常有意思的事情发生了:智能体先是按部就班地进行测试集群是否可以写入,很不凑巧的是恰好写入成功了,那么智能体是不是会给出集群是正常的结论?智能体保持严谨的态度,继续查了namenode的日志,它从日志中很准确地分析出,集群上节点反复的关闭和启动不是正常行为。这已经是一个非常专业的专家经验了。
鉴于第一次诊断中出现的不确定性,我们再进行一次诊断,看看结果是否会有不同:
注入硬盘写满故障后的第二次诊断
当前集群存在问题,主节点的日志显示它没有可用资源,'/dev/vdb'的可用空间为0,低于配置的保留量。集群无法正常运行,需要立即处理。
第二次的诊断与第一次的不同点在于抓取的日志出现了更多的线索,智能体直接从日志读取到了/dev/vdb分区可用空间为0
,这条重要的线索,使得智能体给出的诊断结论更具体。
综合两次的诊断,我们可以看出,智能体的诊断模式和人非常像:
- 智能体能够自主选择工具,借助工具来进行分析,有自己的推理过程。
- 单个工具可能会存在不确定性,智能体能够通过多个工具多条线索交叉验证。
- 得益于大模型前期海量的训练数据,智能体的专业知识表现能够与该领域的专家媲美。
实验3 故障集群恢复后的诊断
仅仅是分析有问题时的集群是不够的,如果集群恢复正常了,智能体也必须要能够分析出来,于是我们进行了实验3。将实验2注入的故障全部去除。
故障恢复后的诊断
当前这个集群是正常的。
在这次智能体的诊断分析中,我们发现即使日志数据中,有很多前面硬盘故障时候的干扰项,它依然能够做出准确的判断:集群是正常的。说实话,这表现可能已经超越了一些运维人员了:因为在故障分析处理中,通常会伴随看似有用的报错线索,而在纷杂的信息中滤掉无用信息,最终能拍板结论的,只有一部分比较有经验的专家才能做到。
进阶实验:根因定界
在三个基础实验中,我们可以看到智能体进行的诊断基本都能达到甚至超过我们预期,而我们在prompt中其实根本没做什么,只是问了问这个集群是否正常。那么如果我们将prompt中的提问再复杂一些,是不是就能获得更好的结果?我们尝试了让智能体来帮我们进行一下问题定界的处理--每次出线上问题之后,最头疼的问题就是定界,到底是软件本身的问题,还是用户使用的问题?非常容易说不清。我们来看看智能体是否能理清楚(我们将现场复原成注入了硬盘打满的现场)。我们将提问prompt变成如下的文本:
请帮忙诊断一下这个集群,并且在结论中给出根因的定界:软件缺陷(software_bug) 或 用户使用问题(user_problem),并使用JSON格式返回
{"cause": "software_bug|user_problem", "suggestion": "给出的建议"}
{"cause": "user_problem", "suggestion": "The NameNode has no resources available, and the space available on volume '/dev/vdb' is 0, which is below the configured reserved amount. Please free up or add more resources to the volume '/dev/vdb'."}
智能体直接用JSON格式将结论给出,非常清晰地把这个问题界定成是一个用户使用问题。这个格式结构甚至可以直接集成到工单系统中:如果每次用户提工单需要排查问题,就直接花1分钟这样分析一波给出结论,画面有点太美了,大家可以畅想一下。
实战总结
- 智能体借助工具排查问题得出结论的整个过程与人类行为已经基本没有大的差别。
- 智能体基本没用exec_command这种通用工具,而是选择合适的垂直工具(get_namenodes、namenode_log等),这点与人的行为非常相似。
明确好用的小工具会比大而全的工具更能提升效率和表现。
3. 大模型已有的知识数据对于专业型的分析完全够用,不需要额外的知识库。
四、智能体工程框架进阶思路
前面章节的实战仅仅面向一个简单的3节点的HDFS集群,实际上的生产集群远比这个复杂,因此我们需要构建适合自己场景的智能体工程框架。那么如何构建呢?
智能体框架原理
刚接触langchain的时候,感觉这工程框架很神奇,像是开了挂一样,原本的大模型是不能接触外部世界的,而有了langchain的tool之后就能随意接触外部世界了。后来读了ReAct的论文之后,明白了原来只要用固定的格式构建prompt,就能实现思维链的效果,不断调用一些工具,去推理达成目标。下面截取了框架最关键部分prompt大家自行感受一下:
System: Respond to the human as helpfully and accurately as possible. You have access to the following tools: get_namenodes: get_namenodes() -> str - Get the namenode list of the HDFS cluster., args: {{}} hdfs_touchz: hdfs_touchz() -> str - Create a test file in HDFS., args: {{}} namenode_log: namenode_log(host: str) -> str - get one HDFS cluster namenode's log., args: {{'host': {{'title': 'Host', 'type': 'string'}}}} Use a json blob to specify a tool by providing an action key (tool name) and an action_input key (tool input). Valid "action" values: "Final Answer" or get_namenodes, hdfs_touchz, namenode_log Provide only ONE action per $JSON_BLOB, as shown: ``` { "action": $TOOL_NAME, "action_input": $INPUT } ``` Follow this format: Question: input question to answer Thought: consider previous and subsequent steps Action: ``` $JSON_BLOB ``` Observation: action result ... (repeat Thought/Action/Observation N times) Thought: I know what to respond Action: ``` { "action": "Final Answer", "action_input": "Final response to human" } ``` Begin! Reminder to ALWAYS respond with a valid json blob of a single action. Use tools if necessary. Respond directly if appropriate. Format is Action:```$JSON_BLOB```then Observation:. Thought: Human: 请帮忙诊断一下这个集群,并且在结论中给出根因的定界:软件缺陷(software_bug) 或 用户使用问题(user_problem),并使用JSON格式返回{"cause": "software_bug|user_problem", "suggestion": "给出的建议"}
这个prompt结构的重点在于2个:
- 把所有的工具的输入输出和功能描述转化成列表。
- 告诉LLM在没有结束之前,需要按照 Question: -> Thought: -> Action: 这样的方式重复N次最终给出结论。
因此,如果需要在已有的系统中嵌入智能体,甚至不需要使用langchain,只要需要自己手搓一个解析字符串框架,就能实现思维链。同时,接入生产的大模型都是国产大模型,可能有些还无法达成标准的思维链交互。这时候,这个字符串解析框架就需要稍微多做一些,比如抛弃JSON结构,使用更多的正则解析;利用字符串相似度去修改正调用参数的不正确等。我们在这里也就不过多展开了,大家可以根据自己手上能用的大模型自行探索。
智能体工程优化点1:适时总结
在智能体的实践过程中,我们发现一些非常有趣的表象:在大模型中,记忆状态会降低模型的推理能力。这一表象在运维诊断中尤其显著,如果要排查20台机器,可能排查到第10台的时候,token限制还没到,但是大模型已经可能有些糊涂了。反观人也是同样,排查信息量大时,如果注意力没有高度集中,分布式系统中ip地址又是如此相似,很可能多查几轮就会有点迷糊了。这个时候人会做的一件事情就是用小本本做笔记总结,把前面的排查过程总结到一个文档中。如果整个排查链路太长,文档总结得太长之后,我们又会再次总结,把中间细节都去除,只保留几个简明的结论。
事实上智能体的框架也同样可以做这样的优化,思维链中Question/Thought/Action的过程就像我们的第一轮笔记,如果过程一长,我们就需要再次总结笔记,使得智能体能够继续聚集于问题的主链路中。所以我们可以看到:适时的总结,能够降低大模型状态记忆负担,提升推理能力。
智能体工程优化点2:业务对象智能体化
另外一条给大模型状态记忆减负的路径,我们可以站在巨人的肩膀上,那就是让智能体面向业务对象进行会话。从前面的第三章我们能够看到,我们整个智能体的实验是基于面向对象的tool,但我认为这还不够,这还不是真正的面向对象。那么怎么是真正的面向对象呢?我下面举个例子让大家有个比较深的感受:
我们构建智能体之间的对话,整个对话会出现了这样4个角色的对象:专家、服务1机器组、服务器10.1.1.2、变更平台。
- 专家:具备专家经验,能够分析解决复杂的问题,但对于很多运维细节,需要其他运维对象的支撑。
- 服务1机器组:管理其组内的4台服务器,遇到组内单个服务器故障,会依据80端口健康检查结果自动切流。
- 服务器10.1.1.2:对本机各种系统运行情况了如指掌。
- 变更平台:管理所有运维变更,针对每个运维变更均记录了运维对象实体。
下面开始4个对象之间的对话:
这样通过智能体间的对话解决问题也并不是我们一开始就想到的,一开始我们是打算使用效仿日常的工作协作拓扑(该思路在第一篇中有提到):用一个Leader带领多个专家分析解决问题,由Leader来做最终汇总---但最终每次对话token花了一大堆,效果并不理想。这里面最关键的问题就是多加了一层专家抽象之后,每次分析都是专家在用工具分析给你结论,多花token耗时长不说,分析参数有时候专家还会搞错。同时,一般专家角色的划分就是Linux专家、网络专家这类,但从上面的例子我们可以看到,这样的角色划分来解决问题也会非常别扭,一个问题可能需要Linux专家和网络专家之间讨论半天才能有所进展,而他们的结论还要由Leader去汇总,想想就复杂。
于是,我们考虑将减少协作的拓扑层级,希望在一个会话流中将问题解决,不再做类似MapRedue架构了。这时候,我们就想到了面向对象编程:对象概念本身只是在编程阶段,我们如果把对象的概念引入到运行时中--每个运维对象都会自己会讲话,会管理自己,会描述自己的情况。那么是不是只要有个专家在顶层问问他们,是不是就能把问题了解清楚了?于是大家就看到这样的例子。
在这个例子中,很重要的一点就是,作为专家,不需要去了解太多的技术细节:他不需要去知道流量切换的原理是什么,使用TCP的4层探测的还是用HTTP的7层探测。这就使得专家这层的经验具有足够的通用性,使用相似的分析行为可以分析流量下跌、作业失败、功能失效等。
不能说这样的例子在所有场景都适用,至少在运维分析诊断场景下,他能够发挥自己的作用。同时,这些智能体也能够直接被接入到工作群中,如果你嫌这个智能体专家分析不够清晰,你可以打个样,你可以亲自上阵去@各个运维实体进行分析,也是完全可行的。
五、总结
本文向大家展示了一个通过智能体完整地对HDFS集群进行问题诊断的案例,智能体能够非常好地调用合适的工具来解决问题,而且从旁观者看来,整个过程也完全符合日常问题排查的推理过程,甚至有些表现还能达到专家的水平。
一般公司接入大模型方案会考虑数据安全问题,会采用自研或可控的大模型。于是针对自研类大模型的prompt工程框架变得至关重要。
通过大量的实践,结合上面的两个例子,我们发现可以通过两种路径,工程化地减少智能体推理过程中的状态记忆负担:
- 阶段性的总结:收敛状态记忆数据。
- 业务对象智能体化:收敛工具和状态细节于智能体内部。
基于顶层的分析型智能体与业务对象智能体交流来解决问题,使得主分析思维链的状态尽可能少,目标尽可能聚焦。
欢迎大家根据自身不同需求,构建出更多的垂直领域的智能体工程。
参考材料:
- Zhiheng Xi, et al. “The Rise and Potential of Large Language Model Based Agents: A Survey” https://arxiv.org/pdf/2309.07864.pdf
- https://python.langchain.com/docs/use_cases/tool_use/
- https://generativeai.pub/rags-from-scratch-indexing-dab7d83a0a36
- https://thenewstack.io/microsoft-one-ups-google-with-copilot-stack-for-developers/
有兴趣的2025届毕业生也可以直接申请实习职位,加入大数据基础工程技术团队,我们一起探索,玩转AI基础设施。