25-让大模型安全调用外部工具-参数权限超时控制

让大模型安全调用外部工具:参数校验、权限和超时控制

系列:Python + FastAPI 大模型应用基础(第 25 篇)

1. 工具调用是一条不可信输入链

模型生成的工具名和参数与普通用户输入一样不可信。安全执行器至少经过:

text 复制代码
工具白名单 → 参数 Schema → 身份权限 → 业务规则
→ 超时/并发 → 幂等执行 → 审计结果

2. 定义调用上下文

python 复制代码
from dataclasses import dataclass
from pydantic import BaseModel, ConfigDict, Field


@dataclass(frozen=True)
class ExecutionContext:
    request_id: str
    user_id: str
    tenant_id: str
    permissions: frozenset[str]


class SendMessageArgs(BaseModel):
    model_config = ConfigDict(extra="forbid")
    recipient_id: str = Field(pattern=r"^USER-[0-9]{1,12}$")
    text: str = Field(min_length=1, max_length=1000)
    idempotency_key: str = Field(min_length=16, max_length=100)

模型不提交 tenant_id 和 user_id,这两项只能来自登录会话。

3. 带超时的异步执行器

python 复制代码
import asyncio
from collections.abc import Awaitable, Callable
from typing import Any


async def run_with_timeout(
    operation: Callable[[], Awaitable[dict[str, Any]]],
    timeout_seconds: float,
) -> dict[str, Any]:
    if not 0 < timeout_seconds <= 30:
        raise ValueError("工具超时必须在 0 到 30 秒之间")
    try:
        return await asyncio.wait_for(operation(), timeout_seconds)
    except TimeoutError as exc:
        raise RuntimeError("工具执行超时") from exc


async def send_message(
    ctx: ExecutionContext,
    args: SendMessageArgs,
) -> dict[str, Any]:
    if "message:send" not in ctx.permissions:
        raise PermissionError("无消息发送权限")

    async def operation() -> dict[str, Any]:
        # 示例不连接真实消息平台;生产实现需传递幂等键
        await asyncio.sleep(0)
        return {
            "status": "accepted",
            "recipient_id": args.recipient_id,
            "request_id": ctx.request_id,
        }

    return await run_with_timeout(operation, timeout_seconds=3)

accepted 仅表示示例执行器接受请求,不代表真实消息送达。真实系统应区分接受、发送、送达和失败。

4. 幂等性防止重复副作用

python 复制代码
class InMemoryIdempotencyStore:
    """演示存储;多进程生产环境应使用共享原子存储。"""

    def __init__(self) -> None:
        self._results: dict[str, dict[str, Any]] = {}
        self._lock = asyncio.Lock()

    async def execute_once(
        self,
        key: str,
        action: Callable[[], Awaitable[dict[str, Any]]],
    ) -> dict[str, Any]:
        async with self._lock:
            if key in self._results:
                return self._results[key]
            result = await action()
            self._results[key] = result
            return result

真实幂等设计还需处理"外部调用成功但本地记录失败"的不确定状态,常用外部幂等键、事务消息或人工对账。

5. 可复验测试

python 复制代码
async def test_permission_denied() -> None:
    ctx = ExecutionContext("r1", "u1", "t1", frozenset())
    args = SendMessageArgs(
        recipient_id="USER-1",
        text="测试",
        idempotency_key="1234567890abcdef",
    )
    try:
        await send_message(ctx, args)
    except PermissionError:
        pass
    else:
        raise AssertionError("缺少权限时必须拒绝")


async def test_idempotent_execution() -> None:
    store = InMemoryIdempotencyStore()
    calls = 0

    async def action() -> dict[str, Any]:
        nonlocal calls
        calls += 1
        return {"ok": True}

    await store.execute_once("key", action)
    await store.execute_once("key", action)
    assert calls == 1

6. 对抗性审查

  • 禁止任意 Shell、任意 SQL 和任意 URL 工具;
  • 写操作默认需要更高权限或人工确认;
  • 幂等键不能由模型随意重复使用;
  • 工具输出限制大小,防止挤占上下文;
  • 请求超时后不能假定外部操作一定失败;
  • 工具凭证采用短期、最小范围授权;
  • 审计日志不保存消息正文等敏感数据。

7. 总结

安全工具调用不是在 Prompt 里写几句禁止事项,而是把模型限制在确定性的执行器之后,并为每个副作用设计权限、超时和幂等语义。

相关推荐
喵个咪4 小时前
GoWind 拆解:全链 ID 类型体系——int64 定版与前端字符串化契约
前端·后端·go
喵个咪4 小时前
GoWind CMS 拆解:Flutter 设计系统内核——双态 token、四档断点与自适应壳层
前端·flutter·交互设计
2601_967980115 小时前
Python自动化办公:模块、库与样式调整教程
python·excel·自动化办公·openpyxl·样式调整
喵个咪5 小时前
GoWind CMS 拆解:发布审批——多级推进、逐级角色与全程留痕
前端·后端·go
喵个咪5 小时前
GoWind Flow 拆解:一个 Go 工作流引擎与它的双模式设计器
前端·后端·go
长沙三为智能科技5 小时前
GEO与传统SEO的5个关键差异:排名与被引用、关键词与答案型的全维度对比
python·aigc·seo·ai搜索·geo优化
喵个咪5 小时前
GoWind Admin 拆解:打印模板设计器——从画布到服务端 PDF 的完整打印链路
前端·后端·go
总要学点什么5 小时前
第96篇:三维数据加载-城市建筑白模样式+着色器——让色带按体量上色,最白的那档却涂不上去
前端·gis·webgl·cesium·着色器·空间分析·白模
Hilaku6 小时前
你们公司有 35 岁以上的前端吗?
前端·javascript·程序员
徐小夕6 小时前
开源Vector-Graph-RAG:向量+图谱双引擎,打破纯向量RAG的效果天花板
前端·vue.js·github