SW-05 p3 fragment 组成数据化:窗口/投影/Thread 注入进 profile.context(ADR-0017) - #13
Conversation
There was a problem hiding this comment.
🟡 Changes recommended
There is a confirmed config semantics bug (known_fact_fields: [] cannot be expressed) and an unresolved/contradictory definition of the window ordering (“near-first” vs current join order) across code/tests/ADR.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
This PR makes p3 fragment cross-episode context composition configurable via profile.context.* (window size for prev_episode_summary, projection fields for known_facts, and an inject_threads switch), with defaults intended to preserve existing behavior, and adds schema/ADR/tests to formalize the change.
Changes:
- Add
ContextSettingstoprofiles/_schema.pyand setProfile.contextwith defaults + whitelist validation. - Update
pipeline.py(run + recompile) to build p3 fragments usingcontext.prev_summary_window,context.known_fact_fields, andcontext.inject_threads. - Add end-to-end stubbed tests covering default behavior, window>1 behavior, and threads injection forwarding.
File summaries
| File | Description |
|---|---|
| tests/test_p3_context_config.py | Adds regression + configuration tests for known_facts projection, prev_summary windowing, and threads injection. |
| src/nsc/passes/pipeline.py | Implements the configurable window/projection/injection logic and helper utilities (_window_join, _threads_view, etc.). |
| src/nsc/passes/p3_beatsheet.py | Forwards injected threads from fragment into the LLM inputs when present. |
| profiles/short_video_v1.yaml | Adds explicit default context block (intended zero behavior change). |
| profiles/short_drama_v1.yaml | Adds explicit default context block (intended zero behavior change). |
| profiles/_schema.py | Introduces ContextSettings with extra=forbid and known_fact_fields whitelist validation. |
| adr/0017-p3-context-profile-section.md | Documents the decision and intended semantics for the new profile.context section. |
Review details
- Files reviewed: 7/7 changed files
- Comments generated: 4
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| def _known_fact_fields_of(profile: dict[str, Any]) -> tuple[str, ...]: | ||
| """profile.context.known_fact_fields → 投影字段(白名单交集,缺省 = 原五字段)。""" | ||
| wanted = profile.get("context", {}).get("known_fact_fields") or list(_KNOWN_FACT_FIELDS) | ||
| return tuple(k for k in _KNOWN_FACT_FIELDS if k in wanted) |
There was a problem hiding this comment.
已修正(10227b0):_known_fact_fields_of 改为 is None 判定——显式 known_fact_fields: [] 现在是合法的空投影(有意隐藏全部字段),只有键缺失才回退五字段缺省。补了回归断言(tests/test_p3_context_config.py::test_known_facts_fields_from_profile)。
| def _window_join(summaries: list[str], window: int) -> str: | ||
| """prev_episode_summary 窗口(SW-05):近端在前、远端追加在后,逐行拼接。 | ||
|
|
||
| window=1 与原行为逐字节一致(单元素直接返回);window=0 即恒空串。 | ||
| """ | ||
| n = max(0, int(window)) | ||
| return "\n".join(summaries[-n:]) if n else "" |
There was a problem hiding this comment.
已修正(10227b0):确认预期语义是时间序(远端在前、近端在后),与 compress_history 的【前情】→【上一集】布局一致——实现本身正确,初稿 docstring 表述反了。已统一 docstring、ADR-0017、测试注释三处表述;断言不变(窗口扩展只在前端追加远端集)。
| | 键 | 语义 | 缺省 | 消费点 | | ||
| |---|---|---|---| | ||
| | `context.prev_summary_window` | p3 `prev_episode_summary` 看近端 N 集(0=恒空) | 1 | `run_pipeline` p3 循环 + `recompile_episode`(`_window_join`:近端在前、远端逐行追加) | | ||
| | `context.known_fact_fields` | `known_facts` 投影字段(白名单子集) | `[id, content, episode_no, status, type]` | `_known_facts` | | ||
| | `context.inject_threads` | 是否把 p2 的 Thread 表注入 p3(投影 `{id,title,status,state}`) | false | `run_pipeline`/`recompile_episode` 组装 fragment,p3 转发进 LLM 输入 | |
There was a problem hiding this comment.
已修正(10227b0):ADR 表述改为"按时间序逐行拼接,远端在前、近端在后,与 compress_history 布局一致",并在约束一节补记 review 的两处澄清(显式空投影合法、窗口时间序)。
| # 窗口=2:第 3 集的窗口严格包含第 2 集的窗口(多了第 1 集摘要) | ||
| assert summaries[2].startswith(summaries[1]), "近端摘要在前,远端追加在后" | ||
| assert len(summaries[2]) > len(summaries[1]) |
There was a problem hiding this comment.
已修正(10227b0):注释改为"时间序拼接(远端在前、近端在后,同 compress_history 布局)——第 3 集的窗口以第 1 集摘要开头、第 2 集摘要收尾"。断言本身(startswith + 长度递增)在新旧表述下都成立于时间序实现,未改。
Copilot review(PR #13): - _known_fact_fields_of 用 or 判断,显式 [] 与未配置混同 → 改为 is None 判定 - _window_join 实现是时间序(远端在前),初稿 docstring/ADR/测试注释写成 "近端在前" → 统一改为时间序表述(与 compress_history【前情】→【上一集】布局一致)
f9d4f85 to
b4bd1a7
Compare
b4bd1a7 to
85cff88
Compare
* SW-05 p3 fragment 组成数据化:窗口/投影/Thread 注入进 profile.context(ADR-0017)
- context.prev_summary_window(缺省 1=原行为):run_pipeline 与 recompile_episode
统一走 _window_join(近端在前、远端逐行追加;window=1 逐字节同原实现)
- context.known_fact_fields(缺省原五字段,白名单校验):_known_facts 投影可窄化
- context.inject_threads(缺省 false):p2 Thread 表按 {id,title,status,state}
投影注入 p3 fragment,p3 转发进 LLM 输入
- profiles/_schema.py 新增 ContextSettings(extra=forbid + field_validator)
- 测试:tests/test_p3_context_config.py(5 例,先红后绿)
* SW-05 review 修正:known_fact_fields 显式空列表不再回退缺省;窗口序表述改为时间序
Copilot review(PR #13):
- _known_fact_fields_of 用 or 判断,显式 [] 与未配置混同 → 改为 is None 判定
- _window_join 实现是时间序(远端在前),初稿 docstring/ADR/测试注释写成
"近端在前" → 统一改为时间序表述(与 compress_history【前情】→【上一集】布局一致)
* sync: 远端 main 快照(API tarball;解决 fetch 阻断)
* style: ruff format(union 解冲突文件)
* lab round10: p4 beat_to_scene 类型矫正(随机后端结构漂移)+lab_smoke_v1 三集评测切片+p3 指令 v2(round8/9:escalation/枚举/PENDING/钩子纪律)
* lab round10b: _to_int 宽容转换(字符串下标/嵌套值,TypeError 实证)
* lab round12: resolve_pending 降级语义(无 donor 解除契约不致命;随机后端 PENDING 悬空最高频死法实证)
* lab round12: resolve_pending 降级语义(无 donor 解除契约不致命)+修正最终返回输入;测试 8 绿
* lab round12b: p1 Prop sku_ref null 归一(NarrativeIR ValidationError 实证)
* lab round12c: 通用 null→字段默认值归一(characters/locations/motifs 同型错误一例多杀)
* lab round13b: present_character_ids 空表全集兜底(scenes.N=[] ValidationError 实证)
* lab round14: p3/p4 结构机械修复——STR-014 承重节拍兜底/BM-002 植入间距重排/STR-010 主角补位(attempt4/5 同门连死实证,相位重试只复述诊断不改结构)
* lab round15: est_duration_s 等比缩放到集目标时长(DLG-006 六集全灭根因)+_retry_pass 传输容错(APIConnectionError 杀死整轮实证)
* lab round16: p5 对白目标区间与 DLG-006 对齐(旧 lo=0.8x 全顺从也死)+_expand_if_thin 欠量当场定点扩写(系统性欠量 26% 实证)
* lab round16b: 对白瞄准线=门禁线+3pp(毫厘之死实证 341/342/344 vs 344.25)+扩写循环至达标(上限2次,只留更厚稿)
* lab round17: p6 prompt 瘦身(_slim_scenes/_slim_profile/_slim_bible_for_episode 投影,实证 46631 字符撞护栏;id 全保留 anchor_map 契约不动)
* lab round18: 暗线步进钳制(INV-19 机械前置,实证 8 章全产物死于 final 门 current_stage 5/7 超界)
* lab round19: CMP-001 绝对化用语机械替换(门禁 fix 要求的确定性执行)+p1 必填 str 空串占位(characters.4.need 实证)
* lab round20: 南浪仔(海南文旅 IP)brief+brand 资产(用户定稿故事五幕:雨林→浪尖→旅程→暗线→陪伴)
* lab round20b: DLG-006 瞄准余量 +3pp→+6pp+扩写上限 2→3(ep8 差 12 字实证)
* lab round20c: canonical_name 改为'南浪仔'(BM-009 系统性失败实证:slogan 本身就违规,'南浪仔 NOLAN'全名在叙事中永不自然;NOLAN 单用降级为偶发可重试)
* lab round20d: ir_io.save 用 mode='json'(datetime 序列化,实证全绿产物死于 ir.json 导出)
* test 修正:Project 必填字段补齐(provenance_id/logline)
* docs: README 快速开始修正(实际 CLI 语法)+独立使用说明(端点配置/新品牌/profile/测试/战役分支)
* lab round23: 战役实测版 prompts 入库(p3 v3.2/p5 round13,全绿产物复现所需;B1 生成物例外,PR 内声明)+ADR-0015/16/17 状态 accepted
* merge 收尾:ruff 全绿(pairwise/values()/去过期 noqa)
* merge 收尾2:ruff format+check 全仓合规(CI lint & typecheck 门禁)
* merge 收尾3:pyright 合规(测试替身 cast PassContext)+format
---------
Co-authored-by: sync <t@t>
Co-authored-by: cnb <cnb@example.com>
这个 PR 改的是哪一层?(必选其一)
spec/profiles/brands/cases/export/)→ 必须打标签asset-change并附 ADRsrc/tests/)→ 无需 ADRADR:adr/0017-p3-context-profile-section.md(status: proposed,含 profiles/_schema.py 变更,等确认)
(编号说明:ADR-0016 已被 sw/sw-07 分支占用,本卡顺延 0017)
工单
Closes # · 工单号:SW-05(上游依赖卡,Lab 仓 docs/WORK_ORDERS.md §上游依赖卡)
问题
p3 的跨集上下文组成硬编码在
pipeline.py:prev_episode_summary窗口恒 1、known_facts投影恒五字段、p2 的threads表永不注入。弱/强模型需要不同窗口与投影面。改动
context.prev_summary_window_window_join:近端在前、远端逐行追加;window=1 与原实现逐字节一致;recompile 同享context.known_fact_fieldscontext.inject_threads{id,title,status,state}注入 fragment,p3 转发进 LLM 输入(同 revivable_ideas 模式)profiles/_schema.py新增ContextSettings(extra=forbid);两个在库 profile 显式写入缺省值,行为零变化验收命令(贴出你本地跑通的输出)
检查表
prompts/影响生成结果吗?