Skip to content

迁移到 OpenHub

本指南帮助你从其他 LLM 平台迁移到 OpenHub。

从 OpenAI 迁移

OpenHub 完全兼容 OpenAI API,只需修改两处配置:

Python

python
from openai import OpenAI

# 旧版 - OpenAI
client = OpenAI(
    api_key="sk-..."  # OpenAI API Key
)

# 新版 - OpenHub
client = OpenAI(
    api_key="YOUR_OPENHUB_API_KEY",  # OpenHub API Key
    base_url="https://api.myopenhub.com/v1"  # 添加这一行
)

# 其他代码无需修改
response = client.chat.completions.create(
    model="gpt-4",
    messages=[{"role": "user", "content": "Hello"}]
)

Node.js

javascript
import OpenAI from 'openai';

// 旧版 - OpenAI
const client = new OpenAI({
  apiKey: 'sk-...'  // OpenAI API Key
});

// 新版 - OpenHub
const client = new OpenAI({
  apiKey: 'YOUR_OPENHUB_API_KEY',  // OpenHub API Key
  baseURL: 'https://api.myopenhub.com/v1'  // 添加这一行
});

// 其他代码无需修改
const response = await client.chat.completions.create({
  model: 'gpt-4',
  messages: [{ role: 'user', content: 'Hello' }]
});

从其他平台迁移

通义千问

python
# 旧版 - 通义千问
import dashscope

response = dashscope.Generation.call(
    model='qwen-max',
    prompt='你好'
)

# 新版 - OpenHub(使用 OpenAI SDK)
from openai import OpenAI

client = OpenAI(
    api_key="YOUR_OPENHUB_API_KEY",
    base_url="https://api.myopenhub.com/v1"
)

response = client.chat.completions.create(
    model='qwen-max',
    messages=[{'role': 'user', 'content': '你好'}]
)

文心一言

python
# 旧版 - 文心一言
import qianfan

response = qianfan.ChatCompletion().do(
    model='ERNIE-4.0',
    messages=[{'role': 'user', 'content': '你好'}]
)

# 新版 - OpenHub(使用 OpenAI SDK)
from openai import OpenAI

client = OpenAI(
    api_key="YOUR_OPENHUB_API_KEY",
    base_url="https://api.myopenhub.com/v1"
)

response = client.chat.completions.create(
    model='ernie-4.0',
    messages=[{'role': 'user', 'content': '你好'}]
)

Claude

python
# 旧版 - Anthropic
import anthropic

client = anthropic.Anthropic(api_key="sk-ant-...")

response = client.messages.create(
    model="claude-3-opus-20240229",
    messages=[{"role": "user", "content": "Hello"}]
)

# 新版 - OpenHub(使用 OpenAI SDK)
from openai import OpenAI

client = OpenAI(
    api_key="YOUR_OPENHUB_API_KEY",
    base_url="https://api.myopenhub.com/v1"
)

response = client.chat.completions.create(
    model='claude-3-opus',
    messages=[{'role': 'user', 'content': 'Hello'}]
)

配置 AI Agent 工具

OpenClaw

编辑 .env 文件:

bash
# 旧版
OPENAI_API_KEY=sk-...
# OPENAI_BASE_URL 不设置

# 新版
OPENAI_API_KEY=YOUR_OPENHUB_API_KEY
OPENAI_BASE_URL=https://api.myopenhub.com/v1

LangChain

python
# 旧版
from langchain.chat_models import ChatOpenAI

llm = ChatOpenAI(
    openai_api_key="sk-...",
    model_name="gpt-4"
)

# 新版
from langchain.chat_models import ChatOpenAI

llm = ChatOpenAI(
    openai_api_key="YOUR_OPENHUB_API_KEY",
    openai_api_base="https://api.myopenhub.com/v1",
    model_name="gpt-4"
)

AutoGPT

编辑 .env 文件:

bash
# 旧版
OPENAI_API_KEY=sk-...

# 新版
OPENAI_API_KEY=YOUR_OPENHUB_API_KEY
OPENAI_API_BASE=https://api.myopenhub.com/v1

模型名称映射

OpenHub 使用简化的模型名称:

原平台原模型名称OpenHub 模型名称
OpenAIgpt-4-0125-previewgpt-4
OpenAIgpt-3.5-turbo-0125gpt-3.5-turbo
Anthropicclaude-3-opus-20240229claude-3-opus
Anthropicclaude-3-sonnet-20240229claude-3-sonnet
通义千问qwen-max-0428qwen-max
文心一言ERNIE-4.0-8Kernie-4.0

提示

OpenHub 会自动使用最新版本的模型,无需指定具体版本号。

使用智能路由节省成本

迁移到 OpenHub 后,建议使用智能路由功能:

python
# 不指定具体模型,让 OpenHub 自动选择
response = client.chat.completions.create(
    model='auto',  # 智能路由
    messages=[{'role': 'user', 'content': '写一首诗'}]
)

智能路由可以节省 50-70% 的成本。

迁移检查清单

  • [ ] 获取 OpenHub API Key
  • [ ] 更新 base_url 配置
  • [ ] 更新 api_key 配置
  • [ ] 测试 API 调用
  • [ ] 更新模型名称(如需要)
  • [ ] 考虑使用智能路由
  • [ ] 监控成本和性能

常见问题

Q: 迁移需要修改很多代码吗?

A: 不需要。只需修改两处配置:api_keybase_url

Q: 所有 OpenAI SDK 功能都支持吗?

A: 是的。OpenHub 完全兼容 OpenAI API。

Q: 可以同时使用多个平台吗?

A: 可以。创建不同的 client 实例即可。

Q: 迁移后成本会增加吗?

A: 不会。OpenHub 的价格通常更低,使用智能路由可节省 50-70% 成本。

获取帮助

// 新版 await fetch('/v1/users/login', { method: 'POST', body: JSON.stringify({ email, password }) });


#### API Keys 管理

| 旧版端点 | 新版端点 | 说明 |
|---------|---------|------|
| `GET /v1/keys` | `GET /v1/keys` | 列出 API Keys |
| `POST /v1/keys` | `POST /v1/keys` | 创建 API Key |
| `DELETE /v1/keys/{id}` | `DELETE /v1/keys/{id}` | 删除 API Key |
| `POST /v1/keys/{id}/rotate` | `POST /v1/keys/{id}/rotate` | 轮换 API Key |

**注意**: 旧版 `/api/keys-v2` 已合并到 `/v1/keys`。

#### 渠道管理

| 旧版端点 | 新版端点 | 说明 |
|---------|---------|------|
| `GET /api/channels` | `GET /v1/admin/channels` | 列出渠道 |
| `POST /api/channels` | `POST /v1/admin/channels` | 创建渠道 |
| `PUT /api/channels/{id}` | `PUT /v1/admin/channels/{id}` | 更新渠道 |
| `DELETE /api/channels/{id}` | `DELETE /v1/admin/channels/{id}` | 删除渠道 |
| `POST /api/channels/{id}/health` | `POST /v1/admin/channels/{id}/health` | 健康检查 |

#### LLM 服务

| 旧版端点 | 新版端点 | 说明 |
|---------|---------|------|
| `GET /v1/llm/models` | `GET /v1/llm/models` | 列出模型 |
| `POST /v1/llm/chat/completions` | `POST /v1/llm/chat/completions` | Chat Completions |

#### 计费

| 旧版端点 | 新版端点 | 说明 |
|---------|---------|------|
| `GET /api/balance` | `GET /v1/billing/balance` | 获取余额 |
| `GET /api/plans` | `GET /v1/billing/plans` | 获取套餐列表 |

#### 统计数据

| 旧版端点 | 新版端点 | 说明 |
|---------|---------|------|
| `GET /api/stats` | `GET /v1/stats` | 获取使用统计 |

#### OAuth

| 旧版端点 | 新版端点 | 说明 |
|---------|---------|------|
| `GET /api/oauth/login` | `GET /v1/oauth/login` | OAuth 登录 |
| `GET /api/oauth/callback` | `GET /v1/oauth/callback` | OAuth 回调 |

#### Webhooks

| 旧版端点 | 新版端点 | 说明 |
|---------|---------|------|
| `GET /api/webhooks` | `GET /v1/webhooks` | 列出 Webhooks |
| `POST /api/webhooks` | `POST /v1/webhooks` | 创建 Webhook |

### 3. 更新响应处理

#### 旧版响应格式(不一致)

```javascript
// 有些端点直接返回数据
const data = await response.json();

// 有些端点返回包装格式
const { success, data, message } = await response.json();

新版响应格式(统一)

所有端点使用统一的 ApiResponse<T> 格式:

javascript
const response = await fetch('/v1/users/me', {
  headers: { 'Authorization': `Bearer ${token}` }
});

const result = await response.json();
// result 结构:
// {
//   success: boolean,
//   data: T | null,
//   error: { code: string, message: string } | null,
//   message: string | null
// }

if (result.success) {
  const user = result.data;
  // 处理成功响应
} else {
  const errorCode = result.error.code;
  const errorMessage = result.error.message;
  // 处理错误响应
}

4. 更新错误处理

新版错误码

错误码HTTP 状态码说明
NOT_FOUND404资源不存在
FORBIDDEN403无权访问
UNAUTHORIZED401未认证或认证失败
INVALID_CREDENTIALS401用户名或密码错误
VALIDATION_ERROR400参数验证失败
DATABASE_ERROR500数据库错误
INTERNAL_ERROR500内部服务器错误

迁移示例:

javascript
// 旧版
try {
  const response = await fetch('/v1/keys');
  if (response.status === 404) {
    console.error('Not found');
  }
} catch (error) {
  console.error('Error:', error);
}

// 新版
try {
  const response = await fetch('/v1/keys');
  const result = await response.json();
  
  if (!result.success) {
    switch (result.error.code) {
      case 'NOT_FOUND':
        console.error('Resource not found');
        break;
      case 'FORBIDDEN':
        console.error('Access denied');
        break;
      default:
        console.error('Error:', result.error.message);
    }
  }
} catch (error) {
  console.error('Network error:', error);
}

5. 检查响应头

v1 端点响应头

X-API-Version: v1
Content-Type: application/json

已废弃端点响应头

如果您仍在使用旧版端点,响应会包含以下警告头:

X-Deprecated: true
X-Deprecated-Message: This endpoint is deprecated. Use /v1/{path} instead
X-Sunset-Date: 2026-06-01

检查示例:

javascript
const response = await fetch('/v1/keys');
const deprecated = response.headers.get('X-Deprecated');
const sunsetDate = response.headers.get('X-Sunset-Date');

if (deprecated === 'true') {
  console.warn(`This endpoint is deprecated and will be removed on ${sunsetDate}`);
}

前端迁移示例

TypeScript/JavaScript

旧版:

typescript
// client.ts
const API_BASE = '/api';

async function getUser() {
  const response = await fetch(`${API_BASE}/me`, {
    headers: { 'Authorization': `Bearer ${token}` }
  });
  return response.json();
}

新版:

typescript
// config.ts
export const API_BASE = '/v1';

// client.ts
import { API_BASE } from './config';

interface ApiResponse<T> {
  success: boolean;
  data?: T;
  error?: {
    code: string;
    message: string;
  };
  message?: string;
}

async function getUser() {
  const response = await fetch(`${API_BASE}/users/me`, {
    headers: { 'Authorization': `Bearer ${token}` }
  });
  
  const result: ApiResponse<User> = await response.json();
  
  if (!result.success) {
    throw new Error(result.error?.message || 'Unknown error');
  }
  
  return result.data;
}

Python

旧版:

python
import requests

API_BASE = "https://api.myopenhub.com/api"

def get_user(token):
    response = requests.get(
        f"{API_BASE}/me",
        headers={"Authorization": f"Bearer {token}"}
    )
    return response.json()

新版:

python
import requests
from typing import TypedDict, Optional

API_BASE = "https://api.myopenhub.com/v1"

class ApiError(TypedDict):
    code: str
    message: str

class ApiResponse(TypedDict):
    success: bool
    data: Optional[dict]
    error: Optional[ApiError]
    message: Optional[str]

def get_user(token):
    response = requests.get(
        f"{API_BASE}/users/me",
        headers={"Authorization": f"Bearer {token}"}
    )
    
    result: ApiResponse = response.json()
    
    if not result["success"]:
        error = result.get("error", {})
        raise Exception(error.get("message", "Unknown error"))
    
    return result["data"]

Rust

旧版:

rust
const API_BASE: &str = "https://api.myopenhub.com/api";

async fn get_user(token: &str) -> Result<User, Error> {
    let response = reqwest::Client::new()
        .get(format!("{}/me", API_BASE))
        .bearer_auth(token)
        .send()
        .await?;
    
    response.json().await
}

新版:

rust
use serde::{Deserialize, Serialize};

const API_BASE: &str = "https://api.myopenhub.com/v1";

#[derive(Deserialize)]
struct ApiResponse<T> {
    success: bool,
    data: Option<T>,
    error: Option<ApiError>,
    message: Option<String>,
}

#[derive(Deserialize)]
struct ApiError {
    code: String,
    message: String,
}

async fn get_user(token: &str) -> Result<User, Error> {
    let response = reqwest::Client::new()
        .get(format!("{}/users/me", API_BASE))
        .bearer_auth(token)
        .send()
        .await?;
    
    let result: ApiResponse<User> = response.json().await?;
    
    if !result.success {
        let error = result.error.unwrap_or_else(|| ApiError {
            code: "UNKNOWN_ERROR".to_string(),
            message: "Unknown error".to_string(),
        });
        return Err(Error::Api(error.message));
    }
    
    result.data.ok_or(Error::NoData)
}

测试迁移

1. 并行测试

在迁移期间,您可以同时调用新旧端点进行对比测试:

javascript
async function testMigration() {
  // 调用旧版端点
  const oldResponse = await fetch('/v1/keys');
  const oldData = await oldResponse.json();
  
  // 调用新版端点
  const newResponse = await fetch('/v1/keys');
  const newResult = await newResponse.json();
  
  // 对比数据
  console.assert(
    JSON.stringify(oldData) === JSON.stringify(newResult.data),
    'Data mismatch between old and new API'
  );
}

2. 检查废弃警告

javascript
async function checkDeprecation() {
  const response = await fetch('/v1/keys');
  
  if (response.headers.get('X-Deprecated') === 'true') {
    const message = response.headers.get('X-Deprecated-Message');
    const sunsetDate = response.headers.get('X-Sunset-Date');
    
    console.warn(`⚠️ ${message}`);
    console.warn(`📅 Sunset date: ${sunsetDate}`);
  }
}

常见问题

Q: 旧版 API 什么时候会被移除?

A: 旧版 API 将在 2026-06-01 被移除。在此之前,旧版 API 将继续工作,但会返回废弃警告头。

Q: 新旧 API 的功能有差异吗?

A: 没有。新版 API 保持与旧版 API 完全相同的功能和业务逻辑,只是路径和响应格式统一了。

Q: 我需要更新 API Key 吗?

A: 不需要。现有的 API Key 在新旧版本中都可以使用。

Q: 响应格式变化会影响我的应用吗?

A: 是的。新版 API 使用统一的 ApiResponse<T> 格式,您需要更新响应处理代码。请参考上面的迁移示例。

Q: 如何知道我的应用是否还在使用旧版 API?

A: 检查 HTTP 响应头中的 X-Deprecated 字段。如果值为 true,说明您正在使用已废弃的端点。

Q: 迁移后性能会有提升吗?

A: 是的。新版 API 进行了数据库查询优化,响应时间平均提升 20-30%。

获取帮助

如果您在迁移过程中遇到问题,请:

  1. 查看 API 文档
  2. 查看 GitHub Issues
  3. 联系技术支持:support@myopenhub.com

更新日志

  • 2024-03-12: 发布迁移指南
  • 2024-03-12: v1 API 正式发布

Released under the MIT License.