洞察

AI Agent 代理:构建可靠、安全的智能体网络连接

为 AI Agent 选择代理方案,对比接入方式,管理粘性会话,并使用含有限错误处理的 Playwright 实践模式。

AI Agent 通过代理网关将网络流量路由到多个区域节点

当获准的 AI Agent 任务需要执行出站策略、选择区域出口、在多步骤浏览器会话中保持同一网络身份、分配独立任务或记录网络遥测时,才需要代理。没有网络工具的纯文本 Agent 不需要代理。代理是一项基础设施控制,不代表可以绕过访问控制。

“AI Agent 代理”是什么意思?

正向代理位于 Agent 运行环境与目标服务之间。Agent 将 HTTP、HTTPS 或 SOCKS 流量交给代理,再由代理代表它连接目标。对于 HTTPS,HTTP CONNECT 方法可通过中间节点建立隧道。

  • 执行目标域名白名单和出站策略。
  • 为合法的本地化或可用性测试选择地域出口。
  • 为有状态的浏览器会话保持稳定 IP。
  • 将相互独立的任务分配到健康的出口池。
  • 统计连接延迟、状态码和失败率。

代理不会自动让 Agent 匿名、合规或稳定。目标服务仍可能观察浏览器指纹、账户行为、Cookie、请求节奏以及应用层标识。

选择接入模式

下表描述的是常见生态接入模式。MIYAIP 提供代理连接能力;这张表并不表示所有托管 API、远程浏览器或 MCP 实现都是 MIYAIP 产品功能。请以实际服务的接口和会话行为为准。

接入方式

适用情况

会话控制

仍由你负责的事项

原始 HTTP(S) 或 SOCKS 代理

HTTP 客户端或浏览器已有代理设置,并且需要直接控制

凭证、端口或供应商定义的会话键

客户端生命周期、重试、解析、速率限制与可观测性

托管抓取 API

希望通过请求 API 完成抓取,并可能由服务处理渲染

API 自己的请求或会话标识

确认供应商行为、数据权利、目标策略和结果质量

远程浏览器

希望由服务托管浏览器进程并暴露控制端点

浏览器实例或远程上下文标识

Cookie 隔离、任务边界、允许访问的目标和资源清理

MCP 工具

Agent 应通过标准工具接口调用受约束的网络能力

由 MCP Server 与下游服务共同定义

工具权限、输入校验、目标策略、密钥与审计日志

为什么 AI Agent 需要明确的代理策略

Agent 会反复调用工具,也可能受到意外输入或恶意内容影响。OpenAI Agents SDK 文档将工具描述为 Agent 获取数据、调用 API、运行代码或使用计算机的机制。OWASP 关于“过度代理权”的指南则提醒:过宽的工具权限可能把含糊或被操纵的模型输出转化为有害操作。

因此,网络路由本身就是一项策略决策。成熟的代理层应在请求离开环境前确认目标是否获准、应使用哪个身份与会话、适用怎样的重试和速率策略,以及哪些遥测信息可以安全记录。

参考架构

生产系统通常将决策与传输分离:Agent 选择经过批准的浏览器或 HTTP 工具;出站策略服务校验目标、任务和租户;代理网关根据路由键选择上游出口;指标返回编排层,但不向模型暴露代理凭证。

可以使用 tenant_id + workflow_id + session_id 作为稳定路由键。这样既能保持网络身份一致,也能让编排层重试或恢复任务。代理用户名和密码应保存在密钥管理系统中,只在运行时注入,绝不能出现在提示词、追踪记录、截图或工具输出中。

代理是传输与策略边界,不是验证码绕过器、授权绕过器,也不代表可以忽略网站条款。

如何选择代理类型

数据中心代理

数据中心出口通常是 API 调用、公开数据集、测试和高吞吐任务的首选。它速度快、成本可控、易于监测。当目标确实需要消费网络特征,或其风控系统会区别对待共享托管网段时,它可能不适合。

静态 ISP 代理

静态 ISP 出口兼具固定地址和 ISP 关联网络,适合需要稳定地域与身份的长期授权会话。应把它视为稀缺的会话资源,并监控账户与 IP 的绑定关系。

住宅或移动代理

只有当合法业务确实需要终端用户网络特征时才应使用,例如经过授权的本地化测试或区域 QA。供应商来源、用户同意、司法辖区、数据保留政策和可接受使用规则,比 IP 池规模更重要。

轮换与粘性会话

轮换适用于彼此独立、无状态的任务。登录、购物车、多步骤表单,以及任何依赖 Cookie 或服务端连续性判断的流程,更适合粘性会话。应在任务之间轮换,而不是在一次交易进行到一半时更换出口。

任务内与任务间的会话策略

