DeepTutor v1.4.2 稳定性版本解析:Gemini 推理默认关闭、认证上下文修复与跨聊天流式渲染加固
【免费下载链接】DeepTutorDeepTutor: Lifelong Personalized Tutoring. https://deeptutor.info/.项目地址: https://gitcode.com/GitHub_Trending/dee/DeepTutor
DeepTutor v1.4.2(发布于 2026.05.28)是构建在 v1.4.1 之上的稳定性与打磨版本,核心解决四类问题:让 Gemini 2.5+ 推理模型在 Visualize 与 chat agent 全链路可用、修复同步 FastAPI 依赖导致认证用户被静默路由到 admin 工作区的回归(源码内记为 #481,发布说明记为 #485)、修正带原生工具调用的推理模型在标签协议下的消息解析,以及把平滑流式渲染铺到每一个聊天界面。阅读本篇后,你将理解这些修复的根因与实现位置,并掌握如何在agents.yaml、provider_registry与前端AssistantResponse层面对齐这些行为。
一、Gemini 2.5+ 推理默认关闭:三段执行路径的单一事实来源
1. 问题根因:thinking 默认开启会烧光 max_tokens 预算
Gemini 2.5 / Gemini 3 系列模型默认启用 thinking。若不显式在请求中发送reasoning_effort: "none",模型会把整个max_tokens预算消耗在推理上,最终对外表现为「空响应体」或「输出被截断」。
v1.4.2 将这一判断集中到deeptutor/services/llm/reasoning_params.py中导出的default_reasoning_effort_for,作为唯一事实来源,供三条互不相同的执行路径复用:
- OpenAI SDK 路径(
deeptutor/services/llm/executors.py内,取reasoning_effort or default_reasoning_effort_for(...)); - aiohttp 兜底路径(
deeptutor/services/llm/cloud_provider.py,同一定式); - reasoning-kwargs 构造器
build_openai_compatible_reasoning_kwargs(同上文件)。
2. 实现细节:子串匹配与大小写不敏感
核心实现是_PROVIDER_DEFAULT_OFF_PATTERNS字典 + 子串匹配:
_PROVIDER_DEFAULT_OFF_PATTERNS: dict[str, tuple[str, ...]] = { "gemini": ("gemini-2.5", "gemini-3"), }def default_reasoning_effort_for(provider: str | None, model: str | None) -> str | None: provider_name = (provider or "").strip().lower() off_patterns = _PROVIDER_DEFAULT_OFF_PATTERNS.get(provider_name) if off_patterns and _matches(model or "", off_patterns): return "none" return None注意注释中的两个设计点:
- 使用子串匹配,因此
models/gemini-2.5-flash这类带models/前缀的 id 也能命中; - provider 名与模型名均做
lower()归一化,大小写不敏感。
3. 测试锚定
tests/services/llm/test_reasoning_params.py用参数化用例锁定了这张表:
gemini-2.5-flash、gemini-2.5-pro、gemini-2.5-flash-lite、大写GEMINI-2.5-FLASH、models/gemini-2.5-flash、gemini-3.0-pro→ 均返回"none";- 遗留模型
gemini-1.5-*、gemini-2.0-flash→ 返回None(不受影响); - 其它 provider(openai、deepseek、dashscope)→ 不受影响;
- 显式传入的
reasoning_effort优先级更高("high"会覆盖默认的"none")。
因此,从 v1.4.1 升上来的用户如果之前接入 Gemini 2.5+ 后看到空输出或截断,无需任何配置改动——default-off 行为自动生效。如果你显式配置了高推理强度(如"high"),该值仍优先。
二、Visualize 流水线加固:三条独立故障链路
1. Per-capability max_tokens 默认值(16k)
Visualize 在agents.yaml中新增了自己的独立条目,默认16k tokens,且该默认值由DEFAULT_AGENTS_SETTINGS播种。这样,持有旧版data/user/settings/agents.yaml(其中完全没有提及 Visualize)的用户会自动拾取更高上限,无需手工编辑。
关键升级语义:若你之前为了调高 Visualize 的max_tokens而手改过data/user/settings/agents.yaml,你手写的值仍然优先。新的 16k 默认值只播种给「配置文件里根本没提到 Visualize」的用户。
2. SVG / HTML 根节点修剪
当模型在输出外层包裹散文(如Here you go: <svg>…),或把闭合围栏和闭合标签挤在同一行时,generator agent 现在会修剪到最外层<svg>…</svg>/<!doctype>…</html>,保证渲染器永远拿到干净的根节点。
3. Review 步骤 JSON-mode 崩溃 → 优雅降级
大型或复杂 SVG 偶尔会在 review 步骤触发 JSON-mode 转义问题。v1.4.2 不再让整个回合崩溃:Visualize 会记录失败日志,并直接送出未经过 review 的草稿,让用户至少能看到一个已渲染的结果。
三、认证请求落回正确工作区:sync 依赖导致的 ContextVar 丢失修复
1. 根因:FastAPI 对 sync 依赖使用线程池分发
v1.4.1 中require_auth是同步FastAPI 依赖。FastAPI 通过anyio.to_thread.run_sync分发 sync 依赖——即在请求上下文的副本下于工作线程中执行:
- 依赖内部调用
set_current_user(...),把用户安装到「线程的上下文」上; - 线程返回后该上下文被丢弃;
- 端点随后读到未设置状态下的默认值,回退到 admin 工作区;
- 于是每个已认证用户的读写都被静默路由到本地 admin 的数据上。
在 v1.4.1 中,认证用户会因此遭遇会话 404(见tests/api/test_auth_contextvar.py的注释说明)。
2. 修复:依赖全部改为 async def
deeptutor/api/routers/auth.py中:
require_auth与require_admin现在是async def,在与端点相同的 asyncio task中执行,因此依赖内写入的ContextVar在下游所有位置可见(require_admin内部以Depends(require_auth)链式依赖,同步 async 化保证整个链留在事件循环上);- HTTP 与 WebSocket 入口现在共用同一个
_install_current_user辅助函数,保证「由 token payload 解析出的用户对象」跨传输层完全一致:payload is None(即AUTH_ENABLED=false,未要求 JWT)→ 解析为本地 admin 用户;- 非空 payload → 经
user_from_token_payload解析为带 scope 的CurrentUser。
- 该函数返回 ContextVar 重置 token。HTTP 调用方可忽略(请求随 task 结束而回收);WebSocket 调用方必须持有 token 并在
finally中调用reset_current_user,因为 WS 连接的寿命长于依赖解析任务。典型用法见ws_require_auth返回的_WsAuthFailed哨兵分支:认证失败时先ws.close(code=4001)再让调用方立即return。
auth.py中还通过_bearer_token_from_header手写解析Authorization: Bearer <token>,刻意不使用HTTPBearer——因为它是基于Request注入的类依赖,而 FastAPI 不会为 WebSocket 依赖解析注入 Request,会让挂载 WS 端点的路由直接抛TypeError。手写解析让require_auth保持 HTTP/WS 对称。
3. 回归测试锚点
tests/api/test_auth_contextvar.py钉住了三条不变式:
require_auth/require_admin必须是协程函数(用inspect.iscoroutinefunction断言);_install_current_user(None)必须安装本地 admin(LOCAL_ADMIN_ID/LOCAL_ADMIN_USERNAME),而不是走 None 路径静默回退;- 端到端用例:
AUTH_ENABLED=true+ 合法 token 时,端点内能读到用户 ContextVar,且get_path_service()解析出的聊天库落在data/users/<uid>/前缀下,而非 admin 回退。
四、推理模型 + 原生工具调用:标签协议的修复
1. v1.4.1 的「小聪明」为何有害
v1.4.1 对「带原生工具调用能力的推理模型」做了两处取巧:
- 在系统提示中告诉模型可以忽略
TOOL/THINK/FINISH/PAUSE标签,仅依赖reasoning_content+tool_calls; - 在
run_labeled_step内把<think>前导和任何入站 tool-call delta 当作隐式标签解析。
实践中两处都出问题:
- 当工具调用以JSON 形式泄漏进内容流(而不是真正的
tool_callsdelta)时,系统没有标签可用于修复,循环会把「JSON 即答案」误判为FINISH; - 多轮「推理 + 工具」工作流要么浪费迭代做修复重试,要么静默提前终止。
2. v1.4.2 的新语义
- 面向「推理 + 原生工具」的系统提示明确告知模型:推理会显示在单独的 trace 区域,但正式内容流必须以
FINISH/TOOL/THINK/PAUSE中的一个精确开头; run_labeled_step(见deeptutor/core/agentic/labeled_step.py)不再把 tool-call delta 当作标签解析的依据;implicit_think_label参数被有意忽略(仅为 API 兼容而保留);- 缺失标签一律落到
LABEL_UNKNOWN(deeptutor/core/agentic/labels.py中定义为"UNKNOWN"),由 chat 流水线的protocol-repair 路径接管,而不是静默错路由回合; - 内联的
<think>...</think>前导会被实时流式送入 reasoning 子 trace,并从返回给循环的正式text中剥离——答案区域不再泄漏 provider 原始标记。
由此,测试tests/agents/chat/test_agentic_parallel_tools.py验证了「推理 + 原生工具」路径仍能解析多工具回合;tests/core/test_labeled_step_think_prelude.py更新为「标签始终必需」的语义。
五、平滑流式渲染铺满每个聊天表面
1. 复用主聊天的 rAF 打字机
上周为主聊天引入的 rAF 对齐打字机useSmoothStreamText(见web/hooks/useSmoothStreamText.ts)现已接入web/components/common/AssistantResponse.tsx。于是书本聊天面板、测验追问标签页以及任何渲染 assistant 消息的表面,在流式期间都获得相同的逐帧(frame-aligned)节奏;对已完结消息则退化为 no-op 直通,不干扰历史消息的瞬时渲染。
2. 配套修复三件套
- Autoscroll 改为 layout 阶段钉住:书本聊天面板与测验追问标签页把自动滚动移到
useLayoutEffect,并停止使用scrollIntoView({behavior: "smooth"})——快速流式下平滑动画会与下一帧布局更新竞争,产生可见抖动。现在改为在 layout 阶段做一次scrollTop = scrollHeight钉住,与主聊天上useChatAutoScroll(web/hooks/useChatAutoScroll.ts)的行为一致。 - 全局 overflow-anchor 抑制:书本聊天面板给滚动容器标记
data-chat-scroll-root,使全局的overflow-anchor: none规则生效——当光标上方的代码块回流时,浏览器内置滚动锚定会与手动钉住打架。 - AssistantResponse 记忆化:组件改为 memoized,当无关的流式兄弟节点更新父级时,已完成的气泡不再重复解析 markdown。
六、侧边栏改版与本地 provider 支持
1. Sidebar Redesign(纯前端)
- 展开侧边栏的聊天会话列表移入独立的可折叠Recents区域,拥有独立滚动视口——长历史不再把次级导航挤出屏幕;
- 「New chat」按钮被移除(点击导航中的Chat即会开启新会话);
- 页脚在 GitHub 链接旁新增 Docs 链接;
- 每个会话渲染一个确定性、友好的 Lucide 图标(sparkles、leaf、feather、cloud、droplet、sun、moon、flame、star 等),re-render 时不 shuffle;运行中的会话有轻微 wiggle 动画,空闲会话保持静止。
2. Lemonade 本地 provider
deeptutor/services/provider_registry.py新增lemonade绑定,面向AMD Ryzen AI / NPU 运行时:
ProviderSpec( name="lemonade", keywords=("lemonade",), env_key="LEMONADE_API_KEY", display_name="Lemonade", backend="openai_compat", is_local=True, detect_by_base_keyword="13305", default_api_base="http://localhost:13305/api/v1", ),- 自动检测:按端口
13305探测,无需 API key(env_key为空串场景下不强制校验); - 默认 base URL:
http://localhost:13305/api/v1; - 与 Ollama(11434)/ LM Studio(1234)/ llama.cpp(8080)等本地 OpenAI-compat 服务并列,README 的 Docker host-gateway 一节与 provider 配置文档中均已收录。
3. models-endpoint 探测遵循DISABLE_SSL_VERIFY
上下文窗口自动探测(models-endpoint probe)此前未遵循全局 SSL 策略:面对自签名证书的本地推理服务器,探测因无法验证证书而失败,只能回退默认上下文窗口。v1.4.2 中,当设置DISABLE_SSL_VERIFY时,该探测会向 aiohttp session 传入aiohttp.TCPConnector(ssl=False),与 HTTP 层其余部分保持一致。
tests/services/config/test_context_window_detection.py新增用例:注入 FakeConnector 捕获探针实际使用的连接器参数,验证DISABLE_SSL_VERIFY生效时连接器确以ssl=False构造。注意:该环境变量在生产环境是被拒绝的(deeptutor/services/llm/openai_http_client.py抛LLMConfigError("DISABLE_SSL_VERIFY is not allowed in production")),仅在本地自签名服务场景使用。
七、测试矩阵与升级指引
1. 新增/更新的测试
| 测试文件 | 锚定内容 |
|---|---|
tests/api/test_auth_contextvar.py | #485(源码记为 #481)回归:syncrequire_auth丢 ContextVar,async 版本跨依赖边界保留 |
tests/services/llm/test_reasoning_params.py | 集中式default_reasoning_effort_for映射表 |
tests/core/test_labeled_step_think_prelude.py | 「标签始终必需」新语义;implicit_think_label被忽略 |
tests/agents/chat/test_agentic_parallel_tools.py | 推理 + 原生工具路径仍能解析多工具回合 |
tests/services/config/test_context_window_detection.py | models 探测尊重DISABLE_SSL_VERIFY,传TCPConnector(ssl=False) |
2. 从 v1.4.1 升级
- pip 用户:
pip install -U deeptutor; - Docker 用户:拉取
ghcr.io/hkuds/deeptutor:latest; - 曾手改
agents.yaml的用户:你为 Visualize 手写的max_tokens仍生效;16k 只播种给未提及 Visualize 的配置; - Gemini 2.5+ 用户:空输出 / 截断问题无需改配置,default-off 自动生效。
说明:文档通篇核心指向当前仓库的
deeptutor/、web/与tests/目录,以上所有行为均可溯源到对应源码与测试,未涉及任何外部能力或性能数据的断言。
【免费下载链接】DeepTutorDeepTutor: Lifelong Personalized Tutoring. https://deeptutor.info/.项目地址: https://gitcode.com/GitHub_Trending/dee/DeepTutor
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考