重构: AI聊天可靠性批次(审批累计计数/write_file diff预览/会话隔离补清/token缓存)
This commit is contained in:
@@ -111,6 +111,7 @@ pub(crate) async fn run_agentic_loop(
|
||||
llm_concurrency: LlmConcurrency,
|
||||
max_iterations: usize,
|
||||
max_retries: usize,
|
||||
start_iteration: usize,
|
||||
) {
|
||||
// B-260615-09: generating 状态由 RAII guard 收敛复位(正常 exit 显式 reset;panic/异常 Drop 兜底)
|
||||
let mut guard = GeneratingGuard::new(session_arc.clone());
|
||||
@@ -168,7 +169,11 @@ pub(crate) async fn run_agentic_loop(
|
||||
// 区分"正常收敛退出"与"达 MAX 被截断退出"——后者末轮 tool_calls 仍非空(tool_result 不再回传 LLM),属异常
|
||||
let mut converged = false;
|
||||
|
||||
for iteration in 0..max_iterations {
|
||||
// F-260616-13: system_prompt 是 run_agentic_loop 的不变参数(整个 loop 期间文本不变),
|
||||
// 其 token 估算在 loop 外算一次缓存复用,避免每轮/每次重试重复 estimate_text(低收益优化,行为不变)。
|
||||
let sys_tokens = TokenEstimator::default().estimate_text(&system_prompt);
|
||||
|
||||
for iteration in start_iteration..max_iterations {
|
||||
// 用户请求停止 → 收尾退出(已生成文本已在上一轮入库)
|
||||
if stop_flag.load(Ordering::SeqCst) {
|
||||
let usage = df_ai::provider::TokenUsage {
|
||||
@@ -190,7 +195,7 @@ pub(crate) async fn run_agentic_loop(
|
||||
// 用户新建/切换对话后 active_conversation_id 变更,本 loop(conv_id 快照)成陈旧,
|
||||
// 继续跑会往新对话 push 消息/pending 造成污染。检测到即退出(guard Drop 复位 generating)。
|
||||
{
|
||||
let session = session_arc.lock().await;
|
||||
let mut session = session_arc.lock().await;
|
||||
if session.active_conversation_id.as_deref() != Some(conv_id.as_str()) {
|
||||
tracing::warn!(
|
||||
stale_conv = %conv_id,
|
||||
@@ -199,6 +204,10 @@ pub(crate) async fn run_agentic_loop(
|
||||
);
|
||||
return;
|
||||
}
|
||||
// F-260616-11 决策 a: 累计 iteration 计数(「下一轮起算值」= 当前轮+1)。
|
||||
// 审批等待/达 max 暂停退出时,本字段即「当前轮+1」;审批续跑 ai_approve 读此值作
|
||||
// start_iteration 透传,实现 iteration 累计不重置(防多次审批反复跑满 max 致 token 失控)。
|
||||
session.iteration_used = iteration + 1;
|
||||
}
|
||||
|
||||
// 新一轮通知前端(第二轮起),前端需新建 assistant 消息
|
||||
@@ -210,9 +219,10 @@ pub(crate) async fn run_agentic_loop(
|
||||
}
|
||||
|
||||
// 构建请求消息(超预算时自动裁剪旧消息,保护工具调用三元组 + 最近 6 条)
|
||||
// F-260616-13: sys_tokens 已在 loop 外缓存;本块构建的 messages 在本轮重试循环中复用
|
||||
// (本轮 stream_llm 不持 session_arc、不改 messages,重试无 push 发生,重建等价于复用)。
|
||||
let messages = {
|
||||
let session = session_arc.lock().await;
|
||||
let sys_tokens = TokenEstimator::default().estimate_text(&system_prompt);
|
||||
let (history_msgs, _trimmed) = session.messages.build_for_request(sys_tokens);
|
||||
let mut msgs = vec![ChatMessage::system(&system_prompt)];
|
||||
msgs.extend(history_msgs);
|
||||
@@ -248,16 +258,15 @@ pub(crate) async fn run_agentic_loop(
|
||||
|
||||
for retry_attempt in 0..=max_retries {
|
||||
// 每次重试重建 request(CompletionRequest 无状态,但 provider.stream() 内部可能消费 body)
|
||||
//
|
||||
// F-260616-13: messages 复用本轮外层构建的快照(不再持 session_arc.lock() 重建)。
|
||||
// 安全前提:本轮 stream_llm 不接收 session_arc、不 push 消息;重试期间 tool 执行
|
||||
// (process_tool_calls)在重试块之后,故重试内 session.messages 必然与外层构建时
|
||||
// 一致,重建与复用等价。收益:省去每次重试的 lock() + build_for_request(含
|
||||
// all_messages_clone 全量 clone)。
|
||||
let retry_request = CompletionRequest {
|
||||
model: provider_config.default_model.clone(),
|
||||
messages: {
|
||||
let session = session_arc.lock().await;
|
||||
let sys_tokens = TokenEstimator::default().estimate_text(&system_prompt);
|
||||
let (history_msgs, _trimmed) = session.messages.build_for_request(sys_tokens);
|
||||
let mut msgs = vec![ChatMessage::system(&system_prompt)];
|
||||
msgs.extend(history_msgs);
|
||||
msgs
|
||||
},
|
||||
messages: messages.clone(),
|
||||
temperature: Some(0.7),
|
||||
max_tokens: Some(8192),
|
||||
stream: true,
|
||||
@@ -487,8 +496,9 @@ pub(crate) async fn run_agentic_loop(
|
||||
// 达 MAX 未收敛(LLM 末轮仍想调工具被截断,末轮 tool_result 不再回传 LLM):转入暂停态询问用户
|
||||
// F-260616-03:不再 emit AiError + 走完成流程,改为 emit AiMaxRoundsReached + 保持 generating=true
|
||||
// (仿审批等待 L313-316),等用户点继续(ai_continue_loop → try_continue_agent_loop 再跑 max_iterations 轮)
|
||||
// 或点停止(ai_stop_loop → 走完成流程)。try_continue 重新 spawn run_agentic_loop,iteration 从 0 重计,
|
||||
// 故续跑天然再跑 max_iterations 轮(决策 a),无需 reset 任何计数器。
|
||||
// 或点停止(ai_stop_loop → 走完成流程)。try_continue 续跑 iteration 由调用方传 start_iteration 决定:
|
||||
// 审批续跑累计(F-260616-11 决策 a,防多次审批反复跑满 max 致 token 失控,传 session.iteration_used),
|
||||
// 达 max 续跑重计(F-260616-03 决策 a,用户点继续=授权重来,传 0 + 重置 iteration_used)。
|
||||
if !converged {
|
||||
tracing::warn!(
|
||||
conv_id = %conv_id,
|
||||
@@ -565,7 +575,7 @@ pub(crate) async fn run_agentic_loop(
|
||||
/// ai_chat_stop/clear/switch 并发改写,属竞态耦合。has_pending=false(全部审批已处理,续生成)
|
||||
/// 分支:审批已被 remove,改为以剩余 pending_approvals 任一 conversation_id 做一致性校验
|
||||
/// (此处空,校验通过即沿用全局值,该期 generating=true 且 switch 为 readonly 不并发)。
|
||||
pub(crate) async fn try_continue_agent_loop(app: &AppHandle, state: &AppState) {
|
||||
pub(crate) async fn try_continue_agent_loop(app: &AppHandle, state: &AppState, start_iteration: usize) {
|
||||
let (is_generating, has_pending, pending_conv_id) = {
|
||||
let session = state.ai_session.lock().await;
|
||||
// pending_approvals 中任一审批的 conversation_id:审批等待态(has_pending)下作为 conv_id 来源,
|
||||
@@ -662,6 +672,6 @@ pub(crate) async fn try_continue_agent_loop(app: &AppHandle, state: &AppState) {
|
||||
});
|
||||
|
||||
tauri::async_runtime::spawn(async move {
|
||||
run_agentic_loop(session_arc, tools_arc, db, app_handle, provider_config, system_prompt, conv_id, knowledge_config, llm_concurrency, max_iterations, max_retries).await;
|
||||
run_agentic_loop(session_arc, tools_arc, db, app_handle, provider_config, system_prompt, conv_id, knowledge_config, llm_concurrency, max_iterations, max_retries, start_iteration).await;
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user