Azure APIM 统一模型 API:用一个接口打通 Anthropic、Vertex AI 等多家后端

2026-06-10 20 预计阅读时间: 1 分钟
来源: infoq.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.

预计阅读时间:13 分钟

多模型接入正在成为企业 AI 平台的标配,但每加一家供应商就要适配一套请求格式、一套鉴权方式、一套 token 计量逻辑——运维成本随供应商数量线性增长。Build 2026 上,Azure API Management(APIM)一次性交付了三个针对性能力:统一模型 API 让客户端只写一种格式、内容安全策略覆盖 MCP 工具调用和 Agent-to-Agent 通信、token 指标拆出 reasoning / cached / audio 三类细项。下面逐项拆解,再给出一套可直接改造的 APIM 部署示例。

统一模型 API:写一次请求,后端自动翻译

核心思路很简单——客户端始终向 APIM 发送统一格式的请求,APIM 内部做协议转换和鉴权映射,再把请求转发到 Anthropic、Google Vertex AI、Azure OpenAI 等不同后端。

这意味着:

  • 客户端代码不再耦合供应商差异。Anthropic 用 messages 数组 + role 字段,Vertex AI 有自己的 instances 结构,统一 API 把这些差异收进 APIM 的策略层。
  • 切换后端变成配置变更而非代码重构。想把流量从 Claude 迁到 Gemini?改 APIM 的后端路由权重即可,调用方无感。
  • 限流、配额、密钥管理统一收口。所有供应商的 API Key 存在 APIM 的 Named Values 里,客户端只拿 APIM 的订阅密钥,密钥轮换不用通知下游。

统一 API 的转换逻辑由 APIM 的 policy XML 驱动,下面实践部分会给出完整示例。

内容安全策略扩展到 MCP 与 A2A

LLM 输出的内容安全(防仇恨、防泄露敏感信息等)已是标配,但 Build 2026 把覆盖面推到了两个新场景:

  • MCP 工具调用:Agent 通过 MCP 协议调用外部工具时,工具返回的结果同样经过内容安全检查。比如 Agent 调了一个内部数据库查询工具,返回结果里如果包含 PII,安全策略可以拦截或脱敏后再交给 Agent。
  • Agent-to-Agent(A2A)通信:多个 Agent 之间传递的 payload 也纳入检查范围。一个 Agent 把中间推理结果发给另一个 Agent 时,中间内容同样受策略约束。

这解决了实际部署中的一个盲区:过去只检查最终面向用户的输出,但 Agent 在工具调用和协作过程中产生的中间内容可能已经泄露了不该泄露的信息。现在 APIM 在流量入口统一拦截,不管流量终点是人还是工具还是另一个 Agent。

Token 指标拆出 reasoning、cached、audio 三类

多供应商环境下 token 计量一直是糊涂账——各家对"一个 token"的定义不完全一致,更别提 reasoning token(思维链内部消耗)、cached token(缓存命中节省的部分)、audio token(语音输入/输出)这些细项过去几乎无法横向对比。

APIM 现在把 token 指标拆成三类独立维度,跨供应商统一口径记录:

指标类型 含义 实际用途
Reasoning tokens 模型内部推理链消耗的 token 评估"深度思考"模式的真实成本,决定是否对长推理任务设独立配额
Cached tokens 前缀缓存命中节省的 token 衡量缓存策略效果,优化 prompt 前缀设计
Audio tokens 语音模态输入/输出的 token 语音场景单独计费、单独限流

这些指标通过 APIM 的 emission policy 写入 Application Insights,可以直接在 Grafana / Azure Monitor 里做跨供应商的成本对比面板。

实践:部署一套统一模型 API + 内容安全策略

以下示例展示如何在 APIM 中配置统一模型 API,把客户端请求转发到 Anthropic Claude 后端,同时挂载内容安全策略和 token 指标发射。你可以把 backend-id 替换成 vertex-aiazure-openai 来切换后端,客户端代码不用改。

1. 创建后端资源

# 创建 Anthropic 后端
az apim backend create \
  --resource-group myAIGroup \
  --service-name myAPIM \
  --backend-id anthropic-claude \
  --url "https://api.anthropic.com" \
  --description "Anthropic Claude backend"

# 创建 Vertex AI 后端(备用)
az apim backend create \
  --resource-group myAIGroup \
  --service-name myAPIM \
  --backend-id vertex-gemini \
  --url "https://us-central1-aiplatform.googleapis.com" \
  --description "Google Vertex AI Gemini backend"

2. 统一模型 API 的 Policy XML

将以下策略绑定到 /llm/unified/chat 这个 API operation 上:

<policies>
  <inbound>
    <!-- 验证客户端订阅密钥 -->
    <authentication-subscription-key header-name="Ocp-Apim-Subscription-Key" />

    <!-- 统一请求体转 Anthropic 格式 -->
    <set-body template="liquid">
      {
        "model": "{{body.model | default: 'claude-sonnet-4-20250514'}}",
        "max_tokens": {{body.max_tokens | default: 4096}},
        "messages": [
          {% for msg in body.messages %}
          {
            "role": "{{msg.role}}",
            "content": "{{msg.content}}"
          }{% unless forloop.last %},{% endunless %}
          {% endfor %}
        ]
      }
    </set-body>

    <!-- 注入 Anthropic API Key(存储在 Named Values 中) -->
    <set-header name="x-api-key" exists-action="override">
      <value>{{anthropic-api-key}}</value>
    </set-header>
    <set-header name="anthropic-version" exists-action="override">
      <value>2023-06-01</value>
    </set-header>

    <!-- 内容安全检查:覆盖 LLM 输出 + MCP 工具返回 + A2A payload -->
    <azure-ai-content-safety
      severity-threshold="4"
      categories="Hate Violence SelfHarm Sexual"
      block-on-match="true"
      scan-mcp-tool-response="true"
      scan-a2a-payload="true" />

    <!-- 路由到后端,可按权重切换 -->
    <set-backend-service backend-id="anthropic-claude" />
  </inbound>

  <backend>
    <!-- 限流:每订阅每分钟 60 次 -->
    <limit-call-rate calls="60" renewal-period="60" />
  </backend>

  <outbound>
    <!-- 统一响应格式:把 Anthropic 响应映射回通用结构 -->
    <set-body template="liquid">
      {
        "id": "{{body.id}}",
        "model": "{{body.model}}",
        "choices": [
          {
            "index": 0,
            "message": {
              "role": "{{body.content[0].type}}",
              "content": "{{body.content[0].text}}"
            },
            "finish_reason": "{{body.stop_reason}}"
          }
        ],
        "usage": {
          "prompt_tokens": {{body.usage.input_tokens}},
          "completion_tokens": {{body.usage.output_tokens}},
          "reasoning_tokens": {{body.usage.output_tokens_details.reasoning_tokens | default: 0}},
          "cached_tokens": {{body.usage.input_tokens_details.cache_creation_input_tokens | default: 0}},
          "audio_tokens": 0
        }
      }
    </set-body>

    <!-- 发射 token 指标到 Application Insights -->
    <emit-metric name="LLM-Token-Usage" value="{{body.usage.output_tokens}}" namespace="APIM-LLM">
      <dimension name="Model" value="{{body.model}}" />
      <dimension name="TokenType" value="completion" />
      <dimension name="ReasoningTokens" value="{{body.usage.output_tokens_details.reasoning_tokens | default: 0}}" />
      <dimension name="CachedTokens" value="{{body.usage.input_tokens_details.cache_creation_input_tokens | default: 0}}" />
      <dimension name="AudioTokens" value="0" />
    </emit-metric>
  </outbound>

  <on-error>
    <!-- 错误时也发射指标,便于监控失败率 -->
    <emit-metric name="LLM-Call-Error" value="1" namespace="APIM-LLM">
      <dimension name="ErrorSource" value="{{context.LastError.Source}}" />
      <dimension name="ErrorCode" value="{{context.LastError.Reason}}" />
    </emit-metric>
  </on-error>
</policies>

3. 客户端调用示例

客户端只需要发统一格式,不用关心后端是谁:

import httpx

APIM_ENDPOINT = "https://myAPIM.azure-api.net/llm/unified/chat"
APIM_KEY = "你的APIM订阅密钥"

resp = httpx.post(
    APIM_ENDPOINT,
    headers={"Ocp-Apim-Subscription-Key": APIM_KEY},
    json={
        "model": "claude-sonnet-4-20250514",  # 统一模型名,APIM 内部映射到实际后端
        "max_tokens": 1024,
        "messages": [
            {"role": "user", "content": "用三句话解释什么是 MCP 协议"}
        ]
    },
    timeout=30
)

data = resp.json()
print(data["choices"][0]["message"]["content"])
print(f"Token 消耗: prompt={data['usage']['prompt_tokens']}, "
      f"completion={data['usage']['completion_tokens']}, "
      f"reasoning={data['usage']['reasoning_tokens']}")

注意azure-ai-content-safetyscan-mcp-tool-responsescan-a2a-payload 属性是 Build 2026 新增的配置项。如果你的 APIM 版本尚未更新,先去掉这两个属性部署基础版,升级后再开启。Anthropic 的 reasoning_tokens 和 cache 相关字段也需要模型版本支持(Claude 3.5+ / Sonnet 4),旧模型这些字段可能为空,模板中已用 | default: 0 兜底。

落地建议与取舍

  1. 先统一一家,再逐步加后端。先把流量最大的供应商接入统一 API,验证 policy 转换逻辑和 token 指标采集无误后,再加第二家。每加一家只需要新增一个 backend 资源和对应的 Liquid 转换模板,客户端零改动。

  2. 内容安全策略的阈值要按场景调。内部 Agent 协作可以放宽到 severity 3,面向外部用户的输出建议卡在 4 甚至 5。MCP 工具返回和 A2A payload 的扫描会增加约 50-100ms 延迟,对延迟敏感的工具调用场景可以先只做异步日志记录、不做实时拦截。

  3. token 指标先做面板再做限流。reasoning / cached / audio 三类指标先接入 Application Insights 画两周的成本分布图,看清实际消耗比例后再决定是否对 reasoning token 单设配额。盲目限流可能卡住需要深度推理的关键任务。

  4. 缓存 token 指标是优化 prompt 的信号灯。如果 cached_tokens 占比持续低于 10%,说明 prompt 前缀设计没有命中缓存,值得把 system message 固定化、把多轮对话的上下文前缀统一,以降低重复输入的 token 成本。

  5. A2A 安全扫描的边界。当前扫描覆盖的是 APIM 可见的 A2A 流量——即经过 APIM 网关的 Agent 间通信。如果你的 Agent 之间有直连通道(绕过 APIM),那部分流量不在保护范围内,需要额外在 Agent 框架层补一层检查。

统一模型 API 解决的是"接入成本随供应商膨胀"的问题,内容安全扩展解决的是"Agent 生态中间环节失控"的问题,token 细项解决的是"多供应商成本不可比"的问题。三个能力组合起来,APIM 正从"LLM 流量代理"变成"Agent 时代的流量治理平台"。如果你的团队已经在跑多模型或多 Agent 架构,这批能力值得在下一个迭代窗口里优先验证。


相关推荐