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 里写几句禁止事项,而是把模型限制在确定性的执行器之后,并为每个副作用设计权限、超时和幂等语义。

相关推荐
A小码哥1 分钟前
大模型成本怎么降,5 个动作先做前两个
java·前端·spring
全栈弄潮儿5 分钟前
Python实战第9期:文件操作
后端·python·编程语言
小星星_ios10 分钟前
从 3Blue1Brown 到 NumPy:手写一个神经网络训练 MNIST
人工智能·python·神经网络
PHP是世界上最好的语言13 分钟前
ComPDF预览本地正常,部署后白屏:检查资源、路径和许可
前端
Maxwin13 分钟前
Manim 竖屏画布选 9×16 还是 4.5×8?从 font_size 和每单位像素数说起
python·ffmpeg
kiros_wang15 分钟前
项目分包方案:模块拆分、跨模块组件与方法引用规范
前端
PedroQue9917 分钟前
unix-utils v0.2.0 发布:modal 全端统一,自绘窗口解决输入框难题
前端·uni-app
Csvn18 分钟前
网络优化
前端
昭昭日月明20 分钟前
使用 Ragas 量化 RAG
python·langchain·ai编程
代码猎人21 分钟前
前端怎么跟进生态新技术
前端