DeepTutor 的 api 层:一个 FastAPI 实例、装配顺序与全部路由挂载点

2026-08-10

读一个后端项目的路由层,最容易掉进去的坑是从最大的那个 router 开始读。DeepTutor 里最大的 router 是 deeptutor/api/routers/knowledge.py,2962 行——从这儿进去,读三个小时也拼不出整体形状。

更省时间的路径是先读装配点。DeepTutor 的 deeptutor/api 包整体是 40 个 .py 文件、15796 行(截至我们采集时),但所有 router 的挂载都收在 deeptutor/api/main.py 一个文件里app.include_router 的 prefix 与顺序一眼可见。

以下行号与数字都对应我们采集的仓库快照:HEAD 456f9c2,采集日 2026-08-10,deeptutor/__version__.py 里的版本号是 1.5.11。你自己 clone 之后行号可能已经漂了,文件名与参数名是稳的。

一、app 那十行里,有一个被主动关掉的默认行为

应用实例的构造在 deeptutor/api/main.py:234-243

app = FastAPI(
    title="DeepTutor API",
    version="1.0.0",
    lifespan=lifespan,
    redirect_slashes=False,
)

前三个参数没什么可说的。值得停一下的是第四个。

redirect_slashes 是 FastAPI/Starlette 的一个默认开启的贴心行为:请求 /api/v1/foo 而路由注册的是 /api/v1/foo/(或反过来)时,框架发一个 307 重定向把你导到正确的那个。绝大多数项目不会去动它。

DeepTutor 把它关了,理由直接写在 main.py:238-242 的注释里:部署在 HTTPS 反向代理后面时,这个 307 重定向可能把 HTTPS 换成 HTTP,也就是协议降级。注释同时给了 issue 编号 #112。

这处反直觉在于:出问题的不是你写的代码,是框架替你做的一个”修正”。反代把 HTTPS 卸载掉之后,应用自己看到的是明文 HTTP,它据此生成的重定向 Location 也就成了 http://,浏览器跟过去就掉出了加密链路。关掉之后代价是尾斜杠不再被自动纠正——路径写错就是 404,不再有兜底。这个取舍是项目的选择,我们没有部署过,不评价它在别的部署形态下合不合适。

二、启动时先做一次”配置漂移”检查

main.py:36-68 定义了一个校验:capability manifest 里引用、但没有在 ToolRegistry 里注册的工具,会直接抛 RuntimeError;调用点在 main.py:112,属于 lifespan 启动路径的一部分。

也就是说这类不一致是启动期 fail-fast,而不是等到某轮对话真的去调那个工具时才炸。排查方向因此很明确:如果 DeepTutor 起不来且报的是这个 RuntimeError,去比对 capability manifest 与 ToolRegistry 两侧的工具名,不用去翻对话链路。

三、lifespan 的启停序列,是对称着写的

启动序列(main.py:114-170):初始化 LLM client → 启动 EventBus → auto_start_partners() → 启动 cron → ping PocketBase → 迁移 v1 memory,并把 legacy 的 tutorbot memory surface 改名为 partner

关闭序列(main.py:177-231):停 cron → 停 partners(带 preserve_auto_start=True)→ 关 MCP 连接 → 关 LLM provider 池 → 关 agentic client 池 → 停 EventBus。

两段对照着看会更省事。关闭时 preserve_auto_start=True 这个参数的意思是这次停机不改写”下次是否自动启动”的状态——也就是说进程重启后 partners 仍会被 auto_start_partners() 拉起来。partners 是 IM 渠道层,我们另有一篇专门讲它。

值得注意的是启动序列里没有”等 PocketBase 就绪”这种阻塞语义,只是 ping;而 memory 迁移排在最后。这些顺序都能在上面两个行段里逐行对上。

四、CORS:鉴权关掉的时候,反而是放开的

CORS 配置在 main.py:79-99,应用在 main.py:290-297。默认放行四个来源:http://localhost:{frontend_port}http://127.0.0.1:{frontend_port},以及 3000 端口的这两个变体。

关键在下一行:auth 关闭时 allow_origin_regex 被设为 r"https?://.*",模式标记为 permissive;auth 开启时改为 explicit,跨源来源需要显式配置 CORS_ORIGIN(S)

这个方向和直觉是反的——通常我们预期”鉴权关掉”意味着更小心,而这里是鉴权关掉→ CORS 放开到任意来源。这个取舍写在代码里,我们只照抄取值,不去推断它的动机,也不推断这个默认值适不适合你的网络环境;只提醒一句:这是默认配置,不是”这样部署就安全”的结论。鉴权机制本身(JWT、cookie、WebSocket 侧怎么校验)我们另有一篇专门讲。

五、日志与进程:两处容易找不到的开关

  • 访问日志只记非 200。自定义中间件在 main.py:245-256,uvicorn 自身的 per-request access log 在所有启动路径上都被关掉(deeptutor/api/run_server.py:77access_log=False)。所以你在日志里翻不到成功请求不是日志坏了,是设计如此。
  • 绑定与热重载:uvicorn 绑 host="0.0.0.0",端口来自配置里的 get_backend_port()reload 默认关闭、由环境变量 DEEPTUTOR_DEV_RELOAD 打开(run_server.py:64-79)。
  • Windows 侧单独一条run_server.py:14-18 在 Windows 下强制 WindowsProactorEventLoopPolicy,注释给的理由是子进程 API(Math Animator 渲染等)需要。这一行同时说明了一件该讲清楚的事——这个后端会在你本机拉起子进程。往后看还有更直接的:路由表里的 /api/v1/space/cli-apps 对应的 cli_apps 是”管理员安装、chat agent 调用的命令行工具”(模块 docstring 在 deeptutor/services/cli_apps/__init__.py:1),而 subagent 相关能力会把你本机已经装好的 agent CLI 当作子代理来驱动(deeptutor/services/subagent/__init__.py:1-7)。要不要开、开给谁,是需要你自己按环境判断的事,本文不给方案。

