修复审批前思考过程的阶段归属

在用户回合边界立即绑定上一轮 reasoning 到对应 assistant 消息

新增审批边界回归测试,防止思考过程错误堆积到下一阶段底部
This commit is contained in:
2026-09-15 15:10:38 +00:00
parent 23fda80caa
commit d8d069895d
@@ -150,14 +150,26 @@ fn persisted_design_reasoning_entries(session: &DesignSession) -> Vec<DesignReas
let mut group_index = 0;
let mut assistant_index = 0;
let mut sequence = 0_u64;
let mut current_reasoning = Vec::new();
let mut pending_reasoning = Vec::new();
let mut current_reasoning: Vec<DesignReasoningEntry> = Vec::new();
let mut pending_reasoning: Vec<DesignReasoningEntry> = Vec::new();
let mut saw_response_output = false;
for item in &session.history {
if item.get("role").and_then(Value::as_str) == Some("user") {
if !pending_reasoning.is_empty() || !current_reasoning.is_empty() {
pending_reasoning.append(&mut current_reasoning);
// A user item closes the previous turn. Resolve its reasoning
// against that turn's last assistant message before moving to
// the next group; otherwise it is incorrectly attached to the
// next turn and rendered at the bottom as an orphan.
let assistant_id = assistant_groups
.get(group_index)
.and_then(|ids| ids.last())
.cloned();
for mut entry in pending_reasoning.drain(..) {
entry.message_id = assistant_id.clone();
entries.push(entry);
}
}
group_index += 1;
assistant_index = 0;
@@ -1638,6 +1650,53 @@ mod tests {
);
}
#[test]
fn persisted_reasoning_stays_with_the_turn_before_an_approval_boundary() {
let mut session = new_design_session("project", "quality");
session.messages = vec![
DesignMessage {
id: "turn-1:user".into(),
role: "user".into(),
text: "第一轮需求".into(),
},
DesignMessage {
id: "turn-1:assistant".into(),
role: "assistant".into(),
text: "第一轮已提交审批".into(),
},
DesignMessage {
id: "turn-2:user".into(),
role: "user".into(),
text: "用户已批准,进入下一阶段".into(),
},
DesignMessage {
id: "turn-2:assistant".into(),
role: "assistant".into(),
text: "查询工作阶段".into(),
},
];
session.history = vec![
json!({"role":"user", "content":"第一轮需求"}),
json!({"type":"reasoning", "id":"before-approval", "content":[{"type":"reasoning_text", "text":"审批前的思考"}]}),
json!({"type":"message", "role":"assistant", "content":[{"type":"output_text", "text":"第一轮已提交审批"}]}),
json!({"role":"user", "content":"用户已批准,进入下一阶段"}),
json!({"type":"reasoning", "id":"after-approval", "content":[{"type":"reasoning_text", "text":"审批后的思考"}]}),
json!({"type":"message", "role":"assistant", "content":[{"type":"output_text", "text":"查询工作阶段"}]}),
];
let entries = persisted_design_reasoning_entries(&session);
assert_eq!(
entries
.iter()
.map(|entry| (entry.id.as_str(), entry.message_id.as_deref()))
.collect::<Vec<_>>(),
vec![
("before-approval", Some("turn-1:assistant")),
("after-approval", Some("turn-2:assistant")),
]
);
}
#[tokio::test(flavor = "current_thread")]
async fn scripted_design_provider_emits_reasoning_without_persisting_it() {
let (_temp, root, _resources) = init_design_project();