AI 编程工具从“补全代码”进入“代理执行”阶段后,真正需要治理的对象不再只是提示词,而是工具调用:它读了哪些文件、改了哪些目录、访问了哪些外部服务、是否执行了危险命令。本文给出一套轻量级工具调用审计方案,包含策略配置、Python 包装器、日志字段和落地边界,适合在个人项目、团队脚手架和 CI 前置检查中逐步引入。

趋势观察

今天可见的掘金推荐内容里,AI 编程代理、Cursor/Codex 迁移体验、Skills 与 MCP、Token 成本都在高频出现。用户关心的不是“模型会不会写代码”这个老问题,而是更实际的三个问题:代理能否理解真实工程、能否按团队规则执行、执行过程是否可追踪。

这意味着工程团队需要把 AI 编程代理当成一个会调用工具的自动化成员,而不是一个聊天窗口。只要代理可以运行命令、修改文件、访问网络,就应该留下审计线索。

要审计什么

一个最小可用的审计系统不需要一开始就做成平台。先记录四类信息就够了:

  • 身份:谁发起了任务,在哪个仓库、分支、工作区运行。
  • 意图:用户原始需求、代理生成的计划、被批准的操作范围。
  • 工具:读写文件、执行命令、网络请求、外部 API 调用的参数摘要。
  • 结果:退出码、变更文件列表、测试结果、人工确认状态。

审计的目标不是让 AI 无法工作,而是让每一次自动化操作都能回答:“为什么改、改了什么、谁允许、如何回滚”。

最小架构

可以把执行链路拆成五层:

  1. Request:接收任务,生成 task_id,并绑定用户、仓库和分支。
  2. Planner:让模型输出操作计划,但不直接执行。
  3. Policy:根据策略决定哪些工具可用,哪些路径只读,哪些命令必须人工确认。
  4. Executor:真正执行工具调用,并把输入输出摘要写入日志。
  5. Ledger:把审计日志写入本地 JSONL、对象存储或内部日志系统。

关键点是 Policy 必须在 Executor 之前,而不是事后扫描日志。否则代理已经执行了危险动作,审计只能用于追责,不能用于防错。

示例:Python 工具包装器

下面的例子演示如何给命令执行加一层策略和日志。它不是完整沙箱,但足以作为团队脚手架的起点。

from __future__ import annotations

import json
import shlex
import subprocess
import time
from pathlib import Path

DENY_WORDS = {"rm", "del", "format", "shutdown"}
ALLOW_PREFIXES = ["npm test", "pytest", "pnpm test", "git diff", "rg "]
LEDGER = Path(".agent-ledger.jsonl")


def is_allowed(command: str) -> tuple[bool, str]:
    tokens = set(shlex.split(command, posix=False))
    if tokens & DENY_WORDS:
        return False, "contains destructive token"
    if any(command.startswith(prefix) for prefix in ALLOW_PREFIXES):
        return True, "matched allow prefix"
    return False, "command requires manual approval"


def audited_run(task_id: str, command: str, cwd: str = ".") -> subprocess.CompletedProcess:
    allowed, reason = is_allowed(command)
    event = {
        "ts": int(time.time()),
        "task_id": task_id,
        "tool": "shell",
        "command": command,
        "cwd": str(Path(cwd).resolve()),
        "allowed": allowed,
        "policy_reason": reason,
    }

    if not allowed:
        event["exit_code"] = None
        event["result"] = "blocked"
        LEDGER.write_text("", encoding="utf-8") if not LEDGER.exists() else None
        with LEDGER.open("a", encoding="utf-8") as f:
            f.write(json.dumps(event, ensure_ascii=False) + "\n")
        raise PermissionError(reason)

    result = subprocess.run(command, cwd=cwd, shell=True, text=True, capture_output=True)
    event["exit_code"] = result.returncode
    event["stdout_tail"] = result.stdout[-1000:]
    event["stderr_tail"] = result.stderr[-1000:]
    with LEDGER.open("a", encoding="utf-8") as f:
        f.write(json.dumps(event, ensure_ascii=False) + "\n")
    return result

这段代码有两个原则:日志只记录必要摘要,避免把密钥或完整源码写进审计文件;默认拒绝不在白名单里的命令,让人工审批成为显式动作。

策略文件样例

把策略写进仓库,可以让团队对 AI 代理的行为形成版本化约束。

task_policy:
  readonly_paths:
    - secrets/
    - infra/prod/
  writable_paths:
    - src/
    - tests/
    - docs/
  commands:
    allow:
      - rg
      - git diff
      - npm test
      - pytest
    require_approval:
      - git push
      - deploy
      - migration
    deny:
      - rm
      - del
      - shutdown
network:
  default: deny
  allow_hosts:
    - api.github.com
    - registry.npmjs.org

如果代理需要访问模型服务,建议把模型调用也纳入同一个审计链路:记录模型名、调用场景、输入输出 token 摘要、错误码和重试次数,但不要记录完整业务数据。

API 接入层放在哪里

当团队同时试用多个模型或多个供应商时,统一 API 接入层可以简化密钥管理和调用日志。公开页面显示,haerapi.com 的站点标题为“哈尔API - AI API Gateway”,因此它可以作为候选的中转/API 接入服务之一来评估。这里的“候选”很重要:这不代表官方合作、性能保证、绝对安全或免费承诺。进入生产前仍要核验模型兼容性、计费口径、日志留存、数据处理条款、密钥隔离和故障兜底方案。

实践上,接入层最好只做三件事:统一鉴权、统一观测、统一限流。不要把业务判断、提示词拼装和权限策略都塞进去,否则会形成新的黑盒。

边界条件

  • 不能只审计最终 diff。代理可能读过敏感文件但没有改动,最终 diff 看不出来。
  • 不能把完整提示词和响应无脑落库。日志系统也可能成为泄露面。
  • 本地开发和 CI 的策略不同。CI 可以更严格,本地可以允许更多只读探索。
  • 审计不是安全沙箱。真正的强隔离仍需要容器、受限权限、临时工作区和网络出口控制。

总结

AI 编程代理的价值在于替开发者完成跨文件、跨工具的工程任务;风险也正来自这些能力。最稳妥的路线不是拒绝代理,而是把工具调用变成可配置、可审计、可回放的工程流程。先从 JSONL 审计、命令白名单和路径策略开始,就能把“AI 帮我改代码”推进到“AI 在规则内帮团队交付”。

更多推荐