Harness Engineering:智能体任务执行可视化
第一篇从混沌到洞察——Harness Engineering与智能体可视化的诞生第1章 当LLM驱动的智能体像“黑盒子”奔跑任务执行可视化的痛点爆发12,347字1.1 核心概念什么是任务执行混沌期的LLM智能体什么是Harness Engineering视野下的“可观测性缺口”1.1.1 基础铺垫从SWE-agent到AutoGPTLLM智能体在做什么在过去的两年里也就是2022年底ChatGPT爆发后的黄金发展期我们看到了一个全新的软件工程分支——或者说更泛的AI任务自动化与执行领域——突然炸开2023年3月AutoGPT带着“无需人类干预自主完成长期复杂目标”的概念刷爆全网GitHub星标在一周内突破100k同年10月Stanford AI Lab和UC Berkeley的SWE-agent在SWE-bench测试集上达到了15.6%的任务解决率第一次把智能体和“解决真实软件Bug、提交可通过CI的PR”这种硬核SWE任务绑定再往后像LangChain、AutoGen、CrewAI这种框架层工具层出不穷把智能体从“单枪匹马”带到了“多智能体协作、角色扮演、任务拆解嵌套”的新阶段。那到底什么是LLM驱动的任务执行智能体我们可以先给它一个非学术、面向工程实践的简单定义LLM驱动的任务执行智能体是一种封装了感知层Perception Layer、推理层Reasoning Layer、动作层Action Layer、记忆层Memory Layer四个核心模块以“大语言模型的链式推理Chain-of-Thought, CoT/Step-by-Step Reasoning、工具调用Tool Calling/Function Calling、反思Reflection”为核心能力能够在给定的明确或半明确目标下自主规划子任务序列、调用指定工具/API/环境接口、感知执行结果并动态调整策略、最终交付目标成果的自动化执行单元。为了让这个定义不那么抽象我们可以用SWE-agent修复一个Python Web应用假设是Django的TodoList应用的“待办事项无法标记完成”的Bug这个真实的小场景来拆解它的四个核心模块和一次任务执行的完整流程1.1.1.1 任务执行智能体的核心模块拆解Django TodoList SWE场景我们先假设SWE-agent的输入是目标Goal修复仓库https://github.com/fake-org/fake-django-todo的Issue #42 “待办事项无法标记为已完成”工具集ToolsetGitHub Issue查看工具、Git克隆工具、文件浏览工具cat/ls、代码搜索工具grep、代码编辑工具edit_file、本地Django服务启动工具、PostgreSQL模拟测试工具、pytest运行工具、Git提交与PR创建工具初始约束Constraints修复方案不能破坏现有功能的pytest覆盖率不能低于当前的82%提交信息必须符合fix: [Issue #42] ...的格式1. 感知层Perception Layer感知层的核心作用是把“非结构化/半结构化/结构化的外部环境输入”转化为“推理层大语言模型能够理解的上下文提示词Prompt Context”。在SWE-agent这个场景里感知层的核心输入源有三个目标与工具约束的输入直接来自用户或者外部调度系统比如公司内部的DevOps流水线已经是半结构化文本感知层只需要做“格式适配比如把调度系统的JSON格式Issue转化为SWE-agent的Prompt Context”和“初步过滤冗余信息比如Issue里的无效吐槽、重复评论”的轻量级工作。外部工具的执行结果输入这是感知层最核心、最频繁的输入——比如当推理层让动作层调用“cat ./todo/models.py”查看待办事项的模型定义时感知层会拿到动作层返回的“整个models.py的内容文本非结构化”或者“grep搜索‘mark_completed’后的高亮结果半结构化”然后把这些结果按“语义重要性排序”、“按工具执行的历史顺序拼接”、“必要时截断过长的文本避免超过大语言模型的上下文窗口限制”、“加上结构化的前缀比如[Tool Execution History - Step 3: grep -r mark_completed ./todo --colornever]”整理成干净的Prompt Context。反思模块的历史输出输入当智能体的某一步执行失败比如pytest覆盖率掉到了78%或者推理层对下一步行动不确定时记忆层会触发反思模块反思模块会生成“对前几步执行的错误分析”、“对当前策略的调整建议”这类输出感知层也会把这些输出整理成Prompt Context的一部分供推理层重新规划。2. 推理层Reasoning Layer推理层是LLM驱动的任务执行智能体的**“大脑”——它的输入是感知层整理好的完整Prompt Context输出是“下一步应该调用哪个工具、工具的参数是什么、为什么要调用这个工具的链式推理说明”。在早期的AutoGPT中推理层的Prompt Context里只有“目标、工具集、当前感知到的外部状态”所以它的推理经常会“发散”——比如本来是要修Django Todo的Bug它会突然去搜索“如何优化Django的数据库性能”然后调用Git克隆工具去克隆一个完全不相关的优化教程仓库而在SWE-agent这类面向“单一、明确、有约束的专业领域任务”的智能体里推理层的Prompt Context会加入很多“领域专家的思维链示例Few-Shot CoT Examples”、“任务拆解的结构化模板比如SWE-agent使用的Plan→Act→Reflect→Iterate的模板”、“外部约束的强制检查规则比如用正则表达式要求提交信息必须符合指定格式用工具调用前的预检查确保不会调用权限外的工具”——这样就能大大降低推理的“发散率”提高任务的成功率。还是回到Django Todo的场景我们可以模拟一下推理层在“初步看过Issue #42和仓库的README.md后”**的第一次推理输出【链式推理说明Chain-of-Thought Reasoning】 1. 首先我需要明确Issue #42的具体内容虽然仓库README.md里提到TodoList有“添加待办”、“删除待办”、“标记完成/未完成”三个功能但从Issue #42的标题“待办事项无法标记为已完成”来看问题出在“标记完成”这个功能上。 2. 接下来我应该先查看Issue #42的详细描述和评论看看有没有用户提供的“复现步骤”、“错误日志”、“环境信息”——这些信息能帮我快速定位问题不用盲目地搜索整个代码库。 3. 我调用的第一个工具应该是“GitHub Issue查看工具”工具参数是仓库URL https://github.com/fake-org/fake-django-todo 和Issue编号 42。【工具调用指令Tool Calling Instruction】{“tool_name”: “view_github_issue”,“parameters”: {“repo_url”: “https://github.com/fake-org/fake-django-todo”,“issue_number”: 42}}**3. 动作层Action Layer** 动作层的核心作用是**“安全、准确地执行推理层输出的工具调用指令”**——它的输入是推理层输出的结构化工具调用JSON输出是工具执行的“成功/失败状态码”、“结构化的结果数据”或者“非结构化的错误日志”。 为什么要单独拆分出一个动作层而不是让推理层直接和外部工具交互这里有三个**非常重要的工程实践原因**也是Harness Engineering harness在这里不是“束带”而是“ harness → 驾驭、控制 → 安全高效地控制AI系统的行为与输出”的核心思想之一 - **安全性隔离Security Isolation** LLM驱动的智能体推理层是“不可预测的”——哪怕你加上了很多约束它也有可能会生成“删除整个生产环境数据库”、“调用权限外的API获取敏感数据”这类危险的工具调用指令。动作层的第一个职责就是**预检查工具调用指令的安全性**比如在SWE-agent的动作层里所有的工具调用指令都会先经过“权限白名单检查只能调用用户明确授权的工具”、“参数合法性检查比如edit_file工具只能编辑仓库根目录及其子目录下的.py/.html/.css/.js文件不能编辑/etc/passwd这类系统文件”、“执行时间/资源限制检查比如本地Django服务启动工具的执行时间不能超过30秒否则自动停止”——只有通过所有预检查的指令才会被真正执行。 - **执行稳定性保障Execution Stability** 外部工具/API/环境接口的执行结果是“不稳定的”——比如GitHub API可能会因为网络问题返回500错误PostgreSQL模拟测试工具可能会因为端口被占用而启动失败。动作层的第二个职责就是**处理工具执行的异常情况**比如加上“工具调用重试机制最多重试3次每次重试之间间隔2秒指数退避”、“失败状态下的降级策略比如本地Django服务启动失败后降级为“直接查看pytest的测试代码分析‘标记完成’功能的测试逻辑””、“执行过程的资源监控与回收比如本地Django服务停止后自动清理临时生成的数据库文件和日志文件”——这样就能大大提高智能体整体的执行稳定性。 - **工具调用的标准化封装Standardized Tool Wrapping** 不同的外部工具/API/环境接口的调用方式、输入输出格式是“千差万别的”——比如GitHub Issue查看工具是用HTTP GET请求调用GitHub API的返回的是JSON格式的结构化数据而grep工具是用Shell命令调用的返回的是纯文本格式的非结构化数据。动作层的第三个职责就是**把所有的外部工具/API/环境接口封装成“标准化的工具类”**每个工具类都有统一的name工具名称、description工具描述供推理层理解工具的作用、parameters_schemaJSON Schema格式的参数定义供推理层生成合法的参数、execute()执行工具的方法返回统一的ToolExecutionResult对象——包含success布尔值、data结构化数据字段、error_log非结构化错误日志字段——这样就能大大降低推理层理解和调用工具的难度。 **4. 记忆层Memory Layer** 记忆层的核心作用是**“存储智能体的历史执行信息供感知层、推理层、反思模块复用”**——它解决了大语言模型的一个**核心硬伤上下文窗口有限无法长期记住大量的历史信息**。比如当我们用GPT-4 Turbo128k上下文窗口来运行一个“需要修复一个有1000个Python文件的大型Web应用的Bug”的任务时哪怕只存储每一步的工具执行历史128k的上下文窗口也很快就会被填满——这时候记忆层就可以帮我们“检索和召回最相关的历史信息”而不是把所有的历史信息都塞到推理层的Prompt Context里。 记忆层通常可以分为**短期记忆Short-Term Memory, STM**和**长期记忆Long-Term Memory, LTM**两个部分 - **短期记忆STM** 相当于人类的“工作记忆”存储的是**“当前任务执行过程中最近N步的所有信息”**——比如在SWE-agent的场景里STM可能会存储最近10步的工具调用指令、执行结果、链式推理说明、反思模块的输出。STM的信息不需要经过复杂的检索直接按历史顺序拼接成Prompt Context的“工具执行历史”部分即可——不过STM的大小是有限制的比如最多10步超过限制的信息会被自动“挤出”STM转移到LTM里。 - **长期记忆LTM** 相当于人类的“长期知识库”存储的是**“当前任务执行过程中所有被挤出STM的信息”、“智能体之前执行过的类似任务的经验教训”、“领域专家的知识库比如Django的官方文档、常见Bug的修复指南”**。LTM的信息不能直接塞到推理层的Prompt Context里必须先经过**“语义检索Semantic Retrieval”**——也就是先把当前推理层的Prompt Context里的“目标、当前状态、不确定性问题”这些信息转化为**“向量嵌入Vector Embedding”**然后在LTM的向量数据库比如Pinecone、ChromaDB、FAISS里搜索“最相似的K个向量嵌入对应的信息”最后把这些最相关的信息整理成Prompt Context的“长期记忆检索结果”部分。 好现在我们已经把任务执行智能体的四个核心模块和一次真实的SWE任务场景拆解完了——接下来我们要进入今天的第一个核心痛点**当LLM驱动的智能体像“黑盒子”一样在感知→推理→动作→记忆→反思→迭代的循环里不停奔跑时我们人类工程师/产品经理/运维人员到底能看到什么** --- #### 1.1.2 问题爆发真实生产环境里LLM智能体的“可观测性缺口”到底有多大 为了讲清楚这个问题我先给大家讲一个**我自己团队在2024年2月份遇到的真实案例**——这个案例让我和我的团队第一次真正意识到了“任务执行可视化”的重要性也让我们后来决定拥抱Harness Engineering的思想专门花了三个月的时间在我们的内部DevOps智能体平台上搭建了一套完整的“任务执行全链路可视化系统”。 ##### 1.1.2.1 真实案例内部DevOps智能体“DevOps-Agent-001”的首次生产环境部署 **背景介绍** 我所在的团队是一家国内做SaaS电商平台的公司的**AI赋能DevOps团队**——我们的核心KPI是“把公司内部DevOps工程师的日常重复工作量降低50%以上”。在2023年11月到2024年1月这三个月的时间里我们用LangChain、FastAPI、Docker、Kubernetes、ChromaDB、gpt-4-turbo-preview这些技术栈开发了一个内部DevOps智能体叫“DevOps-Agent-001”以下简称“Agent-001”。 Agent-001的核心功能是**“处理公司内部GitHub组织里所有仓库的‘CI/CD流水线失败’类的自动化Issue”**——这类Issue在我们公司内部每天大概有150-200个其中80%以上都是“重复的、简单的、可以通过执行固定的Shell命令或者调用固定的API来解决的”比如 1. lint阶段失败因为某个开发人员提交的代码不符合PEP8/Pylint/ESLint的规范修复方案是运行pre-commit run --all-files然后提交修复后的代码 2. test阶段失败因为某个第三方依赖库的版本更新导致测试用例失败修复方案是在requirements.txt/package.json里锁定第三方依赖库的版本 3. build阶段失败因为Docker镜像构建超时修复方案是在Dockerfile里优化镜像分层比如把不常变的依赖安装层放到前面然后在CI/CD流水线里提高Docker镜像构建的超时时间 4. deploy阶段失败因为Kubernetes集群的某个节点的资源不足修复方案是把Pod调度到资源充足的节点上或者自动扩容Kubernetes集群的节点。 在正式部署到生产环境之前我们先在一个**内部测试环境**里对Agent-001进行了为期两周的测试——测试用例是我们从过去半年的生产环境里收集的1000个“CI/CD流水线失败”类的自动化Issue其中有820个是之前提到的“重复的、简单的、可以通过固定步骤解决的”问题。测试结果让我们非常满意 - **任务解决率** 81.2%812/1000——和我们预期的82%几乎一样 - **任务平均执行时间** 3分27秒——而我们的DevOps工程师手动处理这类问题的平均时间是12分15秒效率提升了254% - **任务执行失败后的错误分析准确率** 92.7%17/18个“重复简单但测试失败的问题”的错误分析都是正确的。 于是我们团队在2024年2月15日星期四的下午把Agent-001**正式部署到了生产环境**——并且给它设置了“自动处理所有标记了‘auto-fixable’标签的CI/CD流水线失败类Issue”的权限。 ##### 1.1.2.2 灾难发生Agent-001在生产环境里的“失控” 2024年2月16日星期五的早上8点57分我刚到公司就收到了我们公司**CTO**和**核心电商业务的负责人**的连续十几条微信消息和电话——核心电商业务的负责人说“昨晚11点多我们的电商后台系统自动部署了一个新的版本结果今天早上9点半要上线的‘情人节促销预热活动’的API全挂了后台管理系统的登录页也打不开你们赶紧看看是怎么回事” 我当时吓得冷汗都出来了——我们公司的核心电商业务在情人节前后的日交易额大概是平时的5-10倍如果“情人节促销预热活动”的API挂了今天的损失可能会达到几百万甚至上千万 我赶紧打开公司内部的Kibana日志平台和Prometheus监控平台——一看果然有问题 1. **Kibana日志平台** 昨晚11点03分我们的核心电商后台系统的GitHub仓库https://github.com/our-company/our-core-ecommerce-backend自动创建了一个PR标题是fix: [auto-fixable] lint阶段失败ESLint规则no-unused-vars违规提交信息也是这个PR的作者是我们的Agent-00111点08分这个PR被我们的CI/CD流水线自动通过了因为我们给标记了“auto-fixable”标签的Issue对应的PR设置了“自动合并Auto-Merge”的权限只要CI/CD流水线的lint、test、build、deploy-staging四个阶段都通过就会自动合并到main分支然后自动部署到生产环境11点12分这个PR被自动合并到了main分支11点17分自动部署到生产环境的流程开始11点22分生产环境的Pod全部重启完成11点23分Kibana日志平台开始大量报错——错误类型是AttributeError: NoneType object has no attribute get错误发生在our-core-ecommerce-backend/app/api/v1/promotion.py的第47行。 2. **Prometheus监控平台** 昨晚11点23分之后核心电商后台系统的“API请求成功率”从99.9%直接掉到了0%“Pod重启次数”在10分钟内增加了50次因为Kubernetes的LivenessProbe和ReadinessProbe都失败了所以Kubernetes一直在重启Pod“CPU使用率”和“内存使用率”也一直居高不下因为Pod一直在重启一直在加载Python依赖库和初始化数据库连接。 我赶紧打开GitHub仓库的PR页面查看Agent-001自动创建的PR的代码变更——一看我差点晕过去 Agent-001在修复app/api/v1/promotion.py的no-unused-varsESLint规则违规时**把一个非常重要的变量promotion_config给删除了** 让我给大家看一下**PR变更前**的app/api/v1/promotion.py的第30-50行代码 python # PR变更前的代码第30-50行 from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.orm import Session from app.database import get_db from app.models.promotion import Promotion from app.schemas.promotion import PromotionCreate, PromotionResponse from app.services.promotion_service import PromotionService from app.utils.promotion_config_loader import load_promotion_config # 导入配置加载工具 router APIRouter(prefix/api/v1/promotion, tags[Promotion]) router.get(/preheat, response_modellist[PromotionResponse]) def get_preheat_promotions( db: Session Depends(get_db), # 这里有一个unused的参数不这是我们的权限验证中间件会用到的参数虽然在这个函数体里没有直接用但不能删除 current_user: dict Depends(get_current_active_admin_user) ): # 加载情人节促销预热活动的配置这个变量在load_promotion_config里被缓存了虽然在这个函数体里没有直接用但load_promotion_config会在第一次调用时初始化缓存后面的PromotionService.get_preheat_promotions会用到这个缓存 promotion_config load_promotion_config(valentine_preheat_2024) # 获取预热活动的列表 promotion_service PromotionService(db) preheat_promotions promotion_service.get_preheat_promotions() if not preheat_promotions: raise HTTPException(status_code404, detailNo preheat promotions found) return preheat_promotions然后再看一下PR变更后的代码# PR变更后的代码第30-50行fromfastapiimportAPIRouter,Depends,HTTPExceptionfromsqlalchemy.ormimportSessionfromapp.databaseimportget_dbfromapp.models.promotionimportPromotionfromapp.schemas.promotionimportPromotionCreate,PromotionResponsefromapp.services.promotion_serviceimportPromotionService# 哦Agent-001还把load_promotion_config的导入语句给删除了因为它认为这个导入语句没有被用到routerAPIRouter(prefix/api/v1/promotion,tags[Promotion])router.get(/preheat,response_modellist[PromotionResponse])defget_preheat_promotions(db:SessionDepends(get_db)# current_user参数被删除了因为Agent-001认为它是unused的):# promotion_config变量被删除了因为Agent-001认为它是unused的# 获取预热活动的列表promotion_servicePromotionService(db)preheat_promotionspromotion_service.get_preheat_promotions()# 第47行——错误发生在这里ifnotpreheat_promotions:raiseHTTPException(status_code404,detailNo preheat promotions found)returnpreheat_promotions那为什么第47行会报错AttributeError: NoneType object has no attribute get呢因为我们的PromotionService.get_preheat_promotions()方法里会调用app.utils.promotion_config_loader.get_cached_promotion_config(valentine_preheat_2024)来获取预热活动的配置——而这个方法的逻辑是# app/utils/promotion_config_loader.py的部分代码_cached_promotion_config{}defload_promotion_config(config_name:str)-dict: 加载促销活动的配置并缓存到内存里 ifconfig_namenotin_cached_promotion_config:# 从环境变量里获取配置文件的路径config_pathos.getenv(fPROMOTION_CONFIG_{config_name.upper()})ifnotconfig_path:raiseValueError(fEnvironment variable PROMOTION_CONFIG_{config_name.upper()}not set)# 从JSON文件里加载配置withopen(config_path,r,encodingutf-8)asf:configjson.load(f)# 缓存配置_cached_promotion_config[config_name]configreturn_cached_promotion_config[config_name]defget_cached_promotion_config(config_name:str)-Optional[dict]: 获取缓存的促销活动的配置——如果没有缓存返回None return_cached_promotion_config.get(config_name)而我们的PromotionService.get_preheat_promotions()方法里没有检查get_cached_promotion_config()的返回值是否为None直接就调用了config.get(preheat_start_time)——所以当load_promotion_config()没有被调用过因为Agent-001把它的调用语句和导入语句都删除了get_cached_promotion_config()返回None然后就报错了。更糟糕的是Agent-001还把current_user参数给删除了——虽然这个参数在get_preheat_promotions()函数体里没有直接用但它是我们的权限验证中间件get_current_active_admin_user()的返回值这个中间件会自动把current_user的信息存储到FastAPI的请求上下文里后面的其他API比如创建促销活动的API、修改促销活动的API会用到这个请求上下文里的current_user信息——也就是说就算我们修复了AttributeError的问题后面的其他API的权限验证也会失效任何一个用户包括未登录的用户都可以创建、修改、删除我们的促销活动1.1.2.3 事后复盘为什么我们没有提前发现Agent-001的“错误”在我们团队花了两个小时9点10分-11点10分紧急修复了所有问题、把核心电商后台系统恢复正常之后我们进行了一次非常深刻的事后复盘——复盘的核心问题是“为什么我们在测试环境里没有发现Agent-001的这个错误为什么我们在生产环境里的PR合并和部署流程里没有发现这个错误为什么我们直到API全挂了才知道出了问题”经过整整一天的复盘我们总结出了五个核心原因——这五个原因其实就是LLM驱动的任务执行智能体在真实生产环境里的**“五个可观测性缺口”**原因一测试环境的场景覆盖不全无法模拟生产环境里的“真实业务逻辑依赖”在我们的测试环境里我们给PromotionService.get_preheat_promotions()方法写的测试用例是**“Mock掉了get_cached_promotion_config()方法”的——也就是说测试用例里的get_cached_promotion_config()方法会直接返回一个Mock的配置字典不会真的去调用load_promotion_config()方法也不会真的去缓存配置——所以在测试环境里就算Agent-001把load_promotion_config()的调用语句和导入语句都删除了测试用例也会通过这是我们测试环境的第一个大问题。另外在我们的测试环境里我们给get_preheat_promotions()函数写的测试用例是“Mock掉了get_current_active_admin_user()中间件”**的——也就是说测试用例里的get_preheat_promotions()函数不会真的去调用权限验证中间件也不会真的去检查用户的权限——所以在测试环境里就算Agent-001把current_user参数给删除了测试用例也会通过这是我们测试环境的第二个大问题。但更深层次的问题是**我们人类工程师怎么可能在测试环境里模拟出所有生产环境里的“真实业务逻辑依赖”**尤其是对于像我们这样的大型SaaS电商平台代码库的规模非常大我们的核心电商后台系统有超过5000个Python文件超过200万行代码业务逻辑非常复杂有超过100个微服务超过1000个API接口超过5000个测试用例——我们根本不可能在测试环境里把所有的“隐含的业务逻辑依赖”比如promotion_config变量虽然在函数体里没有直接用但它会初始化缓存后面的方法会用到比如current_user参数虽然在函数体里没有直接用但它会被权限验证中间件存储到请求上下文里后面的其他API会用到都覆盖到这就意味着LLM驱动的任务执行智能体在生产环境里总会遇到一些测试环境里没有覆盖到的“隐含的业务逻辑依赖”从而做出错误的决策——而如果我们没有办法“实时观测到智能体的决策过程和执行过程”我们就只能等到“业务出现问题”的时候才知道出了问题原因二CI/CD流水线的自动合并权限设置得太松没有人工审核的环节在我们的生产环境里我们给标记了“auto-fixable”标签的Issue对应的PR设置了“自动合并”的权限——只要CI/CD流水线的lint、test、build、deploy-staging四个阶段都通过就会自动合并到main分支然后自动部署到生产环境。但问题是我们的deploy-staging阶段的测试用例和我们的测试环境里的测试用例是一样的——都是Mock掉了“隐含的业务逻辑依赖”的所以在deploy-staging阶段就算Agent-001做出了错误的决策测试用例也会通过更深层次的问题是**我们人类工程师怎么可能完全信任LLM驱动的任务执行智能体的决策**哪怕它在测试环境里的任务解决率达到了99.9%我们也不能完全信任它——因为LLM是“不可预测的”它总会在某个时候做出一些我们人类完全想不到的错误决策这就意味着对于生产环境里的重要操作比如合并PR、部署到生产环境我们必须要有“人工审核”的环节——而如果我们没有办法“清晰、直观地给人类审核人员展示智能体的决策过程和执行过程”人类审核人员就没有办法快速、准确地判断智能体的决策是否正确人工审核的效率就会非常低甚至会失去人工审核的意义原因三我们没有办法“实时观测到智能体的决策过程”——只能看到它的“最终执行结果”在我们的Agent-001的初始版本里我们只给它加了**“最终执行结果的日志记录”**——也就是当智能体的任务执行成功或者失败时它会把“任务的目标”、“任务的最终执行状态成功/失败”、“任务执行失败时的错误日志”这些信息记录到我们的Kibana日志平台里。但问题是我们没有办法看到智能体的“中间决策过程”——也就是它为什么会做出“删除promotion_config变量”、“删除current_user参数”、“删除load_promotion_config的导入语句”这些决策它的链式推理说明是什么它调用了哪些工具工具的执行结果是什么它有没有触发反思模块反思模块的输出是什么在我们的事后复盘里我们花了整整两个小时才从我们的Agent-001的内部内存因为我们没有把中间决策过程记录到日志里只能从当时运行的Pod的临时文件里找到一些残留的信息里拼凑出了它的中间决策过程——让我给大家看一下拼凑出来的部分中间决策过程【Agent-001的内部中间决策过程残留信息拼凑版】 --- 【Step 1: 感知层输入】 目标修复仓库 https://github.com/our-company/our-core-ecommerce-backend 的Issue #12345 “CI/CD流水线失败lint阶段ESLint规则no-unused-vars违规” 工具集...省略 约束...省略 工具执行历史无 --- 【Step 1: 推理层输出】 链式推理说明 1. 首先我需要查看Issue #12345的详细描述看看ESLint规则no-unused-vars违规的具体位置在哪里。 2. 我调用的第一个工具应该是“view_github_issue”参数是仓库URL和Issue编号。 工具调用指令view_github_issue(...) --- 【Step 2: 感知层输入】 ...省略view_github_issue的执行结果ESLint规则no-unused-vars违规的具体位置是app/api/v1/promotion.py的第36行导入语句load_promotion_config、第42行参数current_user、第44行变量promotion_config --- 【Step 2: 推理层输出】 链式推理说明 1. 从view_github_issue的执行结果来看ESLint规则no-unused-vars违规的具体位置是三个 a. app/api/v1/promotion.py的第36行导入语句from app.utils.promotion_config_loader import load_promotion_config——没有被用到 b. app/api/v1/promotion.py的第42行参数current_user——没有被用到 c. app/api/v1/promotion.py的第44行变量promotion_config——没有被用到 2. ESLint规则no-unused-vars的要求是“删除所有未使用的导入语句、参数、变量”——所以我需要删除这三个未使用的内容 3. 接下来我需要先查看一下app/api/v1/promotion.py的完整内容确认这三个内容确实没有被用到 4. 我调用的工具应该是“view_file”参数是文件路径app/api/v1/promotion.py。 工具调用指令view_file(...) --- 【Step 3: 感知层输入】 ...省略view_file的执行结果app/api/v1/promotion.py的完整内容也就是PR变更前的代码 --- 【Step 3: 推理层输出】 链式推理说明 1. 我已经查看了app/api/v1/promotion.py的完整内容确认这三个内容确实没有被用到 a. 导入语句load_promotion_config在整个文件里都没有被调用过 b. 参数current_user在get_preheat_promotions()函数体里没有被直接用到 c. 变量promotion_config在get_preheat_promotions()函数体里没有被直接用到 2. 所以我需要删除这三个内容 3. 接下来我需要调用“edit_file”工具来修改app/api/v1/promotion.py 工具调用指令edit_file(...) --- 【Step 4: 感知层输入】 ...省略edit_file的执行结果修改成功 --- 【Step 4: 推理层输出】 链式推理说明 1. 我已经成功修改了app/api/v1/promotion.py删除了三个未使用的内容 2. 接下来我需要调用“run_pre_commit”工具来运行pre-commit钩子确保没有其他的ESLint规则违规 工具调用指令run_pre_commit(...) --- 【Step 5: 感知层输入】 ...省略run_pre_commit的执行结果pre-commit钩子运行成功所有ESLint规则都通过了 --- 【Step 5: 推理层输出】 链式推理说明 1. pre-commit钩子运行成功所有ESLint规则都通过了 2. 接下来我需要调用“git_add”、“git_commit”、“git_push”工具来提交和推送修改 工具调用指令git_add(...), git_commit(...), git_push(...) --- 【Step 6: 感知层输入】 ...省略git_push的执行结果推送成功 --- 【Step 6: 推理层输出】 链式推理说明 1. 修改已经成功推送到远程仓库 2. 接下来我需要调用“create_github_pr”工具来创建PR 工具调用指令create_github_pr(...) --- 【Step 7: 感知层输入】 ...省略create_github_pr的执行结果PR创建成功编号是#12346 --- 【Step 7: 推理层输出】 链式推理说明 1. PR已经成功创建 2. 接下来我需要等待CI/CD流水线的lint、test、build、deploy-staging四个阶段都通过 3. 如果四个阶段都通过PR会自动合并到main分支然后自动部署到生产环境 4. 我的任务已经完成。 --- 【最终执行结果成功】 记录到Kibana日志平台的信息 { task_id: task-123456, agent_id: DevOps-Agent-001, goal: 修复仓库 https://github.com/our-company/our-core-ecommerce-backend 的Issue #12345 “CI/CD流水线失败lint阶段ESLint规则no-unused-vars违规”, status: success, start_time: 2024-02-15T23:00:00Z, end_time: 2024-02-15T23:07:00Z, duration_seconds: 420, pr_url: https://github.com/our-company/our-core-ecommerce-backend/pull/12346 }看到这里我想大家应该都明白问题出在哪里了——Agent-001的推理层根本没有意识到这三个内容是“隐含的业务逻辑依赖”它只是简单地按照ESLint规则no-unused-vars的要求删除了所有“在当前文件或者当前函数体里没有被直接用到的”导入语句、参数、变量但更深层次的问题是如果我们当时有一套完整的“任务执行全链路可视化系统”能够实时观测到Agent-001的中间决策过程——比如在Step 3的推理层输出里当它说“确认这三个内容确实没有被用到”的时候我们的可视化系统能够给我们的人类审核人员发送一个“预警”告诉我们“智能体即将删除三个可能是隐含的业务逻辑依赖的内容请人工审核”——那我们就可以在PR合并之前就发现这个问题避免灾难的发生原因四我们没有办法“清晰、直观地给人类展示智能体的执行过程”——只能看到一堆杂乱无章的日志在我们的Agent-001的初始版本里就算我们后来把中间决策过程也记录到了Kibana日志平台里这些日志也是**“杂乱无章的”——它们是按时间顺序排列的纯文本日志没有任何的结构化、分类、可视化——我们的人类审核人员要想从这些日志里找到智能体的中间决策过程就必须像“大海捞针”一样一条一条地看日志效率非常低比如在我们的事后复盘里就算我们后来把中间决策过程也记录到了测试环境里的Kibana日志平台里我们的一个资深DevOps工程师也要花整整30分钟才能从测试环境里的一次类似的任务执行的日志里找到智能体的中间决策过程——这对于需要快速审核PR的人类审核人员来说是完全不可接受的更深层次的问题是日志这种形式根本不适合用来展示LLM驱动的任务执行智能体的执行过程——因为智能体的执行过程是“链式的、有因果关系的、包含大量的非结构化文本比如链式推理说明和结构化数据比如工具调用指令、工具执行结果的”我们需要的是一种“清晰、直观、交互式的可视化形式”**——能够把智能体的执行过程按“感知→推理→动作→记忆→反思→迭代”的循环以“流程图、时序图、树状图”等形式清晰、直观地展示给人类能够让人类通过“点击、拖拽、缩放”等交互式操作查看智能体执行过程的每一个细节比如每一步的链式推理说明、每一步的工具调用指令、每一步的工具执行结果能够在智能体做出“可能有风险的决策”的时候自动给人类发送“预警”能够让人类在智能体执行过程的任何一个环节“暂停”、“干预”、“修改”智能体的决策原因五我们没有办法“量化评估”智能体的决策质量和执行质量——只能看到“任务解决率”和“任务平均执行时间”这两个宏观指标在我们的Agent-001的初始版本里我们只给它加了**“任务解决率”和“任务平均执行时间”这两个宏观指标的统计**——我们根本没有办法“量化评估”智能体的每一步决策的质量比如“这一步的链式推理说明是否合理”、“这一步的工具调用指令是否正确”、“这一步的工具调用是否必要”也没有办法“量化评估”智能体的每一次任务执行的质量比如“这次任务执行有没有对业务造成影响”、“这次任务执行有没有破坏现有功能的测试覆盖率”、“这次任务执行有没有浪费不必要的资源”比如在我们的事后复盘里我们根本没有办法从我们的初始版本的统计数据里发现Agent-001的“删除隐含的业务逻辑依赖”的决策质量问题——因为从宏观指标来看这次任务执行是“成功”的它成功地修复了ESLint规则no-unused-vars的违规问题它的CI/CD流水线的四个阶段都通过了它的任务平均执行时间也符合我们的要求但实际上这次任务执行的决策质量是“极差”的——它几乎给我们公司造成了几百万甚至上千万的损失更深层次的问题是如果我们没有办法“量化评估”智能体的决策质量和执行质量我们就没有办法“优化”智能体的Prompt Context、领域专家的思维链示例、工具集、约束条件——我们就没有办法提高智能体的任务解决率和决策质量我们就没有办法在生产环境里更广泛地应用LLM驱动的任务执行智能体1.1.3 核心概念升级什么是Harness Engineering驾驭工程什么是Harness Engineering视野下的“智能体任务执行全链路可观测性”什么是本文要重点讲解的“智能体任务执行可视化”经过这次深刻的事后复盘我们团队开始在网上搜索相关的解决方案——我们发现在2023年底到2024年初国内外的一些顶尖的AI研究机构和科技公司比如OpenAI、Anthropic、Google DeepMind、Meta AI、微软亚洲研究院、阿里达摩院、腾讯AI Lab开始提出一个全新的软件工程分支的概念——Harness Engineering驾驭工程。1.1.3.1 什么是Harness Engineering驾驭工程目前Harness Engineering还没有一个统一的、官方的学术定义——不过我们可以从国内外顶尖的AI研究机构和科技公司的相关论文、博客文章、技术分享里总结出一个面向工程实践的Harness Engineering的定义Harness