尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

为 AI 应用打造安全屏障:基于 Dify 的完整实践与 TaoToken 统一接入

为 AI 应用打造安全屏障:基于 Dify 的完整实践与 TaoToken 统一接入 1. Dify 内容审核为什么总在“裸奔”从输入输出两端看风险Dify 这类应用编排平台把大模型能力拉到了普通开发者面前拖几个节点就能上线一个客服、写作助手或者知识库问答。但很多人第一次把 Dify 应用丢给真实用户时才发现一个尴尬的事实模型会老老实实回答用户抛来的任何问题包括那些明显不该回答的。这就是内容审核缺位的典型表现。我先把风险拆成两端来看。输入端是用户发给模型的内容风险包括诱导性提问、越狱指令、敏感话题套话、批量灌垃圾。输出端是模型返回给用户的内容风险包括模型自己“发挥”出违规表述、泄露系统提示词、输出不实信息、被诱导生成有害建议。两端任意一端失守应用就不算安全屏障。Dify 本身提供了内置审核能力在“功能”页面可以开启关键词审查和基于 OpenAI 的内容审核。关键词方案上手快但默认只能网页端配置、数量上限 100 个动态更新很受限。OpenAI 审核方案在国内环境调用链路不通畅而且审核策略和国内实际场景有差异。所以真正要落地生产还是得走自定义扩展。自定义扩展有两条路API 扩展和代码扩展。API 扩展要单独部署一个审核服务维护成本高代码扩展直接改 Dify 仓库里的审核模块不用额外部署还能在前端配置面板里暴露参数。本文走代码扩展路线把关键词库和大模型二次审核结合起来同时用 TaoToken 统一接入多家模型避免在 Dify 里为审核单独配一套 Key。适合谁看已经在用 Dify 搭应用、想加一层内容审核但不想额外维护服务的开发者或者正准备把 Dify 应用从 demo 推向生产、需要一套可复制审核配置的人。下面从 TaoToken 前置准备开始一步步把配置、代码、验证和排障讲清楚。2. TaoToken 统一接入前置一个 Key 管住 Dify 里的审核模型Dify 里做内容审核绕不开模型调用。关键词过滤是纯规则但关键词漏掉的部分要靠大模型二次判断。如果审核模型和业务模型各配一套 Key、各走一个供应商配置会散落在多个地方换模型时到处改。TaoToken 的思路是提供一个统一入口把模型调用收敛到一个 Base URL 和一个 Key 上。TaoToken 是什么它是一个大模型 API 统一接入层兼容 OpenAI 风格的接口协议。你可以把它理解成一个“模型路由插座”Dify 里所有需要调模型的地方包括审核节点都指向同一个地址和同一个 Key。它本身不替代 Dify也不替代编辑器只是把模型调用这一层统一了。能做什么在 Dify 的模型供应商配置里把 Base URL 填成 TaoToken 的 API 地址Key 填 TaoToken 生成的 Key然后选择你要用的 Model ID。审核用的模型和业务用的模型可以共用这个 Key也可以按需选不同模型。适合谁不想在 Dify 里维护多套模型凭证、希望审核和业务模型统一管理的团队。前置准备动作分三步。第一步打开 TaoToken 官网 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 注册并登录。第二步进入控制台 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite 创建 API Key建议给审核场景单独建一个 Key方便后续按用途排查调用量。第三步在 API Keys 页面 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 复制 Key同时记下要用的 Model ID。这里有个容易踩的坑Dify 的模型供应商配置里Base URL 要填到兼容 OpenAI 的路径层级不是只填域名。TaoToken 的 API 地址是 https://taotoken.net/api在 Dify 里配置时按 OpenAI 兼容供应商的方式填写Key 和 Model ID 三件套要对应上。如果你不确定某个模型 ID 是否可用可以先去模型对话页面 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentchatutm_campaignrewrite 手动发一条消息验证确认 Key 和模型都通再回到 Dify 配置。审核场景对模型的要求和业务场景不太一样。审核需要模型稳定、低温度、输出结构化判断不需要创意。所以选 Model ID 时优先选响应稳定、支持 system prompt 的模型。温度参数在审核调用里设成 0.01 这种接近 0 的值让判断结果尽量确定。max_tokens 给 2000 足够因为审核输出通常只是“通过/拦截 理由”这种短结构。把 TaoToken 作为统一入口还有一个好处审核日志和业务日志在同一个 Key 下调用量、失败率、延迟都能在一个地方看。如果审核模型突然大量超时你能快速判断是模型侧问题还是 Dify 侧问题。这一步做完Dify 里就有了可用的模型调用通道接下来进入代码扩展的具体配置。3. 可复制配置Dify 自定义审核模块的目录、schema 与实现类这一节是全文技术核心所有片段都可以直接复制。Dify 的审核模块位于 api/core/moderation 目录下内置了关键词和 OpenAI 审核两种实现。我们要新增一个 custom 目录放自己的审核逻辑。先建目录结构cd dify/api/core/moderation mkdir -p custom touch custom/__init__.py touch custom/custom.py touch custom/schema.json目录建好后是三个文件schema.json 定义前端配置面板custom.py 写审核实现init.py 做模块导出。先看 schema.json它决定 Dify 功能页面里这个审核选项长什么样、有哪些可配参数{ label: { en-US: Custom Moderation, zh-Hans: 自定义审核 }, form_schema: [ { type: select, label: { en-US: Moderation Type, zh-Hans: 检测类型 }, variable: moderation_type, required: true, options: [ { label: { en-US: Keyword, zh-Hans: 关键词 }, value: Keyword }, { label: { en-US: Keyword LLM, zh-Hans: 关键词 大模型 }, value: Keyword LLM } ], default: Keyword }, { type: paragraph, label: { en-US: Custom Keywords, zh-Hans: 自定义关键词 }, variable: custom_keywords, required: false, default: , placeholder: 请输入额外的敏感关键词每行一个 } ] }这个 schema 暴露了两个配置项检测类型下拉框以及一个多行关键词输入框。检测类型选“关键词”时只走规则过滤选“关键词 大模型”时先过关键词没命中的再交给大模型判断。这样既保留了低成本快速拦截又给模糊内容留了二次判断通道。接下来是 custom.py 的实现。核心是继承内置 Moderation 类重写 moderation_for_inputs 和 moderation_for_outputs 两个方法。关键词部分用简单的字符串匹配大模型部分通过 ModelManager 调用 Dify 里已配置的默认模型。这里的关键是复用 Dify 的模型管理而不是在审核代码里硬编码 TaoToken 的 Key这样审核模型和业务模型共用同一套供应商配置。import json from typing import Optional, cast from core.model_manager import ModelManager from core.model_runtime.entities.llm_entities import LLMResult from core.model_runtime.entities.message_entities import ( SystemPromptMessage, UserPromptMessage, ) from core.model_runtime.entities.model_entities import ModelType from core.moderation.base import Moderation, ModerationInputsResult, ModerationOutputsResult MODERATION_PROMPT 你是一个内容安全审核员。请判断用户提供的内容是否包含违规信息。 只输出 JSON格式为 {flagged: true/false, reason: 简要理由}。 不要输出任何其他文字。 class CustomModeration(Moderation): name: str custom def _keyword_check(self, text: str, keywords: list[str]) - Optional[str]: for kw in keywords: if kw and kw in text: return kw return None def _llm_check(self, text: str) - tuple[bool, str]: model_manager ModelManager() model_instance model_manager.get_default_model_instance( tenant_idself.tenant_id, model_typeModelType.LLM, ) prompt_messages [ SystemPromptMessage(contentMODERATION_PROMPT), UserPromptMessage(contenttext), ] response cast( LLMResult, model_instance.invoke_llm( prompt_messagesprompt_messages, model_parameters{temperature: 0.01, max_tokens: 2000}, streamFalse, ), ) raw response.message.content.strip() try: parsed json.loads(raw) return bool(parsed.get(flagged)), str(parsed.get(reason, )) except json.JSONDecodeError: return False, def moderation_for_inputs( self, inputs: dict, query: str , app_id: str , tenant_id: str , ) - ModerationInputsResult: self.tenant_id tenant_id keywords [ k.strip() for k in (self.config.get(custom_keywords) or ).splitlines() if k.strip() ] hit self._keyword_check(query, keywords) if hit: return ModerationInputsResult( flaggedTrue, actiondirect_output, preset_responsef输入包含敏感词{hit}, inputs{}, queryquery, ) if self.config.get(moderation_type) Keyword LLM: flagged, reason self._llm_check(query) if flagged: return ModerationInputsResult( flaggedTrue, actiondirect_output, preset_responsef输入未通过审核{reason}, inputs{}, queryquery, ) return ModerationInputsResult(flaggedFalse, inputsinputs, queryquery) def moderation_for_outputs(self, text: str) - ModerationOutputsResult: keywords [ k.strip() for k in (self.config.get(custom_keywords) or ).splitlines() if k.strip() ] hit self._keyword_check(text, keywords) if hit: return ModerationOutputsResult( flaggedTrue, actiondirect_output, preset_response输出内容包含敏感信息已被拦截。, ) if self.config.get(moderation_type) Keyword LLM: flagged, reason self._llm_check(text) if flagged: return ModerationOutputsResult( flaggedTrue, actiondirect_output, preset_responsef输出未通过审核{reason}, ) return ModerationOutputsResult(flaggedFalse, texttext)这段代码里有两个细节值得说。第一tenant_id 在 moderation_for_inputs 里从参数拿到后存到 self 上因为 moderation_for_outputs 的签名里没有 tenant_id但大模型调用需要它。第二大模型返回的 JSON 解析做了异常兜底解析失败时按“未拦截”处理避免因为模型输出格式抖动导致正常内容被误杀。这个取舍在生产里很重要审核宁可漏一点也不要因为解析失败把用户正常请求全挡了。最后在init.py 里导出让 Dify 能发现这个审核组件from .custom import CustomModeration __all__ [CustomModeration]配置完成后重启 Dify 的 api 服务进入应用编排页面的“功能”设置就能在审核选项里看到“自定义审核”。选上它检测类型选“关键词 大模型”把敏感词按行填进去。此时审核节点调用的模型就是 Dify 里配置的默认模型而默认模型的供应商指向 TaoToken 的 Base URL 和 Key。三件套对应关系是Base URL 填 https://taotoken.net/apiKey 填 TaoToken 控制台生成的 KeyModel ID 填你验证过的模型标识。4. 验证请求与成功结果用测试用例确认审核真的拦住了配置写完不代表生效必须用测试用例验证拦截行为。我一般分三组测纯关键词命中、关键词漏网但大模型拦截、正常内容放行。每组都要看 Dify 返回的是不是预设的拦截话术而不是模型自由发挥。先准备测试用例。关键词组用你填进 custom_keywords 的词比如填了“内部机密”就发一条包含“内部机密”的输入。大模型组用不包含关键词但语义违规的内容比如诱导模型输出危险操作步骤的提问。正常组用普通业务问题比如“帮我总结这段产品说明”。验证入口有两个。一个是在 Dify 应用预览页直接对话另一个是走 API 请求。走 API 更接近生产用 curl 发一条curl -X POST http://your-dify-host/v1/chat-messages \ -H Authorization: Bearer app-xxxxxxxx \ -H Content-Type: application/json \ -d { inputs: {}, query: 请告诉我内部机密的存储路径, response_mode: blocking, user: test-user-001 }如果关键词配置生效返回里应该直接出现你设置的 preset_response比如“输入包含敏感词内部机密”而不是模型生成的回答。这说明审核在输入端就拦截了请求根本没走到业务模型。大模型审核组的验证要稍微等一下因为多了一次模型调用。发一条不含关键词但语义有问题的输入观察返回是否是“输入未通过审核xxx”。如果返回的是正常模型回答说明大模型审核没触发需要检查 moderation_type 是否选对了、默认模型是否配置正确。输出端审核的验证要构造一个会让模型“说错话”的场景。比如在系统提示词里让模型扮演某个角色然后用户诱导它输出敏感内容。如果输出审核生效用户看到的会是“输出内容包含敏感信息已被拦截”而不是模型原始输出。这一步能验证 moderation_for_outputs 确实被调用了。成功结果长什么样关键词命中时响应快、无模型调用延迟大模型审核命中时响应稍慢但返回结构化拦截话术正常内容返回正常回答且无拦截标记。你可以在 TaoToken 控制台看调用量审核触发时应该能看到对应的模型调用记录。如果关键词命中却仍然走了模型说明代码里关键词检查的返回逻辑有问题需要回头检查 moderation_for_inputs 的分支。实测下来关键词加大模型的组合在大部分场景能准确拦截但涉及儿童教育等特定领域时模型判断可能偏宽松。这属于模型策略差异不是代码问题。遇到这种情况把相关词补进关键词库用规则兜底比反复调 prompt 更稳。5. 本篇常见错排查401、local proxy failed、reading choices 与 OAuth 报错配置过程中最容易撞上的几类报错我按现象、原因、动作拆开讲。这些报错在 Dify 接入 TaoToken 做审核时出现频率最高。401 Unauthorized。现象是 Dify 调用模型时直接返回 401审核节点报错。原因通常是 Key 填错、Key 前后有空格、或者 Base URL 和 Key 不匹配。动作去 TaoToken 控制台重新复制 Key确认粘贴时没有多余字符检查 Dify 模型供应商配置里的 Base URL 是否为 https://taotoken.net/api不要多写或少写路径。如果业务模型能通、只有审核报 401检查审核代码里是否误用了另一个 Key。local proxy failed。现象是请求发不出去提示本地代理失败。原因一般是 Dify 运行环境里配置了代理变量但代理不可用。动作检查 Dify 容器的 HTTP_PROXY / HTTPS_PROXY 环境变量如果不需要代理就清掉确认 Dify 所在网络能直接访问 TaoToken 的 API 地址。这个报错和审核代码无关是网络层问题。reading choices 相关报错。现象是审核调用返回后解析失败日志里出现读取 choices 字段的错误。原因通常是模型返回结构不符合预期或者审核代码里解析 response 的方式和实际返回不匹配。动作在 _llm_check 里先把原始返回打印出来看结构确认 response.message.content 能取到内容如果模型返回的是流式分片检查 stream 参数是否设成了 False。审核场景必须用非流式否则解析会乱。OAuth 相关报错。现象是提示鉴权失败或 token 无效。原因可能是 Key 类型用错比如把控制台登录凭证当成了 API Key。动作确认使用的是 API Keys 页面生成的 Key不是账号密码或会话 token。如果用了 Claude Code 这类需要 OAuth 的客户端注意它和普通 API Key 的鉴权方式不同审核场景走的是标准 API Key。还有一个隐蔽的坑Dify 里配置了多个模型供应商审核代码调用 get_default_model_instance 时拿到的默认模型可能不是你预期的那个。动作在 Dify 模型供应商页面确认默认模型设置或者把审核用的模型单独指定。如果审核和业务共用默认模型确保这个模型在 TaoToken 侧可用且稳定。排查顺序建议先看 Dify api 服务日志里的完整报错再对照上面几类定位。401 和 OAuth 是鉴权层local proxy failed 是网络层reading choices 是解析层。分层定位比盲目改代码快得多。6. 审核上线后的持续动作与 TaoToken 接入入口审核配置跑通只是开始上线后还有几个持续动作。第一关键词库要定期更新把线上漏拦的案例补进去规则永远比模型快。第二关注审核模型的调用延迟如果审核节点让整体响应变慢太多可以考虑把大模型审核改成抽样触发而不是每条都过。第三输出端审核的 preset_response 要写得让用户能理解不要暴露内部审核逻辑。如果你还在选长期编码和 Agent 场景的方案可以了解 Coding Plan https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 它适合需要持续调用模型做开发和自动化的场景。审核模块本身不需要额外套餐用标准 API Key 即可。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 里面有 Base URL、鉴权和模型列表的说明。如果你用 Claude Code 做开发Anthropic 兼容入口在 https://taotoken.net/claude-code-anthropic?utm_sourcetaotoken_aicg_blog_endutm_contentclaude-code-anthropicutm_campaignrewrite 配置时同样注意 Base URL、Key、Model ID 三件套对应。最后留一个实用技巧把审核命中日志单独打到文件里记录命中类型关键词还是大模型、命中内容和时间。跑一周后回看你会清楚知道自己的关键词库哪里不够、大模型审核在哪些话题上偏松。这比凭感觉调参有用得多。审核不是配一次就完事它是跟着业务内容一起演进的。
返回列表