Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

5 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Multi-Agent Startup OS

项目定位

这是一个面向真实落地场景的多 Agent 技术尽调与可行性分析后端项目。系统输入一个创业项目想法后,由多个专业 Agent 协作完成市场判断、MVP 范围裁剪、技术评估、财务建模、跨 artifact 一致性审查,并最终输出技术可行性分析和落地评估 brief。

项目不是普通 RAG 聊天机器人,也不是多个 prompt 串联的 demo。它重点展示 AI 应用开发中的结构化输出、Agent 编排、Skill/Tool/MCP 分层、错误分类、人工介入、恢复执行,以及后端工程化中的 API contract、异步任务、状态机、Postgres 持久化、Redis 队列、Docker Compose 部署、可观测性和告警。

系统架构

flowchart LR
    client["Client / 前端 / 调用方"] --> api["FastAPI API"]
    api --> pg["Postgres\n运行状态事实源"]
    api --> redis["Redis\n队列 / 延迟重试 / run lock"]
    redis --> worker["worker\n执行 startup / recovery job"]
    worker --> runtime["Agent Runtime\nscheduler / retry / errors / status"]
    runtime --> agents["Market / Product / Tech / Finance / Critic / Decision Agents"]
    agents --> skills["Skill Layer"]
    skills --> tools["Tool Layer"]
    tools --> mcp["MCP Adapter Layer"]
    agents --> deepseek["DeepSeek API"]
    worker --> pg
    sweeper["sweeper\n扫描 stale heartbeat"] --> pg
    sweeper --> redis
    api --> prometheus["Prometheus"]
    prometheus --> grafana["Grafana"]
Loading

核心模块:

  • main.py:FastAPI 应用入口,注册 API 路由、中间件和启动校验。
  • api/routes.py:创建 run、查询 run、resume、recover、heartbeat、metrics 等接口。
  • worker.py:从 Redis 队列消费任务,拿 run lock,执行工作流,处理 worker-level retry。
  • sweeper.py:扫描运行中的任务,发现 heartbeat 过期后重新入队或标记失败。
  • runtime/:Agent 调度、状态更新、错误分类、重试、恢复计划、心跳、生命周期事件、metrics。
  • agents/:市场、产品、技术、财务、审查、最终决策 Agent。
  • skills/:Agent 可组合业务能力,例如市场研究、MVP 裁剪、架构评估、财务分析、一致性审查、投资决策。
  • tools/:确定性工具,例如市场规模估算、功能优先级、技术风险、unit economics、一致性检查、技术可行性 brief。
  • mcp_layer/:把内部 Tool 包装成 MCP 风格 tool definition 和 call result。
  • storage/:JSON 本地仓库和 Postgres 仓库,包含轻量 migration runner。
  • queues/:Redis 队列和 Redis run lock。
  • monitoring/:Prometheus、Grafana dashboard 和 alert rules。

本地运行

复制环境变量文件:

copy .env.example .env

.env.example 只用于本地开发和演示。公网 SaaS 部署前请参考 .env.production.example.env.staging.exampledocs/production_config.md,真实密钥不要提交到仓库,应由 Secret Manager、CI/CD secret 或部署平台环境变量注入。

本地开发可以使用 JSON repository:

APP_ENV=local
REPOSITORY_BACKEND=json
DEEPSEEK_API_KEY=你的_deepseek_key

安装依赖:

pip install -r requirements.txt

启动 API:

uvicorn main:app --reload

如果要测试异步队列模式,需要启动 Redis,并分别启动 worker 和 sweeper:

python worker.py
python sweeper.py

运行测试:

python -m pytest -q

部署前预检查:

python scripts/preflight.py

公网 SaaS 上线配置审计:

set -a; . ./.env.production; set +a; python scripts/production_audit.py

生产发布顺序建议固定为:

migration -> production_audit -> compose_config -> start_services -> smoke -> smoke_staging -> smoke_prod -> smoke_tenant_control -> smoke_rejection -> rollback_ready

也就是先执行数据库 migration:

python scripts/migrate.py

再执行 set -a; . ./.env.production; set +a; python scripts/production_audit.py,然后启动 API、worker、sweeper,最后运行 python scripts/smoke_test.py。如果使用生产 Docker Compose,可以在启动服务前运行:

make public-compose-config
docker compose --env-file .env.production -f docker-compose.yml -f docker-compose.prod.yml -f docker-compose.public.yml run --rm api python scripts/migrate.py

发布清单已经固化在 docs/release_checklist.md,上线前可以执行静态门禁,确认 migration、生产审计、compose 配置、服务启动、smoke、生产 smoke、单租户 run 创建止损、429 拒绝链路和 rollback 准备都没有漏项:

make release-check

预发环境可以使用 staging release gate 把本地静态门禁和黑盒 smoke gate 串成固定顺序。默认命令只打印 dry-run 计划,不执行任何操作:

make staging-release-plan
python scripts/staging_release_gate.py

确认 .env.production 中的生产静态审计配置已就绪,.env.staging.example 已复制为 .env.staging 并由 Secret Manager 注入真实 staging smoke 值,且 SMOKE_EXPECT_TENANT_CONTROL_GATE=1SMOKE_PLATFORM_OPERATOR_API_KEYSMOKE_API_KEY 指向专用 smoke tenant 后,再执行:

make staging-release-gate
python scripts/staging_release_gate.py --execute

这个 gate 会按顺序执行 release-plan -> production_audit -> preflight -> release-check -> smoke-staging。其中 production_audit 显式读取 .env.productionsmoke-staging 显式读取 .env.staging;执行结果会对 smoke/API/operator 相关密钥做脱敏,失败时停在第一个失败步骤,避免后续 smoke 继续制造额外流量。

依赖版本固定是生产部署门禁之一。requirements.txt 中的直接依赖必须使用 == 固定版本,production_audit.py 会检查该要求,避免每次镜像构建时解析到不可预期的新版本。

GitHub Actions 已配置 CI 门禁:每次 push 或 pull request 会安装固定依赖、运行 python -m pytest -q、执行 python scripts/production_audit.py,最后执行 python scripts/preflight.py。这保证安全配置、依赖固定、Docker Compose 配置和测试不会只依赖本地手工检查。

生产审计还包含 repository tenant guard:PostgresWorkflowRepository 的关键查询方法必须提供 tenant-aware 查询或显式 tenant filter,例如 load_run_for_tenantlist_runs(tenant_id=...)list_usage_ledger(tenant_id=...)list_api_keys(tenant_id=...)。这一步不能替代 Postgres RLS,但可以防止后续开发时把跨租户查询能力误暴露给 API 层。

真实 Postgres 生产环境可以继续按 docs/postgres_rls.md 启用数据库层行级安全,SQL 模板见 db/postgres_rls.sql。RLS 与 repository tenant guard 是两层防线:前者在数据库层隔离数据,后者在应用层阻止危险查询路径进入 API。

创建数据库模式 API key。该命令只会在终端输出一次明文 key,数据库只保存 hash:

python scripts/create_api_key.py `
  --tenant-id demo-tenant `
  --scope admin `
  --key-id demo-admin-key `
  --note "initial admin key"

生成 Postgres 备份或恢复命令。脚本只输出命令计划,不直接执行生产备份/恢复:

python scripts/postgres_backup.py --mode backup --file backups/agent.dump
python scripts/postgres_backup.py --mode restore --file backups/agent.dump

如果本地安装了 make,也可以使用:

make verify

服务启动后的 smoke test:

python scripts/smoke_test.py

如果 API 启用了 API_KEY,需要设置:

$env:SMOKE_API_KEY="your_api_key"

生产模式下 smoke test 还应该验证 Prometheus metrics token、平台队列接口和 docs 关闭策略:

$env:SMOKE_BASE_URL="https://api.acme-corp.com"
$env:SMOKE_METRICS_API_KEY="your_metrics_key"
$env:SMOKE_PLATFORM_OPERATOR_API_KEY="your_platform_operator_key"
$env:SMOKE_EXPECT_PRODUCTION_DOCS_CLOSED="1"
python scripts/smoke_test.py

生产模式 smoke 要求 SMOKE_BASE_URL 是真实 https:// 公网 DNS 地址,不能使用 localhost、IP 地址或 example.com 模板域名。脚本会检查 /api/metrics/prometheus 带 metrics token 时是否包含 agent_queue_ready_jobsagent_worker_jobs_totalagent_sweeper_dispatch_failures_totalagent_sweeper_version_conflicts_total,并验证无 token 访问会返回 401/403;同时检查 /api/admin/queues 是否返回 ready、processing、delayed,并在创建 run 时携带 Idempotency-Key,避免 smoke test 重试造成重复任务。

发布窗口或预发环境还可以打开维护模式 smoke gate,用来验证平台可以暂停新 run、暴露维护指标、记录审计事件,并在关闭维护后恢复创建:

$env:SMOKE_PLATFORM_OPERATOR_API_KEY="your_platform_operator_key"
$env:SMOKE_EXPECT_MAINTENANCE_GATE="1"
$env:SMOKE_MAINTENANCE_TENANT_ID="tenant_maintenance_smoke"
python scripts/smoke_test.py

该 gate 会调用 /api/admin/maintenance 开启维护模式,确认 POST /api/startup/runs 返回 maintenance_mode_enabled,检查 /api/metrics/prometheus 中的 agent_maintenance_mode_enabledagent_maintenance_run_rejections_total,再通过 /api/admin/audit-events 确认 maintenance.run_rejected,最后关闭维护模式并创建一个正常 run。

预发环境还可以执行单租户 run 创建止损 smoke gate,用来验证平台 operator 可以暂停某个专用 smoke tenant 创建新 run,并在验证后恢复:

$env:SMOKE_PLATFORM_OPERATOR_API_KEY="your_platform_operator_key"
$env:SMOKE_EXPECT_TENANT_CONTROL_GATE="1"
$env:SMOKE_TENANT_CONTROL_TENANT_ID="tenant_control_smoke"
python scripts/smoke_test.py

