From 2276f8cb00f36ebf3d6828ab9f94ba0fd67db9ec Mon Sep 17 00:00:00 2001 From: zizi Date: Sat, 19 Sep 2026 01:44:05 +0800 Subject: [PATCH] =?UTF-8?q?fix(=E4=B8=8A=E4=B8=8B=E6=96=87):=20=E5=AE=A1?= =?UTF-8?q?=E6=9F=A5=E4=BF=AE=E5=A4=8D=E2=80=94=E2=80=94=E6=8E=A2=E7=B4=A2?= =?UTF-8?q?=E4=BD=8D=E9=99=8D=E7=BA=A7=E5=9B=9E=E5=A1=AB=E4=BF=9D=E5=80=99?= =?UTF-8?q?=E9=80=89=E9=93=BE=E5=AE=8C=E6=95=B4=EF=BC=8C=E6=96=87=E6=A1=A3?= =?UTF-8?q?=E5=8F=A3=E5=BE=84=E5=AF=B9=E9=BD=90=E5=AE=A1=E8=AE=A1=E9=93=BE?= =?UTF-8?q?=E4=B8=8E=E8=90=BD=E8=B4=A6=E6=97=B6=E6=9C=BA=EF=BC=8C=E5=89=8D?= =?UTF-8?q?=E7=AB=AF=E8=90=BD=E8=B4=A6=E5=B9=82=E7=AD=89=E4=B8=8E=E5=8F=AF?= =?UTF-8?q?=E8=A7=81=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../新版设计/模块设计/B06-审校修订.md | 2 +- .../新版设计/模块设计/B07-作者经验.md | 2 +- docs/系统架构/新版设计/模块设计/B09-上下文.md | 4 +-- src/muse/上下文/偏好重排.py | 25 +++++++++++++------ tests/单元/test_偏好重排.py | 2 ++ web/src/功能/正文写作/选段改写.tsx | 19 +++++++++----- 6 files changed, 37 insertions(+), 17 deletions(-) diff --git a/docs/系统架构/新版设计/模块设计/B06-审校修订.md b/docs/系统架构/新版设计/模块设计/B06-审校修订.md index 19e7579..eb3ff19 100644 --- a/docs/系统架构/新版设计/模块设计/B06-审校修订.md +++ b/docs/系统架构/新版设计/模块设计/B06-审校修订.md @@ -181,7 +181,7 @@ HTTP的`revision-tasks`及CLI的`审校 模型修订/修订报告`共用请求 冻结修订步骤在既有"修订目标+声音"之外补充两类材料绑定:作者活跃偏好的偏好材料(与生成正文冻结回放同合同)与选中参考方法的原文材料(可选材料语义,受字节预算约束,超预算记省略原因不截断)。两类绑定均入快照;任一来源漂移则快照当前失效,沿用既有依赖复检。 -工作台交互合同:候选默认高亮重排前三,支持键盘多选与"本次跳过"(跳过不阻塞,等价于不挑参考);每张候选卡显示推荐理由(命中词+历史选用次数),使挑选是知情的;点踩即时落账并影响后续重排。 +工作台交互合同:候选默认高亮重排前三,支持键盘多选;每张候选卡显示推荐理由(双路得分分项+历史选用次数),使挑选是知情的。点踩与选用在发起改写时一并落账为 B07 参考挑选反馈,记录的是作者最终选择状态;发起前点踩只是本地状态,作者未发起即离开不留痕。跳过不设显式控件:不挑参考直接发起即等价跳过,不阻塞流程。落账失败不阻断改写,但对作者可见。 ## 持久有限返修会话 diff --git a/docs/系统架构/新版设计/模块设计/B07-作者经验.md b/docs/系统架构/新版设计/模块设计/B07-作者经验.md index 2362b66..7fbf6b7 100644 --- a/docs/系统架构/新版设计/模块设计/B07-作者经验.md +++ b/docs/系统架构/新版设计/模块设计/B07-作者经验.md @@ -24,7 +24,7 @@ | 记录运行观察 | 上述确切来源定位、具名现象、原话、背景和可选问题键 | 只追加运行观察;同命令和同来源现象幂等,改内容拒绝 | | 读取运行观察 / 列出运行观察 / 检索重复问题 | 当前作者、观察或任务身份、可选问题键 | 历史观察与按实际任务计数的重复问题,不生成效果判断 | | 记录参考挑选反馈 | 检索回执引用、领域标签、选中与拒绝的方法版本清单 | 只追加参考挑选反馈;进入数值信号轨,不直接生成文本偏好 | -| 读取挑选信号 | 作者、可选领域与方法版本范围 | 选用/拒绝计数与位次聚合,供 B09 偏好重排消费;只读,不改台账 | +| 读取挑选信号 | 作者、可选领域 | 选用/拒绝计数与位次聚合,供 B09 偏好重排消费;只读,不改台账 | | 汇总推断偏好建议 | 作者、可选领域 | 确定性聚合的只读建议(含真实原话依据与内联证据);不落库,采纳走既有保存→确认链 | | 读取复利度量 | 作者 | 台账统计:首稿采纳率、点踩率等;分母为零保持空值,不构成因果结论 | diff --git a/docs/系统架构/新版设计/模块设计/B09-上下文.md b/docs/系统架构/新版设计/模块设计/B09-上下文.md index 057ba8c..5d1fd6d 100644 --- a/docs/系统架构/新版设计/模块设计/B09-上下文.md +++ b/docs/系统架构/新版设计/模块设计/B09-上下文.md @@ -95,9 +95,9 @@ A使用连续历史基线及一般关键词补充原文,B仅使用时点卡片 弱强信号分列计数,不混算:参考挑选是弱信号(受位置偏置影响,排序靠前者天然更易被选中),改写采纳与实际消费是强信号。"种类→权重"映射由版本化权重表承担;偏好路初始权重保守(相对双路各 1.0 的量级不超过 0.25),权重表版本号写入检索回执。 -重排保留 1~2 个探索位:将未被消费过的高相关方法版本注入候选,对抗熟悉度泡沫(挑熟悉的→推熟悉的→方法面越用越窄)。重排是纯函数:同一输入(得分表、信号表、权重版本)得到同一输出。 +重排保留 1~2 个探索位:将无任何挑选或消费信号的高相关版本注入候选,对抗熟悉度泡沫(挑熟悉的→推熟悉的→方法面越用越窄);未晋升的尾部候选按原序保留,不破坏候选回填链。重排是纯函数:同一输入(得分表、信号表、权重版本)得到同一输出。 -用于冻结任务的检索,其信号表快照与权重版本进冻结快照(沿用偏好绑定先例),保证重排结果可回放、可审计。 +重排结果的审计链由两处承担:检索回执记录权重表版本、每个候选的偏好分项(选用/拒绝/消费计数与偏好值)及探索位标记;作者的挑选动作经 B07 参考挑选反馈落账,固化索引代次与权重版本。检索本身不是冻结任务的来源;作者实际选定的参考版本经任务范围 references 节冻结并受依赖复检(见 B06 参考改写),沿用偏好绑定先例的是该节而非检索回执。 ### 读取前预算与摘要分页 diff --git a/src/muse/上下文/偏好重排.py b/src/muse/上下文/偏好重排.py index 9807d75..5912511 100644 --- a/src/muse/上下文/偏好重排.py +++ b/src/muse/上下文/偏好重排.py @@ -91,6 +91,8 @@ def 注入探索位( 未探索 = 无任何挑选或消费信号。头部已够则不动;不足时从尾部按分 取未探索候选,替换头部末尾的已探索项;确定性、不改分数。 + 未晋升的尾部按原序接回,保证消费方的候选回填链完整; + 探索集只统计最终头部(前「数量」项)。 """ 位数 = 权重表["探索位"] if 探索位数 is None else 探索位数 if 位数 <= 0 or 数量 <= 0 or not 排名: @@ -99,23 +101,32 @@ def 注入探索位( def 未探索(vid: str) -> bool: return vid not in 信号表 + def 头部探索集(序: list[tuple[str, float]]) -> frozenset[str]: + return frozenset(vid for vid, _ in 序[:数量] if 未探索(vid)) + 头部, 尾部 = 排名[:数量], 排名[数量:] 已有 = sum(1 for vid, _ in 头部 if 未探索(vid)) if 已有 >= min(位数, len(头部)): - return 排名, frozenset(vid for vid, _ in 头部 if 未探索(vid)) + return 排名, 头部探索集(排名) 候选 = [(vid, 分) for vid, 分 in 尾部 if 未探索(vid)][: 位数 - 已有] if not 候选: - return 排名, frozenset(vid for vid, _ in 头部 if 未探索(vid)) - 结果: list[tuple[str, float]] = [] + return 排名, 头部探索集(排名) + 新头部: list[tuple[str, float]] = [] + 降级: list[tuple[str, float]] = [] 待换 = len(候选) for 项 in reversed(头部): if 待换 > 0 and not 未探索(项[0]): + 降级.append(项) 待换 -= 1 continue - 结果.append(项) - 结果.reverse() - 结果.extend(候选) - return 结果, frozenset(vid for vid, _ in 结果 if 未探索(vid)) + 新头部.append(项) + 新头部.reverse() + 降级.reverse() + 晋升集 = {vid for vid, _ in 候选} + 剩余尾部 = [项 for 项 in 尾部 if 项[0] not in 晋升集] + # 被替换的头部项降级回尾部作回填候选,总长度不变。 + 结果 = 新头部 + 候选 + 降级 + 剩余尾部 + return 结果, 头部探索集(结果) __all__ = [ diff --git a/tests/单元/test_偏好重排.py b/tests/单元/test_偏好重排.py index 1f6fb83..25e3a95 100644 --- a/tests/单元/test_偏好重排.py +++ b/tests/单元/test_偏好重排.py @@ -80,6 +80,8 @@ def test_探索位晋升确定且有界__a10017(): # d、e 未探索:晋升两个,替换头部末尾的已探索项(c、b) assert 头部[0] == "a" and set(头部[1:]) == {"d", "e"} assert 探索集 == {"d", "e"} + # 未晋升的尾部按原序接回,候选回填链完整、总长度不变 + assert [vid for vid, _ in 新排名] == ["a", "d", "e", "b", "c"] # 确定性:同输入同输出 assert 注入探索位(排名, 信号表, 3)[0] == 新排名 # 头部已含足量未探索项时不动 diff --git a/web/src/功能/正文写作/选段改写.tsx b/web/src/功能/正文写作/选段改写.tsx index 2af3817..6e3b49b 100644 --- a/web/src/功能/正文写作/选段改写.tsx +++ b/web/src/功能/正文写作/选段改写.tsx @@ -67,8 +67,9 @@ export function Ui选段改写({ const [refRejected, setRefRejected] = useState([]); const [refSignals, setRefSignals] = useState>({}); const [refBusy, setRefBusy] = useState(false); - const refNoticed = useRef(false); + const [refNoticed, setRefNoticed] = useState(false); const refFeedbackDone = useRef(false); + const refCommandId = useRef(""); const [params, setParams] = useSearchParams(); const task = params.get("selection_task"); const pending = useRef<发起请求>(); @@ -133,6 +134,9 @@ export function Ui选段改写({ setRefSelected([]); setRefRejected([]); refFeedbackDone.current = false; + // 每轮检索固定一个落账命令:网络假失败后重试复用同一命令,幂等键防重复落账。 + refCommandId.current = crypto.randomUUID(); + setRefNoticed(false); const { data: 信号 } = await 客户端.GET("/api/v1/reference-selection-signals", { params: { query: { domain: refDomain } }, }); @@ -166,7 +170,7 @@ export function Ui选段改写({ try { const { error: 失败 } = await 客户端.POST("/api/v1/reference-selections", { body: { - command_id: crypto.randomUUID(), + command_id: refCommandId.current || crypto.randomUUID(), selection: { query: refQuery.trim(), domain: refDomain, @@ -185,10 +189,13 @@ export function Ui选段改写({ }, }, }); - if (失败) refNoticed.current = true; - else refFeedbackDone.current = true; + if (失败) setRefNoticed(true); + else { + refFeedbackDone.current = true; + setRefNoticed(false); + } } catch { - refNoticed.current = true; + setRefNoticed(true); } } async function 发起() { @@ -401,7 +408,7 @@ export function Ui选段改写({ )} {refSelected.length > 0 &&

已选 {refSelected.length}/8 个参考方法。

} - {refNoticed.current && ( + {refNoticed && (

本次挑选落账失败,不影响改写;信号未计入偏好。

)}