使用模型无法随意编造的标识生成稳定会话键。在同一个有状态任务中,包括有限重试,都保持这个键不变;无关的新任务使用新键。只有获准工作流确实需要同一账户和网络身份时,才跨任务延续会话,并设置明确的过期时间。

tenant_acme + workflow_checkout + task_8472 -> session_6f2a…
  步骤 1:打开商品页        -> session_6f2a…
  步骤 2:加入购物车        -> session_6f2a…
  步骤 2 有限重试一次       -> session_6f2a…

下一个独立任务              -> session_b91c…
恢复获准的任务 8472         -> session_6f2a…,直到过期
  • 同一个有状态任务内:复用相同的代理会话、浏览器上下文、Cookie 和路由键。
  • 相互独立的任务之间:创建新会话,避免失败和状态在任务间泄漏。
  • 恢复获准任务时:在提示词之外保存会话引用,加密浏览器状态,并让两者同时过期。

真正影响可靠性的模式

仅仅轮换代理并不能带来可靠性。应围绕每个出口建立反馈闭环:

  • 分别设置连接、TLS、响应和任务总超时。
  • 只重试可能属于暂时性的错误;对 429 和部分 5xx 响应使用有上限的指数退避与随机抖动。
  • 遵循 Retry-After,配置熔断器,并按成功率、延迟和挑战信号给出口评分。
  • 重试写操作时保留幂等键,并限制每个任务的尝试次数。

不要通过不断更换 IP 来重试认证失败、策略拒绝或确定性的校验错误。这会掩盖真正的问题,也可能放大滥用行为。

浏览器 Agent:最小 Playwright 示例

Playwright 可以在浏览器启动时应用 HTTP(S) 或 SOCKSv5 代理。这个经过实测的模式使用一个启动级代理和一个浏览器上下文,让三步任务中的出口线路、Cookie 和浏览器状态保持一致。

安装依赖并设置运行环境

npm install playwright@1.61.1

$env:PROXY_SERVER = "http://[redacted-proxy-host]:[redacted-port]"
$env:PROXY_USERNAME = "[redacted-user]"
$env:PROXY_PASSWORD = "[redacted-password]"
$env:TARGET_URL = "https://example.com/"

node .\agent-proxy-example.mjs
import { chromium } from "playwright";

const required = ["PROXY_SERVER", "PROXY_USERNAME", "PROXY_PASSWORD", "TARGET_URL"];
for (const name of required) {
  if (!process.env[name]) throw new Error(`Missing ${name}`);
}

const target = new URL(process.env.TARGET_URL);
const allowedHosts = new Set(["example.com"]); // replace with approved destinations
if (target.protocol !== "https:" || !allowedHosts.has(target.hostname)) {
  throw new Error("Target is not allowlisted");
}

const redactIp = (value) => value.includes(".")
  ? value.replace(/\.\d+$/, ".xxx")
  : `${value.split(":").slice(0, 4).join(":")}::`;
const startedAt = performance.now();
let retries = 0;
let status = 0;
let steps = 0;
let outcome = "failed";

let browser;

try {
  browser = await chromium.launch({
  channel: "chrome",
  headless: true,
  proxy: {
    server: process.env.PROXY_SERVER,
    username: process.env.PROXY_USERNAME,
    password: process.env.PROXY_PASSWORD,
  },
  });

  const context = await browser.newContext();
  try {
    const page = await context.newPage();
    page.setDefaultTimeout(15_000);
    const readExitIp = async () => {
      const response = await page.goto("https://api.ipify.org?format=json", {
        waitUntil: "domcontentloaded",
        timeout: 20_000,
      });
      if (!response?.ok()) throw new Error(`Exit check failed: ${response?.status()}`);
      steps += 1;
      return String((await response.json()).ip ?? "unavailable");
    };

    const firstExitIp = await readExitIp();
    for (let attempt = 0; attempt < 2; attempt += 1) {
      const response = await page.goto(target.href, {
        waitUntil: "domcontentloaded",
        timeout: 30_000,
      });
      status = response?.status() ?? 0;
      if (status !== 429 && status < 500) break;
      if (attempt === 1) throw new Error(`Target remained transiently unavailable: ${status}`);
      retries += 1;
      const retryAfter = Number(response?.headers()["retry-after"] ?? 1);
      await page.waitForTimeout(Math.min(Math.max(retryAfter, 1), 10) * 1_000);
    }
    steps += 1;

    const result = {
      title: await page.title(),
      heading: await page.locator("h1").first().textContent(),
    };
    result.titleMatchesHeading = result.title === result.heading;
    const secondExitIp = await readExitIp();
    const sameExitAcrossTask = firstExitIp === secondExitIp;
    outcome = status >= 200 && status < 400 ? "success" : "failed";

    console.log({
      outcome,
      status,
      steps,
      retries,
      durationMs: Math.round(performance.now() - startedAt),
      exitIp: redactIp(firstExitIp),
      sameExitAcrossTask,
      result,
    });
  } finally {
    await context.close();
  }
} catch (error) {
  console.error({
    outcome,
    status,
    steps,
    retries,
    durationMs: Math.round(performance.now() - startedAt),
    error: String(error),
  });
  process.exitCode = 1;
} finally {
  await browser?.close();
}

