Dify变量注入实现上下文安全传递
你有没有遇到过这种情况:销售同事刚上传了一份客户合同,想确认交付周期,结果AI助手却引用了HR部门的薪酬调整通知?或者更糟——某个临时外包人员无意中查到了本不该看到的财务预算表。
这听起来像是模型“答非所问”,但问题的根源往往不在AI本身,而在于它不知道自己该知道什么。一个真正智能的系统,不仅要理解“用户问了什么”,更要清楚“这个用户是谁、能访问哪些信息、当前处于哪个业务流程”。
尤其是在企业级知识管理场景下,这种“认知错位”可能直接导致数据泄露或合规风险。我们不能再把AI当作一个孤立的问答机器,而是要让它成为情境感知的协作者——而这正是Dify变量注入机制的价值所在。
当我们谈论本地RAG(检索增强生成)工具时,Anything-LLM是一个绕不开的名字。它部署简单、界面友好,支持多种文档格式和主流大模型后端(Ollama、OpenAI等),几乎是个人用户搭建专属知识库的首选方案。
但一旦从“个人助手”升级为“团队共享平台”,它的短板就暴露出来了:
- 如何防止市场部员工查看研发文档?
- 怎样确保实习生只能访问公开资料?
- 谁在什么时候查询了哪些敏感内容?如何审计?
原生的Workspace隔离功能虽然提供了一定程度的数据划分,但它缺乏动态权限控制能力。每个空间是静态配置的,无法根据用户身份实时调整可见范围。
这时候,引入Dify作为前端编排层,就成了性价比极高的解决方案。Dify不只是个提示词工厂,它更像是一个运行时上下文调度中心——你可以通过变量注入,在每一次请求中动态传递用户角色、权限边界、会话状态等关键信息,并让这些元数据真正影响AI的行为逻辑。
变量注入的本质:从静态模板到动态响应
传统AI应用常采用“一套Prompt走天下”的模式:无论谁提问,看到的都是同样的系统指令。这种做法看似高效,实则粗暴。它忽略了最基础的事实——不同角色对同一问题的需求完全不同。
而Dify的变量注入机制打破了这一局限。它允许你在调用API时,传入结构化的inputs字段,例如:
{ "inputs": { "user_id": "U1001", "role": "sales_representative", "allowed_spaces": ["public", "sales"], "session_context": { "project_name": "Project_Apollo", "client_type": "enterprise" } }, "query": "这份合同的付款条件是什么?" }这些字段不会被直接暴露给模型,而是作为上下文插槽,在工作流中参与提示词渲染。比如这样一段Prompt:
你是一名销售助理,请基于以下文档回答问题。 注意:你只能参考位于 {{inputs.allowed_spaces}} 空间内的资料。 当前项目背景:{{inputs.session_context.project_name}} 问题:{{query}}当引擎执行时,Jinja2模板语法会自动替换占位符,最终送入LLM的是一段完全个性化的指令。更重要的是,Dify默认开启字段白名单机制——任何未在工作流中声明的输入都会被丢弃,这本身就是一道有效的防注入防线。
但这还不够。真正的安全不是靠“不传敏感数据”来实现的,而是建立一套最小权限传递 + 后端策略解析的闭环机制。
举个例子,下面这种做法就是危险的:
"allowed_docs": ["薪资表2024.xlsx", "高管考核细则.docx"]你把具体的文件名直接放在请求体里,等于在网络上传输一份“可访问清单”。一旦中间链路被监听,后果不堪设想。
正确的做法是使用抽象标识进行间接映射:
"allowed_tags": ["confidential", "hr"], "role": "hr_manager"前端只传递标签或角色,实际的文档访问规则由后端服务(如Anything-LLM的插件或中间件)根据预设策略动态解析。这样一来,即使请求被截获,攻击者也无法反推出具体受保护资源。
实战:构建一条安全的上下文传递链
现在我们来动手实现这个架构。目标很明确:用户在Dify发起提问 → 注入身份与权限上下文 → 安全透传至Anything-LLM → 返回符合权限边界的答案。
第一步:定义输入契约
在Dify的工作流中,首先声明所需的输入参数:
| 字段名 | 类型 | 说明 |
|---|---|---|
user_id | string | 用户唯一标识 |
role | string | 角色(admin/sales/hr/guest) |
allowed_spaces | array | 允许访问的空间列表 |
session_context | object | 当前会话元信息 |
query | string | 用户原始提问 |
建议勾选“严格模式”(Strict Mode),拒绝所有未声明字段。这是防止恶意构造payload的第一道闸门。
第二步:编写上下文感知的提示词
接下来,在主LLM节点中设置Prompt模板:
你是企业知识助手,请根据用户权限范围内的文档回答问题。 【上下文信息】 - 当前用户角色:{{inputs.role}} - 可访问知识空间:{{inputs.allowed_spaces | join(', ')}} - 当前项目:{{inputs.session_context.project_name}} 请严格遵守以下规则: 1. 若问题涉及未授权空间的内容,应回复:“您无权查看该信息。” 2. 回答需简洁专业,引用来源文档名称。 问题:{{inputs.query}}这里用了Jinja2的join过滤器将数组转为字符串,提升可读性。同时通过明确的角色和空间提示,引导模型自我约束输出范围。
第三步:中间件做上下文“翻译官”
由于Anything-LLM原生接口不支持自定义元数据,我们需要一个轻量级代理服务来完成上下文的解析与改写。
以下是基于FastAPI的实现示例:
from fastapi import FastAPI, Request, HTTPException from typing import List import httpx from pydantic import BaseModel, validator app = FastAPI() ANYTHING_LLM_API = "http://localhost:3001/api/chat" class UserContext(BaseModel): user_id: str role: str allowed_spaces: List[str] @validator('role') def valid_role(cls, v): valid_roles = ['admin', 'sales', 'hr', 'guest'] if v not in valid_roles: raise ValueError(f'Invalid role: {v}') return v @app.post("/chat") async def proxy_chat(request: Request): try: body = await request.json() inputs = body.get("inputs", {}) # 使用Pydantic进行强类型校验 ctx = UserContext(**inputs) query = inputs.get("query", "").strip() if not query: raise HTTPException(status_code=400, detail="Query is required") # 构造带空间标记的查询语句 space_filter = "[SPACE:" + ",".join(ctx.allowed_spaces) + "]" augmented_query = f"{space_filter} {query}" async with httpx.AsyncClient() as client: response = await client.post( ANYTHING_LLM_API, json={ "message": augmented_query, "chatbotId": "default", "userId": ctx.user_id }, timeout=30.0 ) return response.json() except Exception as e: raise HTTPException(status_code=500, detail=str(e))这个中间件完成了几个关键动作:
- 输入验证:通过Pydantic模型强制校验字段合法性,防止非法角色或空查询进入系统;
- 上下文编码:将
allowed_spaces转化为[SPACE:sales,public]这样的指令前缀,嵌入查询语句; - 请求转发:保留
user_id用于日志追踪,避免权限上下文在传输中丢失。
第四步:Anything-LLM端实现标签化过滤
最后一步是在Anything-LLM侧启用上下文感知检索。我们可以通过修改其RAG提示词模板来识别特殊指令:
你正在从以下文档中检索信息: {{context}} 注意事项: - 如果查询包含[SPACE:xxx]指令,则仅可引用属于对应空间的文档。 - 禁止推测或编造不在检索结果中的内容。 问题:{{query}}同时,在文档上传阶段为其打上元数据标签。例如:
curl -X POST http://localhost:3001/api/v1/docs/upload \ -H "Content-Type: multipart/form-data" \ -F "file=@contract.pdf" \ -F "metadata={\"space\": \"sales\", \"sensitivity\": \"internal\"}"Anything-LLM会将这些标签存入向量数据库(如ChromaDB),并在检索时结合查询中的[SPACE:...]指令做匹配过滤。这就实现了物理层面的数据隔离——即便模型想“越界”,也拿不到相关文档片段。
整个系统的数据流动如下图所示:
sequenceDiagram participant User participant Dify participant Middleware participant AnythingLLM User->>Dify: 发起提问(含inputs) Dify->>Middleware: 转发请求,携带上下文 Middleware->>Middleware: 校验+重写查询 Middleware->>AnythingLLM: 注入[SPACE:...]指令 AnythingLLM->>AnythingLLM: 按标签过滤文档 AnythingLLM-->>Middleware: 返回受限结果 Middleware-->>Dify: 原始响应透传 Dify-->>User: 展示最终答案这条链路不仅保障了安全性,还带来了额外好处:
- 个性化响应:可以根据
role调整语气,如对高管更正式,对内部员工更直白; - 审计追踪:
user_id贯穿全程,便于记录“谁查了什么”; - 低维护成本:所有权限逻辑集中在Dify配置,无需改动Anything-LLM源码;
- 灵活扩展:未来可轻松加入时间窗口限制、IP白名单等新策略。
当然,这套方案在落地时也有几点需要注意:
控制上下文体积
LLM的上下文窗口有限,不要在inputs里塞入大段描述。推荐只传ID、标签、状态码等轻量标识,具体内容由后端按需加载。启用会话级继承
Dify支持session_id机制,可以跨轮次保持用户权限状态。配合Redis缓存,能显著减少重复认证开销。日志脱敏处理
记录调试日志时务必对敏感字段(如allowed_spaces)做脱敏,避免二次泄露。建议只记录哈希值或枚举类型。监控异常行为
设置告警规则,如单用户短时间内频繁切换角色、尝试访问多个受限空间等,及时发现潜在滥用。
回过头看,AI系统的智能化,从来不只是模型参数规模的堆砌。真正的智能,是在正确的时间,以正确的身份,获取正确的信息。
Dify的变量注入机制,本质上是在填补AI系统中的“情境空白”。它让我们能把零散的上下文碎片——用户身份、组织架构、项目阶段、权限边界——编织成一条完整的线索,交到模型手中。
而Anything-LLM,则是这条线索的最终消费者。它可以是你的私人读书笔记助手,也可以是企业的合规知识中枢。变的是形态,不变的是那根由上下文牵引的主线。
未来的AI应用,一定是上下文驱动的。我们不再满足于一个“通用答案机器”,而是期待一个“懂我所想、知我所能”的伙伴。掌握变量注入,不只是学会一项技术,更是培养一种思维方式:
让AI‘知情’,才是真正的智能化起点。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考