免费POC, 零成本试错
AI知识库

53AI知识库

学习大模型的前沿技术与行业应用场景


我要投稿

Claude Code 源码揭秘:为什么它能无感切换 AWS、Google、Azure

发布日期:2026-02-11 21:29:15 浏览次数: 1517
作者:与AI同行之路

微信搜一搜,关注“与AI同行之路”

推荐语

揭秘Claude Code如何实现多云无感切换,满足企业级数据合规与成本优化需求。

核心内容:
1. 企业选择多云架构的核心诉求:数据合规与成本优化
2. 工厂模式实现的多云适配技术架构解析
3. 环境变量驱动的自动切换机制设计

杨芳贤
53AI创始人/腾讯云(TVP)最具价值专家

 

💡 阅读前记得关注+星标,及时获取更新推送

「Claude Code 源码揭秘」系列的第十五篇,上一篇《Claude Code 源码揭秘:CLAUDE.md,一个 Markdown 文件如何驯服 AI》,说的是怎么用 Markdown 文件定制 AI 行为。但不管规则怎么配,最终请求还是要发到云端。问题来了:你用的是哪个云?

你可能没注意过,Claude Code 不只能连 Anthropic 官方 API。

把 AWS_REGION 和 AWS_ACCESS_KEY_ID 配好,它就自动切换到 AWS Bedrock。把 ANTHROPIC_VERTEX_PROJECT_ID 配好,它就走 Google Vertex AI。甚至从 2.0.45 版本开始,还支持了 Microsoft Azure 的 Foundry。不需要改代码,不需要换命令行参数,环境变量一设,底层就变了。

有人可能会问:Bedrock 和 Vertex AI 上跑的不还是 Claude 模型吗?为什么不直连 Anthropic?

模型确实是同一个,但企业访问它的路径完全不同。很多大企业跟 AWS 或 Google 签了年框合约,走云厂商账单可以抵扣承诺消费。更关键的是数据合规——数据不出自己的云环境,IAM 权限、VPC 网络隔离、审计日志都走现有体系。在大公司新增一个供应商要走几个月的采购审批,走已有云厂商能省掉这些流程。所以多云适配不是技术炫技,是实实在在的企业刚需。

翻源码才发现,这套多云适配不是简单地 if-else 判断,而是一套完整的抽象层设计——工厂模式、策略模式、模型映射、认证适配、错误转换,每一层都有讲究。

工厂模式:入口只有一个

整个多云适配的核心是 createClient() 这个工厂函数:

export function createClient(config?: ProviderConfig): Anthropic {
  const
 providerConfig = config || detectProvider();

  switch
 (providerConfig.type) {
    case
 'bedrock':
      return
 createBedrockClient(providerConfig);
    case
 'vertex':
      return
 createVertexClient(providerConfig);
    case
 'foundry':
      return
 createFoundryClient(providerConfig);
    default
:
      return
 new Anthropic({
        apiKey
: providerConfig.apiKey,
      });
  }
}

上层代码不管你用的是哪个云厂商,统一调 createClient() 就行。返回的都是 Anthropic 客户端接口,方法一样,参数一样,行为一样。

这就像 USB 接口——不管你插的是鼠标、键盘还是 U 盘,接口都是一样的,设备自己去适配。

环境变量驱动的自动检测

最让我欣赏的是 detectProvider() 这个设计:

function detectProvider(): ProviderConfig {
  // 优先级 1:显式指定用 Bedrock

  if
 (process.env.CLAUDE_CODE_USE_BEDROCK === 'true') {
    return
 detectBedrockConfig();
  }

  // 优先级 2:显式指定用 Vertex

  if
 (process.env.CLAUDE_CODE_USE_VERTEX === 'true') {
    return
 detectVertexConfig();
  }

  // 优先级 3:有 Bedrock 环境变量就用 Bedrock

  if
 (process.env.AWS_BEDROCK_MODEL ||
      (process.env.AWS_REGION && process.env.AWS_ACCESS_KEY_ID)) {
    return
 detectBedrockConfig();
  }

  // 优先级 4:有 Vertex 环境变量就用 Vertex

  if
 (process.env.ANTHROPIC_VERTEX_PROJECT_ID ||
      process.env.GOOGLE_APPLICATION_CREDENTIALS) {
    return
 detectVertexConfig();
  }

  // 优先级 5:降级到 Anthropic 直连

  return
 {
    type
: 'anthropic',
    apiKey
: process.env.ANTHROPIC_API_KEY,
  };
}

