一个端到端可跑的 AWS Bedrock AgentCore Harness 示例:从浏览器登录到数据落地, 中间的身份透传、工具授权、知识检索、评测护栏都在仓库里,不是片段。
起点是一个真实的大型 Dify 路由型售后智能体(12k 行 DSL / 192 节点):它的意图识别层 用"工具描述即路由"的方式复刻,18 路条件分支随之消失。但真正花掉时间的不是复刻, 而是当智能体代替技师访问业务系统时,权限该怎么办——那是四层授权的由来。
选 Harness 而非 Runtime:不需要自建容器,工具以 inline_function 在客户端执行,
因此身份可以原样透传给数据源与策略引擎。代价见下文实测结论与关键技术点。
串起来的 AgentCore 模块:
| 模块 | 承担 |
|---|---|
| Harness | 托管 agent 循环、工具路由、多轮上下文(Managed Memory) |
| Gateway + Cedar | 受限工具以 MCP 暴露,按 JWT 岗位声明判权(默认拒绝) |
| Managed KB | 车主手册 / 维修手册 / 质保政策,按知识域隔离检索 |
| Evaluations + Observability | 确定性评测 + LLM 裁判 + 代码型合规校验 |
| Cognito / DynamoDB / Lambda / S3 | 代演车企既有系统与工具执行体 |
- 源系统架构分析与迁移映射(脱敏):SOURCE-ARCHITECTURE.md
- Agentic 增益场景集(原架构做不到的那一类):SCENARIOS.md
- 评测方法与结果报告(golden dataset 由来 / evaluator 清单 / 三配置对比):EVALUATION-REPORT.md
- 接 Web 前端(AG-UI):Harness 路线(本仓库所用)· Runtime 路线(自建容器,Runtime 已原生支持 AG-UI)
本仓库为技术方案与工程实现。工具名与平台名已匿名化为通用标识;VIN / 车牌 / 车主姓名 / 手机号 / 各类单号均为构造的测试数据。车型名是车企公开产品名(取自其公开文档), 刻意与知识库语料对齐——对不上不会表现为"查不到",而是答出另一车型的政策, 因为该 KB 的检索分数被归一化,问语料未覆盖的车型也返回高分。
| 文件 | 说明 |
|---|---|
agent/tools_def.py |
22 个工具定义(description 就是路由规则,改路由=改描述),其中 12 个已接真实后端 |
agent/system_prompt.py |
横切规则:实体识别 / 反问 / 多轮 / 脱敏 / 平台裁剪 |
agent/invoke.py |
InvokeHarness 封装:流解析、toolResult 回填、模型/缓存 override |
agent/eval_dataset.py |
80 条 GSR 导向评测集(可接受工具集合 + 必须实体) |
agent/run_eval.py |
确定性评测器(PASS/ENTITY/FAIL 三级判定,支持 --model/--cache) |
agent/run_mtg_eval.py |
Mind-the-Goal LLM 裁判接入(AgentCore Evaluations),含 harness span 正文合并 adapter |
deploy.sh |
harness 供给:以 tools_def.py / system_prompt.py 为配置源推送线上配置 |
agent/kb_def.py |
知识库域定义(6 个知识域 → S3 前缀 → 对应工具),含与 tools_def.py 的交叉自检 |
deploy_kb.sh |
Managed KB 供给:建 KB + 每域一个 data source,含 upload / sync / query |
tools/ |
工具实现,按后端依赖分组(一组=一个 Lambda):vehicle-data 7 个、knowledge 5 个 |
tools/gateway.sh |
Lambda / Gateway / Cedar 策略供给(受限工具的判权落点) |
webui/ |
服务端桥(SSE ↔ InvokeHarness,含 toolResult 回填)+ 单文件前端,无构建 |
simulated-oem/ |
车企既有系统的替身:Cognito 账号体系 + 业务库演示数据(生产整体退场) |
eval/ |
代码型评估器(输出合规硬校验,如回答中出现完整 VIN 即判失败) |
teardown.sh |
环境拆除:按名字白名单删本项目资源,dry-run 默认、要求键入账号号码 |
评测报告 agent/eval_*.json 为运行产物,不入库(各人跑出的结果不同,入库只会互相覆盖);
历史实测数字见 EVALUATION-REPORT.md。
cp agent/.env.example agent/.env # 填入你的 harness ARN 等
# 部署(需 aws CLI >= 2.36,旧版无 harness 子命令)
./deploy.sh create <execution-role-arn> # 新建 harness,输出 ARN
./deploy.sh grant-memory # 一次性:给执行角色补 managed memory 权限
./deploy.sh update # 改完工具描述/prompt 后推送,生成新版本
./deploy.sh show # 线上状态 + 本地/线上工具集漂移检查
# 拆除(切换环境后回收旧环境;默认 dry-run,真删要 --yes 并键入账号号码)
AWS_PROFILE=<profile> ./teardown.sh # 只列出将删什么
AWS_PROFILE=<profile> ./teardown.sh --yes # 真删(托管 KB 索引拆除约 18 分钟)
# WebUI(本地起,浏览器看编排与授权)
./webui/run.sh --check # 只校验环境变量与登录连通性
./webui/run.sh # http://localhost:8088
# 评测
python3 agent/run_eval.py --workers 6 # 全量评测
python3 agent/run_eval.py --cache # 开 prompt caching
python3 agent/run_eval.py --model <model-id> --cache # 换模型
python3 agent/run_mtg_eval.py --demo # LLM 裁判演示
# 知识库(Bedrock Managed KB,全托管 RAG)
./deploy_kb.sh bootstrap-role # 一次性:建 KB 服务角色
./deploy_kb.sh create # bucket + KB + 每域一个 data source
./deploy_kb.sh upload warranty-policy a.pdf b.pdf # 上传 + 侧车 metadata + 自动 sync
./deploy_kb.sh query warranty-policy "三电质保多久" # retrieve 冒烟(按域过滤)
./deploy_kb.sh show # KB 状态 + 各域文档数 + 漂移检查须用 python3(boto3 ≥ 1.43):默认 python3 是 3.9,既不支持 str | None
语法,其 boto3 1.42 也缺 harness 与 managed KB 的 shape。deploy_kb.sh 走 aws CLI
(≥ 2.36)以绕开这一点。
grant-memory 是必要的一步:harness 会自动配 managed memory,但不会给执行角色对应权限,
不补则首次调用即 ListEvents AccessDenied。它单列而非挂进 create,因为需要
iam:PutRolePolicy 这一高危权限,常规部署不该带着它跑。
| 配置 | GSR | 端到端 p50/p90/p99 | 成本/千次 |
|---|---|---|---|
| Sonnet 无缓存 | 100% | 3.6 / 5.0 / 12.0s | ~$20 |
| Sonnet + cache(已设为默认) | 100% | 3.8 / 5.4 / 11.5s | ~$4.6 |
| Haiku 4.5 + cache | 92.5% | LLM p50 1.4s | ~$1.1 |
关键技术点:
- harness 无原生 cachePoint 配置,经
model.bedrockModelConfig.additionalParams覆写 Conversesystem数组注入,span 中cache_read_input_tokens验证生效 - inline_function 回填 toolResult 须与 assistant toolUse 消息成对传
- harness 的 aws/spans 不含消息正文,正文在 runtime 日志组 OTel records, 喂给 Evaluations 前需合并(见 run_mtg_eval.py 的 merge())
四层,各管一件事。面向评审的完整说明页是 webui/static/about.html,从登录页可进:
| 层 | 管什么 | 落在哪 |
|---|---|---|
| 身份 | 你是谁,不由你自己说 | IdP 签名的 cognito:groups;staffId 不在任何工具 schema 里 |
| 工具 | 这个岗位能否用这个能力 | Gateway 上的 Cedar 策略,默认拒绝 |
| 数据范围 | 能看哪些车 | 门店级隔离,校验放在工具分发处 |
| 字段 | 同一台车能看哪些字段 | 拆工具:维修上下文与车主隐私读不同的排序键 |
前端不做任何权限判断:受限工具照常下发给模型、模型照常调用,由服务端拒绝。 前端隐藏按钮式的"权限"是假的,而这正是本项目要证伪的东西。
客户原始 DSL、接口清单等敏感材料不入库(见 .gitignore)。
本仓库外发,且多人各有自己的 AWS 环境,所以:
| 入库 | 例 | |
|---|---|---|
| 资源名 | ✅ | aftersales-gw、aftersales_demo、aftersales_policy_engine |
| 资源 id / ARN / 账号 / 池 id / 端点 | ❌ | 一律走环境变量或 *.local(见 .gitignore) |
理由有两条。一是脱敏:id 里含账号号码。二是避免张冠李戴——曾出现一份文档按某个人的 环境断言"harness 已重建、实验资源已清理",而另一人的线上完全是另一回事,据此做清理会误删在用资源。
所以描述线上状态时只写资源名 + 用途,
具体 id 各自记在本地不入库的文件里。每个供给脚本都提供 show 子命令,
现状以脚本读回的为准,而非文档里的记载。
同一条理由决定了 teardown.sh 按名字白名单删而不按 aftersales-* 前缀匹配:
账号认错时,前缀匹配会删掉别人的同类资源。