AI PLAY玩法更新持续收录
← 回到更新流

2026-09-16 · 原文发布日

向量入库前先过同意登记的合规RAG索引流程

给做知识库向量化的团队加一道合规闸门的n8n模板:每晚读取Postgres中未索引的文档,逐条对照同意登记表检查用途授权、保留期限、撤回状态、画像异议和特殊类别标记,合格的才切块、用OpenAI生成向量写入pgvector表,并记录索引清单与合法依据;拒绝或暂缓的条目同样写明具体原因,次日仍会重新检查,有任何拒绝就向Slack发运行报告。判定规则是配置数据而非代码,向量库可换成Pinecone或Qdrant。

原文要点

原作者Melbin Francis发布免费n8n模板:每晚定时从Postgres读取未索引文档,对照同库的同意登记表逐条评估用途授权、保留期限、撤回状态、画像异议和特殊类别标记,合格才切块并用OpenAI text-embedding-3-small写入pgvector表。所有决定写入index_manifest清单,拒绝与暂缓注明具体理由,被拒绝条目保留在队列中次日重查,有拒绝就向Slack发分组汇总报告。判定规则是配置数据而非代码,向量库可换成Pinecone或Qdrant,模板不处理已入索引向量的删除。

这次做成什么

在本地n8n里搭一条带合规闸门的RAG索引进水线:先建同意登记表和清单表,用合成数据跑通“查同意→判定→向量化或拒绝→写清单→发报告”全链路,掌握把判定规则做成可改配置的做法。

准备什么

  • 一个可自建的n8n实例(桌面版或自托管均可),有Schedule Trigger、Postgres、OpenAI、Slack节点
  • 已启用pgvector扩展的PostgreSQL数据库(Neon、Supabase、RDS或自建)和一个有建表权限的账号
  • 一个OpenAI API密钥,且账号可调用text-embedding-3-small模型
  • 一个Slack工作区及OAuth2凭据,选定接收报告的频道
  • 已存在的同意登记数据(本流程只读取同意,不采集同意)

具体怎么做

  1. 建四张表并开启pgvector

    在Postgres里先执行CREATE EXTENSION vector,然后按下方SQL建kb_documents(含indexed_at列)、consent_register、index_manifest和向量表kb_vectors,向量维度按text-embedding-3-small的默认输出设为1536。

  2. 灌入合成测试数据

    把下方artifacts里的CSV分别导入kb_documents和consent_register,覆盖六种典型情形:授权有效、无个人信息、用途不符、同意过期、已撤回、登记缺字段,保证一次运行能同时触发通过、拒绝和暂缓。

  3. 搭触发与读取节点

    在n8n新建工作流,放Schedule Trigger设为每天凌晨运行;接两个Postgres节点:一个查SELECT * FROM kb_documents WHERE indexed_at IS NULL,另一个全量读consent_register,用Merge节点按subject_id把两份数据按文档逐条配对。

  4. 写判定规则节点

    加一个Code节点,把下方consent_rules.json作为规则常量读入,对每条配对结果输出decision(embedded/refused/held)、ground(如purpose_limitation、retention_expired)和lawful_basis。规则写在该节点顶部的配置区,新增拒绝理由只加一块判断,不改主逻辑。

  5. 分支处理合格与不合格

    用Switch节点按decision分三路:embedded路走文本切块→OpenAI Embeddings节点→Postgres写入kb_vectors并UPDATE kb_documents SET indexed_at=now();refused和held两路只往index_manifest插入决定、理由和当晚日期,不改indexed_at,使其明晚重查。

  6. 汇总并发送Slack报告

    末尾用Code节点把当晚所有manifest行聚合成report_text(嵌入数、拒绝数按ground分组计数、暂缓数),接IF节点判断拒绝或暂缓数大于0时,用Slack节点把report_text发到数据保护负责人频道。

可直接复制的材料

建表SQL(pgvector环境)

本站按原模板描述独立编写的示例建表语句,列名需与你实际schema对齐;向量维度1536对应text-embedding-3-small,换模型要同步改。

CREATE EXTENSION IF NOT EXISTS vector;
CREATE TABLE kb_documents (
  doc_id text PRIMARY KEY,
  subject_id text,
  title text,
  body text,
  indexed_at timestamptz
);
CREATE TABLE consent_register (
  subject_id text PRIMARY KEY,
  purposes text[],
  consent_withdrawn boolean DEFAULT false,
  profiling_objection boolean DEFAULT false,
  special_category boolean DEFAULT false,
  retention_until date,
  entry_complete boolean DEFAULT true
);
CREATE TABLE kb_vectors (
  id bigserial PRIMARY KEY,
  doc_id text,
  chunk_seq int,
  content text,
  embedding vector(1536),
  metadata jsonb
);
CREATE TABLE index_manifest (
  id bigserial PRIMARY KEY,
  run_date date,
  doc_id text,
  decision text,
  ground text,
  lawful_basis text
);

合成测试数据(两张表)

本站虚构的合成数据,仅用于隔离测试环境,不含任何真实个人信息;导入时注意Postgres数组字面量写法。

kb_documents:
doc_id,subject_id,title,body,indexed_at
D-01,S-OK,退款政策摘要,客户咨询退款时限的内部标准答复,
D-02,,公开价目表,官网同步的产品价格说明不含个人信息,
D-03,S-PURPOSE, newsletter订阅备注,用户订阅时留下的偏好备注,
D-04,S-EXPIRED,旧工单对话,两年前的售后沟通记录,
D-05,S-WITHDRAWN,历史聊天日志,客户此前同意训练用途的对话,
D-06,S-INCOMPLETE,字段不全的登记条目,一条登记信息缺失的客户备注,

consent_register:
subject_id,purposes,consent_withdrawn,profiling_objection,special_category,retention_until,entry_complete
S-OK,"{support,ai_training}",f,f,f,2028-12-31,t
S-PURPOSE,"{support,marketing}",f,f,f,2028-12-31,t
S-EXPIRED,"{support,ai_training}",f,f,f,2024-01-01,t
S-WITHDRAWN,"{support,ai_training}",t,f,f,2028-12-31,t
S-INCOMPLETE,"{support}",f,f,f,,f

判定规则配置(consent_rules.json)

本站按原模板“规则是数据不是代码”的思路独立设计的示例规则集;required_purpose的值必须与登记表里的用途字符串完全一致,暂缓要排在拒绝之前判断。

{
  "required_purpose": "ai_training",
  "grounds": [
    {"id": "no_personal", "when": "subject_id为空", "decision": "embedded", "lawful_basis": "not_personal"},
    {"id": "no_row", "when": "登记表无该subject_id", "decision": "refused", "ground": "no consent record"},
    {"id": "incomplete", "when": "entry_complete为false", "decision": "held", "ground": "incomplete record"},
    {"id": "withdrawn", "when": "consent_withdrawn为true", "decision": "refused", "ground": "consent withdrawn"},
    {"id": "retention", "when": "retention_until早于当前日期", "decision": "refused", "ground": "retention expired"},
    {"id": "profiling", "when": "profiling_objection为true", "decision": "refused", "ground": "objection to profiling"},
    {"id": "special", "when": "special_category为true", "decision": "refused", "ground": "special category"},
    {"id": "purpose", "when": "purposes不含required_purpose", "decision": "refused", "ground": "purpose limitation"},
    {"id": "ok", "when": "以上均未命中", "decision": "embedded", "lawful_basis": "consent: ai_training"}
  ]
}

输入与输出示例

本站虚构演示,非原作者运行结果;报告措辞为示意,可按自己的report_text模板调整。

输入

当晚队列有6条未索引文档:D-01主体S-OK同意ai_training且在保留期内;D-02无主体(公开价目表);D-03主体S-PURPOSE只同意support和marketing;D-04主体S-EXPIRED保留期止于2024-01-01;D-05主体S-WITHDRAWN已撤回;D-06主体S-INCOMPLETE登记字段不全。

对应输出

index_manifest当晚写入6行:D-01/embedded/consent: ai_training;D-02/embedded/not_personal;D-03/refused/purpose limitation;D-04/refused/retention expired;D-05/refused/consent withdrawn;D-06/held/incomplete record。Slack收到一条消息:「2 of 6 record(s) were embedded. 3 refused, 1 held for a human. Why: consent withdrawn: 1, incomplete record: 1, purpose limitation: 1, retention expired: 1」。仅D-01与D-02的indexed_at被更新。

做完检查这些

  • 运行一次后kb_vectors中只有D-01和D-02的向量行,且kb_documents里只有这两条indexed_at非空
  • index_manifest对当晚每条文档都有一行,refused和held行的ground列写明具体理由而不是空值
  • Slack频道收到按理由分组的汇总报告,拒绝理由出现具体名称如purpose limitation而非只有总数
  • 把S-PURPOSE的purposes数组补上ai_training后次日重跑,D-03转为embedded,证明拒绝条目留在队列中被重查

容易卡住的地方

  • 本闸门只挡新索引,不删除已入库向量:同意撤回发生在嵌入之后时,需要另建一条清除流程
  • required_purpose字符串与登记表用途写法不一致(大小写、连字符)会导致全部误判为purpose limitation
  • 向量表维度必须与所用嵌入模型输出一致,换模型时忘记改维度会直接插入失败
  • 暂缓与拒绝要区分开:登记字段不全时直接拒绝会静默丢掉大概率合规的记录,应held留人工
  • Slack频道在模板里是空的,首次运行前不选频道会导致报告节点报错
  • 嵌入供应商有速率限制时需在分批节点调小批大小,否则大批量夜间运行会触发限流

原文与作者资料 ↗

本站借助AI整理来源事实,独立编写中文操作指南与示例。示例不代表实际运行结果;来源效果尚未由本站独立复现。付费模板与模型、服务费用分别计算。

原文标题
Check consent before pgvector indexing in Postgres with OpenAI and Slack
原作者
Melbin Francis
原始发布日期
2026-09-16T00:48:35.061Z
来源
https://n8n.io/workflows/19639