这个设计很巧妙——环境变量本身就是意图声明。你设置了 AWS 相关的变量,说明你想用 AWS;设置了 Google 相关的变量,说明你想用 Google。不需要额外的配置文件或命令行参数。

画个图更清楚:

我之前做的那个多模型网关,用户必须在配置文件里写明「我要用哪个供应商」。Claude Code 这种自动检测更智能——你有什么凭证,我就用什么供应商。

认证方式:四朵云,四种方言

这是多云适配最复杂的部分。每家云厂商的认证方式都不一样,就像去不同国家过海关,每个国家要的证件都不同:

  • • Anthropic:标准的 API Key,Authorization: Bearer <key>
  • • AWS Bedrock:AWS Signature V4 签名,每个请求都要签
  • • Google Vertex:JWT + OAuth2,要用 Service Account 换 Access Token
  • • Azure Foundry:Azure AD 认证,走 Microsoft 的身份体系

Anthropic 最简单,直接把 API Key 塞 header 就完事了。

Bedrock 麻烦得多。AWS 不用 API Key,用 IAM 凭证。每个请求都要算一遍签名:

export function signAWSRequest(
  method
: string,
  url
: string,
  body
: string,
  credentials
: {
    accessKeyId: string;
    secretAccessKey: string;
    sessionToken?: string;
    region: string;
    service: string;
  }
): Record<string, string> {
  const
 date = new Date();
  const
 dateStamp = formatDate(date, 'YYYYMMDD');
  const
 amzDate = formatDate(date, 'YYYYMMDDTHHmmssZ');

  // 1. 创建规范请求

  const
 canonicalRequest = [
    method,
    parsedUrl.pathname,
    parsedUrl.searchParams.toString(),
    canonicalHeaders,
    signedHeaders,
    hashBody
(body),
  ].join('\n');

  // 2. 创建待签字符串

  const
 credentialScope = `${dateStamp}/${region}/${service}/aws4_request`;
  const
 stringToSign = [
    'AWS4-HMAC-SHA256'
,
    amzDate,
    credentialScope,
    hash
(canonicalRequest),
  ].join('\n');

  // 3. 计算签名

  const
 signingKey = getSigningKey(secretAccessKey, dateStamp, region, service);
  const
 signature = hmacSha256(signingKey, stringToSign);

  // 4. 构建授权头

  return
 {
    'Authorization'
: `AWS4-HMAC-SHA256 Credential=${accessKeyId}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signature}`,
    'X-Amz-Date'
: amzDate,
    'X-Amz-Security-Token'
: sessionToken,  // 临时凭证需要这个
  };
}

AWS Signature V4 这套签名流程,做过 AWS 集成的应该都踩过坑。日期格式、header 排序、编码规则,任何一个细节错了,就是 InvalidSignatureException

Claude Code 有个巧妙的 fallback 机制。如果装了官方的 @anthropic-ai/bedrock-sdk,就用 SDK(性能更好);否则降级到手动签名:

try {
  const
 AnthropicBedrock = require('@anthropic-ai/bedrock-sdk').default;
  return
 new AnthropicBedrock({
    awsAccessKey
: config.accessKeyId,
    awsSecretKey
: config.secretAccessKey,
    awsSessionToken
: config.sessionToken,
    awsRegion
: config.region,
  });
} catch {
  console
.warn('[Bedrock] 官方 SDK 未找到,降级到手动签名');
  return
 createManualBedrockClient(config);
}

这种「优雅降级」的设计,让 Claude Code 不依赖特定的 SDK 版本也能工作。

