Files
group_fqcd_jr/docs/09-底座使用文档.md
T
qyqy 7677aeaee1 merge: 并入同事的场外申购/推广/行情/NL2SQL 线(11 提交、334 文件)
冲突仅 3 个文件,全部取并集(双方都没有需要丢弃的改动):
- app/main.py:import 双方路由(我方 knowledge_management + 同事的 offsite_fund/
  promotion_material);include_router 段本已自动合并
- app/service/agent/bootstrap.py:import 与工具注册均取并集
  (query_customer_profile + query_financial_data 都注册)
- tests/integration/test_config_release_mysql.py:outbox 清理同时保留
  架构师的 event_type 限定(防误删其它域 outbox 行)与同事新增的 peer_release_id

同事这轮带入:11 个 alembic 迁移(建 offsite_* / promotion_* 等表)、
场外申购与推广素材 Agent、financial NL2SQL 工具。
注意:本库尚无 offsite_*/promotion_* 表,跑相关测试前需要执行 alembic upgrade。

边界核对:同事的场外代码未写入场内交易表(fin_sim_order/fin_capital_flow/fin_cash_ledger),
符合 AGENTS.md 规则 8。
2026-09-11 18:56:26 +08:00

17 KiB
Raw Blame History

Agent 底座使用文档

版本:v1.0
适用范围:底座负责人、业务 Agent 开发人员、接口联调人员
当前运行方式:FastAPI + MySQL + 独立 Worker + Redis/Milvus/Neo4j 可选基础设施

本文说明如何启动底座、注册业务 Agent、提交运行、读取结果,以及如何使用记忆、配置、模型路由和公共会话能力。 接口字段的最终权威定义仍是《05-接口文档.md》,数据库的最终权威定义仍是《00-新数据库基线设计.md》和《02-数据库建表设计.md》。

1. 运行前提

项目固定使用项目自带虚拟环境(Python 3.13,实测 3.13.5):

.\.venv\Scripts\python.exe --version

首次安装依赖:

.\.venv\Scripts\python.exe -m pip install -r requirements.txt

项目依赖的 .env 不提交到代码仓库。可以复制模板后填写本机配置:

Copy-Item .env.example .env

至少需要配置:

JWT_ISSUER=jr-local
JWT_AUDIENCE=jr-agent-platform
JWT_PUBLIC_KEY_PATH=config/jwt/dev/jwt-public.pem
MYSQL_DSN=mysql+asyncmy://用户名:密码@127.0.0.1:3306/jr
REDIS_URL=redis://127.0.0.1:6379/0
MILVUS_URI=http://127.0.0.1:19530
NEO4J_URI=bolt://127.0.0.1:7687

JWT 私钥只用于签发测试 Token,服务端只读取公钥。模型密钥通过 secret_ref 引用环境变量或密钥服务,不能写入 .py、数据库明文字段或日志。

2. 数据库和迁移

迁移由 Alembic 执行,业务代码不会在运行时创建表:

.\.venv\Scripts\python.exe -m alembic upgrade head
.\.venv\Scripts\python.exe tools\audit_schema.py

当前数据库共 52 张表,其中 51 张业务表(另有 Alembic 版本表 alembic_version,不计入业务表口径)。 tools/audit_schema.py 的实测输出即 schema audit passed: 51 business tables, no missing or unexpected tables。

51 张业务表相对同一批迁移构建的基线是:

  • 49 张基线和已落地表(2026-09-09 补建会话表之前保存的 49 张表指纹即该口径, 见 docs/08-数据库结构审计基线.md §二);
  • svc_conversation_session 会话表(迁移 20260909_session,是 2026-09-09 那次 49 张之后的第 1 张新增表);
  • api_request_receipt 公共 HTTP 写操作幂等回执表(迁移 20260909_api_receipt,第 2 张新增表)。

口径提醒:docs/08 §三 记录的「纠偏前 52 张表快照」是 SHOW CREATE TABLE 全量表数, 含 alembic_version,与上面的 51 张业务表是同一批表的两种计数方式,不是两套结构。

修改表结构前必须先对照 00 基线。允许新增表和字段,禁止重命名、删除或改变已有字段。结构指纹检查命令:

.\.venv\Scripts\python.exe tools\schema_fingerprint.py

3. 启动 HTTP 服务和 Worker

需要两个终端。

终端一启动 HTTP 服务:

.\.venv\Scripts\python.exe -m uvicorn app.main:app --host 127.0.0.1 --port 8099

终端二启动运行 Worker:

.\.venv\Scripts\python.exe -m app.worker

只处理一轮任务并退出:

.\.venv\Scripts\python.exe -m app.worker --once

Worker 的运行参数在 .env 中配置:

WORKER_POLL_SECONDS=1
WORKER_LEASE_SECONDS=60
WORKER_RETRY_LIMIT=3

