Skip to content

Commit a76472e

Browse files
committed
Merge branch 'pr/26'
2 parents 544d00f + c60c16f commit a76472e

2 files changed

Lines changed: 160 additions & 10 deletions

File tree

app/core/config.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,9 @@ def longcat_token_list(self) -> List[str]:
141141
THINKING_MODEL: str = os.getenv("THINKING_MODEL", "GLM-4.5-Thinking")
142142
SEARCH_MODEL: str = os.getenv("SEARCH_MODEL", "GLM-4.5-Search")
143143
AIR_MODEL: str = os.getenv("AIR_MODEL", "GLM-4.5-Air")
144+
GLM46_MODEL: str = os.getenv("GLM46_MODEL", "GLM-4.6")
145+
GLM46_THINKING_MODEL: str = os.getenv("GLM46_THINKING_MODEL", "GLM-4.6-Thinking")
146+
GLM46_SEARCH_MODEL: str = os.getenv("GLM46_SEARCH_MODEL", "GLM-4.6-Search")
144147

145148

146149

@@ -154,6 +157,9 @@ def provider_model_mapping(self) -> Dict[str, str]:
154157
"GLM-4.5-Thinking": "zai",
155158
"GLM-4.5-Search": "zai",
156159
"GLM-4.5-Air": "zai",
160+
"GLM-4.6": "zai",
161+
"GLM-4.6-Thinking": "zai",
162+
"GLM-4.6-Search": "zai",
157163
# K2Think models
158164
"MBZUAI-IFM/K2-Think": "k2think",
159165
# LongCat models

app/providers/zai_provider.py

Lines changed: 154 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ def __init__(self):
4646
settings.THINKING_MODEL: "0727-360B-API", # GLM-4.5-Thinking
4747
settings.SEARCH_MODEL: "0727-360B-API", # GLM-4.5-Search
4848
settings.AIR_MODEL: "0727-106B-API", # GLM-4.5-Air
49+
settings.GLM46_MODEL: "GLM-4-6-API-V1", # GLM-4.6
50+
settings.GLM46_THINKING_MODEL: "GLM-4-6-API-V1", # GLM-4.6-Thinking
51+
settings.GLM46_SEARCH_MODEL: "GLM-4-6-API-V1", # GLM-4.6-Search
4952
}
5053

5154
def get_supported_models(self) -> List[str]:
@@ -54,7 +57,10 @@ def get_supported_models(self) -> List[str]:
5457
settings.PRIMARY_MODEL,
5558
settings.THINKING_MODEL,
5659
settings.SEARCH_MODEL,
57-
settings.AIR_MODEL
60+
settings.AIR_MODEL,
61+
settings.GLM46_MODEL,
62+
settings.GLM46_THINKING_MODEL,
63+
settings.GLM46_SEARCH_MODEL,
5864
]
5965

6066
async def get_token(self) -> str:
@@ -131,16 +137,16 @@ async def transform_request(self, request: OpenAIRequest) -> Dict[str, Any]:
131137

132138
# 确定请求的模型特性
133139
requested_model = request.model
134-
is_thinking = requested_model == settings.THINKING_MODEL
135-
is_search = requested_model == settings.SEARCH_MODEL
136-
is_air = requested_model == settings.AIR_MODEL
140+
is_thinking = "-thinking" in requested_model.casefold()
141+
is_search = "-search" in requested_model.casefold()
142+
is_air = "-air" in requested_model.casefold()
137143

138144
# 获取上游模型ID
139145
upstream_model_id = self.model_mapping.get(requested_model, "0727-360B-API")
140146

141147
# 构建MCP服务器列表
142148
mcp_servers = []
143-
if is_search:
149+
if is_search and "-4.5" in requested_model:
144150
mcp_servers.append("deep-web-search")
145151
self.logger.info("🔍 检测到搜索模型,添加 deep-web-search MCP 服务器")
146152

@@ -158,7 +164,38 @@ async def transform_request(self, request: OpenAIRequest) -> Dict[str, Any]:
158164
"auto_web_search": is_search,
159165
"preview_mode": False,
160166
"flags": [],
161-
"features": [],
167+
"features": [
168+
{
169+
"type": "mcp",
170+
"server": "vibe-coding",
171+
"status": "hidden"
172+
},
173+
{
174+
"type": "mcp",
175+
"server": "ppt-maker",
176+
"status": "hidden"
177+
},
178+
{
179+
"type": "mcp",
180+
"server": "image-search",
181+
"status": "hidden"
182+
},
183+
{
184+
"type": "mcp",
185+
"server": "deep-research",
186+
"status": "hidden"
187+
},
188+
{
189+
"type": "tool_selector",
190+
"server": "tool_selector",
191+
"status": "hidden"
192+
},
193+
{
194+
"type": "mcp",
195+
"server": "advanced-search",
196+
"status": "hidden"
197+
}
198+
],
162199
"enable_thinking": is_thinking,
163200
},
164201
"background_tasks": {
@@ -614,7 +651,114 @@ async def _handle_non_stream_response(
614651
chat_id: str,
615652
model: str
616653
) -> Dict[str, Any]:
617-
"""处理非流式响应"""
618-
# 简化的非流式响应处理
619-
content = "非流式响应处理中..."
620-
return self.create_openai_response(chat_id, model, content)
654+
"""处理非流式响应
655+
656+
说明:上游始终以 SSE 形式返回(transform_request 固定 stream=True),
657+
因此这里需要聚合 aiter_lines() 的 data: 块,提取 usage、思考内容与答案内容,
658+
并最终产出一次性 OpenAI 格式响应。
659+
"""
660+
final_content = ""
661+
reasoning_content = ""
662+
usage_info: Dict[str, int] = {
663+
"prompt_tokens": 0,
664+
"completion_tokens": 0,
665+
"total_tokens": 0,
666+
}
667+
668+
try:
669+
async for line in response.aiter_lines():
670+
if not line:
671+
continue
672+
673+
line = line.strip()
674+
675+
# 仅处理以 data: 开头的 SSE 行,其余行尝试作为错误/JSON 忽略
676+
if not line.startswith("data:"):
677+
# 尝试解析为错误 JSON
678+
try:
679+
maybe_err = json.loads(line)
680+
if isinstance(maybe_err, dict) and (
681+
"error" in maybe_err or "code" in maybe_err or "message" in maybe_err
682+
):
683+
# 统一错误处理
684+
msg = (
685+
(maybe_err.get("error") or {}).get("message")
686+
if isinstance(maybe_err.get("error"), dict)
687+
else maybe_err.get("message")
688+
) or "上游返回错误"
689+
return self.handle_error(Exception(msg), "API响应")
690+
except Exception:
691+
pass
692+
continue
693+
694+
data_str = line[5:].strip()
695+
if not data_str or data_str in ("[DONE]", "DONE", "done"):
696+
continue
697+
698+
# 解析 SSE 数据块
699+
try:
700+
chunk = json.loads(data_str)
701+
except json.JSONDecodeError:
702+
continue
703+
704+
if chunk.get("type") != "chat:completion":
705+
continue
706+
707+
data = chunk.get("data", {})
708+
phase = data.get("phase")
709+
delta_content = data.get("delta_content", "")
710+
edit_content = data.get("edit_content", "")
711+
712+
# 记录用量(通常在最后块中出现,但这里每次覆盖保持最新)
713+
if data.get("usage"):
714+
try:
715+
usage_info = data["usage"]
716+
except Exception:
717+
pass
718+
719+
# 思考阶段聚合(去除 <details><summary>... 包裹头)
720+
if phase == "thinking":
721+
if delta_content:
722+
if delta_content.startswith("<details"):
723+
cleaned = (
724+
delta_content.split("</summary>\n>")[-1].strip()
725+
if "</summary>\n>" in delta_content
726+
else delta_content
727+
)
728+
else:
729+
cleaned = delta_content
730+
reasoning_content += cleaned
731+
732+
# 答案阶段聚合
733+
elif phase == "answer":
734+
# 当 edit_content 同时包含思考结束标记与答案时,提取答案部分
735+
if edit_content and "</details>\n" in edit_content:
736+
content_after = edit_content.split("</details>\n")[-1]
737+
if content_after:
738+
final_content += content_after
739+
elif delta_content:
740+
final_content += delta_content
741+
742+
except Exception as e:
743+
self.logger.error(f"❌ 非流式响应处理错误: {e}")
744+
import traceback
745+
self.logger.error(traceback.format_exc())
746+
# 返回统一错误响应
747+
return self.handle_error(e, "非流式聚合")
748+
749+
# 清理并返回
750+
final_content = (final_content or "").strip()
751+
reasoning_content = (reasoning_content or "").strip()
752+
753+
# 若没有聚合到答案,但有思考内容,则保底返回思考内容
754+
if not final_content and reasoning_content:
755+
final_content = reasoning_content
756+
757+
# 返回包含推理内容的标准响应(若无推理则不会携带)
758+
return self.create_openai_response_with_reasoning(
759+
chat_id,
760+
model,
761+
final_content,
762+
reasoning_content,
763+
usage_info,
764+
)

0 commit comments

Comments
 (0)