DeepTutor tools 工具面全景导读¶
- 基线:origin/dev @
fe003ddce(v1.6.12)。 - 范围:
deeptutor/tools/全部顶层模块 +vision/子包 +builtin/注册集 + 与deeptutor/capabilities/的调用关系 +tests/tools/覆盖对照。 - 所有锚点形如
path:line,相对仓库根。
1. 这一层是什么:四段注册链¶
工具从"一个 Python 类"走到"模型这一轮真正能调用",经过四段链路:
BaseTool 协议 BuiltinToolSpec 目录 ToolRegistry 进程表 每轮 compose
(核心协议,谁都能实现) → (启动期零导入的"花名册") → (按名懒加载实例 + 别名解析) → (per-turn 决定挂哪些)
deeptutor/core/tool_protocol.py:220 deeptutor/tools/builtin_specs.py:20 deeptutor/runtime/registry/tool_registry.py:26 deeptutor/agents/_shared/tool_composition.py:170
- 协议层
deeptutor/core/tool_protocol.py ToolParameter:17(单个参数,含sensitive:43脱敏、items:36数组内层 schema)、ToolDefinition:56(OpenAI function-calling schema,raw_parameters:69支持整段 JSON-Schema 直通)、ToolResult:130(content给模型、sources给引用、pause_for_user:164让ask_user暂停回合等用户、model_message:167只进模型上下文的私有多模态附件)、BaseTool:220(deferred:246标记渐进披露工具)、ToolLookup:184(注册表只读面)。- 目录层
deeptutor/tools/builtin_specs.py:20BuiltinToolSpec - 只存
name + "module:Class"字符串,load_class():24/create():32时才 import,并在create()里校验类名与目录名不漂移。BUILTIN_TOOL_SPECS:46是唯一的内置工具花名册(共 94 个名字,见 §5),BUILTIN_TOOL_NAMES:207、TOOL_ALIASES:217(pdf→read_source、rag_hybrid→rag{mode:hybrid}等 4 条)、LazyBuiltinToolTypes:228(给 Settings API 的惰性类序列)。 - 进程注册表
deeptutor/runtime/registry/tool_registry.py:26ToolRegistry load_builtins():55只把目录 dict 拷进来(零导入);get():96/execute():151命中时才_load_builtin():59实例化;_resolve_request():75做别名合并;get_tool_registry():170进程级单例。- 多用户外部工具(MCP 等)不进进程表,走
deeptutor/runtime/registry/scoped_registry.py:40ScopedToolRegistry:per-turn overlay + 派发时授权(execute拒绝未授权 provider 工具,注释见文件头 1-21 行)。 - 每轮组合
deeptutor/agents/_shared/tool_composition.py:170compose_enabled_tools - 纯函数,chat 与 quiz 共用。顺序:用户开关工具 → 条件自动挂载 → 能力自有工具 → 常开工具(
write_memory/web_fetch/github/ask_user/cron,deeptutor/agents/_shared/tool_composition.py:262)→forced/suppressed修正(_finalize:268)。AUTO_MOUNTED_TOOLS:40直接由CONFIGURABLE_BUILTIN_TOOL_NAMES派生,两个面永不漂移。
2. 能力选择点(这一轮挂什么、参数从哪来)¶
- 条件挂载表
_CONDITIONAL_MOUNT_FLAGS(deeptutor/agents/_shared/tool_composition.py:47):rag/kb_files/knowledge_frontier ← has_kb、read_source ← has_sources、read_memory ← has_memory、list_notebook/write_note ← has_notebooks、question_bank ← has_question_bank、read_skill ← has_skills、load_tools ← has_deferred_tools、exec ← has_exec、四个mastery_*导航工具 ←has_mastery_nav/has_mastery_topics。探测函数(user_has_memory:287、user_has_notebooks:307、user_has_mastery_topics:324、user_has_question_bank:340)一律 fail-closed。 - 管线装配点
deeptutor/agents/loop/pipeline.py:726AgenticLoopPipeline._compose_enabled_tools:逐项填ToolMountFlags:142(has_kb只统计"能力不持有的共选 KB"——纯 Obsidian 回合不挂 rag,issue #650;has_sources在答案环恒为 False,read_source由 explore_context 前置阶段持有,deeptutor/agents/loop/pipeline.py:739-742);随后drop_unconfigured_generation_tools(deeptutor/agents/_shared/tool_runtime.py:20)摘掉没配模型的imagegen/videogen,workspace_export在注册表存在时追加(deeptutor/agents/loop/pipeline.py:778-781)。 - 服务端注入参数缝
deeptutor/agents/loop/pipeline.py:1323_augment_tool_kwargs:模型永远不填的参数都在这里注入——rag的mode:"hybrid"与_vision_supported、cron的_cron_owner(路由归属,deeptutor/agents/loop/pipeline.py:1366-1395)、write_note的真实conversation_history、geogebra_analysis的首图 base64、web_search的 output_dir 等。读任何工具的"输入契约"时要意识到:schema 是模型可见的一半,这一层是另一半。 - Prompt 提示层
deeptutor/tools/prompting/__init__.py:52load_prompt_hints从prompting/hints/<zh|en>/<tool>.yaml读每工具的 when_to_use/guideline;compose_prompt_text:203提供 5 种渲染格式。这是"能力选择"的软一半:模型看到什么决定它挑什么。 - 渐进披露
BaseTool.deferred(deeptutor/core/tool_protocol.py:246)+load_tools工具(deeptutor/tools/builtin/__init__.py:1731):MCP 等外部工具只以一行清单进系统提示,模型用load_tools拉全 schema;DeferredToolLoader在deeptutor/runtime/registry/deferred_tools.py:116。
3. 顶层模块一览表¶
deeptutor/tools/ 下 29 个顶层模块,按角色分组(锚点为模块入口/核心函数):
| 模块 | 角色 | 挂载类别 | tests/tools 覆盖 |
|---|---|---|---|
builtin/(__init__.py) |
21 个内置工具的 BaseTool 包装层 | 见 §5 | 部分(见 §7) |
builtin_specs.py |
注册目录(§1) | — | 间接(tests/core) |
ask_user.py |
澄清卡 payload 构建 | 内置 ask_user 的实现体 | ✅ test_ask_user.py |
brainstorm.py |
发散式一次 LLM 调用 | 用户可开关 | ❌(包装层在 tests/core) |
reason.py |
深推理一次 LLM 调用 | 用户可开关 | ❌(包装层在 tests/core) |
rag_tool.py |
RAGService 薄包装 | 条件自动挂 | ✅ test_rag_tool.py |
web_search.py |
纯 re-export services.search | 用户可开关 | ✅(测的是 services 层) |
web_fetch.py |
抓 URL→markdown(含 SSRF 防护) | 常开 | ✅ test_web_fetch.py |
exec_tool.py |
唯一沙箱执行面 | 条件(has_exec) | ✅ 三个测试文件 |
cron_tool.py |
定时任务 schedule/list/cancel | 常开 | ❌(tests/services/cron 覆盖) |
github_query.py |
gh CLI 只读查询 | 常开 | ✅ test_github_query.py |
knowledge_frontier.py |
KB 前沿论文发现 | 条件(has_kb) | ✅ test_knowledge_frontier_tool.py |
paper_search_tool.py |
arXiv 搜索 | 用户可开关 | ❌(包装层在 tests/core) |
zotero_search.py |
Zotero Web API 检索 | 用户可开关 | ✅ test_zotero_search.py |
read/list/write 笔记三件套:list_notebook.py、write_note.py |
笔记索引/读写 | 条件(has_notebooks) | ✅ 两文件均有 |
question_bank.py |
错题库六动作 | 条件(has_question_bank) | ✅ test_question_bank_tool.py |
mastery_nav.py |
mastery 图谱四个只读导航工具 | 条件(has_mastery_nav) | ⚠️ tests/tools 无(deeptutor/learning/tests/test_mastery_navigation.py 有 19 例) |
mastery_tool.py / solve_tool.py |
兼容 re-export(实现已迁 capabilities) | — | 随 capabilities 测试 |
media_gen_tool.py |
imagegen/videogen | 用户可开关 + 模型配置门 | ❌(tests/services/test_media_gen 覆盖服务层) |
partner_memory.py |
伙伴三分区记忆/历史 | 伙伴回合强制挂载 | ❌(tests/services/partners 覆盖) |
workspace.py |
工作区 list/read/search/present/export | 基线常挂 | ⚠️ 仅 flow 级(test_pptx_workspace_flow 间接) |
file_tools.py |
read_file/write_file/edit_file/list_dir | 未注册(休眠模块) | ✅ test_file_tools.py |
vision/ |
图像/GGB 子包 | 供 geogebra/可视化链路 | ❌ tests/tools 无(tests/visualizers 有 ggb_validator) |
question/ |
MinerU 解析 + 出题 | research/question 管线专用 | ✅ test_question_extractor.py、test_mineru*.py |
tex_chunker.py / tex_downloader.py |
arXiv LaTeX 下载与分块 | research 管线专用 | ❌ 薄弱 |
prompting/ |
提示 hints 加载/渲染 | 全工具共用 | ⚠️ 仅经 exec 提示测试间接覆盖 |
__init__.py |
顶层 lazy export(_LAZY_EXPORTS:7) |
— | — |
4. 每个顶层工具一卡(输入 / 输出契约 / 失败语义)¶
4.1 内置包装层 builtin/(21 个工具的 BaseTool 面孔)¶
每个卡 = 一个 BaseTool 子类,get_definition 给 schema、execute 委托给同目录实现模块。共同基类 _PromptHintsMixin:26(builtin/__init__.py,下同)接上 §2 的提示层。
- brainstorm(
BrainstormTool:33):入参topic(必填)+context;输出ToolResult(content=answer, metadata={topic, answer, model})。失败:无模型配置时实现体抛ValueError(deeptutor/tools/brainstorm.py:69),由循环兜成工具错误。 - rag(
RAGTool:105):入参query+kb_name(二者非空否则raise ValueError:127-132);输出正文+sources(_rag_sources:68用检索自身 provenance,仅成功非空答案才回退 query echo,issue #694/#1500)+success=not failed。失败语义:error_type/needs_reindex转成给模型的一句话正文;命中视觉素材时按_vision_supported决定注入像素(_rag_visual_model_message:182,多用户作用域先解析再读文件)或提示模型"没看图别装看过"。 - kb_files(
KbFilesTool:227):kb_name必填,patternglob/子串、limit由_kb_files_limit:312钳制;KB 不可访问抛ValueError:289。语言由服务端注入(§2)。 - knowledge_frontier(
KnowledgeFrontierTool:323):kb_name必填 +focus/max_papers≤10/years_limit≤10;输出报告 +sources(kb_sources + arxiv 论文行)。KB 不可访问抛ValueError:377;只推荐不导入。 - web_search(
WebSearchTool:406):query(缺省由 §2 注入用户消息);asyncio.to_thread包同步搜索(asyncio.to_threadatdeeptutor/tools/builtin/__init__.py:422);dict 结果取answer/citations,非 dict 兜成字符串。无显式失败分支——搜索服务异常会上抛由循环兜底。 - reason(
ReasonTool:446):query+context;一次 LLM 调用(deeptutor/tools/reason.py:42)。无模型配置抛ValueError(deeptutor/tools/reason.py:81)。 - paper_search(
PaperSearchToolWrapper:484):query/max_results≤20/years_limit/sort_by;失败软着陆:任何 arXiv 异常→ToolResult("arXiv search is temporarily unavailable…", error:True)(:526-531),空结果给"No arXiv preprints found"。 - zotero_search(
ZoteroSearchToolWrapper:564):query/user_id必填 +api_key(sensitive=True)/max_results≤25;ZoteroSearchError码表(:567-573)把 invalid_api_key/library_not_found/rate_limited/network_unavailable/invalid_response 翻成模型可读文案,success=False。api_key走sensitive不进 trace。 - read_source(
ReadSourceTool:821):source_id(nb-/bk-/rd-/hs-/qb-/at-前缀)从注入的source_index取全文;id 缺失时唯一来源自动补齐,多来源报错;未知 id 回显合法 id 列表(:872-881)。工具本身无状态、无 IO。 - read_memory / write_memory(
ReadMemoryTool:888/WriteMemoryTool:918):read 无参、读 L3 四文档拼接;write 校验op∈{add,edit}与非空text(:973-980),先落 L1 trace 再write_preference;拒绝时content="write_memory rejected: …",去重时显式回 "already saved"(issue #647)。 - read_skill(
ReadSkillTool:1637):name必填 +file默认 SKILL.md;技能名不存在→列可用技能;文件不存在→列出该技能前 40 个文件(_SKILL_FILE_LIST_LIMIT:309)终止重试环;非法名/路径转success=False文案。 - load_tools(
LoadToolsTool:1731):names数组;_tool_loader服务端注入,缺失时返回"(unavailable)";未知名单独列出,success规则见:1784。 - web_fetch(
WebFetchTool:1023):url必填 +max_chars(默认 50000,解析失败兜默认);FetchOutcome.ok=False原样转success=False。实现卡见 §4.2 web_fetch。 - list_notebook / write_note(
ListNotebookTool:1087/WriteNoteTool:1304):实现契约见 §4.2。write_note 的 append 默认正文=真实转录(注入conversation_history),只有显式给content才写 agent 正文。 - question_bank(
QuestionBankTool:1137):action六枚举 + 场景参数(question/answers/category/entry_ids/limit);outcome.ok=False时content=error。实现卡见 §4.2。 - github(
GithubTool:1430):query_type∈{pr,issue,run,repo,api}+target;只读构造(见 §4.2 github_query);gh 缺失/超时/非零退出都转success=False文案。 - ask_user(
AskUserTool:1493):questions1-4 条 +intro;payload 校验失败转success=False(:1620-1621);成功时返回占位正文 +pause_for_user暂停回合等回复。实现卡见 §4.2。 - cron(
CronTool:1789):action∈{schedule,list,cancel}+ 排程三选一(at/every_seconds/cron_expr,deeptutor/tools/cron_tool.py:59-68强制恰一个);_cron_owner缺失→"not available in this context"(deeptutor/tools/cron_tool.py:77-82);在定时任务上下文里禁止再排程(:107-111);时间解析失败/未知 job 均为ok=False文案。 - geogebra_analysis(
GeoGebraAnalysisTool:687):question+image_base64(无图→success=False:729-733;非 data URI 防御性补前缀:739-740);240s 视觉调用上限(_VISION_ANALYSIS_TIMEOUT_S:693,超时/管线异常都转success=False);language服务端注入、拒绝模型覆盖(:726-727)。
4.2 实现模块卡¶
- ask_user.py:
build_ask_user_payload:102把 v2{questions[]}与 legacy{question, options}归一成AskUserPayload;上限常量MAX_QUESTIONS=4/MAX_OPTIONS=8:30-31;丢弃截断流修复出的"半个 option"(_drop_half_written_options:217)、去重 label/body(#1409)、去模型自加的 "Other";错误一律(None, error_message)返回而非抛出。build_ask_user_preview:160用 json_repair 把流式半截参数画成卡片预览(best-effort,不影响真实派发)。 - brainstorm.py / reason.py:同构的一次 LLM 调用(
brainstorm:45/reason:42),agent 参数取自配置(get_agent_params("brainstorm")/("solve")),返回{topic|query, answer, model}。失败:ValueError("No model configured…");LLM 异常上抛。 - rag_tool.py:
rag_search:15校验非空 query/kb_name,多用户走resolve_for_rag(无显式 base_dir 时),KB 不可访问抛ValueError:41;同文件还有initialize_rag:54/delete_rag:66(索引生命周期,非 chat 工具)。 - web_search.py:纯 re-export(
:26-42);真身在deeptutor/services/search/__init__.py:131。 - web_fetch.py:
fetch_url_as_markdown:73→FetchOutcome:57。安全姿态(文件头 10-22 行自述):仅 http/https;私网/环回/链路本地地址拒绝且 pre-flight + 每一跳重定向后复查(_is_disallowed_host:177,DNS 失败 fail-closed);响应硬顶 4MB(MAX_RESPONSE_BYTES:39)、正文截断max_chars;15s 超时、最多 5 跳。正文抽取优先复用 html_extractor,失败退正则路径(_extract_readable:242)。 - exec_tool.py:
ExecTool:36是唯一沙箱执行面。_execute_shell:192过_DENY_PATTERNS:19(rm -rf、mkfs、fork 炸弹等→sandbox.command_blocked);_execute_source:223把 python/c/cpp 源码落盘再跑(平台命令拼装_command_for_platform:106);超时 30s 默认、300s 上限(_DEFAULT_TIMEOUT/_MAX_TIMEOUT:32-33);产物经_render_result:301快照-收集-渲染,success=ok and exit_code==0:366。_sandbox_*kwargs 全部服务端注入。 - cron_tool.py:
run_cron_action:76纯函数;三动作 +add/remove旧别名(:86-89);schedule 参数构造_build_schedule:59(恰一个时间源,否则 ValueError);无_cron_owner即拒绝。输出给模型的是渲染文本。 - github_query.py:
run_github_query:66→GithubOutcome:49。argv 白名单_build_argv:152只有 view/list,api分支固定 GET、不经过 shell(:192-196);gh 不安装→友好ok=False;20s 超时、16k 输出截断。 - knowledge_frontier.py:
discover_frontier:26三步:KB 摘要(内部调 rag)→ LLM 派生 ≤3 个 arXiv 查询(失败时退化用文档名)→ 搜索去重渲染。参数钳制_clamped_int,文档名 fallback 保证"删掉 LLM 也还有结果"。 - list_notebook.py:
list_notebooks_or_records:49双模式(索引/下钻);上限 50 笔记本/80 记录(:31-32);未知 notebook_id 回显合法 id(:121-126);错误返回ListOutcome(ok=False, error=…)不抛。 - write_note.py:
write_note:61双模式 append/edit;长度帽 title 200/note 4k/content 200k(:37-39);turns_to_include默认 3 或 "all"(:43-44);依赖注入notebook_manager/conversation_history便于测试;一切失败走WriteOutcome(ok=False)。 - question_bank.py:
run_question_bank:470六动作(ACTIONS:45);_coerce_ids:77把模型给脏的 entry_ids 拆成"可用+拒绝"两份,绝不静默丢弃;list 上限 20/100(:52-53);伙伴回合把写路径解析到 learner 真实库(_partner_bank_paths:137);全部依赖注入store。 - mastery_nav.py:四工具(
MASTERY_NAV_TOOL_NAMES:56)mastery_topics/sessions/open_session/new_session。两条安全线(模块头 18-32 行):只读(不租约、不登记、不动 mastery 等级)与不导航(后两者只产mastery_handoff卡,HANDOFF_META_KEY:54,学习者点击才跳)。所有 id 先对 store 校验(_topic_or_error:80);在当前话题自己的会话里拒绝发"开新会话"卡(_handoff_lands_where_we_already_are:94,#1412/#1411)。 - media_gen_tool.py:
ImagegenTool:135(prompt必填、n≤4)与VideogenTool:217(aspect_ratio/duration)。未配置模型→ValueError转success=False;GenerationProviderError→失败文案;产物写进工作区 outputs/ 并以 artifacts 返回,呈现必须再走 workspace_present(模块头 10-15 行)。 - partner_memory.py:三工具
partner_read/memorize/search(PARTNER_BUILTIN_TOOL_NAMES:26)。分裂记忆模型(模块头 3-15 行):owner 的 L3 只读 + 伙伴自有分区可写;产品 chat 中被抑制、伙伴回合强制挂载。search 是伙伴会话历史的关键词扫描(snippet 140 字、上限 300 命中,:32-33)。 - workspace.py:五工具
WorkspaceListTool:25 / ReadTool:73 / SearchTool:116 / PresentTool:156 / ExportTool:219。前三只读(WorkspaceError统一转_failure:21);present 是"把文件以卡片呈现给用户"的唯一正当路径;export 是唯一越出 outputs/ 的写,且必须经 ask_user 授权卡一次一签(:269-327)。前四个是WORKSPACE_BASELINE_TOOLS(deeptutor/agents/_shared/tool_composition.py:73)——独占能力回合与伙伴过滤都摘不掉的基线。 - paper_search_tool.py:
ArxivSearchTool:24;抓取量 = 2×需求(上限 30)再按年过滤截断(:72、:116);30s 超时、429 退避重试一次(:94-107);一切异常→空列表(由包装层转文案)。 - zotero_search.py:
ZoteroSearchClient:33+ 码化异常ZoteroSearchError:24;401/403→invalid_api_key、404→library_not_found、429→rate_limited、网络→network_unavailable、非 JSON→invalid_response(:81-97);user_id 白名单正则(:19)。 - file_tools.py:
ReadFileTool:41/WriteFileTool:109/EditFileTool:134/ListDirTool:189。路径必须在_allowed_dir内(_resolve_workspace_path:13,越界PermissionError);edit 的 not-found 会给最相似片段的 unified diff(_not_found_message:266)。注意:该模块当前未进入 BUILTIN_TOOL_SPECS,也没有任何管线挂载它——注册表视角是休眠代码(仅 tests/tools/test_file_tools.py 引用)。 - mastery_tool.py:9 / solve_tool.py:8:纯兼容 re-export,实现已迁
deeptutor/capabilities/mastery/tools与deeptutor/capabilities/solve/tools。 - tex_downloader.py:
TexDownloader:44——arXiv e-print 下载→解包→定位主 tex;TexDownloadResult(success, tex_path, tex_content, error)(:28)。requests 同步 30s 超时(:83)。 - tex_chunker.py:
TexChunker:21——按 section/token 分块(tiktoken,模型不支持时退 cl100k_base,:37-45);token 估算失败退 len/4(:62-65)。 - question/:
deeptutor/tools/question/question_extractor.py:321extract_questions_from_paper(MinerU 产物→LLM 抽题);deeptutor/tools/question/exam_mimic.py:18mimic_exam_questions是出题协调器的薄入口(pdf_path / paper_dir 二选一,:29-32);deeptutor/tools/question/__init__.py:10-16把 MinerU 解析 re-export 成历史 API。 - prompting/:见 §2 末条。
ToolPromptComposer:89五种格式(list/table/aliases/phased/list_with_usage)。 - vision/:见 §6。
5. builtin 集:94 个注册名与四种挂载类别¶
BUILTIN_TOOL_SPECS(deeptutor/tools/builtin_specs.py:46-205)共 94 个名字,按宿主分:tools/builtin 21 + exec 1 + workspace 5 + submit_visualization 1 + imagegen/videogen 2 + mastery_nav 4 + capabilities 持有的 60(mastery 14、solve 3、obsidian 9、marginnote4 7、subagent 1、ima 5、reading 8、setup 4、partner_authoring 1、partner_group 1、course_study 4)+ partner_memory 3。
挂载类别(决定谁有权开关它):
| 类别 | 名单锚点 | 机制 |
|---|---|---|
| 用户开关(/settings/tools "体验增强") | USER_TOGGLEABLE_TOOL_NAMES(deeptutor/tools/builtin/__init__.py:1907):brainstorm、web_search、paper_search、zotero_search、reason、geogebra_analysis、imagegen、videogen |
composer 开关 + 管理员全局白名单(admin_enabled_optional_tools:98) |
| 条件自动挂载(对用户"锁定") | CONFIGURABLE_BUILTIN_TOOL_NAMES:1932 共 20 个 |
_CONDITIONAL_MOUNT_FLAGS(§2) |
| 常开自动挂载 | write_memory、web_fetch、github、ask_user、cron(deeptutor/agents/_shared/tool_composition.py:262) |
无条件(伙伴白名单仍可减) |
| 能力自有 | capabilities 的 owned_tools(§7) |
能力激活即加挂 |
| 伙伴强制/抑制 | PARTNER_BUILTIN_TOOL_NAMES:208;chat 记忆工具被 _PARTNER_SUPPRESSED_TOOLS 抑制 |
forced/suppressed(deeptutor/agents/_shared/tool_composition.py:219-228) |
| 基线(不可摘) | WORKSPACE_BASELINE_TOOLS(deeptutor/agents/_shared/tool_composition.py:73)workspace_list/read/search/present |
独占回合也保留(compose_enabled_tools:247) |
| 占位(暂停上架) | COMING_SOON_TOOL_TYPES:1896(当前为空) |
Settings 显示 "Coming soon",不进注册表 |
6. vision 子包¶
deeptutor/tools/vision/(deeptutor/tools/vision/__init__.py:1-41 一次导出四组),服务 GeoGebra 可视化链路(geogebra_analysis 工具 + agents/vision_solver + visualizers):
- image_utils.py:URL→base64 下载与校验(
is_valid_image_url:33、is_base64_image:49),10MB 上限、30s 超时(:21-24);ImageError:27。 - coord_transform.py:BBox 像素坐标 ↔ GeoGebra 数学坐标(文件头 1-14 行写明两坐标系差异);
GGBCoordSystem:29、bbox_to_ggb/ggb_to_bbox/suggest_coord_system等。 - ggb_validator.py:
validate_command/validate_ggbscript+ValidationResult:12(original/fixed/warnings/errors);内置"该用方括号"的命令白名单修复 LLM 常见笔误。 - block_parser.py:从 LLM 流式输出里抽
``ggbscript[page-id;title] 围栏块(parse_ggb_blocks:47、StreamingBlockParser`),块内容过 ggb_validator 校验并保留原文。 - 测试现状:
tests/tools/无 vision 测试;tests/visualizers/test_ggb_validator.py覆盖 validator;tests/agents/vision_solver/覆盖 agent 侧。coord_transform/block_parser/image_utils 全测试树无直接单测(见 §8 缺口 3)。
7. 与 capabilities 的调用关系¶
- 两种能力协议(
deeptutor/capabilities/protocol.py): LoopExtension:38(普通能力):复用 chat 全量工具面,只在激活时把owned_tools:104加在面上(deeptutor/agents/loop/pipeline.py:811-816汇集)。可选钩子:pre_loop:53(回合前预 pass)、augment_kwargs:118(给自有工具注入服务端 kwargs)、rebinding_tools:81(如mastery_switch,先执行并重绑本轮其余调用)。KnowledgeCapability:130:独占面。激活时exclusive_tools:145生效,回合只剩owned_tools + ask_user 地板,外加共选 KB 的rag/kb_files/knowledge_frontier三件(_KB_COEXISTING_TOOLS,deeptutor/agents/_shared/tool_composition.py:68、compose_enabled_tools:230-248,#650)。obsidian、marginnote4、ima 是此类。- 能力注册
deeptutor/capabilities/registry.py:28LoopCapabilitySpec(class_path + 惰性工厂 + 漂移校验);插件 entry-point 亦可在运行期并入(registry.py 内_LegacyLoopCapabilitiesView:107一带)。 - tools/ 与 capabilities/ 的边界:通用工具住
tools/;一种能力专属、跟随能力激活的工具住capabilities/<name>/tools.py,但仍登记进同一张BUILTIN_TOOL_SPECS目录(deeptutor/tools/builtin_specs.py:96-204),注册与挂载机制完全一致——差别只在"谁决定挂"(§2 的 flags vs §7 的 owned_tools)。 - 搜索/研究等固定管线(research、question 管线)不经过 chat 组合器,直接 import
tools/的函数(如tex_downloader、paper_search_tool.ArxivSearchTool、question/)——这也是 tex/question 两族缺 chat 测试的原因:它们的调用方在agents/research、agents/question。
8. tests/tools 覆盖对照(薄弱工具标注)¶
tests/tools/ 现有 21 个测试文件(含 builtin/test_read_skill_tool.py)。按"模块 → 测试"映射:
有直接覆盖:ask_user(563 行,最厚)、write_note(368 行)、web_fetch(323 行)、question_bank(298 行)、exec_tool(test_code_execution_guidance + test_exec_prompt_hints + test_pptx_workspace_flow + test_office_artifact_execution,含 workspace_present 联动)、github_query(187 行)、zotero_search(195 行)、kb_files(196 行)、read_source(85 行)、list_notebook(147 行)、knowledge_frontier(175 行)、rag_tool(127 行,测 services.factory + rag_tool 契约)、question_extractor(75 行)+ mineru 两文件、file_tools(60 行)。
tests/tools 缺口(括号内为树内其他位置的既有覆盖,若无标注则全仓测试树内也无直接单测):
- cron_tool —
tests/services/cron/test_cron_tool.py有;tests/tools/无(可接受,位置合理)。 - mastery_nav.py(500 行) —
tests/tools/无,但deeptutor/learning/tests/test_mastery_navigation.py(372 行、19 例)直接覆盖全部四工具:id 解析、跨话题拒绝、自我会话拒绝(#1412/#1411)、空图谱。覆盖充分,只是位置在 learning 包内。 - vision/coord_transform.py(436 行)、block_parser.py(251 行)、image_utils.py(210 行) — ❗ 薄弱:全测试树(tests/、deeptutor/**/tests/)零直接引用。坐标换算与围栏解析是纯函数、易测;目前 vision 子包只有 ggb_validator 有专测(
tests/visualizers/test_ggb_validator.py)。 - tex_downloader.py / tex_chunker.py — ❗ 薄弱:下载/解包/主文件定位、按 token 分块全树无单测(research 管线消费方亦无)。
- paper_search_tool.py — 包装层被
tests/core/test_builtin_tools.py:616用 fake 覆盖;ArxivSearchTool 本体(年过滤、429 重试、抓取量放大)无直接单测(knowledge_frontier 测试同样只 fake 它)。 - media_gen_tool.py —
tests/services/test_media_gen.py覆盖服务层 + 包装层的提示语与ImagegenTool().execute主路径(:584-599);参数钳制边界(n≤4、size 透传)与 videogen 失败分支覆盖较薄。 - partner_memory.py —
tests/services/partners/test_partner_memory_tools.py有;tests/tools/无(位置合理)。 - brainstorm.py / reason.py 本体 — 包装层在 tests/core 有 fake 级覆盖;实现体(agent 参数回退、无模型 ValueError)无专测。
- workspace.py — 仅
test_pptx_workspace_flow.py以流式集成路径触达 exec+present;WorkspaceListTool/ReadTool/SearchTool/ExportTool的参数钳制与_failure转换无直接单测。export 的授权卡流程值得补。 - file_tools.py — 有测试但工具本身未注册(§4.2):要么补进目录并挂载,要么明确标注弃用——二选一,当前状态最容易误导读者。
- prompting/ —
load_prompt_hints的 zh/en 回退仅经test_exec_prompt_hints.py间接触达;ToolPromptComposer五种格式无格式级单测。 - web_search.py — 测试都在 services 层(provider 白名单等);顶层 re-export 无需独立测试,正常。
覆盖强度排序(最需补):vision(coord_transform/block_parser/image_utils) > tex_downloader/tex_chunker > ArxivSearchTool 本体 > workspace 四工具 > prompting 格式渲染 > media_gen 边界分支。
9. 读代码的推荐路径¶
- 先读协议:
deeptutor/core/tool_protocol.py:220(BaseTool)与:130(ToolResult 的 pause_for_user/model_message 两个特殊通道)。 - 再读目录与注册表:
deeptutor/tools/builtin_specs.py:46+deeptutor/runtime/registry/tool_registry.py:96(get 的懒加载与别名)。 - 然后读组合器:
deeptutor/agents/_shared/tool_composition.py:170的 docstring 就是本层最完整的语义说明。 - 挑三个代表工具精读:
rag(builtin/init.py:105,多用户+视觉+失败语义最复杂)、ask_user(ask_user.py:102 + AskUserTool:1493,暂停-恢复回合)、exec(exec_tool.py:36,沙箱+产物)。 - 最后按需进 capabilities:
deeptutor/capabilities/protocol.py:38/130理解"加挂"与"独占"两族。
本页原文 Markdown:在 AtomGit 查看·内容源自开源项目 dee/DeepTutor