|
|
@@ -277,14 +277,14 @@ async def init_global_resources():
|
|
|
global GLOBAL_HTTP_SESSION, GLOBAL_REDIS_CLIENT
|
|
|
|
|
|
if GLOBAL_HTTP_SESSION is None or GLOBAL_HTTP_SESSION.closed:
|
|
|
- # 增加 DNS 缓存和连接复用,针对阿里云域名优化
|
|
|
+ # 增加 DNS 缓存和连接复用,针对蜀天算力域名优化
|
|
|
connector = aiohttp.TCPConnector(limit=100, limit_per_host=20, ttl_dns_cache=300, force_close=False)
|
|
|
GLOBAL_HTTP_SESSION = aiohttp.ClientSession(
|
|
|
timeout=aiohttp.ClientTimeout(total=120, connect=10, sock_read=10), # 连接超时稍长以防网络波动
|
|
|
connector=connector,
|
|
|
- headers={"User-Agent": "FastAPI-DashScope-Optimized/2.0"}
|
|
|
+ headers={"User-Agent": "FastAPI-Shutian-Optimized/2.0"}
|
|
|
)
|
|
|
- logger.info("✅ 全局 HTTP 连接池已初始化 (DashScope Ready)")
|
|
|
+ logger.info("✅ 全局 HTTP 连接池已初始化 (Shutian Ready)")
|
|
|
|
|
|
if GLOBAL_REDIS_CLIENT is None:
|
|
|
try:
|
|
|
@@ -314,38 +314,70 @@ async def get_redis_client():
|
|
|
await init_global_resources()
|
|
|
return GLOBAL_REDIS_CLIENT
|
|
|
|
|
|
-# ==================== 自定义 API 配置 (阿里云 DashScope) ====================
|
|
|
+# ==================== 自定义 API 配置 (蜀天算力 Qwen3.5-122B) ====================
|
|
|
|
|
|
class CustomAPIConfig:
|
|
|
- # 【关键修改】阿里云 DashScope 兼容模式地址
|
|
|
- # 注意:必须包含 /chat/completions 后缀
|
|
|
- DASHSCOPE_BASE_URL = "https://dashscope.aliyuncs.com/compatible-mode/v1"
|
|
|
- DASHSCOPE_CHAT_URL = f"{DASHSCOPE_BASE_URL}/chat/completions"
|
|
|
-
|
|
|
- # 【关键修改】您的 API Key
|
|
|
- DASHSCOPE_API_KEY = "sk-ae805c991b6a4a8da3a09351c34963a5"
|
|
|
-
|
|
|
- # 【关键修改】目标模型
|
|
|
- DEFAULT_MODEL_NAME = "qwen3-30b-a3b-instruct-2507"
|
|
|
-
|
|
|
+ # model_setting.yaml 中的功能名称
|
|
|
+ FUNCTION_NAME = "write_outline_generate"
|
|
|
+
|
|
|
+ # 兜底默认值(蜀天 Qwen3.5-122B-A10B)
|
|
|
+ SHUTIAN_SERVER_URL_DEFAULT = "http://183.220.37.46:25423/v1"
|
|
|
+ SHUTIAN_API_KEY_DEFAULT = "lq123456"
|
|
|
+ DEFAULT_MODEL_NAME = "/model/Qwen3.5-122B-A10B"
|
|
|
+
|
|
|
+ @staticmethod
|
|
|
+ def _resolve_from_model_handler():
|
|
|
+ """通过 model_handler 统一解析模型配置(url, api_key, model_id)"""
|
|
|
+ try:
|
|
|
+ from foundation.ai.models.model_handler import model_handler
|
|
|
+ llm = model_handler.get_model_by_function(CustomAPIConfig.FUNCTION_NAME)
|
|
|
+ url = getattr(llm, 'base_url', None) or getattr(llm, 'openai_api_base', '')
|
|
|
+ url = str(url) if url else ''
|
|
|
+ model_id = getattr(llm, 'model_name', None) or getattr(llm, 'model', '')
|
|
|
+ model_id = str(model_id) if model_id else ''
|
|
|
+ api_key = getattr(llm, 'openai_api_key', None)
|
|
|
+ if api_key:
|
|
|
+ api_key = api_key.get_secret_value() if hasattr(api_key, 'get_secret_value') else str(api_key)
|
|
|
+ else:
|
|
|
+ api_key = ''
|
|
|
+ if url and api_key:
|
|
|
+ return url, api_key, model_id
|
|
|
+ except Exception:
|
|
|
+ pass
|
|
|
+ return None, None, None
|
|
|
+
|
|
|
@staticmethod
|
|
|
def get_api_url() -> str:
|
|
|
- # 优先使用硬编码的阿里云地址
|
|
|
- return CustomAPIConfig.DASHSCOPE_CHAT_URL
|
|
|
-
|
|
|
+ configured_url = config_handler.get("custom_api", "API_URL", "")
|
|
|
+ if configured_url:
|
|
|
+ return configured_url
|
|
|
+ url, _, _ = CustomAPIConfig._resolve_from_model_handler()
|
|
|
+ if url:
|
|
|
+ return url
|
|
|
+ return config_handler.get("shutian", "SHUTIAN_122B_SERVER_URL", CustomAPIConfig.SHUTIAN_SERVER_URL_DEFAULT)
|
|
|
+
|
|
|
@staticmethod
|
|
|
def get_api_key() -> str:
|
|
|
- return CustomAPIConfig.DASHSCOPE_API_KEY
|
|
|
-
|
|
|
+ configured_key = config_handler.get("custom_api", "API_KEY", "")
|
|
|
+ if configured_key:
|
|
|
+ return configured_key
|
|
|
+ _, api_key, _ = CustomAPIConfig._resolve_from_model_handler()
|
|
|
+ if api_key:
|
|
|
+ return api_key
|
|
|
+ return config_handler.get("shutian", "SHUTIAN_122B_API_KEY", CustomAPIConfig.SHUTIAN_API_KEY_DEFAULT)
|
|
|
+
|
|
|
@staticmethod
|
|
|
def get_model_name() -> str:
|
|
|
- # 允许配置覆盖,否则使用默认
|
|
|
configured_model = config_handler.get("custom_api", "MODEL_NAME", "")
|
|
|
- return configured_model if configured_model else CustomAPIConfig.DEFAULT_MODEL_NAME
|
|
|
-
|
|
|
+ if configured_model:
|
|
|
+ return configured_model
|
|
|
+ _, _, model_id = CustomAPIConfig._resolve_from_model_handler()
|
|
|
+ if model_id:
|
|
|
+ return model_id
|
|
|
+ return config_handler.get("shutian", "SHUTIAN_122B_MODEL_ID", CustomAPIConfig.DEFAULT_MODEL_NAME)
|
|
|
+
|
|
|
@staticmethod
|
|
|
def is_enabled() -> bool:
|
|
|
- # 只要 Key 不为空即启用
|
|
|
return bool(CustomAPIConfig.get_api_key()) and bool(CustomAPIConfig.get_api_url())
|
|
|
|
|
|
# ==================== 极速流式调用 (核心优化) ====================
|
|
|
@@ -359,9 +391,9 @@ async def call_custom_api_stream(
|
|
|
model_name = CustomAPIConfig.get_model_name()
|
|
|
api_key = CustomAPIConfig.get_api_key()
|
|
|
|
|
|
- logger.debug(f"[{trace_id}] 正在调用阿里云 DashScope: {model_name} @ {api_url}")
|
|
|
+ logger.debug(f"[{trace_id}] 正在调用蜀天算力: {model_name} @ {api_url}")
|
|
|
|
|
|
- # 截断过长的 Prompt (阿里云对输入长度有限制,且为了速度)
|
|
|
+ # 截断过长的 Prompt (服务端对输入长度有限制,且为了速度)
|
|
|
max_prompt_len = 10000
|
|
|
if len(prompt) > max_prompt_len:
|
|
|
prompt = prompt[-max_prompt_len:]
|
|
|
@@ -376,7 +408,7 @@ async def call_custom_api_stream(
|
|
|
"max_tokens": max_tokens,
|
|
|
"temperature": temperature,
|
|
|
"stream": True,
|
|
|
- "incremental_output": True # 阿里云兼容模式可能支持此参数,优化流式体验
|
|
|
+ "incremental_output": True # 蜀天算力兼容模式支持此参数,优化流式体验
|
|
|
}
|
|
|
|
|
|
headers = {
|
|
|
@@ -391,7 +423,7 @@ async def call_custom_api_stream(
|
|
|
session = await get_http_session()
|
|
|
|
|
|
try:
|
|
|
- # 阿里云 HTTPS 连接,保持 read_bufsize=1 以获取最快首字
|
|
|
+ # 蜀天算力 HTTP 连接,保持 read_bufsize=1 以获取最快首字
|
|
|
async with session.post(api_url, json=payload, headers=headers, read_bufsize=1) as response:
|
|
|
if response.status != 200:
|
|
|
error_text = await response.text()
|
|
|
@@ -416,7 +448,7 @@ async def call_custom_api_stream(
|
|
|
|
|
|
try:
|
|
|
event_data = json.loads(data)
|
|
|
- # 处理阿里云可能的错误格式
|
|
|
+ # 处理服务端可能的错误格式
|
|
|
if "error" in event_data:
|
|
|
err_msg = event_data["error"].get("message", "Unknown Error")
|
|
|
logger.error(f"[{trace_id}] 流式数据中包含错误: {err_msg}")
|
|
|
@@ -510,13 +542,13 @@ async def generate_content_stream(callback_task_id, source_task_id, user_id, req
|
|
|
|
|
|
yield format_sse_event("generating", json.dumps({
|
|
|
"status": "generating",
|
|
|
- "message": f"正在调用阿里云 Qwen3 ({CustomAPIConfig.get_model_name()})...",
|
|
|
+ "message": f"正在调用蜀天 Qwen3.5-122B ({CustomAPIConfig.get_model_name()})...",
|
|
|
"timestamp": int(time.time())
|
|
|
}, ensure_ascii=False))
|
|
|
|
|
|
# 执行生成
|
|
|
if CustomAPIConfig.is_enabled():
|
|
|
- logger.info(f"[{callback_task_id}] 使用阿里云 DashScope API (模型:{CustomAPIConfig.get_model_name()})")
|
|
|
+ logger.info(f"[{callback_task_id}] 使用蜀天算力 API (模型:{CustomAPIConfig.get_model_name()})")
|
|
|
async for content, ftl in call_custom_api_stream(
|
|
|
prompt=user_prompt,
|
|
|
system_prompt=CONTEXT_GENERATE_SYSTEM_PROMPT,
|
|
|
@@ -615,7 +647,7 @@ async def context_generate(request: ContextGenerateRequest):
|
|
|
async def health_check():
|
|
|
return {
|
|
|
"status": "healthy",
|
|
|
- "provider": "Aliyun DashScope",
|
|
|
+ "provider": "Shutian",
|
|
|
"current_model": CustomAPIConfig.get_model_name(),
|
|
|
"api_url_prefix": "https://dashscope.aliyuncs.com/compatible-mode/v1"
|
|
|
}
|
|
|
@@ -635,7 +667,7 @@ async def get_api_status():
|
|
|
code=200, message="success",
|
|
|
data={
|
|
|
"enabled": enabled,
|
|
|
- "provider": "Aliyun DashScope",
|
|
|
+ "provider": "Shutian",
|
|
|
"model": CustomAPIConfig.get_model_name()
|
|
|
}
|
|
|
)
|
|
|
@@ -802,6 +834,7 @@ async def generating_outline(request: OutlineGenerationRequest):
|
|
|
last_event_type = "processing"
|
|
|
last_message = ""
|
|
|
no_change_count = 0
|
|
|
+ no_data_count = 0
|
|
|
|
|
|
while True:
|
|
|
try:
|
|
|
@@ -835,6 +868,7 @@ async def generating_outline(request: OutlineGenerationRequest):
|
|
|
progress_data = await progress_manager.get_progress(callback_task_id)
|
|
|
|
|
|
if progress_data:
|
|
|
+ no_data_count = 0
|
|
|
current_progress = progress_data.get("current", last_progress)
|
|
|
current_event_type = progress_data.get("event_type", "processing")
|
|
|
current_message = progress_data.get("message", "")
|
|
|
@@ -887,6 +921,11 @@ async def generating_outline(request: OutlineGenerationRequest):
|
|
|
last_progress_data = stream_data
|
|
|
yield format_sse_event("processing", json.dumps(stream_data, ensure_ascii=False))
|
|
|
break
|
|
|
+ else:
|
|
|
+ no_data_count += 1
|
|
|
+ if no_data_count >= 60: # 30 秒无数据
|
|
|
+ logger.info(f"[{callback_task_id}] 进度数据丢失,结束SSE")
|
|
|
+ break
|
|
|
|
|
|
await asyncio.sleep(0.5)
|
|
|
|
|
|
@@ -1306,6 +1345,7 @@ async def regenerate_outline(request: RegenerateOutlineRequest):
|
|
|
last_event_type = "processing"
|
|
|
last_message = ""
|
|
|
no_change_count = 0
|
|
|
+ no_data_count = 0
|
|
|
|
|
|
while True:
|
|
|
try:
|
|
|
@@ -1340,6 +1380,7 @@ async def regenerate_outline(request: RegenerateOutlineRequest):
|
|
|
progress_data = await progress_manager.get_progress(new_callback_task_id)
|
|
|
|
|
|
if progress_data:
|
|
|
+ no_data_count = 0
|
|
|
current_progress = progress_data.get("current", last_progress)
|
|
|
current_event_type = progress_data.get("event_type", "processing")
|
|
|
current_message = progress_data.get("message", "")
|
|
|
@@ -1392,6 +1433,11 @@ async def regenerate_outline(request: RegenerateOutlineRequest):
|
|
|
last_progress_data = stream_data
|
|
|
yield format_sse_event("processing", json.dumps(stream_data, ensure_ascii=False))
|
|
|
break
|
|
|
+ else:
|
|
|
+ no_data_count += 1
|
|
|
+ if no_data_count >= 60: # 30 秒无数据
|
|
|
+ logger.info(f"[{new_callback_task_id}] 进度数据丢失,结束SSE")
|
|
|
+ break
|
|
|
|
|
|
await asyncio.sleep(0.5)
|
|
|
|