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

相关推荐
运行时异常9 小时前
【WMS 仓储系统集成 AI Agent 实战】第 8 讲(终篇):生产部署与并发安全——Semaphore 放进 Flux.defer 的坑,压测抓了一晚上
人工智能·安全
FungLeo9 小时前
成为全栈·React 管理后台篇·总复盘:从“接口能调通”到“后台值得使用”
前端·react.js·前端框架·项目复盘·成为全栈
杨杨杨大侠9 小时前
Jev、Kev、Laya:决策模型怎么选,什么时候需要微调?
人工智能·python·agent
虹科网络安全9 小时前
“顶会”看安全(十八):超越越狱:揭示由能力边界模糊引发的 LLM 应用安全风险
人工智能·安全
hasty10 小时前
OAuth 登录成功,不代表邮箱可信:Flarum 账号关联漏洞的安全编码启示
安全
LOVE️YOU10 小时前
Python 在定义函数时,究竟定义的是什么?
python
段一凡-华北理工大学10 小时前
大模型应用开发 100 天:Python + LLM 从入门到精通 day26~Prompt 调试与优化——A/B 测试与效果评估
windows·python·大模型·prompt·智能体·提示词工程·高炉智能化
Minecraft红客10 小时前
以撒的结合
python·游戏·电脑·娱乐
Circ.10 小时前
Python 调试 Litellm 本地大模型接口:模型列表与对话接口实操踩坑记录
开发语言·python
Csvn11 小时前
并发模式:让渲染学会排队、插队和让路
前端