HTTP 服务只负责认证、权限、参数校验和运行受理。模型调用、记忆召回、结果持久化由 Worker 执行。

4. 注册业务 Agent

业务 Agent 必须继承 BaseAgent,只实现 AgentDefinition 和 handle(),由 AgentFactory 创建。 业务代码不得覆盖 execute()、validate_access()、resolve_config()、recall_memory()、check_compliance() 或治理绑定方法。

示例:

from app.core.contracts import AgentDefinition, AgentRequest, CoreResult, RequestContext
from app.service.agent.base import BaseAgent


class CustomerServiceAgent(BaseAgent):
    definition = AgentDefinition(
        agent_type="customer_service",
        version="1.0.0",
        allowed_roles=("customer", "operator", "admin"),
        allowed_portals=("api",),
        allowed_tools=(),
        supported_intents=("general",),
    )

    async def handle(
        self, request: AgentRequest, context: RequestContext
    ) -> CoreResult:
        return CoreResult(text=f"已收到:{request.message}")

在 app/service/agent/bootstrap.py 的唯一注册入口注册:

from app.service.agent.factory import AgentFactory


def register_business_agents(factory: AgentFactory) -> None:
    factory.register(
        CustomerServiceAgent.definition,
        lambda _context: CustomerServiceAgent(CustomerServiceAgent.definition),
    )

然后由 get_agent_factory() 在首次创建时调用注册函数。HTTP 服务和 Worker 必须共用这个注册入口,不能各自维护一份注册表。

当前 register_business_agents()(app/service/agent/bootstrap.py 约 L210-225)已注册两个业务 Agent: FundQueryDemoAgent(agent_type="fund_query_demo")与 CustomerServiceAgent(agent_type="customer_service")。 组员新增 Agent 时在同一个函数里追加一行 factory.register(...)。

agent_type 使用小写蛇形命名,例如 customer_service、risk、advisor。提交未注册的 agent_type 会返回 404 AGENT_TYPE_NOT_FOUND。

注册表契约测试会扫描所有已注册 Agent:构造器必须返回 BaseAgent,实例定义必须与注册时的 AgentDefinition 完全一致。业务 Agent 未通过该契约测试不得合并;工厂创建阶段也会再次拒绝 非法构造器,避免绕过公共执行链。

get_agent_factory() 是 HTTP 服务和 Worker 共用的底座组装入口。它会从 model_endpoint_config 按端点代码读取激活配置,构造 DatabaseModelGateway → ModelGenerationService,并注册公共只读工具 到 ToolRegistry → ToolExecutor 后注入工厂。业务组员只需注册自己的 Agent,不得在业务代码中重新 创建模型 Adapter、读取模型密钥或另建工具执行器。

5. Agent 的公共执行顺序

每次运行固定经过:

输入校验
→ 角色、入口和权限校验
→ 读取生效配置
→ 召回当前用户记忆
→ 执行业务 handle()
→ 超时控制
→ 禁止表达、引用、敏感信息和工具记录审查
→ 持久化结果、审计和 Outbox

业务 Agent 可读取:

  • self.config:本次运行的不可变配置快照;
  • self.memories:当前用户的授权记忆集合;
  • request:客户端业务输入;
  • context:服务端生成的身份和权限上下文。

业务 Agent 不得直接创建 SQLAlchemy Session、Redis、Milvus 或 Neo4j Driver。关系查询必须经过 RelationshipService,模型调用必须经过模型路由和 ModelGateway。

6. 提交一次 Agent 运行

POST http://127.0.0.1:8099/api/v1/agent-runs
Authorization: Bearer <JWT>
Idempotency-Key: customer-request-202609090001
Content-Type: application/json

请求体:

{
  "agent_type": "customer_service",
  "message": "请介绍一下基金风险",
  "session_id": "session-uuid",
  "idempotency_key": "customer-request-202609090001",
  "metadata": {
    "locale": "zh-CN",
    "client_version": "web-1.0",
    "ui_entry": "customer-chat"
  }
}

返回 202:

{
  "run_id": "run-uuid",
  "trace_id": "trace-uuid",
  "status": "queued",
  "status_url": "/api/v1/agent-runs/run-uuid",
  "events_url": "/api/v1/agent-runs/run-uuid/events"
}

同一用户、同一 Agent、同一幂等键和同一正文会返回原运行;正文不同会返回 409 IDEMPOTENCY_CONFLICT。

7. 查询和 SSE 恢复

查询运行:

GET /api/v1/agent-runs/{run_id}
Authorization: Bearer <JWT>

订阅 SSE:

GET /api/v1/agent-runs/{run_id}/events
Authorization: Bearer <JWT>
Accept: text/event-stream

