我翻了5个主流Agent框架的授权模型:DeepSeek那个“不可变授权“设计,是孤例

结论先给:LangGraph、AutoGen、CrewAI、OpenAI Assistants API、Semantic Kernel——5个框架全部没有不可变授权对象。它们的"授权"本质都是运行时可变的进程内配置,同进程代码随时能改。dsh-superagent的Mandate(创建后冻结、只能收回)是目前唯一把授权做成数据结构的方案。上下文/权限分离在多数框架中存在,但不可变授权是dsh独有。
0. 为什么做这个对比
上一篇《我审了DeepSeek开源的超级Agent原型:三个安全命题,两个半成立》里,我审了DeepSeek开源的dsh-superagent-prototype,验证了它的三个安全命题:Mandate不可变、Subscription不扩权、没有自主Watchdog。审计过程中我一直在想一个问题:这个"不可变授权"的设计,在主流Agent框架里有没有人做过?
这个问题直接决定了一个实践判断:生产环境部署Agent,授权模型该抄谁的?抄LangGraph的middleware拦截,还是抄dsh-superagent的Mandate冻结?这两条路的底层逻辑完全不同。
我把5个主流Agent框架的官方文档翻了一遍,只看授权模型相关的技术细节,不看营销话术。对比维度四个:
- 工具调用的权限控制方式——有没有授权对象?怎么限制Agent能调什么工具?
- 上下文与权限是否分离——Agent"看到"的信息和"有权做"的操作是不是两条独立管道?
- 有没有不可变授权对象——类似dsh-superagent的Mandate:创建后不能改,只能收回?
- 人工介入/审批机制——有没有human-in-the-loop?怎么触发?
先说结论:5个框架全部没有不可变授权对象。 dsh-superagent的Mandate是孤例。
1. LangGraph:middleware拦截,审批原语最完整
LangGraph是LangChain的图执行框架,授权控制靠HumanInTheLoopMiddleware。
1.1 权限控制方式
创建agent时传入interrupt_on字典,按工具名映射审批策略:
HumanInTheLoopMiddleware(
interrupt_on={
"write_file": True, # 全部决策允许
"execute_sql": {"allowed_decisions": ["approve","reject"]},# 不允许edit
"read_data": False, # 免审批
})
粒度是工具级+参数级。InterruptOnConfig的when谓词可以检查参数——比如只对路径不在/workspace/下的写文件、非SELECT的SQL触发审批。这是5个框架里参数级控制做得最完整的。
1.2 上下文与权限分离
是,两条独立管道。模型照常看到工具schema并发出tool_call(可见性管道),middleware在执行前拦截(权限管道)。另外LangGraph Platform还有独立的平台层RBAC,@auth.on处理器对threads/assistants/crons/store资源做访问控制,与agent内部工具控制完全解耦。
1.3 不可变授权对象
没有。interrupt_on是普通dict,进程内任何代码都能改。checkpoint持久化的是图执行状态,不是授权凭证。interrupt是一次性审批事件,不是"可收回的授权"。
1.4 人工介入
触发条件是tool_call命中interrupt_on策略→图暂停、状态写入checkpointer→人工用Command(resume={"decisions":[...]})恢复。四种决策类型:approve(原样执行)、edit(改参数后执行)、reject(拒绝并反馈)、respond(直接回复作为工具结果)。
这是5个框架里审批原语最完整的。但注意:**审批是事件流(interrupt→决策→恢复),不是授权物。**审批完就结束了,没有留下一个"不能被改的授权凭证"。
2. AutoGen:双轨注册,分离最显式但粒度最粗
AutoGen分两个版本世代:0.2经典版(ConversableAgent)和0.4+ AgentChat。
2.1 权限控制方式
没有工具级ACL。权限模型=“工具挂在哪个agent上、谁有执行权”。0.2中UserProxyAgent是代码和工具的唯一执行主体,AssistantAgent只产代码/调用请求。粒度为agent级,无参数级内置策略。
2.2 上下文与权限分离
是,且是5个框架中表达最显式的。0.2的双装饰器设计:
@assistant.register_for_llm——只把schema放进LLM的tools[]("思考"侧)@user_proxy.register_for_execution——只把可执行函数绑定到执行agent("动手"侧)register_function便捷方法同时注册两侧
即"模型知道工具存在"与"某个agent有权执行"是两次独立注册。0.4中工具直接传给AssistantAgent(tools=[...]),可见性与执行权重新耦合在该agent内,但跨agent边界(handoff)仍然隔离。
2.3 不可变授权对象
没有。注册表是agent实例上的可变dict。
2.4 人工介入
0.2用human_input_mode三档:NEVER(不问)、TERMINATE(默认,仅在终止条件满足时问)、ALWAYS(每次响应前问)。0.4改为UserProxyAgent(input_func=...)作为团队成员:团队轮到它时阻塞整个run等待输入。
注意:0.4的HITL是"对话轮次级"介入,不是工具调用级审批。模型已经决定调工具了,人只能在整个run结束后给反馈,不能在单个tool_call级别拦截。
3. CrewAI:三层控制,但全是可变配置
3.1 权限控制方式
三层——
- Agent级白名单:
Agent(tools=[...])决定agent能用哪些工具(不在列表里的工具该agent既看不到也调不到) - 执行时拦截:hook返回
False即阻断执行。ToolCallHookContext暴露tool_name/tool_input/tool/agent/task/crew,可按工具名、参数值(如..路径检测)、agent role做条件阻断 - 企业版RBAC:HITL管理有两个权限位
manage_human_feedback和respond_to_human_feedback
粒度:工具级+参数级+agent级,覆盖面最杂但都是代码逻辑而非声明式策略。
3.2 上下文与权限分离
半分离。Agent(tools=[...])同时决定可见性和可执行性(耦合);hook是执行侧的独立第二道管道(模型已决定调用后仍可被拦)。没有独立的"可见性过滤器"。
3.3 不可变授权对象
没有。hook反而可被unregister_*/clear_all_tool_call_hooks()动态注销——是可变配置的典型形态。
3.4 人工介入
三种形态:hook内context.request_human_input()同步控制台审批;Task(human_input=True)任务完成后暂停等人工复核;Enterprise webhook任务进入Pending Human Input状态后回调通知。
4. OpenAI Assistants API:架构强制分离,但无审批原语
前置事实:该API已于2025-03宣布弃用,目标关停时间为2026年上半年,继任者是Responses API + Agents SDK。以下按原API机制描述。
4.1 权限控制方式
创建assistant时用tools数组声明可用工具(code_interpreter/file_search/function),可用tool_choice强制本次run用某工具。
**关键架构事实:OpenAI从不执行你的function。**模型决定调用后,run进入requires_action,客户端轮询拿到tool_calls(含函数名+参数),由你自己的代码决定执行与否,再POST submit_tool_outputs把结果喂回去。
粒度:工具级,无参数级策略、无审批原语。审批逻辑100%由客户端在暂停点自实现。
4.2 上下文与权限分离
对function工具是架构性强制分离——模型只能产参数,执行天然在客户端,可见性(schema上送)与执行权(客户端代码)物理隔离。但对内置工具(code_interpreter、file_search)是OpenAI沙箱内自动执行,客户端没有执行闸门,只能靠不声明该工具来禁用。
4.3 不可变授权对象
没有。assistant和run都是可变资源对象;唯一的时效性约束是run创建10分钟后过期,这是TTL而非可收回的授权凭证。
4.4 人工介入
无内置机制。实务模式就是利用requires_action暂停点:客户端收到tool_call后先问人、人同意才执行并提交输出。继任者OpenAI Agents SDK补上了原语:工具可声明needs_approval。
5. Semantic Kernel:三管道最清晰,但filter可运行时改
5.1 权限控制方式
双轨制——
- 可见性侧:
FunctionChoiceBehavior(Auto/Required/None)+过滤器dict(included_plugins/excluded_plugins等),控制哪些已注册插件/函数会播发给模型 - 执行侧:过滤器用
next(context)委托链实现——不调next操作就永远不会执行。FunctionInvocationContext含插件名、函数名、完整参数,可做参数级校验
粒度:插件级+函数级+参数级,且filter可覆写函数结果(执行前可缓存、执行后可脱敏)。
5.2 上下文与权限分离
是,架构上最明确的双管道。FunctionChoiceBehavior管"模型看到什么",Filters管"函数能不能跑",IPromptRenderFilter还能在prompt发给模型前查看/改写/拦截(官方用例:PII脱敏)。三条管道互不依赖。
5.3 不可变授权对象
没有。filter注册在Kernel上,运行时可通过Kernel.FunctionInvocationFilters属性增删改排序(官方文档明确说"can be added, removed, or reordered at runtime")。
5.4 人工介入
SK本体无原生审批原语,官方推荐模式是在IFunctionInvocationFilter里自己写交互。其后继Microsoft Agent Framework补齐了原语:@tool(approval_mode="always_require")声明式审批。
6. 横向对比:一张表看清楚
| 框架 | 核心机制 | 授权粒度 | 上下文/权限分离 | 不可变授权对象 | 原生审批原语 |
|---|---|---|---|---|---|
| LangGraph | HITL Middleware + interrupt_on | 工具级+参数级 | ✅ middleware管道 | ❌ | ✅ approve/edit/reject/respond |
| AutoGen 0.2 | 双轨注册 + human_input_mode | agent级 | ✅ 双注册最显式 | ❌ | ⚠️ 轮次级,非工具级 |
| CrewAI | 工具白名单 + Tool Hooks | 工具级+参数级+agent级 | ⚠️ 半分离 | ❌ | ⚠️ hook内手写/webhook |
| Assistants API | requires_action客户端执行 | 工具级 | ✅ function工具架构强制 | ❌ | ❌(Agents SDK补了) |
| Semantic Kernel | Filters + FunctionChoiceBehavior | 插件/函数级+参数级 | ✅ 三管道最清晰 | ❌ | ❌(Agent Framework补了) |
| dsh-superagent | Mandate + Subscription | scope文本级 | ✅ 双管道物理隔离 | ✅ 运行时无写入路径 | ⚠️ 人工stop/retry |
7. 核心发现:两种范式的本质差异
把6个方案摆在一起,能看到两种完全不同的授权范式。
7.1 主流框架的范式:事件流审批
LangGraph/AutoGen/CrewAI/SK的授权模型本质上是事件流:模型发出tool_call→middleware/hook/filter拦截→(可选)人工审批→执行→结束。审批完就结束了,没有留下一个"不能被改的授权凭证"。
这个范式的特点是:
- 授权是过程,不是对象。权限体现在拦截逻辑里,不在一个独立的数据结构里。
- 运行时可变。策略dict、hook函数、filter列表都是进程内可变配置,同进程代码随时能改。不仅授权可变,连"什么时候需要审批"这个规则本身也是可变的。
- 审批是事件,不是授权。interrupt/approve完了就完了,没有"这个agent从此只能做这个scope的事"的持久保证。
7.2 dsh-superagent的范式:授权物冻结
dsh-superagent的Mandate是一个不可变授权对象:accept时创建,包含strength/policyVersion/basis/actor/scope,创建后运行时无任何写入路径,只能由人工stop或revalidate失败来收回。
这个范式的特点是:
- 授权是对象,不是过程。权限固化在一个数据结构里,独立于执行流程存在。
- 运行时不可变。创建后冻结,代码里没有写入路径(我审计过,93处引用全是读)。
- 授权可收回但不可篡改。人工可以stop,但没有任何代码路径能改scope或提strength。
7.3 哪种更硬?
如果你的威胁模型是"Agent被诱导后自行扩大权限",dsh-superagent的范式更硬——因为即使LLM被prompt注入说服了,它也改不了Mandate,代码里没有写入路径。
如果你的威胁模型是"Agent需要在执行中动态调整策略",主流框架的范式更灵活——因为你可以随时改hook/filter/策略dict。
但灵活性是有代价的。 CrewAI的hook可以被clear_all_tool_call_hooks()一键清空;SK的filter可以运行时增删改;LangGraph的interrupt_on是普通dict。这些"灵活"在攻击者眼里就是"可篡改"。
dsh-superagent的Mandate不可变,代价是scope在accept时就定死了,执行中不能扩大也不能缩小——要改只能stop后重新accept。这是用灵活性换安全性。
8. 上下文/权限分离:多数框架有,但强度不同
上下文/权限分离不是dsh-superagent独有的。5个框架里有4个做到了某种程度的分离:
- Semantic Kernel:三管道最清晰(可见性/执行/prompt渲染互不依赖)
- LangGraph:middleware管道分离,平台层还有独立RBAC
- AutoGen 0.2:双注册是最显式的分离(register_for_llm vs register_for_execution)
- Assistants API:function工具架构强制分离
CrewAI是半分离(白名单同时决定可见性和可执行性)。
但dsh-superagent的分离有一个其他框架没有的特点:Subscription扇出的上下文Input不携带任何授权字段。其他框架的分离是"模型看到工具schema但执行被拦截",dsh的分离是"上下文流动和权限冻结是两条物理隔离的管道"——上下文可以自由流动,但权限管道是封死的。
这个差异的后果是:在其他框架里,Agent"看到"一个工具后可能会试图调用它,然后被middleware拦住;在dsh里,Agent拿到的上下文Input里根本没有授权信息,它"看到"的东西和"有权做"的东西在数据结构层面就是分离的。
9. 一句话总结
5个主流Agent框架的授权模型都是"运行时可变的进程内配置",审批是事件流而非授权物。dsh-superagent的Mandate是目前唯一把授权做成不可变数据结构的方案——创建后冻结、只能收回不能篡改。上下文/权限分离在多数框架中存在,但不可变授权是dsh独有。
如果你要在生产环境部署Agent,授权模型的选择取决于你的威胁模型:要灵活性抄LangGraph/SK,要安全性抄dsh-superagent,要两者兼得——目前没有现成的,得自己造。
附:调研来源
| 框架 | 官方文档 |
|---|---|
| LangGraph | docs.langchain.com/oss/python/langchain/human-in-the-loop、docs.langchain.com/oss/python/langgraph/interrupts |
| AutoGen | microsoft.github.io/autogen/0.2/docs/tutorial/human-in-the-loop、microsoft.github.io/autogen/stable/user-guide/agentchat-user-guide/tutorial/human-in-the-loop.html |
| CrewAI | docs.crewai.com/en/learn/tool-hooks、docs.crewai.com/en/learn/human-in-the-loop |
| Assistants API | platform.openai.com/docs、openai.github.io/openai-agents-python/human_in_the_loop/ |
| Semantic Kernel | learn.microsoft.com/en-us/semantic-kernel/concepts/enterprise-readiness/filters、learn.microsoft.com/en-us/semantic-kernel/concepts/ai-services/chat-completion/function-calling/function-choice-behaviors |
| dsh-superagent | github.com/deepseek-ai/dsh-superagent-prototype(审计见上一篇) |
更多推荐

所有评论(0)