每个逻辑会话保持一个浏览器上下文,每条代理线路使用一次独立的浏览器启动。不要在客户之间共享 Cookie,也不要让模型自行选择任意代理端点或凭证。

请通过 PowerShell 或密钥管理系统设置四个环境变量。白名单会拒绝任意目标。代理配置位于 chromium.launch,因此同一浏览器中的所有 context 都使用相同代理;需要更换代理线路时应启动新的浏览器,每个逻辑会话则保持一个 context。

{
  "recordedAt": "2026-09-05T08:46:48Z",
  "runtime": "Node.js 22.18.0",
  "playwright": "1.61.1",
  "browser": "Chrome",
  "outcome": "success",
  "status": 200,
  "steps": 3,
  "retries": 0,
  "durationMs": 7322,
  "exitIp": "[redacted-ip]",
  "sameExitAcrossTask": true,
  "result": {
    "title": "Example Domain",
    "heading": "Example Domain",
    "titleMatchesHeading": true
  }
}

故障处理与安全日志字段

信号

处理动作

重试或会话规则

407 代理认证失败

停止任务,检查密钥、账户状态、端点和分配的认证模式

不要通过轮换 IP 或使用不变凭证反复重试

429 速率限制

遵循 Retry-After 并降低请求速率

只在尝试次数上限内重试;有状态任务保持同一会话

连接或导航超时

分别设置 DNS、连接、TLS 和页面超时,并检查出口健康状态

操作可安全重放时最多重试一次;仅在任务边界或确认出口故障后轮换

确定性的 4xx 或策略拒绝

修复请求或授权

不要重试

浏览器或解析错误

记录已脱敏的错误类别和失败步骤

只有状态可以安全重建时才重启上下文

建议记录时间戳、租户 ID、任务 ID、哈希后的会话或路由 ID、目标主机名、步骤、尝试次数、状态类别、duration_ms、结果和脱敏错误类别。绝不能记录代理密码、Authorization 请求头、Cookie、响应正文或带有查询密钥的完整 URL。

安全护栏

网络控制应降低提示词注入和工具误用造成的影响:

  • 按工具设置目标主机、协议和端口白名单。
  • 除非明确需要,否则阻断环回、链路本地、云元数据服务和私有网段,并防范 DNS 重绑定。
  • 为每个 Agent 或租户发放短期代理凭证,并在模型之外执行并发和带宽限制。
  • 对购买、账户变更、破坏性操作和敏感数据传输要求人工审批。
  • 记录目标、策略决定、耗时、响应类别和路由 ID;避免记录正文、Cookie、授权头或原始凭证。

这些措施是模型护栏的补充,即使模型做出错误决定,网络边界仍然有效。

合规必须进入架构

在自动访问目标之前,应记录业务目的、授权依据、适用条款、速率限制、数据权利和地域限制。对于类似爬虫的行为,应评估网站公开政策和 Robots Exclusion Protocol。robots.txt 不能替代授权或法律,但它是自动化客户端应正确处理的标准化信号。

NIST AI 风险管理框架以 Govern、Map、Measure、Manage 四项职能组织风险工作。映射到 Agent 网络系统,就是明确责任人、梳理目标与数据流、度量失败和影响,并维护响应计划。

上线检查清单

  • 定义允许访问的目标和任务目的;根据工作流选择代理类型,而不是根据营销话术。
  • 为每个有状态会话绑定一个路由键,并隔离租户、Cookie 和浏览器存储。
  • 在网关和目标两个层面执行速率限制;仅对合适的错误重试,并设置退避、抖动和次数上限。
  • 监控出口健康状态,配置熔断器和默认拒绝的失败模式。
  • 审核供应商来源、用户同意、保留政策、司法辖区、事件响应和凭证轮换。

结论

最适合 AI Agent 的代理,并不等于拥有最大的 IP 池。它应匹配工作流对身份和地域的需求,提供可度量的可靠性,支持最小权限凭证,并让运维人员能够执行明确的策略控制。先从小规模白名单架构开始,把会话行为定义清楚,在指标和治理成熟后再扩展。

来源

准备构建更干净的数据流程?

了解 MIYAIP 面向采集、自动化与数据访问的代理基础设施。