7628 字
38 分钟

Agent 工程基础底座:从 Python 服务到可演进的 Runtime 骨架

本文是 精英 Agent 工程师学习路线:从范式理解到生产级落地 阶段 0 的配套学习笔记。 核心目标:先搭出一个不依赖特定模型与 Agent 框架、能够测试和替换组件的工程底座,再进入 Prompt、Harness、RAG 与多 Agent。本文不会把“装好一堆中间件”误写成“具备生产能力”。


0. 学完后要交付什么#

阶段 0 不是 Python、Docker、Redis 的百科全书。它只要求把后续文章反复使用的工程边界一次建立正确。

完成后,你应该能交付一个最小但完整的 OrderFlow-Agent 骨架,并回答下面的问题:

  1. HTTP 请求、Agent Run、模型调用和工具调用分别有什么 ID?
  2. Pydantic Schema 在 API 边界、模型边界和工具边界各校验什么?
  3. 哪些状态写 PostgreSQL,哪些状态放 Redis,哪些内容进入向量库?
  4. 模型超时、限流、格式错误、工具失败时,系统如何重试、降级或转人工?
  5. 为什么“模型返回成功”不等于“退款已经成功”?
  6. 如何在不启动真实模型的情况下测试主要业务路径?
  7. 如何从一条失败请求追到 Prompt 版本、模型、工具参数、状态变更和最终响应?
  8. 如何替换模型、向量库或编排框架,而不重写业务核心?

最终交付物不是一个聊天 Demo,而是下面这组可验证资产:

agent-system/
app/
tests/
migrations/
docs/
docker-compose.yml
pyproject.toml
.env.example
README.md

验收命令至少包括:

Terminal window
pytest
ruff check .
mypy app
docker compose config
docker compose up -d
curl http://localhost:8000/health/live
curl http://localhost:8000/health/ready

1. 先建立正确心智模型:Agent 是有副作用的分布式系统#

一次普通模型调用大致是:

input -> model -> output

一个真实 Agent Run 更接近:

request
-> authentication
-> input normalization
-> context assembly
-> model/router
-> tool selection
-> remote service call
-> state transition
-> verification
-> response
-> trace/evaluation/audit

它同时具有四类复杂性:

复杂性典型问题工程响应
概率性相同输入可能产生不同计划Schema、评估集、版本冻结
分布式模型、工具、数据库跨网络超时、重试、熔断、追踪
有状态多轮会话与长任务需要恢复Checkpoint、状态机、幂等
有副作用退款、发信、改数据不可随意重放授权、确认、事务、审计、验证

Anthropic 在 Building effective agents 中把可预定义路径的 workflow 与由模型动态决定过程的 agent 区分开;OpenAI 的 Agents SDK、Google ADK、Microsoft AutoGen/Agent Framework 与 AWS Bedrock Agents 虽然抽象不同,但都在解决“模型、工具、状态、交接和追踪如何被 Runtime 管理”的问题。[S1] [S2] [S3] [S4] [S5]

这给阶段 0 一个非常重要的结论:

先建设普通后端工程能力,再叠加 Agent 能力。模型不应该绕过身份、权限、事务、审计和业务不变量。

1.1 三个平面#

建议从一开始把系统分成三个平面:

Control Plane
配置、版本、策略、权限、模型路由、发布
Execution Plane
Run、节点、模型调用、工具执行、状态迁移
Evidence Plane
Trace、Audit、Eval、Failure Sample、成本与延迟

很多 Demo 只有 Execution Plane,所以“能跑”;生产系统还需要 Control Plane 决定“允许怎样跑”,Evidence Plane 证明“刚才究竟怎样跑”。

1.2 四种真相源#

不要把所有状态都塞进一段对话历史。至少区分:

真相源例子权威性
业务数据库订单状态、实付金额、退款流水业务事实真相
Agent State当前意图、已完成步骤、下一节点本次 Run 的流程真相
Trace模型、工具、耗时、错误执行过程证据
Memory / RAG用户偏好、历史案例、规则文档可检索上下文,不自动等于事实

模型生成的文字不是第五种真相源。它只能提出结构化判断或动作建议。


2. 能力边界:学到“可组合”,不要学成技术清单#

路线中列出 FastAPI、Pydantic、Redis、PostgreSQL、向量库和 Docker,不代表项目必须同时深度依赖全部组件。阶段 0 的达标尺度如下。

能力必须掌握暂时不必深挖
Python类型、异步、异常、包结构、测试元类与解释器内部
FastAPI生命周期、依赖注入、流式响应、错误映射自研 Web 框架
Pydantic输入/输出 Schema、判别联合、严格校验复杂代码生成
PostgreSQL事务、约束、索引、迁移、JSONB 边界提前分库分表
RedisTTL、幂等键、限流、短期状态把 Redis 当永久真相源
向量检索chunk、metadata filter、召回与评估一开始追求超大规模
Docker Compose本地一致环境、健康检查、持久卷把 Compose 当生产编排器
LLM API结构化输出、工具调用、流式事件、错误分类绑定某一家私有响应对象
Observabilitytrace/span、结构化日志、关键指标只做漂亮 Dashboard

2.1 成熟度阶梯#

L0 脚本:一次模型调用,结果打印到终端
L1 服务:HTTP API + Schema + 错误处理
L2 Runtime:状态、工具注册、超时、幂等、追踪
L3 可恢复:Checkpoint、队列、重放、人工接管
L4 可治理:版本、评估门禁、灰度、审计、成本预算

阶段 0 的目标是稳定达到 L2,并为 L3/L4 留出接口。


3. 推荐工程目录:按责任边界,而不是按框架教程#

agent-system/
app/
api/
routes/
dependencies.py
errors.py
agents/
state.py
runtime.py
workflow.py
core/
config.py
logging.py
ids.py
clock.py
domain/
orders.py
refunds.py
policies.py
llm/
protocol.py
gateway.py
models.py
errors.py
tools/
schemas.py
registry.py
executor.py
order_tools.py
refund_tools.py
rag/
documents.py
retriever.py
citations.py
memory/
repository.py
policy.py
persistence/
database.py
models.py
repositories.py
observability/
tracing.py
metrics.py
audit.py
security/
identity.py
authorization.py
secrets.py
main.py
tests/
unit/
contract/
integration/
scenario/
migrations/
docs/
architecture.md
runbook.md
docker-compose.yml
pyproject.toml
.env.example

目录的核心规则是依赖方向:

API / Runtime / Adapter
Application Service
Domain Contract
Domain 不反向依赖 FastAPI、OpenAI SDK、Redis Client 或向量库 SDK。

这样做不是为了追求形式上的“整洁架构”,而是为了获得三个直接收益:

  1. 单元测试可用 Fake Model、Fake Tool、Fake Repository;
  2. 模型供应商和框架可替换;
  3. 业务规则不被 Prompt 或 SDK 响应类型吞没。

4. 配置与依赖:可复现比“在我机器上能跑”更重要#

4.1 pyproject.toml 的最小分组#

[project]
name = "orderflow-agent"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = [
"fastapi>=0.115,<1",
"pydantic>=2.10,<3",
"pydantic-settings>=2.7,<3",
"uvicorn[standard]>=0.34,<1",
"httpx>=0.28,<1",
"sqlalchemy[asyncio]>=2.0,<3",
"asyncpg>=0.30,<1",
"redis>=5,<7",
"opentelemetry-api>=1.30,<2",
]
[dependency-groups]
dev = [
"pytest>=8,<9",
"pytest-asyncio>=0.25,<1",
"mypy>=1.14,<2",
"ruff>=0.9,<1",
]

示例版本范围表达的是策略,不是本文替你锁定的最新版本。真实项目必须提交 lockfile,并通过依赖更新 PR 验证回归集。

4.2 配置必须有来源与敏感级别#

from pydantic import SecretStr
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
model_config = SettingsConfigDict(
env_file=".env",
env_nested_delimiter="__",
extra="ignore",
)
environment: str = "local"
database_url: SecretStr
redis_url: SecretStr
model_provider: str = "openai"
model_name: str
model_api_key: SecretStr
request_timeout_seconds: float = 30.0
max_agent_steps: int = 12
max_run_cost_usd: float = 0.25

配置治理至少包含:

  • .env.example 只放键名和非敏感示例;
  • 生产 Secret 进入云密钥服务,不进入镜像、日志或 Trace;
  • 启动时检查必填项与组合约束,失败就拒绝 Ready;
  • Prompt、模型、工具 Schema 和策略使用独立版本号;
  • 配置变更能够关联部署版本和评估报告。

5. 用 Pydantic 建立三层契约#

Pydantic 不只是把 JSON 转成 Python 对象。Agent 系统至少需要三类不同契约。

5.1 API 契约#

from typing import Annotated, Literal
from pydantic import BaseModel, Field, StringConstraints
OrderId = Annotated[
str,
StringConstraints(pattern=r"^ORD-[A-Z0-9]{8}$"),
]
class AgentRequest(BaseModel):
request_id: str
user_id: str
message: Annotated[str, Field(min_length=1, max_length=8000)]
order_id: OrderId | None = None
response_mode: Literal["sync", "stream"] = "stream"

