Skip to content

feat(config): 按功能拆分 embedding 模型配置与 v3.15.0 稳定性修复 - #97

Open
69gg wants to merge 30 commits into
mainfrom
bugfix/lots-of-bugs
Open

69gg wants to merge 30 commits into
mainfrom
bugfix/lots-of-bugs

Conversation

@69gg

@69gg 69gg commented Sep 19, 2026

Copy link
Copy Markdown
Owner

变更说明

本 PR 包含 v3.15.0 的全部变更:按功能拆分 embedding 模型配置,并修复一批影响长期运行的稳定性问题(详见 CHANGELOG.md v3.15.0 条目)。共 20 个提交。

影响范围

功能

  • [models.embedding] 保留为全局默认,新增 [models.embedding.features.<name>]knowledge / cognitive / memes)按功能覆写:use_default 整体继承或按字段覆写连接信息、context_window_tokensqueue_interval_secondsdimensionsquery_instruction / document_instructionrequest_params 按键合并;生效配置相同的功能复用同一 Embedder 队列,重排器全局共享;WebUI 配置表单同步适配(use_proxy 三态下拉)
  • 修复嵌入指令前缀被 strip 的问题("passage: " 等带尾随空格的前缀原样保留)

修复

  • 认知侧写并发合并丢失更新:同实体「读取 → LLM 改写 → 写入」互斥;新增版本恢复(scripts/restore_profile.py,恢复前自动快照)与史官并发上限 [cognitive.historian].max_concurrency(默认 4)
  • 随包 Agent handler 无法加载(改按真实包路径导入,相对导入可解析);注册即预导入,失败项记录 load_error 并从 schema 排除
  • 配置热更新失败不再静默:步骤表逐项隔离,失败项 error 级输出;嵌入/重排配置变更加入需重启提示
  • 新增 SIGTERM 优雅停机(容器/systemd 停止不再跳过落盘清理)
  • 队列重试上限口径统一(coordinator 与 QueueManager 分叉导致误判失败或干等 480s 超时)
  • scripts/reembed_cognitive.py 支持维度变化迁移(重建 collection 不丢记录)
  • undefined-chat WorkspaceLayout 多余 Fragment 清理

清理与治理

  • crawl4ai / langchain-community 改为必需依赖,删除「未安装则降级」静默回退;移除死配置 rewrite_max_retry 与死依赖 imgkit、croniter
  • skills 内共享助手收敛 + 导入边界棘轮(只减不增);源码字符串断言预算棘轮
  • CI 补齐:只读权限、并发取消、任务超时、Python 3.11/3.13 兼容矩阵、65% 覆盖率门禁、前端行为测试真实执行

关联 Issue

自检

  • uv run ruff check .uv run ruff format --check . 通过
  • uv run mypy . 通过
  • uv run pytest tests/ --cov 通过(68.8% ≥ 65%)
  • 改动 apps/undefined-console/:已跑 cd apps/undefined-console && npm run check
  • 改动 apps/undefined-chat/:已跑 cd apps/undefined-chat && npm run check
  • 涉及配置项:已同步 config.toml.exampledocs/configuration.md

备注

  • 升级后若修改了 embedding / rerank 模型配置需重启生效(热重载会明确提示)
  • 旧配置无需迁移:未写 [models.embedding.features.*] 时行为与原单表配置完全一致

69gg added 20 commits September 19, 2026 15:30
- [models.embedding] 保留为默认配置,新增 [models.embedding.features.<name>]
  (knowledge / cognitive / memes),支持 use_default 整体继承或按字段覆写;
- 覆写支持连接信息、context_window_tokens、queue_interval_seconds、dimensions、
  query_instruction / document_instruction 与 request_params 合并;
- 生效配置相同的功能复用同一个 Embedder 与发车队列,重排器全局共享;
- 修复指令前缀被 strip 的缺陷:指令与文本直接拼接,首尾空白与换行必须保留;
- 认知重嵌入脚本改用 cognitive 功能实际生效的配置;
- 嵌入/重排配置变更加入需重启提示,避免热重载静默无效。
聚合在 p0 上方的说明改为每个插槽各自的注释,明确写入的 Prompt 区块。
- 覆写段说明移到区段提示,字段注释各自归位,避免被解析成同一段 hint;
- use_proxy 在三态语义下改用下拉选择(inherit / true / false)。
- 同一实体的「读取 → LLM 改写 → 写入」整段互斥:并发合并不再互相覆盖,
  后一个任务基于前一个任务已落盘的侧写继续合并(merge_guard 与写入锁分离,
  避免同锁重入死锁);
