From 67d5e43ec4007f99773c369f56d6cadd011a16f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=89=E9=91=AB=E5=8D=87?= Date: Fri, 22 May 2026 18:03:24 +0800 Subject: [PATCH] =?UTF-8?q?=E6=A0=87=E9=A2=98=E7=94=9F=E6=88=90=E5=BC=BA?= =?UTF-8?q?=E5=88=B6=E5=85=B3=E9=97=AD=20think=20=E6=A8=A1=E5=BC=8F,?= =?UTF-8?q?=E5=9B=9E=E7=AD=94=E9=80=9F=E5=BA=A6=E6=8C=87=E7=A4=BA=E5=99=A8?= =?UTF-8?q?=E9=BB=98=E8=AE=A4=E5=BC=80=E5=90=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - TitleMiddleware 给模型实例注入 extra_body 关 think(覆盖 Zhipu GLM 与 Qwen/vLLM 两套写法),并兜底剥离不闭合/残留的 标签,避免 GLM5 起标题时长时间思考与标签泄漏。 - 前端 streamSpeedMeterEnabled 默认改为 true,设置项描述同步为"默认开启"。 Co-Authored-By: Claude Opus 4.7 (1M context) --- .../settings/appearance-settings-page.tsx | 2 +- frontend-web/src/core/settings/local.ts | 16 +++---- .../agents/middlewares/title_middleware.py | 43 ++++++++++++++++++- 3 files changed, 50 insertions(+), 11 deletions(-) diff --git a/frontend-web/src/components/workspace/settings/appearance-settings-page.tsx b/frontend-web/src/components/workspace/settings/appearance-settings-page.tsx index cd1feb3b..0d09f5e1 100644 --- a/frontend-web/src/components/workspace/settings/appearance-settings-page.tsx +++ b/frontend-web/src/components/workspace/settings/appearance-settings-page.tsx @@ -122,7 +122,7 @@ export function AppearanceSettingsPage() {
回答速度指示器

在生成回答时显示一个小胶囊,实时估算当前 token/s - 并用绿到红的渐变条直观反映快慢。默认关闭。 + 并用绿到红的渐变条直观反映快慢。默认开启。

str: - """Remove ... blocks emitted by reasoning models (e.g. minimax, DeepSeek-R1).""" - return re.sub(r"[\s\S]*?", "", text, flags=re.IGNORECASE).strip() + """Remove ... blocks emitted by reasoning models (e.g. minimax, GLM, DeepSeek-R1). + + Also handles malformed cases: an unclosed `` (response truncated mid-reasoning) + or a stray `` (open tag eaten by upstream parsing) — both can otherwise leak + raw reasoning into the title. + """ + text = re.sub(r"[\s\S]*?", "", text, flags=re.IGNORECASE) + text = re.sub(r"[\s\S]*$", "", text, flags=re.IGNORECASE) + text = re.sub(r"^[\s\S]*?", "", text, flags=re.IGNORECASE) + return text.strip() def _parse_title(self, content: object) -> str: """Normalize model output into a clean title string.""" @@ -146,6 +154,36 @@ class TitleMiddleware(AgentMiddleware[TitleMiddlewareState]): _, user_msg = self._build_title_prompt(state) return {"title": self._fallback_title(user_msg)} + def _force_disable_thinking(self, model: object) -> None: + """Force-disable thinking on the title model via `extra_body` overrides. + + Title generation never needs reasoning. The model factory only emits + disable params when the model config declares `when_thinking_disabled` + or a matching `when_thinking_enabled` shape — many user-added reasoning + models (e.g. GLM-4.5/5) only set `supports_thinking: true` and would + otherwise keep thinking on, which is slow and leaks tags into + the title prompt response. Injecting both common OpenAI-compatible + disable shapes covers Zhipu GLM (`thinking.type`) and Qwen/vLLM + (`chat_template_kwargs.enable_thinking`); other providers ignore + unknown body fields. + """ + if not hasattr(model, "extra_body"): + return + current = getattr(model, "extra_body", None) or {} + merged_chat_template_kwargs = { + **(current.get("chat_template_kwargs") or {}), + "enable_thinking": False, + } + merged = { + **current, + "thinking": {**(current.get("thinking") or {}), "type": "disabled"}, + "chat_template_kwargs": merged_chat_template_kwargs, + } + try: + model.extra_body = merged + except Exception: + logger.debug("Could not override extra_body to disable thinking on title model", exc_info=True) + async def _agenerate_title_result(self, state: TitleMiddlewareState) -> dict | None: """Generate a title asynchronously and fall back locally on failure.""" if not self._should_generate_title(state): @@ -162,6 +200,7 @@ class TitleMiddleware(AgentMiddleware[TitleMiddlewareState]): model = create_chat_model(name=config.model_name, **model_kwargs) else: model = create_chat_model(**model_kwargs) + self._force_disable_thinking(model) response = await model.ainvoke(prompt, config=self._get_runnable_config()) title = self._parse_title(response.content) if title: