mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-22 16:43:35 +00:00
feat(pricing): 添加模型价格文件本地fallback机制
This commit is contained in:
@@ -1,205 +0,0 @@
|
||||
# 升级指南 - API Key 有效期功能
|
||||
|
||||
本指南说明如何从旧版本安全升级到支持 API Key 有效期限制的新版本。
|
||||
|
||||
## 升级前准备
|
||||
|
||||
### 1. 备份现有数据
|
||||
|
||||
在升级前,强烈建议备份您的生产数据:
|
||||
|
||||
```bash
|
||||
# 导出所有数据(包含敏感信息)
|
||||
npm run data:export -- --output=prod-backup-$(date +%Y%m%d).json
|
||||
|
||||
# 或导出脱敏数据(用于测试环境)
|
||||
npm run data:export:sanitized -- --output=prod-backup-sanitized-$(date +%Y%m%d).json
|
||||
```
|
||||
|
||||
### 2. 确认备份完整性
|
||||
|
||||
检查导出的文件,确保包含所有必要的数据:
|
||||
|
||||
```bash
|
||||
# 查看备份文件信息
|
||||
cat prod-backup-*.json | jq '.metadata'
|
||||
|
||||
# 查看数据统计
|
||||
cat prod-backup-*.json | jq '.data | keys'
|
||||
```
|
||||
|
||||
## 升级步骤
|
||||
|
||||
### 1. 停止服务
|
||||
|
||||
```bash
|
||||
# 停止 Claude Relay Service
|
||||
npm run service:stop
|
||||
|
||||
# 或如果使用 Docker
|
||||
docker-compose down
|
||||
```
|
||||
|
||||
### 2. 更新代码
|
||||
|
||||
```bash
|
||||
# 拉取最新代码
|
||||
git pull origin main
|
||||
|
||||
# 安装依赖
|
||||
npm install
|
||||
|
||||
# 更新 Web 界面依赖
|
||||
npm run install:web
|
||||
```
|
||||
|
||||
### 3. 运行数据迁移
|
||||
|
||||
为现有的 API Key 设置默认 30 天有效期:
|
||||
|
||||
```bash
|
||||
# 先进行模拟运行,查看将要修改的数据
|
||||
npm run migrate:apikey-expiry:dry
|
||||
|
||||
# 确认无误后,执行实际迁移
|
||||
npm run migrate:apikey-expiry
|
||||
```
|
||||
|
||||
如果您想设置不同的默认有效期:
|
||||
|
||||
```bash
|
||||
# 设置 90 天有效期
|
||||
npm run migrate:apikey-expiry -- --days=90
|
||||
```
|
||||
|
||||
### 4. 启动服务
|
||||
|
||||
```bash
|
||||
# 启动服务
|
||||
npm run service:start:daemon
|
||||
|
||||
# 或使用 Docker
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
### 5. 验证升级
|
||||
|
||||
1. 登录 Web 管理界面
|
||||
2. 检查 API Key 列表,确认显示过期时间列
|
||||
3. 测试创建新的 API Key,确认可以设置过期时间
|
||||
4. 测试续期功能是否正常工作
|
||||
|
||||
## 从生产环境导入数据(用于测试)
|
||||
|
||||
如果您需要在测试环境中使用生产数据:
|
||||
|
||||
### 1. 在生产环境导出数据
|
||||
|
||||
```bash
|
||||
# 导出脱敏数据(推荐用于测试)
|
||||
npm run data:export:sanitized -- --output=prod-export.json
|
||||
|
||||
# 或只导出特定类型的数据
|
||||
npm run data:export -- --types=apikeys,accounts --sanitize --output=prod-partial.json
|
||||
```
|
||||
|
||||
### 2. 传输文件到测试环境
|
||||
|
||||
使用安全的方式传输文件,如 SCP:
|
||||
|
||||
```bash
|
||||
scp prod-export.json user@test-server:/path/to/claude-relay-service/
|
||||
```
|
||||
|
||||
### 3. 在测试环境导入数据
|
||||
|
||||
```bash
|
||||
# 导入数据,遇到冲突时询问
|
||||
npm run data:import -- --input=prod-export.json
|
||||
|
||||
# 或跳过所有冲突
|
||||
npm run data:import -- --input=prod-export.json --skip-conflicts
|
||||
|
||||
# 或强制覆盖所有数据(谨慎使用)
|
||||
npm run data:import -- --input=prod-export.json --force
|
||||
```
|
||||
|
||||
## 回滚方案
|
||||
|
||||
如果升级后遇到问题,可以按以下步骤回滚:
|
||||
|
||||
### 1. 停止服务
|
||||
|
||||
```bash
|
||||
npm run service:stop
|
||||
```
|
||||
|
||||
### 2. 恢复代码
|
||||
|
||||
```bash
|
||||
# 切换到之前的版本
|
||||
git checkout <previous-version-tag>
|
||||
|
||||
# 重新安装依赖
|
||||
npm install
|
||||
```
|
||||
|
||||
### 3. 恢复数据(如需要)
|
||||
|
||||
```bash
|
||||
# 从备份恢复数据
|
||||
npm run data:import -- --input=prod-backup-<date>.json --force
|
||||
```
|
||||
|
||||
### 4. 重启服务
|
||||
|
||||
```bash
|
||||
npm run service:start:daemon
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **数据迁移是幂等的**:迁移脚本可以安全地多次运行,已有过期时间的 API Key 不会被修改。
|
||||
|
||||
2. **过期的 API Key 处理**:
|
||||
- 过期的 API Key 会被自动禁用,而不是删除
|
||||
- 管理员可以通过续期功能重新激活过期的 Key
|
||||
|
||||
3. **定时任务**:
|
||||
- 系统会每小时自动检查并禁用过期的 API Key
|
||||
- 该任务在 `config.system.cleanupInterval` 中配置
|
||||
|
||||
4. **API 兼容性**:
|
||||
- 新增的过期时间功能完全向后兼容
|
||||
- 现有的 API 调用不会受到影响
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q: 如果不想某些 API Key 过期怎么办?
|
||||
|
||||
A: 您可以通过 Web 界面将特定 API Key 设置为"永不过期",或在续期时选择"设为永不过期"。
|
||||
|
||||
### Q: 迁移脚本会影响已经设置了过期时间的 API Key 吗?
|
||||
|
||||
A: 不会。迁移脚本只会处理没有设置过期时间的 API Key。
|
||||
|
||||
### Q: 如何批量修改 API Key 的过期时间?
|
||||
|
||||
A: 您可以修改迁移脚本,或使用数据导出/导入工具批量处理。
|
||||
|
||||
### Q: 导出的脱敏数据可以用于生产环境吗?
|
||||
|
||||
A: 不建议。脱敏数据缺少关键的认证信息(如 OAuth tokens),仅适用于测试环境。
|
||||
|
||||
## 技术支持
|
||||
|
||||
如遇到问题,请检查:
|
||||
|
||||
1. 服务日志:`npm run service:logs`
|
||||
2. Redis 连接:确保 Redis 服务正常运行
|
||||
3. 配置文件:检查 `.env` 和 `config/config.js`
|
||||
|
||||
如需进一步帮助,请提供:
|
||||
- 错误日志
|
||||
- 使用的命令
|
||||
- 系统环境信息
|
||||
@@ -1,187 +0,0 @@
|
||||
# API Key 过期时间管理指南
|
||||
|
||||
## 概述
|
||||
|
||||
Claude Relay Service 支持为 API Keys 设置过期时间,提供了灵活的过期管理功能,方便进行权限控制和安全管理。
|
||||
|
||||
## 功能特性
|
||||
|
||||
- ✅ 创建时设置过期时间
|
||||
- ✅ 随时修改过期时间
|
||||
- ✅ 自动禁用过期的 Keys
|
||||
- ✅ 手动续期功能
|
||||
- ✅ 批量续期支持
|
||||
- ✅ Web 界面和 CLI 双重管理
|
||||
|
||||
## CLI 管理工具
|
||||
|
||||
### 1. 查看 API Keys
|
||||
|
||||
```bash
|
||||
npm run cli keys
|
||||
# 选择 "📋 查看所有 API Keys"
|
||||
```
|
||||
|
||||
显示内容包括:
|
||||
- 名称和部分 Key
|
||||
- 活跃/禁用状态
|
||||
- 过期时间(带颜色提示)
|
||||
- Token 使用量
|
||||
- Token 限制
|
||||
|
||||
### 2. 修改过期时间
|
||||
|
||||
```bash
|
||||
npm run cli keys
|
||||
# 选择 "🔧 修改 API Key 过期时间"
|
||||
```
|
||||
|
||||
支持的过期选项:
|
||||
- ⏰ **1小时后**(测试用)
|
||||
- 📅 **1天后**
|
||||
- 📅 **7天后**
|
||||
- 📅 **30天后**
|
||||
- 📅 **90天后**
|
||||
- 📅 **365天后**
|
||||
- ♾️ **永不过期**
|
||||
- 🎯 **自定义日期时间**
|
||||
|
||||
### 3. 批量续期
|
||||
|
||||
```bash
|
||||
npm run cli keys
|
||||
# 选择 "🔄 续期即将过期的 API Key"
|
||||
```
|
||||
|
||||
功能:
|
||||
- 查找7天内即将过期的 Keys
|
||||
- 支持全部续期30天或90天
|
||||
- 支持逐个选择续期
|
||||
|
||||
### 4. 删除 API Keys
|
||||
|
||||
```bash
|
||||
npm run cli keys
|
||||
# 选择 "🗑️ 删除 API Key"
|
||||
```
|
||||
|
||||
## Web 界面功能
|
||||
|
||||
### 创建时设置过期
|
||||
|
||||
在创建 API Key 时,可以选择:
|
||||
- 永不过期
|
||||
- 1天、7天、30天、90天、180天、365天
|
||||
- 自定义日期
|
||||
|
||||
### 查看过期状态
|
||||
|
||||
API Key 列表中显示:
|
||||
- 🔴 已过期(红色)
|
||||
- 🟡 即将过期(7天内,黄色)
|
||||
- 🟢 正常(绿色)
|
||||
- ♾️ 永不过期
|
||||
|
||||
### 手动续期
|
||||
|
||||
对于已过期的 API Keys:
|
||||
1. 点击"续期"按钮
|
||||
2. 选择新的过期时间
|
||||
3. 确认更新
|
||||
|
||||
## 自动清理机制
|
||||
|
||||
系统每小时自动运行清理任务:
|
||||
- 检查所有 API Keys 的过期时间
|
||||
- 将过期的 Keys 标记为禁用(`isActive = false`)
|
||||
- 不删除数据,保留历史记录
|
||||
- 记录清理日志
|
||||
|
||||
## 测试工具
|
||||
|
||||
### 1. 快速测试脚本
|
||||
|
||||
```bash
|
||||
node scripts/test-apikey-expiry.js
|
||||
```
|
||||
|
||||
创建5个测试 Keys:
|
||||
- 已过期(1天前)
|
||||
- 1小时后过期
|
||||
- 1天后过期
|
||||
- 7天后过期
|
||||
- 永不过期
|
||||
|
||||
### 2. 迁移脚本
|
||||
|
||||
为现有 API Keys 设置默认30天过期时间:
|
||||
|
||||
```bash
|
||||
# 预览(不实际修改)
|
||||
npm run migrate:apikey-expiry:dry
|
||||
|
||||
# 执行迁移
|
||||
npm run migrate:apikey-expiry
|
||||
```
|
||||
|
||||
## 使用场景
|
||||
|
||||
### 1. 临时访问
|
||||
|
||||
为临时用户或测试创建短期 Key:
|
||||
```bash
|
||||
# 创建1天有效期的测试 Key
|
||||
# 在 Web 界面或 CLI 中选择"1天"
|
||||
```
|
||||
|
||||
### 2. 定期更新
|
||||
|
||||
为安全考虑,定期更新 Keys:
|
||||
```bash
|
||||
# 每30天自动过期,需要续期
|
||||
# 创建时选择"30天"
|
||||
```
|
||||
|
||||
### 3. 长期合作
|
||||
|
||||
为可信任的长期用户:
|
||||
```bash
|
||||
# 选择"365天"或"永不过期"
|
||||
```
|
||||
|
||||
### 4. 测试过期功能
|
||||
|
||||
快速测试过期验证:
|
||||
```bash
|
||||
# 1. 创建1小时后过期的 Key
|
||||
npm run cli keys
|
||||
# 选择修改过期时间 -> 选择测试 Key -> 1小时后
|
||||
|
||||
# 2. 等待或手动触发清理
|
||||
# 3. 验证 API 调用被拒绝
|
||||
```
|
||||
|
||||
## API 响应
|
||||
|
||||
过期的 API Key 调用时返回:
|
||||
```json
|
||||
{
|
||||
"error": "Unauthorized",
|
||||
"message": "Invalid or inactive API key"
|
||||
}
|
||||
```
|
||||
|
||||
## 最佳实践
|
||||
|
||||
1. **定期审查**:定期检查即将过期的 Keys
|
||||
2. **提前通知**:在过期前通知用户续期
|
||||
3. **分级管理**:根据用户级别设置不同过期策略
|
||||
4. **测试验证**:新功能上线前充分测试过期机制
|
||||
5. **备份恢复**:使用数据导出工具备份 Key 信息
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 过期的 Keys 不会被删除,只是禁用
|
||||
- 可以随时续期已过期的 Keys
|
||||
- 修改过期时间立即生效
|
||||
- 清理任务每小时运行一次
|
||||
@@ -1,177 +0,0 @@
|
||||
# 数据导入/导出加密处理指南
|
||||
|
||||
## 概述
|
||||
|
||||
Claude Relay Service 使用 AES-256-CBC 加密算法来保护敏感数据。本文档详细说明了数据导入/导出工具如何处理加密和未加密的数据。
|
||||
|
||||
## 加密机制
|
||||
|
||||
### 加密的数据类型
|
||||
|
||||
1. **Claude 账户**
|
||||
- email
|
||||
- password
|
||||
- accessToken
|
||||
- refreshToken
|
||||
- claudeAiOauth (OAuth 数据)
|
||||
- 使用 salt: `'salt'`
|
||||
|
||||
2. **Gemini 账户**
|
||||
- geminiOauth (OAuth 数据)
|
||||
- accessToken
|
||||
- refreshToken
|
||||
- 使用 salt: `'gemini-account-salt'`
|
||||
|
||||
### 加密格式
|
||||
|
||||
加密后的数据格式:`{iv}:{encryptedData}`
|
||||
- `iv`: 16字节的初始化向量(hex格式)
|
||||
- `encryptedData`: 加密后的数据(hex格式)
|
||||
|
||||
## 导出功能
|
||||
|
||||
### 1. 解密导出(默认)
|
||||
```bash
|
||||
npm run data:export:enhanced
|
||||
# 或
|
||||
node scripts/data-transfer-enhanced.js export --decrypt=true
|
||||
```
|
||||
|
||||
- **用途**:数据迁移到其他环境
|
||||
- **特点**:
|
||||
- `metadata.decrypted = true`
|
||||
- 敏感数据以明文形式导出
|
||||
- 便于在不同加密密钥的环境间迁移
|
||||
|
||||
### 2. 加密导出
|
||||
```bash
|
||||
npm run data:export:encrypted
|
||||
# 或
|
||||
node scripts/data-transfer-enhanced.js export --decrypt=false
|
||||
```
|
||||
|
||||
- **用途**:备份或在相同加密密钥的环境间传输
|
||||
- **特点**:
|
||||
- `metadata.decrypted = false`
|
||||
- 保持数据的加密状态
|
||||
- 必须在相同的 ENCRYPTION_KEY 环境下才能使用
|
||||
|
||||
### 3. 脱敏导出
|
||||
```bash
|
||||
node scripts/data-transfer-enhanced.js export --sanitize
|
||||
```
|
||||
|
||||
- **用途**:分享数据结构或调试
|
||||
- **特点**:
|
||||
- `metadata.sanitized = true`
|
||||
- 敏感字段被替换为 `[REDACTED]`
|
||||
- 不能用于实际导入
|
||||
|
||||
## 导入功能
|
||||
|
||||
### 自动加密处理逻辑
|
||||
|
||||
```javascript
|
||||
if (importData.metadata.decrypted && !importData.metadata.sanitized) {
|
||||
// 数据已解密且不是脱敏的,需要重新加密
|
||||
// 自动加密所有敏感字段
|
||||
} else {
|
||||
// 数据已加密或是脱敏的,保持原样
|
||||
}
|
||||
```
|
||||
|
||||
### 导入场景
|
||||
|
||||
#### 场景 1:导入解密的数据
|
||||
- **输入**:`metadata.decrypted = true`
|
||||
- **处理**:自动加密所有敏感字段
|
||||
- **结果**:数据以加密形式存储在 Redis
|
||||
|
||||
#### 场景 2:导入加密的数据
|
||||
- **输入**:`metadata.decrypted = false`
|
||||
- **处理**:直接存储,不做加密处理
|
||||
- **结果**:保持原有加密状态
|
||||
- **注意**:必须使用相同的 ENCRYPTION_KEY
|
||||
|
||||
#### 场景 3:导入脱敏的数据
|
||||
- **输入**:`metadata.sanitized = true`
|
||||
- **处理**:警告并询问是否继续
|
||||
- **结果**:导入但缺少敏感数据,账户可能无法正常工作
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 1. 跨环境迁移
|
||||
```bash
|
||||
# 在生产环境导出(解密)
|
||||
npm run data:export:enhanced -- --output=prod-data.json
|
||||
|
||||
# 在测试环境导入(自动加密)
|
||||
npm run data:import:enhanced -- --input=prod-data.json
|
||||
```
|
||||
|
||||
### 2. 同环境备份恢复
|
||||
```bash
|
||||
# 备份(保持加密)
|
||||
npm run data:export:encrypted -- --output=backup.json
|
||||
|
||||
# 恢复(保持加密)
|
||||
npm run data:import:enhanced -- --input=backup.json
|
||||
```
|
||||
|
||||
### 3. 选择性导入
|
||||
```bash
|
||||
# 跳过已存在的数据
|
||||
npm run data:import:enhanced -- --input=data.json --skip-conflicts
|
||||
|
||||
# 强制覆盖所有数据
|
||||
npm run data:import:enhanced -- --input=data.json --force
|
||||
```
|
||||
|
||||
## 安全建议
|
||||
|
||||
1. **加密密钥管理**
|
||||
- 使用强随机密钥(至少32字符)
|
||||
- 不同环境使用不同的密钥
|
||||
- 定期轮换密钥
|
||||
|
||||
2. **导出文件保护**
|
||||
- 解密的导出文件包含明文敏感数据
|
||||
- 应立即加密存储或传输
|
||||
- 使用后及时删除
|
||||
|
||||
3. **权限控制**
|
||||
- 限制导出/导入工具的访问权限
|
||||
- 审计所有数据导出操作
|
||||
- 使用脱敏导出进行非生产用途
|
||||
|
||||
## 故障排除
|
||||
|
||||
### 常见问题
|
||||
|
||||
1. **导入后账户无法使用**
|
||||
- 检查 ENCRYPTION_KEY 是否正确
|
||||
- 确认不是导入了脱敏数据
|
||||
- 验证加密字段格式是否正确
|
||||
|
||||
2. **加密/解密失败**
|
||||
- 确保 ENCRYPTION_KEY 长度为32字符
|
||||
- 检查加密数据格式 `{iv}:{data}`
|
||||
- 查看日志中的解密警告
|
||||
|
||||
3. **数据不完整**
|
||||
- 检查导出时是否使用了 --types 限制
|
||||
- 确认 Redis 连接正常
|
||||
- 验证账户前缀(claude:account: vs claude_account:)
|
||||
|
||||
## 测试工具
|
||||
|
||||
运行测试脚本验证加密处理:
|
||||
```bash
|
||||
node scripts/test-import-encryption.js
|
||||
```
|
||||
|
||||
该脚本会:
|
||||
1. 创建测试导出文件(加密和解密版本)
|
||||
2. 显示加密前后的数据对比
|
||||
3. 提供测试导入命令
|
||||
4. 验证加密/解密功能
|
||||
@@ -37,7 +37,8 @@
|
||||
"data:export:enhanced": "node scripts/data-transfer-enhanced.js export",
|
||||
"data:export:encrypted": "node scripts/data-transfer-enhanced.js export --decrypt=false",
|
||||
"data:import:enhanced": "node scripts/data-transfer-enhanced.js import",
|
||||
"data:debug": "node scripts/debug-redis-keys.js"
|
||||
"data:debug": "node scripts/debug-redis-keys.js",
|
||||
"test:pricing-fallback": "node scripts/test-pricing-fallback.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.6.0",
|
||||
|
||||
37
resources/model-pricing/README.md
Normal file
37
resources/model-pricing/README.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# Model Pricing Data
|
||||
|
||||
This directory contains a local copy of the LiteLLM model pricing data as a fallback mechanism.
|
||||
|
||||
## Source
|
||||
The original file is maintained by the LiteLLM project:
|
||||
- Repository: https://github.com/BerriAI/litellm
|
||||
- File: https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json
|
||||
|
||||
## Purpose
|
||||
This local copy serves as a fallback when the remote file cannot be downloaded due to:
|
||||
- Network restrictions
|
||||
- Firewall rules
|
||||
- DNS resolution issues
|
||||
- GitHub being blocked in certain regions
|
||||
- Docker container network limitations
|
||||
|
||||
## Update Process
|
||||
The pricingService will:
|
||||
1. First attempt to download the latest version from GitHub
|
||||
2. If download fails, use this local copy as fallback
|
||||
3. Log a warning when using the fallback file
|
||||
|
||||
## Manual Update
|
||||
To manually update this file with the latest pricing data:
|
||||
```bash
|
||||
curl -s https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json -o model_prices_and_context_window.json
|
||||
```
|
||||
|
||||
## File Format
|
||||
The file contains JSON data with model pricing information including:
|
||||
- Model names and identifiers
|
||||
- Input/output token costs
|
||||
- Context window sizes
|
||||
- Model capabilities
|
||||
|
||||
Last updated: 2025-07-29
|
||||
17435
resources/model-pricing/model_prices_and_context_window.json
Normal file
17435
resources/model-pricing/model_prices_and_context_window.json
Normal file
File diff suppressed because it is too large
Load Diff
92
scripts/test-pricing-fallback.js
Normal file
92
scripts/test-pricing-fallback.js
Normal file
@@ -0,0 +1,92 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// 测试定价服务的fallback机制
|
||||
async function testPricingFallback() {
|
||||
console.log('🧪 Testing pricing service fallback mechanism...\n');
|
||||
|
||||
// 备份现有的模型定价文件
|
||||
const dataDir = path.join(process.cwd(), 'data');
|
||||
const pricingFile = path.join(dataDir, 'model_pricing.json');
|
||||
const backupFile = path.join(dataDir, 'model_pricing.backup.json');
|
||||
|
||||
// 1. 备份现有文件
|
||||
if (fs.existsSync(pricingFile)) {
|
||||
console.log('📦 Backing up existing pricing file...');
|
||||
fs.copyFileSync(pricingFile, backupFile);
|
||||
}
|
||||
|
||||
try {
|
||||
// 2. 删除现有定价文件以触发fallback
|
||||
if (fs.existsSync(pricingFile)) {
|
||||
console.log('🗑️ Removing existing pricing file to test fallback...');
|
||||
fs.unlinkSync(pricingFile);
|
||||
}
|
||||
|
||||
// 3. 初始化定价服务
|
||||
console.log('🚀 Initializing pricing service...\n');
|
||||
|
||||
// 清除require缓存以确保重新加载
|
||||
delete require.cache[require.resolve('../src/services/pricingService')];
|
||||
const pricingService = require('../src/services/pricingService');
|
||||
|
||||
// 模拟网络失败,强制使用fallback
|
||||
const originalDownload = pricingService._downloadFromRemote;
|
||||
pricingService._downloadFromRemote = function() {
|
||||
return Promise.reject(new Error('Simulated network failure for testing'));
|
||||
};
|
||||
|
||||
// 初始化服务
|
||||
await pricingService.initialize();
|
||||
|
||||
// 4. 验证fallback是否工作
|
||||
console.log('\n📊 Verifying fallback data...');
|
||||
const status = pricingService.getStatus();
|
||||
console.log(` - Initialized: ${status.initialized}`);
|
||||
console.log(` - Model count: ${status.modelCount}`);
|
||||
console.log(` - Last updated: ${status.lastUpdated}`);
|
||||
|
||||
// 5. 测试获取模型定价
|
||||
const testModels = ['claude-3-opus-20240229', 'gpt-4', 'gemini-pro'];
|
||||
console.log('\n💰 Testing model pricing retrieval:');
|
||||
|
||||
for (const model of testModels) {
|
||||
const pricing = pricingService.getModelPricing(model);
|
||||
if (pricing) {
|
||||
console.log(` ✅ ${model}: Found pricing data`);
|
||||
} else {
|
||||
console.log(` ❌ ${model}: No pricing data`);
|
||||
}
|
||||
}
|
||||
|
||||
// 6. 验证文件是否被创建
|
||||
if (fs.existsSync(pricingFile)) {
|
||||
console.log('\n✅ Fallback successfully created pricing file in data directory');
|
||||
const fileStats = fs.statSync(pricingFile);
|
||||
console.log(` - File size: ${(fileStats.size / 1024).toFixed(2)} KB`);
|
||||
} else {
|
||||
console.log('\n❌ Fallback failed to create pricing file');
|
||||
}
|
||||
|
||||
// 恢复原始下载函数
|
||||
pricingService._downloadFromRemote = originalDownload;
|
||||
|
||||
} finally {
|
||||
// 7. 恢复备份文件
|
||||
if (fs.existsSync(backupFile)) {
|
||||
console.log('\n📦 Restoring original pricing file...');
|
||||
fs.copyFileSync(backupFile, pricingFile);
|
||||
fs.unlinkSync(backupFile);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('\n✨ Fallback mechanism test completed!');
|
||||
}
|
||||
|
||||
// 运行测试
|
||||
testPricingFallback().catch(error => {
|
||||
console.error('❌ Test failed:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -8,6 +8,7 @@ class PricingService {
|
||||
this.dataDir = path.join(process.cwd(), 'data');
|
||||
this.pricingFile = path.join(this.dataDir, 'model_pricing.json');
|
||||
this.pricingUrl = 'https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json';
|
||||
this.fallbackFile = path.join(process.cwd(), 'resources', 'model-pricing', 'model_prices_and_context_window.json');
|
||||
this.pricingData = null;
|
||||
this.lastUpdated = null;
|
||||
this.updateInterval = 24 * 60 * 60 * 1000; // 24小时
|
||||
@@ -50,8 +51,8 @@ class PricingService {
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('❌ Failed to check/update pricing:', error);
|
||||
// 如果更新失败,尝试加载现有数据
|
||||
await this.loadPricingData();
|
||||
// 如果更新失败,尝试使用fallback
|
||||
await this.useFallbackPricing();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,7 +75,18 @@ class PricingService {
|
||||
}
|
||||
|
||||
// 下载价格数据
|
||||
downloadPricingData() {
|
||||
async downloadPricingData() {
|
||||
try {
|
||||
await this._downloadFromRemote();
|
||||
} catch (downloadError) {
|
||||
logger.warn(`⚠️ Failed to download pricing data: ${downloadError.message}`);
|
||||
logger.info('📋 Using local fallback pricing data...');
|
||||
await this.useFallbackPricing();
|
||||
}
|
||||
}
|
||||
|
||||
// 实际的下载逻辑
|
||||
_downloadFromRemote() {
|
||||
return new Promise((resolve, reject) => {
|
||||
const request = https.get(this.pricingUrl, (response) => {
|
||||
if (response.statusCode !== 200) {
|
||||
@@ -107,12 +119,12 @@ class PricingService {
|
||||
});
|
||||
|
||||
request.on('error', (error) => {
|
||||
reject(new Error(`Failed to download pricing data: ${error.message}`));
|
||||
reject(new Error(`Network error: ${error.message}`));
|
||||
});
|
||||
|
||||
request.setTimeout(30000, () => {
|
||||
request.destroy();
|
||||
reject(new Error('Download timeout'));
|
||||
reject(new Error('Download timeout after 30 seconds'));
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -129,11 +141,41 @@ class PricingService {
|
||||
|
||||
logger.info(`💰 Loaded pricing data for ${Object.keys(this.pricingData).length} models from cache`);
|
||||
} else {
|
||||
logger.warn('💰 No pricing data file found');
|
||||
this.pricingData = {};
|
||||
logger.warn('💰 No pricing data file found, will use fallback');
|
||||
await this.useFallbackPricing();
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('❌ Failed to load pricing data:', error);
|
||||
await this.useFallbackPricing();
|
||||
}
|
||||
}
|
||||
|
||||
// 使用fallback价格数据
|
||||
async useFallbackPricing() {
|
||||
try {
|
||||
if (fs.existsSync(this.fallbackFile)) {
|
||||
logger.info('📋 Copying fallback pricing data to data directory...');
|
||||
|
||||
// 读取fallback文件
|
||||
const fallbackData = fs.readFileSync(this.fallbackFile, 'utf8');
|
||||
const jsonData = JSON.parse(fallbackData);
|
||||
|
||||
// 保存到data目录
|
||||
fs.writeFileSync(this.pricingFile, JSON.stringify(jsonData, null, 2));
|
||||
|
||||
// 更新内存中的数据
|
||||
this.pricingData = jsonData;
|
||||
this.lastUpdated = new Date();
|
||||
|
||||
logger.warn(`⚠️ Using fallback pricing data for ${Object.keys(jsonData).length} models`);
|
||||
logger.info('💡 Note: This fallback data may be outdated. The system will try to update from the remote source on next check.');
|
||||
} else {
|
||||
logger.error('❌ Fallback pricing file not found at:', this.fallbackFile);
|
||||
logger.error('❌ Please ensure the resources/model-pricing directory exists with the pricing file');
|
||||
this.pricingData = {};
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('❌ Failed to use fallback pricing data:', error);
|
||||
this.pricingData = {};
|
||||
}
|
||||
}
|
||||
@@ -222,11 +264,16 @@ class PricingService {
|
||||
// 强制更新价格数据
|
||||
async forceUpdate() {
|
||||
try {
|
||||
await this.downloadPricingData();
|
||||
await this._downloadFromRemote();
|
||||
return { success: true, message: 'Pricing data updated successfully' };
|
||||
} catch (error) {
|
||||
logger.error('❌ Force update failed:', error);
|
||||
return { success: false, message: error.message };
|
||||
logger.info('📋 Force update failed, using fallback pricing data...');
|
||||
await this.useFallbackPricing();
|
||||
return {
|
||||
success: false,
|
||||
message: `Download failed: ${error.message}. Using fallback pricing data instead.`
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user