fix(上下文): 审查修复——探索位降级回填保候选链完整,文档口径对齐审计链与落账时机,前端落账幂等与可见性

This commit is contained in:
zizi 2026-09-19 01:44:05 +08:00
parent 2d0410d053
commit 2276f8cb00
6 changed files with 37 additions and 17 deletions

View File

@ -181,7 +181,7 @@ HTTP的`revision-tasks`及CLI的`审校 模型修订/修订报告`共用请求
冻结修订步骤在既有"修订目标+声音"之外补充两类材料绑定:作者活跃偏好的偏好材料(与生成正文冻结回放同合同)与选中参考方法的原文材料(可选材料语义,受字节预算约束,超预算记省略原因不截断)。两类绑定均入快照;任一来源漂移则快照当前失效,沿用既有依赖复检。 冻结修订步骤在既有"修订目标+声音"之外补充两类材料绑定:作者活跃偏好的偏好材料(与生成正文冻结回放同合同)与选中参考方法的原文材料(可选材料语义,受字节预算约束,超预算记省略原因不截断)。两类绑定均入快照;任一来源漂移则快照当前失效,沿用既有依赖复检。
工作台交互合同:候选默认高亮重排前三,支持键盘多选与"本次跳过"(跳过不阻塞,等价于不挑参考);每张候选卡显示推荐理由(命中词+历史选用次数),使挑选是知情的;点踩即时落账并影响后续重排。 工作台交互合同:候选默认高亮重排前三,支持键盘多选;每张候选卡显示推荐理由(双路得分分项+历史选用次数),使挑选是知情的。点踩与选用在发起改写时一并落账为 B07 参考挑选反馈,记录的是作者最终选择状态;发起前点踩只是本地状态,作者未发起即离开不留痕。跳过不设显式控件:不挑参考直接发起即等价跳过,不阻塞流程。落账失败不阻断改写,但对作者可见。
## 持久有限返修会话 ## 持久有限返修会话

View File

@ -24,7 +24,7 @@
| 记录运行观察 | 上述确切来源定位、具名现象、原话、背景和可选问题键 | 只追加运行观察;同命令和同来源现象幂等,改内容拒绝 | | 记录运行观察 | 上述确切来源定位、具名现象、原话、背景和可选问题键 | 只追加运行观察;同命令和同来源现象幂等,改内容拒绝 |
| 读取运行观察 / 列出运行观察 / 检索重复问题 | 当前作者、观察或任务身份、可选问题键 | 历史观察与按实际任务计数的重复问题,不生成效果判断 | | 读取运行观察 / 列出运行观察 / 检索重复问题 | 当前作者、观察或任务身份、可选问题键 | 历史观察与按实际任务计数的重复问题,不生成效果判断 |
| 记录参考挑选反馈 | 检索回执引用、领域标签、选中与拒绝的方法版本清单 | 只追加参考挑选反馈;进入数值信号轨,不直接生成文本偏好 | | 记录参考挑选反馈 | 检索回执引用、领域标签、选中与拒绝的方法版本清单 | 只追加参考挑选反馈;进入数值信号轨,不直接生成文本偏好 |
| 读取挑选信号 | 作者、可选领域与方法版本范围 | 选用/拒绝计数与位次聚合,供 B09 偏好重排消费;只读,不改台账 | | 读取挑选信号 | 作者、可选领域 | 选用/拒绝计数与位次聚合,供 B09 偏好重排消费;只读,不改台账 |
| 汇总推断偏好建议 | 作者、可选领域 | 确定性聚合的只读建议(含真实原话依据与内联证据);不落库,采纳走既有保存→确认链 | | 汇总推断偏好建议 | 作者、可选领域 | 确定性聚合的只读建议(含真实原话依据与内联证据);不落库,采纳走既有保存→确认链 |
| 读取复利度量 | 作者 | 台账统计:首稿采纳率、点踩率等;分母为零保持空值,不构成因果结论 | | 读取复利度量 | 作者 | 台账统计:首稿采纳率、点踩率等;分母为零保持空值,不构成因果结论 |

View File

