ai-robot-core/spec/metadata-role-separation/design.md

616 lines
34 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.

# 元数据职责分层优化 - 技术设计
## 1. 系统架构
### 1.1 整体架构
```
┌─────────────────────────────────────────────────────────────────────────┐
│ 管理端 (ai-service-admin) │
├─────────────────────────────────────────────────────────────────────────┤
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ 元数据字段配置 │ │ 槽位定义配置 │ │ 按角色过滤视图 │ │
│ │ (field_roles) │ │ (SlotDefinition) │ │ (RoleFilter) │ │
│ └────────┬────────┘ └────────┬────────┘ └────────┬────────┘ │
└───────────┼────────────────────┼────────────────────┼───────────────────┘
│ │ │
▼ ▼ ▼
┌─────────────────────────────────────────────────────────────────────────┐
│ 后端服务 (ai-service) │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ API Layer │ │
│ │ ┌──────────────────────┐ ┌──────────────────────┐ │ │
│ │ │ MetadataFieldAPI │ │ SlotDefinitionAPI │ │ │
│ │ │ - CRUD │ │ - CRUD │ │ │
│ │ │ - getByRole │ │ - getByRole │ │ │
│ │ └──────────┬───────────┘ └──────────┬───────────┘ │ │
│ └─────────────┼────────────────────────┼──────────────────────────┘ │
│ │ │ │
│ ┌─────────────┼────────────────────────┼──────────────────────────┐ │
│ │ │ Service Layer │ │ │
│ │ ┌──────────▼───────────┐ ┌────────▼──────────┐ │ │
│ │ │ MetadataFieldService │ │ SlotDefinitionSvc │ │ │
│ │ │ - create/update │ │ - create/update │ │ │
│ │ │ - getByRole │ │ - getByRole │ │ │
│ │ │ - validateRoles │ │ - linkToField │ │ │
│ │ └──────────┬───────────┘ └────────┬──────────┘ │ │
│ └─────────────┼────────────────────────┼──────────────────────────┘ │
│ │ │ │
│ ┌─────────────┼────────────────────────┼──────────────────────────┐ │
│ │ │ Tool Integration │ │ │
│ │ ┌──────────▼────────────────────────▼──────────┐ │ │
│ │ │ RoleBasedFieldProvider │ │ │
│ │ │ - getFieldsByRole(role) │ │ │
│ │ │ - getSlotDefinitionsByRole(role) │ │ │
│ │ └──────────────────────┬───────────────────────┘ │ │
│ └─────────────────────────┼──────────────────────────────────────┘ │
│ │ │
│ ┌─────────────────────────┼──────────────────────────────────────┐ │
│ │ Tool Consumers │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │
│ │ │kb_search_ │ │memory_recall │ │intent_hint/ │ │ │
│ │ │dynamic │ │ │ │high_risk_ │ │ │
│ │ │[resource_ │ │[slot] │ │check │ │ │
│ │ │filter] │ │ │ │[routing_ │ │ │
│ │ └──────────────┘ └──────────────┘ │signal] │ │ │
│ │ └──────────────┘ │ │
│ │ ┌──────────────┐ │ │
│ │ │template_ │ │ │
│ │ │engine │ │ │
│ │ │[prompt_var] │ │ │
│ │ └──────────────┘ │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
│ │ │
▼ ▼ ▼
┌─────────────────────────────────────────────────────────────────────────┐
│ 数据层 │
├─────────────────────────────────────────────────────────────────────────┤
│ ┌─────────────────────┐ ┌─────────────────────┐ │
│ │ metadata_field_ │ │ slot_definitions │ │
│ │ definitions │ │ │ │
│ │ - id │ │ - id │ │
│ │ - field_key │ │ - slot_key │ │
│ │ - field_roles[] │ │ - type │ │
│ │ - ... │ │ - linked_field_id │ │
│ └─────────────────────┘ └─────────────────────┘ │
│ │
│ ┌─────────────────────┐ ┌─────────────────────┐ │
│ │ Redis Cache │ │ PostgreSQL │ │
│ │ - field_roles:by_tenant │ - 持久化存储 │ │
│ └─────────────────────┘ └─────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
```
### 1.2 模块依赖关系
```
metadata-role-separation
├── metadata-governance (依赖)
│ └── 元数据字段定义基础能力
├── intent-driven-mid-platform (依赖)
│ └── 中台运行时工具链
└── ai-service-admin (依赖)
└── 管理端配置界面
```
---
## 2. 数据模型设计
### 2.1 MetadataFieldDefinition 扩展
在现有 `metadata_field_definitions` 表基础上新增字段:
```sql
-- 新增字段field_roles
ALTER TABLE metadata_field_definitions
ADD COLUMN field_roles JSONB DEFAULT '[]'::jsonb;
-- 创建 GIN 索引支持按角色查询
CREATE INDEX idx_metadata_field_definitions_roles
ON metadata_field_definitions USING GIN (field_roles);
-- 注释
COMMENT ON COLUMN metadata_field_definitions.field_roles IS '字段角色列表resource_filter, slot, prompt_var, routing_signal';
```
**字段定义**
| 字段名 | 类型 | 必填 | 默认值 | 说明 |
|-------|------|-----|-------|------|
| `field_roles` | JSONB | 否 | `[]` | 字段角色列表,存储字符串数组 |
**角色枚举值**
```python
class FieldRole(str, Enum):
RESOURCE_FILTER = "resource_filter" # 资源过滤
SLOT = "slot" # 运行时槽位
PROMPT_VAR = "prompt_var" # 提示词变量
ROUTING_SIGNAL = "routing_signal" # 路由信号
```
### 2.2 SlotDefinition 新增表
```sql
-- 槽位定义表
CREATE TABLE slot_definitions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL,
slot_key VARCHAR(100) NOT NULL,
type VARCHAR(20) NOT NULL,
required BOOLEAN NOT NULL DEFAULT FALSE,
extract_strategy VARCHAR(20),
validation_rule TEXT,
ask_back_prompt TEXT,
default_value JSONB,
linked_field_id UUID REFERENCES metadata_field_definitions(id),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT uk_slot_definitions_tenant_key UNIQUE (tenant_id, slot_key),
CONSTRAINT chk_slot_definitions_type CHECK (type IN ('string', 'number', 'boolean', 'enum', 'array_enum')),
CONSTRAINT chk_slot_definitions_extract_strategy CHECK (extract_strategy IS NULL OR extract_strategy IN ('rule', 'llm', 'user_input'))
);
-- 索引
CREATE INDEX idx_slot_definitions_tenant ON slot_definitions(tenant_id);
CREATE INDEX idx_slot_definitions_linked_field ON slot_definitions(linked_field_id);
-- 注释
COMMENT ON TABLE slot_definitions IS '槽位定义表';
COMMENT ON COLUMN slot_definitions.slot_key IS '槽位键名,可与元数据字段 field_key 关联';
COMMENT ON COLUMN slot_definitions.linked_field_id IS '关联的元数据字段 ID';
```
### 2.3 ER 图
```
┌─────────────────────────────┐ ┌─────────────────────────────┐
│ metadata_field_definitions │ │ slot_definitions │
├─────────────────────────────┤ ├─────────────────────────────┤
│ id (PK) │◄──────│ linked_field_id (FK) │
│ tenant_id │ │ id (PK) │
│ field_key │ │ tenant_id │
│ label │ │ slot_key │
│ type │ │ type │
│ required │ │ required │
│ options │ │ extract_strategy │
│ default_value │ │ validation_rule │
│ scope │ │ ask_back_prompt │
│ is_filterable │ │ default_value │
│ is_rank_feature │ │ created_at │
│ status │ │ updated_at │
│ field_roles ◀── NEW │ │ │
│ version │ │ │
│ created_at │ │ │
│ updated_at │ │ │
└─────────────────────────────┘ └─────────────────────────────┘
```
---
## 3. 核心流程设计
### 3.1 字段职责配置流程
```
┌─────────────────────────────────────────────────────────────────────────┐
│ 管理端配置流程 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 1. 管理员进入元数据字段配置页面 │
│ │ │
│ ▼ │
│ 2. 创建/编辑字段定义 │
│ │ │
│ ├── 填写基本信息 (field_key, label, type, etc.) │
│ │ │
│ ├── 选择字段角色 (field_roles 多选) │
│ │ ├── [ ] resource_filter - 资源过滤 │
│ │ ├── [ ] slot - 运行时槽位 │
│ │ ├── [ ] prompt_var - 提示词变量 │
│ │ └── [ ] routing_signal - 路由信号 │
│ │ │
│ ▼ │
│ 3. 后端校验 │
│ │ │
│ ├── field_key 格式校验 (小写字母数字下划线) │
│ ├── field_roles 枚举值校验 │
│ └── 租户内唯一性校验 │
│ │ │
│ ▼ │
│ 4. 保存到数据库 │
│ │ │
│ └── field_roles 以 JSONB 格式存储 │
│ │
└─────────────────────────────────────────────────────────────────────────┘
```
### 3.2 按角色查询流程
```
┌─────────────────────────────────────────────────────────────────────────┐
│ 按角色查询流程 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 工具/模块请求字段 │
│ │ │
│ ▼ │
│ RoleBasedFieldProvider.getFieldsByRole(role, tenant_id) │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ SELECT * FROM metadata_field_definitions │ │
│ │ WHERE tenant_id = :tenant_id │ │
│ │ AND status = 'active' │ │
│ │ AND field_roles ? :role -- JSONB contains 查询 │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 返回字段定义列表 │
│ │ │
│ ▼ │
│ 工具按需消费字段 │
│ │
└─────────────────────────────────────────────────────────────────────────┘
```
### 3.3 工具协同改造流程
```
┌─────────────────────────────────────────────────────────────────────────┐
│ 工具协同改造流程 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────────────────────────────────────────────────────┐ │
│ │ kb_search_dynamic [AC-MRS-11] │ │
│ │ │ │
│ │ 改造前: │ │
│ │ filterable_fields = get_filterable_fields(tenant_id) │ │
│ │ WHERE is_filterable = true │ │
│ │ │ │
│ │ 改造后: │ │
│ │ filterable_fields = get_fields_by_role( │ │
│ │ tenant_id, role='resource_filter' │ │
│ │ ) │ │
│ └──────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────────────┐ │
│ │ memory_recall [AC-MRS-12] │ │
│ │ │ │
│ │ 改造前: │ │
│ │ slots = context.get('slots', {}) # 无角色过滤 │ │
│ │ │ │
│ │ 改造后: │ │
│ │ slot_fields = get_fields_by_role( │ │
│ │ tenant_id, role='slot' │ │
│ │ ) │ │
│ │ slots = {k: v for k, v in context.items() │ │
│ │ if k in slot_fields} │ │
│ └──────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────────────┐ │
│ │ intent_hint / high_risk_check [AC-MRS-13] │ │
│ │ │ │
│ │ 改造前: │ │
│ │ routing_fields = all_metadata_fields # 全量字段 │ │
│ │ │ │
│ │ 改造后: │ │
│ │ routing_fields = get_fields_by_role( │ │
│ │ tenant_id, role='routing_signal' │ │
│ │ ) │ │
│ └──────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────────────┐ │
│ │ template_engine [AC-MRS-14] │ │
│ │ │ │
│ │ 改造前: │ │
│ │ variables = extract_all_variables(template) │ │
│ │ values = get_all_metadata_values(context) │ │
│ │ │ │
│ │ 改造后: │ │
│ │ prompt_var_fields = get_fields_by_role( │ │
│ │ tenant_id, role='prompt_var' │ │
│ │ ) │ │
│ │ values = {k: v for k, v in context.items() │ │
│ │ if k in prompt_var_fields} │ │
│ └──────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
```
---
## 4. 接口设计
### 4.1 后端 API 接口
| 接口 | 方法 | 说明 | AC |
|------|------|------|-----|
| `/admin/metadata-schemas` | GET | 获取字段列表(支持 role 过滤) | AC-MRS-06 |
| `/admin/metadata-schemas` | POST | 创建字段(含 field_roles | AC-MRS-01,02,03 |
| `/admin/metadata-schemas/{id}` | PUT | 更新字段(含 field_roles | AC-MRS-01 |
| `/admin/metadata-schemas/{id}` | DELETE | 删除字段(无需兼容) | AC-MRS-16 |
| `/admin/metadata-schemas/by-role` | GET | 按角色查询字段 | AC-MRS-04,05 |
| `/admin/slot-definitions` | GET | 获取槽位定义列表 | - |
| `/admin/slot-definitions` | POST | 创建槽位定义 | AC-MRS-07,08 |
| `/admin/slot-definitions/{id}` | PUT | 更新槽位定义 | - |
| `/admin/slot-definitions/{id}` | DELETE | 删除槽位定义 | AC-MRS-16 |
| `/mid/slots/by-role` | GET | 运行时按角色获取槽位 | AC-MRS-10 |
| `/mid/slots/{slot_key}` | GET | 获取运行时槽位值 | AC-MRS-09 |
### 4.2 内部服务接口
```python
class RoleBasedFieldProvider:
"""基于角色的字段提供者"""
async def get_fields_by_role(
self,
tenant_id: str,
role: FieldRole,
include_deprecated: bool = False,
) -> list[MetadataFieldDefinition]:
"""
按角色获取字段定义
Args:
tenant_id: 租户ID
role: 字段角色
include_deprecated: 是否包含已废弃字段
Returns:
字段定义列表
"""
async def get_slot_definitions_by_role(
self,
tenant_id: str,
role: FieldRole,
) -> list[SlotDefinition]:
"""
按角色获取槽位定义(包含关联字段信息)
"""
```
---
## 5. 前端设计
### 5.1 元数据字段配置页面改造
**新增组件**`FieldRolesSelector.vue`
```vue
<template>
<div class="field-roles-selector">
<label>字段角色</label>
<el-checkbox-group v-model="selectedRoles">
<el-checkbox
v-for="role in availableRoles"
:key="role.value"
:label="role.value"
>
<span>{{ role.label }}</span>
<el-tooltip :content="role.description">
<el-icon><QuestionFilled /></el-icon>
</el-tooltip>
</el-checkbox>
</el-checkbox-group>
</div>
</template>
<script setup>
const availableRoles = [
{
value: 'resource_filter',
label: '资源过滤',
description: '用于 KB 文档检索时的元数据过滤'
},
{
value: 'slot',
label: '运行时槽位',
description: '对话流程中的结构化槽位,用于信息收集'
},
{
value: 'prompt_var',
label: '提示词变量',
description: '注入到 LLM Prompt 中的变量'
},
{
value: 'routing_signal',
label: '路由信号',
description: '用于意图路由和风险判断的信号'
},
];
</script>
```
### 5.2 按角色过滤视图
**新增过滤器**:在元数据字段列表页面增加角色过滤下拉框
```vue
<template>
<div class="filter-bar">
<el-select
v-model="selectedRole"
placeholder="按角色过滤"
clearable
@change="handleRoleFilter"
>
<el-option
v-for="role in availableRoles"
:key="role.value"
:label="role.label"
:value="role.value"
/>
</el-select>
</div>
</template>
```
---
## 6. 缓存策略
### 6.1 缓存设计
```python
class FieldRoleCache:
"""字段角色缓存"""
CACHE_KEY_PREFIX = "field_roles"
CACHE_TTL = 300 # 5分钟
async def get_fields_by_role(
self,
tenant_id: str,
role: FieldRole,
) -> list[MetadataFieldDefinition]:
cache_key = f"{self.CACHE_KEY_PREFIX}:{tenant_id}:{role}"
# 尝试从缓存获取
cached = await self._redis.get(cache_key)
if cached:
return self._deserialize(cached)
# 从数据库查询
fields = await self._db_query(tenant_id, role)
# 写入缓存
await self._redis.setex(
cache_key,
self.CACHE_TTL,
self._serialize(fields),
)
return fields
async def invalidate(self, tenant_id: str):
"""失效租户所有角色缓存"""
pattern = f"{self.CACHE_KEY_PREFIX}:{tenant_id}:*"
keys = await self._redis.keys(pattern)
if keys:
await self._redis.delete(*keys)
```
### 6.2 缓存失效策略
- 字段创建/更新/删除时失效该租户所有角色缓存
- 槽位定义变更时失效相关角色缓存
---
## 7. 异常处理
### 7.1 错误码定义
| 错误码 | 说明 | HTTP 状态码 |
|-------|------|------------|
| `INVALID_ROLE` | 无效的角色参数 | 400 |
| `FIELD_KEY_EXISTS` | field_key 已存在 | 409 |
| `SLOT_KEY_EXISTS` | slot_key 已存在 | 409 |
| `LINKED_FIELD_NOT_FOUND` | 关联字段不存在 | 404 |
### 7.2 异常处理示例
```python
class InvalidRoleError(Exception):
"""无效角色异常"""
def __init__(self, role: str):
self.role = role
self.valid_roles = [r.value for r in FieldRole]
super().__init__(
f"Invalid role '{role}'. Valid roles are: {', '.join(self.valid_roles)}"
)
```
---
## 8. 测试策略
### 8.1 单元测试
- `RoleBasedFieldProvider` 按角色查询测试
- `FieldRoleCache` 缓存读写测试
- 字段角色校验测试
### 8.2 集成测试
- API 端点测试CRUD + 按角色查询)
- 工具协同改造测试(验证各工具只消费对应角色字段)
### 8.3 契约测试
- OpenAPI Schema 校验
- 响应结构验证
---
## 9. 迁移与部署
### 9.1 数据库迁移
```sql
-- 1. 新增 field_roles 字段
ALTER TABLE metadata_field_definitions
ADD COLUMN field_roles JSONB DEFAULT '[]'::jsonb;
-- 2. 创建索引
CREATE INDEX idx_metadata_field_definitions_roles
ON metadata_field_definitions USING GIN (field_roles);
-- 3. 创建 slot_definitions 表
CREATE TABLE slot_definitions (
-- ... 见 2.2 节
);
-- 4. 初始化现有字段的默认角色(可选)
-- 根据 is_filterable 推断 resource_filter 角色
UPDATE metadata_field_definitions
SET field_roles = '["resource_filter"]'::jsonb
WHERE is_filterable = true AND status = 'active';
```
### 9.2 部署顺序
1. 执行数据库迁移脚本
2. 部署后端服务向后兼容field_roles 可为空)
3. 部署前端页面
4. 配置字段角色
---
## 10. 监控与观测
### 10.1 关键指标
| 指标名 | 说明 |
|-------|------|
| `field_role_query_count` | 按角色查询次数 |
| `field_role_query_latency` | 按角色查询延迟 |
| `field_role_cache_hit_rate` | 角色缓存命中率 |
| `tool_field_consumption` | 工具字段消费统计 |
### 10.2 日志字段
```json
{
"event": "field_role_query",
"tenant_id": "xxx",
"role": "resource_filter",
"field_count": 5,
"duration_ms": 12,
"cache_hit": true
}
```