该 gate 会调用 /api/admin/tenants/{tenant_id}/run-creation-control 开启暂停,确认 POST /api/startup/runs 返回 tenant_run_creation_suspended,检查 /api/metrics/prometheus 中的 agent_tenant_run_creation_suspendedagent_tenant_run_creation_rejections_total,再通过 /api/admin/audit-events 确认 tenant.run_creation_rejected。最后脚本会关闭暂停并创建一个正常 run,避免预发租户被长期锁住。正式生产环境不要对真实客户 tenant 执行这个 gate,只能使用专用 smoke tenant。

也可以使用:

make smoke

Docker Compose 运行

生产化演示建议使用 Docker Compose,因为它会同时启动 API、worker、sweeper、Postgres、Redis、Prometheus 和 Grafana。

准备 .env

copy .env.example .env

Docker 环境至少需要配置:

DEEPSEEK_API_KEY=你的_deepseek_key
API_KEY_AUTH_BACKEND=database
METRICS_API_KEY=你的_metrics_key
PLATFORM_OPERATOR_API_KEY=你的_platform_operator_key
CORS_ALLOWED_ORIGINS=https://app.example.com,https://admin.example.com
TRUSTED_HOSTS=api.example.com
GRAFANA_ADMIN_PASSWORD=你的_grafana_强密码
DEEPSEEK_BASE_URL=https://api.deepseek.com
DEEPSEEK_MODEL=deepseek-chat
DEEPSEEK_INPUT_TOKEN_PRICE_PER_1K=0
DEEPSEEK_OUTPUT_TOKEN_PRICE_PER_1K=0
DEEPSEEK_TIMEOUT_SECONDS=60
DEEPSEEK_MAX_CONCURRENCY=4
DEEPSEEK_CIRCUIT_BREAKER_FAILURE_THRESHOLD=5
DEEPSEEK_CIRCUIT_BREAKER_RECOVERY_SECONDS=60
POSTGRES_POOL_SIZE=5
POSTGRES_MAX_OVERFLOW=10
POSTGRES_POOL_RECYCLE_SECONDS=1800
REDIS_SOCKET_TIMEOUT_SECONDS=5
REDIS_HEALTH_CHECK_INTERVAL_SECONDS=30
API_PROCESS_MODEL=gunicorn
API_BIND=0.0.0.0:8000
API_WORKERS=2
API_TIMEOUT_SECONDS=120
API_GRACEFUL_TIMEOUT_SECONDS=30
API_KEEP_ALIVE_SECONDS=5
API_MAX_REQUEST_BODY_BYTES=1048576

公网 .env.production 不要加入 localhost127.0.0.1、通配符或 URL origin 到 TRUSTED_HOSTS;本地调试域名只应留在本地 .env.example

按需开启高并发控制:

WORKER_CONCURRENCY=2
WORKER_TENANT_INFLIGHT_LIMIT=2
API_RATE_LIMIT_PER_MINUTE=120
TENANT_RUN_QUOTA_PER_DAY=50
TENANT_MONTHLY_RUN_QUOTA=1000
TENANT_MONTHLY_TOKEN_QUOTA=5000000
TENANT_MONTHLY_COST_QUOTA_USD=50

如果只是本地演示,可以继续使用 API_KEY_AUTH_BACKEND=envAPI_KEYAPI_KEY_TENANT_MAP;公网 SaaS 上线前应使用 database-backed API key,并通过 python scripts/create_api_key.py 创建初始 admin key。

启动完整服务:

docker compose up --build

公网 SaaS 部署不要直接使用本地 demo 配置。生产环境应叠加 docker-compose.prod.yml,它会把服务切到 APP_ENV=production、强制使用 database-backed API key、要求 Secret Manager 注入关键密钥,并清除 Postgres、Redis、Prometheus、Grafana 的本地端口暴露:

docker compose --env-file .env.production -f docker-compose.yml -f docker-compose.prod.yml -f docker-compose.public.yml config --quiet
docker compose --env-file .env.production -f docker-compose.yml -f docker-compose.prod.yml -f docker-compose.public.yml up --build

生产 override 同时配置 restart: unless-stopped 和 API/worker/sweeper 的 stop_grace_period,让异常退出可以自动拉起,滚动发布时也能给 readiness draining、worker 当前 batch 和 sweeper 当前扫描留下退出时间。它还为公网容器配置 security_opt: no-new-privileges:truecap_drop: ALLpids_limitmem_limit,降低小服务器上进程逃逸、fork 炸弹和单容器内存打满的风险。生产 compose 校验使用 config --quiet,只验证配置可渲染,不把注入后的密钥打印到 CI 或发布日志。

服务地址:

  • API: http://localhost:8000
  • API Docs: http://localhost:8000/docs
  • Prometheus: http://localhost:9090
  • Grafana: http://localhost:3000
  • Postgres: localhost:5432
  • Redis: localhost:6379

