├── .dev.vars ├── README.md ├── examples ├── claude_example.py └── gemini_example.py ├── worker.js └── wrangler.toml /.dev.vars: -------------------------------------------------------------------------------- 1 | secret="ilovexiaotian" -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 通用LLM代理 Cloudflare Worker 2 | 3 | 这是一个通用的LLM(大语言模型)代理Cloudflare Worker,可以将请求转发到多种AI服务提供商的API。通过简单的URL路径配置,您可以使用单个Worker代理所有主流LLM服务。 4 | 5 | ## 功能特点 6 | 7 | - **统一接口**:使用一致的URL路径格式代理到不同的LLM服务 8 | - **多服务支持**:内置支持OpenAI、Anthropic(Claude)、Google Gemini、Groq、SambaNova等 9 | - **易于扩展**:只需添加新的端点配置即可支持更多服务 10 | - **CORS支持**:自动处理跨域请求问题 11 | - **无状态设计**:简洁高效的实现方式 12 | 13 | ## 如何使用 14 | 15 | ### 部署Worker 16 | 17 | 1. 在Cloudflare注册并创建Worker 18 | 2. 将`worker.js`文件内容复制到Worker编辑器中 19 | 3. 根据需要修改`LLM_ENDPOINTS`配置 20 | 4. 保存并部署 21 | 22 | ### 使用方法 23 | 24 | 通过在URL路径前缀中指定服务提供商名称来路由请求: 25 | 26 | ``` 27 | https://your-worker.workers.dev/{provider}/{original-api-path} 28 | ``` 29 | 30 | 例如: 31 | 32 | - OpenAI: `/openai/v1/chat/completions` 33 | - Claude: `/anthropic/v1/messages` 34 | - Gemini: `/gemini/v1beta/models/gemini-pro:generateContent` 35 | - Groq: `/groq/chat/completions` 36 | 37 | ### API密钥 38 | 39 | 您需要在请求中包含各服务所需的API密钥。Worker只是转发请求,不会修改或添加认证信息。 40 | 41 | ## Worker代码说明 42 | 43 | 该Worker的工作原理: 44 | 45 | 1. **路由解析**:提取URL路径中的第一个段作为提供商标识符 46 | 2. **端点匹配**:根据提供商标识符查找对应的API端点 47 | 3. **请求转换**:重新构建目标URL并转发请求 48 | 4. **响应处理**:返回API响应并添加CORS头 49 | 50 | Worker通过简单的配置对象`LLM_ENDPOINTS`来定义路由映射关系,每个键值对表示一个LLM服务及其端点。 51 | 52 | ## 自定义配置 53 | 54 | 您可以根据需要修改`LLM_ENDPOINTS`对象添加其他LLM服务: 55 | 56 | ```javascript 57 | const LLM_ENDPOINTS = { 58 | 'openai': 'https://api.openai.com', 59 | 'anthropic': 'https://api.anthropic.com', 60 | 'gemini': 'https://generativelanguage.googleapis.com', 61 | 'groq': 'https://api.groq.com', 62 | 'sambanova': 'https://api.sambanova.ai', 63 | 'azure': 'https://YOUR_AZURE_RESOURCE_NAME.openai.azure.com', 64 | // 添加更多服务... 65 | }; 66 | ``` 67 | 68 | ## 高级用法 69 | 70 | 对于需要特殊处理的API(如格式转换或参数调整),您可以扩展Worker代码添加额外处理逻辑。 71 | 72 | ## 注意事项 73 | 74 | - 此Worker默认仅转发请求,不会缓存、修改或存储任何数据 75 | - 确保您有权限访问相应的API并持有有效的API密钥 76 | - 生产环境使用时,建议增加速率限制、请求验证等安全措施 77 | 78 | ## 贡献 79 | 80 | 欢迎通过Issues或Pull Requests贡献改进意见和代码。 81 | 82 | ## 许可 83 | 84 | MIT 85 | -------------------------------------------------------------------------------- /examples/claude_example.py: -------------------------------------------------------------------------------- 1 | # Claude API调用示例 2 | # 使用代理访问Anthropic Claude API 3 | 4 | import requests 5 | import json 6 | 7 | # 配置 8 | PROXY_URL = "https://your-worker.workers.dev" # 替换为您的Worker URL 9 | API_KEY = "your-anthropic-api-key" # 替换为您的Anthropic API密钥 10 | 11 | # 构建请求 12 | url = f"{PROXY_URL}/anthropic/v1/messages" 13 | headers = { 14 | "Content-Type": "application/json", 15 | "x-api-key": API_KEY, 16 | "anthropic-version": "2023-06-01" 17 | } 18 | 19 | payload = { 20 | "model": "claude-3-opus-20240229", 21 | "max_tokens": 500, 22 | "system": "你是一个友好、专业且乐于助人的AI助手。", 23 | "messages": [ 24 | { 25 | "role": "user", 26 | "content": "解释一下人工智能中的'大型语言模型'是什么?" 27 | } 28 | ], 29 | "temperature": 0.7 30 | } 31 | 32 | # 发送请求 33 | try: 34 | response = requests.post(url, headers=headers, json=payload) 35 | response.raise_for_status() # 检查HTTP错误 36 | 37 | # 解析响应 38 | result = response.json() 39 | 40 | # 提取生成的文本 41 | if "content" in result: 42 | for item in result["content"]: 43 | if item["type"] == "text": 44 | print(item["text"]) 45 | else: 46 | print("No content generated") 47 | print(result) 48 | except Exception as e: 49 | print(f"Error: {e}") 50 | print(response.text if 'response' in locals() else "No response") -------------------------------------------------------------------------------- /examples/gemini_example.py: -------------------------------------------------------------------------------- 1 | # Gemini API调用示例 2 | # 使用代理访问Google Gemini API 3 | 4 | import requests 5 | import json 6 | 7 | # 配置 8 | PROXY_URL = "https://your-worker.workers.dev" # 替换为您的Worker URL 9 | API_KEY = "your-gemini-api-key" # 替换为您的Gemini API密钥 10 | 11 | # 构建请求 12 | url = f"{PROXY_URL}/gemini/v1beta/models/gemini-pro:generateContent" 13 | headers = { 14 | "Content-Type": "application/json", 15 | "x-goog-api-key": API_KEY 16 | } 17 | 18 | payload = { 19 | "contents": [ 20 | { 21 | "parts": [ 22 | { 23 | "text": "介绍一下量子计算的基本原理" 24 | } 25 | ] 26 | } 27 | ], 28 | "generationConfig": { 29 | "temperature": 0.7, 30 | "maxOutputTokens": 500 31 | } 32 | } 33 | 34 | # 发送请求 35 | try: 36 | response = requests.post(url, headers=headers, json=payload) 37 | response.raise_for_status() # 检查HTTP错误 38 | 39 | # 解析响应 40 | result = response.json() 41 | 42 | # 提取生成的文本 43 | if "candidates" in result and len(result["candidates"]) > 0: 44 | generated_text = result["candidates"][0]["content"]["parts"][0]["text"] 45 | print(generated_text) 46 | else: 47 | print("No content generated") 48 | print(result) 49 | except Exception as e: 50 | print(f"Error: {e}") 51 | print(response.text if 'response' in locals() else "No response") -------------------------------------------------------------------------------- /worker.js: -------------------------------------------------------------------------------- 1 | // LLM service endpoint mappings 2 | const LLM_ENDPOINTS = { 3 | 'openai': 'https://api.openai.com', 4 | 'anthropic': 'https://api.anthropic.com', 5 | 'gemini': 'https://generativelanguage.googleapis.com', 6 | 'groq': 'https://api.groq.com', 7 | 'sambanova': 'https://api.sambanova.ai', 8 | 'azure': 'https://YOUR_AZURE_RESOURCE_NAME.openai.azure.com', 9 | // Add more providers as needed 10 | }; 11 | 12 | addEventListener('fetch', event => { 13 | event.respondWith(handleRequest(event.request)); 14 | }); 15 | 16 | async function handleRequest(request) { 17 | // Add logging 18 | console.log(`Incoming request to: ${request.url}`); 19 | 20 | // Handle CORS preflight requests 21 | if (request.method === 'OPTIONS') { 22 | console.log('Handling CORS preflight request'); 23 | return handleCORS(request); 24 | } 25 | 26 | const url = new URL(request.url); 27 | const pathParts = url.pathname.split('/').filter(part => part); 28 | 29 | // Check if the first path segment matches any of our LLM providers 30 | if (pathParts.length > 0 && LLM_ENDPOINTS[pathParts[0]]) { 31 | const provider = pathParts[0]; 32 | const targetEndpoint = LLM_ENDPOINTS[provider]; 33 | console.log(`Proxying request to ${provider} at ${targetEndpoint}`); 34 | 35 | // Remove the provider prefix from the path 36 | const newPathname = '/' + pathParts.slice(1).join('/'); 37 | 38 | // Create the new target URL 39 | const targetUrl = new URL(targetEndpoint); 40 | targetUrl.pathname = newPathname; 41 | targetUrl.search = url.search; 42 | 43 | // Clone the request and modify it for the target API 44 | const cleanedHeaders = new Headers(); 45 | for (const [key, value] of request.headers) { 46 | // Skip Cloudflare-specific headers and other headers we want to clean 47 | if (!key.toLowerCase().startsWith('cf-') && 48 | !['x-real-ip', 'x-forwarded-for', 'x-forwarded-proto', 49 | 'x-forwarded-host', 'x-forwarded-port', 'x-forwarded-scheme', 50 | 'x-forwarded-ssl', 'cdn-loop'].includes(key.toLowerCase())) { 51 | cleanedHeaders.set(key, value); 52 | } 53 | } 54 | 55 | const modifiedRequest = new Request(targetUrl.toString(), { 56 | method: request.method, 57 | headers: cleanedHeaders, 58 | body: request.body, 59 | redirect: 'follow' 60 | }); 61 | 62 | // Forward the request to the appropriate LLM API 63 | try { 64 | console.log('Forwarding request with cleaned headers:', 65 | JSON.stringify(Object.fromEntries(cleanedHeaders.entries()), null, 2)); 66 | const response = await fetch(modifiedRequest); 67 | console.log(`Response received with status: ${response.status}`); 68 | 69 | // Create a new response with CORS headers 70 | const modifiedResponse = new Response(response.body, { 71 | status: response.status, 72 | statusText: response.statusText, 73 | headers: response.headers 74 | }); 75 | 76 | // Add CORS headers 77 | modifiedResponse.headers.set('Access-Control-Allow-Origin', request.headers.get('Origin') || '*'); 78 | modifiedResponse.headers.set('Access-Control-Allow-Credentials', 'true'); 79 | 80 | return modifiedResponse; 81 | } catch (error) { 82 | console.error(`Error proxying request to ${provider}:`, error); 83 | return new Response(`Error proxying request to ${provider}: ${error.message}`, { status: 500 }); 84 | } 85 | } 86 | 87 | // If no valid provider is specified in the path 88 | console.log('Invalid provider path requested'); 89 | return new Response('Invalid LLM provider path. Use /provider/api/path format.', { status: 400 }); 90 | } 91 | 92 | function handleCORS(request) { 93 | // Handle CORS preflight requests 94 | const corsHeaders = { 95 | 'Access-Control-Allow-Origin': request.headers.get('Origin') || '*', 96 | 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS', 97 | 'Access-Control-Allow-Headers': request.headers.get('Access-Control-Request-Headers') || 'Content-Type, Authorization', 98 | 'Access-Control-Allow-Credentials': 'true', 99 | 'Access-Control-Max-Age': '86400' 100 | }; 101 | 102 | return new Response(null, { 103 | status: 204, 104 | headers: corsHeaders 105 | }); 106 | } -------------------------------------------------------------------------------- /wrangler.toml: -------------------------------------------------------------------------------- 1 | name = "llm-proxy" 2 | main = "worker.js" 3 | compatibility_date = "2025-02-14" 4 | --------------------------------------------------------------------------------