Vertex 的认证又是另一套逻辑。Google 用 Service Account 认证,需要用私钥签名一个 JWT,然后拿 JWT 去换 Access Token:

private async fetchServiceAccountToken(
  credentials
: GoogleServiceAccount
): Promise<AccessToken> {
  const
 now = Math.floor(Date.now() / 1000);

  // 1. 构建 JWT Claims

  const
 claim = {
    iss
: credentials.client_email,
    scope
: 'https://www.googleapis.com/auth/cloud-platform',
    aud
: credentials.token_uri,
    iat
: now,
    exp
: now + 3600,  // 1 小时有效
  };

  // 2. 用私钥签名 JWT

  const
 jwt = this.signJWT(
    { alg: 'RS256', typ: 'JWT' },
    claim,
    credentials.private_key
  );

  // 3. 换取 Access Token

  const
 response = await fetch(credentials.token_uri, {
    method
: 'POST',
    body
: new URLSearchParams({
      grant_type
: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
      assertion
: jwt,
    }),
  });

  return
 response.json();
}

Access Token 是有过期时间的,Claude Code 还做了自动刷新:

private scheduleTokenRefresh(token: AccessToken): void {
  // 在过期前 5 分钟刷新

  const
 refreshTime = (token.expires_in - 300) * 1000;

  this
.tokenRefreshTimer = setTimeout(async () => {
    this
.cachedToken = null;
    await
 this.getAccessToken();  // 重新获取
  }, refreshTime);
}

这个细节很重要。我之前做的项目没有自动刷新,用户会遇到「下午 4 点后 API 就不工作了」的问题——因为 token 在中午过期了。

模型 ID 映射:统一方言

各家云厂商对同一个模型的命名完全不同——同一个 sonnet,换个云就换个马甲:

export const MODEL_MAPPING: Record<ProviderType, Record<string, string>> = {
  anthropic
: {
    'claude-3-5-sonnet'
: 'claude-3-5-sonnet-20241022',
    'claude-3-opus'
: 'claude-3-opus-20240229',
    'claude-3-haiku'
: 'claude-3-haiku-20240307',
  },
  bedrock
: {
    'claude-3-5-sonnet'
: 'anthropic.claude-3-5-sonnet-20241022-v2:0',
    'claude-3-opus'
: 'anthropic.claude-3-opus-20240229-v1:0',
    'claude-3-haiku'
: 'anthropic.claude-3-haiku-20240307-v1:0',
  },
  vertex
: {
    'claude-3-5-sonnet'
: 'claude-3-5-sonnet-v2@20241022',
    'claude-3-opus'
: 'claude-3-opus@20240229',
    'claude-3-haiku'
: 'claude-3-haiku@20240307',
  },
};

上层代码只需要说「我要 sonnet」,底层自动翻译成各厂商的 ID。用户不需要记「Bedrock 里 sonnet 的 ID 是 anthropic.claude-3-5-sonnet-20241022-v2:0」这种细节。

export function getModelForProvider(
  modelName
: string,
  providerType
: ProviderType
): string {
  const
 mapping = MODEL_MAPPING[providerType];

  // 先查映射表

  if
 (mapping[modelName]) {
    return
 mapping[modelName];
  }

  // 没有映射就原样返回(可能是用户指定的完整 ID)

  return
 modelName;
}

Bedrock 还有个特殊的 ARN 解析:

export function parseBedrockModelArn(input: string): BedrockModelArn | null {
  // ARN 格式:arn:aws:bedrock:{region}:{accountId}:{type}/{modelId}

  const
 arnPattern = /^arn:aws:bedrock:([^:]+):([^:]*):([^/]+)\/(.+)$/;
  const
 match = input.match(arnPattern);

  if
 (!match) return null;

  const
 [, region, accountId, resourceType, modelId] = match;

  return
 {
    region,
    accountId,
    resourceType,
    modelId,
    isFoundationModel
: resourceType === 'foundation-model',
    isProvisionedModel
: resourceType === 'provisioned-model',
    isInferenceProfile
: resourceType.includes('inference-profile'),
  };
}

