|
import json |
|
import os |
|
import random |
|
import time |
|
import uuid |
|
import asyncio |
|
from starlette.responses import Response, StreamingResponse |
|
from fastapi import FastAPI, Request |
|
from fastapi.middleware.cors import CORSMiddleware |
|
|
|
|
|
CONFIG = { |
|
"API": { |
|
"BASE_URL": "https://fragments.e2b.dev", |
|
"API_KEY": os.getenv("API_KEY","sk-123456") |
|
}, |
|
"RETRY": { |
|
"MAX_ATTEMPTS": 1, |
|
"DELAY_BASE": 1000 |
|
}, |
|
"MODEL_CONFIG": { |
|
"o1": { |
|
"id": "o1", |
|
"provider": "OpenAI", |
|
"providerId": "openai", |
|
"name": "o1", |
|
"multiModal": True, |
|
"Systemprompt": "", |
|
"opt_max": { |
|
"temperatureMax": 2, |
|
"max_tokensMax": 0, |
|
"presence_penaltyMax": 2, |
|
"frequency_penaltyMax": 2, |
|
"top_pMax": 1, |
|
"top_kMax": 500 |
|
} |
|
}, |
|
"o1-mini": { |
|
"id": "o1", |
|
"provider": "OpenAI", |
|
"providerId": "openai", |
|
"name": "o1-mini", |
|
"multiModal": False, |
|
"Systemprompt": "", |
|
"opt_max": { |
|
"temperatureMax": 2, |
|
"max_tokensMax": 0, |
|
"presence_penaltyMax": 2, |
|
"frequency_penaltyMax": 2, |
|
"top_pMax": 1, |
|
"top_kMax": 500 |
|
} |
|
}, |
|
"o3-mini": { |
|
"id": "o3-mini", |
|
"provider": "OpenAI", |
|
"providerId": "openai", |
|
"name": "o3 mini", |
|
"multiModal": False, |
|
"Systemprompt": "", |
|
"opt_max": { |
|
"temperatureMax": 2, |
|
"max_tokensMax": 0, |
|
"presence_penaltyMax": 2, |
|
"frequency_penaltyMax": 2, |
|
"top_pMax": 1, |
|
"top_kMax": 500 |
|
} |
|
}, |
|
"gpt-4.5-preview": { |
|
"id": "gpt-4.5-preview", |
|
"provider": "OpenAI", |
|
"providerId": "openai", |
|
"name": "GPT-4.5", |
|
"multiModal": True, |
|
"Systemprompt": "", |
|
"opt_max": { |
|
"temperatureMax": 2, |
|
"max_tokensMax": 0, |
|
"presence_penaltyMax": 2, |
|
"frequency_penaltyMax": 2, |
|
"top_pMax": 1, |
|
"top_kMax": 500 |
|
} |
|
}, |
|
"gpt-4o": { |
|
"id": "gpt-4o", |
|
"provider": "OpenAI", |
|
"providerId": "openai", |
|
"name": "GPT-4o", |
|
"multiModal": True, |
|
"Systemprompt": "", |
|
"opt_max": { |
|
"temperatureMax": 2, |
|
"max_tokensMax": 16380, |
|
"presence_penaltyMax": 2, |
|
"frequency_penaltyMax": 2, |
|
"top_pMax": 1, |
|
"top_kMax": 500 |
|
} |
|
}, |
|
"gpt-4-turbo": { |
|
"id": "gpt-4-turbo", |
|
"provider": "OpenAI", |
|
"providerId": "openai", |
|
"name": "GPT-4 Turbo", |
|
"multiModal": True, |
|
"Systemprompt": "", |
|
"opt_max": { |
|
"temperatureMax": 2, |
|
"max_tokensMax": 16380, |
|
"presence_penaltyMax": 2, |
|
"frequency_penaltyMax": 2, |
|
"top_pMax": 1, |
|
"top_kMax": 500 |
|
} |
|
}, |
|
"gemini-1.5-pro": { |
|
"id": "gemini-1.5-pro-002", |
|
"provider": "Google Vertex AI", |
|
"providerId": "vertex", |
|
"name": "Gemini 1.5 Pro", |
|
"multiModal": True, |
|
"Systemprompt": "", |
|
"opt_max": { |
|
"temperatureMax": 2, |
|
"max_tokensMax": 8192, |
|
"presence_penaltyMax": 2, |
|
"frequency_penaltyMax": 2, |
|
"top_pMax": 1, |
|
"top_kMax": 500 |
|
} |
|
}, |
|
"gemini-exp-1121": { |
|
"id": "gemini-exp-1121", |
|
"provider": "Google Generative AI", |
|
"providerId": "google", |
|
"name": "Gemini Experimental 1121", |
|
"multiModal": True, |
|
"Systemprompt": "", |
|
"opt_max": { |
|
"temperatureMax": 2, |
|
"max_tokensMax": 8192, |
|
"presence_penaltyMax": 2, |
|
"frequency_penaltyMax": 2, |
|
"top_pMax": 1, |
|
"top_kMax": 40 |
|
} |
|
}, |
|
"gemini-2.0-flash-exp": { |
|
"id": "models/gemini-2.0-flash-exp", |
|
"provider": "Google Generative AI", |
|
"providerId": "google", |
|
"name": "Gemini 2.0 Flash", |
|
"multiModal": True, |
|
"Systemprompt": "", |
|
"opt_max": { |
|
"temperatureMax": 2, |
|
"max_tokensMax": 8192, |
|
"presence_penaltyMax": 2, |
|
"frequency_penaltyMax": 2, |
|
"top_pMax": 1, |
|
"top_kMax": 40 |
|
} |
|
}, |
|
"claude-3-7-sonnet-latest": { |
|
"id": "claude-3-5-sonnet-latest", |
|
"provider": "Anthropic", |
|
"providerId": "anthropic", |
|
"name": "Claude 3.7 Sonnet", |
|
"multiModal": True, |
|
"Systemprompt": "", |
|
"opt_max": { |
|
"temperatureMax": 1, |
|
"max_tokensMax": 8192, |
|
"presence_penaltyMax": 2, |
|
"frequency_penaltyMax": 2, |
|
"top_pMax": 1, |
|
"top_kMax": 500 |
|
} |
|
}, |
|
"claude-3-5-sonnet-latest": { |
|
"id": "claude-3-5-sonnet-latest", |
|
"provider": "Anthropic", |
|
"providerId": "anthropic", |
|
"name": "Claude 3.5 Sonnet", |
|
"multiModal": True, |
|
"Systemprompt": "", |
|
"opt_max": { |
|
"temperatureMax": 1, |
|
"max_tokensMax": 8192, |
|
"presence_penaltyMax": 2, |
|
"frequency_penaltyMax": 2, |
|
"top_pMax": 1, |
|
"top_kMax": 500 |
|
} |
|
}, |
|
"claude-3-5-haiku-latest": { |
|
"id": "claude-3-5-haiku-latest", |
|
"provider": "Anthropic", |
|
"providerId": "anthropic", |
|
"name": "Claude 3.5 Haiku", |
|
"multiModal": False, |
|
"Systemprompt": "", |
|
"opt_max": { |
|
"temperatureMax": 1, |
|
"max_tokensMax": 8192, |
|
"presence_penaltyMax": 2, |
|
"frequency_penaltyMax": 2, |
|
"top_pMax": 1, |
|
"top_kMax": 500 |
|
} |
|
} |
|
}, |
|
"DEFAULT_HEADERS": { |
|
'accept': '*/*', |
|
'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6', |
|
'cache-control': 'no-cache', |
|
'content-type': 'application/json', |
|
'origin': 'https://fragments.e2b.dev', |
|
'pragma': 'no-cache', |
|
'priority': 'u=1, i', |
|
'referer': 'https://fragments.e2b.dev/', |
|
'sec-ch-ua': '"Not(A:Brand";v="99", "Microsoft Edge";v="133", "Chromium";v="133"', |
|
'sec-ch-ua-mobile': '?0', |
|
'sec-ch-ua-platform': '"Windows"', |
|
'sec-fetch-dest': 'empty', |
|
'sec-fetch-mode': 'cors', |
|
'sec-fetch-site': 'same-origin', |
|
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36 Edg/133.0.0.0' |
|
}, |
|
"MODEL_PROMPT": "Chatting with users and starting role-playing, the most important thing is to pay attention to their latest messages, use only 'text' to output the chat text reply content generated for user messages, and finally output it in code" |
|
} |
|
|
|
|
|
|
|
class Utils: |
|
@staticmethod |
|
def uuidv4(): |
|
return str(uuid.uuid4()) |
|
|
|
@staticmethod |
|
async def config_opt(params, model_config): |
|
if not model_config.get("opt_max"): |
|
return None |
|
|
|
options_map = { |
|
"temperature": "temperatureMax", |
|
"max_tokens": "max_tokensMax", |
|
"presence_penalty": "presence_penaltyMax", |
|
"frequency_penalty": "frequency_penaltyMax", |
|
"top_p": "top_pMax", |
|
"top_k": "top_kMax" |
|
} |
|
|
|
constrained_params = {} |
|
for key, value in params.items(): |
|
max_key = options_map.get(key) |
|
if (max_key and |
|
max_key in model_config["opt_max"] and |
|
value is not None): |
|
constrained_params[key] = min(value, model_config["opt_max"][max_key]) |
|
|
|
return constrained_params |
|
|
|
|
|
|
|
class ApiClient: |
|
def __init__(self, model_id): |
|
if model_id not in CONFIG["MODEL_CONFIG"]: |
|
raise ValueError(f"不支持的模型: {model_id}") |
|
self.model_config = CONFIG["MODEL_CONFIG"][model_id] |
|
|
|
def process_message_content(self, content): |
|
if isinstance(content, str): |
|
return content |
|
if isinstance(content, list): |
|
return "\n".join([item["text"] for item in content if item["type"] == "text"]) |
|
if isinstance(content, dict): |
|
return content.get("text") |
|
return None |
|
|
|
async def prepare_chat_request(self, request, config=None): |
|
opt_config = config or {"model": self.model_config["id"]} |
|
return { |
|
"userID": Utils.uuidv4(), |
|
"messages": await self.transform_messages(request), |
|
"template": { |
|
"text": { |
|
"name": CONFIG["MODEL_PROMPT"], |
|
"lib": [""], |
|
"file": "pages/ChatWithUsers.txt", |
|
"instructions": self.model_config["Systemprompt"], |
|
"port": None |
|
} |
|
}, |
|
"model": { |
|
"id": self.model_config["id"], |
|
"provider": self.model_config["provider"], |
|
"providerId": self.model_config["providerId"], |
|
"name": self.model_config["name"], |
|
"multiModal": self.model_config["multiModal"] |
|
}, |
|
"config": opt_config |
|
} |
|
|
|
async def transform_messages(self, request): |
|
merged_messages = [] |
|
for current in request["messages"]: |
|
current_content = self.process_message_content(current["content"]) |
|
if current_content is None: |
|
continue |
|
|
|
if (merged_messages and |
|
current and |
|
merged_messages[-1]["role"] == current["role"]): |
|
last_content = self.process_message_content(merged_messages[-1]["content"]) |
|
if last_content is not None: |
|
merged_messages[-1]["content"] = f"{last_content}\n{current_content}" |
|
continue |
|
|
|
merged_messages.append(current) |
|
|
|
messages = [] |
|
for msg in merged_messages: |
|
if msg["role"] in ["system", "user"]: |
|
messages.append({ |
|
"role": "user", |
|
"content": [{ |
|
"type": "text", |
|
"text": msg["content"] |
|
}] |
|
}) |
|
elif msg["role"] == "assistant": |
|
messages.append({ |
|
"role": "assistant", |
|
"content": [{ |
|
"type": "text", |
|
"text": msg["content"] |
|
}] |
|
}) |
|
|
|
return messages |
|
|
|
|
|
|
|
class ResponseHandler: |
|
@staticmethod |
|
async def handle_stream_response(chat_message, model): |
|
async def stream_generator(): |
|
index = 0 |
|
while True: |
|
|
|
if index >= len(chat_message): |
|
yield f"data: [DONE]\n\n" |
|
break |
|
|
|
chunk_size = random.randint(15, 29) |
|
chunk = chat_message[index:index + chunk_size] |
|
|
|
event_data = { |
|
"id": Utils.uuidv4(), |
|
"object": "chat.completion.chunk", |
|
"created": int(time.time()), |
|
"model": model, |
|
"choices": [{ |
|
"index": 0, |
|
"delta": {"content": chunk}, |
|
"finish_reason": "stop" if index + chunk_size >= len(chat_message) else None |
|
}] |
|
} |
|
|
|
try: |
|
payload = f"data: {json.dumps(event_data)}\n\n" |
|
yield payload |
|
except Exception as error: |
|
raise Exception(f"json转换失败: {error}") |
|
|
|
index += chunk_size |
|
await asyncio.sleep(0.05) |
|
|
|
return StreamingResponse( |
|
stream_generator(), |
|
media_type="text/event-stream", |
|
headers={ |
|
"Cache-Control": "no-cache", |
|
"Connection": "keep-alive", |
|
} |
|
) |
|
|
|
@staticmethod |
|
async def handle_normal_response(chat_message, model): |
|
response_data = { |
|
"id": Utils.uuidv4(), |
|
"object": "chat.completion", |
|
"created": int(time.time()), |
|
"model": model, |
|
"choices": [{ |
|
"index": 0, |
|
"message": { |
|
"role": "assistant", |
|
"content": chat_message |
|
}, |
|
"finish_reason": "stop" |
|
}], |
|
"usage": None |
|
} |
|
|
|
return Response( |
|
content=json.dumps(response_data), |
|
media_type="application/json" |
|
) |
|
|
|
|
|
|
|
app = FastAPI() |
|
|
|
|
|
app.add_middleware( |
|
CORSMiddleware, |
|
allow_origins=["*"], |
|
allow_credentials=True, |
|
allow_methods=["*"], |
|
allow_headers=["*"], |
|
) |
|
|
|
|
|
|
|
@app.get("/hf/v1/models") |
|
async def get_models(): |
|
return { |
|
"object": "list", |
|
"data": [ |
|
{ |
|
"id": model, |
|
"object": "model", |
|
"created": int(time.time()), |
|
"owned_by": "e2b", |
|
} |
|
for model in CONFIG["MODEL_CONFIG"].keys() |
|
] |
|
} |
|
|
|
|
|
|
|
@app.post("/hf/v1/chat/completions") |
|
async def chat_completions(request: Request): |
|
try: |
|
|
|
auth_header = request.headers.get("authorization", "") |
|
auth_token = auth_header.replace("Bearer ", "") if auth_header else "" |
|
|
|
if auth_token != CONFIG["API"]["API_KEY"]: |
|
return Response( |
|
content=json.dumps({"error": "Unauthorized"}), |
|
status_code=401, |
|
media_type="application/json" |
|
) |
|
|
|
|
|
request_body = await request.json() |
|
model = request_body.get("model") |
|
temperature = request_body.get("temperature") |
|
max_tokens = request_body.get("max_tokens") |
|
presence_penalty = request_body.get("presence_penalty") |
|
frequency_penalty = request_body.get("frequency_penalty") |
|
top_p = request_body.get("top_p") |
|
top_k = request_body.get("top_k") |
|
stream = request_body.get("stream", False) |
|
|
|
|
|
|
|
config_opt = await Utils.config_opt( |
|
{ |
|
"temperature": temperature, |
|
"max_tokens": max_tokens, |
|
"presence_penalty": presence_penalty, |
|
"frequency_penalty": frequency_penalty, |
|
"top_p": top_p, |
|
"top_k": top_k |
|
}, |
|
CONFIG["MODEL_CONFIG"][model] |
|
) |
|
|
|
|
|
|
|
api_client = ApiClient(model) |
|
request_payload = await api_client.prepare_chat_request(request_body, config_opt) |
|
|
|
|
|
import httpx |
|
async with httpx.AsyncClient() as client: |
|
response = await client.post( |
|
f"{CONFIG['API']['BASE_URL']}/api/chat", |
|
headers=CONFIG["DEFAULT_HEADERS"], |
|
json=request_payload |
|
) |
|
response_data = response.json() |
|
|
|
|
|
chat_message = (response_data.get('code', '') or response_data.get('text', '') or response_data or '').strip() or None |
|
|
|
if not chat_message: |
|
raise Exception("No response from upstream service") |
|
|
|
|
|
if stream: |
|
return await ResponseHandler.handle_stream_response(chat_message, model) |
|
else: |
|
return await ResponseHandler.handle_normal_response(chat_message, model) |
|
|
|
except Exception as error: |
|
return Response( |
|
content=json.dumps({ |
|
"error": { |
|
"message": f"{str(error)} 请求失败,可能是上下文超出限制或其他错误,请稍后重试。", |
|
"type": "server_error", |
|
"param": None, |
|
"code": getattr(error, "code", None) |
|
} |
|
}), |
|
status_code=500, |
|
media_type="application/json" |
|
) |
|
|
|
|
|
|
|
@app.api_route("/{path:path}", methods=["GET", "POST", "PUT", "DELETE"]) |
|
async def not_found(request: Request, path: str): |
|
return Response( |
|
content="服务运行成功,请使用正确请求路径", |
|
status_code=404, |
|
headers={"Access-Control-Allow-Origin": "*"} |
|
) |
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
import uvicorn |
|
uvicorn.run(app, host="0.0.0.0", port=7860) |