新增: 飞书文件消息入站与补充注入链完善
- 文件消息入站:20MB 上限落盘引用,模型经 read_file 按需读盘;下载带 3 次退避+120s 超时防线,复用媒体域 hash 去重/OSS 双写 - 群聊门控对齐 u-claw 模式:前移至类型分流前,@自己/@所有人响应,未艾特静默旁观(原门控后置致未支持类型误回提示卡) - 修复补充注入丢失:pending_injects 未消化计数+收敛出口续轮+桌面注入 IPC+前端本地气泡 - df-ai-core 测试构造器补 source 字段(溯源字段遗漏,全量门禁暴露)
This commit is contained in:
@@ -527,6 +527,31 @@ pub(crate) async fn run_agentic_loop(
|
||||
// 热改 agent_max_iterations 不影响在跑 loop)+ 清零上 loop 残留的注入顺延余量。
|
||||
conv.loop_max_rounds = max_iterations;
|
||||
conv.round_budget_extra = 0;
|
||||
// 真目标源(2026-10-15 目标语义重构):pinned_goals 从「工具推理流水账」改为「用户任务原文」
|
||||
// ——流水账模式下用户真实意图从不进目标体系,压缩/重启后 AI 即失忆(2026-10-12/15
|
||||
// 两次丢目标事故同根)。首条任务型 user 消息写入首位;已有目标不覆盖(尊重
|
||||
// UPDATE_GOALS 显式修正);[系统恢复] 引导与超短碎片不入(递归套娃教训)。
|
||||
if conv.pinned_goals.is_empty() {
|
||||
let task_text = conv
|
||||
.messages
|
||||
.iter()
|
||||
.find(|m| {
|
||||
matches!(m.role, df_ai::provider::MessageRole::User)
|
||||
&& !m.content.starts_with("[系统恢复]")
|
||||
&& m.content.chars().count() >= 4
|
||||
})
|
||||
.map(|m| {
|
||||
let mut t: String = m.content.chars().take(GOAL_MAX_CHARS).collect();
|
||||
if m.content.chars().count() > GOAL_MAX_CHARS {
|
||||
t.push('…');
|
||||
}
|
||||
t
|
||||
});
|
||||
if let Some(t) = task_text {
|
||||
conv.pinned_goals.push(super::GoalEntry::new(t));
|
||||
tracing::info!(conv_id = %conv_id, "[ai] 会话目标取首条任务消息(目标语义重构)");
|
||||
}
|
||||
}
|
||||
(
|
||||
conv.stop_flag.clone(),
|
||||
conv.notify.clone(),
|
||||
@@ -2013,6 +2038,9 @@ async fn prepare_round_inputs(
|
||||
// 条目已消失则跳过,防 conv() 惰性创建孤儿状态
|
||||
if session.conv_read(conv_id).is_some() {
|
||||
session.conv(conv_id).messages.set_cache_friendly(cache_friendly);
|
||||
// 本轮请求已含截至此刻的全部消息,注入计数清零(=已消化);此后到收敛前
|
||||
// 再到的注入会计数,收敛出口凭 >0 续一轮消化(见收敛分支守卫)。
|
||||
session.conv(conv_id).pending_injects = 0;
|
||||
}
|
||||
let conv = match session.conv_read(conv_id) {
|
||||
Some(c) => c,
|
||||
@@ -2218,7 +2246,10 @@ async fn finalize_round_output(
|
||||
})
|
||||
.collect();
|
||||
} else {
|
||||
update_pinned_goals(&mut session, conv_id, tool_calls_acc);
|
||||
// 工具推理流水账停写(2026-10-15 目标语义重构):「执行 xxx/查看 xxx」是手段记录
|
||||
// 不是用户目标,入 pinned_goals 挤占真目标且压缩/重启后即失忆;工具轨迹已由
|
||||
// 轨迹卡与审计表完整覆盖。目标来源收敛为:首条任务消息(入口写入)+UPDATE_GOALS
|
||||
// 显式修正(上方分支,LLM 显式目标管理继续有效)。
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2345,19 +2376,49 @@ async fn finalize_round_output(
|
||||
// 正常收敛(loop 主体置 converged 后 break)
|
||||
// P2-1 主会话侧最小修复:收敛文本若被 max_tokens 腰斩,追加系统提示消息
|
||||
// (对齐 MidStream 保文的系统提示机制),用户/下轮请求能看见截断而非当完整回复。
|
||||
if round_truncated_by_max_tokens {
|
||||
// 注入未消化守卫(2026-10-14 实测):本轮组装后到达的补充指令,纯问答轮一轮收敛即
|
||||
// 无人消费(本轮上下文已在生成中定型)。计数>0 → 插提示续一轮,下轮
|
||||
// build_for_request 自然带上补充内容;计数已 take 清零,同批注入不重复触发。
|
||||
let pending_injects = {
|
||||
let mut session = session_arc.lock().await;
|
||||
if session.per_conv.contains_key(conv_id) {
|
||||
std::mem::take(&mut session.conv(conv_id).pending_injects)
|
||||
} else {
|
||||
0
|
||||
}
|
||||
};
|
||||
if pending_injects > 0 {
|
||||
let mut session = session_arc.lock().await;
|
||||
if session.per_conv.contains_key(conv_id) {
|
||||
let conv = session.conv(conv_id);
|
||||
let mut notice = ChatMessage::system(
|
||||
"⚠ 回复因 max_tokens 上限被截断,以上内容可能不完整;可要求模型继续输出。",
|
||||
);
|
||||
notice.model = Some(resolved_model.to_string());
|
||||
conv.messages.push(notice);
|
||||
let mut tip = ChatMessage::system(format!(
|
||||
"用户在回答生成期间补充了 {pending_injects} 条消息(见对话记录中的补充指令);\
|
||||
请结合补充内容继续或修正上面的回答,勿忽略。"
|
||||
));
|
||||
tip.model = Some(resolved_model.to_string());
|
||||
conv.messages.push(tip);
|
||||
}
|
||||
tracing::warn!(conv_id = %conv_id, "[ai] 收敛回复因 max_tokens 截断,已追加系统提示");
|
||||
tracing::info!(
|
||||
conv_id = %conv_id, pending = pending_injects,
|
||||
"[ai] 收敛时检出未消化注入,插提示续一轮消化"
|
||||
);
|
||||
// 不 return Converged:掉到函数尾部 RunTools(本轮无工具空转),
|
||||
// loop 顶部正常续轮,补充指令获得完整消费轮。
|
||||
} else {
|
||||
if round_truncated_by_max_tokens {
|
||||
let mut session = session_arc.lock().await;
|
||||
if session.per_conv.contains_key(conv_id) {
|
||||
let conv = session.conv(conv_id);
|
||||
let mut notice = ChatMessage::system(
|
||||
"⚠ 回复因 max_tokens 上限被截断,以上内容可能不完整;可要求模型继续输出。",
|
||||
);
|
||||
notice.model = Some(resolved_model.to_string());
|
||||
conv.messages.push(notice);
|
||||
}
|
||||
tracing::warn!(conv_id = %conv_id, "[ai] 收敛回复因 max_tokens 截断,已追加系统提示");
|
||||
}
|
||||
return RoundEpilogue::Converged;
|
||||
}
|
||||
return RoundEpilogue::Converged;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3602,6 +3663,7 @@ fn push_assistant_message(
|
||||
// ── update_pinned_goals: G1 目标钉扎提取+裁剪(扁平抽自原嵌套块,原 8 层 → 3 层) ──
|
||||
// 从工具调用推理新目标 → 去重 add → 标记旧 active 为 completed → 超上限时淘汰最早 completed。
|
||||
// 入参 session 需外层调用方持锁;纯内存 mutate,无 await。
|
||||
#[allow(dead_code)]
|
||||
fn update_pinned_goals(
|
||||
session: &mut AiSession,
|
||||
conv_id: &str,
|
||||
|
||||
Reference in New Issue
Block a user