六、路由挂载点全表

下面这张表是本篇的主数据:每一行是 app.include_router 的一个 prefix,以及对应 router 文件里路由装饰器的条数(grep -c 数出来的)。看表的方法是按数量找重心,而不是逐条记端点。

挂载 prefix路由装饰器数
/api/v1/auth(公开,无 _auth 依赖)15
/api/outputs1(@router.api_route
/api/v1/multi-user5
/api/v1(chat)4
/api/v1/question2(均为 WebSocket)
/api/v1/knowledge49
/api/v1/imports / /api/v1/dashboard2 / 2
/api/v1/learning(mastery_path)8
/api/v1/co_writer / /api/v1/notebook12 / 11
/api/v1/book / /api/v1/memory23 / 27
/api/v1/capabilities / /api/v1/sessions2 / 7
/api/v1/question-notebook12
/api/v1/settings46
/api/v1/settings/mcp / /api/v1/space/mcp5 / 8
/api/v1/space/cli-apps5
/api/v1/skills / /api/v1/subagents12 / 10
/api/v1/personas / /api/v1/tools5 / 1
/api/v1/system6
/api/v1/voice2(POST /ttsPOST /stt
/api/v1/plugins / /api/v1/agent-config4 / 2
/api/attachments1
/api/v1/partners32
/api/v1(unified_ws)1(WS /ws
/api/v1(quiz_judge)1(WS /question/judge

重心一眼可见:knowledge(49)、settings(46)、partners(32)、memory(27)四块占了将近一半。想快速摸清这个项目在做什么,从这四个 prefix 进去比从首页进去有效。

一句必要的限定:/api/v1/learning 这 8 条端点背后的掌握度计算、复习间隔一类参数,是该项目的实现选择,不是经过验证的教学结论;本篇只讲它挂在哪个 prefix 下,不评价它的教学效果。

七、表里有四个例外,比表本身更值得记

  1. /api/outputs 用的不是常规装饰器。它是一条 @router.api_route("/{output_path:path}", methods=["GET","HEAD"])deeptutor/api/routers/outputs.py:40),所以按 @router.get|post|... 去 grep 时数不到它。
  2. /api/v1/partners 是唯一整体挂 require_admin 的业务 routermain.py:464-466)。别的 router 是逐端点判定,这个是整块拦死。
  3. /api/v1/settings 里有一条是公开的GET /ui 挂在 public_router 上、无需登录(main.py:410-421deeptutor/api/routers/settings.py:1153)。理由写在代码里:登录页得先拿到界面语言。
  4. 两个 WebSocket router 的鉴权在 handler 内部做main.py:474-480):unified_wsWS /wsquiz_judgeWS /question/judge,原因是 WebSocket 用不了常规的 FastAPI 依赖。所以你在 include_router 那一行看不到它们的鉴权,得进 handler 里找。

八、可复现的核查动作

这几步你自己 clone 之后就能跑,不需要装依赖、不需要启动服务:

# 1. 数路由装饰器总数(不含 outputs 的 api_route)
grep -rnE '@router\.(get|post|put|delete|patch|websocket)|@public_router\.' \
  deeptutor/api/routers/ deeptutor/multi_user/router.py | wc -l

# 2. 看所有挂载点与顺序
grep -n 'include_router' deeptutor/api/main.py

# 3. 直接跳到那处被关掉的默认行为
sed -n '234,243p' deeptutor/api/main.py

第一条命令在我们采集时得到 322。把上面表格里除 /api/outputs 之外的各项相加,同样是 322——这两个数能对上,说明表没有漏项。

322 不等于 322 个可访问 URL:同一路径的不同方法会各占一个装饰器(比如 GET|DELETE /chat/sessions/{session_id} 就是两条),我们也没有逐条拼出完整 URL 全表来核实是否存在别的重复计数情形。要精确到 URL,得自己展开。

另一个值得做的比对:把第 2 步的输出和 deeptutor/api/routers/ls 结果对一遍,看有没有存在文件但未被挂载的 router。

九、一处文档与代码对不上的地方

仓库根目录的 AGENTS.md 有一张 Key Files 表(AGENTS.md:101-118)。这张表在 api 层只列了一个文件:deeptutor/api/routers/unified_ws.pyAGENTS.md:117)。本文引用到的 deeptutor/api/main.py、上面那几个高密度 router,以及 deeptutor/multi_user/deeptutor/logging/deeptutor/i18n/ 等包,都不在这张表里。

两处口径不一致,以我们实读的仓库状态为准。至于为什么不一致,我们没有依据,不推断。

实际影响只有一条:如果你把 AGENTS.md 的 Key Files 当作 api 层的入口索引,会漏掉装配点本身。读这一层,还是直接从 main.py 进。


本文依据 DeepTutor 官方仓库(github.com/HKUDS/DeepTutor)的 README、AGENTS.mdpyproject.tomldeeptutor/ 下的源码整理,核对日 2026-08-10,对应仓库快照 456f9c2(版本 1.5.11)。 本文内容为仓库源码与文档口径,我们没有安装、部署或运行过该项目,也没有调用过其中任何一个模型 API, 因此不涉及生成质量、响应速度与教学效果的任何描述。 参数与默认值随版本变动,请以仓库最新代码与 --help 的实际输出为准。 该项目会在本机启动子进程并驱动已安装的命令行工具,安全相关做法请结合自身环境评估,本文不构成安全方案建议。

想系统学会用 AI?报名体系课或加入会员,照着学、照着用。