深入解析基于 LangGraph 的生产级 AI Agent 架构:Bedrock 知识库按需检索、MCP 工具源、人类审核持久化、代码沙盒执行及 Prompt 缓存等关键设计。
每个 AI agent demo 都是调用一个工具然后打印结果。没有一个真正展示 agent 实际上在推理什么、工具调用需要人工确认时会发生什么、需要真正计算而不是猜测时会发生什么,以及在每一轮都描述几十个工具的 token 账单如何在不知不觉中蚕食你的利润空间。ai-agent-template 是一个 LangGraph agent,专门为解决这些问题而构建:按需调用的 Bedrock Knowledge Base 检索,而不是每轮都自动注入,让它有真实的内容来支撑答案——这可以说是整个系统的真正大脑——工具完全从 MCP 获取而非手工逐个注册、带有人工审批的人机协作机制且在决策过程中服务器重启也能恢复、用于处理工具调用无法完成的数学和图表的真正代码沙箱、永不让聊天因失败而崩溃的输入护栏、以及真正控制 token 成本的 prompt 缓存而不是仅仅宣称有。默认运行在 OpenAI 上——clone 一下、填入 API key,无需 AWS 账号——一旦你真正需要生产级的能力(真正的护栏、持久化记忆、CloudWatch 可观测性),Bedrock 是推荐路径。
接下来会介绍:让上述每一点成为现实的中间件链、一个看似浪费但仔细算过数字后才发现合理的缓存策略、三个已修复的线上 bug,以及一次真实的 Bedrock 线上运行是什么样的。

与其将每个关注点——记忆、prompt 缓存、护栏、上下文限制——直接写入 graph 节点逻辑,不如把每一个都做成独立的中间件,按顺序组合,可独立测试。大致按执行顺序:生命周期(调用前读取记忆、调用后写入轮次摘要)、动态上下文注入(每轮追加记忆和文档,从不写回消息历史)、prompt 缓存设置、上下文溢出安全网(在调用返回过大时裁剪并重试一次)、输入护栏(每轮运行一次而非每次模型调用都运行)、以及与风险分类器配对的 HITL 策略,决定哪些工具调用需要人工介入。
工具在每次模型调用时重新绑定,而非硬编码到静态 graph 中;系统 prompt 本身在启动时从配套的 MCP 服务器获取而非硬编码——agent 不定义自己的工具,而是从外部获取。

