Weekend Agent Challenge

早上 6 点交易风险审查

基于 AgentCore Runtime、Memory、Identity、S3 与 SES 的事件驱动多 Agent 交易风险审查架构。

周末 Agent 挑战:早上 6 点交易风险审查

早间交易风险审查邮件

交易风险审查系统架构

应用或仓库链接

源代码:https://github.com/dchan-dev/aws_morning_trading_review_mail

六个运行时包分别是 BrainAgentCreditMemoAgentCreditProductsAgentCreditRiskManageAgentCreditTradingAgentFinModelAnalystAgent

最好的演示不是一个聊天窗口,而是一串安静但可验证的证据:调度器在 06:00 触发,六份研究产物出现在 S3 中,一份风险简报在交易员开口询问前送达。到了这个时刻,Agent 才不再只是一个有趣的提示词,而开始成为一个系统。


详情

每天早上 6:00,在宏观交易台开盘之前,一个事件会启动一条研究工作流。它不像聊天机器人,更像一个小型的虚拟信用委员会。

它收集当前市场证据,让五个专家 Agent 从不同专业视角审查同一个风险问题,再由 Brain Agent 质疑并综合它们的结论,把研究轨迹保存在 Amazon S3 中,并通过电子邮件发送早间报告。分析师不需要登录系统、复制提示词,也不需要守在浏览器旁边等待。

最后这一点是这个项目的核心:有用的工作会在没有我介入的情况下完成

本项目是一个研究和决策支持演示。它不执行交易,不发放信贷,也不替代经授权的风险、合规或投资专业人员。


愿景与 Agent 的工作内容

宏观交易员一天中的第一个小时成本很高。隔夜利率、汇率、大宗商品、主权利差和企业信用的变化,必须在流动性和市场注意力转移之前转化为仓位观点。普通新闻摘要并不够。交易台需要知道:

  • 发生了什么变化?
  • 这次波动是由增长、通胀、流动性、偿付能力、政策、仓位还是技术性资金流驱动的?
  • 它如何传导到主权债、企业信用、外汇、大宗商品和融资市场?
  • 哪些信息已经被定价?
  • 在考虑融资、对冲、流动性和执行成本之后,哪个仓位具有更好的 carry 和凸性?
  • 哪个可观察信号会推翻这个观点?

6 AM Trading Risk Review 将这个过程变成了一条事件驱动的 Agent 工作流。

Amazon EventBridge Scheduler 会在交易台配置的时区 06:00 调用一个小型 AWS Lambda 函数。Lambda 将一个有边界的早间审查请求发送给运行在 Amazon Bedrock AgentCore Runtime 上的 Brain Agent。Brain Agent 再把同一个问题委派给五个独立的专家运行时:

Agent 机构角色 主要贡献
CreditRiskManageAgent 信用风险经理 PD、LGD、EAD、预期损失、评级迁移、限额、契约、集中度和压力损失
FinModelAnalystAgent 基本面信用分析师 现金流标准化、杠杆、流动性跑道、偿债能力、蒙特卡洛和反向压力测试
CreditProductsAgent 产品结构师 融资条款、抵押品、受偿顺位、担保、CDS 机制、交易对手风险和剩余产品风险
CreditTradingAgent 信用交易员 现券/CDS 基差、相对价值、carry、roll-down、流动性、市场冲击、对冲规模和失效水平
CreditMemoAgent 信用审批备忘录起草助手 证据台账、重大风险、缓释因素、例外、条件和可供审阅的决策记录

Brain Agent 不是第六个简单投票的意见。它是编排者。它的提示词从风险环境和政策反应函数开始,比较专家之间的分歧,并把它们的发现转化为一份受治理的早间简报:市场环境、证据、组合影响、候选行动、对冲、确认信号和失效触发条件。

每个专家都有自己的领域提示词和 AgentCore Memory。记忆配置将语义事实、用户偏好、会话摘要和情景经验分开。这样系统可以记住交易台希望如何表达风险,同时不会假装昨天的市场事实今天仍然有效。

对于当前信息,Agent 可以使用 Exa.ai 网页搜索。Exa API key 应该作为 API-key 凭证提供方放在 AgentCore Identity 中,而不是放在源代码、Lambda 环境变量或提示词里。运行时只在工具需要时才取回凭证。搜索证据和 Agent 输出会写入 S3,这样最终建议可以追溯到每个专家看到的内容。

