Skip to content

refactor: long-file audit full split (awakening package + LLM reply chain) - #247

Merged
3aKHP merged 9 commits into
devfrom
refactor/long-file-split
Sep 14, 2026
Merged

3aKHP merged 9 commits into
devfrom
refactor/long-file-split

Conversation

@3aKHP

@3aKHP 3aKHP commented Sep 13, 2026

Copy link
Copy Markdown
Owner

概要

按 2026-09-14 长文件审计快照的建议执行全流程拆分(单分支单 PR,Huge PR 流程)。行为保持型重构:命令、配置、持久化格式、交付行为与用户可见文案均不变;唯一可观察差异是 persona 兴趣话题读取失败时新增一条 debug 日志,以及两处日志记录器命名空间随模块迁移。

变更内容

唤醒模块拆包(chat/awakening.py 1188 行单文件 → chat/awakening/ 包):

  • 快速合规:静默吞异常改 debug 日志(fail-soft 保持)、E305 空行、删除 5 处死参数 user_id
  • 配置去重:resolve_group 改 fields(ResolvedAwakeningSettings) 驱动合并;两个 from_dict 收敛为 _filter_config_fields;Web 路由 _OVERRIDE_FIELDS 改由 AwakeningSettingsBody.model_fields 派生(键集与旧硬编码逐项相同)
  • 接口收窄:结构化 Protocol 集合(judge 通道 / persona 话题 / 群可用性 / 规则开关 / 限流 / 统计 / 生成调用)替代 svc: Any 宽对象;_is_group_llm_enabled 升公共名;BoredomSendPlan.reply_result 经 TypedDict 定形;check_awakening_triggers / iter_boredom_send_plans 新增可选 config 注入参数作为正式测试接缝;LLMService.persona_interest_topics 承担 persona extras 窄读取
  • 拆包:config / state / text_signals / judge / triggers / boredom 六子模块 + facade,依赖单向,最大子模块 442 行,包内全部 ≤100 列;消费方 import 面不变,测试接缝(单例赋值 / random patch / 私有名导入)随拆分同步迁移

LLM 回复主链瘦身(llm/service.py 1907 → 1681 行):

  • identity 信封编排下沉 llm/identity.py(collect_mention_profiles / collect_known_participants,service 留薄委托)
  • 新增 llm/reply_chain.py:TurnRequestAssembler 显式装配对象替代 74 行闭包 + 6 个 nonlocal;normalize_turn_input / finalize_reply_text / reply_result 工厂 / build_raw_turn_text(双份 raw_turn 拼装去重,字节级一致经差分验证)
  • 新增 llm/reply_types.py:ChatTurnRequest / ReplyResult 定形输入输出契约,三层 24 形参穿透收敛为两层;_generate_reply_for_scope 524 → ~395 行分阶段编排
  • 契约面保持:plugins/llm_runtime.py 12 个 re-export 名不变;quickquip.llm.service._get_sensitive_filter / build_provider_client / _reload_sensitive_filter 模块 patch 点不变(被移代码以参数接收已解析过滤器与绑定可调用)

文档:docs/dev/llm-module.md 同步新结构(reply_chain / identity 条目、awakening 包路径)。

验证

  • .venv/bin/ruff check . 全绿;validate_toml_examples.py 12 文件通过
  • pytest -n auto 2088 全绿(含 re-export 契约测试、预算重试、分段交付、取消、敏感词、epoch/锚点、身份注入等集成路径)
  • 集成审查期另做过差分验证:raw_turn 拼装 ~46k 组合、normalize_turn_input 393k 组合与旧实现零偏差

Deep-CR(Tier 2,五透镜)

scripts/check/deep-cr-trigger.sh origin/dev → trigger: true(20 文件 / +4556 行 / 跨 3 域)。五个独立透镜(provider/MCP、工具副作用与敏感词、持久化、触发与配置契约、全局结构)+ 独立交叉复核(≥80 置信度保留):

  • Blocking:0;Should-fix:2(均已修复) — ① TurnRequestAssembler.session_preset 死字段(效果已含于预计算 system_prompt)→ 删字段;② awakening 包内生产代码跨子模块 import 下划线私有名 → 跨模块消费的名字升公共,模块内部助手保持私有(AST 复核清零)
  • Nit 修复 4:judge 诊断在 default_provider=None 时输出 "None" → 归一空串;_filter_config_fields docstring 与实际透传行为不符 → 改写;llm/identity.py 两个无消费常量放宽为公共 → 恢复私有;facade 冗余 re-export(AwakeningExtendSession)与 docstring 失实 → 移除并如实描述
  • Nit 剔除 2:日志 logger 名迁移(全仓无按名过滤的消费者,root 桥接下 caplog 仍捕获);配置单例 patch 点迁移(拆分计划明文要求的接缝迁移)
  • 延后记录:service.py 拆分后 1681 行,仍存多个变更原因聚集(STS 单发入口、图像预处理阶段为下一批候选)——本 PR 范围即审计点名的四个切面,继续扩大单一 Huge PR 的风险面得不偿失;TurnRequestAssembler 经结构透镜判定为显式装配对象而非新宽 bag(36 字段中 35 个被 assemble 消费、4 个可调用为刻意窄缝)

CHANGELOG 草稿(release 时汇总)

变更

  • 唤醒模块(awakening)内部结构整改:单文件拆为包(config/state/text_signals/judge/triggers/boredom + facade),对外契约不变;persona 话题读取失败时新增 debug 日志
  • LLM 回复主链内部结构整改:identity 信封编排下沉 llm/identity.py,请求装配与产出 shaping 拆至 llm/reply_chain.py / llm/reply_types.py;对外契约与文案不变

- Replace silent except-pass on persona interest_topics with debug log (fail-soft kept)

- Add missing second blank line after class definition (E305)

- Drop dead user_id params from record_message/check_interest/check_fallback/check_relevance/check_qa; sync orchestrator, adapter and test callers
- Drive resolve_group by fields(ResolvedAwakeningSettings): single merge loop replaces two 10-line per-field enumerations

- Converge both from_dict filters into _filter_config_fields helper

- Derive routes _OVERRIDE_FIELDS from AwakeningSettingsBody.model_fields; document interest_topics Web-contract decision

- Fold over-length lines in routes/awakening.py
- Define structural Protocols for the LLM surface awakening depends on (judge channel, persona topics, group status, rule switch, rate limiter, stats, generate callable); svc/Any annotations converge to narrow types

- Replace _judge_target getattr probing with explicit JudgeTarget/JudgeSettings resolved via typed config view

- Add LLMService.persona_interest_topics as the narrow persona-extras read owned by the persona domain

- Promote _is_group_llm_enabled to public is_group_llm_enabled; scheduler and tests follow

- Type BoredomSendPlan.reply_result via ReplyResult TypedDict (new llm/reply_types.py) with delivered_text extension

- Add optional config injection param to check_awakening_triggers/iter_boredom_send_plans as the official test seam
- chat/awakening.py -> chat/awakening/ package: config/state/text_signals/judge/triggers/boredom with one-way deps (config <- state <- text_signals <- judge <- triggers <- boredom) and a facade re-exporting only the public contract

- Facade attribute-assignment test seams migrated with the split: orchestrator tests use the config injection param; boredom tests patch owning submodule singletons (state._state) and pass config through

- random patch point moves to quickquip.chat.awakening.triggers.random.random; group-messages harness patches owning config/state singletons

- routes/awakening.py imports CONFIG_AWAKENING_TOML from common.paths directly; facade drops the pass-through re-export

- All package files satisfy the <=100 col baseline
- Move collect_mention_profiles / collect_known_participants (plus AT_QQ_PATTERN and MENTION_PROFILE_LIMIT) into the llm identity domain as pure projections over IdentityIndex

- LLMService keeps thin delegates resolving the scope identities, preserving the test seam and call sites

- service.py sheds ~130 lines of envelope identity knowledge; identity.py grows from a re-export shim into the envelope orchestration owner
- New llm/reply_chain.py: TurnRequestAssembler (explicit assembly object replacing the 74-line _assemble_request closure with 6 nonlocals), normalize_turn_input, finalize_reply_text, reply_result factory, build_raw_turn_text (dedup of the twin raw_turn assembly in _begin_agent_recorder / _persist_turn_and_build_reply), image_caption_blob, LLM_RULE_NAME / MAX_QUOTED_MESSAGE_CHARS constants (re-exported by service for plugins/llm_runtime)

- New ChatTurnRequest input struct (llm/reply_types.py) collapses the three-layer 24-param threading through generate_reply / generate_private_reply / _generate_reply_for_scope

- _generate_reply_for_scope drops from 524 to ~395 lines of phase-annotated orchestration; per-path error dicts converge on the factory with key sets preserved exactly

- Module patch points stay in service.py: moved code receives the resolved sensitive filter and bound callables as explicit parameters (single_shot pattern)
- llm-module.md: service.py entry notes the reply-chain extraction; new reply_chain.py entry; identity.py entry covers envelope orchestration; awakening module path updated to the package layout
- Drop dead TurnRequestAssembler.session_preset field (effect already baked into system_prompt)

- Promote cross-submodule contract names in the awakening package to public (judge/text_signals/triggers exports consumed by sibling modules), leaving module-internal helpers private

- Normalize judge target diagnostics: empty string instead of str(None) when default_provider is unset

- Fix _filter_config_fields docstring to match actual passthrough behavior

- Restore private names for identity.py pattern constants (no external consumers)

- Remove AwakeningExtendSession from the facade (zero external consumers); docstring states the real export surface

@khpilot khpilot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bot Review — 💬 Comment

This PR refactors two large modules by splitting awakening.py into a 6-submodule package and extracting identity/reply-chain logic from llm/service.py, with behavior-preserving changes to internal structure only. The verdict is "comment" because the general reviewer's coverage is incomplete (19/20 units, 1 failed batch); an incomplete review cannot validly approve or request changes regardless of the standing findings. Notable unresolved concerns from the capped findings include: _parse_judge_text exception handling gaps that could break the fail-closed contract on malformed LLM JSON output (should-fix), deep-cr-trigger.sh path mapping not updated for the awakening package (should-fix), misaligned svc Protocol annotations in check_awakening_triggers, and delivery_sink typed as Any instead of reusing the existing DeliverySink Protocol.

Findings

🔴 BLOCKING: General reviewer coverage is incomplete (19/20 units, 1 failed batch). Per adjudication rules, an incomplete review cannot produce a valid approve or request_changes verdict.

File: coverage

Await completion of the failed batch and re-adjudicate.

Review provenance

  • General reviewer: incomplete — 19/20 units completed, 1 failed batches
  • Style reviewer: complete — 20/20 units completed
  • Coverage: incomplete — manifest=complete; general=incomplete(1 failed batches); style=complete
  • Reviewed head: 49130d9 (base 18181b4)

Automated review by @KHPilot. Reply with @KHPilot to ask follow-up questions.

- _parse_judge_text: fail-closed on non-numeric score values (string/null/nested) instead of raising through the judge chain; regression tests added

- deep-cr-trigger.sh: map the awakening package paths (src/quickquip/chat/awakening*) to message-policy so the Deep-CR gate keeps covering the split modules

- check_awakening_triggers: svc typed as AwakeningServiceView (judge channel + persona topics composite protocol) aligning orchestrator and per-rule narrow interfaces

- ChatTurnRequest.delivery_sink typed via the existing DeliverySink Protocol instead of Any
@3aKHP

3aKHP commented Sep 14, 2026

Copy link
Copy Markdown
Owner Author

Bot Review 处置(head 5d9caec)

评审由宕机前的入队 job 经队列恢复自动完成,感谢覆盖。逐条处置:

  • [BLOCKING] general reviewer 覆盖不完整(19/20,1 failed batch):属服务器宕机导致的中断性缺口。按维护者决定不重跑 Bot Review;质量门以本 PR 已完成的 Tier 2 五透镜 Deep-CR(含交叉复核)+ CI 全绿为准,合并由人工裁决。
  • [should-fix] _parse_judge_text 异常处理缺口:已修——score 值不可数值化(字符串/null/嵌套)时 fail-closed 返回 None,不再击穿判定链;新增回归测试三例(5d9caec)。
  • [should-fix] deep-cr-trigger.sh 路径映射未随拆包更新:已修——src/quickquip/chat/awakening* 通配同时覆盖旧单文件与新包路径,触发脚本对新 head 实测仍正确归类 message-policy。
  • [nit] check_awakening_triggers 的 svc Protocol 标注错位:已修——编排入口收敛为组合协议 AwakeningServiceView(judge 通道 + persona 话题),子规则继续各消费窄接口。
  • [nit] delivery_sink: Any:已修——改用既有 DeliverySink Protocol(TYPE_CHECKING 引用,保持 reply_types 零运行时依赖)。

其中前两条在本地五透镜 Deep-CR 中未捕获(parse 缺口为拆分前既有、随代码迁移进入 diff;脚本映射为本 PR 直接引入),系 Bot Review 的有效增量发现。

@3aKHP
3aKHP merged commit 7591d2b into dev Sep 14, 2026
3 checks passed
@3aKHP
3aKHP deleted the refactor/long-file-split branch September 14, 2026 00:22
3aKHP added a commit that referenced this pull request Sep 14, 2026
Marks the structural-governance batch set (PR #247/#248/#249) as one integrated phase on the 1.16.0 target
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