From 705996585663085008709754ea6342aea0a8d22b Mon Sep 17 00:00:00 2001 From: Huan-zhaojun <111294954+Huan-zhaojun@users.noreply.github.com> Date: Fri, 27 Mar 2026 02:07:30 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20SuperGrok=20?= =?UTF-8?q?=E4=BB=98=E8=B4=B9=E8=B4=A6=E5=8F=B7=E5=A4=9A=E6=99=BA=E8=83=BD?= =?UTF-8?q?=E4=BD=93=E6=A8=A1=E5=9E=8B=E7=A9=BA=E5=93=8D=E5=BA=94=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 modeId 映射替代旧的 modelName/modelMode 请求格式 (Grok API 要求付费账号多智能体管线使用 modeId 字段) - 移除已废弃的 enable420 标志 - 修复 think_closed_once 门控导致多智能体思维链 token 被丢弃的问题 - 使用 messageStepId 将 Agent 思考聚合到单个 块 - 丢弃正文输出中途插入的孤儿思考 token(Grok 官网也隐藏这部分) - CollectProcessor 添加 fallback token 收集兜底非流式模式 --- app/services/grok/services/chat.py | 36 ++++++++++++++++++++++++------ app/services/reverse/app_chat.py | 22 ++++++++++++++++-- 2 files changed, 49 insertions(+), 9 deletions(-) diff --git a/app/services/grok/services/chat.py b/app/services/grok/services/chat.py index eec1e8ae8..e7b92e9d3 100644 --- a/app/services/grok/services/chat.py +++ b/app/services/grok/services/chat.py @@ -537,8 +537,8 @@ def __init__( self.fingerprint: str = "" self.rollout_id: str = "" self.think_opened: bool = False - self.think_closed_once: bool = False self.image_think_active: bool = False + self._content_started: bool = False self.role_sent: bool = False self.filter_tags = get_config("app.filter_tags") self.tool_usage_enabled = ( @@ -816,7 +816,6 @@ async def process(self, response: AsyncIterable[bytes]) -> AsyncGenerator[str, N if self.image_think_active and self.think_opened: yield self._sse("\n\n") self.think_opened = False - self.think_closed_once = True self.image_think_active = False for url in proc_base._collect_images(mr): parts = url.split("/") @@ -860,15 +859,26 @@ async def process(self, response: AsyncIterable[bytes]) -> AsyncGenerator[str, N if (token := resp.get("token")) is not None: if not token: continue - if is_thinking and self.think_closed_once and not self.image_think_active: - continue filtered = self._filter_token(token) if not filtered: continue + # 判断是否在 Agent 思考/处理阶段: + # - isThinking=true → 归入 think + # - 有 messageStepId → Agent 处理中,归入 think + # - image_think_active → 图片生成中 + # 正式内容开始后,丢弃中途插入的思考(Grok 官网也隐藏了这部分) + has_step_id = bool(resp.get("messageStepId")) in_think = ( - (is_thinking and not self.think_closed_once) + is_thinking + or has_step_id or self.image_think_active ) + # 正式内容已开始后,丢弃中途插入的 Agent 思考(1-2 句内部注释,无用户价值) + if self._content_started and in_think and not self.image_think_active: + continue + # 空 token 不关闭 think 块(搜索结果间的空 token 不算正式内容) + if not in_think and not filtered.strip(): + continue if in_think: if not self.show_think: continue @@ -879,7 +889,7 @@ async def process(self, response: AsyncIterable[bytes]) -> AsyncGenerator[str, N if self.think_opened: yield self._sse("\n\n") self.think_opened = False - self.think_closed_once = True + self._content_started = True if in_think: self._record_content(filtered) @@ -901,7 +911,6 @@ async def process(self, response: AsyncIterable[bytes]) -> AsyncGenerator[str, N if self.think_opened: yield self._sse("\n") - self.think_closed_once = True if self._tool_stream_enabled: for kind, payload in self._flush_tool_stream(): @@ -1022,6 +1031,8 @@ async def process(self, response: AsyncIterable[bytes]) -> dict[str, Any]: response_id = "" fingerprint = "" content = "" + # 兜底收集非 thinking 且无 messageStepId 的最终内容 token + fallback_tokens: list[str] = [] idle_timeout = get_config("chat.stream_timeout") try: @@ -1041,6 +1052,13 @@ async def process(self, response: AsyncIterable[bytes]) -> dict[str, Any]: if (llm := resp.get("llmInfo")) and not fingerprint: fingerprint = llm.get("modelHash", "") + # 收集非 thinking 且无 messageStepId 的 token(最终内容兜底) + is_thinking = bool(resp.get("isThinking")) + has_step_id = bool(resp.get("messageStepId")) + if not is_thinking and not has_step_id: + if tok := resp.get("token"): + fallback_tokens.append(tok) + if mr := resp.get("modelResponse"): response_id = mr.get("responseId", "") content = mr.get("message", "") @@ -1140,6 +1158,10 @@ def _render_card(match: re.Match) -> str: finally: await self.close() + # modelResponse.message 为空时(多智能体模型),用兜底 token 拼接 + if not content and fallback_tokens: + content = "".join(fallback_tokens) + content = self._filter_content(content) # Parse for tool calls if tools were provided diff --git a/app/services/reverse/app_chat.py b/app/services/reverse/app_chat.py index 526059f0d..fdb486967 100644 --- a/app/services/reverse/app_chat.py +++ b/app/services/reverse/app_chat.py @@ -102,6 +102,19 @@ def _resolve_custom_personality() -> Optional[str]: return None return value + # modelMode → modeId 映射(Grok Web 新 API 格式) + # 基于浏览器前端 JS 逆向和 API 全量测试验证: + # 付费 SuperGrok 的多智能体模式需要 modeId 字段才能正常响应 + # 有 modeId 时不发 modelName/modelMode(浏览器前端逻辑) + _MODE_ID_MAP = { + "MODEL_MODE_FAST": "fast", + "MODEL_MODE_EXPERT": "expert", + "MODEL_MODE_HEAVY": "heavy", + "MODEL_MODE_GROK_420": "expert", + "MODEL_MODE_GROK_4_1_THINKING": "expert", + "MODEL_MODE_GROK_4_1_MINI_THINKING": "expert", + } + @staticmethod def build_payload( message: str, @@ -152,8 +165,13 @@ def build_payload( "toolOverrides": tool_overrides or {}, } - if model == "grok-420": - payload["enable420"] = True + # 优先使用 modeId(Grok 新 API 格式,付费号多智能体模式必需) + # 有 modeId 时移除 modelName/modelMode(浏览器前端逻辑) + mode_id = AppChatReverse._MODE_ID_MAP.get(mode) + if mode_id: + payload["modeId"] = mode_id + payload.pop("modelName", None) + payload.pop("modelMode", None) custom_personality = AppChatReverse._resolve_custom_personality() if custom_personality is not None: