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

相关推荐
驳是1 小时前
代码洁癖患者会喜欢的几款 CLI 小工具
前端
Csvn1 小时前
🛋️ requestIdleCallback:把非关键任务塞进浏览器的"空闲时间",告别卡顿
前端
江华森1 小时前
从 fork 到协程:一次完整的操作系统并发编程实操
前端·程序员
Code额1 小时前
Python 连接 DeepSeek API,OpenAI 对话方式总结
后端·python·ai·ai编程
电化学仪器白超1 小时前
MV-CS200-10UC相机参数配置
python·单片机·嵌入式硬件·数码相机·自动化·ltspice
四千岁1 小时前
稀疏向量BM25Retriever不支持中文怎么办?jieba来帮忙
前端·javascript·后端
这是个栗子1 小时前
【JS代码分析】前端鉴权基础:Token 的本地存储与状态重置实践
开发语言·前端·javascript
颜进强1 小时前
14 - OpenSpec 老页面改造骨架:定位 + 增量 + 回归三件套
前端·后端·ai编程
做前端的娜娜子1 小时前
async/await 错误处理:try...catch vs .catch() 完全指南
前端·面试·掘金·金石计划
半个落月1 小时前
React 性能优化入门:用 memo 避免无关的子组件重复渲染
前端·react.js