@ -95,9 +95,9 @@ A使用连续历史基线及一般关键词补充原文,B仅使用时点卡片
弱强信号分列计数,不混算:参考挑选是弱信号(受位置偏置影响,排序靠前者天然更易被选中),改写采纳与实际消费是强信号。"种类→权重"映射由版本化权重表承担;偏好路初始权重保守(相对双路各 1.0 的量级不超过 0.25),权重表版本号写入检索回执。 弱强信号分列计数,不混算:参考挑选是弱信号(受位置偏置影响,排序靠前者天然更易被选中),改写采纳与实际消费是强信号。"种类→权重"映射由版本化权重表承担;偏好路初始权重保守(相对双路各 1.0 的量级不超过 0.25),权重表版本号写入检索回执。
重排保留 1~2 个探索位:将未被消费过的高相关方法版本注入候选,对抗熟悉度泡沫(挑熟悉的→推熟悉的→方法面越用越窄)。重排是纯函数:同一输入(得分表、信号表、权重版本)得到同一输出。 重排保留 1~2 个探索位:将无任何挑选或消费信号的高相关版本注入候选,对抗熟悉度泡沫(挑熟悉的→推熟悉的→方法面越用越窄);未晋升的尾部候选按原序保留,不破坏候选回填链。重排是纯函数:同一输入(得分表、信号表、权重版本)得到同一输出。
用于冻结任务的检索,其信号表快照与权重版本进冻结快照(沿用偏好绑定先例),保证重排结果可回放、可审计。 重排结果的审计链由两处承担:检索回执记录权重表版本、每个候选的偏好分项(选用/拒绝/消费计数与偏好值)及探索位标记;作者的挑选动作经 B07 参考挑选反馈落账,固化索引代次与权重版本。检索本身不是冻结任务的来源;作者实际选定的参考版本经任务范围 references 节冻结并受依赖复检(见 B06 参考改写),沿用偏好绑定先例的是该节而非检索回执。
### 读取前预算与摘要分页 ### 读取前预算与摘要分页

View File

