Files
group_fqcd_jr/docs/客服Agent接入底座扩展说明_v1.md
T
张胜宇 9aaacc242f chore: 清理违反底座规则的死代码并修正接口文档编号
- 删除生产死代码 app/service/knowledge_tool_service.py 与
  app/infrastructure/milvus_knowledge_adapter.py:后者硬编码 Milvus 字段名,
  违反 AGENTS.md §E,且仅被前者引用;生产检索链路实际走
  knowledge_search_tool -> KnowledgeSearchService -> knowledge_schema 运行时探测。
- 删除上述两模块的单测,以及依赖 legacy 位置参数构造的
  tests/unit/service/test_knowledge_retrieval.py。
- app/service/knowledge_retrieval_service.py 整文件回退底座版本,
  移除 legacy 双构造与重复检索实现。
- docs/05-接口文档.md:客服画像候选改登记为 §8.5,恢复 §8.2 解析知识引用;
  既有 §8.1-§8.4 编号全部保持,修复此前出现两个 8.3 的问题。
- app/model/profile.py:current_customer_id 改为普通可空列映射,与
  alembic/baseline_generated.sql 及真实库一致;原 Computed 声明会让 ORM 把该列
  从 INSERT 中排除,与「必须显式写入」的实际 schema 不符。
- 新增 docs/客服Agent接入底座扩展说明_v1.md,供集成分支评审逐项确认。

验证:pytest tests/unit tests/contract -> 1275 passed, 2 skipped, 0 failed;
ruff check app tests tools alembic 通过;mypy app 通过(244 个源文件)。
2026-09-12 11:15:24 +08:00

155 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 客服 Agent 接入底座扩展说明(v1)
> 用途:提交 `qyqy_develop`(集成分支)评审时使用。
> 本文档逐项列出「客服 Agent + RAG」能力为接入底座而**触碰的既有文件**、改动性质、合规依据,
> 以及**明确未改动**的底座边界,便于架构师逐条确认。
>
> 依据:`AGENTS.md`、`docs/00`、`docs/01`、`docs/05`、`docs/09`、`docs/14`、`docs/20`。
---
## 一、总体口径
- 本次接入遵循 `AGENTS.md` 规则 7:客服 Agent 继承公共 `BaseAgent`、由 `AgentFactory` 创建,
**不绕过**公共鉴权、记忆、模型路由、工具、合规、审计与事件流程。
- 数据库侧**未做任何基线改动**:没有新增/重命名/删除表,没有改字段类型、可空性或业务含义,
没有新增迁移脚本。
- 底座中所有「既有 Agent 默认行为」均通过**带默认值的声明位**扩展,默认值等于改动前行为,
其他 Agent(`FundQueryDemoAgent`、`RiskAgent`、`PlatformProbeAgent`)路径不受影响。
---
## 二、A 类:纯新增,不影响既有代码
| 文件 | 说明 |
|---|---|
| `app/core/conversation_privacy.py` | 客服会话落库前的凭据最小化(密码/验证码/证件号/银行卡/手机号 → 占位符) |
| `app/api/controllers/visitor_tokens.py`、`app/api/schemas/visitor_tokens.py` | 访客短时令牌签发接口 |
| `app/service/agent/customer_service_routing.py` | 客服确定性意图路由(安全/账户/人工/合规/闲聊/公开知识) |
| `app/service/customer_service_session_memory_service.py` | 客服 Redis 短期会话记忆(30 分钟滑动 TTL、24 小时绝对上限、16 条/约 4096 Token 截断) |
| `app/service/customer_service_handover_context.py`、`customer_service_handover_admin_service.py` | 转人工上下文构造与管理员只读工单服务 |
| `app/service/customer_profile_candidate_service.py` | 画像候选(Phase 2,异步、需用户确认 + 管理员审核) |
| `app/service/knowledge_authority.py`、`knowledge_config.py`、`knowledge_publication_service.py` | 知识权威回查、运行期配置、受控发布工具所依赖的服务 |
| `app/infrastructure/milvus_profile_projection.py`、`neo4j_profile_projection.py` | 画像投影适配器(异步派生写入) |
| `app/worker/memory_sync_outbox_worker.py`、`customer_profile_candidate_worker.py` | 画像投影 Outbox 消费者、候选画像 Worker |
| `app/static/index.html` | 客服联调测试页(`/customer-service-test`) |
| `tools/publish_customer_service_knowledge.py`、`verify_customer_service_phase1.py`、`knowledge_import_preflight.py` | 受控发布、只读门禁、导入预检 |
以上均为新增文件,不改变任何既有模块行为。
---
## 三、B 类:带默认值、默认行为不变的扩展(对齐底座扩展点,建议直接确认)
| 文件 | 改动 | 默认值是否等于原行为 |
|---|---|---|
| `app/core/contracts.py` | `AgentDefinition` 新增 `requires_model_intent_classification=True`、`recalls_customer_memory=True`;`AgentRequestMetadata` 新增 `chitchat_streak`/`clarification_round`/`session_context`;`CoreResult` 新增 `clarification_required` | 是 |
| `app/service/agent/base.py` | `recall_memory()` 在声明关闭或访客时置空;`classify_intent()` 在声明关闭时返回 `None` | 是 |
| `app/service/agent/factory.py` | 仅在 `requires_model_intent_classification` 为真时绑定意图分类器 | 是 |
| `app/core/config.py` | 新增 `visitor_token_ttl_seconds`、`milvus_local_uri`、`knowledge_embedding_endpoint_code`、`knowledge_embedding_timeout_ms` 与 `resolved_milvus_uri` 属性 | 是(新增项均有默认值) |
| `app/core/knowledge_contracts.py` | 新增 `ALLOWED_KNOWLEDGE_COLLECTIONS` 别名(与 `ALLOWED_COLLECTIONS` 恒等);`KnowledgeHit.answer` 可选字段;`KnowledgeSearchResult.hits` 补默认值 | 是(三个 `fin_*_collection` 白名单内容未变) |
| `app/model/knowledge.py` | 新增 `FinKnowledgeMeta = KnowledgeMeta` 别名 | 是 |
| `app/model/memory.py` | `__all__` 与 `ProfileSnapshot` 兼容转出 | 是 |
| `app/service/agent/bootstrap.py` | 新增注册 `query_knowledge` 工具,`handler` 复用 `knowledge_search_tool`(与 `search_knowledge` 同一实现),`required_permission="knowledge:query"` | 是(不新增能力面,实际范围仍由 `config_release` 白名单收口) |
| `app/service/memory_service.py` | `upsert_memory()` 新增 `status` 参数(`active`/`candidate`),`candidate` 不覆盖现有有效记忆 | 是(默认 `active`) |
| `app/service/public_platform_service.py` | 转人工工单 `reason_detail` 落库前做凭据脱敏 | 是(安全收紧) |
| `app/main.py` | 注册 `visitor_tokens_router`、挂载 `/customer-service-test` 静态页 | 是(纯新增装配) |
| `pyproject.toml`、`requirements.txt` | 新增 `milvus-lite>=3.2,<4` | 是(可选本地开发依赖) |
| `.gitignore` | 新增 `.worktrees/`、`data/milvus/` | 是 |
---
## 四、C 类:需要架构师确认的底座语义扩展
### C1. 访客身份(`app/api/dependencies/auth.py` + `app/core/security.py`)
- `security.py`:`JwtAuthenticator.authenticate()` 新增分支——当令牌含 `visitor: true` 声明时,
返回 `roles=("visitor",)`、`permissions=("agent:run", "knowledge:query")`、`data_scope="public"`。
- `auth.py`:`build_request_context()` 对 `visitor` 角色跳过 `IdentityService().resolve()`
(访客不在 `sys_user` 中,无法解析身份)。
**合规说明:**
- `visitor` 角色只可能出自用项目 RS256 私钥签名、且显式带 `visitor: true` 的令牌,普通用户令牌
无法携带该角色;
- 访客权限固定为 `agent:run` + `knowledge:query`,`data_scope="public"`,
**不包含任何账户、持仓、订单、银行卡、投诉进度或画像权限**;
- 该分支只影响访客令牌,非访客令牌仍走完整 `IdentityService` 身份解析。
**请求确认:** 是否接受在底座统一鉴权链路中增加这一受控访客身份;如需要,我们可补一份
`docs/` 说明或在 `docs/05` 登记该令牌类型。
### C2. 客服 Agent 的角色与安全路由(`app/service/agent/implementations/customer_service.py`)
- `allowed_roles` 由 `("customer",)` 扩为 `("visitor", "customer")`;
- `handle()` 首部插入确定性安全路由 `route_message()`(安全提示、合规拒答、账户入口、人工转接优先于检索);
- 新增 `recalls_customer_memory=False`(客服不隐式召回长期/画像记忆);
- 新增 `chitchat_streak == 4` 的一次性业务引导;
- 业务口径:`COMPANY` 由 `南方科技` 改为 `奶龙基金责任有限公司`。
**请求确认:** 角色扩容与行为契约变更(安全路由前置、闲聊计数);品牌名属业务口径调整。
### C3. 画像快照 ORM 声明清理与 `current_customer_id` 映射更正
三处相关改动(**属于修既有缺陷,不是改设计**):
1. `app/model/risk_questionnaire.py`:原有内联 `ProfileSnapshot` 声明与 `app/model/profile.py`
的同表声明**重复**,两个 declarative 类映射同一张表会让 SQLAlchemy 直接拒绝导入。
现改为从 `app.model.profile` 转出规范类,保留对既有调用方的兼容导出。
2. `app/model/profile.py`:把 `current_customer_id` 映射为**普通可空列**。
3. 依据:该列在**真实库中是普通可空列**(`EXTRA=''`、`GENERATION_EXPRESSION=''`,已实测),
`alembic/baseline_generated.sql` 的建表语句也没有 `GENERATED` 子句,
`tools/seed_profile_demo.py` 明确写着「**不是生成列**,必须显式写入」。
因此**不能**声明 `Computed(...)`——那会让 SQLAlchemy 把它从 INSERT 中排除,反而永远写不进去。
4. **附带发现(建议底座侧修正):** `docs/00-新数据库基线设计.md` 第 783 行把该列描述为
「生成列」,与 `docs/02`/`alembic` 基线 DDL 及真实库不一致。本次未改动 `docs/00`,
仅在此登记,请底座侧决定以哪一侧为准。
**请求确认:** 上述三点是否按「修缺陷」接受。
### C4. Milvus 双地址与部署约束(`app/core/config.py` + `app/service/health_service.py`)
- 新增 `milvus_local_uri`(默认空串)与 `resolved_milvus_uri = milvus_local_uri or milvus_uri`;
- `health_service.py` 的健康检查改用 `resolved_milvus_uri`。
**约束(请一并确认并写入环境规范):**
- 团队/生产环境的 `.env` **必须留空 `MILVUS_LOCAL_URI`**,否则健康检查与部分检索链路会指向
本地 Milvus Lite 文件,出现「健康检查正常、实际查的是另一个库」的隐性偏差;
- `.env` 中默认值为空串,**默认行为与改动前完全一致**。
---
## 五、D 类:本次已从 ZSY 侧移除、不进入集成分支的内容
为避免把违反底座规则的内容带进集成分支,以下内容已在本分支删除:
| 内容 | 原因 |
|---|---|
| `app/service/knowledge_tool_service.py` | **生产死代码**(全仓唯一引用是它自己的单测)。生产路径是 `bootstrap.get_knowledge_search_service()` → `KnowledgeSearchService` |
| `app/infrastructure/milvus_knowledge_adapter.py` | **违反 `AGENTS.md` §E**:硬编码 `knowledge_id`/`snippet`/`tags` 等 Milvus 字段名,会打挂字段名不同的环境。且仅被上面那个死模块引用 |
| `tests/unit/service/test_knowledge_tool_service.py`、`tests/unit/infrastructure/test_milvus_knowledge_adapter.py` | 上述两模块的单测 |
| `tests/unit/service/test_knowledge_retrieval.py` | 依赖已移除的 legacy 位置参数构造 |
| `app/service/knowledge_retrieval_service.py` 的 legacy 双构造与 `_legacy_search` | 已整文件回退为底座版本,消除「同一服务两套构造语义 + 两套检索实现」的技术债 |
检索仍统一走底座既有链路:`ToolExecutor` → `knowledge_search_tool` → `KnowledgeSearchService`
→ `app/core/knowledge_schema.py` 的 `detect_schema()` **运行时字段探测**,无任何硬编码字段名。
---
## 六、E 类:明确未改动的底座边界
- `docs/00`、`docs/02` 的表结构与字段语义:**未改动**;
- `alembic/` 迁移脚本:**未改动**;
- Milvus 字段名:**无硬编码**,全部走运行时探测;
- `docs/05-接口文档.md` 的既有章节编号:**§8.1–§8.4 全部保持原编号**,客服二期新增的
「客服画像候选」登记为 **§8.5**(不占用既有号段),另有 §9.7 追加;原有交叉引用
「§8.3 知识库管理三端点」「§8.4 公共只读工具索引」仍然成立;
- 其他业务 Agent、场外/推广/风控域:**未改动**行为契约。
---
## 七、F 类:需要业务方在环境侧确认的既有约束(非本次引入)
1. `config_release` 是**环境数据、不随代码合并**:换环境需重新发布客服工具白名单。
2. Milvus 集合 schema 因环境而异,检索层已运行时探测,**任何新增代码都不得硬编码字段名**。
3. 本地 Milvus Lite 仅用于本地开发;团队环境应使用受管 Milvus 并留空 `MILVUS_LOCAL_URI`。