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凭据,选定接收报告的频道
- 已存在的同意登记数据(本流程只读取同意,不采集同意)
具体怎么做
建四张表并开启pgvector
在Postgres里先执行CREATE EXTENSION vector,然后按下方SQL建kb_documents(含indexed_at列)、consent_register、index_manifest和向量表kb_vectors,向量维度按text-embedding-3-small的默认输出设为1536。
灌入合成测试数据
把下方artifacts里的CSV分别导入kb_documents和consent_register,覆盖六种典型情形:授权有效、无个人信息、用途不符、同意过期、已撤回、登记缺字段,保证一次运行能同时触发通过、拒绝和暂缓。
搭触发与读取节点
在n8n新建工作流,放Schedule Trigger设为每天凌晨运行;接两个Postgres节点:一个查SELECT * FROM kb_documents WHERE indexed_at IS NULL,另一个全量读consent_register,用Merge节点按subject_id把两份数据按文档逐条配对。
写判定规则节点
加一个Code节点,把下方consent_rules.json作为规则常量读入,对每条配对结果输出decision(embedded/refused/held)、ground(如purpose_limitation、retention_expired)和lawful_basis。规则写在该节点顶部的配置区,新增拒绝理由只加一块判断,不改主逻辑。
分支处理合格与不合格
用Switch节点按decision分三路:embedded路走文本切块→OpenAI Embeddings节点→Postgres写入kb_vectors并UPDATE kb_documents SET indexed_at=now();refused和held两路只往index_manifest插入决定、理由和当晚日期,不改indexed_at,使其明晚重查。
汇总并发送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