API Schema 保护服务免受非法输入影响,但不能替代认证与资源归属检查。

5.2 模型输出契约#

class RefundIntent(BaseModel):
intent: Literal["refund_request"]
order_id: OrderId | None
reason: str | None
confidence: Annotated[float, Field(ge=0, le=1)]
needs_clarification: bool
class OtherIntent(BaseModel):
intent: Literal["logistics_query", "complaint", "unknown"]
confidence: Annotated[float, Field(ge=0, le=1)]
IntentResult = Annotated[
RefundIntent | OtherIntent,
Field(discriminator="intent"),
]

OpenAI Structured Outputs、Google Gemini structured output、Anthropic tool use 等方案都在把自由文本转为可校验数据。[S6] [S7] [S8] 但 Schema 只能约束“形状”,不能证明字段事实正确:一个格式合法的订单号仍可能属于其他用户。

5.3 工具契约#

class CreateRefundArgs(BaseModel):
order_id: OrderId
amount_cents: Annotated[int, Field(gt=0)]
reason_code: Literal[
"merchant_not_shipped",
"logistics_lost",
"quality_issue",
]
idempotency_key: str
class RefundCreated(BaseModel):
status: Literal["created"]
refund_id: str
order_id: OrderId
class RefundRejected(BaseModel):
status: Literal["rejected"]
code: Literal[
"permission_denied",
"state_conflict",
"amount_exceeded",
"duplicate_request",
]
retryable: bool = False

工具契约还必须在 Schema 之外声明:

元数据示例
副作用write
权限refund:create
风险级别high
幂等要求required
确认策略金额超过阈值需人工
超时3 秒
可重试仅连接失败且服务未受理
数据分类包含订单与支付数据

6. 模型网关:把厂商 SDK 隔离在适配器中#

模型提供商正在快速增加 Responses、tool use、context caching、reasoning、computer use 等能力。直接让业务代码遍布某个 SDK 类型,会让模型升级变成全项目迁移。

6.1 内部协议#

from collections.abc import AsyncIterator
from dataclasses import dataclass
from typing import Any, Protocol
@dataclass(frozen=True)
class ModelRequest:
run_id: str
messages: list[dict[str, Any]]
tools: list[dict[str, Any]]
output_schema: dict[str, Any] | None
timeout_seconds: float
@dataclass(frozen=True)
class ModelEvent:
kind: str
payload: dict[str, Any]
class ModelGateway(Protocol):
async def stream(
self,
request: ModelRequest,
) -> AsyncIterator[ModelEvent]: ...

适配器负责把 OpenAI、Anthropic、Gemini、Azure 或 Bedrock 的事件归一化为内部事件:

response.started
text.delta
tool_call.started
tool_call.arguments.delta
tool_call.completed
usage.completed
response.failed

6.2 错误不能只剩一个 Exception#

class ModelError(Exception):
retryable: bool = False
class ModelRateLimited(ModelError):
retryable = True
class ModelTimeout(ModelError):
retryable = True
class ModelInvalidOutput(ModelError):
retryable = False
class ModelSafetyBlocked(ModelError):
retryable = False
class ModelContextExceeded(ModelError):
retryable = False

重试策略必须基于错误语义,而不是“失败就再请求一次”。无条件重试会放大限流、成本和重复副作用。

6.3 路由依据#

模型路由可以参考各大云平台的 Model Gateway / AI Gateway 思路,但路由条件应该由业务 SLO 驱动,而不是模型排行榜驱动:[S9] [S10] [S11]

任务类型
+ 数据敏感级别
+ 结构化输出能力
+ 工具调用能力
+ 延迟预算
+ 单 Run 成本预算
+ 区域与合规要求
+ 当前错误率/限流状态
= provider + model + fallback chain

一个明确的反模式是:强模型失败后自动换弱模型继续执行高风险写操作。降级必须同时降低权限,例如退化为只读查询或转人工。


7. HTTP 与流式事件:传输进度,不传输未经确认的事实#

7.1 Run API#

from fastapi import APIRouter, Depends, status
router = APIRouter(prefix="/v1/agent-runs")
@router.post("", status_code=status.HTTP_202_ACCEPTED)
async def create_run(
body: AgentRequest,
service: "AgentRunService" = Depends(),
) -> dict[str, str]:
run = await service.create(body)
return {
"run_id": run.id,
"status": run.status,
"events_url": f"/v1/agent-runs/{run.id}/events",
}

对于可能超过普通 HTTP 超时的任务,推荐创建 Run 后通过 SSE/WebSocket/轮询读取事件,而不是让一个连接承担所有恢复语义。

