ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

基于OpenClaw与Apache Doris构建AI Agent可观测性系统实践

2026/8/16 21:59:32 拓冰建站 浏览量
基于OpenClaw与Apache Doris构建AI Agent可观测性系统实践

1. 项目概述:当AI Agent遇上可观测性

最近在折腾一个AI Agent项目,想把OpenClaw和Apache Doris这两个看起来八竿子打不着的玩意儿凑一块儿,结果意外地捅开了AI Agent内部运作的几个“黑盒”。这事儿挺有意思,感觉像给一个只会埋头干活的聪明助手装上了“X光机”和“行车记录仪”,它每一步在想什么、做了什么、卡在哪里,都变得清晰可见。如果你也在搞AI Agent,或者对如何让这些智能体变得更透明、更可控感兴趣,那接下来的内容或许能给你一些启发。

简单来说,OpenClaw是一个功能丰富的AI Agent开发框架,而Apache Doris则是一个高性能的实时分析型数据库。我最初的想法很简单:用Doris来存储和分析Agent运行过程中产生的海量日志与状态数据,从而实现对Agent行为的深度观测。这个组合,本质上是在构建一套专为AI Agent设计的“可观测性”系统。没想到,这个过程中,我们不仅解决了监控问题,还顺带揭示了Agent在任务规划、工具调用和长期记忆这三个核心环节中,那些通常被隐藏起来的内部逻辑与潜在瓶颈。

2. 核心思路:为什么是OpenClaw + Doris?

在深入细节之前,得先聊聊为什么选这两个组件。市面上AI Agent框架不少,比如LangChain、AutoGen,OpenClaw的优势在于它设计了一套相对清晰且可扩展的“技能”与“工具”调用机制,并且原生支持与多种大模型对接,这为我们埋点采集数据提供了结构化的基础。而Apache Doris,作为一款MPP架构的数据库,其强项在于高并发实时写入和快速即席查询。想象一下,一个活跃的Agent每秒可能产生数十条状态记录(思考、调用工具、得到结果),传统的日志文件或者通用监控系统在数据聚合和实时分析上会很快遇到瓶颈。

核心需求解析:我们想要的不只是记录“Agent执行了A任务,成功了/失败了”。我们想知道:

  • 任务拆解黑盒:Agent是如何理解我的指令,并将其拆解成一个个子步骤的?它的推理链条是否合理?
  • 工具调用黑盒:在众多可用工具中,Agent为什么选择了工具A而不是工具B?调用参数是如何生成的?调用耗时和成功率如何?
  • 记忆与上下文黑盒:Agent的“记忆”是如何被存储、检索和使用的?哪些历史对话或工具结果对当前决策产生了关键影响?

将OpenClaw的运行时数据(日志、中间状态、工具调用记录)实时写入Doris,再利用Doris强大的SQL分析能力,我们就能从时间、会话、工具类型、模型响应等多个维度,对上述黑盒进行透视。

注意:这个方案的核心是“非侵入式”或“低侵入式”的数据采集。我们不应为了观测而大幅修改Agent的核心推理逻辑,理想情况是通过框架提供的钩子函数或中间件来旁路采集数据。

3. 环境搭建与数据链路设计

3.1 Apache Doris的快速部署与表设计

为了快速验证,我选择了Doris的单机版进行部署。从官网下载最新稳定版的二进制包,解压后按照官方文档进行配置和启动,过程比较标准。重点在于表结构的设计,这直接决定了后续分析的灵活性和效率。

我设计了两张核心表:

1. agent_events 表(存储原子事件)这张表记录Agent运行过程中的每一个关键事件,类似于审计日志。

CREATE TABLE IF NOT EXISTS agent_events ( `event_id` BIGINT NOT NULL, `session_id` VARCHAR(255) NOT NULL, `event_time` DATETIMEV2(3) NOT NULL, `event_type` VARCHAR(50) NOT NULL, -- 如:`plan_start`, `tool_selected`, `tool_executed`, `llm_invoke`, `error` `agent_name` VARCHAR(100), `model_name` VARCHAR(100), `tool_name` VARCHAR(100), `input_content` TEXT, `output_content` TEXT, `metadata` TEXT, -- 存储JSON格式的额外信息,如工具参数、置信度、耗时等 `duration_ms` INT ) DUPLICATE KEY(`event_id`, `session_id`, `event_time`) PARTITION BY RANGE(`event_time`)() DISTRIBUTED BY HASH(`session_id`) BUCKETS 10 PROPERTIES ( "replication_num" = "1" );