- ProfileStorage 新增 read_revision / restore_revision,恢复前把当前内容
  另存为新快照,恢复操作本身可回退;版本名做路径穿越校验;
- 新增 scripts/restore_profile.py 作为历史版本的 list / show / restore 入口,
  侧写回滚不再依赖手工 cp;
- 史官 worker 增加 max_concurrency(默认 4,cognitive.historian.max_concurrency)
  与在途计数 / 信号量双重约束,不再无上限并发;补 CLI、示例与文档。
- handler 模块名改用真实包路径(Undefined.skills.<...>.handler),
  code_delivery_agent 的 from .docker_utils import 等相对导入终于可解析;
- 按真实包导入而非按文件注册,常规 import 与注册表加载共用同一模块对象,
  热重载用 importlib.reload 原地重执行,外部引用不再指向另一份状态;
- SkillItem 新增 load_error:注册阶段即导入全部 handler,失败项打印错误、
  记录 load_error 并从 schema 中排除,主 AI 不会再看到不可用的技能;
- 补测试覆盖 agents/toolsets 的 handler 导入、相对导入与失败排除(原先只测
  tools,CI 查不出 code_delivery_agent 挂掉)。
- apply_config_updates 改为步骤表逐个执行:单步异常不再中断其余步骤,
  失败项与“未完全生效”汇总以 error 级日志输出;
- 异步步骤(技能热重载 / 自动化并发 / 监听器重启)改为带强引用与
  done 回调的任务,异常直接落 error 日志,不再无人知晓;
- _apply_skills_hot_reload 按注册表隔离失败,避免一个注册表出错时其余
  注册表既不停止也不启动;
- Config.update_from 补充可见性说明并在 finally 中刷新派生集合;
- ConfigManager._notify 回调失败记录名单后继续,其余回调不受影响;
- 文档补充 §5.5 热更新失败的可见性与嵌入/重排的需重启说明。
- 容器 / systemd / supervisor 默认发送 SIGTERM,此前只捕获 KeyboardInterrupt,
  会被直接终止并跳过落盘清理;
- 新增 install_shutdown_signal_handlers 把 SIGTERM/SIGINT 收敛到同一事件,
  非 POSIX 事件循环回退 signal.signal + call_soon_threadsafe;
- _run_until_shutdown 在停止信号到达时取消连接任务并等待其收敛,
  连接任务自身结束/报错仍按原路径处理;
- 部署文档补充优雅停机说明。
coordinator 判断是否已耗尽重试时用的是 config.ai_request_max_retries,
而真正的重试上限来自 QueueManager._max_retries;热更新后两者可分叉,
导致等待方在仍会重试时被唤醒为失败、或在重试已耗尽时干等到 480s 超时。

改为统一走 resolve_effective_retry_count(优先 QueueManager,缺失时回退
config),与等待超时预算的计算口径一致,并补覆盖分叉场景的测试。
- 删除 services/commands/{stats,bugfix}.py:CommandDispatcher 已不再通过
  多重继承组合它们,全仓无引用(实际 /stats、/bugfix 走 skills/commands/*;
  与 services/command.py 的 _handle_* 方法);
- 移除死配置 cognitive.historian.rewrite_max_retry(解析并写进文档但无调用点),
  同步清理示例与文档;
- 移除死依赖 imgkit、croniter(源码零引用)并重新生成 uv.lock;
- 删除 .githooks/pre-tag:git 没有 pre-tag 事件,该钩子永不执行;
  文档改指向 Release workflow 的 release_notes.py validate;
- 移除 SecurityService.check_rate_limit / record_rate_limit 两个无调用方法。
原实现只用 upsert 覆写,docstring 却声称覆盖“维度变化”;ChromaDB 的 collection
首次写入即定维,换嵌入模型后异维向量 upsert 会直接 InvalidArgumentError。

- 先读取现有向量维度并与新向量比较;
- 维度不同则先读全量记录、删除并重建同名 collection(沿用原 hnsw 元数据)
  后按新维度写回,记录不丢;
- --dry-run 只提示会重建,不做任何写入;缺失 client 时明确报错而不是静默失败;
- 补 chromadb 真实读写的维度变化 / 维度一致 / dry-run / 空库测试与文档说明。
- CLAUDE.md 模块表改为真实包路径(ai/client/、ai/llm/、ai/prompts/、
  services/coordinator/、cognitive/historian/、handlers/、onebot/、attachments/),
  消息流与多模型池分工里的同名旧路径一并修正;
- ARCHITECTURE.md 去掉不存在的 '*_shim' 与门面说明,目录级事实来源指向
  docs/development.md;
- 「4 级优先级」统一改为 6 条车道(超管私聊 / 群聊超管 / 普通私聊 / 群聊@ /
  群聊普通 / 后台请求),与 queue_manager 常量一致;
- docs/message-batching.md 相关文件链接补 ../;docs/development.md 更新
  services/commands 与 command.py 的职责(死 mixins 已删除);
- 文档互链自检:docs/ 与三个根文档相对链接全部可解析。
- 两个 App 的 biome.json 由 root:false(实际仍参与父配置合并链)改为
  extends: [],并各自声明 formatter.indentStyle = tab,App 格式与规则完全
  由本目录决定,根目录 WebUI 规则不再渗入;
- 文档同步:不再声称靠 files.includes 隐式隔离,明确 Console 的
  lint:webui 通过 --config-path 显式使用根配置;
- 实测:WebUI(17 文件)、Console(6 文件)、Chat(92 文件)三处检查均通过。
- 新增 src/Undefined/skills/shared.py:private_access_error、parse_positive_int;
- 迁移 7 份 _private_access_error 与 5 份 _parse_positive_int 到共享实现,
  消息文案统一(此前有的带“已被访问控制拦截”有的不带);
- 新增 tests/test_skills_import_boundary.py 棘轮:handler 只能依赖标准库、
  第三方包、Undefined.skills.*、同目录相对导入与 context 注入;新增越界导入
  直接失败,基线里已消失的条目也会失败(只减不增);
- skills/README.md 把“尽量不要”改成硬规则,并修正自身越界的官方示例
  (改用 context 读取),CLAUDE.md / AGENTS.md 同步口径。
CI:
- 工作流级 permissions: contents: read、并发取消、每个 job 的 timeout-minutes;
- quality-check 增加 setup-node,让 4 个 node 前端行为测试真正执行而不是静默 skip;
- 新增 python-compat(3.11 / 3.13)覆盖 pyproject 声明的版本区间两端;
- 测试改用 --cov,覆盖率门禁 65% 写入 pyproject(当前 68.8%);
- 新增 CONTRIBUTING.md 与 PR 模板;新增端到端链路烟测
  tests/test_end_to_end_message_pipeline.py(真实 Config/MessageHandler/
  命令注册表/队列,仅替身 OneBot 与 LLM)。

依赖卫生:
- crawl4ai 与 langchain-community 是必需依赖,删除“未安装则降级”的探测与
  文案(缺失时直接暴露环境问题),并同步 handler 提示与用例;
- types-markdown / types-aiofiles 从运行期依赖移入 dev;
- 删除与 dependency-groups 完全重复的 [project.optional-dependencies] 与
  纯子集 ci 组,CI/Release 统一使用 --group dev。
高严重度:
- 技能/工具/工具集/Agent 超时描述改为与代码一致(工具/工具集默认 480s,
  Agent 调用未启用超时);
- get_current_time README 补全 format / include_lunar / include_almanac 参数。

中严重度:
- skills/README.md 目录树与示例改用真实存在的工具(删除 send_message /
  get_recent_messages / save_memory 等不存在的名字);
- pipelines 目录树改为扁平结构并补 douyin;
- python_interpreter README 补 libraries / send_files,修正“无法访问网络”的
  绝对化描述;
- toolsets 目录树补齐全部 13 个分类;music 列出全部 10 个工具(原树只画 4 个);
- get_picture README 补 delivery 参数并修正“默认发送”的描述。

低严重度:
- end README 补 perspective;render README 补 delivery / target_id /
  message_type / output_format;
- commands 目录树补齐 12 个命令;
- agents README 补 douyin_video 共享工具与内置 Agent 清单;
- tools README 修正 summary_agent.fetch_messages 的写法;
- anthropic_skills 命名规则改用真实 TOML 路径 [tools].dot_delimiter;
- 为 12 个缺 README 的工具 / 工具集 / 命令补齐文档(参数表与 config.json 对齐)。
统一为 _ensure_private_allowed(user_id, action):未放行时按动作名记录
warning 日志并抛出同样的 PermissionError,行为与日志保持不变;
send_group/wechat 文件/语音/消息、私聊合并转发、拍一拍、文件发送共用。
- /stats 的统计、绘图与私聊投递(约 850 行,含 matplotlib 绘图)拆到
  services/stats_command.py 的 StatsCommandMixin;
- /bugfix 的上下文收集与诊断拆到 services/bugfix_command.py 的
  BugfixCommandMixin;command.py 只保留命令解析、分发、权限与限流
  (1582 行 → 550 行),不再内联 matplotlib 绘图;
- 修复 skills 层调用 dispatcher 私有方法的分层倒置:handle_stats /
  handle_stats_private / build_private_stats_image_message / handle_bugfix
  改为公开方法,skills/commands/{stats,bugfix}/handler.py 与相关测试同步;
- 行为与日志保持不变,全部 3272 个测试通过。
test_webui_runtime_chat_frontend.py 一类「读源码 + assert 子串」的写法是
变更检测器(全仓约 800 处,该文件占 537);一次性改写为行为断言体量过大,
先建立 tests/test_source_assertion_budget.py 预算棘轮:

- AST 统计各测试文件里对源码变量的字符串比较总数,超过预算即失败;
- 新增此类断言会被拦截,改成行为断言(node+vm / Vitest / 断言解析结构)
  后应顺带调低预算;
- 在该测试文件头部写明迁移路径,标记为历史遗留、勿继续扩张。
@coderabbitai

coderabbitai Bot commented Sep 19, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Too many files!

This PR contains 110 files, which is 10 over the limit of 100.

To get a review, reduce the PR to 100 files or fewer by splitting it into smaller PRs or changing its base branch.

Upgrade to a paid plan to raise the limit.

This review couldn't start because sufficient usage credits or metered capacity aren't available. Add credits or update usage-based reviews in the billing tab, then retry.

⚙️ Run configuration

Configuration used: Repository: 69gg/Undefined/.coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: c06439cd-83d6-440b-8ac5-71b67175b50c

📥 Commits

Reviewing files that changed from the base of the PR and between 533e533 and f6e0f51.

⛔ Files ignored due to path filters (5)
  • apps/undefined-chat/package-lock.json is excluded by !**/package-lock.json
  • apps/undefined-chat/src-tauri/Cargo.lock is excluded by !**/*.lock
  • apps/undefined-console/package-lock.json is excluded by !**/package-lock.json
  • apps/undefined-console/src-tauri/Cargo.lock is excluded by !**/*.lock
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (110)
  • .githooks/pre-tag
  • .github/pull_request_template.md
  • .github/workflows/ci.yml
  • .github/workflows/release.yml
  • AGENTS.md
  • ARCHITECTURE.md
  • CHANGELOG.md
  • CLAUDE.md
  • CONTRIBUTING.md
  • apps/undefined-chat/biome.json
  • apps/undefined-chat/package.json
  • apps/undefined-chat/src-tauri/Cargo.toml
  • apps/undefined-chat/src-tauri/tauri.conf.json
  • apps/undefined-chat/src/App.tsx
  • apps/undefined-console/biome.json
  • apps/undefined-console/package.json
  • apps/undefined-console/src-tauri/Cargo.toml
  • apps/undefined-console/src-tauri/tauri.conf.json
  • config.toml.example
  • docs/app.md
  • docs/build.md
  • docs/cognitive-memory.md
  • docs/configuration.md
  • docs/deployment.md
  • docs/development.md
  • docs/knowledge.md
  • docs/message-batching.md
  • pyproject.toml
  • scripts/README.md
  • scripts/reembed_cognitive.py
  • scripts/restore_profile.py
  • src/Undefined/__init__.py
  • src/Undefined/ai/client/ask_loop.py
  • src/Undefined/ai/client/setup.py
  • src/Undefined/ai/crawl4ai_support.py
  • src/Undefined/cognitive/historian/worker.py
  • src/Undefined/cognitive/profile_storage.py
  • src/Undefined/config/coercers.py
  • src/Undefined/config/config_class.py
  • src/Undefined/config/domain_parsers.py
  • src/Undefined/config/hot_reload.py
  • src/Undefined/config/load_sections/knowledge.py
  • src/Undefined/config/manager.py
  • src/Undefined/config/model_parsers.py
  • src/Undefined/config/models.py
  • src/Undefined/config/parsers/__init__.py
  • src/Undefined/config/parsers/embedding.py
  • src/Undefined/knowledge/__init__.py
  • src/Undefined/knowledge/runtime.py
  • src/Undefined/main.py
  • src/Undefined/services/bugfix_command.py
  • src/Undefined/services/command.py
  • src/Undefined/services/coordinator/background.py
  • src/Undefined/services/security.py
  • src/Undefined/services/stats_command.py
  • src/Undefined/skills/README.md
  • src/Undefined/skills/agents/README.md
  • src/Undefined/skills/agents/code_delivery_agent/tools/end/handler.py
  • src/Undefined/skills/agents/web_agent/tools/crawl_webpage/handler.py
  • src/Undefined/skills/agents/web_agent/tools/web_search/handler.py
  • src/Undefined/skills/commands/README.md
  • src/Undefined/skills/commands/bugfix/handler.py
  • src/Undefined/skills/commands/stats/handler.py
  • src/Undefined/skills/commands/summary/README.md
  • src/Undefined/skills/commands/version/README.md
  • src/Undefined/skills/registry.py
  • src/Undefined/skills/shared.py
  • src/Undefined/skills/tools/README.md
  • src/Undefined/skills/tools/__init__.py
  • src/Undefined/skills/tools/calculator/README.md
  • src/Undefined/skills/tools/end/README.md
  • src/Undefined/skills/tools/fetch_image_uid/README.md
  • src/Undefined/skills/tools/get_current_time/README.md
  • src/Undefined/skills/tools/get_picture/README.md
  • src/Undefined/skills/tools/knowledge_list/README.md
  • src/Undefined/skills/tools/knowledge_semantic_search/README.md
  • src/Undefined/skills/tools/knowledge_text_search/README.md
  • src/Undefined/skills/tools/python_interpreter/README.md
  • src/Undefined/skills/tools/task_progress/README.md
  • src/Undefined/skills/toolsets/README.md
  • src/Undefined/skills/toolsets/__init__.py
  • src/Undefined/skills/toolsets/attachments/README.md
  • src/Undefined/skills/toolsets/cognitive/README.md
  • src/Undefined/skills/toolsets/contacts/README.md
  • src/Undefined/skills/toolsets/group/get_member_title/handler.py
  • src/Undefined/skills/toolsets/messages/react_message_emoji/handler.py
  • src/Undefined/skills/toolsets/messages/send_message/handler.py
  • src/Undefined/skills/toolsets/messages/send_poke/handler.py
  • src/Undefined/skills/toolsets/messages/send_private_message/handler.py
  • src/Undefined/skills/toolsets/messages/send_text_file/handler.py
  • src/Undefined/skills/toolsets/messages/send_url_file/handler.py
  • src/Undefined/skills/toolsets/music/README.md
  • src/Undefined/skills/toolsets/render/README.md
  • src/Undefined/utils/sender.py
  • src/Undefined/webui/static/js/config-form.js
  • tests/test_cognitive_profile_revision.py
  • tests/test_config_hot_reload.py
  • tests/test_coordinator_retry_alignment.py
  • tests/test_crawl_webpage_tool.py
  • tests/test_embedding_feature_config.py
  • tests/test_end_to_end_message_pipeline.py
  • tests/test_main_shutdown.py
  • tests/test_reembed_cognitive_script.py
  • tests/test_skill_handler_loading.py
  • tests/test_skills_import_boundary.py
  • tests/test_source_assertion_budget.py
  • tests/test_stats_handler_scope.py
  • tests/test_stats_private_delivery.py
  • tests/test_stats_private_images.py
  • tests/test_webui_runtime_chat_frontend.py

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

merge_guard / _get_lock 原先 if-not-in-then-create 在并发首次进入同一
新实体时会各自创建独立 Lock,后建覆盖先建,互斥形同虚设(读旧快照后
写覆盖)。改为 get + setdefault 双检:并发双方必然拿到同一把锁。
补并发首次进入的回归测试。
crawl4ai 仍是必需依赖,但安装损坏 / 版本不兼容时 ClientSetupMixin
__init__ 直接调用能力探测会把 ImportError / RuntimeError 抛到启动
路径上。改为捕获后置 None 并以 error 日志暴露环境问题:网页获取
工具运行时自行报错,proxy 注入按不可用处理,其余功能照常启动。
_poll_loop 的在途计数限制任务对象数量(dequeue 暂停,防内存堆积),
_semaphore 约束实际并发处理;当前发车路径下两层上限一致、信号量
不会真正阻塞,保留它是为了约束未来绕过门控的调用。stop() 对两层
的收敛语义一并注明。
指令与文本直接拼接,_coerce_instruction 正确保留首尾空白,但纯空白
字符串与空串同样视为未设置、回落继承默认表。在 config.toml.example
(默认表 + 三个功能覆写块)与 docs/configuration.md 中写明:空/纯空白
= 继承,覆写表无法表达显式清空,并给出「默认留空、按功能显式设置」
的替代写法。
private_access_error 统一时把所有调用点都套上「发送失败:」前缀与
「已被访问控制拦截」后缀,改变了 LLM 看到的工具返回:表情反应本无
发送语义、文件类工具原不带拦截说明、code_delivery end 原为「上传
失败:」。为 helper 增加 access_note 参数并按调用点回传原参,恢复
各自合并前的文案。
install_shutdown_signal_handlers 现返回 ShutdownSignalGuard(携带停机
事件),安装前保存原处理器,停机流程结束后调用 restore() 归还信号
控制权:loop 处理器先 remove_signal_handler,再 signal.signal 还原,
注册失败的信号不进入恢复名单。main() 在清理 finally 末尾恢复。
except KeyboardInterrupt 分支注明仅在处理器注册全部失败时可达。
原先 delete_collection 后逐批 upsert,中途被 SIGKILL 会留下残缺的
正式库。现改为:新向量全部写入 <name>__rebuild_tmp 临时库(原库在
迁移完成前保持不动),写完校验记录数后删原库并 modify 换名,中断
窗口从「整个迁移过程」缩小到两次元数据操作之间;脚本启动时自动清
理遗留临时库,或在其持有全量数据时换名恢复。同时 client 改为必需
参数,删除不可达的 None 兜底分支。
共享重排器的 _reranker_initialized 布尔守卫与 for_feature 的运行时
去重扫描在并发初始化下可能重复创建 Reranker / 运行时。单事件循环内
无 await 本就按协程粒度原子,加 threading.Lock 防御未来从线程池并发
调用的场景;stop() 锁内只做快照清理,await 移到锁外避免阻塞事件循环。
未知功能名警告现在标注「疑似拼写错误」并列出全部可用功能名,降低
静默回落默认配置被忽视的概率;CHANGELOG 补记 SecurityService 限流
方法移除、crawl4ai 启动降级与 reembed 原子迁移的行为口径。
- 侧写锁按复审建议简化为单次 setdefault 原子创建;
- background.py 注明约定:新增 _execute_queued_* 一律使用
  resolve_effective_retry_count,禁止直读 config.ai_request_max_retries;
- 史官 max_concurrency 注明仅启动时读取,若开放热更新须同步重建
  在途门控与信号量;
- configuration.md 强调 dimensions 三态语义(-1 继承 / 0 模型默认 /
  >0 显式),并说明继承与模型默认的区别及定维后果。

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant