基于
@camelai/ai-sdk-provider、dify-plugin-camelai和cherry-studio三个项目的核心实现逻辑,本文档为开发者提供快速接入 CaMeL AI 统一网关的技术指南。
1. 核心接入模式
1.1 统一鉴权与路由
// 核心逻辑:替换 API Key、Base URL,添加 APP-Code
const config = {
apiKey: 'your-camelai-api-key', // 替换为 CaMeL AI API Key
baseURL: 'https://camel.kr777.top', // 替换为 CaMeL AI 网关
headers: {
'APP-Code': 'APP Code' // 应用Code可从https://camel.kr777.top/appstore获取
}
};
// 模型路由规则
function routeModel(modelName: string) {
if (modelName.startsWith('claude')) {
// Claude 模型:使用 Anthropic SDK
return 'anthropic';
} else if (modelName.startsWith('gemini') && !modelName.endsWith('-nothink') && !modelName.endsWith('-search')) {
// Gemini 模型:使用 Google SDK,端点 https://camel.kr777.top/gemini
return 'gemini';
} else {
// 其他模型:使用 OpenAI 兼容接口
return 'openai';
}
}1.2 特殊处理要点
- 空工具修复:当
tools=[]且存在tool_choice时,自动移除tool_choice - 文件扩展名:根据
mediaType自动设置正确的文件扩展名 - 缓存控制:支持
<cache>标签实现缓存控制
2. 统一接入实现
2.1 核心客户端封装
class CaMeL AIModelClient {
private config: {
apiKey: string;
baseURL: string;
appCode: string;
};
constructor(apiKey: string) {
this.config = {
apiKey,
baseURL: 'https://camel.kr777.top',
appCode: 'APP Code'
};
}
async chatCompletion(model: string, messages: any[], options: any = {}) {
// 根据模型名称自动路由到对应的 SDK
if (model.startsWith('claude')) {
return this.claudeCompletion(model, messages, options);
} else if (model.startsWith('gemini')) {
return this.geminiCompletion(model, messages, options);
} else {
return this.openaiCompletion(model, messages, options);
}
}
private async claudeCompletion(model: string, messages: any[], options: any) {
const { Anthropic } = await import('@anthropic-ai/sdk');
const client = new Anthropic({
apiKey: this.config.apiKey,
baseURL: this.config.baseURL,
defaultHeaders: { 'APP-Code': this.config.appCode }
});
return client.messages.create({ model, messages, ...options });
}
private async geminiCompletion(model: string, messages: any[], options: any) {
const { GoogleGenerativeAI } = await import('@google/generative-ai');
const genAI = new GoogleGenerativeAI(this.config.apiKey, {
baseURL: `${this.config.baseURL}/gemini/v1beta`,
defaultHeaders: { 'APP-Code': this.config.appCode }
});
const genModel = genAI.getGenerativeModel({ model });
return genModel.generateContent(messages);
}
private async openaiCompletion(model: string, messages: any[], options: any) {
const OpenAI = await import('openai');
const client = new OpenAI.default({
apiKey: this.config.apiKey,
baseURL: `${this.config.baseURL}/v1`,
defaultHeaders: { 'APP-Code': this.config.appCode }
});
return client.chat.completions.create({ model, messages, ...options });
}
}
// 使用示例
const client = new CaMeL AIModelClient('your-camelai-api-key');
await client.chatCompletion('gpt-4o-mini', messages);
await client.chatCompletion('claude-3-5-sonnet-20241022', messages);
await client.chatCompletion('gemini-2.5-flash', messages);2.2 特殊处理与工具函数
// 空工具修复
function fixToolChoice(requestBody: any): any {
if (requestBody.tools?.length === 0 && requestBody.tool_choice) {
delete requestBody.tool_choice;
}
return requestBody;
}
// 文件扩展名映射
function setFileExtension(mediaType: string): string {
const mimeToExt: Record<string, string> = {
'audio/mpeg': 'mp3', 'audio/wav': 'wav', 'audio/flac': 'flac'
};
return mimeToExt[mediaType] || 'bin';
}
// 缓存控制
function processCacheTags(content: string): { content: string; cacheControl?: any } {
if (content.includes('<cache>')) {
return { content: content.replace('<cache>', ''), cacheControl: { type: 'ephemeral' } };
}
return { content };
}3. 部署与配置
3.1 环境变量
const config = {
apiKey: process.env.CAMELAI_API_KEY || '',
baseURL: process.env.CAMELAI_BASE_URL || 'https://camel.kr777.top',
appCode: process.env.CAMELAI_APP_CODE || 'APP Code'
};3.2 错误处理
class CaMeL AIError extends Error {
constructor(message: string, public code?: string, public status?: number) {
super(message);
this.name = 'CaMeL AIError';
}
}
function handleCaMeL AIErrors(error: any): CaMeL AIError {
const message = error.message || 'Unknown error';
if (message.toLowerCase().includes('rate limit')) {
return new CaMeL AIError('Rate limit exceeded', 'RATE_LIMIT', 429);
} else if (message.toLowerCase().includes('unauthorized')) {
return new CaMeL AIError('Authentication failed', 'AUTH_ERROR', 401);
} else {
return new CaMeL AIError(message, error.code, error.status);
}
}4. 参考实现与对齐清单
4.1 cherry-studio 客户端参考(TypeScript)
下述要点来自 cherry-studio 的 CamelaiAPIClient.ts,可作为第三方前端/桌面端在 TypeScript 侧接入 CaMeL AI 的落地范式:
- 统一追加折扣码:在 Provider 级别合并
extra_headers并设置APP-Code(项目中为MLTG2087) - 多客户端路由:
claude*→ 使用 Anthropic 客户端gemini*/imagen*且不以-nothink/-search结尾且不包含embedding→ 使用 Gemini 客户端(apiHost: https://camel.kr777.top/gemini)- OpenAI 系列(排除
gpt-oss)→ 使用 OpenAI 兼容响应客户端 - 其他 → 回退到默认 OpenAI 客户端
- BaseURL 获取:从当前已路由的具体客户端导出,保持各家端点差异
4.2 dify-plugin-camelai 参考(Python)
下述要点来自 dify-plugin-camelai 的实现,可作为第三方 Python 工具接入 CaMeL AI 的落地范式:
- 统一追加折扣码:在 Provider 级别合并
extra_headers并设置APP-Code(项目中为Dify2025) - 多客户端路由:
claude*→ 使用 Anthropic 客户端gemini*/imagen*且不以-nothink/-search结尾且不包含embedding→ 使用 Gemini 客户端(apiHost: https://camel.kr777.top/gemini)- OpenAI 系列(排除
gpt-oss)→ 使用 OpenAI 兼容响应客户端 - 其他 → 回退到默认 OpenAI 客户端
- BaseURL 获取:从当前已路由的具体客户端导出,保持各家端点差异
4.3 对齐清单
- Provider 入口统一合并
extra_headers并注入APP-Code - Gemini 客户端使用
https://camel.kr777.top/gemini作为apiHost - 路由规则与
claude*、gemini*/imagen*、OpenAI 系列(排除gpt-oss)一致 - 默认回退到 OpenAI 客户端,保持与 OpenAI 兼容接口行为
getBaseURL()始终从当前路由客户端导出,避免硬编码
5. 迁移检查清单
- 替换 API Key 为 CaMeL AI API Key
- 替换 Base URL 为
https://camel.kr777.top - 添加
APP-Codeheader 享受折扣 - 实现模型路由逻辑(claude/gemini/openai)
- 处理空工具时的
tool_choice修复 - 配置文件上传的 MIME 类型处理
- 测试各种模型调用
- 配置错误处理和重试机制