HITL with stateless resume. 审批、拒绝、回复或编辑一个提议的工具调用。中断发生在 graph 执行时,而非中间件内部——轮次结束,等决策回来后,一个独立的新轮次从检查点恢复继续。正是这一点使得审批能够跨服务器重启存活,而不依赖连接保持打开。
Tools sourced entirely from MCP. 没有手工注册的工具列表——它通过 HTTP 连接到配套的 MCP 服务器,在启动时列出工具一次,并自动转换为 LangChain 工具。
Knowledge-base retrieval via Bedrock KB. kb_retrieve 是一个真实的工具,与从 MCP 获取的工具一起绑定,由 Bedrock Knowledge Base 的只读 Retrieve API 支持——模型在决定需要外部知识时按需调用,而不是每轮都自动注入上下文,不管需不需要。结果按来源去重并附带引用。如果没有配置 KB ID,工具就不会注册——agent 正常运行其他工具。这一点让 agent 能够基于你自己的文档来支撑答案,而不是靠猜测——可以说比任何一个单独的工具承担了更多真正的推理工作。无论哪个模型提供商活跃,Bedrock 独有。
Guardrails. 在轮次完成前运行真正的 ApplyGuardrail 检查——真正的内容/主题过滤,不是关键词列表——设计上为 fail-open,所以护栏故障会优雅降级而不是让聊天崩溃。
Long-term memory across conversations. AgentCore 支持的会话记忆,持久化跨轮次和跨会话的事实,而不是仅限单会话内;如果后端存储不可达,自动回退到内存状态。
Observability. 带有轮次关联 ID 的结构化日志,加上标准化 token 和缓存计费,接入 CloudWatch traces 和 metrics——不是靠你去找的 print 语句。
Prompt caching that's actually measured. 每次调用都做标准化 token 和缓存计费,因为没人看得见的缓存指标比没有指标更糟糕。
A real code sandbox for math and charts (run_python). 一个远程 AgentCore 沙箱,当答案需要真正计算时——统计、聚合、Plotly 图表——agent 把任务交给它,而不是靠工具调用。服务器拥有完整的生命周期(启动、执行、注册生成的文件、停止)在一个自包含的调用中;模型永远不会看到沙箱 id。它完全无法访问这个 agent 自己的工具——这是故意的,见下。
An orchestration tool for dependent tool calls (run_orchestration). 一个单独的、本地的、亚秒级的沙箱,专门处理单一工具调用无法处理的场景:相互依赖的调用——链、分支、重塑、循环——不需要每步都浪费一次往返。只读;更多解释见下。
A WebSocket API, plus a health endpoint.
HITL 和 MCP 工具获取之后的所有功能默认关闭——每一个都只需一个环境变量即可开启,在全部未设置的情况下 agent 也能干净地完成一个完整轮次。
一个显而易见的优化是只检索与用户当前问题相关的工具并绑定它们。一旦 prompt 缓存介入,这就是错误的做法:缓存的前缀写入昂贵但读取便宜,而这个溢价只有在前后轮次前缀保持稳定时才能回本。每轮收窄工具集意味着每次都改变前缀,击碎了你本来想用的缓存。所以这个模板故意反其道而行——无差别地、强制地、每轮绑定所有工具。block 大了一些,但逐字逐句相同,所以能待在缓存里:一次付清写入溢价,之后读取便宜。
A telemetry counter that read zero forever. 接到了一个被重构掉的代码路径,所以它一直报告零而不是报错——修复:在测试中针对当前路径断言计数器存在,而不只是断言它存在。
HITL state that didn't survive a restart. 在运行进程中持有状态的审批循环,如果审批过程中进程重启就会丢失一切——修复:在 graph 执行时中断,结束轮次,从检查点作为新轮次恢复。
A safety rule living only in the prompt. "只自动审批只读工具"作为系统 prompt 指令,意味着你要求一个概率模型每轮都遵守这条规则——修复:一种命名约定(get_* 是只读,其他需要审批)由风险分类器在代码中强制执行,而不是在文本中请求。
结构层面:每个中间件和网关都用了假模型独立测试,集成测试针对模拟 MCP 服务器和假 Bedrock 客户端运行完整轮次——日常开发不需要真实的模型调用。两个沙箱同样处理:run_orchestration 的测试确认工作线程/主循环的交接确实发生(不只是被调用了),确认变更被拒绝并返回审批消息而不是静默跳过,确认调用预算能切断失控循环;run_python 的测试针对假的 CodeExecutionService 运行,确认沙箱在每个失败分支都停止,而不只是正常路径——没有任何一个接触真实的 AWS 或 MCP。端到端、真实的:一次针对真实 Bedrock 的线上运行干净地完成了完整轮次——护栏检查应用了,响应逐 token 流回,轮次完成无需人工干预。
如果你想自己试试,有一个前置条件值得提一下:在默认的 OpenAI 提供商上,你只需要一个 API key——不需要 AWS 账号。切换到 Bedrock 就需要真实的 AWS 凭证才能访问,一旦配置了 AgentCore 记忆更是如此——模型调用本身没有离线或 mock 路径。本地 AWS_PROFILE、BEDROCK_API_KEY bearer token,或显式的 AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY/AWS_SESSION_TOKEN 都可以;一旦选中 Bedrock,agent 缺少任何一个都无法启动。
OpenAI by default, Bedrock for production. OpenAI 是默认提供商,API key 零配置,create_model() 是简单的 if/else 分派而非提供商接口——与这个模板在其他地方所做的防过度工程化选择一致。Bedrock 是生产环境的推荐提供商,因为有六样东西在 OpenAI 根本没有等价物:真正的 ApplyGuardrail 调用而非关键词过滤、Bedrock Knowledge Base 检索、AgentCore 的持久化可恢复记忆、真正的代码解释器沙箱、cachePoint 调优的 prompt 缓存、跨区域模型访问、以及基于 IAM 的凭证而非 env 文件中的 API key。工程努力花在了让这每一项在你不在 Bedrock 上时都能优雅降级而非报错:护栏回退到基于 LangChain 自身检测函数构建的真正 PII 脱敏检查(匿名化并继续,而非阻断,符合框架自身有文档的指导),记忆即使 AgentCore memory ID 仍在配置中也会自动回退到内存状态,知识库检索如果未配置就根本不注册为工具。87 个测试专门覆盖这些降级路径,而不只是 Bedrock 的正常路径,套件中对任何一个提供商都没有真实调用。
Two sandboxes, kept deliberately separate. run_python 和 run_orchestration 从外部看起来相似——都允许模型写代码而不是在纯文本中推理——但它们存在的原因正好相反,且没有一个能完成另一个的工作。run_python 是一个远程 AgentCore 沙箱,有 pandas/numpy/plotly 且完全无法访问这个 agent 自己的工具,用于数学和图表。run_orchestration 是一个本地、进程内的沙箱,根本没有数学库,它的全部目的是在依赖链中调用已绑定的工具而无需每步一次往返。它们合并成一个"代码工具"意味着要么给数学沙箱访问它根本不该做的实时工具调用,要么在一个不需要它的工具调用循环上硬塞 pandas——分开之后,每个都足够小,可以推理清楚。
Read-only inside the tool-calling sandbox, on purpose. run_orchestration 的明显版本允许脚本调用任何已绑定的工具,包括变更——很有用,因为链/重塑/循环模式通常以写入结尾。但这同时也是真正的人机协作绕过:从沙箱内脚本发出的调用永远不会经过普通工具调用的审批路径,所以埋在循环里的变更会直接执行而无人审查,尽管相同调用直接由模型发出时会先停等人审批。这里的修复是一条硬规则,而非 prompt 指令:沙箱的工具桥接层检查与 agent 其他部分使用的相同风险分类器,只有只读(get_*)工具可以从内部访问。需要变更的计划必须先用 run_orchestration 收集所需内容,然后直接调用变更,此时人类才能真正看到它。不如无限制版本能力强——但这是保持这个模板在其他地方已做出的人工审批保证完整所必须付出的诚实权衡。
Middleware over graph nodes. 缓存和护栏这类关注点在作为独立中间件时更容易添加、移除或重排序,而不是把逻辑穿在 graph 本身里。
Ephemeral context, never persisted to history. 记忆和文档在调用时渲染进 prompt,从不写回消息列表——保持缓存前缀稳定,也防止消息历史因仅与单轮相关的数据而膨胀。
Resilient fallback over hard failure. 如果 AgentCore 记忆或持久化不可达,agent 降级到内存状态并记录一次警告,而不是让聊天崩溃。
最后三件事之一:聊天 UI,通过 WebSocket 与这个 agent 通信——一个 React 19 应用,渲染流式 token、工具调用和 HITL 审批提示。
模板是 MIT 许可且公开的:ai-agent-template。它获取工具的配套 MCP 服务器是 mcp-server-template。