当最终的 Brain Agent 对象落到报告前缀下时,一个 S3 ObjectCreated 事件会调用交付 Lambda。该函数取回报告,并通过 Amazon Simple Email Service(Amazon SES)发送到宏观交易员已验证的邮箱地址。

交易员醒来时看到的是一份已经完成的审查,而不是一个提醒他开始审查的通知。

演示需要捕捉的证据: EventBridge Scheduler 在 06:00 成功调用、六个带时间戳的 S3 产物,以及收到的 SES 早间报告。这三张截图可以端到端展示这条自主工作流。


你是如何构建它的

1. 我先建模交易台,再选择 Agent 拓扑

最重要的设计决策不是模型,而是专业视角的分离。

在真实信用工作中,承销、组合风险、产品结构、市场定价和审批文档回答的是不同问题。把它们混在一起会产生熟悉但危险的类别错误:把利差当成纯粹的违约概率,把 PD 当成完整的授信决策,在考虑融资成本之前就把负的现券/CDS 基差称为套利,或者把经济损失直接对应到 IFRS 9 ECL。

因此,提示词会编码明确的金融恒等式和决策边界:

Expected Loss = PD × LGD × EAD

Expected Net P&L =
  Spread Alpha + Carry + Roll-Down + Catalyst Value
  - Default Loss - Hedge Cost - Funding Cost
  - Transaction Cost - Liquidity Cost - Model Error

Credit Trading Agent 必须区分预测和可执行策略。Financial Modeling Agent 必须把宏观冲击连接到借款人的现金流,而不是套用表面化的百分比折减:

Macro shock → volume decline → margin compression → working-capital draw
→ covenant erosion → revolver use → refinancing dependence
→ liquidity event → default or restructuring

Credit Memo Agent 会保留来源冲突,而不是把它们平均成虚假的共识。重大审批仍然属于负责任的人类。这不仅是提示词工程,也是控制设计。

2. 我将专家部署为独立的 AgentCore 运行时

六个 Python Agent 都使用 Strands Agents,并作为 HTTP 运行时运行在 Amazon Bedrock AgentCore 上。项目通过 CodeZip 打包,并通过 Amazon Bedrock 使用 Amazon Nova Pro 模型。

将专家保留在独立运行时中有几个实际好处:

  • 提示词、记忆、依赖和权限可以独立演进;
  • 运行时身份可以按角色收紧;
  • 某个专家可以单独测试或重新部署,而不影响其他专家;
  • 故障会按专家显现,而不是埋在一次很长的模型回合里;
  • 编排契约保持简单:输入提示词,输出有证据支持的 Markdown。

Brain Agent 通过 AgentCore 数据平面调用每个专家,收集流式响应,并把五个输出注入到自己的综合上下文中。当某个专家无法响应时,它也会记录一个明确的失败产物。一份指出缺失控制的部分早间报告,比静默假装审查完整更安全。

初始实现按顺序调用专家。这是有意保持简单、方便调试的选择,但由于五个审查彼此独立,并行分发是下一步延迟优化。在生产环境中,我会使用有界并发、整体截止时间和每个 Agent 的超时,而不是无限制并行调用。

代码讲解:六个 AgentCore 应用内部

app 目录有意保持一定重复。每个专家都是一个可独立部署的应用,而不是藏在编排器内部的一个 Python 类:

app/
├── BrainAgent/
├── CreditMemoAgent/
├── CreditProductsAgent/
├── CreditRiskManageAgent/
├── CreditTradingAgent/
└── FinModelAnalystAgent/

每个包都拥有相同的运行时边界:

<Agent>/
├── main.py                 # AgentCore 入口点、提示词和工具
├── model/load.py           # Amazon Bedrock 模型配置
├── memory/session.py       # AgentCore Memory 会话适配器
├── mcp_client/client.py    # Streamable HTTP MCP 客户端
├── pyproject.toml          # 独立运行时依赖
└── README.md

这少量重复换来的是有价值的隔离。信用交易依赖或提示词变更不会意外改变信用备忘录运行时。它也为六个包建立了统一的审查清单。

声明六个可部署运行时

AgentCore 项目配置会把每个包映射到一个 HTTP 运行时。实际文件声明了全部六个;下面这个简化示例展示了模式:

{
  "name": "tradingRiskReview",
  "runtimes": [
    {
      "name": "BrainAgent",
      "build": "CodeZip",
      "entrypoint": "main.py",
      "codeLocation": "app/BrainAgent/",
      "runtimeVersion": "PYTHON_3_14",
      "networkMode": "PUBLIC",
      "protocol": "HTTP"
    },
    {
      "name": "CreditTradingAgent",
      "build": "CodeZip",
      "entrypoint": "main.py",
      "codeLocation": "app/CreditTradingAgent/",
      "runtimeVersion": "PYTHON_3_14",
      "networkMode": "PUBLIC",
      "protocol": "HTTP"
    }
  ]
}

CodeZip 让这个 Python 演示保持简单:运行时会打包源代码和依赖,而不需要应用容器。当需要原生库、操作系统控制或自定义构建链时,容器仍然是更好的选择。

加载一个受治理的模型配置

每个运行时都通过一个小型工厂函数加载模型:

from strands.models.bedrock import BedrockModel


def load_model() -> BedrockModel:
    return BedrockModel(
        model_id="apac.amazon.nova-pro-v1:0",
        max_tokens=10000,
    )

这个工厂函数避免模型设置散落在编排代码中。10,000 token 上限是对专家报告在 4,000 token 处被截断的实际回应。更大的上限并不意味着可以无边界生成:提示词仍然要求输出章节简洁,生产控制也应跟踪每次运行的输入 token、输出 token、延迟和成本。

构建通用 Strands 运行时

每个专家都会创建一个 BedrockAgentCoreApp,注册研究和推理工具,并为每个会话和用户组合构建一个 Strands Agent

app = BedrockAgentCoreApp()

tools = [add_numbers, exa, tavily, think]
tools.extend(client for client in mcp_clients if client)


def agent_factory():
    cache = {}

    def get_or_create_agent(session_id, user_id):
        key = f"{session_id}/{user_id}"
        if key not in cache:
            cache[key] = Agent(
                model=load_model(),
                session_manager=get_memory_session_manager(session_id, user_id),
                conversation_manager=NullConversationManager(),
                system_prompt=DEFAULT_SYSTEM_PROMPT,
                tools=tools,
            )
        return cache[key]

    return get_or_create_agent

这个缓存避免在 warm runtime 中反复重建 Agent,而复合 key 可以防止不同会话中的两个用户共享同一个进程内 Agent 实例。持久上下文仍然在 AgentCore Memory 中;进程本地缓存只是一个优化,绝不能被当作持久状态。

专家 HTTP 入口点刻意保持很薄:

@app.entrypoint
async def invoke(payload, context):
    session_id = getattr(context, "session_id", "default-session")
    user_id = getattr(context, "user_id", "default-user")
    agent = get_or_create_agent(session_id, user_id)
    prompt = _extract_prompt(payload)

    async for event in agent.stream_async(prompt):
        if isinstance(event, dict) and "event" in event:
            yield event

_extract_prompt 可以接受普通的 prompt、harness 风格的 messages,或工具结果。这个边界把传输规范化从金融提示词中隔离出来,并让同一个 Agent 包可以同时适用于本地开发、AgentCore 调用和工具续写。

按参与者和会话连接 AgentCore Memory

每个包都通过 AgentCore 提供的环境变量接收已部署的 memory ID。Brain Agent 使用 MEMORY_BRAINAGENTMEMORY_ID;五个专家使用各自对应生成的名称。

MEMORY_ID = os.getenv("MEMORY_BRAINAGENTMEMORY_ID")
REGION = os.getenv("AWS_REGION")


def get_memory_session_manager(session_id, actor_id):
    if not MEMORY_ID:
        return None

    session_id = session_id or uuid.uuid4().hex
    retrieval_config = {
        f"/users/{actor_id}/facts": RetrievalConfig(top_k=3, relevance_score=0.5),
        f"/users/{actor_id}/preferences": RetrievalConfig(top_k=3, relevance_score=0.5),
        f"/episodes/{actor_id}/{session_id}": RetrievalConfig(top_k=5, relevance_score=0.5),
        f"/summaries/{actor_id}": RetrievalConfig(top_k=3, relevance_score=0.5),
    }

    return AgentCoreMemorySessionManager(
        AgentCoreMemoryConfig(
            memory_id=MEMORY_ID,
            session_id=session_id,
            actor_id=actor_id,
            retrieval_config=retrieval_config,
        ),
        REGION,
    )

有两个细节很重要。第一,当没有 memory ID 时,该函数会在本地开发中安全降级为无记忆 Agent。第二,检索路径包含 actor ID,情景检索还额外包含 session ID。命名空间是安全模型的一部分,而不只是组织约定。

