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

616 lines
34 KiB
Markdown
Raw Permalink Normal View History

# 元数据职责分层优化 - 技术设计
## 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
}
```