核心 API

创建异步技术可行性分析任务:

curl -X POST http://localhost:8000/api/startup/runs `
  -H "Content-Type: application/json" `
  -H "X-API-Key: your_api_key" `
  -H "Idempotency-Key: tenant_a_create_run_001" `
  -d "{\"user_input\":\"给独立开发者做一个 AI 技术可行性分析工具\",\"user_id\":\"demo-user\"}"

公网客户端创建 run 时建议总是传 Idempotency-Key。如果客户端超时后用同一个 key 重试,API 会返回已经创建过的 run,不会重复创建工作流、重复入队,也不会重复扣减月度 run 配额。

异步创建链路会先把 queued run 持久化到 Postgres,持久化成功后才写入 Redis 队列。这样 worker 不会拿到数据库里还不存在的 run;如果数据库保存失败,请求会直接失败且不会产生孤儿队列任务。

查询 run:

curl http://localhost:8000/api/runs/{run_id} -H "X-API-Key: your_api_key"

恢复执行:

curl -X POST http://localhost:8000/api/runs/{run_id}/resume `
  -H "Content-Type: application/json" `
  -H "X-API-Key: your_api_key" `
  -d "{\"from_agent\":\"tech\"}"

触发恢复任务:

curl -X POST http://localhost:8000/api/runs/{run_id}/recover -H "X-API-Key: your_api_key"

Prometheus 指标:

curl http://localhost:8000/api/metrics/prometheus `
  -H "X-Metrics-API-Key: your_metrics_key"

队列运维状态,只允许 platform operator token 调用:

curl http://localhost:8000/api/admin/queues `
  -H "X-Platform-API-Key: your_platform_operator_api_key"

暂停或恢复单个租户创建新 run,只允许 platform operator token 调用:

curl -X POST http://localhost:8000/api/admin/tenants/{tenant_id}/run-creation-control `
  -H "X-Platform-API-Key: your_platform_operator_api_key" `
  -H "Content-Type: application/json" `
  -d "{\"run_creation_suspended\":true,\"reason\":\"cost anomaly investigation\"}"

查询当前租户 API key 元数据,不返回明文或 hash:

curl http://localhost:8000/api/admin/api-keys `
  -H "X-API-Key: your_admin_api_key"

创建新的 database-backed API key。响应里的 api_key 只出现一次:

curl -X POST http://localhost:8000/api/admin/api-keys `
  -H "Content-Type: application/json" `
  -H "X-API-Key: your_admin_api_key" `
  -d "{\"key_id\":\"tenant-member-key\",\"scope\":\"member\",\"note\":\"member service key\"}"

禁用或轮换 API key:

curl -X POST http://localhost:8000/api/admin/api-keys/{key_id}/disable `
  -H "Content-Type: application/json" `
  -H "X-API-Key: your_admin_api_key" `
  -d "{\"reason\":\"leaked key response\",\"confirm_key_id\":\"{key_id}\"}"

curl -X POST http://localhost:8000/api/admin/api-keys/{key_id}/rotate `
  -H "Content-Type: application/json" `
  -H "X-API-Key: your_admin_api_key" `
  -d "{\"new_key_id\":\"tenant-member-key-v2\",\"scope\":\"member\",\"note\":\"rotated service key\",\"reason\":\"scheduled key rotation\",\"confirm_key_id\":\"{key_id}\"}"

当前租户用量统计:

curl http://localhost:8000/api/tenant/usage `
  -H "X-API-Key: your_api_key"

当前租户用量账本:

curl http://localhost:8000/api/tenant/usage-ledger `
  -H "X-API-Key: your_api_key"

当前租户配额状态:

curl http://localhost:8000/api/tenant/quota `
  -H "X-API-Key: your_api_key"

可靠性机制

系统包含 agent-level retry、worker retry、Redis run lock、heartbeat、stale job sweeper、recovery plan、resume workflow、human review request、结构化日志和 Prometheus 指标。