7.2 事件信封#

{
"event_id": "evt_01J...",
"run_id": "run_01J...",
"sequence": 17,
"type": "tool.completed",
"occurred_at": "2026-03-21T08:12:03.421Z",
"data": {
"tool_call_id": "tc_01J...",
"tool_name": "query_order",
"result_ref": "result://tc_01J..."
}
}

关键约束:

  • sequence 让客户端检测丢帧与重连;
  • event_id 支持去重;
  • 大型或敏感结果使用引用,不直接塞进事件;
  • “正在检查退款资格”是进度,“退款已完成”必须等写工具与状态验证成功;
  • 客户端断线不能取消服务端 Run,除非显式调用取消接口。

8. 工具执行器:模型提议,系统裁决,适配器执行#

Meta 的 Toolformer、Gorilla、ToolLLM 等工作说明模型可以学习何时以及如何使用外部 API;ReAct 则把推理与行动后的观察组织成循环。[P1] [P2] [P3] [P4] 工程系统需要在这些思想上增加权限、事务和验证。

8.1 安全调用管线#

Model Tool Proposal
-> Parse
-> Schema Validate
-> Resolve Identity
-> Authorize
-> Policy Check
-> Risk / Confirmation Check
-> Idempotency Reservation
-> Execute with Timeout
-> Normalize Result
-> Verify Business State
-> Audit
-> Return Observation

8.2 注册信息#

from dataclasses import dataclass
from enum import StrEnum
from typing import type
from pydantic import BaseModel
class SideEffect(StrEnum):
NONE = "none"
READ = "read"
WRITE = "write"
EXTERNAL = "external"
@dataclass(frozen=True)
class ToolSpec:
name: str
version: str
input_model: type[BaseModel]
output_model: type[BaseModel]
side_effect: SideEffect
permission: str
timeout_seconds: float
confirmation_required: bool

不要把工具描述当权限系统。即使描述写着“仅管理员可用”,执行器仍必须根据已认证身份做授权。

8.3 幂等不是简单的缓存#

写操作建议使用:

idempotency_key = hash(
tenant_id,
user_id,
operation,
business_resource_id,
normalized_arguments,
confirmation_version
)

服务端记录:

reserved -> executing -> succeeded | failed_unknown | rejected

failed_unknown 不能盲目重试,因为远端可能已经成功但响应丢失。此时应先调用查询工具对账。


9. 数据层:按一致性与访问模式选存储#

9.1 PostgreSQL:业务与审计真相#

适合保存:

  • Agent Run 元数据与状态迁移;
  • 工具调用记录与幂等记录;
  • 业务事务引用;
  • Prompt/Tool/Policy 版本引用;
  • Evaluation Case 与结果;
  • 可长期保留的审计事件。

最小表关系:

agent_runs 1---n run_steps
agent_runs 1---n tool_calls
agent_runs 1---n model_calls
agent_runs 1---n state_transitions
agent_runs 1---n eval_results
tool_calls 1---0..1 idempotency_records

状态迁移应使用乐观锁或条件更新:

UPDATE agent_runs
SET status = :next_status,
version = version + 1,
updated_at = now()
WHERE id = :run_id
AND status = :expected_status
AND version = :expected_version;

受影响行数为 0,说明有并发更新或非法迁移,不能静默覆盖。

9.2 Redis:有 TTL 的协调数据#

适合保存:

  • 限流计数;
  • 短生命周期缓存;
  • 分布式锁或租约;
  • SSE 最近事件窗口;
  • 任务队列;
  • 幂等预留的快速索引。

不适合把唯一一份退款状态或审计记录只放 Redis。缓存失效、淘汰与主从切换必须是设计前提。

9.3 向量库:候选证据索引#

向量检索保存的是“可能相关的候选”,不是业务判断结果。最小 metadata 至少包含:

{
"document_id": "policy-refund-v7",
"chunk_id": "R001-C03",
"tenant_id": "tenant-a",
"acl": ["support", "risk"],
"effective_from": "2026-01-01",
"effective_to": null,
"source_uri": "policy://refund/v7#R001",
"content_hash": "sha256:..."
}

检索必须先做租户和权限过滤,再做语义相关性排序。论文 Retrieval-Augmented Generation 奠定了“参数记忆 + 外部非参数记忆”的基本范式;后续工程实践进一步要求版本、ACL、引用和评估。[P5]


10. 可靠性:每一步都要有预算和终止条件#

10.1 Run Budget#

