Skill: chatgpt34993/llm-key-pool (version 1.0.0)

--- name: llm-key-pool description: 多平台API Key分层轮询与智能故障转移；当用户需要绕过单一API Key速率限制、实现高可用大模型调用或管理多厂商API密钥池时使用 dependency: python: - pyyaml>=6.0.0 - requests>=2.28.0 --- # LLM Key Pool - 分层轮询多平台API Key管理 ## 任务目标 - 本Skill用于：多平台API Key智能管理，实现分层轮询、跨层切换和自动故障转移 - 能力包含：分层配置管理、自动轮询、跨层切换、429错误智能处理、统一OpenAI兼容接口 - 触发条件：当Agent需要稳定调用大模型API且面临速率限制或单点故障风险时 ## 前置准备 ### 依赖安装 SKILL依赖以下Python包： ``` pyyaml>=6.0.0 ``` ### 配置文件准备 在使用前需要创建配置文件 `llm_config.yaml`，支持分层配置： - **主力层（primary）**：高额度平台（阿里云百炼、智谱AI） - **每日回血层（daily）**：每日刷新平台（火山引擎、Google AI Studio） - **兜底层（fallback）**：开源/聚合平台（硅基流动、OpenRouter等） 配置文件格式见 [references/config_format.md](references/config_format.md) 支持的AI平台及配置方法见 [references/supported_providers.md](references/supported_providers.md) ## 操作步骤 ### 标准流程 1. **准备配置文件** - 在当前目录创建 `llm_config.yaml` - 按分层策略配置各平台的API Key - 优先配置主力层，然后是每日回血层，最后是兜底层 2. **调用LLM服务** - 执行脚本：`python -m llm_key_pool.llm_client` - 参数说明： - `--config`: 配置文件路径（默认：./llm_config.yaml） - `--prompt`: 用户提示词 - `--system-prompt`: 系统提示词（可选） - `--temperature`: 温度参数（可选，默认：0.7） - `--max-tokens`: 最大Token数（可选，默认：2000） 3. **自动分层轮询** - 优先使用主力层API Key - 主力层所有Key不可用时，自动切换到每日回血层 - 每日回血层也不可用时，切换到兜底层 - 跨层切换对上层应用透明 4. **智能故障转移** - 监听429 Too Many Requests错误 - 立即标记当前Key为冷却中 - 无缝切换到下一个Key或下一层 - 冷却结束后自动恢复 ### 可选分支 - **当需要测试配置是否正确**：使用 `--test` 参数进行配置验证 - **当需要查看Key池状态**：使用 `--status` 参数查看各层级Key的使用情况 ## 资源索引 ### 核心脚本 - [llm_key_pool/config_loader.py](llm_key_pool/config_loader.py) - 配置文件加载和验证 - [llm_key_pool/key_pool.py](llm_key_pool/key_pool.py) - 分层API Key池管理和轮询逻辑 - [llm_key_pool/llm_client.py](llm_key_pool/llm_client.py) - 统一LLM调用接口（OpenAI兼容） ### 参考文档 - [references/config_format.md](references/config_format.md) - 配置文件格式说明（分层版） - [references/supported_providers.md](references/supported_providers.md) - 支持的AI平台列表 ## 注意事项 - API Key信息敏感，请勿将配置文件提交到版本控制系统 - 建议按分层策略配置至少3个平台（主力层、每日回血层、兜底层各1个） - 429错误会触发立即冷却，冷却时间可通过配置调整 - 故障转移和跨层切换对上层应用透明，但会略微增加延迟 - 优先选择支持OpenAI兼容接口的平台，简化配置 ## 使用示例 ### 示例1：基本调用 ```bash python -m llm_key_pool.llm_client \ --config ./llm_config.yaml \ --prompt "解释什么是量子计算" \ --temperature 0.7 \ --max-tokens 500 ``` ### 示例2：带系统提示词 ```bash python -m llm_key_pool.llm_client \ --config ./llm_config.yaml \ --prompt "写一个关于AI的短故事" \ --system-prompt "你是一个创意写作专家" ``` ### 示例3：查看Key池状态 ```bash python -m llm_key_pool.llm_client \ --config ./llm_config.yaml \ --status ``` ### 示例4：验证配置 ```bash python -m llm_key_pool.llm_client \ --config ./llm_config.yaml \ --test ``` ## 分层轮询策略 ### 主力层（primary） - **特点**：初始赠送额度极大 - **推荐平台**：阿里云百炼、智谱AI - **用途**：处理大部分日常任务 ### 每日回血层（daily） - **特点**：额度每日刷新 - **推荐平台**：火山引擎、Google AI Studio - **用途**：主力层耗尽后保证基本可用性 ### 兜底层（fallback） - **特点**：开源模型/聚合平台 - **推荐平台**：硅基流动、OpenRouter、GitHub Models、Groq - **用途**：大厂API都限流时保证服务不中断

path: .gitignore
content: # Config with secrets
llm_config.yaml

# Python
*.pyc
__pycache__/
*.egg-info/
.venv/
venv/
dist/
build/
*.egg

# IDE
.idea/
.vscode/


path: pyproject.toml
content: [build-system]
requires = ["setuptools>=68.0", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "llm-key-pool"
version = "0.1.0"
description = "分层轮询多平台 LLM API Key 管理工具，支持故障转移与自动切换"
readme = "README.md"
license = {text = "Apache-2.0"}
requires-python = ">=3.9"
dependencies = [
    "pyyaml>=6.0.0",
    "requests>=2.28.0",
]

[project.optional-dependencies]
dev = [
    "pytest>=7.0",
]

[project.scripts]
llm-key-pool = "llm_key_pool.llm_client:main"

[tool.setuptools.packages.find]
include = ["llm_key_pool*"]


path: README.md
content: # LLM Key Pool

分层轮询多平台 LLM API Key 管理工具，支持故障转移与自动切换。

## 兼容运行平台

本工具/skill 可安装运行于以下AI编程客户端：

| 平台 | 安装方式 | 支持 |
|------|---------|------|
| **Claude Code** | Skill 直接安装 | ✅ 原生支持 |
| **OpenClaw** | 作为Python依赖库集成 | ✅ 原生支持 |
| **OpenCode** | 作为Python依赖库集成 | ✅ 原生支持 |
| **通义千问 (Qwen)** | 命令行调用 / Python集成 | ✅ 支持 |
| **Any editor/IDE** | Python脚本调用 | ✅ 支持 |
| **独立使用** | 命令行直接调用 | ✅ 支持 |

## 功能

- **三层 Key 池**：主力层 (primary) → 每日回血层 (daily) → 兜底层 (fallback)
- **智能故障转移**：429 自动冷却、401 永久禁用、500/503 重试
- **OpenAI 兼容接口**：统一 `/chat/completions` 格式，同时支持 Anthropic Messages API
- **多平台支持**：主流大模型厂商全兼容（见下方完整列表）
- **线程安全**：内置锁机制，支持并发调用

## 支持的AI模型平台

所有支持OpenAI兼容接口的大模型平台均可接入，当前已验证支持：

### 主力层 (primary) - 高额度推荐
- **阿里云百炼** - qwen-max, qwen-plus, qwen-turbo
- **智谱AI** - glm-4, glm-3-turbo, glm-4-air
- **小龙虾** - xiaolongxia-72b, xiaolongxia-34b

### 每日回血层 (daily) - 额度每日刷新
- **火山引擎** - doubao-pro-256k, doubao-pro-32k
- **Google AI Studio** - gemini-1.5-pro, gemini-1.5-flash

### 兜底层 (fallback) - 开源/聚合平台保证可用性
- **硅基流动** - Qwen2.5, Llama 3.1, GLM-4 等开源模型
- **OpenRouter** - 聚合上百种大模型，免费模型可用
- **GitHub Models** - GitHub官方模型服务
- **Groq** - 极速推理，免费额度
- **OpenCode** - 专注代码生成
- **Qwen独立API** - 通义千问独立API服务
- **ClaudeCode (Anthropic)** - claude-3-5-sonnet, claude-3-opus
- **腾讯混元** - hunyuan-lite, hunyuan-standard

> 详细配置说明见：[references/supported_providers.md](references/supported_providers.md)

## 安装

### 方式一：作为 Claude Code Skill 安装（推荐）

Claude Code 用户可以安装为 Skill：

```bash
# 创建 skills 目录（如果不存在）
mkdir -p ~/.claude/skills

# 克隆或复制项目到 skills 目录
git clone https://github.com/OpenClaw/llm-key-pool.git ~/.claude/skills/llm-key-pool

# 安装依赖
pip install -r ~/.claude/skills/llm-key-pool/requirements.txt
```

Windows 路径：
```
C:\Users\用户名\.claude\skills\llm-key-pool\
```

安装后，Claude Code 需要调用大模型时会自动触发该 Skill。

### 方式二：作为独立 Python 包安装

```bash
pip install .
```

或可编辑模式（开发）：

```bash
pip install -e .
```

## 快速开始

### 1. 安装依赖

```bash
pip install -r requirements.txt
# 或者如果已经 pip 安装了包，可以跳过这一步
```

### 2. 创建配置文件（填入你的 API Key）

项目中已经提供了配置模板，只需要复制并填写你的 API Key：

```bash
# 复制模板文件到当前目录
cp assets/llm_config.yaml.example ./llm_config.yaml

# 编辑文件，替换占位符为你的真实 API Key
notepad llm_config.yaml  # Windows
# 或者
vim llm_config.yaml     # Linux/macOS
```

**模板位置**: `assets/llm_config.yaml.example` 已经预填了各大平台的配置，你只需要：
- 去掉不需要平台的注释（或保留，删掉占位符 `sk-...` 换成你的 Key）
- 将 `"[REDACTED_SECRET]"` 替换成你真实的 API Key
- `.gitignore` 已经默认忽略 `llm_config.yaml`，不用担心密钥泄露

### 3. 调用 LLM

```bash
python -m llm_key_pool.llm_client \
  --config ./llm_config.yaml \
  --prompt "你好，请介绍一下自己"
```

### 4. 在 Python 代码中使用

```python
from llm_key_pool import TieredLLMClient

client = TieredLLMClient('./llm_config.yaml')
result, usage_info = client.call_llm(prompt="你好")
print(result)
```

## 分层架构

| 层级 | 说明 | 推荐平台 |
|------|------|---------|
| `primary` | 主力层，高额度 | 阿里云百炼、智谱 AI |
| `daily` | 每日回血层，额度每日刷新 | 火山引擎、Google AI Studio |
| `fallback` | 兜底层，开源/聚合平台 | 硅基流动、OpenRouter、Groq |

系统自动从主力层开始轮询，不可用时自动切换到下一层，无需手动干预。

## 命令行参数

| 参数 | 说明 |
|------|------|
| `--config` | 配置文件路径（默认：`./llm_config.yaml`） |
| `--prompt` | 用户提示词 |
| `--system-prompt` | 系统提示词（可选） |
| `--temperature` | 温度参数（默认：0.7） |
| `--max-tokens` | 最大 Token 数（默认：2000） |
| `--test` | 测试配置 |
| `--status` | 查看 Key 池状态 |

## 配置示例

```yaml
providers:
  alibaba_bailian:
    tier: primary
    model: "qwen-max"
    api_keys:
      - "sk-your-api-key"
    base_url: "https://dashscope.aliyuncs.com/compatible-mode/v1"

  siliconflow:
    tier: fallback
    model: "Qwen/Qwen2.5-7B-Instruct"
    api_keys:
      - "sk-your-api-key"
    base_url: "https://api.siliconflow.cn/v1"

global:
  max_retries: 3
  cooldown_seconds: 300
  error_threshold: 5
```

## 项目结构

```
llm-key-pool/
├── llm_key_pool/            # 核心包
│   ├── __init__.py
│   ├── config_loader.py     # 配置加载与验证
│   ├── key_pool.py          # Key 池管理与轮询
│   └── llm_client.py        # LLM 调用客户端
├── assets/                  # 模板与示例
├── references/              # 参考文档
├── pyproject.toml
├── requirements.txt
├── LICENSE
└── README.md
```

## 许可证

Apache License 2.0


path: requirements.txt
content: pyyaml>=6.0.0
requests>=2.28.0


path: skill.yaml
content: ---
name: llm-key-pool
description: 多平台API Key分层轮询池 | 自动切换配额，1亿+免费Token高可用调用
author: OpenClaw
version: 0.1.0
license: Apache-2.0
tags:
  - llm
  - api-key
  - load-balancing
  - failover
  - alibaba-bailian
  - model

arguments:
  - name: prompt
    description: 用户提示词
    required: true
  - name: system-prompt
    description: 系统提示词（可选）
    required: false
  - name: temperature
    description: 温度参数（默认: 0.7）
    required: false
  - name: max-tokens
    description: 最大Token数（默认: 2000）
    required: false

# Python依赖
dependencies:
  python:
    - pyyaml>=6.0.0
    - requests>=2.28.0

# 入口脚本
entrypoint: scripts/run.sh


path: scripts/run.sh
content: #!/bin/bash
#
# llm-key-pool Skill入口脚本
#

# 获取参数
PROMPT="$1"
shift

# 运行llm-key-pool，默认使用Skill目录下的配置
llm-key-pool --prompt "$PROMPT" "$@"


path: references/config_format.md
content: # 配置文件格式说明（分层轮询版）

## 目录
- [格式概述](#格式概述)
- [分层策略](#分层策略)
- [完整示例](#完整示例)
- [字段说明](#字段说明)
- [验证规则](#验证规则)
- [常见错误](#常见错误)

## 格式概述

配置文件使用YAML格式，文件名默认为 `llm_config.yaml`，支持多平台分层配置。

**分层策略：**
- **primary（主力层）**：高额度平台，处理大部分日常任务
- **daily（每日回血层）**：每日刷新额度，保证基本可用性
- **fallback（兜底层）**：开源/聚合平台，保证服务不中断

**基本结构：**
```yaml
providers:
  <platform_name>:
    tier: "primary|daily|fallback"  # 层级（必需）
    model: "model_name"             # 模型名称（必需）
    api_keys:                       # API Key列表（必需）
      - "key_1"
      - "key_2"
    base_url: "https://api.example.com/v1"  # API基础URL（必需）

global:
  max_retries: 3                   # 最大重试次数
  retry_delay: 1                   # 重试延迟（秒）
  error_threshold: 5               # 错误阈值
  cooldown_seconds: 300            # 冷却时间（秒）
  quota_check_enabled: true        # 是否启用配额检查
```

## 分层策略

### 主力层（primary）
**特点：** 初始赠送额度极大，适合处理大部分日常任务

**推荐平台：**
- 阿里云百炼（DashScope）
- 智谱AI（BigModel）

**配置示例：**
```yaml
alibaba_bailian:
  tier: primary
  model: "qwen-max"
  api_keys:
    - "sk-xxxxxxxxxxxxxxxx"
  base_url: "https://dashscope.aliyuncs.com/compatible-mode/v1"

zhipu:
  tier: primary
  model: "glm-4"
  api_keys:
    - "xxxxxxxxxxxxxxxx"
  base_url: "https://open.bigmodel.cn/api/paas/v4"
```

### 每日回血层（daily）
**特点：** 额度每日刷新，即使主力层耗尽也能保证基本可用性

**推荐平台：**
- 火山引擎（字节跳动）
- Google AI Studio

**配置示例：**
```yaml
volcengine:
  tier: daily
  model: "doubao-pro-256k"
  api_keys:
    - "xxxxxxxxxxxxxxxx"
  base_url: "https://ark.cn-beijing.volces.com/api/v3"

google_aistudio:
  tier: daily
  model: "gemini-1.5-pro"
  api_keys:
    - "AIzaSyxxxxxxxxxxxxxxxx"
  base_url: "https://generativelanguage.googleapis.com/v1beta"
```

### 兜底层（fallback）
**特点：** 开源模型/聚合平台，当大厂API都限流时保证服务不中断

**推荐平台：**
- 硅基流动（SiliconFlow）
- OpenRouter
- GitHub Models
- Groq
- 腾讯混元

**配置示例：**
```yaml
siliconflow:
  tier: fallback
  model: "Qwen/Qwen2.5-7B-Instruct"
  api_keys:
    - "sk-xxxxxxxxxxxxxxxx"
  base_url: "https://api.siliconflow.cn/v1"

openrouter:
  tier: fallback
  model: "meta-llama/llama-3.1-8b-instruct:free"
  api_keys:
    - "sk-or-xxxxxxxxxxxxxxxx"
  base_url: "https://openrouter.ai/api/v1"
```

## 完整示例

```yaml
# 多平台分层配置示例
providers:
  # 主力层：阿里云百炼
  alibaba_bailian:
    tier: primary
    model: "qwen-max"
    api_keys:
      - "[REDACTED_SECRET]"
      - "[REDACTED_SECRET]"
    base_url: "https://dashscope.aliyuncs.com/compatible-mode/v1"

  # 主力层：智谱AI
  zhipu:
    tier: primary
    model: "glm-4"
    api_keys:
      - "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
      - "yyyyyyyyyyyyyyyyyyyyyyyyyyyy"
    base_url: "https://open.bigmodel.cn/api/paas/v4"

  # 每日回血层：火山引擎
  volcengine:
    tier: daily
    model: "doubao-pro-256k"
    api_keys:
      - "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
    base_url: "https://ark.cn-beijing.volces.com/api/v3"

  # 每日回血层：Google AI Studio
  google_aistudio:
    tier: daily
    model: "gemini-1.5-pro"
    api_keys:
      - "AIzaSyxxxxxxxxxxxxxxxxxxxxxxxx"
    base_url: "https://generativelanguage.googleapis.com/v1beta"

  # 兜底层：硅基流动
  siliconflow:
    tier: fallback
    model: "Qwen/Qwen2.5-7B-Instruct"
    api_keys:
      - "[REDACTED_SECRET]"
    base_url: "https://api.siliconflow.cn/v1"

  # 兜底层：OpenRouter
  openrouter:
    tier: fallback
    model: "meta-llama/llama-3.1-8b-instruct:free"
    api_keys:
      - "[REDACTED_SECRET]"
    base_url: "https://openrouter.ai/api/v1"

# 全局配置
global:
  max_retries: 3              # 最大重试次数
  retry_delay: 1              # 重试延迟（秒）
  error_threshold: 5          # 错误阈值
  cooldown_seconds: 300       # 冷却时间（秒）
  quota_check_enabled: true   # 是否启用配额检查
```

## 字段说明

### providers（必需）
各平台的配置集合，每个平台是一个独立的配置对象。

#### 平台配置字段

| 字段 | 必需 | 类型 | 说明 |
|------|------|------|------|
| `tier` | 是 | string | 层级（primary/daily/fallback） |
| `model` | 是 | string | 模型名称 |
| `api_keys` | 是 | array | API Key数组，至少包含一个有效的Key |
| `base_url` | 是 | string | API基础URL |

### global（可选）
全局配置项，影响所有平台的行为。

| 字段 | 默认值 | 说明 |
|------|--------|------|
| `max_retries` | 3 | 最大重试次数 |
| `retry_delay` | 1 | 重试延迟（秒） |
| `error_threshold` | 5 | 错误阈值，超过则暂时禁用Key |
| `cooldown_seconds` | 300 | 冷却时间（秒） |
| `quota_check_enabled` | true | 是否启用配额检查 |

## 验证规则

配置文件必须满足以下规则：

1. **格式正确性**
   - 必须是有效的YAML格式
   - `providers` 字段必须存在且非空

2. **平台配置**
   - 每个平台必须包含 `tier`、`model`、`api_keys`、`base_url` 字段
   - `tier` 必须是以下值之一：`primary`、`daily`、`fallback`
   - `api_keys` 数组必须包含至少一个非空字符串

3. **值类型**
   - `tier`、`model` 和 `base_url` 必须是字符串
   - `api_keys` 必须是数组
   - 全局配置中的数值字段必须是非负整数
   - `quota_check_enabled` 必须是布尔值

4. **URL格式**
   - `base_url` 必须是有效的HTTP/HTTPS URL

## 常见错误

### 错误1：缺少tier字段
```yaml
# 错误：缺少tier字段
providers:
  alibaba_bailian:
    model: "qwen-max"
    api_keys:
      - "sk-xxxxxxxx"
    base_url: "https://dashscope.aliyuncs.com/compatible-mode/v1"
```

**修正：**
```yaml
providers:
  alibaba_bailian:
    tier: primary  # 添加tier字段
    model: "qwen-max"
    api_keys:
      - "sk-xxxxxxxx"
    base_url: "https://dashscope.aliyuncs.com/compatible-mode/v1"
```

### 错误2：tier值无效
```yaml
# 错误：tier值无效
providers:
  alibaba_bailian:
    tier: "high"  # 应该是 primary/daily/fallback
    model: "qwen-max"
    api_keys:
      - "sk-xxxxxxxx"
    base_url: "https://dashscope.aliyuncs.com/compatible-mode/v1"
```

**修正：**
```yaml
providers:
  alibaba_bailian:
    tier: primary  # 使用正确的层级值
    model: "qwen-max"
    api_keys:
      - "sk-xxxxxxxx"
    base_url: "https://dashscope.aliyuncs.com/compatible-mode/v1"
```

## 配置模板

```yaml
providers:
  # 主力层
  alibaba_bailian:
    tier: primary
    model: "qwen-max"
    api_keys:
      - "your-api-key-1"
      - "your-api-key-2"
    base_url: "https://dashscope.aliyuncs.com/compatible-mode/v1"

  # 每日回血层
  volcengine:
    tier: daily
    model: "doubao-pro-256k"
    api_keys:
      - "your-api-key"
    base_url: "https://ark.cn-beijing.volces.com/api/v3"

  # 兜底层
  siliconflow:
    tier: fallback
    model: "Qwen/Qwen2.5-7B-Instruct"
    api_keys:
      - "your-api-key"
    base_url: "https://api.siliconflow.cn/v1"

global:
  max_retries: 3
  retry_delay: 1
  error_threshold: 5
  cooldown_seconds: 300
  quota_check_enabled: true
```

## OpenAI兼容接口说明

本Skill优先使用OpenAI兼容接口，所有配置的平台都应提供OpenAI兼容的API端点：

**标准请求格式：**
```
POST {base_url}/chat/completions
Authorization: Bearer {api_key}
Content-Type: application/json

{
  "model": "model_name",
  "messages": [...],
  "temperature": 0.7,
  "max_tokens": 2000
}
```

**标准响应格式：**
```json
{
  "choices": [
    {
      "message": {
        "content": "响应内容"
      }
    }
  ]
}
```

各平台的OpenAI兼容接口地址请参考 [supported_providers.md](supported_providers.md)


path: references/supported_providers.md
content: # 支持的AI平台列表（OpenAI兼容接口）

## 目录
- [主力层（primary）](#主力层primary)
- [每日回血层（daily）](#每日回血层daily)
- [兜底层（fallback）](#兜底层fallback)
- [接口标准化说明](#接口标准化说明)
- [平台对比](#平台对比)

## 主力层（primary）

### 阿里云百炼（DashScope）

**特点：** 初始赠送额度极大，适合处理大部分日常任务

**支持的模型：**
- qwen-max
- qwen-plus
- qwen-turbo

**API Key获取方式：**
1. 访问 [阿里云百炼控制台](https://bailian.console.aliyun.com/)
2. 创建API Key
3. 复制生成的Key（格式：`sk-xxxxxxxxxxxxxxxx`）

**配置示例：**
```yaml
alibaba_bailian:
  tier: primary
  model: "qwen-max"
  api_keys:
    - "sk-xxxxxxxxxxxxxxxx"
  base_url: "https://dashscope.aliyuncs.com/compatible-mode/v1"
```

**OpenAI兼容接口：**
- 端点：`https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions`
- 文档：[OpenAI兼容接口文档](https://help.aliyun.com/zh/dashscope/developer-reference/compatibility-of-openai-with-dashscope)

**速率限制：**
- Qwen-Max：约100请求/分钟
- 具体限制取决于账户类型

### 智谱AI（BigModel）

**特点：** 国产大模型，初始赠送额度大

**支持的模型：**
- glm-4
- glm-3-turbo
- glm-4-air

**API Key获取方式：**
1. 访问 [智谱AI开放平台](https://open.bigmodel.cn/)
2. 注册账号，创建API Key
3. 复制生成的Key

**配置示例：**
```yaml
zhipu:
  tier: primary
  model: "glm-4"
  api_keys:
    - "xxxxxxxxxxxxxxxx"
  base_url: "https://open.bigmodel.cn/api/paas/v4"
```

**OpenAI兼容接口：**
- 端点：`https://open.bigmodel.cn/api/paas/v4/chat/completions`
- 文档：[智谱AI API文档](https://open.bigmodel.cn/dev/api#glm-4)

**速率限制：**
- GLM-4：约20请求/分钟
- 具体限制取决于账户类型

### 小龙虾（XiaoLongXia）

**特点：** 专为大模型推理优化，性价比高

**支持的模型：**
- xiaolongxia-72b
- xiaolongxia-34b
- xiaolongxia-14b

**API Key获取方式：**
1. 访问小龙虾官网获取API Key
2. 注册账号，创建API Key
3. 复制生成的Key

**配置示例：**
```yaml
xiaolongxia:
  tier: primary
  model: "xiaolongxia-72b"
  api_keys:
    - "your_api_key"
  base_url: "https://api.xiaolongxia.com/v1"
```

**OpenAI兼容接口：**
- 端点：`https://api.xiaolongxia.com/v1/chat/completions`
- 支持标准OpenAI格式

**速率限制：**
- 根据套餐不同，限制不同
- 具体限制取决于账户类型

## 每日回血层（daily）

### 火山引擎（字节跳动）

**特点：** 额度每日刷新，稳定可用

**支持的模型：**
- doubao-pro-256k
- doubao-pro-32k
- doubao-lite-32k

**API Key获取方式：**
1. 访问 [火山引擎控制台](https://console.volcengine.com/ark)
2. 创建应用并获取API Key
3. 配置推理接入点

**配置示例：**
```yaml
volcengine:
  tier: daily
  model: "doubao-pro-256k"
  api_keys:
    - "xxxxxxxxxxxxxxxx"
  base_url: "https://ark.cn-beijing.volces.com/api/v3"
```

**OpenAI兼容接口：**
- 端点：`https://ark.cn-beijing.volces.com/api/v3/chat/completions`
- 文档：[火山引擎API文档](https://www.volcengine.com/docs/82379)

**速率限制：**
- 每日免费额度：根据账户类型
- 超出后按量计费

### Google AI Studio

**特点：** 每日免费额度，Gemini模型质量高

**支持的模型：**
- gemini-1.5-pro
- gemini-1.5-flash

**API Key获取方式：**
1. 访问 [Google AI Studio](https://aistudio.google.com/)
2. 创建API Key
3. 复制生成的Key（格式：`AIzaSyxxxxxxxx`）

**配置示例：**
```yaml
google_aistudio:
  tier: daily
  model: "gemini-1.5-pro"
  api_keys:
    - "AIzaSyxxxxxxxxxxxxxxxx"
  base_url: "https://generativelanguage.googleapis.com/v1beta"
```

**注意：** Google Gemini的API Key需要作为URL参数传递，需要特殊处理

**速率限制：**
- 每日免费额度：15请求/分钟（Gemini 1.5 Pro）
- 具体限制取决于账户类型

## 兜底层（fallback）

### 硅基流动（SiliconFlow）

**特点：** 提供大量开源模型，支持免费额度

**支持的模型：**
- Qwen/Qwen2.5-7B-Instruct
- meta-llama/Llama-3.1-8B-Instruct
- THUDM/glm-4-9b-chat

**API Key获取方式：**
1. 访问 [硅基流动官网](https://siliconflow.cn/)
2. 注册账号，创建API Key
3. 复制生成的Key（格式：`sk-xxxxxxxx`）

**配置示例：**
```yaml
siliconflow:
  tier: fallback
  model: "Qwen/Qwen2.5-7B-Instruct"
  api_keys:
    - "sk-xxxxxxxxxxxxxxxx"
  base_url: "https://api.siliconflow.cn/v1"
```

**OpenAI兼容接口：**
- 端点：`https://api.siliconflow.cn/v1/chat/completions`
- 文档：[硅基流动API文档](https://docs.siliconflow.cn/docs)

**速率限制：**
- 免费额度：每日一定额度
- 超出后按量计费

### OpenCode

**特点：** 专注代码生成的AI平台

**支持的模型：**
- opencode-72b
- opencode-34b
- opencode-14b

**API Key获取方式：**
1. 访问OpenCode官网
2. 注册账号，创建API Key
3. 复制生成的Key

**配置示例：**
```yaml
opencode:
  tier: fallback
  model: "opencode-72b"
  api_keys:
    - "your_api_key"
  base_url: "https://api.opencode.com/v1"
```

**OpenAI兼容接口：**
- 端点：`https://api.opencode.com/v1/chat/completions`
- 支持标准OpenAI格式

**速率限制：**
- 根据套餐不同，限制不同
- 具体限制取决于账户类型

### Qwen（通义千问独立API）

**特点：** 阿里云通义千问独立API，与百炼平台分开

**支持的模型：**
- qwen-max
- qwen-plus
- qwen-turbo
- qwen-long

**API Key获取方式：**
1. 访问通义千问官网
2. 注册账号，创建API Key
3. 复制生成的Key

**配置示例：**
```yaml
qwen:
  tier: fallback
  model: "qwen-max"
  api_keys:
    - "sk-xxxxxxxxxxxxxxxx"
  base_url: "https://api.qwen.ai/v1"
```

**OpenAI兼容接口：**
- 端点：`https://api.qwen.ai/v1/chat/completions`
- 支持标准OpenAI格式

**速率限制：**
- 免费额度：每日一定额度
- 超出后按量计费

### ClaudeCode

**特点：** Anthropic Claude代码专用版本

**支持的模型：**
- claude-3-5-sonnet-20241022
- claude-3-opus-20240229
- claude-3-sonnet-20240229

**API Key获取方式：**
1. 访问Anthropic控制台
2. 创建API Key
3. 复制生成的Key（格式：`sk-ant-xxxxxxxx`）

**配置示例：**
```yaml
claudecode:
  tier: fallback
  model: "claude-3-5-sonnet-20241022"
  api_keys:
    - "[REDACTED_SECRET]"
  base_url: "https://api.anthropic.com/v1"
```

**OpenAI兼容接口：**
- 端点：`https://api.anthropic.com/v1/messages`
- 需要特殊处理：使用`x-api-key`头而不是`Authorization: Bearer`
- 需要添加`anthropic-version: 2023-06-01`头

**速率限制：**
- Claude 3.5 Sonnet：约50请求/分钟
- 具体限制取决于账户类型

**注意：** ClaudeCode使用Anthropic的Messages API，虽然兼容OpenAI格式，但需要特殊的请求头处理

### OpenRouter

**特点：** 聚合多个模型，提供免费模型

**支持的模型：**
- meta-llama/llama-3.1-8b-instruct:free
- mistralai/mistral-7b-instruct:free
- google/gemma-7b-it:free

**API Key获取方式：**
1. 访问 [OpenRouter官网](https://openrouter.ai/)
2. 注册账号，创建API Key
3. 复制生成的Key（格式：`sk-or-xxxxxxxx`）

**配置示例：**
```yaml
openrouter:
  tier: fallback
  model: "meta-llama/llama-3.1-8b-instruct:free"
  api_keys:
    - "sk-or-xxxxxxxxxxxxxxxx"
  base_url: "https://openrouter.ai/api/v1"
```

**OpenAI兼容接口：**
- 端点：`https://openrouter.ai/api/v1/chat/completions`
- 文档：[OpenRouter API文档](https://openrouter.ai/docs/quick-start)

**速率限制：**
- 免费模型：有限制
- 付费模型：更高配额

### GitHub Models

**特点：** GitHub官方模型服务

**支持的模型：**
- gpt-4o
- gpt-4o-mini
- 其他开源模型

**API Key获取方式：**
1. 访问 [GitHub Marketplace](https://github.com/marketplace/models)
2. 获取GitHub Token
3. 配置模型访问权限

**配置示例：**
```yaml
github_models:
  tier: fallback
  model: "gpt-4o"
  api_keys:
    - "ghp_xxxxxxxxxxxxxxxx"
  base_url: "https://models.inference.ai.azure.com"
```

**OpenAI兼容接口：**
- 端点：`https://models.inference.ai.azure.com/chat/completions`
- 文档：[GitHub Models文档](https://docs.github.com/en/rest/models)

### Groq

**特点：** 极速推理，免费额度

**支持的模型：**
- llama3.1-70b-versatile
- llama3.1-8b-instant
- mixtral-8x7b-32768

**API Key获取方式：**
1. 访问 [Groq官网](https://groq.com/)
2. 注册账号，创建API Key
3. 复制生成的Key（格式：`gsk_xxxxxxxxxxxxxxxx`）

**配置示例：**
```yaml
groq:
  tier: fallback
  model: "llama3.1-70b-versatile"
  api_keys:
    - "gsk_xxxxxxxxxxxxxxxx"
  base_url: "https://api.groq.com/openai/v1"
```

**OpenAI兼容接口：**
- 端点：`https://api.groq.com/openai/v1/chat/completions`
- 文档：[Groq API文档](https://console.groq.com/docs)

**速率限制：**
- 免费层：30请求/分钟
- 付费层：更高配额

### 腾讯混元

**特点：** 腾讯出品，支持免费额度

**支持的模型：**
- hunyuan-lite
- hunyuan-standard
- hunyuan-pro

**API Key获取方式：**
1. 访问 [腾讯云混元](https://cloud.tencent.com/product/hunyuan)
2. 开通服务，获取SecretId和SecretKey
3. 生成API Key

**配置示例：**
```yaml
tencent_hunyuan:
  tier: fallback
  model: "hunyuan-lite"
  api_keys:
    - "xxxxxxxxxxxxxxxx"
  base_url: "https://hunyuan.tencentcloudapi.com/v1"
```

**注意：** 腾讯混元需要使用特殊的签名方式，可能需要适配

**速率限制：**
- 免费额度：每日一定额度
- 超出后按量计费

## 接口标准化说明

本Skill优先使用**OpenAI兼容接口**，所有配置的平台都应提供OpenAI兼容的API端点。

### 统一请求格式

```http
POST {base_url}/chat/completions
Authorization: Bearer {api_key}
Content-Type: application/json

{
  "model": "model_name",
  "messages": [
    {"role": "system", "content": "系统提示词"},
    {"role": "user", "content": "用户提示词"}
  ],
  "temperature": 0.7,
  "max_tokens": 2000
}
```

### 统一响应格式

```json
{
  "choices": [
    {
      "message": {
        "content": "响应内容",
        "role": "assistant"
      },
      "finish_reason": "stop"
    }
  ],
  "usage": {
    "prompt_tokens": 10,
    "completion_tokens": 20,
    "total_tokens": 30
  }
}
```

### 异常处理

重点监听以下错误码：

| 错误码 | 说明 | 处理方式 |
|--------|------|----------|
| 429 | Too Many Requests | 立即冷却Key，切换到下一个Key |
| 401 | Unauthorized | 永久禁用该Key |
| 500 | Internal Server Error | 短暂重试（最多3次） |
| 503 | Service Unavailable | 短暂重试（最多3次） |

## 平台对比

| 平台 | 层级 | 模型质量 | 速率限制 | 价格 | OpenAI兼容 |
|------|------|---------|---------|------|-----------|
| 阿里云百炼 | primary | ⭐⭐⭐⭐⭐ | 高 | 中 | ✓ |
| 智谱AI | primary | ⭐⭐⭐⭐ | 中 | 中 | ✓ |
| 小龙虾 | primary | ⭐⭐⭐⭐ | 高 | 中 | ✓ |
| 火山引擎 | daily | ⭐⭐⭐⭐ | 每日刷新 | 中 | ✓ |
| Google AI Studio | daily | ⭐⭐⭐⭐⭐ | 每日刷新 | 中 | 部分 |
| 硅基流动 | fallback | ⭐⭐⭐ | 中 | 低 | ✓ |
| OpenRouter | fallback | ⭐⭐⭐⭐ | 中 | 低 | ✓ |
| GitHub Models | fallback | ⭐⭐⭐⭐ | 中 | 低 | ✓ |
| Groq | fallback | ⭐⭐⭐⭐ | 高 | 低 | ✓ |
| 腾讯混元 | fallback | ⭐⭐⭐ | 中 | 低 | 部分 |
| OpenCode | fallback | ⭐⭐⭐⭐ | 中 | 低 | ✓ |
| Qwen独立API | fallback | ⭐⭐⭐⭐⭐ | 中 | 低 | ✓ |
| ClaudeCode | fallback | ⭐⭐⭐⭐⭐ | 中 | 高 | 特殊 |

## 配置建议

### 最小配置（3个平台）
```yaml
providers:
  alibaba_bailian:
    tier: primary
    model: "qwen-max"
    api_keys: ["sk-xxx"]
    base_url: "https://dashscope.aliyuncs.com/compatible-mode/v1"

  volcengine:
    tier: daily
    model: "doubao-pro-256k"
    api_keys: ["sk-xxx"]
    base_url: "https://ark.cn-beijing.volces.com/api/v3"

  siliconflow:
    tier: fallback
    model: "Qwen/Qwen2.5-7B-Instruct"
    api_keys: ["sk-xxx"]
    base_url: "https://api.siliconflow.cn/v1"
```

### 推荐配置（5个平台）
```yaml
providers:
  # 主力层
  alibaba_bailian:
    tier: primary
    model: "qwen-max"
    api_keys: ["sk-xxx1", "sk-xxx2"]
    base_url: "https://dashscope.aliyuncs.com/compatible-mode/v1"

  zhipu:
    tier: primary
    model: "glm-4"
    api_keys: ["sk-xxx"]
    base_url: "https://open.bigmodel.cn/api/paas/v4"

  # 每日回血层
  volcengine:
    tier: daily
    model: "doubao-pro-256k"
    api_keys: ["sk-xxx"]
    base_url: "https://ark.cn-beijing.volces.com/api/v3"

  google_aistudio:
    tier: daily
    model: "gemini-1.5-pro"
    api_keys: ["AIzaSyxxx"]
    base_url: "https://generativelanguage.googleapis.com/v1beta"

  # 兜底层
  siliconflow:
    tier: fallback
    model: "Qwen/Qwen2.5-7B-Instruct"
    api_keys: ["sk-xxx"]
    base_url: "https://api.siliconflow.cn/v1"
```

## 注意事项

1. **API Key安全**
   - 不要在代码中硬编码API Key
   - 使用配置文件管理API Key
   - 定期轮换API Key

2. **分层策略**
   - 优先使用主力层，保证服务质量
   - 主力层耗尽后自动切换到每日回血层
   - 最后使用兜底层保证服务不中断

3. **接口兼容性**
   - 优先选择支持OpenAI兼容接口的平台
   - 只需更换Base_URL和API Key
   - 统一请求/响应格式

4. **错误处理**
   - 429错误：立即冷却，无缝切换
   - 401错误：永久禁用
   - 500错误：短暂重试


path: llm_key_pool/config_loader.py
content: """
配置文件加载器
负责加载、验证YAML配置文件
"""

import yaml
import os
from typing import Dict, Any, List
from urllib.parse import urlparse


class ConfigValidationError(Exception):
    """配置验证错误"""
    pass


class ConfigLoader:
    """配置文件加载器"""

    # 全局配置默认值
    DEFAULT_GLOBAL_CONFIG = {
        'max_retries': 3,
        'retry_delay': 1,
        'error_threshold': 5,
        'cooldown_seconds': 300,
        'quota_check_enabled': True,
        'quota_min_remaining': 1000  # 剩余Token少于这个值自动切换
    }

    def __init__(self, config_path: str):
        """
        初始化配置加载器

        Args:
            config_path: 配置文件路径
        """
        self.config_path = config_path
        self.config = None

    def load(self) -> Dict[str, Any]:
        """
        加载配置文件

        Returns:
            配置字典

        Raises:
            FileNotFoundError: 配置文件不存在
            yaml.YAMLError: YAML格式错误
            ConfigValidationError: 配置验证失败
        """
        if not os.path.exists(self.config_path):
            raise FileNotFoundError(f"配置文件不存在: {self.config_path}")

        try:
            with open(self.config_path, 'r', encoding='utf-8') as f:
                self.config = yaml.safe_load(f)
        except yaml.YAMLError as e:
            raise yaml.YAMLError(f"YAML格式错误: {str(e)}")

        # 验证配置
        self.validate()

        return self.config

    def validate(self):
        """
        验证配置文件格式

        Raises:
            ConfigValidationError: 配置验证失败
        """
        if not isinstance(self.config, dict):
            raise ConfigValidationError("配置文件根节点必须是字典")

        # 检查providers字段
        if 'providers' not in self.config:
            raise ConfigValidationError("缺少必需字段: providers")

        if not isinstance(self.config['providers'], dict):
            raise ConfigValidationError("providers必须是字典类型")

        if not self.config['providers']:
            raise ConfigValidationError("providers不能为空")

        # 验证每个平台的配置
        for provider_name, provider_config in self.config['providers'].items():
            self._validate_provider_config(provider_name, provider_config)

        # 验证全局配置（如果存在）
        if 'global' in self.config:
            self._validate_global_config(self.config['global'])

    def _validate_provider_config(self, provider_name: str, config: Dict[str, Any]):
        """
        验证单个平台的配置

        Args:
            provider_name: 平台名称
            config: 平台配置

        Raises:
            ConfigValidationError: 配置验证失败
        """
        required_fields = ['model', 'api_keys', 'base_url', 'tier']
        valid_tiers = ['primary', 'daily', 'fallback']

        # 检查必需字段
        for field in required_fields:
            if field not in config:
                raise ConfigValidationError(f"平台 {provider_name} 缺少必需字段: {field}")

        # 验证tier
        if not isinstance(config['tier'], str) or not config['tier'].strip():
            raise ConfigValidationError(f"平台 {provider_name} 的 tier 必须是非空字符串")

        if config['tier'] not in valid_tiers:
            raise ConfigValidationError(
                f"平台 {provider_name} 的 tier 必须是以下值之一: {', '.join(valid_tiers)}"
            )

        # 验证model
        if not isinstance(config['model'], str) or not config['model'].strip():
            raise ConfigValidationError(f"平台 {provider_name} 的 model 必须是非空字符串")

        # 验证api_keys
        if not isinstance(config['api_keys'], list):
            raise ConfigValidationError(f"平台 {provider_name} 的 api_keys 必须是数组")

        if len(config['api_keys']) == 0:
            raise ConfigValidationError(f"平台 {provider_name} 的 api_keys 不能为空")

        for idx, key in enumerate(config['api_keys']):
            if not isinstance(key, str) or not key.strip():
                raise ConfigValidationError(
                    f"平台 {provider_name} 的 api_keys[{idx}] 必须是非空字符串"
                )

        # 验证base_url
        if not isinstance(config['base_url'], str) or not config['base_url'].strip():
            raise ConfigValidationError(f"平台 {provider_name} 的 base_url 必须是非空字符串")

        # 验证URL格式
        try:
            parsed = urlparse(config['base_url'])
            if not parsed.scheme or not parsed.netloc:
                raise ConfigValidationError(
                    f"平台 {provider_name} 的 base_url 不是有效的URL: {config['base_url']}"
                )
        except Exception as e:
            raise ConfigValidationError(
                f"平台 {provider_name} 的 base_url 解析失败: {str(e)}"
            )

    def _validate_global_config(self, global_config: Dict[str, Any]):
        """
        验证全局配置

        Args:
            global_config: 全局配置

        Raises:
            ConfigValidationError: 配置验证失败
        """
        # max_retries
        if 'max_retries' in global_config:
            if not isinstance(global_config['max_retries'], int) or global_config['max_retries'] < 0:
                raise ConfigValidationError("global.max_retries 必须是非负整数")

        # retry_delay
        if 'retry_delay' in global_config:
            if not isinstance(global_config['retry_delay'], int) or global_config['retry_delay'] < 0:
                raise ConfigValidationError("global.retry_delay 必须是非负整数")

        # error_threshold
        if 'error_threshold' in global_config:
            if not isinstance(global_config['error_threshold'], int) or global_config['error_threshold'] < 0:
                raise ConfigValidationError("global.error_threshold 必须是非负整数")

        # cooldown_seconds
        if 'cooldown_seconds' in global_config:
            if not isinstance(global_config['cooldown_seconds'], int) or global_config['cooldown_seconds'] < 0:
                raise ConfigValidationError("global.cooldown_seconds 必须是非负整数")

        # quota_check_enabled
        if 'quota_check_enabled' in global_config:
            if not isinstance(global_config['quota_check_enabled'], bool):
                raise ConfigValidationError("global.quota_check_enabled 必须是布尔值")

        # quota_min_remaining
        if 'quota_min_remaining' in global_config:
            if not isinstance(global_config['quota_min_remaining'], int) or global_config['quota_min_remaining'] < 0:
                raise ConfigValidationError("global.quota_min_remaining 必须是非负整数")

    def get_provider_config(self, provider_name: str) -> Dict[str, Any]:
        """
        获取指定平台的配置

        Args:
            provider_name: 平台名称

        Returns:
            平台配置

        Raises:
            KeyError: 平台不存在
        """
        if provider_name not in self.config['providers']:
            raise KeyError(f"平台不存在: {provider_name}")
        return self.config['providers'][provider_name]

    def get_global_config(self) -> Dict[str, Any]:
        """
        获取全局配置

        Returns:
            全局配置（包含默认值）
        """
        global_config = self.DEFAULT_GLOBAL_CONFIG.copy()

        if 'global' in self.config:
            global_config.update(self.config['global'])

        return global_config

    def get_provider_names(self) -> List[str]:
        """
        获取所有平台名称

        Returns:
            平台名称列表
        """
        return list(self.config['providers'].keys())


path: llm_key_pool/key_pool.py
content: """
分层API Key池管理器
实现分层轮询、跨层切换、故障转移和配额管理
"""

import threading
from typing import Dict, List, Optional, Tuple
from datetime import datetime, timedelta


class TieredKeyPool:
    """分层API Key池管理器"""

    # 层级定义
    TIER_ORDER = ['primary', 'daily', 'fallback']  # primary -> daily -> fallback
    TIER_NAMES = {
        'primary': '主力层（高额度）',
        'daily': '每日回血层（稳定）',
        'fallback': '兜底层（开源/聚合）'
    }

    def __init__(self, provider_name: str, tier: str, api_keys: List[str], model: str, config: Dict):
        """
        初始化Key池

        Args:
            provider_name: 平台名称
            tier: 层级（primary/daily/fallback）
            api_keys: API Key列表
            model: 模型名称
            config: 全局配置
        """
        self.provider_name = provider_name
        self.tier = tier
        self.model = model
        self.api_keys = api_keys
        self.config = config

        # Key状态
        self.key_states = [
            {
                'index': i,
                'key': key,
                'error_count': 0,
                'last_error_time': None,
                'last_success_time': None,
                'quota_remaining': None,
                'is_disabled': False,
                'is_cooling': False  # 冷却状态标记
            }
            for i, key in enumerate(api_keys)
        ]

        # 轮询索引
        self.current_index = 0

        # 线程锁
        self.lock = threading.Lock()

        # 配置参数
        self.error_threshold = config.get('error_threshold', 5)
        self.cooldown_seconds = config.get('cooldown_seconds', 300)
        self.quota_check_enabled = config.get('quota_check_enabled', True)
        self.quota_min_remaining = config.get('quota_min_remaining', 1000)  # 剩余少于这个值就自动切换

    def get_next_key(self) -> Tuple[str, int]:
        """
        获取下一个可用的API Key（轮询策略）

        Returns:
            (api_key, key_index)

        Raises:
            Exception: 所有Key都不可用
        """
        with self.lock:
            # 尝试找到可用的Key
            for _ in range(len(self.api_keys)):
                index = self.current_index
                key_state = self.key_states[index]

                # 检查Key是否可用
                if self._is_key_available(key_state):
                    self.current_index = (self.current_index + 1) % len(self.api_keys)
                    return key_state['key'], index

                # 移动到下一个Key
                self.current_index = (self.current_index + 1) % len(self.api_keys)

            # 所有Key都不可用
            tier_name = self.TIER_NAMES.get(self.tier, self.tier)
            raise Exception(f"平台 {self.provider_name} ({tier_name}) 的所有API Key都不可用")

    def _is_key_available(self, key_state: Dict) -> bool:
        """
        检查Key是否可用

        Args:
            key_state: Key状态

        Returns:
            是否可用
        """
        # 如果被永久禁用，直接返回False
        if key_state['is_disabled']:
            return False

        # 检查是否在冷却中
        if key_state['is_cooling']:
            if key_state['last_error_time']:
                cooldown_end = key_state['last_error_time'] + timedelta(seconds=self.cooldown_seconds)
                if datetime.now() < cooldown_end:
                    return False
                else:
                    # 冷却结束，恢复可用
                    key_state['is_cooling'] = False
                    key_state['error_count'] = 0

        # 检查配额是否耗尽（剩余少于阈值也标记为不可用）
        if self.quota_check_enabled and key_state['quota_remaining'] is not None:
            if key_state['quota_remaining'] <= self.quota_min_remaining:
                print(f"[QUOTA] 平台 {self.provider_name} Key[{key_state['index']}] 剩余Token不足 ({key_state['quota_remaining']} < {self.quota_min_remaining})，自动跳过")
                return False

        # 检查错误次数是否超过阈值
        if key_state['error_count'] >= self.error_threshold:
            return False

        return True

    def mark_key_success(self, key_index: int, headers: Optional[Dict] = None):
        """
        标记Key使用成功

        Args:
            key_index: Key索引
            headers: HTTP响应头（用于提取配额信息）
        """
        with self.lock:
            if 0 <= key_index < len(self.key_states):
                key_state = self.key_states[key_index]
                key_state['error_count'] = max(0, key_state['error_count'] - 1)
                key_state['last_success_time'] = datetime.now()
                key_state['is_cooling'] = False

                # 更新配额信息
                if headers:
                    self._update_quota_info(key_state, headers)

    def mark_key_error(self, key_index: int, error_type: str, error_msg: str = ""):
        """
        标记Key使用失败

        Args:
            key_index: Key索引
            error_type: 错误类型（401, 429, 500等）
            error_msg: 错误消息
        """
        with self.lock:
            if 0 <= key_index < len(self.key_states):
                key_state = self.key_states[key_index]

                # 429错误：立即进入冷却状态
                if error_type == '429':
                    key_state['is_cooling'] = True
                    key_state['last_error_time'] = datetime.now()
                    tier_name = self.TIER_NAMES.get(self.tier, self.tier)
                    print(f"[COOLING] 平台 {self.provider_name} ({tier_name}) 的Key[{key_index}] 因429错误进入冷却 ({self.cooldown_seconds}秒)")
                    return

                # 401错误：永久禁用该Key
                if error_type == '401':
                    key_state['is_disabled'] = True
                    tier_name = self.TIER_NAMES.get(self.tier, self.tier)
                    print(f"[DISABLED] 平台 {self.provider_name} ({tier_name}) 的Key[{key_index}] 已被永久禁用（401 Unauthorized）")
                    return

                # 其他错误：增加错误计数
                key_state['error_count'] += 1
                key_state['last_error_time'] = datetime.now()

                # 如果错误次数超过阈值，进入冷却期
                if key_state['error_count'] >= self.error_threshold:
                    key_state['is_cooling'] = True
                    tier_name = self.TIER_NAMES.get(self.tier, self.tier)
                    print(
                        f"[COOLING] 平台 {self.provider_name} ({tier_name}) 的Key[{key_index}] 错误次数达到阈值，"
                        f"进入{self.cooldown_seconds}秒冷却期"
                    )

    def _update_quota_info(self, key_state: Dict, headers: Dict):
        """
        更新配额信息

        Args:
            key_state: Key状态
            headers: HTTP响应头
        """
        # OpenAI兼容接口的配额信息
        if 'x-ratelimit-remaining-tokens' in headers:
            key_state['quota_remaining'] = int(headers['x-ratelimit-remaining-tokens'])
            print(f"[INFO] Key[{key_state['index']}] 剩余[REDACTED_SECRET]'quota_remaining']}")
            # 如果剩余不足，提示
            if key_state['quota_remaining'] <= self.quota_min_remaining:
                print(f"[WARNING] Key[{key_state['index']}] 剩余Token不足 {self.quota_min_remaining}，将自动切换")

        # 阿里云百炼可能使用的配额头
        if 'X-RateLimit-Remaining-Tokens' in headers:  # 首字母大写变体
            key_state['quota_remaining'] = int(headers['X-RateLimit-Remaining-Tokens'])
            print(f"[INFO] Key[{key_state['index']}] 剩余[REDACTED_SECRET]'quota_remaining']}")
            if key_state['quota_remaining'] <= self.quota_min_remaining:
                print(f"[WARNING] Key[{key_state['index']}] 剩余Token不足 {self.quota_min_remaining}，将自动切换")

        if 'x-ratelimit-remaining' in headers:
            key_state['quota_remaining'] = int(headers['x-ratelimit-remaining'])
            print(f"[INFO] Key[{key_state['index']}] 剩余[REDACTED_SECRET]'quota_remaining']}")
            if key_state['quota_remaining'] <= self.quota_min_remaining:
                print(f"[WARNING] Key[{key_state['index']}] 剩余Token不足 {self.quota_min_remaining}，将自动切换")

        # Anthropic配额信息
        if 'anthropic-ratelimit-tokens-remaining' in headers:
            key_state['quota_remaining'] = int(headers['anthropic-ratelimit-tokens-remaining'])
            print(f"[INFO] Key[{key_state['index']}] 剩余[REDACTED_SECRET]'quota_remaining']}")
            if key_state['quota_remaining'] <= self.quota_min_remaining:
                print(f"[WARNING] Key[{key_state['index']}] 剩余Token不足 {self.quota_min_remaining}，将自动切换")

    def get_status(self) -> Dict:
        """
        获取Key池状态

        Returns:
            状态信息字典
        """
        with self.lock:
            tier_name = self.TIER_NAMES.get(self.tier, self.tier)
            status = {
                'provider': self.provider_name,
                'tier': self.tier,
                'tier_name': tier_name,
                'model': self.model,
                'total_keys': len(self.api_keys),
                'available_keys': sum(1 for ks in self.key_states if self._is_key_available(ks)),
                'keys': []
            }

            for ks in self.key_states:
                key_info = {
                    'index': ks['index'],
                    'key': ks['key'][:10] + '...' if len(ks['key']) > 10 else ks['key'],
                    'error_count': ks['error_count'],
                    'is_disabled': ks['is_disabled'],
                    'is_cooling': ks['is_cooling'],
                    'quota_remaining': ks['quota_remaining']
                }

                if ks['last_error_time']:
                    key_info['last_error_time'] = ks['last_error_time'].isoformat()

                if ks['last_success_time']:
                    key_info['last_success_time'] = ks['last_success_time'].isoformat()

                status['keys'].append(key_info)

            return status

    def has_available_key(self) -> bool:
        """
        检查是否有可用的Key

        Returns:
            是否有可用的Key
        """
        with self.lock:
            return any(self._is_key_available(ks) for ks in self.key_states)

    def reset_key_status(self, key_index: int):
        """
        重置指定Key的状态

        Args:
            key_index: Key索引
        """
        with self.lock:
            if 0 <= key_index < len(self.key_states):
                key_state = self.key_states[key_index]
                key_state['error_count'] = 0
                key_state['last_error_time'] = None
                key_state['is_disabled'] = False
                key_state['is_cooling'] = False
                print(f"[INFO] 已重置Key[{key_index}]的状态")


path: llm_key_pool/llm_client.py
content: """
统一LLM客户端 - 分层轮询版本
支持多平台OpenAI兼容接口，实现分层轮询、跨层切换和自动故障转移
"""

import argparse
import json
import os
import sys
import time
from typing import Dict, List, Optional, Tuple

import requests

# 修复Windows控制台中文乱码问题
if sys.platform.startswith('win'):
    import locale
    sys.stdout.reconfigure(encoding='utf-8')
    sys.stderr.reconfigure(encoding='utf-8')

# 支持直接 python 运行（开发模式）
_project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
if _project_root not in sys.path:
    sys.path.insert(0, _project_root)

from llm_key_pool.config_loader import ConfigLoader, ConfigValidationError
from llm_key_pool.key_pool import TieredKeyPool


class TieredLLMClient:
    """分层LLM客户端"""

    def __init__(self, config_path: str):
        """
        初始化LLM客户端

        Args:
            config_path: 配置文件路径
        """
        # 加载配置
        self.config_loader = ConfigLoader(config_path)
        self.config = self.config_loader.load()
        self.global_config = self.config_loader.get_global_config()

        # 按层级组织Key池
        self.tier_pools = {
            'primary': [],
            'daily': [],
            'fallback': []
        }

        # 初始化Key池
        for provider_name in self.config_loader.get_provider_names():
            provider_config = self.config_loader.get_provider_config(provider_name)
            tier = provider_config['tier']

            key_pool = TieredKeyPool(
                provider_name=provider_name,
                tier=tier,
                api_keys=provider_config['api_keys'],
                model=provider_config['model'],
                config=self.global_config
            )

            self.tier_pools[tier].append(key_pool)

        # 当前层级索引
        self.current_tier_index = 0
        self.tier_order = ['primary', 'daily', 'fallback']

    def call_llm(
        self,
        prompt: str,
        system_prompt: Optional[str] = None,
        temperature: float = 0.7,
        max_tokens: int = 2000
    ) -> Tuple[str, Dict]:
        """
        调用LLM服务（分层轮询策略）

        Args:
            prompt: 用户提示词
            system_prompt: 系统提示词
            temperature: 温度参数
            max_tokens: 最大Token数

        Returns:
            (响应文本, 使用信息字典)

        Raises:
            Exception: 所有平台的Key都不可用
        """
        # 构建消息
        messages = []
        if system_prompt:
            messages.append({"role": "system", "content": system_prompt})
        messages.append({"role": "user", "content": prompt})

        # 重试逻辑
        max_retries = self.global_config.get('max_retries', 3)
        retry_delay = self.global_config.get('retry_delay', 1)

        # 按层级轮询
        for attempt in range(max_retries):
            # 尝试从当前层级获取可用的Key
            result = self._try_call_from_tiers(messages, temperature, max_tokens)
            if result:
                return result

            # 如果当前层级都不可用，切换到下一层
            if self.current_tier_index < len(self.tier_order) - 1:
                old_tier = self.tier_order[self.current_tier_index]
                self.current_tier_index += 1
                new_tier = self.tier_order[self.current_tier_index]
                print(f"[SWITCH] 从 {old_tier} 切换到 {new_tier}")
                continue
            else:
                # 所有层级都不可用，重置层级索引
                print("[INFO] 所有层级Key池都不可用，等待冷却后重试...")
                if attempt < max_retries - 1:
                    time.sleep(retry_delay)
                    self.current_tier_index = 0
                    continue
                else:
                    raise Exception("调用失败，所有平台的API Key都不可用，请检查配置或等待冷却")

    def _try_call_from_tiers(
        self,
        messages: List[Dict],
        temperature: float,
        max_tokens: int
    ) -> Optional[Tuple[str, Dict]]:
        """
        尝试从当前层级及其后续层级调用

        Args:
            messages: 消息列表
            temperature: 温度参数
            max_tokens: 最大Token数

        Returns:
            (响应文本, 使用信息) 或 None
        """
        # 从当前层级开始，依次尝试每个层级
        for tier_idx in range(self.current_tier_index, len(self.tier_order)):
            tier_name = self.tier_order[tier_idx]
            key_pools = self.tier_pools[tier_name]

            # 尝试当前层级的每个Key池
            for key_pool in key_pools:
                if key_pool.has_available_key():
                    try:
                        result = self._call_with_pool(
                            key_pool,
                            messages,
                            temperature,
                            max_tokens
                        )
                        if result:
                            return result
                    except Exception as e:
                        print(f"[WARN] 平台 {key_pool.provider_name} 调用失败: {e}")
                        continue

        return None

    def _call_with_pool(
        self,
        key_pool: TieredKeyPool,
        messages: List[Dict],
        temperature: float,
        max_tokens: int
    ) -> Tuple[str, Dict]:
        """
        使用指定Key池调用API

        Args:
            key_pool: Key池
            messages: 消息列表
            temperature: 温度参数
            max_tokens: 最大Token数

        Returns:
            (响应文本, 使用信息)

        Raises:
            Exception: 调用失败
        """
        try:
            # 获取API Key
            api_key, key_index = key_pool.get_next_key()
            api_key_preview = api_key[:8] + '...' if len(api_key) > 8 else api_key

            # 显示当前使用的Key信息
            tier_name = key_pool.tier
            print(f"[INFO] 使用平台: {key_pool.provider_name}, 层级: {tier_name}, 模型: {key_pool.model}, Key: {api_key_preview} (索引: {key_index})")

            # 获取平台配置
            provider_config = self.config_loader.get_provider_config(key_pool.provider_name)
            base_url = provider_config['base_url']

            # 统一使用OpenAI兼容接口格式
            # 特殊处理ClaudeCode的API格式
            if key_pool.provider_name == 'claudecode':
                response = self._call_anthropic_format(
                    base_url=base_url,
                    [REDACTED_SECRET],
                    model=key_pool.model,
                    messages=messages,
                    temperature=temperature,
                    max_tokens=max_tokens
                )
            else:
                response = self._call_openai_compatible(
                    base_url=base_url,
                    [REDACTED_SECRET],
                    model=key_pool.model,
                    messages=messages,
                    temperature=temperature,
                    max_tokens=max_tokens
                )

            # 提取响应头
            headers = response.headers if hasattr(response, 'headers') else {}

            # 标记Key使用成功
            key_pool.mark_key_success(key_index, dict(headers))

            # 提取响应内容
            content = self._extract_response_content(response)

            # 返回结果和使用信息
            usage_info = {
                'provider': key_pool.provider_name,
                'tier': tier_name,
                'model': key_pool.model,
                'key_index': key_index,
                'temperature': temperature,
                'max_tokens': max_tokens
            }

            return content, usage_info

        except Exception as e:
            error_msg = str(e)
            error_type = self._extract_error_type(error_msg)

            print(f"[ERROR] 调用失败: {error_msg}")

            # 标记Key使用失败
            if 'key_index' in locals():
                key_pool.mark_key_error(key_index, error_type, error_msg)

            raise

    def _call_openai_compatible(
        self,
        base_url: str,
        api_key: str,
        model: str,
        messages: List[Dict],
        temperature: float,
        max_tokens: int
    ):
        """
        调用OpenAI兼容接口

        Args:
            base_url: API基础URL
            api_key: API Key
            model: 模型名称
            messages: 消息列表
            temperature: 温度参数
            max_tokens: 最大Token数

        Returns:
            响应对象
        """
        url = f"{base_url}/chat/completions"
        headers = {
            "Content-Type": "application/json",
            "Authorization": f"Bearer {api_key}"
        }
        data = {
            "model": model,
            "messages": messages,
            "temperature": temperature,
            "max_tokens": max_tokens
        }

        response = requests.post(url, headers=headers, json=data, timeout=60)
        response.raise_for_status()
        return response

    def _call_anthropic_format(
        self,
        base_url: str,
        api_key: str,
        model: str,
        messages: List[Dict],
        temperature: float,
        max_tokens: int
    ):
        """
        调用Anthropic格式接口（ClaudeCode专用）

        Args:
            base_url: API基础URL
            api_key: API Key
            model: 模型名称
            messages: 消息列表
            temperature: 温度参数
            max_tokens: 最大Token数

        Returns:
            响应对象
        """
        url = f"{base_url}/messages"
        headers = {
            "Content-Type": "application/json",
            "x-api-key": api_key,
            "anthropic-version": "2023-06-01"
        }

        # Anthropic需要将system prompt单独提取
        system_prompt = None
        filtered_messages = []
        for msg in messages:
            if msg['role'] == 'system':
                system_prompt = msg['content']
            else:
                # 将role转换为Anthropic格式
                role = 'user' if msg['role'] == 'user' else 'assistant'
                filtered_messages.append({"role": role, "content": msg['content']})

        data = {
            "model": model,
            "messages": filtered_messages,
            "max_tokens": max_tokens,
            "temperature": temperature
        }

        if system_prompt:
            data["system"] = system_prompt

        response = requests.post(url, headers=headers, json=data, timeout=60)
        response.raise_for_status()
        return response

    def _extract_response_content(self, response) -> str:
        """
        从响应中提取内容

        Args:
            response: 响应对象

        Returns:
            响应文本
        """
        try:
            data = response.json()

            # OpenAI兼容格式
            if 'choices' in data:
                return data['choices'][0]['message']['content']
            # Anthropic格式
            elif 'content' in data:
                return data['content'][0]['text']
            else:
                raise Exception(f"未知的响应格式: {data}")
        except (KeyError, IndexError) as e:
            raise Exception(f"解析响应失败: {str(e)}, 响应内容: {response.text}")

    def _extract_error_type(self, error_msg: str) -> str:
        """
        从错误消息中提取错误类型

        Args:
            error_msg: 错误消息

        Returns:
            错误类型（401, 429, 500等）
        """
        if '401' in error_msg or 'Unauthorized' in error_msg:
            return '401'
        elif '429' in error_msg or 'Too Many Requests' in error_msg:
            return '429'
        elif '500' in error_msg or 'Internal Server Error' in error_msg:
            return '500'
        elif '503' in error_msg or 'Service Unavailable' in error_msg:
            return '503'
        else:
            return 'unknown'

    def get_status(self) -> Dict:
        """
        获取所有平台的Key池状态

        Returns:
            状态信息字典
        """
        status = {
            'tiers': {}
        }

        for tier_name, key_pools in self.tier_pools.items():
            status['tiers'][tier_name] = [pool.get_status() for pool in key_pools]

        return status

    def test_config(self) -> bool:
        """
        测试配置是否正确

        Returns:
            是否测试通过
        """
        print("[INFO] 开始测试配置...")

        all_passed = True
        for tier_name, key_pools in self.tier_pools.items():
            print(f"[INFO] 测试层级: {tier_name}")

            for key_pool in key_pools:
                try:
                    result, usage_info = self.call_llm(
                        prompt="Hello",
                        system_prompt=None,
                        temperature=0.5,
                        max_tokens=10
                    )

                    print(f"[INFO] 平台 {key_pool.provider_name} 测试成功")
                    print(f"[INFO] 响应: {result[:50]}...")

                except Exception as e:
                    print(f"[ERROR] 平台 {key_pool.provider_name} 测试失败: {str(e)}")
                    all_passed = False

        if all_passed:
            print("[INFO] 所有平台测试通过")
        else:
            print("[WARNING] 部分平台测试失败")

        return all_passed


def main():
    """命令行入口"""
    # 默认配置路径：优先Skill目录，然后当前目录
    default_config = './llm_config.yaml'
    skill_config_path = os.path.expanduser('~/.claude/skills/llm-key-pool/llm_config.yaml')
    if os.path.exists(skill_config_path):
        default_config = skill_config_path

    parser = argparse.ArgumentParser(description='LLM Key Pool - 分层轮询多平台API Key管理')
    parser.add_argument('--config', type=str, default=default_config, help='配置文件路径 (默认: ~/.claude/skills/llm-key-pool/llm_config.yaml)')
    parser.add_argument('--prompt', type=str, help='用户提示词')
    parser.add_argument('--system-prompt', type=str, help='系统提示词')
    parser.add_argument('--temperature', type=float, default=0.7, help='温度参数')
    parser.add_argument('--max-tokens', type=int, default=2000, help='最大Token数')
    parser.add_argument('--test', action='store_true', help='测试配置')
    parser.add_argument('--status', action='store_true', help='查看Key池状态')

    args = parser.parse_args()

    try:
        # 初始化客户端
        client = TieredLLMClient(args.config)

        # 查看状态
        if args.status:
            status = client.get_status()
            print(json.dumps(status, indent=2, ensure_ascii=False))
            return 0

        # 测试配置
        if args.test:
            if client.test_config():
                print("[SUCCESS] 配置测试通过")
                return 0
            else:
                print("[FAILED] 配置测试失败")
                return 1

        # 调用LLM
        if not args.prompt:
            parser.print_help()
            print("\n[ERROR] 缺少必需参数: --prompt")
            return 1

        result, usage_info = client.call_llm(
            prompt=args.prompt,
            system_prompt=args.system_prompt,
            temperature=args.temperature,
            max_tokens=args.max_tokens
        )

        print("\n=== 响应内容 ===")
        print(f"平台: {usage_info['provider']}")
        print(f"层级: {usage_info['tier']}")
        print(f"模型: {usage_info['model']}")
        print(f"温度: {usage_info['temperature']}")
        print(f"最大Token数: {usage_info['max_tokens']}")
        print("-" * 50)
        print(result)
        print("\n=== 调用完成 ===")

        return 0

    except FileNotFoundError as e:
        print(f"[ERROR] {str(e)}")
        return 1
    except ConfigValidationError as e:
        print(f"[ERROR] 配置验证失败: {str(e)}")
        return 1
    except Exception as e:
        print(f"[ERROR] {str(e)}")
        return 1


if __name__ == '__main__':
    sys.exit(main())


path: llm_key_pool/__init__.py
content: """llm-key-pool: 分层 LLM API Key 池，支持轮询与故障转移。"""

from llm_key_pool.config_loader import ConfigLoader, ConfigValidationError
from llm_key_pool.key_pool import TieredKeyPool
from llm_key_pool.llm_client import TieredLLMClient

__version__ = "0.1.0"

__all__ = [
    "ConfigLoader",
    "ConfigValidationError",
    "TieredKeyPool",
    "TieredLLMClient",
]


path: examples/binary_tree.py
content: """
二叉搜索树（Binary Search Tree）实现

支持操作：
- 插入节点
- 查找节点
- 删除节点
- 前序/中序/后序/层序遍历
- 获取最小/最大值
- 获取树的高度
- 检查是否为二叉搜索树
"""


class TreeNode:
    """二叉树节点"""
    def __init__(self, key, value):
        self.key = key
        self.value = value
        self.left = None
        self.right = None
        self.size = 1  # 以该节点为根的子树节点总数

    def update_size(self):
        """更新以该节点为根的子树大小"""
        self.size = 1
        if self.left:
            self.size += self.left.size
        if self.right:
            self.size += self.right.size


class BinarySearchTree:
    """二叉搜索树实现"""

    def __init__(self):
        """初始化空二叉搜索树"""
        self.root = None

    def size(self) -> int:
        """返回树中节点总数"""
        if self.root is None:
            return 0
        return self.root.size

    def is_empty(self) -> bool:
        """检查树是否为空"""
        return self.root is None

    def put(self, key, value) -> None:
        """插入或更新键值对"""
        self.root = self._put(self.root, key, value)

    def _put(self, node: TreeNode, key, value) -> TreeNode:
        """递归插入辅助函数"""
        if node is None:
            return TreeNode(key, value)

        if key < node.key:
            node.left = self._put(node.left, key, value)
        elif key > node.key:
            node.right = self._put(node.right, key, value)
        else:
            # 键已存在，更新值
            node.value = value

        node.update_size()
        return node

    def get(self, key):
        """根据键查找值，键不存在返回None"""
        return self._get(self.root, key)

    def _get(self, node: TreeNode, key):
        """递归查找辅助函数"""
        if node is None:
            return None

        if key < node.key:
            return self._get(node.left, key)
        elif key > node.key:
            return self._get(node.right, key)
        else:
            return node.value

    def contains(self, key) -> bool:
        """检查键是否存在"""
        return self.get(key) is not None

    def delete(self, key) -> bool:
        """删除键对应的节点，返回是否删除成功"""
        if not self.contains(key):
            return False
        self.root = self._delete(self.root, key)
        return True

    def _delete(self, node: TreeNode, key) -> TreeNode:
        """递归删除辅助函数"""
        if node is None:
            return None

        if key < node.key:
            node.left = self._delete(node.left, key)
        elif key > node.key:
            node.right = self._delete(node.right, key)
        else:
            # 找到要删除的节点
            # 情况1：没有子节点或只有一个子节点
            if node.left is None:
                return node.right
            if node.right is None:
                return node.left

            # 情况2：有两个子节点
            # 找到右子树中的最小节点替代当前节点
            successor = self._find_min(node.right)
            node.key = successor.key
            node.value = successor.value
            node.right = self._delete(node.right, successor.key)

        node.update_size()
        return node

    def _find_min(self, node: TreeNode) -> TreeNode:
        """找到以node为根的子树中的最小节点"""
        while node.left is not None:
            node = node.left
        return node

    def _find_max(self, node: TreeNode) -> TreeNode:
        """找到以node为根的子树中的最大节点"""
        while node.right is not None:
            node = node.right
        return node

    def min_key(self):
        """返回树中的最小键，树为空返回None"""
        if self.root is None:
            return None
        return self._find_min(self.root).key

    def max_key(self):
        """返回树中的最大键，树为空返回None"""
        if self.root is None:
            return None
        return self._find_max(self.root).key

    def height(self) -> int:
        """返回树的高度（空树返回0，只有根节点返回1）"""
        return self._height(self.root)

    def _height(self, node: TreeNode) -> int:
        """递归计算高度"""
        if node is None:
            return 0
        left_height = self._height(node.left)
        right_height = self._height(node.right)
        return 1 + max(left_height, right_height)

    # ========== 遍历方法 ==========

    def pre_order(self) -> list:
        """前序遍历（根-左-右）"""
        result = []
        self._pre_order(self.root, result)
        return result

    def _pre_order(self, node: TreeNode, result: list):
        if node is not None:
            result.append((node.key, node.value))
            self._pre_order(node.left, result)
            self._pre_order(node.right, result)
        return result

    def in_order(self) -> list:
        """中序遍历（左-根-右），二叉搜索树的中序遍历是有序的"""
        result = []
        self._in_order(self.root, result)
        return result

    def _in_order(self, node: TreeNode, result: list):
        if node is not None:
            self._in_order(node.left, result)
            result.append((node.key, node.value))
            self._in_order(node.right, result)
        return result

    def post_order(self) -> list:
        """后序遍历（左-右-根）"""
        result = []
        self._post_order(self.root, result)
        return result

    def _post_order(self, node: TreeNode, result: list):
        if node is not None:
            self._post_order(node.left, result)
            self._post_order(node.right, result)
            result.append((node.key, node.value))
        return result

    def level_order(self) -> list:
        """层序遍历（广度优先）"""
        if self.root is None:
            return []

        result = []
        from collections import deque
        queue = deque()
        queue.append(self.root)

        while queue:
            node = queue.popleft()
            result.append((node.key, node.value))
            if node.left:
                queue.append(node.left)
            if node.right:
                queue.append(node.right)

        return result

    def is_valid_bst(self) -> bool:
        """验证是否为合法的二叉搜索树"""
        return self._is_valid_bst(self.root, None, None)

    def _is_valid_bst(self, node: TreeNode, min_key, max_key) -> bool:
        if node is None:
            return True

        # 当前节点必须在(min_key, max_key)范围内
        if min_key is not None and node.key <= min_key:
            return False
        if max_key is not None and node.key >= max_key:
            return False

        # 递归验证左右子树
        return (self._is_valid_bst(node.left, min_key, node.key) and
                self._is_valid_bst(node.right, node.key, max_key))

    def __contains__(self, key):
        """支持 'in' 运算符"""
        return self.contains(key)

    def __len__(self):
        """支持 len() 函数"""
        return self.size()

    def print_structure(self) -> None:
        """以树形结构打印（用于调试）"""
        if self.root is None:
            print("Empty tree")
            return

        lines = self._get_tree_lines(self.root)
        for line in lines:
            print(line)

    def _get_tree_lines(self, node: TreeNode) -> list:
        """生成树形结构字符串（用于打印）"""
        if node.right is None and node.left is None:
            return [f"{node.key}"]

        if node.right is None:
            left_lines = self._get_tree_lines(node.left)
            result = [f"{node.key}"]
            result.append("└── " + left_lines[0])
            prefix = "    "
            for line in left_lines[1:]:
                result.append(prefix + line)
            return result

        if node.left is None:
            right_lines = self._get_tree_lines(node.right)
            result = [f"{node.key}"]
            result.append("├── " + right_lines[0])
            prefix = "    "
            for line in right_lines[1:]:
                result.append(prefix + line)
            return result

        left_lines = self._get_tree_lines(node.left)
        right_lines = self._get_tree_lines(node.right)
        result = [f"{node.key}"]
        result.append("├── " + right_lines[0])
        prefix_right = "│   "
        for line in right_lines[1:]:
            result.append(prefix_right + line)
        result.append("└── " + left_lines[0])
        prefix_left = "    "
        for line in left_lines[1:]:
            result.append(prefix_left + line)
        return result


if __name__ == "__main__":
    # 使用示例
    bst = BinarySearchTree()

    # 插入测试数据
    print("=== 插入测试数据 ===")
    data = [(50, "fifty"), (30, "thirty"), (70, "seventy"),
            (20, "twenty"), (40, "forty"), (60, "sixty"), (80, "eighty")]

    for key, value in data:
        bst.put(key, value)
        print(f"Inserted: {key} => {value}")

    print(f"\n树大小: {len(bst)}")
    print(f"树高度: {bst.height()}")
    print(f"Is valid BST: {bst.is_valid_bst()}")

    # 打印树形结构
    print("\n=== 树形结构 ===")
    bst.print_structure()

    # 查找测试
    print("\n=== 查找测试 ===")
    for key in [30, 50, 90]:
        value = bst.get(key)
        print(f"Get {key}: {value}")

    # 最小最大键
    print("\n=== 最小/最大键 ===")
    print(f"Min key: {bst.min_key()}")
    print(f"Max key: {bst.max_key()}")

    # 遍历测试
    print("\n=== 遍历测试 ===")
    print(f"Pre-order:  {bst.pre_order()}")
    print(f"In-order:   {bst.in_order()}  (should be sorted)")
    print(f"Post-order: {bst.post_order()}")
    print(f"Level-order:{bst.level_order()}")

    # 删除测试 - 删除叶子节点
    print("\n=== 删除叶子节点 (20) ===")
    print(f"Delete 20: {bst.delete(20)}")
    print(f"Tree size: {len(bst)}")
    print("In-order:", bst.in_order())
    bst.print_structure()

    # 删除测试 - 删除有一个子节点的节点
    print("\n=== 删除有一个子节点的节点 (30) ===")
    print(f"Delete 30: {bst.delete(30)}")
    print(f"Tree size: {len(bst)}")
    print("In-order:", bst.in_order())
    bst.print_structure()

    # 删除测试 - 删除有两个子节点的节点（根节点）
    print("\n=== 删除根节点 (50) ===")
    print(f"Delete 50: {bst.delete(50)}")
    print(f"Tree size: {len(bst)}")
    print(f"Is valid BST: {bst.is_valid_bst()}")
    print("In-order:", bst.in_order())
    bst.print_structure()

    # 包含测试
    print("\n=== 包含测试 ===")
    print(f"50 in bst: {50 in bst}")
    print(f"60 in bst: {60 in bst}")


path: examples/demo.py
content: """
哈希表和二叉树使用示例演示

这个文件展示了如何使用 hash_table.py 和 binary_tree.py 中的数据结构
"""

from hash_table import HashTable
from binary_tree import BinarySearchTree


def demo_hash_table():
    print("=" * 50)
    print("哈希表 (Hash Table) 演示")
    print("=" * 50)

    # 创建哈希表
    ht = HashTable(capacity=8)

    # 添加学生成绩
    students = {
        "Alice": 95,
        "Bob": 82,
        "Charlie": 78,
        "David": 90,
        "Eve": 88
    }

    for name, score in students.items():
        ht.put(name, score)

    print(f"1. 添加了 {len(ht)} 个学生成绩")
    print(f"   所有学生: {ht.keys()}")

    # 查询
    print(f"\n2. 查询成绩:")
    for name in ["Alice", "Bob", "Frank"]:
        score = ht.get(name, "未找到")
        print(f"   {name}: {score}")

    # 更新
    print(f"\n3. 更新 Bob 的成绩: {ht.get('Bob')} -> 85")
    ht.put("Bob", 85)
    print(f"   Bob 的新成绩: {ht.get('Bob')}")

    # 删除
    print(f"\n4. 删除 Charlie")
    ht.remove("Charlie")
    print(f"   Charlie 在哈希表中吗? {'Charlie' in ht}")
    print(f"   当前大小: {len(ht)}")

    # 遍历所有键值对
    print(f"\n5. 遍历所有成绩:")
    for name, score in ht.items():
        print(f"   {name}: {score}")

    # 统计信息
    print(f"\n6. 哈希表统计:")
    ht.print_stats()

    print()


def demo_binary_search_tree():
    print("=" * 50)
    print("二叉搜索树 (Binary Search Tree) 演示")
    print("=" * 50)

    # 创建二叉搜索树
    bst = BinarySearchTree()

    # 插入商品价格
    products = {
        150: "Apple",
        80: "Banana",
        200: "Cherry",
        50: "Date",
        100: "Elderberry",
        180: "Fig",
        250: "Grape"
    }

    for price, name in products.items():
        bst.put(price, name)

    print(f"1. 插入了 {len(bst)} 个商品")
    print(f"   树高度: {bst.height()}")
    print(f"   是否为合法BST: {bst.is_valid_bst()}")

    # 树形结构
    print("\n2. 树形结构:")
    bst.print_structure()

    # 查找
    print("\n3. 查找商品:")
    for price in [100, 200, 300]:
        name = bst.get(price)
        if name:
            print(f"   价格 {price}: {name}")
        else:
            print(f"   价格 {price}: 未找到")

    # 最值
    print(f"\n4. 价格范围:")
    print(f"   最低价格: {bst.min_key()}")
    print(f"   最高价格: {bst.max_key()}")

    # 不同遍历方式
    print(f"\n5. 各种遍历结果 (价格, 商品名):")
    print(f"   前序遍历: {bst.pre_order()}")
    print(f"   中序遍历: {bst.in_order()}  (价格递增)")
    print(f"   后序遍历: {bst.post_order()}")
    print(f"   层序遍历: {bst.level_order()}")

    # 删除操作
    print("\n6. 删除价格 150 (根节点):")
    bst.delete(150)
    print(f"   当前节点数: {len(bst)}")
    print(f"   是否仍然是合法BST: {bst.is_valid_bst()}")
    print("\n   删除后的树结构:")
    bst.print_structure()

    print()


if __name__ == "__main__":
    demo_hash_table()
    demo_binary_search_tree()

    print("演示完成！")
    print("\n文件位置:")
    print("- 哈希表实现: examples/hash_table.py")
    print("- 二叉搜索树实现: examples/binary_tree.py")
    print("- 演示代码: examples/demo.py")


path: examples/hash_table.py
content: """
哈希表（Hash Table）实现
使用链地址法（Separate Chaining）解决哈希冲突

支持操作：
- 插入键值对
- 根据键获取值
- 删除键值对
- 检查键是否存在
- 获取哈希表大小
"""


class HashNode:
    """哈希表节点"""
    def __init__(self, key, value):
        self.key = key
        self.value = value
        self.next = None


class HashTable:
    """哈希表实现"""

    def __init__(self, capacity: int = 16):
        """
        初始化哈希表
        :param capacity: 初始容量，默认为16
        """
        self.capacity = capacity
        self.size = 0
        self.buckets = [None] * self.capacity

    def _hash(self, key) -> int:
        """
        哈希函数，将键映射到桶索引
        使用多项式滚动哈希方法
        """
        if isinstance(key, int):
            # 整数直接取模
            return key % self.capacity
        elif isinstance(key, str):
            # 字符串使用多项式哈希
            hash_val = 0
            p = 31  # 基数
            m = self.capacity  # 模
            for i, char in enumerate(key):
                hash_val = (hash_val * p + ord(char)) % m
            return hash_val
        else:
            # 其他类型使用内置hash函数
            return hash(key) % self.capacity

    def put(self, key, value) -> None:
        """插入或更新键值对"""
        index = self._hash(key)
        current = self.buckets[index]

        # 检查键是否已存在，如果存在则更新
        while current is not None:
            if current.key == key:
                current.value = value
                return
            current = current.next

        # 不存在则插入新节点（头插法）
        new_node = HashNode(key, value)
        new_node.next = self.buckets[index]
        self.buckets[index] = new_node
        self.size += 1

        # 当负载因子超过0.7时扩容
        if self.size / self.capacity > 0.7:
            self._resize()

    def get(self, key, default=None):
        """根据键获取值，如果键不存在返回default"""
        index = self._hash(key)
        current = self.buckets[index]

        while current is not None:
            if current.key == key:
                return current.value
            current = current.next

        return default

    def remove(self, key) -> bool:
        """删除键值对，返回是否删除成功"""
        index = self._hash(key)
        current = self.buckets[index]
        prev = None

        while current is not None:
            if current.key == key:
                if prev is None:
                    # 删除头节点
                    self.buckets[index] = current.next
                else:
                    # 删除中间或尾节点
                    prev.next = current.next
                self.size -= 1
                return True
            prev = current
            current = current.next

        return False

    def contains(self, key) -> bool:
        """检查键是否存在"""
        return self.get(key) is not None

    def __len__(self) -> int:
        """返回哈希表大小"""
        return self.size

    def __contains__(self, key):
        """支持 'in' 运算符"""
        return self.contains(key)

    def _resize(self) -> None:
        """扩容为原来的2倍"""
        new_capacity = self.capacity * 2
        new_buckets = [None] * new_capacity

        # 重新哈希所有元素
        old_buckets = self.buckets
        self.capacity = new_capacity
        self.buckets = new_buckets
        self.size = 0

        for bucket in old_buckets:
            current = bucket
            while current is not None:
                self.put(current.key, current.value)
                current = current.next

    def keys(self):
        """返回所有键"""
        all_keys = []
        for bucket in self.buckets:
            current = bucket
            while current is not None:
                all_keys.append(current.key)
                current = current.next
        return all_keys

    def values(self):
        """返回所有值"""
        all_values = []
        for bucket in self.buckets:
            current = bucket
            while current is not None:
                all_values.append(current.value)
                current = current.next
        return all_values

    def items(self):
        """返回所有键值对"""
        all_items = []
        for bucket in self.buckets:
            current = bucket
            while current is not None:
                all_items.append((current.key, current.value))
                current = current.next
        return all_items

    def print_stats(self) -> None:
        """打印哈希表统计信息（用于调试）"""
        print(f"Capacity: {self.capacity}, Size: {self.size}")
        print(f"Load factor: {self.size / self.capacity:.2f}")
        max_chain = 0
        empty_buckets = 0
        for i, bucket in enumerate(self.buckets):
            if bucket is None:
                empty_buckets += 1
                continue
            length = 0
            current = bucket
            while current is not None:
                length += 1
                current = current.next
            max_chain = max(max_chain, length)
            print(f"  Bucket {i}: chain length = {length}")
        print(f"Empty buckets: {empty_buckets}, Max chain length: {max_chain}")


if __name__ == "__main__":
    # 使用示例
    ht = HashTable(capacity=8)

    # 插入一些数据
    print("=== 插入测试数据 ===")
    data = [
        ("apple", 100),
        ("banana", 200),
        ("cherry", 300),
        ("date", 400),
        ("elderberry", 500),
        (123, "one two three"),
        (456, "four five six"),
    ]

    for key, value in data:
        ht.put(key, value)
        print(f"Inserted: {key} => {value}")

    print(f"\n哈希表大小: {len(ht)}")

    # 查询测试
    print("\n=== 查询测试 ===")
    for key in ["apple", "banana", "not_exists", 123, 999]:
        value = ht.get(key)
        print(f"Get {key}: {value}")

    # 包含测试
    print("\n=== 包含测试 ===")
    print(f"'apple' in ht: {'apple' in ht}")
    print(f"'orange' in ht: {'orange' in ht}")

    # 更新测试
    print("\n=== 更新测试 ===")
    print(f"Original apple: {ht.get('apple')}")
    ht.put("apple", 999)
    print(f"Updated apple: {ht.get('apple')}")

    # 删除测试
    print("\n=== 删除测试 ===")
    print(f"Delete 'banana': {ht.remove('banana')}")
    print(f"Delete 'not_exists': {ht.remove('not_exists')}")
    print(f"Get 'banana' after delete: {ht.get('banana')}")
    print(f"Size after delete: {len(ht)}")

    # 遍历所有元素
    print("\n=== 遍历所有元素 ===")
    print(f"Keys: {ht.keys()}")
    print(f"Values: {ht.values()}")
    print(f"Items: {ht.items()}")

    # 统计信息
    print("\n=== 统计信息 ===")
    ht.print_stats()


path: assets/example_usage.py
content: #!/usr/bin/env python3
"""
LLM Key Pool 使用示例
演示如何使用分层轮询的多平台API Key管理
"""

import sys
import os
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), '..'))

from llm_key_pool import TieredLLMClient


def main():
    print("=" * 60)
    print("LLM Key Pool 使用示例")
    print("=" * 60)

    # 1. 初始化客户端
    print("\n[步骤1] 初始化客户端...")
    config_path = './llm_config.yaml'
    client = TieredLLMClient(config_path)
    print(f"✓ 客户端初始化成功")

    # 2. 查看Key池状态
    print("\n[步骤2] 查看Key池状态...")
    import json
    status = client.get_status()
    print(json.dumps(status, indent=2, ensure_ascii=False))

    # 3. 调用LLM（简单问题）
    print("\n[步骤3] 调用LLM - 简单问题...")
    result1, usage1 = client.call_llm(
        prompt="用一句话解释什么是人工智能",
        temperature=0.7,
        max_tokens=100
    )
    print(f"\n平台: {usage1['provider']}")
    print(f"层级: {usage1['tier']}")
    print(f"模型: {usage1['model']}")
    print(f"响应: {result1}")

    # 4. 调用LLM（复杂问题）
    print("\n" + "=" * 60)
    print("[步骤4] 调用LLM - 复杂问题...")
    result2, usage2 = client.call_llm(
        prompt="请详细介绍量子计算的基本原理和应用场景",
        system_prompt="你是一个专业的科学讲解者，请用通俗易懂的语言解释",
        temperature=0.7,
        max_tokens=300
    )
    print(f"\n平台: {usage2['provider']}")
    print(f"层级: {usage2['tier']}")
    print(f"模型: {usage2['model']}")
    print(f"响应:\n{result2}")

    # 5. 再次查看状态
    print("\n" + "=" * 60)
    print("[步骤5] 再次查看Key池状态...")
    status_after = client.get_status()
    print(json.dumps(status_after, indent=2, ensure_ascii=False))

    print("\n" + "=" * 60)
    print("✓ 示例运行完成")
    print("=" * 60)


if __name__ == '__main__':
    main()


path: assets/INSTALL_GUIDE.md
content: # 平台集成安装快速指南

## 📋 概述

本文档帮助您快速集成安装以下4个平台：
- **小龙虾（XiaoLongXia）** - 专为大模型推理优化
- **OpenCode** - 专注代码生成
- **Qwen独立API** - 通义千问独立服务
- **ClaudeCode** - Anthropic Claude代码专用版本

## 🚀 三种配置方式

### 方式1：使用配置助手（推荐）

```bash
cd assets
python setup_helper.py
```

按照提示选择：
1. 快速配置向导 - 交互式创建配置文件
2. 查看获取API Key指南 - 了解如何获取各平台API Key
3. 测试现有配置 - 验证配置是否正确

### 方式2：使用快速配置模板

```bash
# 1. 复制快速配置模板
cp assets/llm_config.quickstart.yaml ./llm_config.yaml

# 2. 编辑配置文件，替换API Key
vim ./llm_config.yaml

# 3. 测试配置
python -m llm_key_pool.llm_client \
  --config ./llm_config.yaml \
  --test
```

### 方式3：手动配置

参考 `assets/PLATFORM_SETUP_GUIDE.md` 中的详细说明。

## 📖 详细文档

- **平台集成完整指南**：`assets/PLATFORM_SETUP_GUIDE.md`
- **配置格式说明**：`references/config_format.md`
- **支持平台列表**：`references/supported_providers.md`

## 🔧 最小配置示例

如果您只想配置1个平台快速开始：

```yaml
providers:
  xiaolongxia:
    tier: fallback
    model: "xiaolongxia-72b"
    api_keys:
      - "你的API-Key"
    base_url: "https://api.xiaolongxia.com/v1"

global:
  max_retries: 3
  cooldown_seconds: 300
```

## ✅ 验证步骤

配置完成后，执行以下命令验证：

```bash
# 1. 查看配置状态
python -m llm_key_pool.llm_client \
  --config ./llm_config.yaml \
  --status

# 2. 测试连接
python -m llm_key_pool.llm_client \
  --config ./llm_config.yaml \
  --test

# 3. 发送测试请求
python -m llm_key_pool.llm_client \
  --config ./llm_config.yaml \
  --prompt "你好，请介绍一下自己"
```

## 🎯 各平台特点

| 平台 | 层级推荐 | 模型 | 特色 |
|------|---------|------|------|
| 小龙虾 | primary | xiaolongxia-72b | 推理优化 |
| OpenCode | fallback | opencode-72b | 代码生成 |
| Qwen | fallback | qwen-max | 综合能力强 |
| ClaudeCode | fallback | claude-3-5-sonnet | 代码质量高 |

## 💡 使用建议

1. **小龙虾**：作为主力层，处理大部分任务
2. **OpenCode**：用于代码生成和审查
3. **Qwen**：日常对话和知识问答
4. **ClaudeCode**：复杂代码调试和架构设计

## ⚠️ 注意事项

1. **API Key安全**：不要将配置文件提交到版本控制
2. **额度监控**：定期检查各平台使用情况
3. **层级策略**：根据需求调整平台层级
4. **ClaudeCode价格**：相对较高，建议作为兜底使用

## 🆘 常见问题

**Q1: API Key在哪里获取？**
- 参考 `PLATFORM_SETUP_GUIDE.md` 中的详细说明

**Q2: 配置后无法调用？**
- 检查API Key是否正确
- 检查网络连接
- 查看错误日志

**Q3: 如何选择模型？**
- 72B模型：复杂任务，高质量
- 34B模型：平衡性能
- 14B模型：快速响应

**Q4: ClaudeCode为什么特殊？**
- 使用特殊的请求头格式
- Skill已自动处理，无需担心

## 📞 获取帮助

如遇问题，请查看：
1. `PLATFORM_SETUP_GUIDE.md` - 详细安装指南
2. `supported_providers.md` - 平台配置说明
3. `config_format.md` - 配置格式规范

## 🎉 开始使用

配置完成后，即可开始使用：

```bash
python -m llm_key_pool.llm_client \
  --config ./llm_config.yaml \
  --prompt "你的问题"
```

祝您使用愉快！🎊


path: assets/llm_config.quickstart.yaml
content: # 快速配置模板 - 小龙虾、OpenCode、Qwen、ClaudeCode
# 复制此文件为 llm_config.yaml 并填入你的API Key

providers:
  # ===== 主力层 =====

  # 小龙虾（XiaoLongXia）
  # 获取API Key: 访问小龙虾官网控制台
  xiaolongxia:
    tier: primary
    model: "xiaolongxia-72b"  # 可选: xiaolongxia-34b, xiaolongxia-14b
    api_keys:
      - "xiaolongxia_sk_你的真实API-Key-1"
      # - "xiaolongxia_sk_你的真实API-Key-2"  # 可选，添加更多Key实现负载均衡
    base_url: "https://api.xiaolongxia.com/v1"

  # ===== 兜底层 =====

  # OpenCode
  # 获取API Key: 访问OpenCode官网API管理页面
  opencode:
    tier: fallback
    model: "opencode-72b"  # 可选: opencode-34b, opencode-14b
    api_keys:
      - "opencode_sk_你的真实API-Key"
    base_url: "https://api.opencode.com/v1"

  # Qwen独立API（通义千问）
  # 获取API Key: 访问https://qwen.ai，创建AccessKey
  qwen:
    tier: fallback
    model: "qwen-max"  # 可选: qwen-plus, qwen-turbo, qwen-long
    api_keys:
      - "sk-你的真实API-Key"
    base_url: "https://api.qwen.ai/v1"

  # ClaudeCode
  # 获取API Key: 访问https://console.anthropic.com，创建API Key
  claudecode:
    tier: fallback
    model: "claude-3-5-sonnet-20241022"  # 推荐使用最新版本
    api_keys:
      - "sk-ant-api03-你的真实API-Key"
    base_url: "https://api.anthropic.com/v1"

# ===== 全局配置 =====
global:
  max_retries: 3              # 最大重试次数
  retry_delay: 1              # 重试延迟（秒）
  error_threshold: 5          # 错误阈值，超过则暂时禁用Key
  cooldown_seconds: 300       # 冷却时间（秒）
  quota_check_enabled: true   # 是否启用配额检查
  quota_min_remaining: 10000  # 剩余Token少于这个值自动切换

# ===== 使用说明 =====
#
# 1. 配置步骤：
#    a) 复制此文件为 llm_config.yaml
#    b) 将上面的 "你的真实API-Key" 替换为实际的API Key
#    c) 根据需要调整 tier（层级）和 model（模型）
#
# 2. 测试配置：
#    python -m llm_key_pool.llm_client \
#      --config ./llm_config.yaml \
#      --test
#
# 3. 开始使用：
#    python -m llm_key_pool.llm_client \
#      --config ./llm_config.yaml \
#      --prompt "你好"
#
# 4. 查看状态：
#    python -m llm_key_pool.llm_client \
#      --config ./llm_config.yaml \
#      --status
#
# ===== 层级说明 =====
# - primary（主力层）：优先使用，高额度平台
# - daily（每日回血层）：主力层不可用时切换，额度每日刷新
# - fallback（兜底层）：最后保障，开源/聚合平台
#
# ===== 模型选择建议 =====
# 小龙虾：
#   - xiaolongxia-72b: 复杂推理、长文本
#   - xiaolongxia-34b: 平衡性能与速度
#   - xiaolongxia-14b: 快速响应、简单任务
#
# OpenCode：
#   - opencode-72b: 复杂架构设计、大型项目
#   - opencode-34b: 模块开发、功能实现
#   - opencode-14b: 快速补全、Bug修复
#
# Qwen：
#   - qwen-max: 最强性能
#   - qwen-plus: 性能平衡
#   - qwen-turbo: 快速响应
#   - qwen-long: 长文本处理
#
# ClaudeCode：
#   - claude-3-5-sonnet-20241022: 最新最强（推荐）
#   - claude-3-opus-20240229: 高质量但较慢
#   - claude-3-sonnet-20240229: 平衡性能与速度


path: assets/PLATFORM_SETUP_GUIDE.md
content: # 平台集成安装指南

## 目录
- [小龙虾（XiaoLongXia）](#小龙虾xiaolongxia)
- [OpenCode](#opencode)
- [Qwen独立API](#qwen独立api)
- [ClaudeCode](#claudecode)

---

## 小龙虾（XiaoLongXia）

### 1. 获取API Key

**步骤：**
1. 访问小龙虾官网
2. 注册账号并登录
3. 进入控制台或API管理页面
4. 创建新的API Key
5. 复制生成的API Key

**API Key格式示例：**
```
xiaolongxia_sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```

### 2. 配置Skill

在 `llm_config.yaml` 中添加以下配置：

```yaml
providers:
  xiaolongxia:
    tier: primary  # 或 daily/fallback
    model: "xiaolongxia-72b"  # 或 xiaolongxia-34b, xiaolongxia-14b
    api_keys:
      - "xiaolongxia_sk_你的真实API-Key-1"
      - "xiaolongxia_sk_你的真实API-Key-2"  # 可选，配置多个Key实现负载均衡
    base_url: "https://api.xiaolongxia.com/v1"
```

### 3. 测试连接

```bash
python -m llm_key_pool.llm_client \
  --config ./llm_config.yaml \
  --prompt "你好，请介绍一下小龙虾模型的特点" \
  --temperature 0.7
```

### 4. 验证配置

```bash
# 查看Key池状态
python -m llm_key_pool.llm_client \
  --config ./llm_config.yaml \
  --status
```

### 5. 支持的模型

| 模型名称 | 参数量 | 适用场景 |
|---------|--------|---------|
| xiaolongxia-72b | 72B | 复杂推理、长文本 |
| xiaolongxia-34b | 34B | 平衡性能与速度 |
| xiaolongxia-14b | 14B | 快速响应、简单任务 |

---

## OpenCode

### 1. 获取API Key

**步骤：**
1. 访问OpenCode官网
2. 注册开发者账号
3. 进入API密钥管理页面
4. 生成新的API Key
5. 保存API Key（仅显示一次）

**API Key格式示例：**
```
opencode_sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```

### 2. 配置Skill

在 `llm_config.yaml` 中添加以下配置：

```yaml
providers:
  opencode:
    tier: fallback  # 或 primary/daily
    model: "opencode-72b"  # 或 opencode-34b, opencode-14b
    api_keys:
      - "opencode_sk_你的真实API-Key"
    base_url: "https://api.opencode.com/v1"
```

### 3. 测试代码生成能力

```bash
python -m llm_key_pool.llm_client \
  --config ./llm_config.yaml \
  --prompt "用Python写一个快速排序算法" \
  --system-prompt "你是一个专业的代码助手，请提供完整的、可直接运行的代码" \
  --temperature 0.3  # 代码生成建议使用较低的温度
```

### 4. 代码审查示例

```bash
python -m llm_key_pool.llm_client \
  --config ./llm_config.yaml \
  --prompt "请审查以下代码并提出优化建议：
def fib(n):
    if n <= 1:
        return n
    return fib(n-1) + fib(n-2)" \
  --system-prompt "你是一个代码审查专家，请指出代码的问题并提供优化方案" \
  --max-tokens 500
```

### 5. 支持的模型

| 模型名称 | 参数量 | 代码能力 |
|---------|--------|---------|
| opencode-72b | 72B | 复杂架构设计、大型项目重构 |
| opencode-34b | 34B | 模块开发、功能实现 |
| opencode-14b | 14B | 快速补全、Bug修复 |

---

## Qwen独立API

### 1. 获取API Key

**步骤：**
1. 访问通义千问官网（[https://qwen.ai](https://qwen.ai)）
2. 注册/登录阿里云账号
3. 进入API密钥管理
4. 创建AccessKey
5. 复制生成的AccessKey

**API Key格式示例：**
```
[REDACTED_SECRET]
```

**注意：** 这与阿里云百炼的API Key不同，是独立的服务。

### 2. 配置Skill

在 `llm_config.yaml` 中添加以下配置：

```yaml
providers:
  qwen:
    tier: fallback  # 或 primary/daily
    model: "qwen-max"  # 或 qwen-plus, qwen-turbo, qwen-long
    api_keys:
      - "sk-你的真实API-Key"
    base_url: "https://api.qwen.ai/v1"
```

### 3. 测试长文本能力（qwen-long）

```yaml
qwen:
  tier: fallback
  model: "qwen-long"  # 长文本模型
  api_keys: ["sk-你的API-Key"]
  base_url: "https://api.qwen.ai/v1"
```

```bash
python -m llm_key_pool.llm_client \
  --config ./llm_config.yaml \
  --prompt "请总结以下长篇文章的核心观点..." \
  --max-tokens 10000  # 支持更长的输出
```

### 4. 支持的模型

| 模型名称 | 特点 | 适用场景 |
|---------|------|---------|
| qwen-max | 最强性能 | 复杂推理、创意写作 |
| qwen-plus | 性能平衡 | 日常对话、知识问答 |
| qwen-turbo | 快速响应 | 实时交互、简单任务 |
| qwen-long | 长文本 | 文档处理、长对话 |

### 5. 对比阿里云百炼

| 特性 | 阿里云百炼 | Qwen独立API |
|------|-----------|-------------|
| API端点 | dashscope.aliyuncs.com | api.qwen.ai |
| 功能 | 全套百炼功能 | 纯模型API |
| 价格 | 可能更低 | 可能更高 |
| 适用 | 企业用户 | 开发者 |

---

## ClaudeCode

### 1. 获取API Key

**步骤：**
1. 访问Anthropic控制台（[https://console.anthropic.com](https://console.anthropic.com)）
2. 登录账号（如果没有则注册）
3. 进入API Keys页面
4. 点击"Create Key"
5. 选择用途（可选）
6. 复制生成的API Key（仅显示一次！）

**API Key格式示例：**
```
[REDACTED_SECRET]
```

**重要提示：**
- API Key只显示一次，请妥善保存
- 如果丢失，需要删除并重新创建
- 建议设置使用限额和日期范围

### 2. 配置Skill

在 `llm_config.yaml` 中添加以下配置：

```yaml
providers:
  claudecode:
    tier: fallback  # 或 primary/daily
    model: "claude-3-5-sonnet-20241022"  # 推荐版本
    api_keys:
      - "sk-ant-api03-你的真实API-Key"
    base_url: "https://api.anthropic.com/v1"
```

### 3. 测试代码能力

```bash
python -m llm_key_pool.llm_client \
  --config ./llm_config.yaml \
  --prompt "用JavaScript实现一个React组件，实现一个待办事项列表" \
  --system-prompt "你是一个React专家，请提供完整的代码和说明" \
  --temperature 0.5
```

### 4. 代码调试示例

```bash
python -m llm_key_pool.llm_client \
  --config ./llm_config.yaml \
  --prompt "帮我找出以下代码中的bug：
async function fetchData() {
  const response = await fetch('https://api.example.com/data');
  const data = await response.json();
  console.log(data.items.map(item => item.name));
}

fetchData();" \
  --system-prompt "你是一个调试专家，请分析代码问题并提供修复方案"
```

### 5. 支持的模型

| 模型名称 | 发布日期 | 特点 |
|---------|---------|------|
| claude-3-5-sonnet-20241022 | 2024年10月 | 最新最强，推荐使用 |
| claude-3-opus-20240229 | 2024年2月 | 高质量，但较慢 |
| claude-3-sonnet-20240229 | 2024年2月 | 平衡性能与速度 |

### 6. 特殊注意事项

**ClaudeCode与其他平台的不同：**
- 使用 `x-api-key` 请求头而非 `Authorization: Bearer`
- 使用 `/messages` 端点而非 `/chat/completions`
- 响应格式为 `content[0].text`
- 需要添加 `anthropic-version: 2023-06-01` 请求头

**这些差异已由Skill自动处理，您只需按标准格式配置即可。**

### 7. 价格提示

ClaudeCode是付费服务，价格相对较高，建议：
- 作为兜底层使用（fallback）
- 仅在代码相关任务中使用
- 监控使用量避免超支

---

## 完整配置示例（包含所有4个平台）

```yaml
providers:
  # 主力层
  xiaolongxia:
    tier: primary
    model: "xiaolongxia-72b"
    api_keys:
      - "xiaolongxia_sk_你的Key-1"
      - "xiaolongxia_sk_你的Key-2"
    base_url: "https://api.xiaolongxia.com/v1"

  # 兜底层
  opencode:
    tier: fallback
    model: "opencode-72b"
    api_keys:
      - "opencode_sk_你的Key"
    base_url: "https://api.opencode.com/v1"

  qwen:
    tier: fallback
    model: "qwen-max"
    api_keys:
      - "sk-你的Key"
    base_url: "https://api.qwen.ai/v1"

  claudecode:
    tier: fallback
    model: "claude-3-5-sonnet-20241022"
    api_keys:
      - "sk-ant-api03-你的Key"
    base_url: "https://api.anthropic.com/v1"

global:
  max_retries: 3
  retry_delay: 1
  error_threshold: 5
  cooldown_seconds: 300
  quota_check_enabled: true
```

---

## 验证安装

### 1. 测试所有平台

```bash
# 测试小龙虾
python -m llm_key_pool.llm_client \
  --config ./llm_config.yaml \
  --prompt "测试小龙虾" \
  --max-tokens 50

# 测试OpenCode
python -m llm_key_pool.llm_client \
  --config ./llm_config.yaml \
  --prompt "print('hello')" \
  --max-tokens 50

# 测试Qwen
python -m llm_key_pool.llm_client \
  --config ./llm_config.yaml \
  --prompt "测试Qwen" \
  --max-tokens 50

# 测试ClaudeCode
python -m llm_key_pool.llm_client \
  --config ./llm_config.yaml \
  --prompt "print('hello')" \
  --max-tokens 50
```

### 2. 查看状态

```bash
python -m llm_key_pool.llm_client \
  --config ./llm_config.yaml \
  --status
```

### 3. 完整测试

```bash
python -m llm_key_pool.llm_client \
  --config ./llm_config.yaml \
  --test
```

---

## 常见问题

### Q1: API Key在哪里获取？

**A:** 每个平台的获取方式不同：
- 小龙虾：访问小龙虾官网控制台
- OpenCode：访问OpenCode官网API管理页面
- Qwen：访问通义千问官网
- ClaudeCode：访问Anthropic控制台

### Q2: 配置后无法调用？

**A:** 检查以下几点：
1. API Key是否正确复制（无多余空格）
2. base_url是否正确
3. 模型名称是否正确
4. 账户是否有足够额度
5. 网络连接是否正常

### Q3: 如何选择合适的层级？

**A:**
- primary：主力层，高额度，优先使用
- daily：每日回血层，额度刷新
- fallback：兜底层，开源/聚合，最后保障

### Q4: ClaudeCode为什么特殊？

**A:** ClaudeCode使用Anthropic的Messages API，与OpenAI兼容格式略有不同，但Skill已自动处理这些差异。

### Q5: 可以同时使用多个平台的多个Key吗？

**A:** 可以！在 `api_keys` 数组中添加多个Key即可实现负载均衡。

---

## 下一步

1. ✅ 注册各平台账号
2. ✅ 获取API Key
3. ✅ 配置 `llm_config.yaml`
4. ✅ 测试连接
5. ✅ 开始使用

需要帮助？查看详细文档：
- `references/supported_providers.md`
- `references/config_format.md`
- `SKILL.md`


path: assets/QUICKSTART.md
content: # LLM Key Pool - 快速开始指南

## 5分钟快速上手

### 1. 获取API Key

您需要至少一个平台的API Key。推荐平台：

**免费/赠送额度大的平台：**
- 阿里云百炼：[https://bailian.console.aliyun.com/](https://bailian.console.aliyun.com/)
- 智谱AI：[https://open.bigmodel.cn/](https://open.bigmodel.cn/)
- 硅基流动：[https://siliconflow.cn/](https://siliconflow.cn/)

### 2. 创建配置文件

```bash
# 复制模板
cp assets/llm_config.yaml.example ./llm_config.yaml

# 编辑配置
vim ./llm_config.yaml
```

### 3. 最简配置（只需1个平台）

```yaml
providers:
  siliconflow:
    tier: fallback  # 兜底层
    model: "Qwen/Qwen2.5-7B-Instruct"
    api_keys:
      - "sk-你的真实API-Key"
    base_url: "https://api.siliconflow.cn/v1"

global:
  max_retries: 3
  cooldown_seconds: 300
```

### 4. 立即测试

```bash
python -m llm_key_pool.llm_client \
  --config ./llm_config.yaml \
  --prompt "你好，请介绍一下自己"
```

## 配置多个平台（推荐）

### 最小配置（3个平台）

```yaml
providers:
  # 主力层
  alibaba_bailian:
    tier: primary
    model: "qwen-max"
    api_keys: ["sk-你的API-Key"]
    base_url: "https://dashscope.aliyuncs.com/compatible-mode/v1"

  # 每日回血层
  volcengine:
    tier: daily
    model: "doubao-pro-256k"
    api_keys: ["你的API-Key"]
    base_url: "https://ark.cn-beijing.volces.com/api/v3"

  # 兜底层
  siliconflow:
    tier: fallback
    model: "Qwen/Qwen2.5-7B-Instruct"
    api_keys: ["sk-你的API-Key"]
    base_url: "https://api.siliconflow.cn/v1"
```

## 常用命令

### 查看帮助
```bash
python -m llm_key_pool.llm_client --help
```

### 查看Key池状态
```bash
python -m llm_key_pool.llm_client \
  --config ./llm_config.yaml \
  --status
```

### 测试配置
```bash
python -m llm_key_pool.llm_client \
  --config ./llm_config.yaml \
  --test
```

## 在Python代码中使用

```python
from llm_key_pool import TieredLLMClient

# 初始化
client = TieredLLMClient('./llm_config.yaml')

# 调用
result, usage_info = client.call_llm(
    prompt="你好",
    temperature=0.7,
    max_tokens=100
)

print(result)
```

## 分层轮询说明

1. **主力层（primary）**：优先使用，额度大
2. **每日回血层（daily）**：主力层不可用时切换
3. **兜底层（fallback）**：最后保障

系统会自动在层级间切换，无需手动干预。

## 故障转移

- **429错误**：立即冷却Key，切换下一个
- **401错误**：永久禁用无效Key
- **500错误**：短暂重试

## 注意事项

1. API Key敏感，不要提交到版本控制
2. 配置文件名为 `llm_config.yaml`
3. 确保网络连接正常
4. 查看日志了解调用详情

## 获取帮助

详细文档：
- 配置格式：`references/config_format.md`
- 支持平台：`references/supported_providers.md`
- 使用说明：`SKILL.md`


path: assets/setup_helper.py
content: #!/usr/bin/env python3
"""
快速配置脚本
帮助用户快速配置各平台API Key
"""

import os
import sys
import yaml

# 支持直接运行此脚本
_project_root = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..')
if _project_root not in sys.path:
    sys.path.insert(0, _project_root)

from llm_key_pool.llm_client import TieredLLMClient


def create_config_template():
    """创建配置模板"""

    config_template = {
        'providers': {},
        'global': {
            'max_retries': 3,
            'retry_delay': 1,
            'error_threshold': 5,
            'cooldown_seconds': 300,
            'quota_check_enabled': True
        }
    }

    print("=" * 60)
    print("LLM Key Pool - 快速配置向导")
    print("=" * 60)
    print()

    # 小龙虾配置
    print("【1/4】配置小龙虾（XiaoLongXia）")
    xiaolongxia_enable = input("是否配置小龙虾？(y/n, 默认: n): ").strip().lower()
    if xiaolongxia_enable == 'y':
        [REDACTED_SECRET]"请输入小龙虾API Key: ").strip()
        if [REDACTED_SECRET]'providers']['xiaolongxia'] = {
                'tier': 'primary',
                'model': 'xiaolongxia-72b',
                'api_keys': [api_key],
                'base_url': 'https://api.xiaolongxia.com/v1'
            }
            print("✓ 小龙虾配置完成")
    print()

    # OpenCode配置
    print("【2/4】配置OpenCode")
    opencode_enable = input("是否配置OpenCode？(y/n, 默认: n): ").strip().lower()
    if opencode_enable == 'y':
        [REDACTED_SECRET]"请输入OpenCode API Key: ").strip()
        if [REDACTED_SECRET]'providers']['opencode'] = {
                'tier': 'fallback',
                'model': 'opencode-72b',
                'api_keys': [api_key],
                'base_url': 'https://api.opencode.com/v1'
            }
            print("✓ OpenCode配置完成")
    print()

    # Qwen配置
    print("【3/4】配置Qwen独立API")
    qwen_enable = input("是否配置Qwen？(y/n, 默认: n): ").strip().lower()
    if qwen_enable == 'y':
        [REDACTED_SECRET]"请输入Qwen API Key: ").strip()
        model_choice = input("选择模型 (max/plus/turbo/long, 默认: max): ").strip().lower() or 'max'
        model_map = {
            'max': 'qwen-max',
            'plus': 'qwen-plus',
            'turbo': 'qwen-turbo',
            'long': 'qwen-long'
        }
        model = model_map.get(model_choice, 'qwen-max')

        if [REDACTED_SECRET]'providers']['qwen'] = {
                'tier': 'fallback',
                'model': model,
                'api_keys': [api_key],
                'base_url': 'https://api.qwen.ai/v1'
            }
            print(f"✓ Qwen配置完成（模型: {model}）")
    print()

    # ClaudeCode配置
    print("【4/4】配置ClaudeCode")
    claudecode_enable = input("是否配置ClaudeCode？(y/n, 默认: n): ").strip().lower()
    if claudecode_enable == 'y':
        [REDACTED_SECRET]"请输入ClaudeCode API Key (sk-ant-xxx): ").strip()
        if api_key and api_key.startswith('sk-ant-'):
            config_template['providers']['claudecode'] = {
                'tier': 'fallback',
                'model': 'claude-3-5-sonnet-20241022',
                'api_keys': [api_key],
                'base_url': 'https://api.anthropic.com/v1'
            }
            print("✓ ClaudeCode配置完成")
        elif [REDACTED_SECRET]"⚠ 警告: API Key格式不正确，应以 'sk-ant-' 开头")
    print()

    # 保存配置
    if config_template['providers']:
        config_file = 'llm_config.yaml'
        with open(config_file, 'w', encoding='utf-8') as f:
            yaml.dump(config_template, f, default_flow_style=False, allow_unicode=True)

        print("=" * 60)
        print(f"✓ 配置已保存到 {config_file}")
        print("=" * 60)
        print()
        print("已配置的平台：")
        for provider_name, config in config_template['providers'].items():
            print(f"  - {provider_name} (层级: {config['tier']}, 模型: {config['model']})")
        print()
        print("下一步：")
        print("1. 测试配置：python scripts/llm_client.py --config ./llm_config.yaml --test")
        print("2. 开始使用：python scripts/llm_client.py --config ./llm_config.yaml --prompt '你好'")
    else:
        print("未配置任何平台，配置文件未创建。")


def show_get_api_key_guide():
    """显示获取API Key的指南"""

    print("=" * 60)
    print("获取API Key指南")
    print("=" * 60)
    print()

    print("【小龙虾（XiaoLongXia）】")
    print("1. 访问小龙虾官网")
    print("2. 注册账号并登录")
    print("3. 进入控制台或API管理页面")
    print("4. 创建新的API Key")
    print("5. 复制生成的API Key")
    print()

    print("【OpenCode】")
    print("1. 访问OpenCode官网")
    print("2. 注册开发者账号")
    print("3. 进入API密钥管理页面")
    print("4. 生成新的API Key")
    print("5. 保存API Key（仅显示一次）")
    print()

    print("【Qwen独立API】")
    print("1. 访问通义千问官网（https://qwen.ai）")
    print("2. 注册/登录阿里云账号")
    print("3. 进入API密钥管理")
    print("4. 创建AccessKey")
    print("5. 复制生成的AccessKey")
    print()

    print("【ClaudeCode】")
    print("1. 访问Anthropic控制台（https://console.anthropic.com）")
    print("2. 登录账号（如果没有则注册）")
    print("3. 进入API Keys页面")
    print("4. 点击'Create Key'")
    print("5. 复制生成的API Key（仅显示一次！）")
    print()


def test_config():
    """测试配置"""

    config_file = 'llm_config.yaml'

    if not os.path.exists(config_file):
        print(f"❌ 配置文件不存在: {config_file}")
        print("请先运行配置向导创建配置文件")
        return

    print(f"正在测试配置: {config_file}")
    print()

    from llm_key_pool.llm_client import TieredLLMClient

    try:
        client = TieredLLMClient(config_file)
        print("✓ 配置文件格式正确")
        print()

        status = client.get_status()
        print("配置的平台：")
        for tier_name, pools in status['tiers'].items():
            for pool in pools:
                print(f"  - {pool['provider']} (层级: {tier_name}, 模型: {pool['model']})")
        print()

        print("正在测试连接...")
        result, usage_info = client.call_llm(
            prompt="你好",
            temperature=0.7,
            max_tokens=50
        )

        print(f"✓ 连接成功！")
        print(f"  平台: {usage_info['provider']}")
        print(f"  层级: {usage_info['tier']}")
        print(f"  模型: {usage_info['model']}")
        print(f"  响应: {result}")
        print()

    except Exception as e:
        print(f"❌ 测试失败: {str(e)}")


def main():
    """主函数"""

    print()
    print("╔══════════════════════════════════════════════════════════╗")
    print("║  LLM Key Pool - 平台配置助手                          ║")
    print("╚══════════════════════════════════════════════════════════╝")
    print()

    print("请选择操作：")
    print("1. 快速配置向导（推荐）")
    print("2. 查看获取API Key指南")
    print("3. 测试现有配置")
    print("4. 退出")
    print()

    choice = input("请输入选项 (1/2/3/4): ").strip()

    if choice == '1':
        create_config_template()
    elif choice == '2':
        show_get_api_key_guide()
    elif choice == '3':
        test_config()
    elif choice == '4':
        print("再见！")
    else:
        print("无效选项，请重新运行")


if __name__ == '__main__':
    main()


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (CRITICAL)