# 元数据职责分层优化 - 技术设计

## 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
}
```