运行未完成时只收到 start 和心跳;最终事务提交后收到 tools(可选)、delta、done。已完成运行重连时使用 replace 返回完整正文。

这是结果级恢复:断线后重新请求同一个 run_id 即可恢复,不依赖 Worker 内存,也不实现事件级 Last-Event-ID 续传。

8. 会话、取消和转人工

创建会话:

POST /api/v1/conversations
Idempotency-Key: session-create-202609090001
{"agent_type": "customer_service"}

结束会话:

POST /api/v1/conversations/{session_id}/closures
Idempotency-Key: session-close-202609090001

取消运行:

POST /api/v1/agent-runs/{run_id}/cancellations
Idempotency-Key: cancel-202609090001
{"reason": "user_cancelled"}

转人工:

POST /api/v1/conversations/{session_id}/handover-requests
Idempotency-Key: handover-202609090001

Agent 只能提出转人工请求,不能直接分配、接单、解决或关闭客服工单。

9. 记忆、关系推理和模型路由

记忆提取不提供客户端写接口。complete_run() 会在最终事务内写入 memory.extraction_requested,由 Outbox Worker 消费。

记忆查询:

GET /api/v1/users/me/memory-profile
GET /api/v1/customers/{customer_id}/memory-profile

Neo4j 关系推理必须调用 RelationshipService,只能使用白名单关系和受限跳数;Neo4j 不可用时返回降级结果,不得绕过数据权限。

模型调用必须经过:

ConfigRelease → ModelRouterService → ModelGateway → 主端点/受控 fallback

业务 Agent 在 handle() 中通过底座注入的 generate_with_model(endpoints, prompt) 调用模型, 不得直接导入 httpx、供应商 SDK 或自己读取密钥。当前已提供 OpenAI-compatible Adapter; 生产接入时由模型平台根据 ModelEndpointConfig.base_url、model_name 和 secret_ref 构造已批准端点,再交给 ModelGenerationService。没有路由端点时会失败关闭并进入 Worker 重试, 不会静默调用环境变量中的默认模型。

意图识别使用公共 IntentClassifier,输入为用户消息、Agent 声明的 supported_intents 和模型路由结果。模型只能返回严格 JSON;底座会校验意图白名单和 confidence(0 到 1),低于发布配置阈值时返回 needs_clarification=true,由业务流程澄清, 不得把低置信结果当成确定意图。格式错误、未声明意图和空输入均失败关闭。业务 Agent 不得直连 模型或自行解析供应商响应。

该分类器已经接入 BaseAgent.execute():完成鉴权、配置和记忆召回后,底座会从数据库解析当前 激活模型端点并在 handle() 前自动分类,最终分类结果自动写入 CoreResult.intent。组员无需调用 ModelRouterService、创建 IntentClassifier 或向 handle() 传端点,也不得覆盖 classify_intent() 和 bind_intent_classifier()。

工具调用必须经过工厂注入的 ToolExecutor。业务 Agent 使用 await self.call_tool(name, arguments, intent=..., context=context),不能直接访问数据库或外部服务。每个工具必须声明 Pydantic 输入模型、权限码、允许角色和只读属性;公共 Agent 工具拒绝写操作。执行前检查当前 意图白名单、角色、权限和参数,执行过程有超时,结果只保留脱敏摘要,并生成工具来源引用和审计记录。

投顾适当性校验必须使用公共 SuitabilityService(或其 suitability_tool_handler 工具入口), 不得在业务 Agent 中复制 C1-C5/R1-R5 匹配规则。输入包括客户风险等级、产品风险等级、专业投资者 标记、风险揭示和测评有效期;输出为不可变的 allowed、reason_code、required_disclosure、 requires_confirmation、requires_recording。客户风险等级低于产品、测评已过期时必须拒绝;专业 投资者也不能绕过审计。通过和拒绝决定都会写入 interaction_audit,服务只读,不修改交易、产品或 客户风险资料。业务 Agent 如需校验,应注册只读工具并通过 self.call_tool(...) 调用。

接口和日志只保存 secret_ref,不保存模型 API 明文密钥。

10. 配置管理

管理接口位于 /api/v1/admin/**,需要 JWT、管理角色和对应权限。写操作必须同时提供:

  • Idempotency-Key;
  • 更新时的 If-Match;
  • 合法的发布状态转换。

配置发布流程:

draft → pending_review → approved → active
                         ↘ rejected
active → superseded / rollback release

配置发布的"审核"节点不可跳过(草稿必须先提交审核);单管理员部署下创建人可以审核自己创建的版本, 不再要求复核人必须是另一个身份,reviewer_id 如实记录审核人。工具白名单只能缩小代码中 AgentDefinition.allowed_tools 的范围,不能通过数据库配置扩大权限。

11. 统一错误处理

常见错误:

HTTP 错误码 含义
422 AGENT_INPUT_INVALID 请求内容不满足输入约束(含字段校验失败,字段级原因见 error.field_errors)
401 AUTHENTICATION_REQUIRED Token 缺失或无效
403 AGENT_PERMISSION_DENIED 角色、入口或数据范围不允许
404 SESSION_NOT_FOUND / SESSION_NOT_ACCESSIBLE / AGENT_TYPE_NOT_FOUND / RUN_NOT_FOUND 资源不存在或不可见;非会话资源按同一"资源隐藏"语义复用 SESSION_NOT_FOUND
409 IDEMPOTENCY_CONFLICT / RESOURCE_VERSION_CONFLICT / RUN_NOT_CANCELLABLE / RUN_CANCELLED 幂等、版本或运行状态冲突
429 RATE_LIMITED 频率、并发或配额限制
503 DEPENDENCY_UNAVAILABLE 必需依赖不可用
504 UPSTREAM_TIMEOUT 上游超过时间预算
500 AGENT_INTERNAL_ERROR 未分类内部错误

变更记录(2026-09-11):本表原先使用 VALIDATION_ERROR、UNAUTHORIZED、FORBIDDEN、 RESOURCE_NOT_FOUND、RECOVERABLE_ERROR 等未在权威文档中定义的码,已按 docs/05-接口文档.md §3.5 / §3.6 对齐。错误码以 docs/05-接口文档.md 为唯一权威源。

错误响应不会返回 SQL、堆栈、模型原文、供应商响应或密钥。

12. 开发完成前的验收命令

.\.venv\Scripts\python.exe -m pytest -q -p no:cacheprovider
.\.venv\Scripts\python.exe -m ruff check app tests tools alembic
.\.venv\Scripts\python.exe -m mypy app
.\.venv\Scripts\python.exe tools\check_authoritative_docs.py
.\.venv\Scripts\python.exe tools\audit_schema.py

当前全量测试基线(2026-09-11 实测):1 failed, 779 passed, 2 skipped;唯一失败 tests/unit/repository/test_fund_readonly_contract.py 是底座既有缺陷,与本文档所述接入流程无关。

新增业务 Agent 至少补充:

  1. 每个支持意图的正常、低置信和异常用例;
  2. 未授权角色、入口、工具和客户范围用例;
  3. 配置、记忆、合规、SSE 和最终持久化回归;
  4. 业务边界用例,例如 Agent 不得代客下单、审核方案或处置风险预警。

13. 当前边界

本底座负责公共架构、鉴权、工厂、运行调度、持久化、记忆基础、关系服务、配置、模型路由和公共接口。 客服、投顾、风控和运营 Agent 的具体意图、提示词、工具实现和业务状态机由业务组员负责,但必须通过本文规定的工厂和公共执行流程接入。

当前系统业务交易范围是场内基金模拟交易。场外运营数据必须使用独立表和独立接口,不得写入场内交易表。

14. 公共只读工具当前状态

get_agent_factory() 向 ToolRegistry 注册了 4 个公共只读工具(app/service/agent/bootstrap.py 约 L155-192), 全部经 ToolRegistry、ToolExecutor 和统一工厂注入,业务 Agent 只能通过 self.call_tool(...) 使用:

工具名 必需权限 允许角色 只读 说明
check_suitability suitability:read customer、advisor、operator、admin 是 C1-C5/R1-R5 适当性匹配;测评过期/缺失拒绝;通过和拒绝都写 interaction_audit
query_fund_quote fund:quote:read customer、advisor、operator、risk_operator、admin 是 场内基金行情;超时 15s;不得改写为成交、委托或持仓语义
query_knowledge knowledge:query customer、operator、advisor、risk_operator、admin 是 知识检索(RAG);集合名由服务端按意图映射;超时 8s
query_customer_profile memory:read:self(查他人为 memory:read:customer) customer、advisor、operator、risk_operator、admin 是 客户画像白名单投影,不返回 PII;越范围按"不存在"处理

工具白名单是两段式:代码里的 AgentDefinition.allowed_tools 是上限;实际可用范围还要与当前 active 的 config_release 中 namespace=agent_tools、config_key=<agent_type>:<intent> 发布的工具白名单 取交集。缺发布配置时交集为空、工具失败关闭;发布配置只能缩小、不能放大代码声明的范围。

以行情工具为例,业务 Agent 必须同时在 AgentDefinition.allowed_tools 和发布配置的 agent_tools/<agent_type>:fund_quote({"allowed_tools": ["query_fund_quote"]})中声明后才能使用。 4 个工具的接口面登记口径见《05-接口文档.md》§8.4「公共只读工具索引(Agent 面)」。 未注册业务 Agent 不能直接提交运行,底座也没有开放绕过 Agent 执行链的行情 HTTP 接口。