一、引言:一次失控的工具调用,差点删掉生产数据库
先讲一个我亲历过的教训。团队里有一个基于 Dify 搭建的运维 Agent,配置了"查询服务器状态"、"执行运维命令"、"读写配置中心"三个工具。起初一切正常,直到某次用户输入了一段包含特殊字符的文本,Agent 在拼接命令时没有做任何转义,直接把用户输入拼进了 shell 命令——一条rm -rf差点落进生产环境的临时目录。事后排查发现:这个 Agent 的权限模型只有一句话——"允许调用全部工具,参数不做校验"。
类似的事故每天都在发生:Agent 把 API Key 打印进了对话记录、代码执行沙箱里跑出了访问内网的请求、文件工具把客户隐私数据写进了共享目录……多 Agent 系统里,工具就是 Agent 的"手"和"嘴"。上一期我们讲了安全边界与权限治理,解决了"谁有权调用什么"的宏观问题;这一期我们往下一层,解决"工具被调用时,怎么保证它跑不偏、炸不坏、泄不了密"的执行层问题——工具权限(Tool Permissions)与安全沙箱(Security Sandbox)。
本文会先拆解工具为什么是多 Agent 系统最大的攻击面,然后依次给出权限治理、沙箱执行、凭据管理、调用护栏、审计追溯五层防护,最后用 DeepSeek + Dify 的实战代码,把"有能力但不越权"真正落地。
为什么说"工具即攻击面"?
模型本身是"没有手"的,它的一切动作都要通过工具完成。这带来一个残酷的现实:模型的所有安全缺陷,最终都会在工具调用上兑现。提示注入让模型"说错话"不可怕,让模型"调用错工具"才致命。归纳起来,工具调用有四大典型风险:
- 越权调用:Agent 权限过宽,一个客服 Agent 能调用财务导出工具。
- 参数注入:用户输入未校验直接拼进命令、SQL、文件路径,形成注入。
- 凭据泄露:密钥硬编码在工具参数里,或被打进日志/对话输出。
- 副作用失控:工具执行没有超时、没有熔断、没有回滚,一次错误调用造成不可逆破坏。
多 Agent 系统让这些问题指数级放大:工具被多个 Agent 共享,一个 Agent 的权限漏洞等于全部 Agent 的漏洞;Agent 之间互相调用工具,攻击者可以通过"最弱一环"横向移动。所以,工具层的安全建设,不是可有可无的加固,而是多 Agent 系统上生产的前提。
这里要先建立一个总纲式的认知:安全不能靠单点防护,要靠纵深防御(Defense in Depth)。单层防护再强也有被绕过的可能——校验正则可以被精心构造的输入骗过、沙箱也可能有逃逸漏洞、凭据服务也可能被攻破。但如果每一层都独立设防,攻击者必须同时击穿多层才能造成实质破坏,攻破成本呈指数上升。本文的五层防护正是按照这个思路设计的:即使某一层失守,后面还有三四层兜底。每一层解决一个不同的问题:权限层管"能不能调",沙箱层管"跑在哪",凭据层管"密钥在哪",护栏层管"调得稳不稳",审计层管"出事后查不查得清"。下面逐一展开。
二、第一层防护:声明式工具权限,把"谁能用什么"写成清单
2.1 工具清单(Tool Manifest):给每个工具一份"身份证"
权限治理的第一步,是给系统里的每一个工具建立声明式清单,明确声明:这个工具是干什么的、接受什么参数、有什么副作用、谁能调用。把隐性的"代码里随便调",变成显性的"清单里写清楚"。
# tool_manifest.yaml - 工具清单示例 tools: - name: query_order description: 按订单号查询订单状态(只读) parameters: order_id: { type: string, pattern: "^[A-Z0-9]{6,20}$" } side_effect: readonly # readonly | mutating | dangerous requires_approval: false # 危险操作需人工审批 allowed_agents: [order_agent, after_sale_agent] rate_limit: 100/min timeout_ms: 3000 - name: execute_shell description: 在受限沙箱中执行运维命令 parameters: command: { type: string, max_length: 500 } side_effect: mutating requires_approval: true # 高危:必须人工确认 allowed_agents: [ops_agent] sandbox: { image: "ops-runner", network: "off", memory_mb: 512, cpu: 1 } timeout_ms: 15000关键字段不是随便写的,它们直接对应安全决策:
- side_effect(副作用级别):只读工具可以放开调用;变更型工具加审批;危险工具默认禁止。
- allowed_agents(授权矩阵):一个工具只对白名单内的 Agent 开放,这是权限治理的最小单元。
- sandbox(沙箱规格):高危工具必须声明运行环境,没有沙箱声明的工具不允许执行。
- requires_approval:突破"模型自己决定"的信任边界,把关键决策交给人。
2.2 调用前校验:拦截器(Interceptor)模式
清单只是静态声明,真正拦得住还要在调用入口做动态校验。推荐在 Dify 的工具调用层之前挂一层"拦截器",统一做四件事:身份校验、参数校验、额度校验、审批校验。
# tool_gate.py - 工具调用拦截器 import re import time class ToolGate: def __init__(self, manifest): self.manifest = manifest # 工具清单 self.budget = {} # 额度计数 def check(self, agent_id: str, tool_name: str, params: dict): tool = self.manifest["tools"].get(tool_name) if not tool: raise PermissionError(f"未知工具: {tool_name}") # 1) 身份校验:Agent 是否在授权矩阵内 if agent_id not in tool["allowed_agents"]: raise PermissionError(f"{agent_id} 无权调用 {tool_name}") # 2) 参数校验:正则/类型/长度 for key, spec in tool["parameters"].items(): if key in params and "pattern" in spec: if not re.match(spec["pattern"], str(params[key])): raise ValueError(f"参数 {key} 不符合规范") # 3) 额度校验:速率限制 now = time.time() self.budget.setdefault(tool_name, []).append(now) recent = [t for t in self.budget[tool_name] if now - t < 60] if len(recent) > tool.get("rate_limit", 60): raise RuntimeError(f"{tool_name} 触发限流") # 4) 审批校验:危险操作必须带审批令牌 if tool.get("requires_approval") and not params.get("_approval_token"): raise PermissionError(f"{tool_name} 为危险操作,需要人工审批") return True这一层的意义在于:把安全判断从"模型自觉"迁移到"代码强制"。模型可能被诱导、可能幻觉、可能忽略指令,但拦截器不会——它是确定性的、可测试的、可审计的。
2.3 授权模型怎么选:RBAC、ABAC 还是直接白名单?
工具多了之后,allowed_agents这种直接白名单会变得难以维护——十几个 Agent、上百个工具,两两关系写死人。实际落地时通常按规模选型:
- 小型系统(<10 个工具):直接用白名单,简单直白,一眼看全。
- 中型系统(10~50 个工具):引入 RBAC(基于角色的访问控制)。给 Agent 定义角色(如
order_staff、ops_admin),角色绑定工具集,Agent 只挂角色。这样新增工具时只需改角色定义,不用逐个改 Agent。 - 大型系统(50+ 工具,权限随业务动态变化):考虑 ABAC(基于属性的访问控制)。权限决策不再依赖"谁调用",而是依赖属性条件——"工具属于订单域"且"Agent 的部门是客服部"且"当前时间在工作时段"才放行。
选型建议:从白名单起步,等维护成本高了再升级 RBAC,ABAC 只在权限规则真正动态化时引入。多数团队卡在中间态——既没有白名单的清晰,也没有 RBAC 的扩展性,是因为一开始没给工具定义"域"(domain)属性。给每个工具加上domain字段(订单域、售后域、财务域……),无论哪种模型都能基于域做粗粒度隔离,这是性价比最高的第一步。
三、第二层防护:沙箱执行,让工具"跑得动但炸不坏"
权限清单解决了"能不能调",沙箱解决"调了之后跑在哪里"。核心思想一句话:让工具在受控、受限、可丢弃的环境里执行。
3.1 代码执行类工具的沙箱化
最常见的危险场景是 Agent 执行 Python/Shell 代码。直接用本机subprocess等于把生产环境交了出去。正确做法是三层隔离:
# sandbox_runner.py - 基于 Docker 的代码执行沙箱 import docker import uuid client = docker.from_env() def run_in_sandbox(code: str, timeout: int = 10): container_id = f"sb-{uuid.uuid4().hex[:8]}" # 关键:只读挂载 + 关闭网络 + 限制资源 + 丢弃特权 container = client.containers.run( image="python:3.11-slim", # 固定镜像,不信任用户镜像 command=["python", "-c", code], name=container_id, network_disabled=True, # 断网:杜绝数据外传 read_only=True, # 根文件系统只读 mem_limit="256m", # 内存上限 nano_cpus=int(0.5 * 1e9), # 0.5 核 pids_limit=64, # 进程数上限 cap_drop=["ALL"], # 丢弃全部内核能力 detach=True, remove=False, ) try: result = container.wait(timeout=timeout) logs = container.logs(stdout=True, stderr=True).decode("utf-8") return result["StatusCode"], logs except Exception: container.kill() # 超时强杀 raise TimeoutError("沙箱执行超时") finally: container.remove(force=True) # 用完即弃,不留残留这套配置每一行都有讲究:network_disabled=True掐断外传通道,read_only=True防止写坏宿主机文件,cap_drop=["ALL"]让容器内代码即使提权也无内核能力可用,mem_limit和pids_limit防资源耗尽攻击。沙箱用完即删,即使代码是恶意的,它也只在一次性环境里存活。
3.2 外部 API 类工具的沙箱化
不是所有工具都能跑容器——查订单、调支付、发消息这类 API 调用,沙箱体现在网络和凭据层面:
- 网络白名单:Agent 运行环境只允许访问预配置的域名/IP,其余一律拦截。可以用 egress 代理(如 mitmproxy)或云安全组实现。
- 请求改写:所有出站请求统一经过代理层,自动注入审计头(trace_id)、剥离敏感头、校验目标域名。
- 响应过滤:返回给 Agent 的响应经过脱敏层,手机号、身份证、密钥用正则或模型检测打码后再进上下文。
# egress_proxy.py - 出站请求代理与响应脱敏 import re import hashlib SENSITIVE_PATTERNS = [ (re.compile(r"1[3-9]\d{9}"), "phone"), # 手机号 (re.compile(r"\b\d{17}[\dXx]\b"), "idcard"), # 身份证 (re.compile(r"(sk-[A-Za-z0-9]{20,})"), "api_key"), # API Key ] def mask_response(text: str) -> str: for pattern, kind in SENSITIVE_PATTERNS: def repl(m, kind=kind): raw = m.group(0) return f"[{kind}:{hashlib.sha256(raw.encode()).hexdigest()[:8]}]" text = pattern.sub(repl, text) return text脱敏不是删除——保留哈希指纹,既防泄露,又保留审计比对能力。
3.3 在 Dify 里怎么接入沙箱
Dify 的工具节点本身不提供沙箱能力,接入方式有两条路,按改造力度从小到大:
方案 A:外挂网关(推荐起步)。Dify 的所有工具调用统一走一个自定义 HTTP 工具——tool_gateway,网关内部再转发到真实工具。这样沙箱、拦截、审计全部收敛在网关一层,Dify 侧零改动,只需把原来直连的工具 URL 换成网关地址。缺点是多一跳网络延迟(通常 5~20ms,可接受),且网关要做成高可用,否则成为单点。
方案 B:自定义代码节点内嵌。如果工具是 Dify 的自定义代码节点(Code Node)实现的,直接在节点内部调用run_in_sandbox(),把 Docker 沙箱封装成公共函数。适合工具数量少、且本来就是代码节点的场景。缺点是把安全逻辑散落在各节点里,后期统一审计困难。
两种方案可以混用:低危工具走方案 B 轻量隔离,高危工具强制走方案 A 网关统一管控。网关方案还有一个额外收益——所有出站请求的 trace_id 注入、响应脱敏可以在网关里统一完成,不用每个工具各自实现一遍。
四、第三层防护:凭据管理,让密钥"不落地、不流转、不硬编码"
工具要调用外部服务,就得有凭据。而凭据管理是翻车重灾区:Key 写在代码里、打在日志里、跟着上下文传给模型、被 Agent 当作普通文本回复给用户。三条铁律:
铁律一:凭据不进上下文。模型不该"看到"密钥原文。工具调用时由网关层注入凭据,模型只传业务参数,凭据在请求发出前才由凭据服务解密注入。
铁律二:凭据不落日志。所有日志系统对sk-、password、token等关键词做掩码,从源头杜绝"日志即泄露"。
铁律三:凭据可轮换、可最小化。给不同 Agent 签发不同范围的临时凭据(如 STS 临时令牌),有效期短、权限最小、吊销即失效。
# credential_vault.py - 凭据服务:按需注入,最小权限 import time import hmac import hashlib import os class CredentialVault: """密钥只存在服务端加密存储,按 Agent 最小权限签发短期令牌""" def __init__(self): self._master_key = os.environ["VAULT_MASTER_KEY"].encode() def issue_token(self, agent_id: str, scopes: list[str], ttl: int = 300): """签发短期访问令牌:scope 限定能力,ttl 限定有效期""" payload = f"{agent_id}|{','.join(sorted(scopes))}|{int(time.time()) + ttl}" sig = hmac.new(self._master_key, payload.encode(), hashlib.sha256).hexdigest() return f"{payload}|{sig}" def inject(self, agent_id: str, tool_name: str, params: dict) -> dict: """调用外部 API 前,临时注入凭据;模型始终看不到密钥""" scopes = self._scopes_for(agent_id, tool_name) # 如 ["order:read"] safe = dict(params) safe["_token"] = self.issue_token(agent_id, scopes) return safe这里的关键设计是注入点:凭据在网关层临时拼进出站请求,绝不进入模型上下文,也不进入 Agent 之间的消息流转。模型从头到尾不知道密钥长什么样,也就谈不上"泄密"。
五、第四层防护:调用护栏,给每个工具调用上"保险丝"
再安全的沙箱也挡不住"合法调用 + 错误参数"造成的业务事故。护栏层负责给工具调用装保险丝,五件套缺一不可:
- 超时(Timeout):每个工具声明自己的超时上限(清单里的
timeout_ms),超时即中断,绝不让一次慢调用挂死整个工作流。 - 重试与退避(Retry & Backoff):网络抖动导致的失败可重试,但必须指数退避 + 抖动,且最多 2-3 次,防止重试风暴。
- 熔断(Circuit Breaker):一个工具连续失败超过阈值(如 5 次/分钟),熔断器打开,后续调用直接走降级路径,不再打到故障服务。
- 幂等与回滚(Idempotency & Rollback):变更型工具必须支持幂等键(idempotency key),重复调用不产生重复副作用;危险变更记录"撤销预案"。
- 输出护栏(Output Guardrail):工具返回值进入模型上下文前,检查体积(防超长输出挤爆上下文)与敏感内容(防数据外带)。
# guardrails.py - 调用护栏:超时 + 熔断 + 幂等 import time from functools import wraps class CircuitBreaker: def __init__(self, failure_threshold=5, cooldown=60): self.threshold, self.cooldown = failure_threshold, cooldown self.failures, self.opened_at = 0, None @property def is_open(self): if self.opened_at and time.time() - self.opened_at > self.cooldown: self.opened_at, self.failures = None, 0 # 半开:尝试恢复 return self.opened_at is not None def record_failure(self): self.failures += 1 if self.failures >= self.threshold: self.opened_at = time.time() def with_guard(breaker: CircuitBreaker, timeout: float = 5.0): def decorator(fn): @wraps(fn) def wrapper(*args, **kwargs): if breaker.is_open: raise RuntimeError("熔断器打开,降级处理") try: # 用 signal/线程池实现真正的超时中断 result = fn(*args, **kwargs) return result except Exception: breaker.record_failure() raise return wrapper return decorator护栏的本质是把"工具调用"当作外部依赖来治理——它有失败率、有延迟、有降级策略,和治理一个第三方 API 完全一样。
六、第五层防护:全链路审计,让每一次调用"可查、可追、可复盘"
安全建设到最后,拼的是"出事之后能不能查清"。审计要覆盖四个维度:
| 审计维度 | 记录内容 | 用途 |
|---|---|---|
| 调用轨迹 | 谁(agent_id)在何时调了哪个工具,传了什么参数 | 定位越权与误调用 |
| 数据流转 | 工具返回值进了哪个上下文、有没有被写入外部 | 追踪数据泄露路径 |
| 审批记录 | 危险操作谁审批的、审批依据 | 责任到人 |
| 异常信号 | 失败率突增、参数异常、脱敏命中 | 触发告警与复盘 |
审计落地的关键是统一 trace_id:用户请求从进入 Dify 工作流开始,生成一个 trace_id,贯穿入口、路由、各 Agent、各工具调用、出站请求。出问题时一条命令拉出完整链路:
# audit_logger.py - 结构化审计日志 import json import time import uuid def log_tool_call(trace_id: str, agent_id: str, tool: str, params: dict, result_status: str, latency_ms: int): entry = { "ts": time.time(), "trace_id": trace_id, "agent_id": agent_id, "tool": tool, # 参数必须脱敏后再落日志 "params_masked": mask_response(json.dumps(params, ensure_ascii=False)), "result_status": result_status, "latency_ms": latency_ms, } # 写入审计存储(追加写,禁止修改) with open(f"/var/log/agent_audit/{trace_id}.jsonl", "a") as f: f.write(json.dumps(entry, ensure_ascii=False) + "\n")审计日志只追加、不修改,配合"敏感操作双人复核"(高危工具调用需要第二个管理员在审批台确认),基本堵住了"内部作案 + 事后删库"的路径。
七、实战落地:DeepSeek + Dify 企业客服多 Agent 的沙箱改造
把五层防护串起来,看一个真实改造案例。假设你有一套基于 DeepSeek + Dify 的客服多 Agent 系统(订单、售后、物流、知识库四个 Agent),改造前"所有 Agent 能调所有工具",改造后按五层模型重构:
第一步:盘点工具,建立清单。把系统里 12 个工具全部登记进tool_manifest.yaml,标清副作用级别:8 个只读(查订单、查物流……)、3 个变更型(改地址、退款登记……)、1 个危险(导出客户数据)。危险工具默认对全部 Agent 关闭。
第二步:授权矩阵收口。订单 Agent 只能调订单域工具;售后 Agent 只能调售后域工具;知识库 Agent 只有只读权限。任何跨域调用都要走审批。
第三步:代码类工具沙箱化。系统里有一个"批量生成催单话术"的脚本工具,原来是subprocess.run直接跑。改造后放入 Docker 沙箱:断网、只读、256M 内存、10 秒超时,用完即删。
第四步:凭据托管。原来写在环境变量里的 5 个 API Key 全部迁入凭据服务,按 Agent 签发 5 分钟有效期的短期令牌,模型上下文里永远只有业务参数。
第五步:挂上护栏与审计。每个工具调用经过ToolGate拦截器(校验 + 限流 + 审批)→ 熔断器 → 审计日志。修改地址这类变更型操作,超过每日次数上限自动告警。
第六步:灰度验证再全量。改造先在一个影子 Agent 上跑一周,对比改造前后的成功率、延迟、误拦截率;确认无误后再全量切换。沙箱化后个别工具的延迟会上升(容器启动开销),通过预拉镜像、容器复用池把平均延迟压回可接受范围。
改造后踩过的三个高频坑,提前帮你排掉:
- 误拦截风暴:参数正则写得过严,合法订单号被拒,客服工单量瞬间上涨。解法:正则先宽后严,上线前用历史真实参数跑一遍回归。
- 沙箱启动开销:每个工具调用都现拉镜像,冷启动 2~3 秒,用户等不起。解法:常驻容器池 + 预热镜像,把冷启动摊到池子里。
- 审计日志爆炸:全量记录所有参数导致存储暴涨。解法:分级采样——只读工具记录摘要,变更型和危险工具记录全量参数(脱敏后)。
改造后的效果对比:
| 维度 | 改造前 | 改造后 |
|---|---|---|
| 工具权限 | 全 Agent 全量放开 | 按清单 + 授权矩阵收口 |
| 代码执行 | 本机 subprocess 裸跑 | Docker 沙箱:断网只读限资源 |
| 密钥管理 | 环境变量硬编码 | 凭据服务短期令牌,不进上下文 |
| 失败处理 | 失败就重试,重试就雪崩 | 超时 + 退避 + 熔断降级 |
| 事故追溯 | 靠回忆 | trace_id 全链路审计 |
这套改造不需要换技术栈——Dify 的工具节点外挂一层网关即可实现,DeepSeek 负责"理解与决策",安全由确定性代码兜底。让模型做模型擅长的事(意图理解、方案生成),把安全交给代码做代码擅长的事(校验、隔离、审计)。
八、工具安全成熟度模型:你的系统在第几级?
给团队做安全评审时,我习惯用五级成熟度给系统定位,方便确定下一步改什么:
| 级别 | 特征 | 典型状态 |
|---|---|---|
| L0 裸奔 | 工具全量放开,无校验 | "所有 Agent 能调所有工具" |
| L1 清单 | 有工具清单,无强制拦截 | 清单躺在文档里,代码不执行 |
| L2 拦截 | 拦截器强制校验 + 授权矩阵 | 能拦住越权调用,但无沙箱 |
| L3 隔离 | 沙箱执行 + 凭据托管 | 危险工具跑在受限环境,密钥不落地 |
| L4 治理 | 护栏 + 全链路审计 + 灰度 | 可量化、可追溯、可回滚 |
多数团队从 L0 起步,直接跳到 L2 是性价比最高的第一跳(清单 + 拦截器半天就能落地);L3 优先覆盖"代码执行类"和"数据导出类"高危工具;L4 是持续运营,配合上一期的安全边界治理一起做。成熟度评估的价值不是打分,而是让团队知道下一个动作是什么。
九、总结与最佳实践清单
工具权限与安全沙箱,本质是回答一个问题:当 Agent 拥有了"手",我们怎么保证它永远只做该做的事?五层防护缺一不可:
- 权限层:声明式工具清单 + 授权矩阵 + 调用拦截器,把"能不能调"变成代码强制;
- 沙箱层:容器隔离 / 断网 / 只读 / 限资源,把"跑在哪"变成一次性受控环境;
- 凭据层:密钥不落地、不流转、不硬编码,短期令牌 + 最小权限;
- 护栏层:超时、退避、熔断、幂等、输出过滤,把"调得稳"变成工程约束;
- 审计层:trace_id 全链路 + 脱敏日志 + 双人复核,把"查得清"变成制度保障。
三条心法供参考:
- 默认拒绝:没有在清单里显式授权的调用,一律拒绝。从"白名单思维"出发,而不是"黑名单修补"。
- 最小权限:每个 Agent 只拿完成任务所需的最小工具集、最小凭据、最短有效期。
- 安全是代码,不是提示词:永远不要用"请小心处理"这种提示词约束安全,安全必须由确定性的代码路径强制。
回顾开头的场景:那个差点删掉生产数据的运维 Agent,改造后即使模型被诱导输出恶意命令,也会被拦截器拦下(权限外调用直接拒绝)、被沙箱兜住(断网只读,删无可删)、被审计记录(trace_id 完整留痕)。工具赋予 Agent 能力,安全决定 Agent 的边界——有能力但不越权,才是一个能上生产的智能体。
下一期预告:多 Agent 系统的可观测性与追踪——当十几个 Agent 协作时,怎么实时看清"谁在干什么、卡在哪里、哪里慢了",把系统的"黑盒"变成"明镜"。
📚 延伸阅读
如果你对 DeepSeek 的实战用法感兴趣,推荐阅读我的另一篇文章:
👉 DeepSeek 实战指南:提示词工程、API 集成与效率提升全攻略
这篇文章系统地拆解了 DeepSeek 的提示词工程技巧、API 封装方法以及日常效率提升场景,全文代码可直接运行,适合已经上手 DeepSeek 但希望更高效使用的开发者。
Dify 多 Agent 实战系列:
- Dify 多 Agent 任务路由与负载均衡实战:让每个请求都被"最合适的智能体"接住
- Dify 多 Agent 记忆架构实战:让智能体"记得住、想得起、用得上"
- Dify 多 Agent 安全边界与权限治理实战:让每一个智能体都"知道边界、守得住门"
- Dify 多 Agent 上下文工程实战:让智能体"记住该记住的,忘掉该忘掉的"
- Dify 多 Agent 故障演练实战:用混沌工程主动"搞破坏",让智能体系统越炸越稳
- Dify 多 Agent 灰度发布实战:让每一次变更都"小步快跑、随时可回滚"
- Dify 多 Agent 评测实战:用 DeepSeek-R1 当裁判,打造多智能体系统的自动化质量保障体系
本文是"华为云Flexus+DeepSeek征文"系列文章之一。该系列基于华为云 Flexus 云服务器与 MaaS 平台 DeepSeek 推理服务,结合 Dify 工作流引擎,从部署、Agent 开发、评测、成本治理、稳定性建设、安全治理、记忆架构、任务路由到工具沙箱,系统拆解企业级 AI 应用的工程实践。