我把 Claude Code 的 Token 费砍了 70%,只用了 SageMaker + 一个路由 Hook

上个月账单出来,我整个人都不好了------Claude Code 的 Token 费用翻了 4 倍。

团队 15 个人全员用上 Claude Code 之后,爽是真爽,贵也是真贵。

折腾了两周搞定了。核心思路:Claude Code 的任务不都需要强模型,简单任务分流到开源模型。实测成本降约 70%,开发者无感。

TL;DR

  • Amazon SageMaker 部署 Kimi-K2.5/GLM-5 开源模型
  • LiteLLM Proxy 做统一网关 + 智能路由
  • 自定义 Hook 根据 Prompt 特征自动分流
  • 支线任务(占 60%+)走私有化模型,主线任务走 Amazon Bedrock
  • 代码不出 VPC,性价比提升约 3.2 倍

Claude Code 的任务有"主线"和"支线"

Claude Code 执行任务时不是一路调同一个模型,会自动拆两类:

主线任务------你直接提的需求:

  • 代码重构、架构设计
  • 复杂 bug 排查
  • 技术方案评审

支线任务------Claude Code 自动触发的辅助操作:

  • 会话标题生成
  • Bash 命令描述
  • Hook 条件评估(判断是否要启动 SubAgent)
  • 下一步建议生成

我翻了一周调用日志,支线任务消耗 Token 占总量 60% 以上。输入输出格式固定、上下文短、逻辑简单,用 Claude Sonnet 纯属浪费。

架构长这样

java 复制代码
开发者 → Claude Code → LiteLLM Proxy
                            ├── 主线任务 → Amazon Bedrock (Claude Sonnet)
                            └── 支线任务 → Amazon SageMaker (Kimi/GLM)

LiteLLM Proxy 是统一入口,开发者不需要知道后面接了啥。一个自定义 Hook 负责根据 Prompt 特征判断任务类型,动态切换后端模型。

关于安全性:支线任务在 VPC 内处理,不出内网。主线走 Amazon Bedrock,有 VPC Endpoint 接入和 SOC2/ISO27001 认证。

动手:4 步搞定

Step 1: SageMaker 部署开源模型

推理引擎用 SGLang,原生支持 SageMaker Inference API。模型推荐 Kimi-K2.5 或 GLM-5。

bash 复制代码
git clone https://github.com/ybalbert001/claude-code-aws-skills.git
cd claude-code-aws-skills/skills/sglang-deploy

python deploy.py \
  --model-id kimi-k2.5 \
  --instance-type ml.p5.48xlarge \
  --endpoint-name kimi-endpoint \
  --region us-east-1

⚠️ 踩坑预警:第一次选了 ml.g5.12xlarge,直接 OOM。大模型吃显存,实例选型宁大勿小。

Step 2: LiteLLM Proxy 配置 + 启动

config.yaml 核心配置:

yaml 复制代码
general_settings:
  store_model_in_db: true
  master_key: "sk-your-master-key"

router_settings:
  timeout: 180

litellm_settings:
  callbacks:
    - "stream_anthropic_schema_fixer.hook"
    - "dynamic_tagging_handler.proxy_handler_instance"

model_list:
  - model_name: sagemaker-kimi-2-5
    litellm_params:
      model: sagemaker-chat/kimi-endpoint
      aws_region_name: us-east-1
      timeout: 180
      max_tokens: 8192
      drop_params: true
  - model_name: bedrock-claude-sonnet46
    litellm_params:
      model: bedrock/anthropic.claude-sonnet-4-6-v1:0
      aws_region_name: us-west-2
      timeout: 300

Docker Compose 一把启动:

yaml 复制代码
services:
  litellm:
    image: ghcr.io/berriai/litellm:v1.82.3-stable
    restart: always
    volumes:
      - ./config.yaml:/app/config.yaml
      - ./stream_anthropic_schema_fixer.py:/app/stream_anthropic_schema_fixer.py:ro
      - ./dynamic_tagging_handler.py:/app/dynamic_tagging_handler.py:ro
    command:
      - "--config=/app/config.yaml"
    ports:
      - "8080:4000"
    environment:
      DATABASE_URL: "postgresql://llmproxy:dbpassword9090@db:5432/litellm"
      STORE_MODEL_IN_DB: "True"
      ENABLE_ANTHROPIC_SCHEMA_FIX: "true"
    env_file:
      - .env
    depends_on:
      - db

  db:
    image: postgres:16
    restart: always
    environment:
      POSTGRES_USER: llmproxy
      POSTGRES_PASSWORD: dbpassword9090
      POSTGRES_DB: litellm
    volumes:
      - pgdata:/var/lib/postgresql/data

volumes:
  pgdata:
bash 复制代码
docker compose up -d
# 确认健康
curl http://localhost:8080/health

Step 3: Claude Code 连上 Proxy

一个 alias 搞定:

bash 复制代码
alias cc_proxy="ANTHROPIC_API_KEY=sk-your-litellm-key \
ANTHROPIC_BASE_URL=http://your-litellm-host:8080 \
ANTHROPIC_DEFAULT_SONNET_MODEL=bedrock-claude-sonnet46 \
ANTHROPIC_DEFAULT_HAIKU_MODEL=bedrock-claude-haiku45 \
CLAUDE_CODE_SUBAGENT_MODEL=bedrock-claude-sonnet45 \
claude"

cc_proxy 启动,和直连 Claude 体验一模一样。

Step 4: 动态路由 Hook(灵魂部分)

这是整套方案最关键的代码。LiteLLM 的 Callback Handler 能在 API 调用前拦截请求,根据 Prompt 特征改路由:

python 复制代码
# dynamic_tagging_handler.py
from litellm.integrations.custom_logger import CustomLogger


class DynamicRoutingHandler(CustomLogger):
    def log_pre_api_call(self, kwargs, response_obj, start_time, end_time):
        messages = kwargs.get("messages", [])
        full_text = self._extract_all_text(messages)

        task_model = self._detect_task_type(full_text)
        if task_model:
            kwargs["model"] = task_model
        return kwargs

    def _extract_all_text(self, messages):
        text_parts = []
        for msg in messages:
            content = msg.get("content", "")
            if isinstance(content, str):
                text_parts.append(content)
            elif isinstance(content, list):
                for block in content:
                    if block.get("type") == "text":
                        text_parts.append(block.get("text", ""))
        return " ".join(text_parts)

    def _detect_task_type(self, text):
        if self._is_hook_evaluator(text):
            return "sagemaker-kimi-2-5"
        elif self._is_session_title(text):
            return "sagemaker-kimi-2-5"
        elif self._is_bash_desc(text):
            return "sagemaker-kimi-2-5"
        elif len(text) > 10000:
            return "bedrock-claude-sonnet46"
        return None

    def _is_hook_evaluator(self, text):
        markers = [
            "You are evaluating a hook in Claude Code",
            "hook condition",
            "Return your evaluation as a JSON object",
            '"satisfied": true'
        ]
        return sum(1 for m in markers if m in text) >= 3

    def _is_session_title(self, text):
        return "Generate a short title" in text

    def _is_bash_desc(self, text):
        return "Describe what this bash command does" in text


proxy_handler_instance = DynamicRoutingHandler()

用多特征阈值匹配避免误判。_is_hook_evaluator 至少 3 个特征命中才路由,误判率极低。

还有个坑:Streaming Schema 不兼容

这个坑了我两天。Claude Code 流式解析器严格按 Anthropic Messages API 设计。开源模型返回数据丢字段,直接报错。

写了个 Hook 逐 chunk 补字段:

python 复制代码
# stream_anthropic_schema_fixer.py
from litellm.integrations.custom_logger import CustomLogger
from typing import AsyncGenerator, Optional, Dict, Any


class AnthropicSchemaFixerHook(CustomLogger):
    async def async_post_call_streaming_iterator_hook(
        self, user_api_key_dict, response: AsyncGenerator,
        request_data: dict
    ) -> AsyncGenerator:
        last_usage = None
        async for chunk in response:
            if not isinstance(chunk, bytes):
                yield chunk
                continue
            try:
                decoded = chunk.decode("utf-8")
                if not decoded.startswith("event:"):
                    yield chunk
                    continue

                event_type, data_json = self._parse_sse(decoded)
                modified = False

                if event_type == "message_start":
                    modified = self._fix_message_start(data_json)
                elif event_type == "message_delta":
                    modified, usage = self._fix_message_delta(data_json)
                    if usage:
                        last_usage = usage
                elif event_type == "message_stop":
                    modified = self._fix_message_stop(
                        data_json, last_usage
                    )

                if modified:
                    yield self._rebuild_sse(event_type, data_json)
                else:
                    yield chunk
            except Exception:
                yield chunk


hook = AnthropicSchemaFixerHook()

修复后流式响应正常。这个 Hook 不能省------不修复会 fallback 到非流式模式,SageMaker 容易 60 秒超时。

效果

指标 数据
支线任务占比 ~60%-65%
成本降低 ~70%
性价比提升 ~3.2 倍
开发者体验 无感
代码安全 支线不出 VPC

踩坑清单

  1. 实例选型 OOM:部署前算显存,宁大勿小
  2. Schema 不兼容:Claude Code 更新快,Schema 修复 Hook 得跟着调
  3. 路由误判:单特征匹配不靠谱,至少 3 个特征才路由
  4. 冷启动慢:SageMaker 第一次请求会慢,配 provisioned concurrency
  5. 版本锁定 :LiteLLM 锁 v1.82.3-stable,别随便升

参考链接

Kimi-K2.5 和 GLM-5 处理支线完全够用,下一步把翻译总结也分流。Token 费用还有压的空间。

评论区聊聊,互相避坑 🤝

相关推荐
yyuuuzz2 天前
谷歌云使用的几个常见注意事项
运维·服务器·网络·安全·web安全·云计算·aws
zhojiew2 天前
在AWS中国区的EMR集群中实现基于向量语义搜索的HBase运维诊断系统
运维·hbase·aws
yyuuuzz2 天前
独立开发者线上服务运维的几点实践经验
运维·服务器·网络·云计算·aws
zhojiew2 天前
使用DBT(data build tool)集成AWS Athena完成数据处理的实践
云计算·aws
yyuuuzz4 天前
aws的核心概念与常见使用场景
运维·服务器·网络·云计算·aws
zhojiew4 天前
在AWS云上使用EC2 嵌套虚拟化实例部署Cube Sandbox的实践和问题
云计算·aws
yyuuuzz5 天前
国际云服务器的技术特点与使用经验
运维·服务器·网络·数据库·云计算·aws
我是小邵6 天前
从 Supabase 迁移到 AWS 的云架构演进实践
架构·云计算·aws
炸裂狸花猫6 天前
开源身份认证与访问管理平台 - Keycloak(三)公有云Console集成实践(AWS / 阿里云 / OCI)
阿里云·云原生·keycloak·aws·oci·sso
xixixi777777 天前
AI的“账号”与“钱包”:AWS与Circle同日出手,AI正从工具进化
人工智能·安全·ai·大模型·云计算·aws