├── Dockerfile ├── README.md ├── __pycache__ ├── api_client.cpython-313.pyc ├── auth.cpython-313.pyc ├── config.cpython-313.pyc ├── main.cpython-313.pyc ├── models.cpython-313.pyc ├── response_formatter.cpython-313.pyc └── routes.cpython-313.pyc ├── api_client.py ├── auth.py ├── config.py ├── docker-compose.yml ├── main.py ├── models.py ├── requirements.txt ├── response_formatter.py └── routes.py /Dockerfile: -------------------------------------------------------------------------------- 1 | # 使用Python官方镜像作为基础镜像 2 | FROM python:3.10-slim 3 | 4 | # 设置工作目录 5 | WORKDIR /app 6 | 7 | # 设置环境变量 8 | ENV API_TOKEN=sk-114514 9 | 10 | # 复制依赖文件 11 | COPY requirements.txt . 12 | 13 | # 设置清华镜像源并安装依赖 14 | RUN pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple \ 15 | && pip install --no-cache-dir -r requirements.txt 16 | 17 | # 复制应用代码 18 | COPY . . 19 | 20 | # 暴露应用端口 21 | EXPOSE 8080 22 | 23 | # 启动应用 24 | CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8080"] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rkui2api 2 | ### 此项目仅供学习交流 3 | #### 这是我的第一个2api项目,如果觉得本项目对你有帮助的话可以点个star~ 4 | 我的一些其他的网站 5 | [洛樱云API](https://api.luoying.work/) [个人博客](https://blog.alcex.cn) 6 | 7 | 目前grok系列模型无法使用.其他正常,等平台恢复即可使用 8 | ### 支持模型 9 | - deepseek-r1-70b 10 | - deepseek-r1-turbo 11 | - deepseek-ai/DeepSeek-R1-Turbo 12 | - deepseek-ai/DeepSeek-V3-Turbo 13 | - deepseek-v3-turbo 14 | - deepseek-v3-0324 15 | - deepseek-r1-search 16 | - grok-3 17 | - grok-3-search 18 | - grok-3-deepsearch 19 | - grok-3-reasoning 20 | - qwen-32b 21 | - qwq-32b 22 | 23 | ### 部署 24 | #### 环境变量 25 | 26 | API_TOKEN:APIkey可自定义默认为sk-114514 27 | 28 | #### 通过Docker部署 29 | ``` 30 | docker run --name rkui2api -d --restart always -p 3014:8080 -e API_TOKEN=sk-114514 -e TZ=Asia/Shanghai alcexn/rkui2api 31 | ``` 32 | #### 直接运行 33 | 请确保您已安装python环境 34 | ``` 35 | pip install --no-cache-dir -r requirements.txt 36 | ``` 37 | ``` 38 | python main.py 39 | ``` 40 | 41 | #### api路由 42 | GET /v1/models 列出所有模型(兼容OPENAI规范) 43 | POST /v1/chat/completions/ 聊天接口(兼容OPENAI规范) 44 | 45 | 46 | ``` 47 | curl -X POST 'http://localhost:3014/v1/chat/completions' -H 'Content-Type: application/json' -H 'Authorization: Bearer sk-114514' -d '{ 48 | "messages": [ 49 | { 50 | "role": "user", 51 | "content": "你好" 52 | } 53 | ], 54 | "model": "deepseek-r1-70b", 55 | "stream": false 56 | }' 57 | ``` 58 | -------------------------------------------------------------------------------- /__pycache__/api_client.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alcexn/rkui2api/76f676b51e0ece6f4e99a4375ec3588239788740/__pycache__/api_client.cpython-313.pyc -------------------------------------------------------------------------------- /__pycache__/auth.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alcexn/rkui2api/76f676b51e0ece6f4e99a4375ec3588239788740/__pycache__/auth.cpython-313.pyc -------------------------------------------------------------------------------- /__pycache__/config.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alcexn/rkui2api/76f676b51e0ece6f4e99a4375ec3588239788740/__pycache__/config.cpython-313.pyc -------------------------------------------------------------------------------- /__pycache__/main.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alcexn/rkui2api/76f676b51e0ece6f4e99a4375ec3588239788740/__pycache__/main.cpython-313.pyc -------------------------------------------------------------------------------- /__pycache__/models.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alcexn/rkui2api/76f676b51e0ece6f4e99a4375ec3588239788740/__pycache__/models.cpython-313.pyc -------------------------------------------------------------------------------- /__pycache__/response_formatter.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alcexn/rkui2api/76f676b51e0ece6f4e99a4375ec3588239788740/__pycache__/response_formatter.cpython-313.pyc -------------------------------------------------------------------------------- /__pycache__/routes.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alcexn/rkui2api/76f676b51e0ece6f4e99a4375ec3588239788740/__pycache__/routes.cpython-313.pyc -------------------------------------------------------------------------------- /api_client.py: -------------------------------------------------------------------------------- 1 | # api_client.py 2 | # API客户端模块,处理与目标API的通信 3 | 4 | import httpx 5 | import json 6 | import asyncio 7 | import random 8 | from fastapi import HTTPException 9 | from fastapi.responses import StreamingResponse 10 | from random_user_agent.user_agent import UserAgent 11 | 12 | 13 | def generate_random_user_agent(): 14 | """生成随机的User-Agent字符串 15 | 16 | Returns: 17 | 随机生成的User-Agent字符串 18 | """ 19 | # 使用random-user-agent库生成随机User-Agent 20 | user_agent_rotator = UserAgent() 21 | return user_agent_rotator.get_random_user_agent() 22 | 23 | 24 | async def call_api(payload, is_stream=False): 25 | """调用目标API并处理响应 26 | 27 | Args: 28 | payload: 请求负载 29 | is_stream: 是否为流式请求 30 | 31 | Returns: 32 | 流式响应或完整内容 33 | """ 34 | try: 35 | async with httpx.AsyncClient() as client: 36 | # 生成随机User-Agent 37 | user_agent = generate_random_user_agent() 38 | 39 | # 发送请求到目标API 40 | response = await client.post( 41 | "https://deepseek.rkui.cn/api/chat", 42 | json=payload, 43 | timeout=60.0, 44 | headers={ 45 | "Accept": "text/event-stream", 46 | "Cache-Control": "no-cache", 47 | "Connection": "keep-alive", 48 | "User-Agent": user_agent 49 | } 50 | ) 51 | 52 | if response.status_code != 200: 53 | raise HTTPException(status_code=response.status_code, detail=response.text) 54 | 55 | # 检查响应内容是否为空 56 | if not response.content: 57 | raise HTTPException(status_code=502, detail="Empty response from API") 58 | 59 | # 处理流式响应 60 | if is_stream: 61 | return handle_stream_response(response) 62 | 63 | # 处理非流式响应 64 | return await handle_non_stream_response(response) 65 | 66 | except Exception as e: 67 | raise HTTPException(status_code=500, detail=str(e)) 68 | 69 | 70 | def handle_stream_response(response): 71 | """处理流式响应 72 | 73 | Args: 74 | response: API响应对象 75 | 76 | Returns: 77 | StreamingResponse对象 78 | """ 79 | async def generate(): 80 | buffer = "" 81 | chunk_count = 0 # 用于跟踪接收到的数据块数量 82 | 83 | print("\n===== 开始接收API响应数据 =====\n") 84 | 85 | # 使用aiter_text处理文本流,确保实时处理 86 | async for chunk in response.aiter_text(): 87 | chunk_count += 1 88 | print(f"\n[接收数据块 #{chunk_count}] 原始数据: {chunk!r}\n") 89 | 90 | # 立即处理接收到的数据块 91 | buffer += chunk 92 | 93 | # 处理缓冲区中的每一行 94 | while "\n" in buffer: 95 | line, buffer = buffer.split("\n", 1) 96 | 97 | # 处理完整的SSE行 98 | if line.startswith("data: "): 99 | print(f"\n[SSE行] {line}") 100 | 101 | if line == "data: [DONE]": 102 | print("\n===== 接收到完成标记 [DONE] =====\n") 103 | # 直接转发[DONE]标记而不尝试解析 104 | yield "data: [DONE]\n\n" 105 | # 强制刷新缓冲区 106 | await asyncio.sleep(0) 107 | # 设置标志位表示已处理过[DONE] 108 | done_processed = True 109 | continue 110 | 111 | # 如果已经处理过[DONE]标记,则跳过后续所有[DONE] 112 | if hasattr(generate, 'done_processed') and generate.done_processed: 113 | print("[跳过重复的DONE标记]") 114 | continue 115 | 116 | try: 117 | # 检查是否存在双重data:前缀 118 | content = line 119 | if line.startswith("data: data:"): 120 | print(f"[检测到双重data:前缀] 原始: {line}") 121 | # 使用正则表达式精确匹配并去除多余的data:前缀 122 | import re 123 | content = re.sub(r'^data:\s*data:', 'data:', line) 124 | print(f"[处理后] {content}") 125 | 126 | # 直接转发SSE行,不尝试解析JSON 127 | # 确保行以data:开头并以\n\n结尾 128 | formatted_response = f"{content}\n\n" 129 | print(f"[直接转发SSE行] {formatted_response!r}") 130 | # 立即发送数据,不等待整个响应完成 131 | yield formatted_response 132 | # 强制刷新缓冲区,确保数据立即发送 133 | await asyncio.sleep(0) 134 | # 添加额外的刷新,确保数据立即发送到客户端 135 | await asyncio.sleep(0.01) 136 | except Exception as e: 137 | print(f"\n[处理行时出错] 错误类型: {type(e).__name__}, 错误信息: {e}\n[问题数据] {line!r}") 138 | continue 139 | 140 | print("\n===== API响应数据接收完毕 =====\n") 141 | if buffer: 142 | print(f"[剩余未处理的缓冲区数据] {buffer!r}") 143 | 144 | # 使用headers参数明确设置Content-Type,确保不包含charset=utf-8 145 | return StreamingResponse( 146 | generate(), 147 | media_type="text/event-stream", 148 | headers={ 149 | "Content-Type": "text/event-stream", 150 | "Cache-Control": "no-cache", 151 | "Connection": "keep-alive" 152 | } 153 | ) 154 | 155 | 156 | async def handle_non_stream_response(response): 157 | """处理非流式响应 158 | 159 | Args: 160 | response: API响应对象 161 | 162 | Returns: 163 | 提取的完整内容 164 | """ 165 | full_content = "" 166 | buffer = "" 167 | print("\n===== 开始接收非流式API响应数据 =====\n") 168 | 169 | # 立即处理每个数据块,不等待整个响应完成 170 | async for chunk in response.aiter_text(): 171 | print(f"\n[接收非流式数据块] 原始数据: {chunk!r}\n") 172 | 173 | # 立即处理接收到的数据块 174 | buffer += chunk 175 | 176 | # 处理缓冲区中的每一行 177 | while "\n" in buffer: 178 | line, buffer = buffer.split("\n", 1) 179 | 180 | # 处理完整的SSE行 181 | if line.startswith("data: "): 182 | print(f"\n[非流式SSE行] {line}") 183 | 184 | if line == "data: [DONE]": 185 | print("\n===== 接收到非流式完成标记 [DONE] =====\n") 186 | # 强制刷新缓冲区 187 | await asyncio.sleep(0) 188 | continue 189 | 190 | try: 191 | # 提取SSE行中的JSON数据 192 | import re 193 | json_str = re.sub(r'^data:\s*', '', line) # 使用正则去掉data:前缀 194 | 195 | # 检查是否存在双重data:前缀 196 | if json_str.startswith("data:"): 197 | print(f"[检测到双重data:前缀] 原始: {json_str}") 198 | json_str = re.sub(r'^data:\s*', '', json_str) # 再次使用正则去掉data:前缀 199 | print(f"[处理后原始] {json_str}") 200 | 201 | # 如果是[DONE]标记则跳过 202 | if json_str.strip() == "[DONE]": 203 | continue 204 | 205 | # 修复JSON格式:确保是有效的JSON字符串 206 | # 检查是否缺少开头的花括号 207 | if not json_str.strip().startswith("{"): 208 | # 构建完整的JSON对象 209 | json_str = '{' + json_str 210 | 211 | # 检查是否缺少结尾的花括号 212 | if not json_str.strip().endswith("}"): 213 | json_str = json_str + "}" 214 | 215 | print(f"[处理后最终] {json_str}") 216 | 217 | # 确保JSON字符串是有效的,处理可能的格式问题 218 | json_str = json_str.strip() 219 | if json_str.endswith("]"): # 处理特殊情况如 "DONE]" 220 | if json_str == "DONE]": 221 | print("[跳过无效JSON] DONE]") 222 | continue 223 | 224 | # 处理不完整的JSON响应 225 | if json_str.startswith("{") and not json_str.endswith("}"): 226 | # 尝试补全JSON 227 | json_str = json_str + "}" 228 | print(f"[修复不完整JSON] 补全后: {json_str}") 229 | 230 | try: 231 | # 尝试解析JSON 232 | data = json.loads(json_str) 233 | if "choices" in data and len(data["choices"]) > 0: 234 | delta = data["choices"][0].get("delta", {}) 235 | content = delta.get("content", "") 236 | full_content += content 237 | # 强制刷新缓冲区 238 | await asyncio.sleep(0) 239 | except json.JSONDecodeError as json_err: 240 | print(f"[JSON解析错误] {json_err} - 尝试修复并重新解析") 241 | try: 242 | # 尝试修复常见的JSON格式问题 243 | # 移除可能导致问题的字符 244 | json_str = json_str.strip() 245 | # 如果字符串不是以{开头,尝试找到第一个{并从那里开始 246 | if not json_str.startswith("{"): 247 | start_idx = json_str.find("{") 248 | if start_idx >= 0: 249 | json_str = json_str[start_idx:] 250 | 251 | # 尝试再次解析 252 | data = json.loads(json_str) 253 | if "choices" in data and len(data["choices"]) > 0: 254 | delta = data["choices"][0].get("delta", {}) 255 | content = delta.get("content", "") 256 | full_content += content 257 | # 强制刷新缓冲区 258 | await asyncio.sleep(0) 259 | except json.JSONDecodeError: 260 | print(f"[JSON修复失败] 无法解析JSON: {json_str} - 跳过此行") 261 | continue 262 | except Exception as e: 263 | print(f"\n[处理非流式行时出错] 错误类型: {type(e).__name__}, 错误信息: {e}\n[问题数据] {line!r}") 264 | continue 265 | 266 | print("\n===== 非流式API响应数据接收完毕 =====\n") 267 | if buffer: 268 | print(f"[剩余未处理的缓冲区数据] {buffer!r}") 269 | # 如果没有成功提取内容,尝试直接解析响应 270 | if not full_content: 271 | try: 272 | data = response.json() 273 | full_content = data.get("content", "") 274 | except json.JSONDecodeError: 275 | raise HTTPException( 276 | status_code=502, 277 | detail=f"Invalid JSON response from API: {response.text[:200]}" 278 | ) 279 | 280 | return full_content 281 | -------------------------------------------------------------------------------- /auth.py: -------------------------------------------------------------------------------- 1 | # auth.py 2 | # 身份验证中间件,处理API请求的认证 3 | 4 | from fastapi import Request, HTTPException 5 | from functools import wraps 6 | from config import AuthConfig 7 | 8 | def verify_token(request: Request) -> bool: 9 | """验证请求中的Bearer Token 10 | 11 | Args: 12 | request: FastAPI请求对象 13 | 14 | Returns: 15 | bool: 令牌是否有效 16 | """ 17 | auth_header = request.headers.get('Authorization') 18 | return AuthConfig.validate_token(auth_header) 19 | 20 | def require_auth(func): 21 | """要求认证的装饰器 22 | 23 | 用法: 24 | @require_auth 25 | async def protected_route(): 26 | ... 27 | """ 28 | @wraps(func) 29 | async def wrapper(*args, **kwargs): 30 | request = kwargs.get('request') 31 | if not request: 32 | for arg in args: 33 | if isinstance(arg, Request): 34 | request = arg 35 | break 36 | 37 | if not request: 38 | raise HTTPException(status_code=500, detail="Internal server error") 39 | 40 | if not verify_token(request): 41 | raise HTTPException( 42 | status_code=401, 43 | detail="Invalid or missing authentication token", 44 | headers={"WWW-Authenticate": "Bearer"} 45 | ) 46 | 47 | return await func(*args, **kwargs) 48 | 49 | return wrapper -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | # config.py 2 | # 配置模块,管理环境变量和配置项 3 | 4 | import os 5 | from typing import Optional 6 | 7 | # API认证配置 8 | class AuthConfig: 9 | # 从环境变量获取API令牌,如果未设置则使用默认值 10 | API_TOKEN: str = os.getenv('API_TOKEN', 'sk-114514') 11 | 12 | @classmethod 13 | def get_token(cls) -> str: 14 | """获取API认证令牌 15 | 16 | Returns: 17 | str: API认证令牌 18 | """ 19 | return cls.API_TOKEN 20 | 21 | @classmethod 22 | def validate_token(cls, token: Optional[str]) -> bool: 23 | """验证API令牌是否有效 24 | 25 | Args: 26 | token: 待验证的令牌 27 | 28 | Returns: 29 | bool: 令牌是否有效 30 | """ 31 | if not token: 32 | return False 33 | 34 | # 移除Bearer前缀并验证 35 | if token.startswith('Bearer '): 36 | token = token[7:] 37 | 38 | return token == cls.API_TOKEN -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | api: 5 | build: 6 | context: . 7 | dockerfile: Dockerfile 8 | ports: 9 | - "3014:8080" 10 | restart: unless-stopped 11 | environment: 12 | - TZ=Asia/Shanghai 13 | - API_TOKEN=${API_TOKEN:sk-114514} 14 | volumes: 15 | - ./:/app 16 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | # main.py 2 | # 主应用入口 3 | 4 | from fastapi import FastAPI 5 | from routes import router 6 | 7 | # 创建FastAPI应用 8 | app = FastAPI() 9 | 10 | # 注册路由 11 | app.include_router(router) 12 | 13 | # 添加健康检查路由 14 | @app.get("/health") 15 | async def health_check(): 16 | return {"status": "ok"} 17 | 18 | if __name__ == "__main__": 19 | import uvicorn 20 | uvicorn.run(app, host="0.0.0.0", port=8080) -------------------------------------------------------------------------------- /models.py: -------------------------------------------------------------------------------- 1 | # models.py 2 | # 模型映射和管理模块 3 | 4 | # 模型映射 5 | MODEL_MAPPING = { 6 | "deepseek-r1-70b": "deepseek70b", 7 | "deepseek-r1-turbo": "deepseekr1turbo", 8 | "deepseek-r1-turbo-2": "deepseekr1turbo2", 9 | "deepseek-r1-turbo-3": "deepseekr1turbo3", 10 | "deepseek-ai/DeepSeek-R1-Turbo": "deepseekr1turbo", 11 | "deepseek-ai/DeepSeek-V3-Turbo": "deepseekv3turbo", 12 | "deepseek-v3-turbo": "deepseekv3turbo", 13 | "deepseek-v3-turbo2": "deepseekv3turbo2", 14 | "deepseek-v3-0324-3": "deepseekv303243", 15 | "deepseek-v3-0324-2": "deepseekv303242", 16 | "deepseek-v3-0324": "deepseekv30324", 17 | "deepseek-r1-search": "volcengine", 18 | "grok-3": "grok3", 19 | "grok-3-search": "grok3search", 20 | "grok-3-deepsearch": "grok3deepsearch", 21 | "grok-3-reasoning": "grok3reasoning", 22 | "qwen-32b": "qwen32b", 23 | "qwq-32b": "qwq32b" 24 | } 25 | 26 | # 可用模型列表,兼容OpenAI格式 27 | AVAILABLE_MODELS = [ 28 | { 29 | "id": model_id, 30 | "object": "model", 31 | "created": 1677650000 + idx, # 使用递增时间戳 32 | "owned_by": "deepseek" if "deepseek" in model_id.lower() else 33 | "xai" if "grok" in model_id.lower() else 34 | "alibaba", 35 | "permission": [], 36 | "root": MODEL_MAPPING[model_id], 37 | "parent": None 38 | } 39 | for idx, model_id in enumerate(MODEL_MAPPING.keys()) 40 | ] 41 | 42 | # 默认模型 43 | DEFAULT_MODEL = "deepseek70b" 44 | 45 | 46 | def get_model_list(): 47 | """获取所有可用模型列表,兼容OpenAI格式""" 48 | return { 49 | "object": "list", 50 | "data": AVAILABLE_MODELS 51 | } 52 | 53 | 54 | def map_model_name(model_name): 55 | """将请求中的模型名称映射到内部使用的模型名称 56 | 57 | Args: 58 | model_name: 请求中指定的模型名称 59 | 60 | Returns: 61 | 映射后的内部模型名称 62 | """ 63 | if not model_name: 64 | return model_name 65 | 66 | return MODEL_MAPPING.get(model_name, model_name) -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # 项目依赖库 2 | # 使用命令安装: pip install -r requirements.txt 3 | fastapi>=0.95.0 4 | uvicorn>=0.22.0 5 | httpx>=0.24.0 6 | random-user-agent>=1.0.1 7 | python-multipart>=0.0.6 8 | uuid>=1.30 -------------------------------------------------------------------------------- /response_formatter.py: -------------------------------------------------------------------------------- 1 | # response_formatter.py 2 | # 响应格式化模块,处理OpenAI格式的响应 3 | 4 | import time 5 | import uuid 6 | 7 | 8 | def format_openai_response(content, model="deepseek70b"): 9 | """将内容格式化为OpenAI兼容的响应格式 10 | 11 | Args: 12 | content: 响应内容 13 | model: 使用的模型名称 14 | 15 | Returns: 16 | OpenAI格式的响应对象 17 | """ 18 | # 生成唯一的响应ID 19 | response_id = f"chatcmpl-{uuid.uuid4().hex[:10]}" 20 | 21 | # 获取当前时间戳 22 | created_timestamp = int(time.time()) 23 | 24 | return { 25 | "id": response_id, 26 | "object": "chat.completion", 27 | "created": created_timestamp, 28 | "model": model, # 添加模型信息 29 | "choices": [{ 30 | "index": 0, 31 | "message": { 32 | "role": "assistant", 33 | "content": content 34 | }, 35 | "finish_reason": "stop" 36 | }], 37 | "usage": { 38 | "prompt_tokens": 0, 39 | "completion_tokens": 0, 40 | "total_tokens": 0 41 | } 42 | } 43 | 44 | 45 | def format_openai_stream_chunk(content, model="deepseek70b", is_first_chunk=False, is_last_chunk=False): 46 | """格式化流式响应的单个数据块为OpenAI兼容格式 47 | 48 | Args: 49 | content: 当前数据块的内容 50 | model: 使用的模型名称 51 | is_first_chunk: 是否为第一个数据块 52 | is_last_chunk: 是否为最后一个数据块 53 | 54 | Returns: 55 | OpenAI格式的流式响应数据块 56 | """ 57 | # 生成唯一的响应ID(对于同一流式响应,ID应保持一致) 58 | # 在实际应用中,应该在外部生成并传入 59 | response_id = f"chatcmpl-{uuid.uuid4().hex[:10]}" 60 | 61 | # 获取当前时间戳 62 | created_timestamp = int(time.time()) 63 | 64 | response = { 65 | "id": response_id, 66 | "object": "chat.completion.chunk", 67 | "created": created_timestamp, 68 | "model": model, 69 | "choices": [{ 70 | "index": 0, 71 | "delta": {}, 72 | "finish_reason": None 73 | }] 74 | } 75 | 76 | # 第一个数据块需要包含角色信息 77 | if is_first_chunk: 78 | response["choices"][0]["delta"] = { 79 | "role": "assistant", 80 | "content": content 81 | } 82 | # 最后一个数据块需要包含完成原因 83 | elif is_last_chunk: 84 | response["choices"][0]["delta"] = {"content": content} 85 | response["choices"][0]["finish_reason"] = "stop" 86 | # 中间数据块只包含内容 87 | else: 88 | response["choices"][0]["delta"] = {"content": content} 89 | 90 | return response -------------------------------------------------------------------------------- /routes.py: -------------------------------------------------------------------------------- 1 | # routes.py 2 | # 路由模块,处理所有API路由 3 | 4 | from fastapi import APIRouter, Request, HTTPException 5 | from fastapi.responses import JSONResponse, StreamingResponse 6 | from auth import require_auth 7 | 8 | from models import get_model_list, map_model_name 9 | from api_client import call_api 10 | from response_formatter import format_openai_response 11 | 12 | # 创建路由器 13 | router = APIRouter() 14 | 15 | 16 | @router.get("/v1/models") 17 | async def list_models(): 18 | """获取可用模型列表,兼容OpenAI格式""" 19 | return JSONResponse(content=get_model_list()) 20 | 21 | 22 | @router.post("/v1/chat/completions") 23 | @require_auth 24 | async def chat_completions(request: Request): 25 | """处理聊天完成请求,兼容OpenAI格式""" 26 | try: 27 | # 获取请求体 28 | body = await request.json() 29 | 30 | # 获取并映射模型名称 31 | model_name = body.get("model") 32 | mapped_model = map_model_name(model_name) 33 | 34 | # 构造转发请求体 35 | payload = { 36 | "messages": body.get("messages", []), 37 | "model": mapped_model 38 | } 39 | 40 | # 检查是否是流式请求 41 | is_stream = body.get("stream", False) 42 | 43 | # 调用API 44 | if is_stream: 45 | # 流式响应直接返回 46 | return await call_api(payload, is_stream=True) 47 | else: 48 | # 非流式响应需要格式化 49 | content = await call_api(payload, is_stream=False) 50 | return format_openai_response(content, model=mapped_model) 51 | 52 | except Exception as e: 53 | raise HTTPException(status_code=500, detail=str(e)) --------------------------------------------------------------------------------