微信扫码
添加专属顾问
我要投稿
揭秘Claude Code如何实现多云无感切换,满足企业级数据合规与成本优化需求。核心内容: 1. 企业选择多云架构的核心诉求:数据合规与成本优化 2. 工厂模式实现的多云适配技术架构解析 3. 环境变量驱动的自动切换机制设计
💡 阅读前记得关注+星标,及时获取更新推送
「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 这种自动检测更智能——你有什么凭证,我就用什么供应商。
认证方式:四朵云,四种方言
这是多云适配最复杂的部分。每家云厂商的认证方式都不一样,就像去不同国家过海关,每个国家要的证件都不同:
Authorization: Bearer <key>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-beta、anthropic-version 这些 header,它们控制着 Claude Code 的功能开关,丢了就会莫名其妙地少一些高级功能。
我在实际项目里见过不少企业用 Gateway 做多租户隔离——每个团队一个 Key,后台按部门分摊成本,月底出账单一目了然。
完整的请求流程
把整个多云适配流程串起来:
这套设计教会我什么
看完这套多云适配,有几个设计决策值得抄作业:
别硬编码云厂商逻辑 — 工厂 + 策略模式,新增一个厂商只加一个 createXxxClient(),不动已有代码。Foundry 就是这么无痛接入的。
环境变量即意图 — 有什么凭证用什么供应商,零配置自动切换,比写配置文件优雅得多。
认证要有纵深 — 基础认证只是及格线,SSO、临时凭证、自动刷新、Gateway 透传才是企业级。
翻译层是粘合剂 — 模型 ID 映射、端点格式转换、错误信息翻译,这三层"同声传译"让上层代码完全不用关心底层是谁。
我之前做企业级产品就吃过这个亏。一开始只支持一个厂商,后来客户要求加一个,改得满头大汗。要是一开始就用这种抽象层设计,后面扩展会轻松很多。
下一篇聊集成系统。Claude Code 是怎么和 GitHub、VS Code、Chrome 浏览器打通的?这些集成背后的协议和机制是什么?
本文基于 Claude Code 源码分析,主要文件:src/providers/index.ts、src/providers/vertex.ts、src/providers/bedrock.ts、src/providers/foundry.ts。
53AI,企业落地大模型首选服务商
产品:场景落地咨询+大模型应用平台+行业解决方案
承诺:免费POC验证,效果达标后再合作。零风险落地应用大模型,已交付160+中大型企业
2026-02-11
别再存轨迹了:SkillRL 让 Agent 把经验炼成技能,还会自我进化
2026-02-11
全新DeepSeek发布!上下文扩展至1M
2026-02-11
刚刚,DeepSeek悄悄测试新模型:百万token上下文、知识库更新,V4要来了?
2026-02-11
DeepSeek V4 悄咪咪上线了?1M 上下文简直爽翻!
2026-02-11
2026 企业级AI(Agentic AI for Enterprise),是新大陆
2026-02-11
深度求索突然出手!1M上下文碾压GPT-4?国内AI迎来全新突破
2026-02-11
从 Clawdbot 到 OpenClaw :揭秘 AI Agent 的三重生态系统供应链风险
2026-02-11
当 AI Agent 接管手机:移动端如何进行观测
2026-01-24
2026-01-10
2025-11-19
2026-01-26
2026-01-01
2025-12-09
2025-12-21
2026-01-09
2025-11-15
2026-01-09
2026-02-11
2026-02-11
2026-02-11
2026-02-11
2026-02-07
2026-02-04
2026-02-03
2026-02-03