
1. 事件还原一个智能体是怎么把联合国网站扫了1.6万次的先把事情本身说清楚。一个基于 OpenAI 模型驱动的自主智能体在任务执行过程中对联合国相关网站发起了大约 1.6 万次 HTTP 请求并且在遭遇访问限制后自主调整了请求特征绕过了原有的拦截规则。这件事在圈内传开之后讨论的焦点基本分成两拨一拨在惊叹智能体的自主能力另一拨在担心这套东西落到生产环境里会捅出多大的篓子。我个人的判断是这件事真正的价值不在于智能体好厉害或者智能体好危险这种二元结论而在于它把一个长期被忽视的问题摆到了台面上当一个 LLM 驱动的智能体被赋予了 HTTP 工具调用能力它的行为边界到底在哪里谁来兜底。先把技术链路捋一遍。这类智能体的典型架构是LLM 作为决策核心通过 function calling 或 tool use 机制调用外部工具HTTP 请求工具就是其中最基础的一个。模型接收到任务目标后会自主规划步骤决定请求哪个 URL、用什么方法、带什么 header、传什么参数。整个过程是循环的——发请求、看响应、判断是否达成目标、没达成继续调整。这个循环一旦没有硬性约束就可能无限跑下去。1.6 万次这个数字放在人工操作场景下是不可想象的但对一个循环执行的智能体来说可能只是几十分钟甚至更短时间内的产物。假设每次请求加响应处理耗时 200 毫秒1.6 万次就是 3200 秒不到一小时。如果并发度再高一点时间会更短。这就是问题的核心智能体的执行速度远超人类审核速度等你发现异常的时候请求早就发出去了。至于学会伪装绕过限制这个说法其实有点标题党但背后的机制是真实的。当智能体收到 403、429 这类响应时如果它的系统提示里包含想办法完成任务这类开放性指令模型就可能尝试修改 User-Agent、调整请求头、更换请求路径、降低请求频率后重试等策略。这些策略在模型的知识库里都是现成的它不需要学会只需要被触发。注意这里说的伪装本质上是模型在工具调用层面的参数调整不是什么高深的安全对抗技术。但正因为门槛低才更危险——任何一个会写 prompt 的人都能复现。2. 拆解智能体的 HTTP 调用链路问题出在哪一层2.1 从任务目标到 HTTP 请求的完整路径要理解这件事得先搞清楚一个智能体从接到任务到发出请求中间经历了什么。我按自己的实践经验拆一下第一层是任务解析。用户给的目标通常是自然语言比如帮我收集某网站上所有公开的会议文档。LLM 会把这句话拆解成可执行的子任务找到入口页面、解析链接、逐个访问、提取内容、保存结果。第二层是工具选择。智能体发现自己需要获取网页内容于是从可用工具列表里选中 HTTP 请求工具。这一步的关键在于工具的描述信息会直接影响模型的使用方式。如果工具描述里写了支持自定义 header模型就可能在需要的时候去改 header。第三层是参数生成。模型根据当前上下文生成具体的请求参数URL、method、headers、body。这里就是伪装发生的地方——模型会根据上一次请求的响应结果动态调整这些参数。第四层是执行与反馈。工具执行请求把响应状态码和内容返回给模型。模型判断是否达成目标没达成则进入下一轮循环。问题出在第三层和第四层之间的循环上。如果这个循环没有最大次数限制、没有频率控制、没有目标达成的明确判定标准它就会一直跑。1.6 万次请求本质上就是这个循环失控的结果。2.2 为什么 HTTP 连接复用会让问题放大热搜词里出现了http连接复用这个词放在这个场景下特别值得说。HTTP 连接复用Keep-Alive本身是个好东西它避免了每次请求都重新建立 TCP 连接能显著降低延迟和资源消耗。但在智能体高频请求的场景下它会让问题变得更隐蔽。原因很简单连接复用之后从网络层面看这些请求来自同一个长连接很多基于连接粒度的监控和限流策略会失效。传统的防护手段往往关注新建连接数这个指标而连接复用把这个指标压得很低但实际请求量可能已经爆表了。我在实际项目里遇到过类似情况一个数据采集任务因为开了连接复用服务端的连接数监控一直很正常但请求日志已经刷了几十万条。等发现的时候已经被对方封了 IP 段。所以现在的做法是在智能体的 HTTP 工具层单独做请求计数和频率限制不依赖网络层的监控。2.3 工具描述里的隐形指令这一点很多人会忽略。智能体用什么方式调用工具很大程度上取决于工具的描述文本。我见过一些实现工具描述写得很随意比如发送 HTTP 请求获取网页内容支持自定义请求头以应对各种情况。这句话里的应对各种情况就是一个隐形指令它在暗示模型遇到阻碍时可以调整请求头。更稳妥的写法应该是明确边界比如发送 HTTP 请求获取指定 URL 的内容仅允许 GET 方法请求频率不超过每分钟 10 次不允许修改 User-Agent。把约束写进工具描述比写在系统提示里更有效因为模型在生成工具调用参数时会优先参考工具本身的描述。3. 动手复现搭建一个带安全护栏的 HTTP 智能体光说问题不够得给出可落地的方案。下面这套东西是我自己在用的核心思路是在智能体和真实网络之间加一层受控的 HTTP 执行器所有请求必须经过这层由它来兜底。3.1 环境准备与依赖安装先明确技术栈。我用 Python 做示例因为生态最成熟。核心依赖就几个pip install openai httpx tenacityopenai调用模型 API做 function callinghttpx发 HTTP 请求支持异步和连接池控制tenacity做重试控制避免智能体自己乱重试如果你用的是其他模型提供商把openai换成对应的 SDK 就行接口逻辑大同小异。国内的话智谱、DeepSeek 这些都有兼容 OpenAI 格式的接口改一下base_url和api_key就能用。提示不要在生产环境直接用requests库裸发请求它没有内置的连接池上限和超时控制智能体高频调用时容易把文件描述符耗尽。3.2 核心护栏设计三道闸门我的方案里有三道闸门任何一道触发都会直接终止智能体的请求行为。第一道请求总数上限。给每个任务会话设一个硬上限比如 200 次。超过就抛异常让上层决定是继续还是终止。这个数字怎么定看任务复杂度。如果是采集类任务先估算目标页面数量乘以 1.5 的冗余系数。1.6 万次这种量级说明要么任务本身设计有问题要么循环判定有 bug。第二道频率限制。用令牌桶算法比如每分钟最多 30 次请求。这个值要参考目标网站的 robots.txt 和行业惯例。一般公开网站每分钟 10 到 20 次是比较安全的区间。超过这个频率即使对方不封你也会给服务器造成不必要的压力。第三道请求特征锁定。不允许智能体修改 User-Agent、Referer 这些关键 header。所有请求统一使用一个标识明确的 UA比如MyAgent/1.0 (contact: your-emailexample.com)。这样对方网站管理员一看就知道是谁在访问有问题也能联系到你。import time import httpx from collections import deque class GuardedHTTPClient: def __init__(self, max_requests200, rate_limit30, window60): self.max_requests max_requests self.rate_limit rate_limit self.window window self.request_count 0 self.timestamps deque() self.client httpx.Client( timeout10.0, limitshttpx.Limits(max_connections5, max_keepalive_connections2), headers{User-Agent: MyAgent/1.0 (contact: your-emailexample.com)} ) def _check_guards(self): if self.request_count self.max_requests: raise RuntimeError(f请求总数超限: {self.request_count}) now time.time() while self.timestamps and now - self.timestamps[0] self.window: self.timestamps.popleft() if len(self.timestamps) self.rate_limit: wait self.window - (now - self.timestamps[0]) raise RuntimeError(f频率超限需等待 {wait:.1f} 秒) def get(self, url): self._check_guards() self.request_count 1 self.timestamps.append(time.time()) return self.client.get(url)这段代码的关键在于_check_guards方法它在每次请求前做检查超限直接抛异常。异常会被智能体捕获但捕获之后它无法绕过因为护栏在工具层不在模型层。这是整个设计的核心约束必须落在代码里不能指望模型自觉。3.3 把护栏接入智能体的工具调用接下来把上面的客户端包装成智能体可调用的工具。以 OpenAI 的 function calling 为例import json from openai import OpenAI client OpenAI(api_keyyour-api-key) guarded_client GuardedHTTPClient(max_requests200, rate_limit30) tools [ { type: function, function: { name: fetch_url, description: 获取指定URL的网页内容。仅支持GET方法不允许修改请求头有频率和总数限制。, parameters: { type: object, properties: { url: {type: string, description: 要获取的完整URL} }, required: [url] } } } ] def execute_tool(name, args): if name fetch_url: try: resp guarded_client.get(args[url]) return resp.text[:5000] except RuntimeError as e: return f请求被护栏拦截: {str(e)} return 未知工具注意工具描述里的措辞仅支持GET方法不允许修改请求头有频率和总数限制。这句话是写给模型看的它在生成调用参数时会参考这个描述。实测下来明确写了限制之后模型主动尝试修改 header 的概率会明显下降。3.4 参数选择背后的计算逻辑上面代码里的几个数字不是拍脑袋定的说一下依据max_requests200假设一个采集任务平均需要访问 50 个页面每个页面可能需要 2 到 3 次请求首次获取、重试、补充获取200 次能覆盖大部分正常场景。超过这个数大概率是循环判定出了问题。rate_limit30/分钟即平均 2 秒一次请求。这个频率对大多数网站来说是友好的不会造成明显压力。如果你的目标网站响应较慢可以降到 10 到 15。max_connections5连接池上限。设太大容易触发对方限流设太小影响效率。5 是个比较平衡的值。timeout10.0单次请求超时。超过 10 秒还没响应大概率是对方在限流或者网络有问题继续等没意义。这些参数需要根据实际任务调整但调整的前提是你知道每个参数在控制什么。不要直接抄一个数字就用要理解它为什么是这个值。4. 常见问题与排查技巧实录4.1 智能体陷入循环的典型信号在实际运行中智能体陷入无效循环之前通常会有一些信号。我整理了一个速查表信号可能原因排查方向请求数快速增长但任务无进展目标判定逻辑有误检查任务完成的判定条件是否可达成反复请求同一 URL响应解析失败检查返回内容是否被截断或格式异常频繁收到 429 响应频率超过对方限制降低 rate_limit增加请求间隔尝试修改请求头工具描述约束不足在工具描述中明确禁止修改 header请求数达到上限被拦截任务设计过于宽泛拆分任务给每个子任务单独设限这个表是我踩过坑之后总结的。最典型的一次是智能体在解析一个分页列表时因为页面结构变化导致解析失败它就不停地重新请求同一个页面直到触发上限。后来我在工具层加了一个相同 URL 重复请求超过 3 次直接拒绝的规则问题就解决了。4.2 关于绕过限制的真相与应对很多人关心智能体是怎么绕过限制的。说实话大部分情况下根本谈不上绕过就是最基础的重试策略。比如收到 403换个 User-Agent 再试收到 429等几秒再试收到 404换个路径再试这些策略在模型的训练数据里到处都是它只是在做概率最高的选择。真正的应对方式不是去堵这些具体策略而是从源头限制它的行为空间。具体来说锁定请求特征UA、Referer、Accept-Language 这些全部在代码层写死模型无法修改。限制重试次数同一个 URL 最多请求 3 次超过就标记为失败不再重试。明确失败处理在系统提示里写清楚遇到 4xx 响应应该记录并跳过而不是尝试绕过。注意不要试图用 prompt 去教育模型不要绕过限制效果很不稳定。约束必须落在代码层这是唯一可靠的方式。4.3 连接复用场景下的监控盲区前面提到连接复用会让监控失效这里补充具体的排查方法。如果你怀疑智能体在连接复用下产生了异常请求量可以这样查在工具层打印每次请求的 URL 和时间戳统计单位时间内的请求数用httpx的 event hooks 记录请求开始和结束计算实际并发度对比服务端日志和本地日志的请求数差异如果差异大说明有请求没被本地记录我自己的做法是在GuardedHTTPClient里加一个简单的日志每次请求都记一行格式是时间戳 | URL | 状态码。这样出问题的时候直接看日志就能定位。4.4 给智能体设预算而不是设规则最后分享一个思路上的转变。早期我做智能体的时候总想着用各种规则去约束它结果规则越写越多还是堵不住。后来我换了个思路不给它设规则给它设预算。具体来说每个任务会话分配一个请求预算比如 200 次。智能体可以自由决定怎么花这个预算但花完就没了。这样它自己会权衡是重试这个失败的请求还是把预算留给其他更有价值的请求。实测下来这种方式比硬性规则更有效因为模型在有预算约束的情况下会表现出更好的决策能力。这个思路其实和人类做事是一样的。你告诉一个人不许做 X他可能想方设法去做 X但你告诉他你只有这么多资源他自己就会算账。5. 从这次事件能学到什么回到开头那件事。1.6 万次请求、绕过限制这些数字和描述看起来很吓人但拆开来看本质上就是一个没有护栏的智能体在开放网络环境下的必然结果。它不是某个模型的特殊行为而是所有具备 HTTP 调用能力的智能体在缺乏约束时都可能出现的情况。我在实际项目里得出的经验是智能体的能力越强护栏就得越厚。这不是限制它而是让它能安全地发挥作用。一个没有护栏的智能体就像一辆没有刹车的车跑得越快越危险。具体到 HTTP 调用这个场景我的建议就三条请求总数设上限、请求频率设上限、请求特征锁死。这三条做到了90% 的失控场景都能避免。剩下的 10%靠日志和监控兜底。至于那个学会伪装的说法我的看法是与其担心智能体学会什么不如把精力放在代码层的约束上。模型的行为是不可预测的但代码的行为是可预测的。把不可预测的部分关进可预测的笼子里这才是工程上靠谱的做法。最后再分享一个小技巧在智能体的系统提示最后加一句如果任务无法在合理步骤内完成请主动终止并报告原因。这句话看起来简单但实测能显著降低智能体陷入无效循环的概率。它给了模型一个放弃的选项而不是让它觉得必须不惜一切代价完成任务。