通过 AgentCore 数据平面调用专家

Brain Agent 不导入专家 Python 模块。它通过 bedrock-agentcore 客户端调用它们已部署的 Runtime ARN:

def _invoke_sub_agent_runtime(runtime_arn: str, query: str) -> str:
    payload = json.dumps({"prompt": query}).encode("utf-8")
    response = runtime_client.invoke_agent_runtime(
        agentRuntimeArn=runtime_arn,
        payload=payload,
    )
    return _extract_text_from_invoke_response(response["response"].read())

Runtime ARN 从环境变量读取,因此部署配置不会进入应用逻辑。当前实现会按确定顺序调用专家,并独立捕获失败:

calls = [
    ("credit_risk_manage_agent_tool", runtime_arns["credit_risk_manage"]),
    ("fin_model_analyst_agent_tool", runtime_arns["fin_model_analyst"]),
    ("credit_products_agent_tool", runtime_arns["credit_products"]),
    ("credit_trading_agent_tool", runtime_arns["credit_trading"]),
    ("credit_memo_agent_tool", runtime_arns["credit_memo"]),
]

outputs = {}
for tool_name, runtime_arn in calls:
    try:
        outputs[tool_name] = _invoke_sub_agent_runtime(runtime_arn, query)
    except Exception as error:
        outputs[tool_name] = f"Invocation failed: {error}"

AgentCore 会以 server-sent events 形式流式返回运行时响应。解析器读取 data: 记录,解码每个 JSON 事件,并拼接 contentBlockDelta.delta.text 片段。显式解析协议可以防止传输元数据混入最终金融报告。

当前同步 SDK 调用运行在 Brain Agent 的异步入口点内。对于受控演示这是可以接受的,但它会在每次专家调用期间阻塞事件循环。生产改进方向是使用有界的 asyncio.to_thread 并行分发或异步客户端,并配合信号量、单个 Agent 超时、全局截止时间和确定性的输出顺序。

等五个审查都返回后再综合

Brain Agent 会把五个输出转换为带标签的上下文:

sections = [
    "All five required sub-agent outputs:",
    f"1) credit_risk_manage_agent_tool:\n{outputs['credit_risk_manage_agent_tool']}",
    f"2) fin_model_analyst_agent_tool:\n{outputs['fin_model_analyst_agent_tool']}",
    f"3) credit_products_agent_tool:\n{outputs['credit_products_agent_tool']}",
    f"4) credit_trading_agent_tool:\n{outputs['credit_trading_agent_tool']}",
    f"5) credit_memo_agent_tool:\n{outputs['credit_memo_agent_tool']}",
    "Synthesize these five outputs in your final answer.",
]

标签很重要。它保留了每个断言来自哪个专业视角,并让缺失 Agent 的失败对综合器可见。上下文会追加到原始请求后,然后 Brain Agent 将综合后的答案流式返回给调用方。

持久化专家和 Brain Agent 证据

存储辅助函数会把 UTF-8 Markdown 直接写入 S3:

def _put_text_file_to_s3(file_name: str, content: str) -> None:
    s3_client.put_object(
        Bucket=output_bucket,
        Key=f"{output_prefix}/{file_name}",
        Body=content.encode("utf-8"),
        ContentType="text/plain; charset=utf-8",
    )

五个专家文件使用同一个时间戳,这让它们可以被识别为同一批次。Brain Agent 在综合完成后获得一个稍晚的时间戳。每个产物的上传错误都会单独记录,这样一个写入失败不会抹掉其他专家结果。

最终运行时流程因此是明确的:

normalize request
→ resolve actor and session
→ invoke five specialist runtimes
→ persist five specialist outputs
→ label and inject specialist context
→ stream Brain Agent synthesis
→ persist final Brain Agent output

3. 我把记忆和实时研究视为不同的数据类别

AgentCore Memory 适合稳定上下文:交易台偏好、反复出现的实体、过往决策、摘要和情景。它不能替代带有 as-of 时间戳的市场数据。

每个运行时都有 30 天记忆资源,包括:

  • 语义记忆,用于持久事实;
  • 用户偏好记忆,用于报告风格和交易台偏好;
  • 摘要记忆,用于压缩会话历史;
  • 情景记忆,用于过往工作流和结果。

