feat: 兼容OpenAI格式的模型服务代理
This commit is contained in:
@@ -2,11 +2,6 @@ from app.config.env import env
|
|||||||
|
|
||||||
ai_configs = {
|
ai_configs = {
|
||||||
# /*---------------------------------------local-------------------------------------------*/
|
# /*---------------------------------------local-------------------------------------------*/
|
||||||
"local": {
|
|
||||||
"model": "deepseek-r1-distill-qwen-7b",
|
|
||||||
"url": "http://127.0.0.1:1234/v1/chat/completions",
|
|
||||||
"key": env.llm_key_local
|
|
||||||
},
|
|
||||||
"local_glm": {
|
"local_glm": {
|
||||||
"model": "glm-4-9b-0414",
|
"model": "glm-4-9b-0414",
|
||||||
"url": "http://127.0.0.1:1234/v1/chat/completions",
|
"url": "http://127.0.0.1:1234/v1/chat/completions",
|
||||||
@@ -17,38 +12,12 @@ ai_configs = {
|
|||||||
"url": "http://127.0.0.1:1234/v1/embeddings",
|
"url": "http://127.0.0.1:1234/v1/embeddings",
|
||||||
"key": env.llm_key_local
|
"key": env.llm_key_local
|
||||||
},
|
},
|
||||||
# /*---------------------------------------deepseek-------------------------------------------*/
|
|
||||||
'deepseek-v3': {
|
|
||||||
'model': 'deepseek-chat',
|
|
||||||
'url': 'https://api.deepseek.com/chat/completions',
|
|
||||||
'key': env.llm_key_deepseek,
|
|
||||||
},
|
|
||||||
'deepseek-r1': {
|
|
||||||
'model': 'deepseek-reasoner',
|
|
||||||
'url': 'https://api.deepseek.com/chat/completions',
|
|
||||||
'key': env.llm_key_deepseek,
|
|
||||||
},
|
|
||||||
# /*---------------------------------------huoshan-------------------------------------------*/
|
# /*---------------------------------------huoshan-------------------------------------------*/
|
||||||
'huoshan-deepseek-v3': {
|
|
||||||
'model': 'deepseek-v3-250324',
|
|
||||||
'url': 'https://ark.cn-beijing.volces.com/api/v3/chat/completions',
|
|
||||||
'key': env.llm_key_huoshan,
|
|
||||||
},
|
|
||||||
'huoshan-deepseek-r1': {
|
|
||||||
'model': 'deepseek-r1-distill-qwen-7b-250120',
|
|
||||||
'url': 'https://ark.cn-beijing.volces.com/api/v3/chat/completions',
|
|
||||||
'key': env.llm_key_huoshan,
|
|
||||||
},
|
|
||||||
'huoshan-doubao': {
|
'huoshan-doubao': {
|
||||||
'model': 'doubao-1-5-lite-32k-250115',
|
'model': 'doubao-1-5-lite-32k-250115',
|
||||||
'url': 'https://ark.cn-beijing.volces.com/api/v3/chat/completions',
|
'url': 'https://ark.cn-beijing.volces.com/api/v3/chat/completions',
|
||||||
'key': env.llm_key_huoshan,
|
'key': env.llm_key_huoshan,
|
||||||
},
|
},
|
||||||
'huoshan-think-pro': {
|
|
||||||
'model': 'doubao-1-5-thinking-pro-250415',
|
|
||||||
'url': 'https://ark.cn-beijing.volces.com/api/v3/chat/completions',
|
|
||||||
'key': env.llm_key_huoshan,
|
|
||||||
},
|
|
||||||
'huoshan-doubao-seed': {
|
'huoshan-doubao-seed': {
|
||||||
'model': 'doubao-seed-1-6-250615',
|
'model': 'doubao-seed-1-6-250615',
|
||||||
'url': 'https://ark.cn-beijing.volces.com/api/v3/chat/completions',
|
'url': 'https://ark.cn-beijing.volces.com/api/v3/chat/completions',
|
||||||
@@ -59,12 +28,6 @@ ai_configs = {
|
|||||||
'url': 'https://ark.cn-beijing.volces.com/api/v3/chat/completions',
|
'url': 'https://ark.cn-beijing.volces.com/api/v3/chat/completions',
|
||||||
'key': env.llm_key_huoshan,
|
'key': env.llm_key_huoshan,
|
||||||
},
|
},
|
||||||
|
|
||||||
'huoshan-embedding-240715': {
|
|
||||||
"model": 'doubao-embedding-text-240715',
|
|
||||||
"url": 'https://ark.cn-beijing.volces.com/api/v3/embeddings',
|
|
||||||
'key': env.llm_key_huoshan,
|
|
||||||
},
|
|
||||||
# /*---------------------------------------bailian-------------------------------------------*/
|
# /*---------------------------------------bailian-------------------------------------------*/
|
||||||
'bailian-qwen-turbo': {
|
'bailian-qwen-turbo': {
|
||||||
'model': 'qwen-turbo',
|
'model': 'qwen-turbo',
|
||||||
@@ -76,6 +39,11 @@ ai_configs = {
|
|||||||
'url': 'https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions',
|
'url': 'https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions',
|
||||||
'key': env.llm_key_bailian,
|
'key': env.llm_key_bailian,
|
||||||
},
|
},
|
||||||
|
'bailian-qwen3.6-plus': {
|
||||||
|
'model': 'qwen3.6-plus',
|
||||||
|
'url': 'https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions',
|
||||||
|
'key': env.llm_key_bailian,
|
||||||
|
},
|
||||||
'bailian-embedding': {
|
'bailian-embedding': {
|
||||||
'model': 'text-embedding-v4',
|
'model': 'text-embedding-v4',
|
||||||
'url': 'https://dashscope.aliyuncs.com/compatible-mode/v1/embeddings',
|
'url': 'https://dashscope.aliyuncs.com/compatible-mode/v1/embeddings',
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ from app.controller.add_graph_proxy_route import add_graph_proxy_route
|
|||||||
from app.controller.add_test_route import add_test_route
|
from app.controller.add_test_route import add_test_route
|
||||||
from app.model.LlmDemoMdel import LlmDemoModel
|
from app.model.LlmDemoMdel import LlmDemoModel
|
||||||
from app.model.ResumeTemplateModel import ResumeTemplateModel
|
from app.model.ResumeTemplateModel import ResumeTemplateModel
|
||||||
|
from app.utils.add_llm_routes import add_llm_routes
|
||||||
from app.utils.add_model_routes import add_model_routes
|
from app.utils.add_model_routes import add_model_routes
|
||||||
from app.utils.next_id import add_next_id_route
|
from app.utils.next_id import add_next_id_route
|
||||||
|
|
||||||
@@ -19,6 +20,10 @@ routes = [
|
|||||||
add_next_id_route, # 生成ID接口
|
add_next_id_route, # 生成ID接口
|
||||||
add_file_route, # 文件上传接口
|
add_file_route, # 文件上传接口
|
||||||
|
|
||||||
|
lambda app: add_llm_routes(app, 'huoshan-doubao'),
|
||||||
|
lambda app: add_llm_routes(app, 'doubao-vision-lite'),
|
||||||
|
lambda app: add_llm_routes(app, 'bailian-qwen3.6-plus'),
|
||||||
|
|
||||||
lambda app: add_model_routes(app,LlmDemoModel,'/llm_demo'), # LlmDemo 测试用户模块
|
lambda app: add_model_routes(app,LlmDemoModel,'/llm_demo'), # LlmDemo 测试用户模块
|
||||||
lambda app: add_model_routes(app,ResumeTemplateModel,'/llm_resume_template'), # 简历模板
|
lambda app: add_model_routes(app,ResumeTemplateModel,'/llm_resume_template'), # 简历模板
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
import httpx
|
||||||
|
from fastapi import APIRouter, Request, HTTPException, FastAPI
|
||||||
|
from fastapi.responses import StreamingResponse
|
||||||
|
|
||||||
|
from app.config.ai_configs import ai_configs
|
||||||
|
|
||||||
|
|
||||||
|
async def proxy_openai_request(request: Request, config_key: str):
|
||||||
|
"""
|
||||||
|
通用代理函数:将请求转发至指定的上游模型服务
|
||||||
|
"""
|
||||||
|
if config_key not in ai_configs:
|
||||||
|
raise HTTPException(status_code=404, detail=f"Config {config_key} not found")
|
||||||
|
|
||||||
|
config = ai_configs[config_key]
|
||||||
|
target_url = config["url"]
|
||||||
|
api_key = config["key"]
|
||||||
|
real_model_name = config["model"]
|
||||||
|
|
||||||
|
# 1. 获取原始请求体并修改模型名称
|
||||||
|
body = await request.json()
|
||||||
|
body["model"] = real_model_name # 强制替换为上游真正识别的模型名
|
||||||
|
|
||||||
|
# 2. 准备请求头
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"Bearer {api_key}",
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
|
||||||
|
client = httpx.AsyncClient()
|
||||||
|
|
||||||
|
# 3. 处理流式响应 (OpenAI 协议的关键)
|
||||||
|
if body.get("stream", False):
|
||||||
|
async def stream_generator():
|
||||||
|
async with client.stream("POST", target_url, json=body, headers=headers, timeout=60.0) as response:
|
||||||
|
async for chunk in response.aiter_bytes():
|
||||||
|
yield chunk
|
||||||
|
await client.aclose()
|
||||||
|
|
||||||
|
return StreamingResponse(stream_generator(), media_type="text/event-stream")
|
||||||
|
|
||||||
|
# 4. 处理非流式响应
|
||||||
|
else:
|
||||||
|
response = await client.post(target_url, json=body, headers=headers, timeout=60.0)
|
||||||
|
await client.aclose()
|
||||||
|
return response.json()
|
||||||
|
|
||||||
|
|
||||||
|
def add_llm_routes(app: FastAPI, config_name: str):
|
||||||
|
"""
|
||||||
|
动态生成路由的辅助函数
|
||||||
|
"""
|
||||||
|
# 自动根据配置判断是 chat 还是 embeddings 路径
|
||||||
|
is_embedding = "embedding" in config_name or "embeddings" in ai_configs[config_name]["url"]
|
||||||
|
path = "/v1/embeddings" if is_embedding else "/v1/chat/completions"
|
||||||
|
|
||||||
|
@app.post(f"/{config_name}{path}")
|
||||||
|
async def dynamic_proxy(request: Request):
|
||||||
|
return await proxy_openai_request(request, config_name)
|
||||||
|
|
||||||
|
print(f"🚀 Proxy LLM route: /{config_name}{path}")
|
||||||
Reference in New Issue
Block a user