租户隔离第一版:

  • 创建 run 时写入 tenant_id
  • 生产推荐使用 API_KEY_TENANT_MAP 把 API key 绑定到 tenant,认证中间件会把租户写入 request.state.tenant_id
  • API_KEY_TENANT_MAP 兼容旧格式 {"key":"tenant"},也支持结构化格式 {"key":{"tenant_id":"tenant","scope":"admin","key_id":"key_admin"}}
  • API key scope 第一版支持 adminmemberread_only;创建 run、同步 run、resume、recover 需要写权限,read_only 只能查询。
  • API key 认证支持 API_KEY_AUTH_BACKEND=envAPI_KEY_AUTH_BACKEND=database 两种模式;env 保留本地和演示兼容,database 会从 Postgres api_keys 表读取 hash 后的 key,不在数据库保存明文 API key。
  • api_keys 表包含 key_idkey_hashtenant_idscopeenabledexpires_atnotecreated_atlast_used_at;认证成功后只把 key_id 写入审计日志,便于禁用、过期、轮换和追踪 key 使用。
  • 使用 database 认证模式时,可以通过 python scripts/create_api_key.py --tenant-id <tenant> --scope admin --key-id <key_id> 创建初始 key;终端输出的 api_key 只出现一次,需要由运维保存到调用方密钥管理系统。
  • 管理 API 提供 /api/admin/api-keys/api/admin/api-keys/{key_id}/disable/api/admin/api-keys/{key_id}/rotate,支持列出 key 元数据、创建 key、禁用 key、轮换 key;这些接口只允许 admin scope 调用,并且不会返回 key_hash
  • 禁用和轮换 API key 必须在请求体中提供 reasonconfirm_key_id,该原因会进入 audit_events.detailsconfirm_key_id 必须等于路径中的 key_id,系统也会拒绝禁用或轮换当前请求正在使用的 key,避免管理员把当前会话锁死后无法继续恢复。
  • 查询 run 列表时按认证后的 tenant 过滤;未启用 API key 时,本地开发仍可用 X-Tenant-ID 兼容调试。
  • 查询、resume、recover 单个 run 时校验 run 所属租户,不匹配返回 404。
  • 创建 run 时优先使用认证后的 tenant,防止客户端通过 body 里的 tenant_id 伪造租户;未启用认证时才使用请求里的租户字段兼容本地开发。
  • repository 提供 tenant-aware load 方法,API 查询单个 run 时优先在 repository 层按 run_id + tenant_id 读取,减少业务层漏校验风险。
  • Postgres repository 会在已知 tenant 的业务操作前调用 _set_tenant_context,通过 set_config('app.current_tenant_id', tenant_id, true) 设置事务级 RLS 上下文;SQLite 本地测试会自动跳过。
  • API key 认证发生在识别 tenant 之前,PostgreSQL 生产路径会通过 db/postgres_auth_lookup.sql 中的 resolve_api_key_principal SECURITY DEFINER 函数解析最小 principal,避免普通 RLS tenant context 无法用于认证入口的问题。
  • Postgres repository 已把 tenant_id 提升为 runs.tenant_id 明确列,并创建 (tenant_id, updated_at) 索引,列表查询可以在数据库层按租户过滤。
  • 未传租户信息时使用 default 租户,兼容本地开发和旧测试数据。
  • 生产环境会收紧公开路径,/docs/openapi.json/api/metrics/prometheus 不再作为默认公开路径。
  • /api/metrics/prometheus 支持独立 METRICS_API_KEY,生产环境必须配置;调用方需要通过 X-Metrics-API-KeyAuthorization: Bearer <token> 传入,避免运行指标在公网裸露。
  • /api/metrics 是 tenant-scoped JSON metrics,只聚合当前租户 runs;/api/metrics/prometheus 是全局运维采集入口,必须使用独立 metrics token,不复用普通租户 API key。
  • 启用 Postgres RLS 后,普通 /api/metrics 使用 app.current_tenant_id 的租户上下文;Prometheus 全局指标使用显式 list_ops_metric_runs / load_ops_metric_run_set_platform_ops_context 设置 app.platform_ops,配合 platform_ops_read_runs 只读 policy,避免普通租户接口复用全局读取路径。
  • /api/admin/queues 是平台级运维接口,只允许 PLATFORM_OPERATOR_API_KEY 通过 X-Platform-API-KeyAuthorization: Bearer <token> 调用,不复用普通租户 admin scope;它会返回 Redis ready、processing、delayed 队列数量,用于判断 worker 是否积压、是否有任务卡在 processing、是否有大量延迟重试。
  • /api/admin/tenants/{tenant_id}/run-creation-control 是租户级止损接口,只允许 platform operator 调用。开启后该租户创建 run 会返回 tenant_run_creation_suspended,不会保存 run、不会写 Redis 队列、不会预扣月度 run quota;每次切换写入 platform.tenant_control.updated,拒绝创建写入 tenant.run_creation_rejected
  • Redis ready queue 支持轻量 tenant fairness:worker 记录 last_dequeued_tenant,如果队首还是同一租户,会向后扫描一小段 ready jobs,优先取另一个租户的任务,避免单个租户短时间提交大量任务时压住其他租户。
  • Redis dequeue 还支持 WORKER_TENANT_INFLIGHT_LIMIT:生产环境要求显式设置正数;当某个 tenant 的 processing job 数量达到阈值时,worker 会跳过该 tenant 的 ready job,优先领取其他 tenant 的任务,避免单租户占满全部 worker in-flight 容量。
  • malformed/poison Redis job 会进入 dead-letter queue,不会反复回到 ready queue 占用 worker;Prometheus 暴露 agent_queue_dead_letter_jobsagent_queue_dead_letter_latest_reason_info,告警为 AgentQueueDeadLetterDetected
  • /api/admin/audit-events 只允许 admin scope 调用,会返回当前租户的 audit_events 持久化审计事件,用于追踪 API key 创建、禁用、轮换、配额拒绝和 metrics 未授权访问;limit 会在 API 层限制到 1 到 500,避免一次查询拉取过多审计数据。
  • audit_events 表会记录 tenant_idapi_key_id、权限 scope、事件类型、资源 ID、request_id、客户端 IP 和脱敏后的 details;不会保存 API key 明文、Authorization、Cookie、DeepSeek key 等敏感字段。
  • API 结构化日志会记录 tenant_idauth_scopeapi_key_id、path、status_code、run_id 和 request_id,便于审计谁在什么租户下访问了什么资源;日志只记录 key_id,不记录 API key 明文。
  • API 错误响应和结构化日志会递归脱敏 AuthorizationX-API-Key、cookie、token、secret、password 等敏感字段。
  • API 支持 CORS_ALLOWED_ORIGINSTRUSTED_HOSTS 两个公网边界配置;生产环境必须显式配置允许的前端域名和 API Host,避免任意 Origin 或异常 Host 访问。
  • 创建 run 的 user_inputmetadata 有长度和类型限制,避免超大请求打爆 prompt、日志和数据库。

