前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
返回 AI 情报前线
All News · 全部资讯8836
  • Mercury 2.5 推理速度达 770 tokens/秒
  • GitHub Copilot 代码审查新增个人配置选项
  • MCP单人上手容易,团队规模落地是另一回事
  • 影子测试100%一致率背后:模型实际正确率仅75%
  • 两个都通过的测试,代价却不同:重试的隐性成本
  • 定时运行AI Agent输出飘移的根因与修复
  • Anthropic如何两周将Claude.ai速度提升3倍
  • claude-code-templates:一键装配 Claude Code 开发套件,含 100+ Agent/MCP
  • Agent 删改测试必须拦截:CI 合并门禁实操方案
  • Google Antigravity SDK 支持本地 AI 模型:Gemma 4 26B 可离线跑
  • NVIDIA Warp 与 MjWarp 加速机器人仿真工作流
  • HEMA 用 MCP 和 Amazon Bedrock 实现内部 AI 助手转型
  • 五大LLM网关工具生产环境横评
  • GitHub Copilot应用如何渲染百万行PR
  • 基于 AWS 构建 Agent 式视频智能对话系统架构解析
  • Bedrock 上用开源权重模型做 AI 编程助手
  • Anthropic 实验室:Claude 自主发现类 CRISPR 新型酶系统
  • ChatGPT Voice 集成邮件、日历和 Slack:Altman 心中的"Her"更近一步
  • OpenAI GPT-6 Sol/Luna 和 Claude Opus 5.5 同步降价 50%
  • AI 工具循环必须显式传递 Retry-After 头否则必死循环
  • AI 代码补丁静默引入新工具调用:merge 前必须强制契约检查
  • AI 写 API 文档无法区分 null/0/缺省三态:OpenAPI 契约必须显式约束
  • AI 编程 Agent 工具输出遭截断:应记录 stdout_bytes 和截断标志
  • Anthropic工程师揭秘:Claude为何越进化写作越差
  • Gemini 3.8 Flash / Flash-Lite TTS 发布:千款语音、30秒克隆、逐行台词控制
  • 工程师详解:新版Claude为何写作风格变得怪异
  • 小米MiMo-V3将搭载HySparse 2:100万Token下KV缓存缩小4.5倍
  • GitHub Copilot 应用新增本地沙箱隔离功能
  • AI Agent 调试指南:重启不是调试,七层架构定位根因
  • AI 加剧软件供应链攻击威胁,行业如何应对
  • 阿里 Qwen Audio 3.1 发布:语音识别/TTS 多模型,API 价格最高降 95%
  • Claude Code部署到Lizard平台实战指南
  • Claude Opus 5.5降价却破坏四个Agent依赖项
  • OpenAI GPT-6 Sol/Luna 半价发布,缓存机制或为更大降本杠杆
  • treg:聚合 3000+ Agent 工具的统一网关
  • 向量检索权限校验应内嵌到 pgvector 查询中
  • Univer:面向 AI Agent 的开源办公套件 SDK
  • DeepSeek公开Agent训练新论文,梁文锋署名
  • 为 AI 编程 Agent 构建可复用技能系统的实践
  • DeepMind研究:百个AI智能体协作求解时出现作弊与告密现象
  • Google AX:开源Agent编排运行时
  • 阿里千问发布Qwen-Audio-3.1:TTS降价70%、ASR降价95%
  • 诺基亚开源AnyJev:无训练即可将任意开源LLM转为校准决策模型
  • 2026年AI网关横评:Bifrost领跑,多路 failover 哪家强
  • GPT-6 Sol/Luna 发布:准确率翻倍、成本减半,价格战开启
  • Claude Opus 5.5 登场,AI 模型价格普降 40-50%
  • Kyutai开源语音模型Voice of Reason,口算GSM8K准确率从27%升至77%
  • 微软Copilot大促:10万席最高半价,向超级AI应用转型
  • OpenAI GPT-6 Sol/Luna:API价格腰斩,长任务Prompt缓存优化
  • AI 编码 Agent 在移动端多久“看”一次才够用
  • GPT-6 Astra 引领 3D 生成技术,竞争格局生变
  • 已加载 51 / 8836
8.0
热点
AI SCORE
技术实践2026-09-24 00:31

AI 代码补丁静默引入新工具调用:merge 前必须强制契约检查

dev.to · AI#AI Agent#安全#代码审查
Editor brief · 编辑速览

AI 在重构时可能悄悄为重试逻辑添加额外工具调用(如退款接口),单元测试只验证返回值不监控出站调用面,建议用 JSON 契约文件白名单化所有合法工具。

文章思维导图
Knowledge map
拖拽缩放
Full translation

完整中文译文

一个支付 Worker 坐在周二的合并队列里。一个 Agent 在夜里重写了重试辅助函数。本地单元测试依然通过,且非常安静。随后生产环境的追踪记录显示了一个新的出站工具名称。

这个辅助函数做的不仅仅是重试失败的扣款。它还在未经审查的情况下调用了退款端点。没有任何测试密切关注出站调用表面。绿灯意味着函数返回了。它并不意味着网络请求保持关闭。

Agent 的修补以这种安静的模式失败。它们在重构中添加了一个有用的工具。对本地返回值的断言错过了额外的调用。俱乐部一夜之间多了一扇侧门。保镖仍然只守着正门的绳子。

把工具表面当作一份打印的来宾名单。名单上的名字可以进入房间。一个新名字会在门口被拦下,无需争辩。额外的 JSON 字段算作新名字。人类在审查中拥有这份名单。Agent 不能编辑那个文件。

这篇文章提出了一个合并门禁,不是一篇回忆录。没有声称生产环境的计时或通过率。代码是一个带标签的、可运行的草图。在信任 CI 之前,先在笔记本电脑上执行它。

在服务代码旁边提交一份 JSON 契约。该文件命名了每个允许的工具。它用封闭模式固定参数形状。它还固定了调用者可能抛出的错误类。

{
  "tools": {
    "charges.create": {
      "type": "object",
      "required": ["amount_cents", "currency", "idempotency_key"],
      "additionalProperties": false,
      "properties": {
        "amount_cents": {"type": "integer", "minimum": 1},
        "currency": {"enum": ["usd", "eur"]},
        "idempotency_key": {"type": "string", "minLength": 16}
      }
    },
    "charges.get": {
      "type": "object",
      "required": ["charge_id"],
      "additionalProperties": false,
      "properties": {
        "charge_id": {"type": "string", "pattern": "^ch_"}
      }
    }
  },
  "errors": ["Timeout", "Conflict", "Unavailable"]
}

Agent 可以在 src/ 下修补 Python。它不能在那条路径中修补 tool_surface.json。审查者将表面编辑视为产品变更。这种分离使契约与人类保持一致。

在测试运行前包装 HTTP 或工具客户端。记录工具名称、参数和错误类。为每次调用写入一行 JSON。在你已经拥有的表征测试中执行此操作。不要等待生产流量来注意漂移。

# trace_client.py
import json
from pathlib import Path

class TracingClient:
    def __init__(self, inner, sink: Path):
        self.inner = inner
        self.sink = sink

    def call(self, name: str, args: dict):
        record = {"name": name, "args": args, "error": None}
        try:
            return self.inner.call(name, args)
        except Exception as exc:
            record["error"] = type(exc).__name__
            raise
        finally:
            with self.sink.open("a") as handle:
                handle.write(json.dumps(record, sort_keys=True) + "\n")

将表征套件指向 TracingClient。将接收器保存在临时路径上。在表征运行通过后复制接收器。将每一行与已提交表面文件进行差异比较。新工具名称会使门禁失败。新参数键会使门禁失败。未知错误类会使门禁失败。

门禁测试看起来应该几乎无聊。无聊的检查在审查中经受住 Agent 的魅力。诗意的断言会被重写以保持绿色。模式检查不会讨价还价。

# test_tool_surface.py
import json
from pathlib import Path
import jsonschema

SURFACE = json.loads(Path("tool_surface.json").read_text())

def load_trace(path: Path):
    rows = []
    for line in path.read_text().splitlines():
        if line.strip():
            rows.append(json.loads(line))
    return rows

def test_trace_stays_inside_surface(tmp_path):
    sink = tmp_path / "trace.jsonl"
    # Proposal: call your real characterization suite here.
    run_characterization(sink)
    rows = load_trace(sink)
    assert len(rows) >= 100, "empty traces cannot pass the surface gate"
    for row in rows:
        assert row["name"] in SURFACE["tools"], row
        jsonschema.validate(row["args"], SURFACE["tools"][row["name"]])
        if row["error"] is not None:
            assert row["error"] in SURFACE["errors"], row

一个封闭的列表仍然不是一个正确的列表。有效字段可以转移错误的资金。在表面保持后,向已知工具投掷垃圾。客户端必须在线上之前拒绝垃圾。它不能打开一个新工具来提供帮助。

# test_tool_properties.py
import json
from pathlib import Path
from hypothesis import given, strategies as st

SURFACE = json.loads(Path("tool_surface.json").read_text())
NAMES = list(SURFACE["tools"])

class StrictClient:
    def __init__(self, inner, surface):
        self.inner = inner
        self.surface = surface

    def call(self, name, args):
        if name not in self.surface["tools"]:
            raise ValueError("unknown tool")
        schema = self.surface["tools"][name]
        extra = set(args) - set(schema.get("properties", {}))
        if extra:
            raise ValueError(f"extra fields: {extra}")
        return self.inner(name, args)