@ -91,6 +91,8 @@ def 注入探索位(
未探索 = 无任何挑选或消费信号。头部已够则不动;不足时从尾部按分 未探索 = 无任何挑选或消费信号。头部已够则不动;不足时从尾部按分
取未探索候选,替换头部末尾的已探索项;确定性、不改分数。 取未探索候选,替换头部末尾的已探索项;确定性、不改分数。
未晋升的尾部按原序接回,保证消费方的候选回填链完整;
探索集只统计最终头部(前「数量」项)。
""" """
位数 = 权重表["探索位"] if 探索位数 is None else 探索位数 位数 = 权重表["探索位"] if 探索位数 is None else 探索位数
if 位数 <= 0 or 数量 <= 0 or not 排名: if 位数 <= 0 or 数量 <= 0 or not 排名:
@ -99,23 +101,32 @@ def 注入探索位(
def 未探索(vid: str) -> bool: def 未探索(vid: str) -> bool:
return vid not in 信号表 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)) 已有 = sum(1 for vid, _ in 头部 if 未探索(vid))
if 已有 >= min(位数, len(头部)): if 已有 >= min(位数, len(头部)):
return 排名, frozenset(vid for vid, _ in 头部 if 未探索(vid)) return 排名, 头部探索集(排名)
候选 = [(vid, 分) for vid, 分 in 尾部 if 未探索(vid)][: 位数 - 已有] 候选 = [(vid, 分) for vid, 分 in 尾部 if 未探索(vid)][: 位数 - 已有]
if not 候选: if not 候选:
return 排名, frozenset(vid for vid, _ in 头部 if 未探索(vid)) return 排名, 头部探索集(排名)
结果: list[tuple[str, float]] = [] 新头部: list[tuple[str, float]] = []
降级: list[tuple[str, float]] = []
待换 = len(候选) 待换 = len(候选)
for 项 in reversed(头部): for 项 in reversed(头部):
if 待换 > 0 and not 未探索(项[0]): if 待换 > 0 and not 未探索(项[0]):
降级.append(项)
待换 -= 1 待换 -= 1
continue continue
结果.append(项) 新头部.append(项)
结果.reverse() 新头部.reverse()
结果.extend(候选) 降级.reverse()
return 结果, frozenset(vid for vid, _ in 结果 if 未探索(vid)) 晋升集 = {vid for vid, _ in 候选}
剩余尾部 = [项 for 项 in 尾部 if 项[0] not in 晋升集]
# 被替换的头部项降级回尾部作回填候选,总长度不变。
结果 = 新头部 + 候选 + 降级 + 剩余尾部
return 结果, 头部探索集(结果)
__all__ = [ __all__ = [

View File

@ -80,6 +80,8 @@ def test_探索位晋升确定且有界__a10017():
# d、e 未探索:晋升两个,替换头部末尾的已探索项(c、b) # d、e 未探索:晋升两个,替换头部末尾的已探索项(c、b)
assert 头部[0] == "a" and set(头部[1:]) == {"d", "e"} assert 头部[0] == "a" and set(头部[1:]) == {"d", "e"}
assert 探索集 == {"d", "e"} assert 探索集 == {"d", "e"}
# 未晋升的尾部按原序接回,候选回填链完整、总长度不变
assert [vid for vid, _ in 新排名] == ["a", "d", "e", "b", "c"]
# 确定性:同输入同输出 # 确定性:同输入同输出
assert 注入探索位(排名, 信号表, 3)[0] == 新排名 assert 注入探索位(排名, 信号表, 3)[0] == 新排名
# 头部已含足量未探索项时不动 # 头部已含足量未探索项时不动

View File

@ -67,8 +67,9 @@ export function Ui选段改写({
const [refRejected, setRefRejected] = useState<string[]>([]); const [refRejected, setRefRejected] = useState<string[]>([]);
const [refSignals, setRefSignals] = useState<Record<string, number>>({}); const [refSignals, setRefSignals] = useState<Record<string, number>>({});
const [refBusy, setRefBusy] = useState(false); const [refBusy, setRefBusy] = useState(false);
const refNoticed = useRef(false); const [refNoticed, setRefNoticed] = useState(false);
const refFeedbackDone = useRef(false); const refFeedbackDone = useRef(false);
const refCommandId = useRef("");
const [params, setParams] = useSearchParams(); const [params, setParams] = useSearchParams();
const task = params.get("selection_task"); const task = params.get("selection_task");
const pending = useRef<发起请求>(); const pending = useRef<发起请求>();
@ -133,6 +134,9 @@ export function Ui选段改写({
setRefSelected([]); setRefSelected([]);
setRefRejected([]); setRefRejected([]);
refFeedbackDone.current = false; refFeedbackDone.current = false;
// 每轮检索固定一个落账命令:网络假失败后重试复用同一命令,幂等键防重复落账。
refCommandId.current = crypto.randomUUID();
setRefNoticed(false);
const { data: 信号 } = await 客户端.GET("/api/v1/reference-selection-signals", { const { data: 信号 } = await 客户端.GET("/api/v1/reference-selection-signals", {
params: { query: { domain: refDomain } }, params: { query: { domain: refDomain } },
}); });
@ -166,7 +170,7 @@ export function Ui选段改写({
try { try {
const { error: 失败 } = await 客户端.POST("/api/v1/reference-selections", { const { error: 失败 } = await 客户端.POST("/api/v1/reference-selections", {
body: { body: {
command_id: crypto.randomUUID(), command_id: refCommandId.current || crypto.randomUUID(),
selection: { selection: {
query: refQuery.trim(), query: refQuery.trim(),
domain: refDomain, domain: refDomain,
@ -185,10 +189,13 @@ export function Ui选段改写({
}, },
}, },
}); });
if (失败) refNoticed.current = true; if (失败) setRefNoticed(true);
else refFeedbackDone.current = true; else {
refFeedbackDone.current = true;
setRefNoticed(false);
}
} catch { } catch {
refNoticed.current = true; setRefNoticed(true);
} }
} }
async function 发起() { async function 发起() {
@ -401,7 +408,7 @@ export function Ui选段改写({
</ul> </ul>
)} )}
{refSelected.length > 0 && <p>已选 {refSelected.length}/8 个参考方法。</p>} {refSelected.length > 0 && <p>已选 {refSelected.length}/8 个参考方法。</p>}
{refNoticed.current && ( {refNoticed && (
<p role="status">本次挑选落账失败,不影响改写;信号未计入偏好。</p> <p role="status">本次挑选落账失败,不影响改写;信号未计入偏好。</p>
)} )}
<button <button