Agent 的信任不在提示词里,而在运行时里

2026-07-14 27 预计阅读时间: 1 分钟
来源: docker.com AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:9 分钟

AI Engineer World’s Fair 2026 的现场讨论指向一个很实际的判断:决定 Agent 能否进入生产环境的,不只是模型会不会规划和调用工具,更是运行时能否约束、观察并中止这些动作。提示词可以表达规则,但真正执行规则的地方必须是 Runtime。

来源摘要没有展开具体演讲内容,因此下面不推断某家公司发布了什么产品,而是沿着“信任在运行时赢得”这一核心命题,拆解它对 Agent 工程的直接影响。

为什么提示词不能充当安全边界

典型 Agent 循环看起来很简单:模型读取上下文,生成工具调用,工具返回结果,模型再决定下一步。然而模型输出本质上是不可信输入。即使系统提示词明确写着“不要访问工作目录之外的文件”,运行时仍然可能收到这样的参数:

{
  "tool": "read_file",
  "args": {
    "path": "../../etc/passwd"
  }
}

如果工具层直接执行这个请求,提示词中的限制就只是一句建议。可靠的 Agent Runtime 至少应当负责以下边界:

  • 能力边界:只暴露明确允许的工具,默认拒绝未知工具。
  • 参数边界:校验参数类型、必填字段、路径、域名和数值范围。
  • 资源边界:限制执行时间、输出长度、并发量、费用和重试次数。
  • 权限边界:按任务签发最小权限凭据,不把长期密钥放进模型上下文。
  • 可追溯性:记录调用者、工具、参数摘要、结果状态、耗时和审批信息。

这也解释了为什么“模型表现很好”和“系统值得信任”是两个不同结论。前者通常来自评测集,后者还需要面对网络抖动、恶意内容、权限泄漏、重复执行和部分失败。

Runtime 是 Agent 的控制平面

可以把 Runtime 理解为模型与真实世界之间的控制平面。模型提出意图,Runtime 决定这个意图是否可以执行,以及应该在什么隔离条件下执行。

一个生产级调用通常要经过这样的路径:

模型生成工具请求
      ↓
结构化解析与 Schema 校验
      ↓
策略判断:身份、资源、环境、风险等级
      ↓
可选的人工审批
      ↓
沙箱或隔离进程执行
      ↓
结果裁剪、敏感信息过滤
      ↓
审计记录与可观测性事件

这里有一个重要区别:可观测性告诉团队“发生了什么”,策略执行则决定“是否允许发生”。只有 Trace、没有阻断机制的 Runtime,能够帮助复盘事故,却不能阻止事故。

同时,Runtime 还要处理 Agent 特有的失败模式。例如模型可能因为模糊的错误信息反复调用同一工具;一个转账动作可能已经成功,但响应在返回途中超时;网页内容可能把恶意指令伪装成普通文本。这些问题分别需要调用预算、幂等键和不可信数据隔离,而不是再增加一段提示词。

可以这样实践:构造一个最小受控工具运行时

下面的示例只使用 Python 标准库,实现工具白名单、严格参数集合、工作目录约束、输出上限和 JSONL 审计日志。它不是完整沙箱,但可以作为本地原型的起点。

将以下内容放入 agent_runtime.py

from pathlib import Path
import json
import sys
import time
import uuid

ROOT = Path("workspace").resolve()
AUDIT_LOG = Path("audit.jsonl")
MAX_OUTPUT_BYTES = 16 * 1024

TOOL_SCHEMAS = {
    "list_files": {"path"},
    "read_file": {"path"},
}


def confined_path(value: str) -> Path:
    if not isinstance(value, str) or not value:
        raise ValueError("path must be a non-empty string")

    candidate = (ROOT / value).resolve()
    if candidate != ROOT and ROOT not in candidate.parents:
        raise PermissionError("path escapes the workspace")
    return candidate


def limit_output(value: str) -> str:
    raw = value.encode("utf-8")
    if len(raw) <= MAX_OUTPUT_BYTES:
        return value
    return raw[:MAX_OUTPUT_BYTES].decode("utf-8", errors="ignore") + "\n[truncated]"


def list_files(args: dict) -> str:
    directory = confined_path(args["path"])
    if not directory.is_dir():
        raise ValueError("path is not a directory")
    files = sorted(str(item.relative_to(ROOT)) for item in directory.iterdir())
    return "\n".join(files)


def read_file(args: dict) -> str:
    path = confined_path(args["path"])
    if not path.is_file():
        raise ValueError("path is not a file")
    return path.read_text(encoding="utf-8")


TOOLS = {
    "list_files": list_files,
    "read_file": read_file,
}


def execute(request: dict) -> dict:
    call_id = str(uuid.uuid4())
    started = time.monotonic()

    try:
        if set(request) != {"tool", "args"}:
            raise ValueError("request must contain exactly: tool, args")

        tool_name = request["tool"]
        args = request["args"]
        if tool_name not in TOOLS:
            raise PermissionError("tool is not allowed")
        if not isinstance(args, dict) or set(args) != TOOL_SCHEMAS[tool_name]:
            raise ValueError("tool arguments do not match the schema")

        output = limit_output(TOOLS[tool_name](args))
        response = {"call_id": call_id, "ok": True, "output": output}
    except Exception as exc:
        response = {"call_id": call_id, "ok": False, "error": str(exc)}

    event = {
        "call_id": call_id,
        "tool": request.get("tool") if isinstance(request, dict) else None,
        "ok": response["ok"],
        "duration_ms": round((time.monotonic() - started) * 1000, 2),
    }
    with AUDIT_LOG.open("a", encoding="utf-8") as log:
        log.write(json.dumps(event, ensure_ascii=False) + "\n")

    return response


if __name__ == "__main__":
    ROOT.mkdir(exist_ok=True)
    request = json.load(sys.stdin)
    print(json.dumps(execute(request), ensure_ascii=False, indent=2))

创建测试文件并执行一次合法调用:

mkdir -p workspace/docs
printf 'runtime policy\n' > workspace/docs/note.txt

python agent_runtime.py <<'JSON'
{
  "tool": "read_file",
  "args": {
    "path": "docs/note.txt"
  }
}
JSON

再验证越界请求会被拒绝:

python agent_runtime.py <<'JSON'
{
  "tool": "read_file",
  "args": {
    "path": "../../etc/passwd"
  }
}
JSON

cat audit.jsonl

这个原型仍有明确边界:它没有隔离 CPU 和内存,没有强制超时,也不能完全消除文件系统检查与读取之间的竞态。生产环境可以把工具放进独立进程、容器或微型虚拟机,并在外层设置 deadline、网络策略和只读文件系统。错误信息也应按环境脱敏,避免把内部路径返回给模型。

上线前,把信任转换成可验证条件

评估 Agent Runtime 时,可以从一组可测试的问题开始:

  • 未注册工具是否默认拒绝,而不是尝试猜测或降级执行?
  • 同一个有副作用的动作重试两次,是否只产生一次业务结果?
  • 超过时间、费用或调用次数预算后,Runtime 能否强制停止任务?
  • 工具返回的网页、邮件和文档是否被标记为不可信数据?
  • 高风险动作是否绑定身份、审批记录和短期凭据?
  • 审计日志能否还原一次任务的模型版本、策略版本和工具调用链?
  • Runtime 故障时,系统是默认拒绝,还是绕过策略继续执行?

采用 Agent 的关键,不是等模型永远不犯错,而是假设模型、工具和外部数据都可能出错,再让 Runtime 把影响限制在可接受范围内。模型决定系统能做多聪明的事;运行时决定它做错事时,系统是否仍然可控。


相关推荐