高并发控制第一版:

  • API 支持按 X-Tenant-ID 做 Redis 计数限流,API_RATE_LIMIT_PER_MINUTE 控制每个租户每分钟最多请求数。
  • API 容器通过 python scripts/start_api.py 启动,Docker Compose 默认使用 gunicorn + uvicorn worker 生产进程模型,不再直接用单进程 uvicorn 作为容器主进程。
  • API_PROCESS_MODEL 支持 uvicorngunicorn;本地开发默认 uvicorn,production 启动校验要求必须使用 gunicorn
  • API_WORKERS 控制 API worker 进程数,API_TIMEOUT_SECONDS 控制单个请求最长执行时间,API_GRACEFUL_TIMEOUT_SECONDS 控制优雅退出等待时间,API_KEEP_ALIVE_SECONDS 控制连接 keep-alive。
  • API_MAX_REQUEST_BODY_BYTES 控制 API 请求体最大字节数;超过上限的请求会在中间件层返回 413。该限制同时覆盖带 Content-Length 和无 Content-Length 的流式请求,避免超大 body 进入 Pydantic、prompt 拼接、日志和数据库写入链路。
  • Postgres engine 配置了连接池参数,POSTGRES_POOL_SIZEPOSTGRES_MAX_OVERFLOWPOSTGRES_POOL_RECYCLE_SECONDS 用于控制连接数量、溢出连接和连接回收。
  • Redis client 配置了 socket timeout 和 health check interval,REDIS_SOCKET_TIMEOUT_SECONDSREDIS_HEALTH_CHECK_INTERVAL_SECONDS 用于避免 Redis 异常时请求长期挂起。
  • 创建分析任务接口支持每日 run 配额,TENANT_RUN_QUOTA_PER_DAY 控制每个租户每天最多创建多少个 run。
  • 创建 run 前会做月度租户配额校验,TENANT_MONTHLY_RUN_QUOTA 控制每月最多 run 数,TENANT_MONTHLY_TOKEN_QUOTA 控制每月最多 token 数,TENANT_MONTHLY_COST_QUOTA_USD 控制每月最多估算成本。
  • 月度配额命中后 API 会返回 429 tenant_quota_exceeded,并且不会创建 run、不会保存初始状态、不会写入 Redis 队列,避免超额任务进入 worker。
  • worker 支持本地并发消费,WORKER_CONCURRENCY 控制单个 worker 进程同时处理多少个 Redis job。
  • WORKER_TENANT_INFLIGHT_LIMIT 控制单个 tenant 同时处于 Redis processing set 的 worker job 数;本地默认 0 表示不启用,production 启动校验要求必须设置为正数。
  • 默认值都是保守配置:限流、配额和 tenant in-flight 限制默认为 0,表示本地开发不启用;worker 默认并发为 1,保持旧行为。
  • Redis job 会携带 tenant_id,延迟重试会保留该租户信息。
  • Redis run lock 使用 agent:tenant:{tenant_id}:lock:run:{run_id} 命名空间,即使多个 worker 或高并发 batch 拿到同一个 run,也只有对应租户下的一个执行者能真正处理该 run。
  • worker 运行期间会随着 heartbeat 刷新 Redis run lock TTL,避免长任务超过锁 TTL 后被其他 worker 重复执行。
  • 当前队列入口仍是全局 worker queue,便于 worker 统一消费所有租户任务;后续如果要做租户优先级或独立 worker pool,可以再拆成 tenant queue。
  • /api/tenant/usage 会按当前租户聚合 run 数、Agent 调用次数、失败次数、重试次数、人工介入次数和估算成本。
  • DeepSeek provider 会记录 prompt_tokenscompletion_tokenstotal_tokens,Agent 输出会把这些信息写入 _provider_usage,最终进入 trace。
  • DeepSeek 调用配置了显式 timeout,DEEPSEEK_TIMEOUT_SECONDS 控制单次请求最长等待时间;worker 内部通过 DEEPSEEK_MAX_CONCURRENCY 限制同时调用大模型的数量,避免高并发下外部 provider 拖垮 worker。
  • DeepSeek provider 增加 circuit breaker;DEEPSEEK_CIRCUIT_BREAKER_FAILURE_THRESHOLD 控制连续 provider 请求失败多少次后打开熔断,DEEPSEEK_CIRCUIT_BREAKER_RECOVERY_SECONDS 控制恢复窗口。熔断打开后会快速失败,避免 worker 继续堆积外部模型请求。
  • /api/tenant/usage 如果发现 trace 中有 provider token usage,会优先按真实 token 聚合,并使用 DEEPSEEK_INPUT_TOKEN_PRICE_PER_1KDEEPSEEK_OUTPUT_TOKEN_PRICE_PER_1K 分别计算输入/输出成本。
  • scheduler 会把每次带 _provider_usage 的 Agent 调用追加到 _usage_ledger,Postgres repository 保存 run 时会同步到独立 usage_ledger 表。
  • /api/tenant/usage-ledger 会按当前租户返回每次 Agent 调用的 token 和成本账本明细,可用于账单对账、配额扣减和成本审计。
  • /api/tenant/quota 会返回当前租户的月度 run、token、成本配额状态,包括已用量、上限、剩余额度和是否允许继续创建任务。
  • 当前只支持单个大模型 provider:DeepSeek。成本金额按本地配置单价换算,不等同于 DeepSeek 官方账单;如果要做真实商业账单,下一步需要做价格同步和账单对账。