按月分区:对于事件表,数据量增长会非常快。我使用了按月动态分区,例如PARTITION BY RANGE(event_time) (PARTITION p202405 VALUES [('2024-05-01'), ('2024-06-01'))),并设置定期增加新分区的任务。这样在查询时,Doris可以高效地进行分区裁剪,只扫描相关月份的数据,极大提升查询性能。

2. agent_sessions 表(会话维度聚合)这张表以会话为粒度,存储一些汇总信息,便于快速查看会话概览。

CREATE TABLE IF NOT EXISTS agent_sessions ( `session_id` VARCHAR(255) NOT NULL, `start_time` DATETIMEV2(3) NOT NULL, `end_time` DATETIMEV2(3), `user_query` TEXT, `final_result` TEXT, `status` VARCHAR(20), -- `running`, `success`, `failed` `total_events` INT, `total_duration_ms` BIGINT, `llm_invoke_count` INT, `tool_call_count` INT ) UNIQUE KEY(`session_id`) DISTRIBUTED BY HASH(`session_id`) BUCKETS 1 PROPERTIES ( "replication_num" = "1" );

3.2 OpenClaw的改造与数据埋点

OpenClaw本身不直接提供向外部数据库写入运行数据的功能。我们需要对其进行轻量级改造。核心思路是利用OpenClaw的“中间件”或“钩子”机制,在关键的执行节点插入我们的数据采集代码。

一个典型的做法是,自定义一个ObservabilityMiddleware类。这个中间件会在Agent执行流程的关键阶段被调用,比如:

  • 任务规划后:记录LLM生成的初始计划步骤。
  • 选择工具时:记录候选工具列表、被选中的工具及其理由(如果LLM提供了)。
  • 执行工具前后:记录工具输入参数、执行结果、耗时。
  • 调用LLM前后:记录发送的Prompt和收到的Response。
  • 发生错误时:记录详细的错误堆栈信息。

这个中间件内部,我们需要实现一个高效的数据发送器。这里有一个关键的坑:避免在Agent的主循环中同步执行耗时的数据库插入操作,否则会严重拖慢Agent的响应速度。我的解决方案是使用异步队列。

# 示例:一个简化的异步数据上报客户端 import asyncio import aiohttp import json from datetime import datetime from queue import Queue from threading import Thread import doris class DorisReporter: def __init__(self, doris_host, doris_port, db, user, password): self.doris_client = doris.Client(host=doris_host, port=doris_port, database=db, user=user, password=password) self.queue = Queue() self.worker_thread = Thread(target=self._batch_insert_worker, daemon=True) self.worker_thread.start() def report_event(self, event_data: dict): """将事件数据放入队列,非阻塞""" self.queue.put(event_data) def _batch_insert_worker(self): """后台工作线程,批量插入数据""" batch = [] BATCH_SIZE = 100 while True: try: event = self.queue.get(timeout=1.0) batch.append(event) if len(batch) >= BATCH_SIZE or self.queue.empty(): if batch: self._insert_batch(batch) batch.clear() except Exception as e: # 记录本地日志,避免影响主进程 print(f"Error in batch worker: {e}") def _insert_batch(self, batch_data): # 使用Doris的Stream Load API进行批量高效写入 # 或者使用INSERT INTO ... VALUES (),(),() 方式 sql = "INSERT INTO agent_events VALUES ..." # 构造批量插入SQL self.doris_client.execute(sql)

然后,在OpenClaw的中间件中,我们只需要调用reporter.report_event()即可,数据会由后台线程批量、异步地写入Doris,对主流程的影响微乎其微。

4. 揭开第一个黑盒:任务规划与推理链条

当Agent收到一个复杂指令,比如“帮我分析一下上个月公司的销售数据,并预测下个季度的趋势”,它内部是如何思考的?通过我们埋点记录的llm_invoke事件(特别是包含完整Prompt和Response的事件),我们可以完整地还原出它的“思维过程”。

在Doris中,我们可以执行这样的分析查询:

-- 查找某个会话中,LLM调用的完整链条 SELECT event_time, event_type, SUBSTRING(input_content, 1, 200) as prompt_preview, SUBSTRING(output_content, 1, 500) as response_preview FROM agent_events WHERE session_id = 'your_session_id_here' AND event_type = 'llm_invoke' ORDER BY event_time ASC;

通过分析这些连续的LLM交互记录,我们可能会发现一些有趣或有问题的地方:

  • 规划冗余:Agent可能将一个简单的查询拆解成了过多不必要的步骤。
  • 逻辑跳跃:在某些步骤,Agent的推理可能缺乏清晰的依据,直接跳到了结论。
  • 上下文遗忘:在长对话中,Agent可能没有正确引用之前步骤已经获得的信息。

实操心得:仅仅记录输入输出还不够,最好在metadata字段里记录本次调用的“角色”或“阶段”,比如{"stage": "planning"}{"stage": "reflection"}。这样在分析时,我们可以轻松过滤出“规划阶段”的所有LLM调用,更清晰地审视其任务拆解能力。

5. 揭开第二个黑盒:工具调用的选择与效能

这是AI Agent能力的核心。我们的系统可以清晰地回答:Agent用了哪些工具?为什么用?用得怎么样?

相关的Doris查询示例:

-- 统计所有会话中,各个工具的被调用次数和平均耗时 SELECT tool_name, COUNT(*) as call_count, AVG(duration_ms) as avg_duration_ms, SUM(CASE WHEN output_content LIKE '%error%' OR event_type = 'error' THEN 1 ELSE 0 END) as error_count FROM agent_events WHERE event_type IN ('tool_executed', 'tool_error') AND tool_name IS NOT NULL GROUP BY tool_name ORDER BY call_count DESC; -- 分析某个工具调用失败的具体原因 SELECT session_id, event_time, input_content, output_content FROM agent_events WHERE event_type = 'tool_error' AND tool_name = 'get_weather_api' LIMIT 10;

通过这样的分析,我们可能发现:

  • 工具偏好:Agent是否过度依赖某个“万能”工具,而忽略了更专业的工具?
  • 性能瓶颈:某个外部API工具的平均响应时间长达5秒,成为了整个工作流的瓶颈。
  • 错误模式:某个工具在特定输入参数下总是失败,这提示我们需要改进工具的输入验证或错误处理逻辑。
  • 选择合理性:通过对比tool_selected事件中记录的“选择理由”和实际tool_executed的结果,可以评估LLM进行工具路由的准确性。

注意:工具调用的duration_ms需要精确测量。最好在中间件的tool_executed事件前后使用高精度计时器,并确保这个耗时记录的是工具执行本身的网络/计算时间,而不包含序列化、队列等待等其他开销。

6. 揭开第三个黑盒:记忆系统的运作与影响

AI Agent的“记忆”是其实现持续对话和个性化服务的关键。无论是简单的对话历史窗口,还是复杂的向量检索记忆,其有效性直接决定了Agent的智能程度。我们的可观测系统需要能够追踪:Agent在本次决策中,检索了哪些记忆?这些记忆是如何被使用的?

这部分的埋点更具挑战性,因为记忆系统的访问可能深埋在框架内部。一种可行的方法是在记忆存储的“读”接口进行拦截。例如,如果OpenClaw使用向量数据库存储记忆片段,我们可以在执行相似性搜索后,记录下查询的向量、返回的top-k记忆片段及其相关性分数。

我们可以在agent_events表中增加一种新的事件类型memory_retrieved,并在其metadata字段中记录:

{ "query_embedding_dim": 768, "retrieved_count": 5, "top_scores": [0.92, 0.85, 0.78, 0.71, 0.65], "memory_ids": ["mem_001", "mem_123", "mem_456", "mem_789", "mem_999"] }

然后,我们可以分析:

  • 记忆相关性:返回的记忆片段与当前问题的匹配度(分数)如何?如果分数普遍偏低,说明记忆系统可能未存储有效信息,或检索策略有待优化。
  • 记忆利用率:被检索出来的记忆,是否真的被后续的LLM Prompt所引用?可以通过关联分析,检查在memory_retrieved事件之后,紧邻的llm_invoke事件的Prompt中是否包含了相关记忆ID或内容。
  • 记忆增长:统计每天新增的记忆条目,分析记忆库的积累情况。

7. 性能调优与问题排查实录

在实施过程中,我遇到了几个典型问题,这里分享出来供大家避坑。

问题一:Doris数据写入速度慢,每分钟只能插入约2万条100列的数据。

这显然不符合Doris的性能预期。排查步骤如下:

  1. 检查写入模式:是否在每条事件产生时都执行了一条INSERT INTO ... VALUES (...)语句?这是最慢的方式。务必改用批量插入。我上面提到的异步队列+批量写入是必须的。可以使用Doris的Stream Load功能(通过HTTP推送JSON或CSV),或者攒够一批数据后执行一条多VALUES的INSERT语句。
  2. 检查表结构:100列确实比较多。评估所有字段是否都是必需的。特别是TEXT类型的字段,如果存储的是大段的Prompt或Response,会显著影响性能。考虑将过大的内容分离到单独的扩展表,或者只存储其摘要或哈希值。
  3. 检查网络与客户端:确认Doris服务器资源(CPU、内存、磁盘IO)是否充足。检查客户端机器到Doris服务器的网络延迟。使用EXPLAIN分析INSERT语句的执行计划。
  4. 调整Doris配置:对于高频写入场景,可以调整Doris的BE(后端)配置,如streaming_load_rpc_max_alive_time_secmax_client_cache_size等,优化Stream Load性能。同时,确保建表时设置了合理的分桶数,数据能均匀分布。

问题二:OpenClaw中间件影响了Agent响应速度。

即使使用了异步队列,如果中间件本身的逻辑过于复杂(比如做了大量的数据序列化、计算),也会带来开销。

  • 优化方案:确保中间件内只做最必要的数据提取和格式化,将任何复杂的计算(如计算哈希、生成摘要)移到后台工作线程中。使用更高效的数据序列化库(如orjson替代标准json)。

问题三:观测数据本身量太大,难以分析。

当数据量积累到一定程度,直接写复杂SQL查询也会变慢。

  • 解决方案:利用Doris的物化视图或Rollup表功能,针对常见的分析维度(如按小时/天的工具调用统计、会话成功率等)预先计算聚合结果。这样,在查看仪表盘时,查询的是轻量的聚合表,速度极快。

8. 构建可观测性仪表盘与告警

数据存好了,分析查询也写了,最后一步就是将其可视化,并设置告警,让观测变得主动。

我使用的是Grafana连接Doris作为数据源。Doris社区提供了官方的Grafana连接器,配置起来很方便。接下来就可以创建一系列面板:

  • 全局概览:显示当前在线Agent数、今日总会话数、成功率、平均会话耗时。
  • 工具健康度:以柱状图或饼图展示各工具调用量和错误率,错误率高的工具自动标红。
  • LLM性能与成本:统计各模型调用次数、平均响应时间、总Token消耗(如果元数据中记录了)。
  • 会话流水线:这是一个非常实用的视图,可以输入一个session_id,以时间线的方式可视化展示该会话内所有事件的类型、耗时和关联内容,就像看一个程序的调用链跟踪一样,一眼就能看清Agent的执行脉络。

告警设置

  1. 错误率告警:当某个工具的错误率在10分钟内超过5%时,发送通知。
  2. 耗时告警:当平均会话耗时或关键工具平均耗时超过设定的阈值时告警。
  3. LLM异常告警:如果LLM调用连续返回格式错误或内容空的结果,可能意味着Prompt构造有问题或模型服务异常。

通过这套组合拳,我们不仅实现了对AI Agent的“观测”,更实现了“洞察”和“干预”。当某个工具持续报错时,运维能第一时间收到通知;当发现Agent的规划逻辑普遍存在冗余时,算法工程师可以有针对性地优化Prompt或Agent的推理配置。这个由OpenClaw和Doris共同构建的可观测系统,真正将AI Agent从黑盒变成了白盒,为它的稳定、高效和持续优化提供了坚实的数据基础。