dan92 commited on
Commit
c86f151
·
verified ·
1 Parent(s): 601a060

Upload 10 files

Browse files
Dockerfile CHANGED
@@ -1,8 +1,13 @@
1
- FROM hpyp/bbapi:latest
2
 
3
- # 设置环境变量
4
- ENV NAMESERVER_1=8.8.8.8
5
- ENV NAMESERVER_2=8.8.4.4
 
 
 
 
 
6
 
7
  EXPOSE 8001
8
 
 
1
+ FROM python:3.10-slim
2
 
3
+ WORKDIR /app
4
+
5
+ # 安装依赖
6
+ COPY requirements.txt .
7
+ RUN pip install --no-cache-dir -r requirements.txt
8
+
9
+ # 复制应用代码
10
+ COPY app/ .
11
 
12
  EXPOSE 8001
13
 
app/api/auth.py ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ import os
2
+ from fastapi.security import HTTPBearer
3
+
4
+ security = HTTPBearer()
5
+ APP_SECRET = os.getenv("APP_SECRET", "sk-zhoudan20241029")
app/api/config.py ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from pydantic_settings import BaseSettings
3
+ from dotenv import load_dotenv
4
+ from typing import Dict, List
5
+
6
+ load_dotenv()
7
+
8
+ class Settings(BaseSettings):
9
+ # Server settings
10
+ HOST: str = os.getenv("HOST", "0.0.0.0")
11
+ PORT: int = int(os.getenv("PORT", "8001"))
12
+ DEBUG: bool = os.getenv("DEBUG", "False").lower() == "true"
13
+ WORKERS: int = int(os.getenv("WORKERS", "1"))
14
+ LOG_LEVEL: str = os.getenv("LOG_LEVEL", "INFO")
15
+
16
+ # API settings
17
+ BASE_URL: str = "https://www.blackbox.ai"
18
+ APP_SECRET: str = os.getenv("APP_SECRET", "")
19
+ REQUEST_TIMEOUT: int = int(os.getenv("REQUEST_TIMEOUT", "30"))
20
+
21
+ # Headers
22
+ HEADERS: Dict[str, str] = {
23
+ 'accept': '*/*',
24
+ 'accept-language': 'zh-CN,zh;q=0.9',
25
+ 'content-type': 'application/json',
26
+ 'origin': 'https://www.blackbox.ai',
27
+ 'priority': 'u=1, i',
28
+ 'sec-ch-ua': '"Chromium";v="130", "Google Chrome";v="130", "Not?A_Brand";v="99"',
29
+ 'sec-ch-ua-mobile': '?0',
30
+ 'sec-ch-ua-platform': '"Windows"',
31
+ 'sec-fetch-dest': 'empty',
32
+ 'sec-fetch-mode': 'cors',
33
+ 'sec-fetch-site': 'same-origin',
34
+ 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36',
35
+ }
36
+
37
+ ALLOWED_MODELS: List[Dict[str, str]] = [
38
+ {"id": "gpt-4o", "name": "gpt-4o"},
39
+ {"id": "gemini-1.5-pro", "name": "gemini-pro"},
40
+ {"id": "claude-3-5-sonnet", "name": "claude-sonnet-3.5"},
41
+ {"id": "blackboxai", "name": "blackboxai"},
42
+ {"id": "blackboxai-pro", "name": "blackboxai-pro"},
43
+ {"id": "blackboxai-search", "name": "blackboxai-search"},
44
+ {"id": "meta-llama/Llama-3.3-70B-Instruct-Turbo", "name": "meta-llama/Llama-3.3-70B-Instruct-Turbo"},
45
+ {"id": "meta-llama/Meta-Llama-3.1-405B-Instruct-Lite-Pro", "name": "meta-llama/Meta-Llama-3.1-405B-Instruct-Lite-Pro"},
46
+ {"id": "Qwen/QwQ-32B-Preview", "name": "Qwen/QwQ-32B-Preview"},
47
+ ]
48
+
49
+ MODEL_MAPPING: Dict[str, str] = {
50
+ "gpt-4o": "gpt-4o",
51
+ "gemini-1.5-pro": "gemini-pro",
52
+ "claude-3-5-sonnet": "claude-sonnet-3.5",
53
+ "blackboxai": "blackboxai",
54
+ "blackboxai-pro": "blackboxai-pro",
55
+ "blackboxai-search": "blackboxai-search",
56
+ "meta-llama/Llama-3.3-70B-Instruct-Turbo": "meta-llama/Llama-3.3-70B-Instruct-Turbo",
57
+ "meta-llama/Meta-Llama-3.1-405B-Instruct-Lite-Pro": "meta-llama/Meta-Llama-3.1-405B-Instruct-Lite-Pro",
58
+ "Qwen/QwQ-32B-Preview": "Qwen/QwQ-32B-Preview",
59
+ }
60
+
61
+ class Config:
62
+ env_file = ".env"
63
+ case_sensitive = True
64
+
65
+ _settings = None
66
+
67
+ def get_settings() -> Settings:
68
+ global _settings
69
+ if _settings is None:
70
+ _settings = Settings()
71
+ return _settings
app/api/logger.py ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ from logging.handlers import RotatingFileHandler
3
+ import os
4
+
5
+ def setup_logger(name):
6
+ logger = logging.getLogger(name)
7
+
8
+ if not logger.handlers:
9
+ logger.setLevel(logging.INFO)
10
+
11
+ # 创建控制台处理器
12
+ console_handler = logging.StreamHandler()
13
+ console_handler.setLevel(logging.INFO)
14
+
15
+ # 设置日志格式
16
+ formatter = logging.Formatter(
17
+ '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
18
+ )
19
+ console_handler.setFormatter(formatter)
20
+
21
+ # 添加处理器到logger
22
+ logger.addHandler(console_handler)
23
+
24
+ return logger
app/api/models.py ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import List, Optional, Union
2
+ from pydantic import BaseModel, Field
3
+
4
+ class Message(BaseModel):
5
+ role: str
6
+ content: Union[str, List]
7
+
8
+ class ChatRequest(BaseModel):
9
+ model: str
10
+ messages: List[Message]
11
+ stream: bool = False
12
+ temperature: float = Field(default=0.7, ge=0, le=2)
13
+ top_p: float = Field(default=0.95, ge=0, le=1)
14
+ max_tokens: int = Field(default=8192, ge=1)
app/api/routes.py ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter, Depends
2
+ from api.models import ChatRequest
3
+ from api.utils import process_streaming_response, process_non_streaming_response
4
+ from api.logger import setup_logger
5
+
6
+ logger = setup_logger(__name__)
7
+ router = APIRouter()
8
+
9
+ @router.post("/chat/completions")
10
+ async def chat_completions(request: ChatRequest):
11
+ """
12
+ 处理聊天完成请求
13
+ """
14
+ logger.info("Entering chat_completions route")
15
+ logger.info(f"Received request: {request}")
16
+
17
+ if request.stream:
18
+ return process_streaming_response(request)
19
+ else:
20
+ return await process_non_streaming_response(request)
app/api/utils.py ADDED
@@ -0,0 +1,200 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from datetime import datetime
2
+ from http.client import HTTPException
3
+ import json
4
+ from typing import Any, Dict, Optional
5
+ import uuid
6
+
7
+ import httpx
8
+ from api import validate
9
+ from api.auth import APP_SECRET
10
+ from api.config import get_settings
11
+ from fastapi import Depends, security
12
+ from fastapi.security import HTTPAuthorizationCredentials
13
+
14
+ from api.models import ChatRequest
15
+
16
+ from api.logger import setup_logger
17
+
18
+ logger = setup_logger(__name__)
19
+
20
+ settings = get_settings()
21
+ BASE_URL = settings.BASE_URL
22
+ MODEL_MAPPING = settings.MODEL_MAPPING
23
+
24
+ def create_chat_completion_data(
25
+ content: str, model: str, timestamp: int, finish_reason: Optional[str] = None
26
+ ) -> Dict[str, Any]:
27
+ return {
28
+ "id": f"chatcmpl-{uuid.uuid4()}",
29
+ "object": "chat.completion.chunk",
30
+ "created": timestamp,
31
+ "model": model,
32
+ "choices": [
33
+ {
34
+ "index": 0,
35
+ "delta": {"content": content, "role": "assistant"},
36
+ "finish_reason": finish_reason,
37
+ }
38
+ ],
39
+ "usage": None,
40
+ }
41
+
42
+ def verify_app_secret(credentials: HTTPAuthorizationCredentials = Depends(security)):
43
+ if credentials.credentials != APP_SECRET:
44
+ raise HTTPException(status_code=403, detail="Invalid APP_SECRET")
45
+ return credentials.credentials
46
+
47
+ def message_to_dict(message):
48
+ if isinstance(message.content, str):
49
+ return {"role": message.role, "content": message.content}
50
+ elif isinstance(message.content, list) and len(message.content) == 2:
51
+ return {
52
+ "role": message.role,
53
+ "content": message.content[0]["text"],
54
+ "data": {
55
+ "imageBase64": message.content[1]["image_url"]["url"],
56
+ "fileText": "",
57
+ "title": "snapshoot",
58
+ },
59
+ }
60
+ else:
61
+ return {"role": message.role, "content": message.content}
62
+
63
+ async def process_streaming_response(request: ChatRequest):
64
+ search_results = [] # 初始化搜索结果数组
65
+
66
+ json_data = {
67
+ "messages": [message_to_dict(msg) for msg in request.messages],
68
+ "previewToken": None,
69
+ "userId": None,
70
+ "codeModelMode": True,
71
+ "agentMode": {},
72
+ "trendingAgentMode": {},
73
+ "isMicMode": False,
74
+ "userSystemPrompt": None,
75
+ "maxTokens": request.max_tokens,
76
+ "playgroundTopP": request.top_p,
77
+ "playgroundTemperature": request.temperature,
78
+ "isChromeExt": False,
79
+ "githubToken": None,
80
+ "clickedAnswer2": False,
81
+ "clickedAnswer3": False,
82
+ "clickedForceWebSearch": False,
83
+ "visitFromDelta": False,
84
+ "mobileClient": False,
85
+ "userSelectedModel": MODEL_MAPPING.get(request.model),
86
+ "validated": validate.getVid(),
87
+ "webSearchModePrompt": True if request.model.endswith("-search") else False
88
+ }
89
+
90
+ async with httpx.AsyncClient(verify=False) as client:
91
+ try:
92
+ async with client.stream(
93
+ "POST",
94
+ f"{BASE_URL}/api/chat",
95
+ headers=settings.HEADERS,
96
+ json=json_data,
97
+ timeout=100,
98
+ ) as response:
99
+ response.raise_for_status()
100
+ timestamp = int(datetime.now().timestamp())
101
+ async for line in response.aiter_lines():
102
+ if line:
103
+ if line.startswith("$~~~$"): # 处理搜索结果
104
+ try:
105
+ json_str = line[5:-5] # 提取中间的JSON字符串
106
+ results = json.loads(json_str)
107
+ search_results = results # 保存搜索结果
108
+ continue
109
+ except json.JSONDecodeError:
110
+ logger.error("Failed to parse search results")
111
+ continue
112
+
113
+ if line == "**":
114
+ continue
115
+ content = line + "\n"
116
+ print(content)
117
+ if "https://www.blackbox.ai" in content:
118
+ validate.getVid(True)
119
+ content = "vid已刷新,重新对话即可\n"
120
+ yield f"data: {json.dumps(create_chat_completion_data(content, request.model, timestamp))}\n\n"
121
+ break
122
+ if content.startswith("$@$v=undefined-rv1$@$"):
123
+ yield f"data: {json.dumps(create_chat_completion_data(content[21:], request.model, timestamp))}\n\n"
124
+ else:
125
+ yield f"data: {json.dumps(create_chat_completion_data(content, request.model, timestamp))}\n\n"
126
+ elif line == "":
127
+ content = line + "\n\n"
128
+ print(content)
129
+ if "https://www.blackbox.ai" in content:
130
+ validate.getVid(True)
131
+ content = "vid已刷新,重新对话即可\n"
132
+ yield f"data: {json.dumps(create_chat_completion_data(content, request.model, timestamp))}\n\n"
133
+ break
134
+ if content.startswith("$@$v=undefined-rv1$@$"):
135
+ yield f"data: {json.dumps(create_chat_completion_data(content[21:], request.model, timestamp))}\n\n"
136
+ else:
137
+ yield f"data: {json.dumps(create_chat_completion_data(content, request.model, timestamp))}\n\n"
138
+ if search_results: # 在结束前输出引用来源
139
+ sources = "\n\n**引用来源**\n" + "\n".join([f"- [{r['title']}]({r['link']})" for r in search_results])
140
+ yield f"data: {json.dumps(create_chat_completion_data(sources, request.model, timestamp))}\n\n"
141
+
142
+ yield f"data: {json.dumps(create_chat_completion_data('', request.model, timestamp, 'stop'))}\n\n"
143
+ yield "data: [DONE]\n\n"
144
+ except httpx.HTTPStatusError as e:
145
+ logger.error(f"HTTP error occurred: {e}")
146
+ raise HTTPException(status_code=e.response.status_code, detail=str(e))
147
+ except httpx.RequestError as e:
148
+ logger.error(f"Error occurred during request: {e}")
149
+ raise HTTPException(status_code=500, detail=str(e))
150
+
151
+ async def process_non_streaming_response(request: ChatRequest):
152
+ json_data = {
153
+ "messages": [message_to_dict(msg) for msg in request.messages],
154
+ "previewToken": None,
155
+ "userId": None,
156
+ "codeModelMode": True,
157
+ "agentMode": {},
158
+ "trendingAgentMode": {},
159
+ "isMicMode": False,
160
+ "userSystemPrompt": None,
161
+ "maxTokens": request.max_tokens,
162
+ "playgroundTopP": request.top_p,
163
+ "playgroundTemperature": request.temperature,
164
+ "isChromeExt": False,
165
+ "githubToken": None,
166
+ "clickedAnswer2": False,
167
+ "clickedAnswer3": False,
168
+ "clickedForceWebSearch": False,
169
+ "visitFromDelta": False,
170
+ "mobileClient": False,
171
+ "userSelectedModel": MODEL_MAPPING.get(request.model),
172
+ "validated": validate.getVid(),
173
+ "webSearchModePrompt": True if request.model.endswith("-search") else False
174
+ }
175
+ full_response = ""
176
+ async with httpx.AsyncClient(verify=False) as client:
177
+ async with client.stream(
178
+ method="POST", url=f"{BASE_URL}/api/chat", headers=settings.HEADERS, json=json_data
179
+ ) as response:
180
+ async for chunk in response.aiter_text():
181
+ full_response += chunk
182
+ if "https://www.blackbox.ai" in full_response:
183
+ validate.getVid(True)
184
+ full_response = "vid已刷新,重新对话即可"
185
+ if full_response.startswith("$@$v=undefined-rv1$@$"):
186
+ full_response = full_response[21:]
187
+ return {
188
+ "id": f"chatcmpl-{uuid.uuid4()}",
189
+ "object": "chat.completion",
190
+ "created": int(datetime.now().timestamp()),
191
+ "model": request.model,
192
+ "choices": [
193
+ {
194
+ "index": 0,
195
+ "message": {"role": "assistant", "content": full_response},
196
+ "finish_reason": "stop",
197
+ }
198
+ ],
199
+ "usage": None,
200
+ }
app/api/validate.py ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import time
2
+ import random
3
+ import string
4
+
5
+ _vid = None
6
+ _last_update = 0
7
+
8
+ def generate_vid():
9
+ """生成随机vid"""
10
+ length = 32
11
+ characters = string.ascii_letters + string.digits
12
+ return ''.join(random.choice(characters) for _ in range(length))
13
+
14
+ def getVid(force_refresh=False):
15
+ """获取或刷新vid"""
16
+ global _vid, _last_update
17
+ current_time = time.time()
18
+
19
+ # 如果强制刷新或者vid不存在或者已过期(1小时)
20
+ if force_refresh or _vid is None or (current_time - _last_update) > 3600:
21
+ _vid = generate_vid()
22
+ _last_update = current_time
23
+
24
+ return _vid
app/main.py ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI
2
+ from fastapi.middleware.cors import CORSMiddleware
3
+ from api.routes import router
4
+
5
+ app = FastAPI()
6
+
7
+ # 配置CORS
8
+ app.add_middleware(
9
+ CORSMiddleware,
10
+ allow_origins=["*"],
11
+ allow_credentials=True,
12
+ allow_methods=["*"],
13
+ allow_headers=["*"],
14
+ )
15
+
16
+ # 添加路由
17
+ app.include_router(router, prefix="/api/v1")
requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ fastapi==0.109.0
2
+ uvicorn==0.27.0
3
+ httpx==0.26.0
4
+ python-dotenv==1.0.0
5
+ pydantic-settings==2.1.0
6
+ python-multipart==0.0.6