from pydantic import BaseModel, Field
class RunBudget(BaseModel):
max_steps: int = Field(default=12, ge=1, le=50)
max_model_calls: int = Field(default=8, ge=1)
max_tool_calls: int = Field(default=12, ge=0)
max_elapsed_seconds: float = Field(default=90, gt=0)
max_cost_usd: float = Field(default=0.25, gt=0)

每次循环前检查预算。预算耗尽的正常输出应该是结构化终止原因,而不是进程崩溃:

{
"status": "needs_human",
"reason": "run_budget_exhausted",
"completed_steps": ["intent", "order_lookup"],
"pending_steps": ["policy_check", "refund_decision"]
}

10.2 超时分层#

HTTP request timeout
>= Agent Run sync budget
>= node timeout
>= model/tool attempt timeout

不要让底层 SDK 的默认超时决定整个系统命运。

10.3 重试矩阵#

失败自动重试条件
模型 429/暂时 5xx指数退避 + jitter + 总预算
模型输出 Schema 错误有限最多一次修复,记录原输出
只读工具网络失败工具声明可重试
写工具连接中断先查询远端最终状态
权限拒绝不能通过重试绕过权限
业务状态冲突刷新状态并重规划或人工
上下文超限压缩/重建上下文,不原样重试

10.4 Backpressure#

长 Run 应进入有界队列。队列满时返回 429/503 + Retry-After,而不是无限接收后让数据库、模型配额和工具服务一起雪崩。Little’s Law 提醒我们:在稳定系统中,并发量约等于吞吐乘以停留时间;Agent 延迟变长会直接放大在途任务数。


11. 可观测性:Trace 是 Agent 的可执行病历#

OpenTelemetry 正在定义 GenAI semantic conventions,用统一属性描述模型请求、响应、token、工具与 Agent 操作。该规范已迁入独立仓库且仍处 Development,内部事件不能绑定未固定版本的实验属性。[S12] 阶段 0 不要求追齐所有字段,但必须先建立父子关系。

HTTP span
└── agent.run
├── context.build
├── model.call
├── tool.query_order
├── rag.retrieve_policy
├── model.decide
├── tool.create_refund
└── state.verify

11.1 每个 Span 至少记录#

trace_id / run_id / tenant_id
operation_name / node_name / attempt
model_provider / model_name / model_config_version
prompt_version / tool_name / tool_version
input_size / output_size / token_usage
latency / error_type / retryable
state_before_hash / state_after_hash

11.2 三条红线#

  1. 不默认记录完整 Prompt、用户隐私、API Key 与工具敏感返回;
  2. Trace 用于诊断,不替代不可篡改的 Audit Log;
  3. 指标聚合不能丢掉失败样本引用,否则只能看到错误率,不能复现错误。

11.3 最小指标集#

agent_runs_total{status}
agent_run_duration_seconds
model_calls_total{provider,model,status}
model_tokens_total{direction}
tool_calls_total{tool,status}
tool_call_duration_seconds{tool}
agent_retries_total{reason}
agent_budget_exhausted_total{budget_type}
human_handoffs_total{reason}

12. 安全基线:模型之外必须仍然安全#

阶段 0 的安全目标可以用一句话表达:

假设模型会被误导、会输出错误参数、会重复调用,系统仍不应越权、泄露或造成不可恢复副作用。

12.1 身份传播#

End User Identity
-> API Authentication
-> Agent Principal
-> Delegated Tool Scope
-> Resource-level Authorization

Agent 使用服务账号并不意味着它可以代表任意用户访问所有订单。每次工具调用都应同时带主体、租户、授权范围和业务资源。

12.2 最小权限#

query_order: order:read:self
search_policy: policy:read:published
create_refund: refund:create:eligible_order
create_compensation: compensation:create:approved_limit

只读工具与写工具使用不同凭据;高风险写工具可以部署在独立服务或网络边界中。

12.3 输入不是指令#

用户、网页、邮件、RAG 文档和工具返回都视为不可信数据。OWASP LLM Top 10 与 NIST AI RMF 的共同启发是:威胁建模、权限控制、数据治理、人工监督和持续监测必须覆盖完整生命周期,而不是只靠 System Prompt。[S13] [S14]


13. Docker Compose:构造开发环境,不伪装生产平台#

services:
api:
build: .
command: uvicorn app.main:app --host 0.0.0.0 --port 8000
env_file: .env
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
ports:
- "8000:8000"
postgres:
image: postgres:17
environment:
POSTGRES_DB: orderflow
POSTGRES_USER: orderflow
POSTGRES_PASSWORD: local-only
healthcheck:
test: ["CMD-SHELL", "pg_isready -U orderflow -d orderflow"]
interval: 5s
timeout: 3s
retries: 10
volumes:
- pgdata:/var/lib/postgresql/data
redis:
image: redis:7-alpine
command: redis-server --appendonly yes
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 10
volumes:
pgdata:

注意:

  • depends_on 的健康条件只解决启动顺序的一部分,应用仍要处理运行期断连;
  • 数据库迁移使用独立 Job,不让每个 API 实例同时迁移;
  • 镜像使用非 root 用户、多阶段构建和明确版本;
  • Compose 中的明文密码只用于本地,不能复制到生产;
  • Liveness 只判断进程是否活着,Readiness 才检查是否具备接收流量的条件。

Docker 官方文档强调 Compose 用声明式 YAML 管理多容器应用;Kubernetes 等生产编排平台还会补充滚动发布、调度、弹性、网络策略和 Secret 等能力。[S15]


14. 测试金字塔:先让模型退出测试,再测试模型#

14.1 纯函数单元测试#

优先覆盖:

  • Schema 校验;
  • 状态迁移;
  • 权限与金额规则;
  • 幂等键生成;
  • 错误分类;
  • Context 选择与脱敏;
  • 路由和预算判断。
def test_refund_cannot_exceed_paid_amount() -> None:
decision = decide_refund(
paid_amount_cents=10_000,
requested_amount_cents=12_000,
policy_limit_cents=10_000,
)
assert decision.allowed is False
assert decision.reason == "amount_exceeded"

14.2 契约测试#

固定厂商响应 fixture,验证适配器归一化:

provider event
-> internal ModelEvent
-> tool arguments
-> validated domain command

SDK 升级时,契约测试应优先暴露流式事件名、usage 字段或工具参数格式变化。

14.3 集成测试#

用真实 PostgreSQL/Redis 容器测试:

  • 事务回滚;
  • 并发状态更新;
  • TTL 与幂等;
  • 断线重连;
  • 迁移向前/向后兼容;
  • Outbox 或事件发布。

14.4 Scenario Test#

{
"case_id": "foundation-refund-001",
"input": {
"message": "订单三天没发货,我要退款",
"order_id": "ORD-A1B2C3D4"
},
"fixtures": {
"order_status": "paid",
"paid_amount_cents": 12900,
"logistics_status": "not_shipped"
},
"expected": {
"tool_sequence": [
"query_order",
"query_logistics",
"search_policy"
],
"forbidden_tools": ["create_compensation"],
"terminal_status": "awaiting_confirmation"
}
}

14.5 在线模型评估#

最后才接真实模型,比较:

Schema Validity
Tool Selection Accuracy
Argument Accuracy
Task Success
Unsafe Action Rate
Latency p50/p95/p99
Tokens / Cost per Successful Run

AgentBench、τ-bench、SWE-bench 等基准的核心启发不是直接拿排行榜当业务结论,而是把 Agent 放进可交互环境,按任务完成、轨迹和状态变化评估。[P6] [P7] [P8]


15. OrderFlow-Agent 的最小闭环#

15.1 主路径#

POST /v1/agent-runs
-> authenticate user
-> create run
-> classify intent
-> query order
-> verify ownership
-> query logistics
-> retrieve effective policy
-> produce structured decision
-> policy engine validates decision
-> request confirmation when required
-> execute refund with idempotency key
-> query refund state
-> persist evidence and audit
-> emit final response

15.2 状态对象#

from typing import Literal
from pydantic import BaseModel, Field
class AgentState(BaseModel):
schema_version: Literal["1"] = "1"
run_id: str
user_id: str
tenant_id: str
status: Literal[
"created",
"running",
"awaiting_input",
"awaiting_confirmation",
"succeeded",
"failed",
"needs_human",
]
current_node: str
completed_nodes: list[str] = Field(default_factory=list)
order_id: OrderId | None = None
evidence_refs: list[str] = Field(default_factory=list)
pending_action: dict | None = None
version: int = 0

状态只保存恢复流程所需的信息。大型工具结果、文档全文和隐私数据应存到有访问控制的对象或记录中,以引用连接。

15.3 终止状态#

succeeded
failed_retryable
failed_terminal
awaiting_input
awaiting_confirmation
needs_human
cancelled
budget_exhausted

“模型没有继续调用工具”不是可靠终止条件。


16. 大厂方案怎么借鉴,而不是怎么照抄#

方案可借鉴思想不应直接推导
OpenAI Agents SDK / Responses APIRun、tool、handoff、guardrail、trace 的 Runtime 抽象使用 SDK 就自动生产可用
Anthropic Building Effective Agents从简单组合开始,workflow 与 agent 分治所有复杂任务都需要自治 Agent
Google ADK / Vertex AI Agent EngineAgent 开发包与托管 Runtime 分层,session/eval/deploy 配套托管后不再需要业务级审计
Microsoft AutoGen / Agent Framework消息驱动、多 Agent、工作流与状态管理多 Agent 一定优于单 Agent
AWS Bedrock AgentsAction Group、Knowledge Base、Guardrail 与云服务集成云 IAM 能替代业务资源授权
LangGraph显式状态图、checkpoint、interrupt、durable execution图框架能修复错误业务建模
OpenTelemetry GenAI跨组件 Trace 语义与供应商中立观测采集越多原始 Prompt 越好

判断一个厂商方案是否适合项目,至少问:

它解决的是 SDK、Runtime、Control Plane 还是 Observability?
状态和数据存在哪里,退出厂商平台能否迁移?
权限在模型、工具、云 IAM、还是业务服务哪一层执行?
能否固定 Prompt、模型、工具和策略版本复现一次 Run?
能否导出 trace、usage、evaluation 与 audit evidence?
失败时能否人工接管、恢复和回放?

17. 论文思想如何进入工程,而不是停在名词#

论文/基准技术思想阶段 0 的工程落点
ReAct [P1]推理、行动、观察交替显式 action/observation 事件和步数预算
Toolformer [P2]模型学习何时调用 API工具候选与调用评估,不取消执行层校验
Gorilla [P3]面向大量 API 的可靠调用工具文档版本、检索与参数契约
ToolLLM [P4]大规模真实 API 学习与规划工具轨迹数据模型与分层评估
RAG [P5]参数模型结合外部检索独立检索接口、来源引用和版本化证据
AgentBench [P6]多环境交互式 Agent 评测Scenario 环境、成功条件与过程指标
τ-bench [P7]工具-用户交互与策略遵循状态数据库、策略约束、工具轨迹评估
SWE-bench [P8]在真实环境验证任务解决最终环境状态比语言自评更可信
Lost in the Middle [P9]长上下文中的位置信息损失不把所有历史粗暴拼接,建设 Context Builder

论文给的是问题定义、方法和特定实验条件下的结果。落地时必须重新测量自己的数据、模型、工具和失败成本。


18. 常见失败与诊断顺序#

18.1 “本地能跑,容器起不来”#

检查:

工作目录与模块路径
依赖是否锁定
环境变量是否完整
容器内 DNS 是否使用 service name
数据库迁移是否执行
健康检查是否把启动慢误判为死亡

18.2 “模型明明选对工具,业务仍失败”#

检查:

参数是否通过业务校验
用户是否拥有订单
订单状态是否在调用前已变化
幂等键是否冲突
远端是否成功但响应丢失
是否执行了 final-state verification

18.3 “加 Redis 后反而出现幽灵状态”#

检查:

Redis 是否被误当真相源
TTL 是否短于 Run 生命周期
缓存键是否缺 tenant/user/version
写数据库与删缓存是否存在竞态
消费是否具备幂等与重放能力

18.4 “Trace 很全,但不能复现”#

检查:

Prompt / model / tool / policy 版本是否记录
随机性参数是否记录
外部工具结果是否有不可变引用或 hash
状态迁移前后是否可定位
敏感原文被脱敏后是否保留受控证据引用

18.5 “成本突然上升”#

按顺序检查:

Run 数量
每 Run 模型调用数
每次输入 token
重试率
检索 chunk 数
模型路由分布
未终止循环
缓存命中率
成功 Run 的单位成本

19. 分阶段实践任务#

19.1 第一轮:纯内存、无真实模型#

1. 建 API Schema
2. 建 AgentState
3. 用 FakeModel 返回固定结构
4. 用 FakeTool 模拟订单与物流
5. 完成状态迁移单元测试
6. 输出结构化事件

19.2 第二轮:接基础设施#

1. PostgreSQL 持久化 Run 与 Tool Call
2. Redis 做 TTL 缓存和限流
3. Docker Compose 启动依赖
4. 增加迁移、Readiness 和集成测试

19.3 第三轮:接真实模型#

1. 实现一个 Provider Adapter
2. 结构化输出解析与错误分类
3. 接入只读工具
4. 记录 token、延迟和成本
5. 跑固定 Scenario Dataset

19.4 第四轮:开放写操作#

1. 身份和资源授权
2. Policy Check
3. Human Confirmation
4. Idempotency Record
5. Final-state Verification
6. Audit Log 与故障演练

每轮都要能单独演示和回归。不要在基础状态机尚不可测试时同时引入 RAG、长期记忆、多 Agent 与微调。


20. 达标检查清单#

工程结构#

  • 业务领域不直接依赖模型 SDK;
  • 配置有类型、默认值、必填校验和 Secret 边界;
  • 依赖有 lockfile,开发/测试/生产构建可复现;
  • 数据库迁移可自动验证;
  • Liveness 与 Readiness 分离。

模型与工具#

  • 模型输出经过 Schema 校验;
  • Provider 错误被归一化并标记是否可重试;
  • 工具注册包含版本、权限、副作用、超时和确认策略;
  • 写工具有幂等机制;
  • 写工具成功后查询业务最终状态。

状态与可靠性#

  • Run 有显式状态机和非法迁移保护;
  • 每个 Run 有步数、时间、模型调用和成本预算;
  • 只对明确可重试操作重试;
  • 进程重启后可读取未完成 Run;
  • 队列、缓存与数据库的真相边界清楚。

安全与观测#

  • 用户身份和租户信息传播到每次工具授权;
  • Prompt、日志和 Trace 默认不记录 Secret/PII;
  • Trace 能关联 Prompt、模型、工具和策略版本;
  • Audit Log 能说明谁在何时批准和执行了什么;
  • 失败样本能进入回归集。

测试#

  • 无真实模型也能跑通核心 Scenario;
  • 有 Provider Adapter 契约测试;
  • 有 PostgreSQL/Redis 集成测试;
  • 有正常、边界、并发、超时和权限失败样例;
  • 能输出任务成功率、工具准确率、p95 延迟和单成功 Run 成本。

达到这些条件,阶段 1 的 Prompt、阶段 3 的 Harness 和后续 RAG/Memory 才有可靠承载面。


21. 面试与复盘问题#

  1. 为什么结构化输出不能替代业务校验?
  2. Agent State、Conversation History、Trace 和 Audit Log 有什么区别?
  3. 为什么写工具网络超时后不能直接重试?
  4. 如何设计跨模型供应商的事件协议?
  5. Redis 适合保存哪些 Agent 数据,不适合保存哪些?
  6. 如何保证两个 Worker 不会重复执行同一笔退款?
  7. 为什么模型降级时通常也应该降低动作权限?
  8. 如何在不记录敏感 Prompt 原文的情况下支持问题复现?
  9. Agent 的 Liveness、Readiness 和业务可用性分别是什么?
  10. 一个 Benchmark 分数为什么不能直接证明业务系统可上线?

22. 参考资料与延伸阅读#

资料按“厂商官方方案”“工程标准”“论文与基准”分组。链接用于核对公开行为与原始论述;本文中的架构组合、风险判断和 OrderFlow-Agent 设计属于工程推导,不代表对应厂商的唯一推荐架构。

厂商官方方案#

[S1] Anthropic#

[S2] OpenAI#

[S3] Google#

[S4] Microsoft#

[S5] AWS#

[S6] OpenAI Structured Outputs#

[S7] Google Gemini#

[S8] Anthropic Structured Tool Use#

[S9] Cloudflare AI Gateway#

  • AI Gateway:模型代理、分析、缓存、限流与可观测性。

[S10] Microsoft AI Gateway#

[S11] Google Cloud Model Armor#

  • Model Armor:模型输入输出安全检查与云端集成边界。

工程标准与基础设施#

[S12] OpenTelemetry#

[S13] OWASP#

[S14] NIST#

[S15] 基础组件#

论文与基准#

[P1] ReAct#

[P2] Toolformer#

[P3] Gorilla#

[P4] ToolLLM#

[P5] RAG#

[P6] AgentBench#

[P7] τ-bench#

[P8] SWE-bench#

[P9] Long-context position effects#


23. 阶段总结#

阶段 0 真正要建立的不是“会用 FastAPI + Redis + Docker”的简历关键词,而是一套稳定的工程判断:

模型输出是提议,不是事实;
工具调用是受控命令,不是字符串;
状态变化要持久化、校验和审计;
网络失败必须按副作用语义处理;
上下文、业务数据、Trace 和 Memory 各有边界;
没有测试、预算、终止条件和版本信息的 Agent 不可维护。

有了这套底座,后续 Prompt Engineering 才能成为可测试的模型接口,Agent Harness 才能成为可恢复的 Runtime,RAG、Memory 与 Multi-Agent 也才不会变成堆叠复杂度。

Agent 工程基础底座:从 Python 服务到可演进的 Runtime 骨架
https://jupiter-ws.cn/posts/agent/agent-engineering-foundation-stack/
作者
Jupiter
发布于
2026-03-21
许可协议
CC BY-NC-SA 4.0