这仍然不是完整企业级租户体系。生产 SaaS 还需要用户/组织表、权限角色、Postgres RLS 行级安全、租户独立队列或优先级队列、跨实例 worker autoscaling、请求排队优先级和租户级成本账单。

重试逻辑分层:

  • Agent 内部失败:scheduler 根据错误分类原地重试当前 Agent。
  • Worker 失败:worker 根据 run retry 配置延迟重新入队。
  • Worker 异常退出:sweeper 根据 heartbeat 判断 stale job 并重新入队。
  • 结果不可信:quality eval 或 critic 可触发 human review。
  • 需要回滚:resume workflow 会清理下游 artifact、trace、错误和 retry 状态。

面试讲法

这个项目可以重点讲四件事:

  1. 自研多 Agent runtime,不依赖 LangGraph,能解释调度、join rule、retry、resume 和 recovery。
  2. Agent 不是纯 prompt,而是 Agent -> Skill -> Tool -> MCP 分层,工具证据会进入 _skill_outputs_tool_evidence
  3. 后端不是 demo:API、worker、Redis、Postgres、run lock、heartbeat、sweeper、metrics、Grafana、CI 和 Docker Compose 都完整。
  4. 项目定位是技术尽调与可行性分析,最终输出 _technical_feasibility_brief,更适合展示 AI 应用工程化能力。

排障手册

详细上线 runbook 见 docs/runbook.md,备份与恢复策略见 docs/backup_recovery.md

常见问题:

  • API 无法启动:检查 .envDEEPSEEK_API_KEYREPOSITORY_BACKENDDATABASE_URL
  • worker 不消费:检查 Redis 是否启动、REDIS_URL 是否正确、队列是否有任务。
  • run 卡在 running:检查 heartbeat、sweeper 日志和 /api/runs/{run_id}/recovery-plan
  • 结构化输出失败:查看 _agent_retrieserrors_traces
  • Docker Compose 失败:生产排障先运行 docker compose --env-file .env.production -f docker-compose.yml -f docker-compose.prod.yml -f docker-compose.public.yml config --quietpython scripts/preflight.py。quiet 模式只校验配置,不把环境变量渲染结果打印到日志。

推荐上线前执行:

set -a; . ./.env.production; set +a; python scripts/production_audit.py
python scripts/preflight.py
python -m pytest -q
docker compose --env-file .env.production -f docker-compose.yml -f docker-compose.prod.yml -f docker-compose.public.yml config --quiet

429 拒绝链路 smoke gate

生产默认 smoke test 不会主动制造 429,因为这会依赖线上套餐/配额配置,并可能创建额外任务。预发或 staging 环境可以显式打开 tenant rejection smoke gate:

$env:SMOKE_EXPECT_TENANT_REJECTION="1"
$env:SMOKE_REJECTION_TENANT_ID="tenant_smoke_rejection"
python scripts/smoke_test.py

打开后,scripts/smoke_test.py 会使用 Idempotency-Key 创建一次允许的 run,再创建第二次并期待 429。它会验证 agent_tenant_quota_rejections_totalagent_tenant_rate_limit_rejections_total 出现在 /api/metrics/prometheus,并通过 /api/admin/audit-events 确认存在 quota.rejectedrate_limit.rejected 审计事件。

这个 gate 建议只在专用租户和低日级 run quota 的预发环境使用,例如把该租户所在环境的 TENANT_RUN_QUOTA_PER_DAY 配成 1。生产正式租户不要用这个 gate 制造拒绝流量。

仓库提供 .env.staging.example 作为预发环境模板,已经包含 TENANT_RUN_QUOTA_PER_DAY=1SMOKE_EXPECT_TENANT_REJECTION=1SMOKE_REJECTION_TENANT_ID。实际部署时仍然要从 Secret Manager 注入真实 SMOKE_API_KEYSMOKE_METRICS_API_KEYSMOKE_PLATFORM_OPERATOR_API_KEY

常用 smoke 命令已经收敛到 Makefile:

make smoke
make smoke-prod
make prepare-smoke-env
make smoke-prod-env
make smoke-rejection
make smoke-maintenance
make smoke-tenant-control
make smoke-staging
make production-init-check
make public-launch-check
make public-compose-config
make public-migrate
make public-up
make release-check
make release-plan
make staging-release-plan
make staging-release-gate
  • make smoke:普通服务启动烟测。
  • make smoke-prod:带 SMOKE_EXPECT_PRODUCTION_DOCS_CLOSED=1,用于生产模式验证 /docs/openapi.json 默认关闭;需要先设置真实 HTTPS SMOKE_BASE_URL
  • make prepare-smoke-env:从 .env.production 和首个 tenant API key 生成 .env.smoke.production,供公网生产 smoke 使用。
  • make smoke-prod-env:加载 .env.smoke.production 后执行生产公网 smoke,避免在命令行手写 metrics/operator 密钥。
  • make smoke-rejection:带 SMOKE_EXPECT_TENANT_REJECTION=1,用于预发环境验证 429、Prometheus tenant rejection 指标和审计事件。
  • make smoke-maintenance:带 SMOKE_EXPECT_MAINTENANCE_GATE=1,用于发布窗口或预发环境验证维护模式开关、503 拒绝、Prometheus maintenance 指标、审计事件和恢复创建。
  • make smoke-tenant-control:带 SMOKE_EXPECT_TENANT_CONTROL_GATE=1,用于预发环境验证单租户 run 创建止损、Prometheus tenant control 指标、审计事件和恢复创建。
  • make smoke-staging:预发环境一键黑盒门禁,顺序执行生产 docs/metrics gate、维护模式 gate、tenant control gate 和 tenant rejection gate,减少发布前漏跑某个 smoke 步骤的风险。
  • make production-init-check:离线检查 .env.production,拒绝占位符、默认密码、过短密钥、非生产模式和不一致的公网域名边界。
  • make public-launch-check:检查单机公网部署 contract,确认 docker-compose.public.yml、Nginx TLS 反代模板、部署文档和 Makefile 命令一致。
  • make public-compose-config:使用 .env.production 静默校验 docker-compose.ymldocker-compose.prod.ymldocker-compose.public.yml 三层叠加,确认公网只暴露 Nginx 80/443。
  • make public-migrate:使用 .env.production 在公网部署 compose 组合下执行数据库 migration。
  • make public-up:使用 .env.production 用公网部署 compose 组合启动 API、worker、sweeper、Postgres、Redis、Prometheus、Grafana 和 Nginx。
  • make release-check:静态校验 docs/release_checklist.md 是否覆盖上线前必须执行的发布门禁。
  • make release-plan:输出 python scripts/release_orchestrator.py 的 dry-run 发布编排计划,确认 maintenance_onqueue_drain_checksmoke_stagingsmoke_maintenancemaintenance_off 等 gate 没有漏项。
  • make staging-release-plan:输出 python scripts/staging_release_gate.py 的 dry-run 计划,不执行命令。
  • make staging-release-gate:执行 python scripts/staging_release_gate.py --execute,用于预发环境串联 production audit、preflight、release checklist 和 make smoke-staging;静态生产审计读取 .env.production,预发 smoke 读取 .env.staging

如果你还没有服务器,先按 docs/public_server_deploy.md 准备域名、DNS、TLS 证书和 .env.production。正式单机公网部署使用:

make public-launch-check
make generate-production-env
make production-init-check
make public-compose-config
make public-migrate
SMOKE_API_KEY="${SMOKE_API_KEY}" make prepare-smoke-env
make public-up
make smoke-prod-env

make generate-production-env now requires PUBLIC_API_HOST, CORS_ALLOWED_ORIGINS, PLATFORM_OPERATOR_ALLOWED_CIDRS, TRUSTED_PROXY_CIDRS, NGINX_GRAFANA_ALLOWED_CIDR, DEEPSEEK_API_KEY, and ALERTMANAGER_WEBHOOK_URL to be set in the shell before running it.

这一路径通过 Nginx 暴露 80/443,不直接暴露 API 8000、Postgres 5432、Redis 6379、Prometheus 9090 或 Grafana 3000。

Production smoke also checks the public Grafana subpath. When SMOKE_EXPECT_PRODUCTION_DOCS_CLOSED=1, python scripts/smoke_test.py first requires SMOKE_BASE_URL to be a real https:// public DNS URL, then requests /grafana/ and accepts 200 or 302, proving Nginx can reach Grafana without exposing port 3000.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages