每次ISO监督审核前,合规团队都要经历一轮高强度的证据整理:从制度文件、操作记录、培训证明到访问日志,材料散落在不同系统中,还要逐条对应标准条款判断是否满足要求。这种人工比对方式不仅效率低,而且判断口径容易因人而异,同一条款在不同审核员眼里可能得出不同结论。Dify作为一个可视化的LLM应用编排平台,可以把ISO标准条款的检索、企业实践描述的语义比对、不符合项的自动分流以及证据补充整合成一条可复用的工作流。合规人员只需要输入待检查的实践描述或上传相关文件,工作流就能输出结构化的合规判定和待补证据清单。

一、合规检查流的核心组件与总体架构
设计合规检查流之前,需要先拆解ISO标准本身的结构特征。以ISO 27001为例,正文条款分为4到10章,附录A还包含93个控制项,每条控制项都有明确的实施要求和验证证据类型。ISO 9001则围绕过程方法展开,强调输入、输出、资源和监控的闭环。无论具体标准是什么,合规检查的本质都是三个问题:标准要求是什么、企业当前实践是否满足、差距在哪里。基于这个逻辑,Dify工作流可以抽象为四个层次:条款检索层、语义分析层、判定分流层、证据与报告层。
条款检索层负责从标准知识库中找到与当前检查项最相关的条款原文及其官方指南。语义分析层由LLM节点承担,输入标准条款、企业实践描述和检查上下文,输出结构化的合规状态。判定分流层使用条件分支节点,根据LLM给出的状态标签路由到不同的处理路径,例如完全符合直接归档,部分符合或不符合进入证据收集环节。证据与报告层则通过代码节点、HTTP请求节点和模板节点完成数据汇总、外部系统查询和审计报告生成。
在Dify中搭建这样的工作流,通常需要准备以下节点:开始节点接收用户输入,知识检索节点查询标准条款,LLM节点进行合规判定,条件分支节点分流,代码节点处理数据转换,HTTP请求节点对接内部证据系统,模板节点或直接输出节点生成最终报告。整体架构的重点不在于节点数量多少,而在于每个节点的输入输出数据结构要清晰,避免后续节点反复解析非标准格式的文本。
二、构建面向ISO条款的知识库与检索策略
知识库的质量直接影响合规检查流的准确性。上传ISO标准文档时,不建议直接把整份PDF丢进Dify默认分段器,因为标准文档的层级结构复杂,条款编号、标题、正文和注释混在一起,自动分段容易把一条完整条款切碎。更稳妥的做法是先对标准文本做预处理:把每条条款独立成段,保留条款编号和标题作为首行,例如“A.9.1.1 访问控制策略”,然后再导入Dify知识库。这样每条索引对应的内容就是一个完整的条款单元,检索结果可以直接被LLM引用。
检索参数需要根据合规场景调整。ISO条款的表述通常比较抽象,而企业实践描述往往使用具体业务语言,两者之间的语义距离较大。建议将知识检索节点中的Top K设置得稍大一些,比如返回5到8条候选条款,同时启用重排序功能,让与当前检查上下文更相关的条款排在前面。关键词权重和向量相似度的混合检索模式在合规场景下效果更好,因为条款编号、控制项名称这类精确匹配信息可以通过关键词召回,而语义相近但用词不同的部分则依赖向量检索补足。
如果同一套知识库需要覆盖多个标准版本或多个行业规范,可以利用Dify的元数据过滤功能。例如给每条条款打上标准编号、版本号和章节号的元数据标签,工作流在检索时通过变量动态指定过滤条件,只检索当前审核所依据的标准版本。这样可以避免新旧版本条款混杂导致LLM引用过时要求。以下是一个在代码节点中调用Dify知识库检索接口的示例,展示了如何通过API传入查询文本和过滤条件:
import requests
def main(query: str, dataset_id: str, standard_version: str) -> list:
# 调用Dify知识库检索API,按标准版本过滤
payload = {
"dataset_id": dataset_id,
"query": query,
"top_k": 6,
"score_threshold": 0.55,
"metadata_filter": {
"standard_version": standard_version
}
}
response = requests.post(
"http://127.0.0.1/v1/datasets/retrieve",
json=payload,
timeout=15
)
response.raise_for_status()
records = response.json().get("records", [])
# 返回条款编号、内容和相似度分数
return [
{
"clause_id": r.get("metadata", {}).get("clause_id", ""),
"content": r.get("content", ""),
"score": r.get("score", 0.0)
}
for r in records
]
上述代码将检索结果规范化为统一的字典列表,后续LLM节点可以直接接收这个结构化数据,而不需要再对原始API响应做文本清洗。需要注意的是,score_threshold不宜设得过高,否则可能漏掉语义相关但用词不同的条款;反之过低会引入噪声,影响LLM判断的聚焦度。
三、LLM合规分析节点设计
LLM节点的提示词需要明确三个角色定位:标准条款的忠实解读者、企业实践描述的客观分析者、合规差距的准确标识者。提示词中应禁止LLM自行发挥或推测标准条款之外的要求,同时要求它区分“未提供证据”和“证据显示不符合”这两种本质不同的情况。一个有效的提示词结构是先给出系统指令,再提供检查所需的标准条款原文,然后是企业实践描述,最后指定JSON输出格式。
为了让下游条件分支节点能够可靠地工作,LLM的输出必须是高度结构化的。建议在Dify的LLM节点中启用JSON输出功能,并给出明确的字段定义。例如输出包含compliance_status字段,取值为compliant、partially_compliant、non_compliant、not_applicable之一;evidence_gap字段列出缺失的证据类型;finding字段给出简要发现;clause_id字段对应检查的条款编号。这样可以避免LLM输出自由文本导致条件分支节点无法解析。
以下代码节点展示了如何对LLM节点输出的JSON字符串进行解析和校验,确保进入条件分支的数据干净可用:
import json
def main(llm_output: str) -> dict:
# 去除可能的Markdown代码块标记
clean_text = llm_output.strip()
if clean_text.startswith("```"):
clean_text = clean_text.split("\n", 1)[1]
if clean_text.endswith("```"):
clean_text = clean_text[:-3]
data = json.loads(clean_text)
allowed_statuses = {"compliant", "partially_compliant", "non_compliant", "not_applicable"}
status = data.get("compliance_status", "unknown")
if status not in allowed_statuses:
status = "unknown"
return {
"clause_id": data.get("clause_id", ""),
"compliance_status": status,
"finding": data.get("finding", ""),
"evidence_gap": data.get("evidence_gap", []),
"confidence": data.get("confidence", 0.0)
}
对于模糊条款或实践描述信息不足的情况,可以在提示词中要求LLM在confidence字段中给出低置信度标记,并在finding中明确写出需要补充的信息。这样后续条件分支可以增加一个“信息不足”的路径,引导用户补充描述或上传更多材料,而不是直接给出可能错误的判定结果。这种设计在ISO审核场景中尤其重要,因为误判为符合而遗漏真实差距,比标记为待确认带来的风险更大。
四、条件分支与证据收集闭环
条件分支节点是整个工作流的控制中枢,它根据LLM输出的compliance_status字段决定后续走向。以四个状态为例,compliant可以进入归档节点,直接生成记录;partially_compliant和non_compliant需要进入证据收集环节,但两者在证据缺口类型上可能不同;not_applicable则需要人工确认该条款是否确实不适用于当前检查对象。在Dify中配置分支条件时,建议使用变量引用而不是硬编码字符串,避免因为提示词微调导致状态值变化时出现分支失效。
证据收集环节通常需要与企业的内部系统对接。例如检查ISO 27001的访问控制条款时,需要从身份管理系统中获取用户权限列表;检查供应商管理条款时,需要从采购系统中调取合格供应商清单。Dify的HTTP请求节点可以完成这类同步调用,但如果内部系统接口复杂,更灵活的方式是使用代码节点编写Python逻辑,处理鉴权、分页和数据转换。以下示例展示了一个代码节点调用内部API获取资产台账的逻辑:
import requests
def main(asset_type: str, owner_dept: str) -> dict:
# 调用内部资产管理系统API,获取待检查的资产清单
params = {
"type": asset_type,
"department": owner_dept,
"page_size": 100
}
response = requests.get(
"http://192.168.0.1/api/assets",
params=params,
timeout=10
)
response.raise_for_status()
data = response.json()
# 提取关键字段用于后续合规比对
assets = data.get("items", [])
result = []
for item in assets:
result.append({
"asset_id": item.get("id"),
"asset_name": item.get("name"),
"owner": item.get("owner"),
"classification": item.get("classification"),
"last_review_date": item.get("last_review_date")
})
return {
"asset_count": len(result),
"assets": result
}
证据收集完成后,可以再次调用LLM节点进行二次判定,把新获取的证据与标准条款一起输入,让LLM给出更新后的合规状态。这种“初判—补证—复判”的闭环设计能够显著降低误判率,同时把需要人工介入的场景压缩到最少。如果二次判定仍然是non_compliant,工作流可以输出整改建议并推送给责任部门。
五、报告生成与审计追踪
合规检查流的最终产出不只是判定结果,还需要一份可追溯、可归档的审计报告。报告应包含检查项标识、引用的标准条款原文、企业实践描述、判定状态、证据清单、发现问题和整改建议。在Dify中可以使用模板节点将变量填充到预先设计好的报告结构中,也可以使用代码节点直接拼接Markdown或HTML文本,然后通过输出节点返回给用户或写入外部文档系统。
审计追踪方面,每一条合规检查记录都应保留工作流运行时的输入快照、检索到的条款列表、LLM的原始输出、证据收集请求和最终判定。Dify本身提供运行日志和对话历史,但这些日志偏重调试用途,合规场景更建议在代码节点中主动将关键数据写入独立的审计数据库或日志文件。可以在工作流末尾添加一个代码节点,把前面所有节点的关键输出汇总成一个审计记录对象,通过HTTP请求发送到内部审计系统。
标准条款会定期更新,知识库也需要配套的维护机制。建议设置一个定时任务,当ISO标准发布新版本或增补件时,重新导入知识库并运行一轮回归测试。测试用例可以覆盖典型条款的合规、不符合、部分符合和信息不足四类场景,确保工作流在新版本标准下仍然输出稳定。对于判定口径的变化,还应记录提示词和检索参数的版本号,方便追溯某次判定是基于哪一版配置做出的。
把Dify用于ISO合规检查流的建设,本质上是用LLM的语义理解能力替代原先完全依赖人工的条款匹配工作。它不能取代有资质的审核员,但可以把初审阶段的大量重复劳动自动化,让专业人力集中在复杂判断和整改验证上。通过合理的知识库拆分、结构化提示词、严格的分支路由和完整的审计记录,一条Dify工作流完全可以成为合规团队日常工作中的可靠助手。