Bedrock 有基础模型、预配置模型、跨区域推理三种类型,ARN 格式都不一样。这个解析函数把复杂性隐藏起来了。

端点格式适配

各家厂商的 API 端点格式也是各说各话:

Anthropic:
https://api.anthropic.com/v1/messages

Bedrock:
https://bedrock-runtime.{region}.amazonaws.com/model/{modelId}/invoke

Vertex:
https://{region}-aiplatform.googleapis.com/v1/projects/{projectId}/locations/{region}/publishers/anthropic/models/{modelId}:streamRawPredict

Claude Code 用 getEndpoint() 系列函数封装这些差异:

// Bedrock 端点
function
 getBedrockEndpoint(region: string, modelId: string): string {
  return
 `https://bedrock-runtime.${region}.amazonaws.com/model/${modelId}/invoke`;
}

// Vertex 端点

function
 getVertexEndpoint(
  region
: string,
  projectId
: string,
  modelId
: string
): string {
  return
 `https://${region}-aiplatform.googleapis.com/v1/projects/${projectId}/locations/${region}/publishers/anthropic/models/${modelId}:streamRawPredict`;
}

上层代码完全不需要知道这些细节。

错误处理的统一抽象

AWS 的错误类型有二十多种,Claude Code 做了友好化转换:

export function handleBedrockError(error: any): string {
  const
 errorMessage = error.message || '';

  if
 (errorMessage.includes('InvalidSignatureException')) {
    return
 '签名验证失败,请检查 AWS_ACCESS_KEY_ID 和 AWS_SECRET_ACCESS_KEY 是否正确';
  }

  if
 (errorMessage.includes('AccessDeniedException')) {
    return
 '权限不足,你的 IAM 用户/角色需要 bedrock:InvokeModel 权限';
  }

  if
 (errorMessage.includes('ResourceNotFoundException')) {
    return
 '模型不存在,请确认该模型在你的 AWS 区域已开通';
  }

  if
 (errorMessage.includes('ThrottlingException')) {
    return
 '请求被限流,请稍后重试或申请提高配额';
  }

  if
 (errorMessage.includes('ServiceQuotaExceededException')) {
    return
 '超出服务配额限制,请在 AWS 控制台申请提高配额';
  }

  if
 (errorMessage.includes('ValidationException')) {
    return
 '请求参数验证失败,请检查 max_tokens 等参数是否符合模型限制';
  }

  // ... 还有十几种错误


  return
 `Bedrock 调用失败: ${errorMessage}`;
}

用户看到的是人话,不是 AWS 的原始错误码。「你的 IAM 用户需要 bedrock:InvokeModel 权限」比 AccessDeniedException 有用多了。

配置验证和诊断

Claude Code 还提供了配置验证工具:

export function validateProviderConfig(config: ProviderConfig) {
  const
 errors: string[] = [];
  const
 warnings: string[] = [];

  if
 (config.type === 'bedrock') {
    // 检查必填字段

    if
 (!config.region) {
      errors.push('AWS_REGION 未设置');
    }
    if
 (!config.accessKeyId) {
      errors.push('AWS_ACCESS_KEY_ID 未设置');
    }
    if
 (!config.secretAccessKey) {
      errors.push('AWS_SECRET_ACCESS_KEY 未设置');
    }

    // 检查格式

    if
 (config.region && !isValidAwsRegion(config.region)) {
      warnings.push(`区域 ${config.region} 不在已知列表中,请确认拼写`);
    }

    // 检查凭证长度

    if
 (config.accessKeyId && config.accessKeyId.length < 16) {
      errors.push('AWS_ACCESS_KEY_ID 长度不正确');
    }
  }

  return
 {
    valid
: errors.length === 0,
    errors,
    warnings,
  };
}

还有个 CLI 诊断命令:

$ claude provider diagnose

Provider Detection: bedrock