检索按 actor 和 session 命名空间限定范围。这在金融服务中很重要:一个交易台的偏好或先前分析不应泄露到另一个用户的会话中。

Exa 提供当前公开网页上下文。每个重要市场陈述都应带有来源、发布时间、检索时间和置信度。报告必须清楚区分有来源的事实、模型推断和建议行动。

4. 我把第三方凭证移出应用代码

开发日志记录了一个常见的从原型走向生产的过渡:网页搜索先跑通了,但 API key 处理需要变成身份问题。

AgentCore 项目将 EXA_API_KEY 注册为 API-key 凭证提供方。运行时中,Exa 工具会通过 AgentCore Identity 请求该凭证。这个设计避免硬编码 key,并支持在不重新构建 Agent 包的情况下轮换凭证。

最小权限规则很直接:

  • scheduler 只能调用 kickoff Lambda;
  • kickoff Lambda 只能调用 Brain Agent runtime;
  • Brain Agent 只能调用五个具名专家 runtime,并写入自己的 S3 前缀;
  • 每个 Agent 只能取回它需要的 Exa 凭证;
  • delivery Lambda 只能读取已完成报告对象,并调用所需的 SES send 操作。

密钥、账号 ID、runtime ARN、收件人地址和 bucket 名称都应作为参数或托管配置,而不是作为示例复制到公开文章中。

5. 我让 S3 成为持久交接边界

Brain Agent 会为每个专家以及自己的最终综合写入带时间戳的 Markdown 产物。这里的 S3 不只是文件存储;它是概率性研究和确定性交付之间的持久边界。

一次实际运行会产生这些带时间戳的对象:

1784285928_CreditMemoAgent_output.md
1784285928_CreditProductsAgent_output.md
1784285928_CreditRiskManageAgent_output.md
1784285928_CreditTradingAgent_output.md
1784285928_FinModelAnalystAgent_output.md
1784285934_BrainAgent_output.md

S3 notification 必须专门过滤 _BrainAgent_output.md 后缀;否则每个专家产物都可能触发一封邮件。

为实现幂等性,delivery Lambda 应从 S3 object version 派生 key,然后使用一个小型持久记录或对象 tag 来防止重复发送。S3 notification 和 Lambda retry 都是至少一次,所以“发送邮件”绝不能假设 exactly-once delivery。

6. 我把 AI 当作加速器,而不是最终审阅者

开发过程记录显示,系统是通过短小、可测试的变更逐步演进的:

  1. 将专家领域知识直接嵌入每个系统提示词;
  2. 添加 Exa、Tavily 和推理工具;
  3. 将配置的最大输出从 4,000 token 提高到 10,000 token,解决模型输出截断问题;
  4. 用 AgentCore Identity 注册 Exa 凭证;
  5. 从 Brain Agent 委派给全部五个运行时;
  6. 将本地输出文件替换为带时间戳的 S3 产物。

AI 在六个相似 Agent 包之间做机械传播、起草领域检查清单时很有效。它在架构真实性上没那么可靠。我仍然必须检查生成代码中的凭证处理、IAM 边界、输出完整性、错误行为,以及某个金融公式是否被用于正确的会计或风险视角。

这就是现实世界的经验:生成很快;保证仍然是工程工作。


使用的 AWS 服务 / 架构概览

交易风险审查系统架构

Amazon EventBridge Scheduler 早上 6 点触发

AgentCore Identity 凭证提供方

早间交易风险报告邮件

AgentCore Memory 配置

交易风险审查监控

AgentCore Runtime 部署

AgentCore Runtime 端点

架构

概览

Amazon EventBridge Scheduler
标签:“6:00 AM”
→ AWS Lambda
标签:“Kickoff”
→ Amazon Bedrock AgentCore Runtime
标签:“BrainAgent”

BrainAgent 调用五个并行的 Amazon Bedrock AgentCore Runtime Agent:
- CreditMemoAgent
- CreditProductsAgent
- CreditRiskManageAgent
- CreditTradingAgent
- FinModelAnalystAgent

将 BrainAgent 和全部五个 Agent 连接到:
- Amazon Bedrock AgentCore Memory
- Amazon Bedrock AgentCore Identity
- Exa.ai Web Search

将 AgentCore Identity 连接标注为:
“EXA_API_KEY”

展示全部五个专家 Agent 将结果返回给 BrainAgent。

BrainAgent 将六个带时间戳的 Markdown 输出写入 Amazon S3:
- 五份专家报告
- 一份 BrainAgent 早间报告
- 包含 Exa.ai 研究和来源链接

