Skip to content

FA 一键诊断 知识库 Metadata 过滤限制与 Access Roles 方案

背景:本文记录 Dify Retrieve API 的 metadata_filtering_conditions 能力边界分析,以及 FA 一键诊断知识库在 category + access_roles 双层过滤需求下的方案选型决策。


1. 场景描述

知识库中有一篇文档:

  • category = "暖通"
  • access_roles = 希望 "超级管理员""运维工程师" 都能看到

用户角色(从用户身份解析得到):

用户拥有的角色 = ["运维工程师", "后勤主管"]

期望的过滤条件(伪代码表达):

(category == "暖通")
AND
(文档角色的集合  inner join  用户角色的集合  结果非空)

即:

(category == "暖通")
AND
(access_roles["超级管理员", "运维工程师"]  inner join  用户角色["运维工程师", "后勤主管"])

                                                  命中 "运维工程师" ✅

inner join 的结果只要有交集就说明该文档对该用户可见。


2. Dify API 能力边界

2.1 metadata_filtering_conditions 的 schema

根据 Dify 官方 API 文档(RetrievalModel schema),metadata_filtering_conditions 的结构如下:

json
{
  "logical_operator": "and" | "or",
  "conditions": [
    {
      "name": "字段名",
      "comparison_operator": "contains / == / in / ...",
      "value": "值"
    }
  ]
}

2.2 关键限制

不支持嵌套条件。每个 condition 只有三个字段:namecomparison_operatorvalue,没有子 conditions 字段,也没有内层 logical_operator

这意味着只能全体 AND全体 OR,无法表达:

(category == X) AND (role == A OR role == B OR role == C)

这种内外层逻辑混合的场景。

⚠️ 说明:以上对 Dify API 的理解基于 Context7 索引的官方文档,不保证为最新版本。开发同学需自行查阅最新 Dify API 文档确认当前版本是否仍为此限制,或已支持嵌套条件。参考链接:docs.dify.ai


3. 方案分析

3.1 方案 A:自建文档管理与元数据过滤(应用层方案)

思路:不在 Dify 层面做 metadata 过滤,自己搭建一套文档管理系统(管理后台),自行管理文档元数据、自行实现复杂的过滤逻辑。

优点

  • 过滤逻辑完全自主可控,嵌套 AND/OR 没有限制
  • 不依赖 Dify 的 API 能力

缺点

  • 需要开发管理后台,管理文档和元数据的 CRUD
  • 需要自行对接 Dify 的检索结果再做一次过滤(二次开发)
  • 维护成本高,与"在 Dify 里一站式搭建"的初衷矛盾

3.2 方案 B(选定方案):约束 access_roles 为单值

核心思想:不在 API 层面解决 OR 问题,而是在文档元数据设计层面规避。

约束说明
access_roles 必须是单值字符串不能是数组。一篇文档只能绑定一个角色
如果一个文档需要被多个角色看到复制文档,分别标不同角色
检索时用 in 操作符access_roles in [用户拥有的角色列表 + "public"]

选定理由:改动最小,不引入额外的系统依赖,不增加开发工作量。代价是文档复用需要复制,对于 FA 诊断知识库(设备手册为主,文档量不大)可以接受。


4. 方案 B 实现思路

4.1 元数据存储

yaml
# Dify 自定义字段
category: "暖通"              # string,精确匹配
access_roles: "运维工程师"     # string,单值(不能是数组)

如果需要同时被 超级管理员运维工程师 看到,就导入两条文档:

文档categoryaccess_roles
暖通空调操作手册 v1暖通运维工程师
暖通空调操作手册 v2暖通超级管理员

4.2 检索时的过滤条件构造

输入:用户角色列表 ["运维工程师", "后勤主管"]

构造逻辑

json
{
  "logical_operator": "and",
  "conditions": [
    {
      "name": "category",
      "comparison_operator": "==",
      "value": "暖通"
    },
    {
      "name": "access_roles",
      "comparison_operator": "in",
      "value": ["运维工程师", "后勤主管", "public"]
    }
  ]
}

注意 "public" 始终追加到 in 列表中,确保没有角色限制的公开文档也能被检索到。

4.3 完整的数据流

用户角色: ["运维工程师", "后勤主管"]


Build Filter Node (Python)
  - 入参: query, user_roles, category
  - 逻辑:
    1. 解析 user_roles → ["运维工程师", "后勤主管"]
    2. 追加 "public" → ["运维工程师", "后勤主管", "public"]
    3. 构造 AND 条件:
       - category == 入参category
       - access_roles in ["运维工程师", "后勤主管", "public"]
  - 输出: retrieve_body (含 metadata_filtering_conditions)


Dataset Retrieve (HTTP Request)
  - POST /datasets/{id}/retrieve
  - Body: retrieve_body


返回结果: 只有 category 匹配 且 角色匹配的文档

4.4 注意事项

  • 文档准入时机:文档上传/导入知识库时,access_roles 字段由标注流程(LLM 自动标注 + 人工审核)填写,严格控制单值约束
  • public 角色:始终追加到 in 列表末尾,确保公开文档不被遗漏
  • 用户角色解析:由上游流程(工单打标/用户身份解析)产出,传入该工作流节点

5. 补充说明:public 角色设计

public 是一个特殊的角色标记:

文档侧:如果一篇文档对所有人可见,设置 access_roles = "public"。该文档会出现在所有用户的检索结果中(无论用户拥有什么角色)。

检索侧public 始终被追加到 in 的 value 列表中,作为保底匹配。即使某个用户没有任何已知角色,["public"] 也能检索到公开文档。

这样可以区分三类文档:

access_roles可见范围
"public"所有人
"运维工程师"仅运维工程师
"超级管理员"仅超级管理员

Released under the Private License.