Environment Variables:
  ✓ AWS_REGION: us-east-1
  ✓ AWS_ACCESS_KEY_ID: AKIA...
  ✓ AWS_SECRET_ACCESS_KEY: ****
  ○ AWS_SESSION_TOKEN: not set

Model Configuration:
  ✓ Model ID: anthropic.claude-3-5-sonnet-20241022-v2:0
  ✓ Region supports model: yes

Validation:
  ✓ Configuration is valid

Connection Test:
  ✓ Successfully connected to Bedrock

这种诊断工具在企业环境里特别有用。出问题的时候,跑一下诊断命令,大部分配置问题都能定位出来。

LLM Gateway:企业级的中间代理层

前面说的都是 Claude Code 直连云厂商的场景。但在真实的企业环境里,还有一种更常见的部署方式——在中间加一层 LLM Gateway。

LLM Gateway 就是夹在 Claude Code 和云厂商之间的代理层——统一认证、追踪用量、控成本、审计日志,一个入口管住所有开发者。几十号人的团队,总不能每人自己管一套 Key 吧。

Claude Code 官方文档专门写了 Gateway 接入方式。以社区里流行的 LiteLLM 为例:

# 直连 Anthropic 格式(推荐)
export
 ANTHROPIC_BASE_URL=https://litellm-server:4000

# 走 Bedrock 透传

export
 ANTHROPIC_BEDROCK_BASE_URL=https://litellm-server:4000/bedrock
export
 CLAUDE_CODE_SKIP_BEDROCK_AUTH=1
export
 CLAUDE_CODE_USE_BEDROCK=1

# 走 Vertex 透传

export
 ANTHROPIC_VERTEX_BASE_URL=https://litellm-server:4000/vertex_ai/v1
export
 CLAUDE_CODE_SKIP_VERTEX_AUTH=1
export
 CLAUDE_CODE_USE_VERTEX=1

注意那两个 SKIP_AUTH 的标志位。设了之后,Claude Code 不再自己处理认证,而是把认证交给 Gateway 去做。这个设计很聪明——Gateway 已经统一管理了密钥和凭证,Claude Code 就不需要再操心签名和 token 这些事了。

有个细节容易踩坑:Gateway 必须正确转发 anthropic-betaanthropic-version 这些 header,它们控制着 Claude Code 的功能开关,丢了就会莫名其妙地少一些高级功能。

我在实际项目里见过不少企业用 Gateway 做多租户隔离——每个团队一个 Key,后台按部门分摊成本,月底出账单一目了然。

完整的请求流程

把整个多云适配流程串起来:

这套设计教会我什么

看完这套多云适配,有几个设计决策值得抄作业:

别硬编码云厂商逻辑 — 工厂 + 策略模式,新增一个厂商只加一个 createXxxClient(),不动已有代码。Foundry 就是这么无痛接入的。

环境变量即意图 — 有什么凭证用什么供应商,零配置自动切换,比写配置文件优雅得多。

认证要有纵深 — 基础认证只是及格线,SSO、临时凭证、自动刷新、Gateway 透传才是企业级。

翻译层是粘合剂 — 模型 ID 映射、端点格式转换、错误信息翻译,这三层"同声传译"让上层代码完全不用关心底层是谁。

我之前做企业级产品就吃过这个亏。一开始只支持一个厂商,后来客户要求加一个,改得满头大汗。要是一开始就用这种抽象层设计,后面扩展会轻松很多。

下一篇聊集成系统。Claude Code 是怎么和 GitHub、VS Code、Chrome 浏览器打通的?这些集成背后的协议和机制是什么?

本文基于 Claude Code 源码分析,主要文件:src/providers/index.tssrc/providers/vertex.tssrc/providers/bedrock.tssrc/providers/foundry.ts


53AI,企业落地大模型首选服务商

产品:场景落地咨询+大模型应用平台+行业解决方案

承诺:免费POC验证,效果达标后再合作。零风险落地应用大模型,已交付160+中大型企业

联系我们

售前咨询
186 6662 7370
预约演示
185 8882 0121

微信扫码

添加专属顾问

回到顶部

加载中...

扫码咨询