Amazon S3
→ “ObjectCreated: *_BrainAgent_output.md”
→ AWS Lambda
标签:“报告交付”
→ Amazon Simple Email Service(Amazon SES)
→ Macro Trader
→ 电子邮件文档图标
标签:“早间交易风险报告”

清楚展示最终用户输出是一封早间报告邮件,内容包括:
- 市场风险摘要
- 信用和主权分析
- 交易影响
- 对冲思路
- 确认和失效信号
- Exa.ai 来源链接

步骤 1 — EventBridge Scheduler 启动无人值守的早上 6 点运行

Amazon EventBridge Scheduler 是系统时钟。在宏观交易台配置的时区 06:00,它会用一个稳定的早间审查请求调用 kickoff Lambda。代表性 payload 如下:

{
  "reportType": "morning-trading-risk-review",
  "asOf": "scheduled-invocation-time",
  "prompt": "Analyze the overnight macro, sovereign, credit, FX, commodity, liquidity, and policy-risk regime. Produce position implications, hedges, confirmation signals, and invalidation triggers."
}

调度负责时间、重试策略和死信处理。它不包含金融逻辑。使用明确的调度时区,可以防止伦敦、纽约、香港或东京交易台的报告在 UTC 偏移变化时发生漂移。

步骤 2 — kickoff Lambda 只调用 Brain Agent

kickoff Lambda 是 EventBridge Scheduler 和 AgentCore Runtime 之间的薄适配器。它验证定时 payload,创建 correlation 或 run ID,并用 InvokeAgentRuntime 调用 Brain Agent。

它的职责在运行时成功调用后就结束。它不调用 Exa,不运行专家提示词,不写报告,也不发邮件。这让事件集成保持确定性,并把 Agent 编排留在 AgentCore 内部。

从概念上看,调用边界是:

response = agentcore_client.invoke_agent_runtime(
    agentRuntimeArn=brain_agent_runtime_arn,
    payload=json.dumps({"prompt": morning_review_prompt}).encode("utf-8"),
)

Lambda 执行角色需要调用 Brain Agent runtime 的权限,但不需要访问 Exa 凭证或五个专家 runtime。

步骤 3 — BrainAgent 编排五个专家 AgentCore 运行时

Brain Agent 接收一个市场风险问题,并分发到为应用配置的五个 runtime ARN:

BrainAgent
├── CreditRiskManageAgent
├── FinModelAnalystAgent
├── CreditProductsAgent
├── CreditTradingAgent
└── CreditMemoAgent

每个专家收到相同的基础问题,但系统提示词会强制它采用不同的机构视角:

  1. CreditRiskManageAgent 通过 PD、LGD、EAD、预期损失、评级迁移、集中度、限额和契约来量化借款人和组合风险。
  2. FinModelAnalystAgent 通过标准化现金流、杠杆、流动性跑道、偿债能力、蒙特卡洛情景和反向压力测试来检验还款能力。
  3. CreditProductsAgent 评估融资结构、抵押品、担保、受偿顺位、CDS 机制、交易对手敞口、适当性和剩余产品风险。
  4. CreditTradingAgent 在考虑 carry、融资、流动性、交易成本、现券/CDS 基差、对冲规模和执行风险之后,将市场观点转化为候选表达方式。
  5. CreditMemoAgent 将证据、冲突事实、风险、缓释因素、例外、条件和来源链路组织成可供审阅的记录。

当前演示会按确定顺序调用这些运行时。Brain Agent 解析每个流式 AgentCore 响应,独立记录每个结果,按专家标注输出,并把全部五份报告注入自己的综合提示词。如果某次调用失败,失败会作为明确结果保留下来,而不是被静默省略。

这会产生两层输出:

  • 五份深入的专家报告,保留各自的风险视角;
  • 一份 Brain Agent 早间简报,对比专家观点,并把分歧转化为组合影响、潜在对冲、确认信号和失效触发条件。

步骤 4 — 每个 Agent 结合提示词、记忆、身份和当前 Exa 研究

六个 Agent 各有四层上下文:

Specialist system prompt
        +
AgentCore Memory
        +
AgentCore Identity credential
        +
Current Exa.ai web evidence
        =
Evidence-backed specialist output

专家提示词: 每个 main.py 都包含领域特定的 DEFAULT_SYSTEM_PROMPT。提示词会编码专业职责、公式、必需报告章节、决策边界和人类审批限制。

Memory: 每个运行时都有自己的 AgentCore Memory 资源。按 actor 和 session 限定范围的命名空间会检索语义事实、报告偏好、会话摘要和相关情景,避免混淆不同用户或交易台。Memory 提供连续性,但不替代当前市场证据。

Identity: EXA_API_KEY 被注册为 AgentCore API-key 凭证提供方。API key 保持在源代码和提示词之外。运行时权限应允许每个 Agent 只取回这一项必需凭证,从而支持集中轮换并减少密钥暴露。

网页搜索: Strands 工具集包含 Exa,用于当前公开网页研究。Agent 使用它调查隔夜市场波动、政策公告、发行人动态、主权收益率、信用利差、大宗商品冲击,以及专家提示词所需的其他信息。

输出必须保留以下几类内容之间的差异:

  • 带 URL 和发布或检索时间的来源事实;
  • 基于多个事实的模型推断;
  • 风险判断;
  • 需要人类批准的拟议行动。

这种区分在金融服务中尤其重要,因为一个没有 as-of 日期的貌似合理陈述,可能在报告到达交易台之前就已经过时。

步骤 5 — Agent 输出和 Exa 派生研究被持久化到 S3

Brain Agent 会在生成最终综合之前持久化每个专家响应。这样即使后续综合或交付步骤失败,证据也会被保留下来。

演示中的一次运行会产生:

1784285928_CreditMemoAgent_output.md
1784285928_CreditProductsAgent_output.md
1784285928_CreditRiskManageAgent_output.md
1784285928_CreditTradingAgent_output.md
1784285928_FinModelAnalystAgent_output.md
1784285934_BrainAgent_output.md

五个专家文件共享委派时间戳。Brain Agent 文件时间戳更晚,因为它是在全部专家结果组装并综合之后写入的。

Markdown 报告包含 Agent 基于 Exa 的发现和来源引用。为了更强的生产级链路追踪,原始 Exa 查询和响应也可以作为 JSON 与报告并列持久化,包含检索时间戳、URL、内容哈希以及请求证据的专家。

S3 是工作流的持久交接点:

agent research completed
→ specialist artifacts stored
→ Brain Agent synthesis stored
→ final-report object event emitted
→ delivery begins

这个边界意味着邮件失败不需要重新运行昂贵的市场研究。报告可以从不可变的 S3 对象中安全地重新交付。

步骤 6 — 最终 S3 对象触发 Lambda 和 SES 交付

S3 event notification 必须过滤最终 Brain Agent 文件名后缀:

_BrainAgent_output.md

如果没有这个过滤器,全部五个专家上传也会调用 delivery Lambda,交易员可能会收到六封不完整邮件。

report-delivery Lambda 会验证 bucket、object key、object version 和 event type;读取 UTF-8 Markdown 报告;创建一个包含报告日期和 correlation ID、适合交易台阅读的邮件主题;然后调用 Amazon SES。

S3 ObjectCreated
→ validate final-report key
→ check object-version idempotency
→ read Brain Agent report
→ format email
→ send through SES
→ record delivery result

S3 event notification 和 Lambda retry 提供的是至少一次交付,而不是 exactly-once delivery。因此,Lambda 在调用 SES 前必须使用 S3 object version 或另一个持久幂等 key。重试应该重新发送失败的交付,但针对已交付对象的重复事件不能发送第二份早间报告。

宏观交易员最终会收到一份报告,包含:

  • 隔夜风险环境摘要;
  • 主权利率、信用、外汇、大宗商品和流动性观察;
  • 专家分歧和置信度限制;
  • 组合和产品影响;
  • 候选对冲或仓位调整;
  • 确认信号和失效触发条件;
  • 来源链接和 as-of 时间戳;
  • 明确说明交易和信用行动需要经授权的人类批准。

服务职责

Amazon EventBridge Scheduler

负责 06:00 触发。我使用具名时区,而不是在脑中把本地交易台时间换算成 UTC。这避免了在适用夏令时的地方出现季节性漂移。调度应配置重试策略和死信队列;当报告有严格开盘前截止时间时,应禁用 flexible time window。

AWS Lambda

kickoff 函数创建 run ID,组装有边界的请求,并调用 Brain Agent。它不应包含 Agent 逻辑。delivery 函数处理 S3 事件验证、幂等性、报告取回、MIME 构建和 SES 交付。让这些函数保持确定性,比把邮件行为嵌入 LLM 运行时更容易测试。

Amazon Bedrock AgentCore Runtime

托管 Brain Agent 和五个专家 Agent。运行时边界提供独立打包、IAM 角色、环境配置和运维可见性。Brain Agent 负责编排和综合;专家负责深度分析。

AgentCore Identity

存储并代理 Exa API-key 凭证提供方。Agent 在执行时请求凭证,将第三方 secret 保持在提示词和源代码之外。

AgentCore Memory

为事实、偏好、摘要和情景提供会话感知检索。命名空间按 actor 和 session 限定范围,以保留租户边界。

Amazon S3

存储不可变研究产物,并作为完成事件来源。应启用加密、bucket-owner-enforced object ownership、阻止公有访问、版本控制、生命周期保留,以及在政策要求时启用 Object Lock。避免在对象 key 中放入敏感借款人数据,因为 key 会出现在日志和事件中。

Amazon SES

从已验证身份发送最终报告。在依赖这个通道前,必须配置生产访问、收件人治理、退信/投诉处理和 suppression-list 行为。敏感报告可能更适合通过短期有效的认证链接交付,而不是作为完整邮件附件发送。

投产前我会要求的运营控制

  • 运行级截止时间,防止迟到报告伪装成当前报告。
  • 针对调度缺失、Lambda 错误、AgentCore 调用失败、不完整 manifest 和 SES 拒收的 CloudWatch 告警。
  • 从 Scheduler 到 Lambda、全部六个运行时、S3 metadata 和邮件主题传递 correlation ID。
  • Reserved concurrency 或其他重叠保护,防止延迟运行与下一次调度冲突。
  • 报告中明确来源时间戳和 stale-data 警告。
  • S3 事件过滤加幂等邮件交付。
  • 提示词和模型版本控制,保证可复现性。
  • 在公开网页查询前进行脱敏和分类控制。
  • 对交易、限额、信用决策和外部分发进行人类审批。

已实现核心与交付集成的区别

当前仓库包含六个 AgentCore 运行时、专家提示词、memory 定义、Exa 工具配置、运行时到运行时的编排,以及 S3 输出逻辑。EventBridge Scheduler、kickoff Lambda、S3 触发的 delivery Lambda 和 SES 资源是本架构中描述的外围事件驱动集成,应在称其为完整工作流部署之前,以基础设施即代码方式添加。

这个区分是有意的。一篇可信的工程文章应该区分正在运行的代码和目标生产设计。


你学到了什么

多 Agent 的价值来自分歧,而不是数量

五个 Agent 重复同一份市场摘要只会放大成本和信心。真正有用的设计给每个 Agent 一个不同职责,并要求 Brain Agent 暴露分歧。当基本面分析师说“还款能力正在改善”,而交易 Agent 说“利差已经为完美情景定价”时,交易员能学到更多。

金融提示词需要决策边界

只有领域词汇并不等于专业能力。强提示词会定义决策、必需证据、公式、失败模式和权限边界。PD、IFRS 9 ECL、公允价值、监管资本和可交易 alpha 都可能描述同一个敞口,但回答的是不同问题。

早间简报是一个有截止时间的系统

到了上午 10 点,一份完美的早上 6 点报告可能已经毫无价值。延迟预算、超时行为、过期数据标签、部分结果策略和交付告警都是产品需求,而不是运营润色。

事件驱动不等于 exactly once

Scheduler retry、Lambda retry、AgentCore failure 和重复 S3 notification 都是正常的分布式系统行为。Run ID、manifest、object version 和幂等交付可以把这些现实转化为受控结果。

Memory 不是真相

Memory 改善连续性,但当前市场陈述仍然需要新鲜来源和时间戳。长期偏好和短期价格需要不同的保留、检索和验证规则。

Identity 是工具设计的一部分

连接一个网页搜索工具很容易。让它在不泄露凭证、不过度授予 IAM 权限、也不把轮换变成部署事件的情况下连接,才是真正的工程任务。AgentCore Identity 让凭证访问变得明确且可审计。

AI 开发仍然需要对抗式审查

AI 缩短了实现时间,尤其是在重复的 Agent 结构之间传播变更时。它也很容易生成看起来完整、但可靠性和安全问题尚未回答的代码。资深工程判断要反复追问:重试时会发生什么?事实来源是什么?哪个声明尚未验证?谁被授权采取行动?