拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Agent 时代的基础设施:数据、编排与可观测性实战

Agent 时代的基础设施:数据、编排与可观测性实战

1. Agent 时代的基础设施到底在解决什么问题

1.1 从“模型能力”到“系统能力”的转折点

过去两年,大家聊 AI 聊得最多的是模型本身——参数多大、榜单多高、推理多强。但真正把 AI 落到业务里的人会发现,模型只是整个系统里的一小块。一个能跑起来的 Agent 应用,背后牵扯的是数据管道、工具调用、状态管理、执行编排、可观测性、安全边界这一整套东西。模型再强,如果数据喂不进去、工具调不通、执行链路断了没法排查,整个系统就是不可用的。

这就是“Agent 时代的数据与 AI 基础设施”这个命题的核心。它讨论的不是某个单点技术,而是当 AI 从“问答玩具”变成“能自主执行任务的智能体”之后,底层需要什么样的支撑体系。Agent 和传统 AI 应用最大的区别在于:传统应用是“输入-推理-输出”的单次链路,而 Agent 是“感知-规划-行动-观察-再规划”的循环链路。这个循环意味着系统需要维护状态、需要调用外部工具、需要处理失败重试、需要记录每一步的执行轨迹。这些需求叠加起来,对基础设施的要求就完全不一样了。

我自己的体会是,很多人做 Agent 项目,前期 demo 跑得飞快,一旦要上生产就各种问题。根因往往不在模型,而在基础设施没搭好。数据格式不统一、工具接口不稳定、执行日志缺失、错误处理缺失,这些都是典型的“基础设施债”。

1.2 数据层:Agent 的“记忆”和“感知”从哪来

Agent 要做事,首先得有信息。信息从哪来?从数据来。这里的数据不只是训练数据,还包括运行时数据。我把它分成三类:

第一类是知识数据,也就是 Agent 需要理解领域知识所依赖的语料、文档、结构化数据。比如一个客服 Agent 需要产品手册、FAQ、历史工单;一个数据分析 Agent 需要数据库 schema、指标定义、业务口径。这类数据的核心问题是“怎么让 Agent 准确检索到”,涉及切分策略、向量化、索引结构、召回排序。

第二类是状态数据,也就是 Agent 在执行任务过程中产生的中间状态。比如它已经完成了哪几步、当前在等什么结果、上一次工具调用返回了什么。这类数据的核心问题是“怎么持久化、怎么恢复、怎么保证一致性”。很多 Agent 框架用内存存状态,一重启就全丢了,这在生产环境是不可接受的。

第三类是轨迹数据,也就是 Agent 每一步的决策记录、工具调用参数、返回结果、耗时、token 消耗。这类数据的核心问题是“怎么采集、怎么存储、怎么分析”。没有轨迹数据,你根本不知道 Agent 为什么失败,优化就无从谈起。

这三类数据的管理方式完全不同。知识数据偏静态,适合用向量库加全文索引;状态数据偏动态,适合用键值存储或关系库;轨迹数据偏追加写,适合用日志系统或列式存储。把它们混在一起处理,是很多项目踩的第一个大坑。

1.3 智能层:编排、工具与执行引擎

数据准备好了,接下来是“智能”怎么体现。Agent 的智能不是模型一个人的事,而是编排层、工具层、执行层协同的结果。

编排层负责决定“下一步做什么”。这可以是简单的规则引擎,也可以是基于 LLM 的动态规划。我见过不少团队一上来就用最复杂的规划方案,结果调试成本极高。实际上,很多业务场景用状态机加少量 LLM 判断就够了,稳定性和可维护性反而更好。

工具层负责“怎么和外部世界交互”。Agent 要查数据库、调 API、发邮件、操作文件,这些都需要工具封装。工具层的核心挑战是接口标准化和错误处理。每个工具的输入输出格式、超时策略、重试逻辑、权限控制都要统一管理,否则 Agent 调用起来就是一团乱麻。

执行层负责“真正把动作跑起来”。这涉及沙箱隔离、资源限制、并发控制、失败恢复。尤其是当 Agent 能执行代码或操作文件系统时,安全边界必须做扎实。我个人的经验是,执行层宁可保守一点,也不要为了灵活性牺牲可控性。

1.4 进化层:反馈闭环与持续优化

“进化”这个词听起来很虚,但落到工程上其实很具体:Agent 跑完任务之后,系统能不能自动收集反馈、评估效果、发现问题、迭代改进。

反馈来源有几个:用户显式评价、任务成功失败信号、人工审核结果、自动评估指标。这些反馈要能回流到数据层和编排层,形成闭环。比如发现某类问题召回率低,就补充知识数据;发现某个工具调用频繁失败,就优化工具封装;发现某条规划路径经常走不通,就调整编排策略。

没有进化能力的 Agent 系统,本质上是一次性工程。上线那天是什么水平,半年后还是什么水平。而有了反馈闭环,系统才能持续变好。

2. 核心组件拆解与选型逻辑

2.1 向量检索与混合检索的取舍

知识数据的检索是 Agent 基础设施里最常被讨论的部分。纯向量检索擅长语义匹配,但对精确关键词、数字、代码标识符的处理往往不够好。纯关键词检索精确但缺乏语义泛化能力。实际项目中,混合检索几乎是标配。

混合检索的常见做法是:一路用向量召回,一路用 BM25 或全文索引召回,然后做融合排序。融合算法可以用 RRF(Reciprocal Rank Fusion)或加权分数融合。RRF 的好处是不需要调权重,对分数尺度不敏感,实现简单,适合快速上线。加权融合更灵活,但需要根据业务调参。

我踩过的一个坑是:切分策略没做好,导致检索效果差。文档切得太碎,语义不完整;切得太大,噪声多。我的经验是,切分要结合文档结构,标题、段落、表格分别处理,chunk 大小控制在 300 到 800 token 之间,并且保留一定的重叠。对于表格和代码,最好单独处理,不要和正文混在一起切。

另一个坑是元数据设计。很多人只存文本和向量,不存来源、时间、权限等元数据。结果检索出来没法过滤,也没法追溯。元数据字段要在入库时就设计好,后面补代价很大。

2.2 状态管理与执行持久化

Agent 的状态管理是区分 demo 和生产的关键。demo 里状态放内存,跑一次就丢,没问题。生产里状态必须持久化,因为任务可能跑几分钟甚至几小时,中间可能重启、可能失败、可能需要人工介入。

状态存储的选型要看场景。如果状态结构简单、读写频繁,Redis 这类键值存储够用。如果需要复杂查询和事务,关系库更合适。如果状态是事件流形式,可以考虑事件存储或消息队列。

关键设计点是“可恢复性”。Agent 执行到一半挂了,重启后能不能从断点继续?这要求每一步的状态变更都要落盘,并且有明确的步骤标识。我通常会用“执行 ID + 步骤序号”作为状态键,每次步骤完成后写入状态,恢复时从最后一个完成步骤继续。

还有一个容易被忽略的点是状态清理。Agent 跑多了,状态数据会膨胀。需要设计 TTL 或归档策略,否则存储成本会失控。

2.3 工具调用的标准化与容错

工具调用是 Agent 和外部世界交互的桥梁。工具设计得好不好,直接决定 Agent 的可用性。

标准化方面,我建议所有工具都遵循统一的接口约定:输入用 JSON Schema 描述,输出包含状态码、数据、错误信息三部分。这样编排层可以统一处理,不需要为每个工具写特殊逻辑。

容错方面,几个关键策略:超时控制、重试退避、熔断降级、幂等保证。超时控制防止工具卡死拖垮整个 Agent;重试退避避免瞬时故障导致任务失败;熔断降级在工具持续不可用时快速失败;幂等保证重试不会产生副作用。

我遇到过一个典型问题:某个查询工具没有做超时控制,数据库慢查询时 Agent 一直挂着,最后整个任务超时。后来加了 5 秒超时和两次重试,稳定性明显提升。

2.4 可观测性:日志、指标与追踪

Agent 系统的可观测性比传统应用更重要,因为它的执行路径是动态的、非确定的。同样的输入,Agent 可能走不同的路径,出问题时很难复现。

日志要记录每一步的决策依据、工具调用参数和结果、耗时、token 消耗。指标要覆盖任务成功率、平均步数、工具调用失败率、端到端延迟。追踪要能把一次任务的所有步骤串起来,形成完整链路。

我习惯用 OpenTelemetry 这类标准协议来做追踪,好处是生态成熟、工具多。日志用结构化格式,方便后续查询和分析。指标接入现有监控系统,设置告警阈值。

没有可观测性的 Agent 系统,优化就是盲人摸象。你只知道成功率低,但不知道低在哪一步、哪个工具、哪类问题。

3. 从零搭建一套可用的 Agent 基础设施

3.1 环境准备与依赖选型

假设我们要搭建一套支持知识检索、工具调用、状态持久化、执行追踪的 Agent 基础设施。以下是我在实际项目中验证过的一套组合,供参考。

基础环境方面,Python 3.10+ 是当前 Agent 生态最友好的选择,主流框架支持都比较好。容器化用 Docker,编排用 Docker Compose 起步,规模大了再上 K8s。

核心依赖选型:

组件选型理由
向量库Qdrant 或 Milvus开源、性能好、支持混合检索
关系库PostgreSQL状态存储、元数据管理、事务支持
缓存Redis会话状态、限流、临时数据
消息队列RabbitMQ 或 Kafka异步任务、事件流
追踪OpenTelemetry + Jaeger标准协议、生态成熟
日志ELK 或 Loki结构化日志采集与查询

这套组合的特点是每个组件都有成熟的开源方案,社区活跃,遇到问题容易找到答案。不需要一上来就追求最先进的方案,稳定可靠优先。

3.2 数据管道搭建:从原始文档到可检索知识库

数据管道的第一步是采集。来源可能是本地文件、对象存储、数据库、API。采集时要记录来源元数据,包括路径、时间、类型、权限标签。

第二步是解析。PDF、Word、HTML、Markdown 各有各的解析方式。PDF 解析是难点,表格和公式容易丢。我通常用专门的解析库处理,解析后人工抽检一批,确认质量。

第三步是切分。按文档结构切分,标题作为层级信息保留,段落作为基本单元,表格和代码单独处理。chunk 大小 300 到 800 token,重叠 50 到 100 token。

第四步是向量化。选 embedding 模型要考虑语言、领域、成本。中文场景下,多语言模型通常够用,专业领域可能需要微调。向量化要批量处理,注意速率限制。

第五步是入库。同时写入向量库和全文索引,元数据存关系库。写入时要保证一致性,可以用事务或补偿机制。

# 数据入库的简化示例 def ingest_document(doc): chunks = split_document(doc) for chunk in chunks: vector = embed(chunk.text) vector_store.upsert( id=chunk.id, vector=vector, payload={ "text": chunk.text, "source": doc.source, "title": chunk.title, "timestamp": doc.timestamp, "permission": doc.permission } ) fulltext_index.add(chunk.id, chunk.text) metadata_db.insert(chunk.metadata)

3.3 编排引擎实现:状态机加 LLM 决策

编排引擎我倾向于用“状态机为主、LLM 为辅”的架构。状态机定义任务的整体流程和状态转移,LLM 负责在关键节点做判断和生成。

这样做的好处是:主流程可控、可测试、可追踪;LLM 只在需要灵活性的地方介入,降低不确定性。纯 LLM 驱动的编排虽然灵活,但调试成本高,生产环境风险大。

状态机的状态包括:初始化、检索知识、规划下一步、调用工具、观察结果、判断是否完成、生成回复、结束。每个状态有明确的进入条件和退出条件。

LLM 介入的节点主要是:规划下一步动作、判断工具返回结果是否满足需求、生成最终回复。这些节点用结构化输出约束 LLM,让它返回固定格式的决策,便于程序处理。

# 状态机核心循环的简化示意 class AgentExecutor: def run(self, task): state = self.load_state(task.id) or InitialState() while not state.is_terminal(): if state.needs_llm_decision(): decision = self.llm_decide(state) next_state = self.transition(state, decision) else: next_state = self.transition(state) self.persist_state(task.id, next_state) self.trace(state, next_state) state = next_state return state.result

3.4 工具层封装:统一接口与安全边界

工具封装的核心是统一接口。我定义了一个 Tool 基类,所有工具继承它,实现name、description、input_schema、execute四个部分。

description很重要,因为 LLM 靠它来决定什么时候调用这个工具。描述要清晰说明工具的功能、适用场景、输入输出格式。

input_schema用 JSON Schema 定义,用于参数校验和给 LLM 提供调用格式参考。

execute是实际执行逻辑,内部要处理超时、重试、异常。

class Tool: name: str description: str input_schema: dict def execute(self, params: dict) -> ToolResult: raise NotImplementedError class DatabaseQueryTool(Tool): name = "query_database" description = "执行只读 SQL 查询,返回结果集。适用于数据统计和分析场景。" input_schema = { "type": "object", "properties": { "sql": {"type": "string", "description": "只读 SQL 语句"} }, "required": ["sql"] } def execute(self, params): sql = params["sql"] if not self.is_readonly(sql): return ToolResult.error("仅支持只读查询") try: result = self.db.query(sql, timeout=5) return ToolResult.success(result) except TimeoutError: return ToolResult.error("查询超时") except Exception as e: return ToolResult.error(str(e))

安全边界方面,几个必须做的:SQL 只读校验、文件路径白名单、API 调用频率限制、敏感操作二次确认。这些不是可选项,是生产环境的底线。

3.5 可观测性接入:追踪、日志与指标

追踪用 OpenTelemetry,每个任务创建一个 trace,每个步骤创建一个 span。span 上记录步骤类型、输入输出、耗时、状态。

from opentelemetry import trace tracer = trace.get_tracer("agent") def execute_step(step): with tracer.start_as_current_span(f"step.{step.type}") as span: span.set_attribute("step.id", step.id) span.set_attribute("step.type", step.type) span.set_attribute("step.input", str(step.input)[:1000]) try: result = step.run() span.set_attribute("step.status", "success") span.set_attribute("step.output", str(result)[:1000]) return result except Exception as e: span.set_attribute("step.status", "error") span.set_attribute("step.error", str(e)) raise

日志用结构化格式,每条日志包含任务 ID、步骤 ID、事件类型、详细信息。方便后续按任务或步骤查询。

指标方面,我关注几个核心:任务成功率、平均步骤数、工具调用失败率、P95 延迟、token 消耗。这些指标接入监控面板,设置告警。

4. 常见问题与排查技巧实录

4.1 检索效果差:从切分到重排的排查路径

检索效果差是最常见的问题。排查顺序我一般是:先看切分,再看 embedding,再看召回,最后看重排。

切分问题表现为:检索出来的内容语义不完整,或者包含大量无关内容。解决方法是调整 chunk 大小和重叠,按文档结构切分,表格代码单独处理。

embedding 问题表现为:语义相近的内容检索不到。解决方法是换模型、检查向量归一化、确认查询和文档用同一个模型。

召回问题表现为:正确内容在库里但没被召回。解决方法是增加召回数量、启用混合检索、检查索引是否完整。

重排问题表现为:正确内容召回了但排在后面。解决方法是引入重排模型,或者调整融合权重。

我遇到过一个案例:用户问“上季度销售额”,检索总是召回“销售额定义”而不是实际数据。原因是数据在表格里,切分时表格被拆散了。后来把表格单独处理,每行作为一个 chunk 并保留表头,问题解决。

4.2 工具调用失败:超时、格式与权限

工具调用失败的原因很多,我整理了一个速查表:

现象可能原因排查方法解决
超时下游慢、网络差看工具耗时分布加超时、重试、降级
格式错误参数不符合 schema看调用参数日志加强 schema 校验、优化 description
权限拒绝凭证过期、权限不足看错误码刷新凭证、检查权限配置
幂等冲突重复调用产生副作用看调用次数加幂等键、去重
结果解析失败返回格式变化看原始返回加解析容错、版本兼容

我踩过最深的坑是幂等问题。一个发邮件的工具,Agent 重试时发了多封。后来加了幂等键,同一个任务同一个步骤只发一次。

4.3 状态丢失与恢复失败

状态丢失通常有几个原因:存储没落盘、键设计冲突、恢复逻辑有 bug。

存储没落盘:检查持久化配置,确认写入成功再进入下一步。我习惯在状态写入后加一个确认读,确保数据真的在。

键设计冲突:任务 ID 和步骤 ID 组合要唯一。多租户场景要加租户 ID。并发场景要加锁或版本号。

恢复逻辑 bug:恢复时要校验状态完整性,缺失的步骤要能重新执行。我通常会在状态里记录“已完成步骤列表”和“当前步骤”,恢复时从当前步骤继续。

一个实用技巧:状态里存一个“检查点”,记录关键中间结果。恢复时从最近检查点开始,不用从头跑。

4.4 成本失控:token 与调用次数的优化

Agent 跑起来之后,成本很容易失控。主要成本来自 LLM token 和工具调用。

token 优化:精简 prompt、压缩上下文、缓存重复查询、用小模型做简单判断。我通常会把历史对话做摘要,只保留关键信息,而不是全量塞进去。

调用次数优化:减少不必要的工具调用、合并相似调用、加缓存。比如同一个查询在短时间内重复,直接返回缓存结果。

还有一个容易被忽略的点是失败重试的成本。如果重试策略太激进,失败任务会消耗大量资源。重试要有上限,并且退避时间要合理。

我一般会设置每日预算告警,超过阈值就降级或暂停,避免账单爆炸。

4.5 安全边界:提示注入与越权操作

Agent 能调用工具,就意味着它有“行动能力”。行动能力带来便利,也带来风险。

提示注入是常见攻击方式:用户在输入里嵌入恶意指令,诱导 Agent 执行非预期操作。防御方法是把用户输入和系统指令严格分离,对用户输入做清洗,关键操作加二次确认。

越权操作是另一个风险:Agent 调用了不该调用的工具,或者访问了不该访问的数据。防御方法是工具级权限控制、数据级权限过滤、操作审计。

我的经验是,安全边界要在设计阶段就考虑,不要等上线后再补。尤其是涉及数据修改、文件操作、外部调用的工具,必须加确认机制。

5. 一些实操心得与后续扩展方向

5.1 从小场景切入,别一上来就做通用 Agent

我见过太多团队一上来就想做“通用智能体”,结果做了半年还在 demo 阶段。通用 Agent 的复杂度是指数级的,涉及的能力太多,很难在短时间内做好。

更务实的做法是选一个具体场景,把闭环跑通。比如“自动整理会议纪要并生成待办”,或者“根据工单内容自动分类并推荐解决方案”。场景越具体,边界越清晰,越容易做好。

做好一个场景之后,再把能力抽象出来,复用到其他场景。这样每一步都有产出,风险可控。

5.2 评估体系要早建,不要等上线后再想

Agent 的效果评估比传统模型复杂,因为它的输出是开放的、多步的。没有评估体系,你根本不知道改动是变好了还是变差了。

评估体系包括:测试集、评估指标、自动化评估流程。测试集要覆盖典型场景和边界情况。评估指标要结合业务,比如任务完成率、平均步数、用户满意度。自动化评估流程要能快速跑,每次改动后都能验证。

我通常会在项目早期就建一个小的评估集,哪怕只有几十条,也比没有强。随着项目推进,不断补充和优化。

5.3 后续可以扩展的方向

这套基础设施搭好之后,可以往几个方向扩展。

一是多 Agent 协作。单个 Agent 能力有限,多个 Agent 分工协作可以处理更复杂的任务。这需要额外的通信机制、协调策略、冲突解决。

二是自适应优化。根据运行数据自动调整检索策略、工具选择、规划路径。这需要更强的反馈闭环和在线学习能力。

三是领域深化。针对特定领域做深度优化,比如医疗、法律、金融,需要领域知识注入、专业工具集成、合规性保障。

四是成本优化。通过模型蒸馏、缓存策略、调用合并等手段持续降低成本,让 Agent 在更大规模上可用。

这些方向不是一蹴而就的,但基础设施搭好之后,每一步扩展都有基础支撑。我在实际项目中的体会是,基础设施的投入前期看起来慢,但后期会越来越快,因为所有上层能力都建立在它之上。反过来,如果基础设施欠债,后面每加一个功能都要还债,越做越累。

返回列表