@given(
    name=st.sampled_from(NAMES),
    extra=st.dictionaries(st.text(min_size=1, max_size=8), st.integers(), min_size=1),
)
def test_unknown_fields_never_hit_the_wire(name, extra):
    sent = []

    def fake_call(tool_name, args):
        sent.append((tool_name, args))
        return {"ok": True}

    client = StrictClient(inner=fake_call, surface=SURFACE)
    try:
        client.call(name, {"_probe": True, **extra})
    except ValueError:
        assert sent == []
        return
    raise AssertionError("strict client must reject extra fields")

Hypothesis 在这里的使用是一个带标签的提案。将策略调整到已提交的架构。不要让 Agent 拥有策略模块。应用与 tool_surface.json 相同的审查分离。

不稳定的测试仍然在繁忙的夜晚困扰着 Agent 合并。一个失败的 flake 看起来像是扩展。一个通过的 flake 可能隐藏一个新工具。这些测试必须离开合并投票。将它们的 nodeid 移入隔离文件。CI 在每次 Agent 修补时读取该文件。隔离的名称不能使任务失败。它们也不能使任务通过。

# conftest.py
from pathlib import Path
import pytest

FROZEN = {
    line.strip()
    for line in Path("flake_quarantine.txt").read_text().splitlines()
    if line.strip() and not line.startswith("#")
}

def pytest_collection_modifyitems(config, items):
    for item in items:
        if item.nodeid in FROZEN:
            item.add_marker(pytest.mark.skip(reason="flake quarantined; no merge vote"))
# flake_quarantine.txt
# nodeids only. Agent patches may not edit this file.
tests/test_retry.py::test_eventual_success

隔离是一个流程冻结,不是一个愿望。人类在一个安静的分支上复现 flake。然后测试返回投票或消亡。Agent 不会与那个列表讨价还价。规则就是控制。

首先运行表征测试,然后是表面门禁。在这两个保持绿色后运行属性检查。始终保持隔离的 nodeid 被跳过。

python -m pytest tests/characterization -q --trace-sink=/tmp/trace.jsonl
python -m pytest tests/test_tool_surface.py -q
python -m pytest tests/test_tool_properties.py -q
python -m pytest tests --strict-markers -q

当 tool_surface.json 与 Agent 代码一起移动时,使构建失败。一个小的 git 检查强制执行人类分离。审查者仍然可以一起更改两个文件。他们在一个单独的人类提交中执行此操作。

changed=$(git diff --name-only origin/main)
echo "$changed" | grep -q 'tool_surface.json'
code_moved=$?
echo "$changed" | grep -qE '^(src/|app/)'
src_moved=$?
if [ "$code_moved" -eq 0 ] && [ "$src_moved" -eq 0 ]; then
  echo "surface and service code moved together" >&2
  exit 1
fi

一些团队在合并日缺乏空闲的 CI 分钟。测试工具是 pytest、JSON 和一个模式库。它可以在笔记本电脑上与 diff 一起运行。当笔记本电脑忙碌时,它也可以在免费的远程服务器上运行。MonkeyCode 提供免费模型访问和免费服务器选项。披露:本文是作为 MonkeyCode 产品推广的一部分准备的。仅使用免费模型为属性文件生成额外的无效 payload。永远不要使用它来编辑 tool_surface.json。永远不要使用它来编辑 flake_quarantine.txt。服务器应该运行你本地运行的相同命令。如果这两个文件在作业中发生变化,则拒绝该作业。

封闭的工具表面不是语义安全。Agent 仍然可以传递错误的金额。模式允许任何大于零的整数。幂等键可以匹配形状但仍然冲突。属性检查在门口捕获额外字段。它们不会捕获有效字段中的业务谎言。

高变动率的公共 API 会经常触发这个门禁。这种痛苦是一个信号,不是缺陷。在人类拥有的变更中更新表面。不要将 additionalProperties 设置为 true 来让 Agent 沉默。

有目的地为研究 Agent 跳过这种方法。那些 Agent 必须探索未列出的工具。封闭的来宾名单会阻止那项工作。不要将其作为支付中的唯一控制。添加账本、限制和人工审查。当无法收集追踪时不要安装门禁。没有追踪的门禁只是 theater。

如果没有调用工具,空的表征仍然会通过。将模式差异与最小追踪计数配对。一百次调用只是一个起始提案。在复制之前根据你自己的套件进行衡量。不要将那个数字当作法律。

修补可以在辅助函数中保持聪明。门禁列表故意保持无聊。无聊的列表是合并队列真正能够执行的。

Original source

本文由 AI 翻译整理自 dev.to · AI,原文版权归原作者所有。

阅读英文原文
上一篇
AI 工具循环必须显式传递 Retry-After 头否则必死循环
下一篇
AI 写 API 文档无法区分 null/0/缺省三态:OpenAPI 契约必须显式约束