优化: 清理无意义注释(走查编号前缀/过时历史标注/死代码注释)
This commit is contained in:
@@ -114,7 +114,7 @@ pub(crate) enum StreamResult {
|
||||
reasoning_content: Option<String>,
|
||||
},
|
||||
/// Init 失败(provider.stream() Err:建连/鉴权/HTTP non-2xx,或流建立后空文本各类中断)。
|
||||
/// UX-260618-15: **不再 emit AiError**——重试可恢复路径的 emit 权交调用方(agentic/mod.rs),
|
||||
/// **不再 emit AiError**——重试可恢复路径的 emit 权交调用方(agentic/mod.rs),
|
||||
/// 由 agentic 在重试耗尽/Fatal 时统一 emit 最终 AiError(单气泡),避免 N 次重试 push N+1 气泡。
|
||||
/// `retryable`: 据状态码分类(retry::is_status_retryable 镜像):true=5xx/429/timeout/connect
|
||||
/// 可重试;false=4xx(非429)/鉴权/参数错 Fatal 立即放弃。调用方据此决定是否重试。
|
||||
@@ -154,7 +154,7 @@ fn emit_ai_chat_event(app_handle: &AppHandle, ev: AiChatEvent) {
|
||||
let _ = app_handle.state::<crate::state::AppState>().ai_event_bus.publish_event(ev);
|
||||
}
|
||||
|
||||
/// AR-8: flush 合批的 AiTextDelta(双写 emit + event_bus)。空 buffer 不 emit。
|
||||
/// flush 合批的 AiTextDelta(双写 emit + event_bus)。空 buffer 不 emit。
|
||||
fn flush_delta(app_handle: &AppHandle, conv_id: &str, pending_delta: &mut String) {
|
||||
if pending_delta.is_empty() {
|
||||
return;
|
||||
@@ -196,16 +196,16 @@ pub(crate) async fn stream_llm(
|
||||
conv_id: &str,
|
||||
) -> StreamResult {
|
||||
/// 流式读取空闲超时:超过此时长无任何 chunk 即判定连接已断
|
||||
/// BUG-2026-07-17:原 120s 太长,后端挂死时用户等太久。改为 45s,正常流式每 chunk 间隔
|
||||
/// 原 120s 太长,后端挂死时用户等太久。改为 45s,正常流式每 chunk 间隔
|
||||
/// 远不到 45s(即使模型思考间隙),超时即判为连接异常走保文/报错路径
|
||||
const STREAM_IDLE_TIMEOUT: Duration = Duration::from_secs(45);
|
||||
/// BUG-2026-07-08: 首 chunk 超时(等首字节)。
|
||||
/// 首 chunk 超时(等首字节)。
|
||||
/// 正常 provider 首 token <3s(即使 48 工具定义 + 长上下文),10s 足够宽容;
|
||||
/// 已有数据后中途静默仍用 STREAM_IDLE_TIMEOUT(120s,允许模型思考间隙)。
|
||||
const FIRST_CHUNK_TIMEOUT: Duration = Duration::from_secs(10);
|
||||
/// 心跳间隔:静默期向前端报「LLM 仍在跑」,reset watchdog
|
||||
const HEARTBEAT_INTERVAL: Duration = Duration::from_secs(30);
|
||||
/// AR-8: 后端 delta 合批窗口。50ms 内的多次 AiTextDelta 合并为一条再 emit,
|
||||
/// 后端 delta 合批窗口。50ms 内的多次 AiTextDelta 合并为一条再 emit,
|
||||
/// 降低 IPC 事件风暴(前端 rAF 只节流渲染不节流 IPC);合并后前端 currentText
|
||||
/// 逐条累加,最终一致。
|
||||
const DELTA_FLUSH_INTERVAL: Duration = Duration::from_millis(50);
|
||||
@@ -287,14 +287,14 @@ pub(crate) async fn stream_llm(
|
||||
let mut finished_received = false;
|
||||
let mut stopped = false;
|
||||
let mut final_usage: Option<df_ai::provider::TokenUsage> = None;
|
||||
// BUG-260617-12: DeepSeek thinking 模式推理内容累积(多轮需回传)
|
||||
// DeepSeek thinking 模式推理内容累积(多轮需回传)
|
||||
let mut reasoning_content_acc: Option<String> = None;
|
||||
// AR-8: per-conv delta 合批累加器。pending_delta 累积未 flush 的文本,
|
||||
// per-conv delta 合批累加器。pending_delta 累积未 flush 的文本,
|
||||
// next_flush_at 记录下次 flush 的绝对时刻(首个入 buffer 时置 now+50ms)。
|
||||
let mut pending_delta = String::new();
|
||||
let mut next_flush_at: Option<tokio::time::Instant> = None;
|
||||
|
||||
// B-260615-15:heartbeat interval 提至 loop 外复用,避免每轮重建计时器
|
||||
// heartbeat interval 提至 loop 外复用,避免每轮重建计时器
|
||||
// (每轮重建会丢已积累的节拍,且 interval 首次 tick 立即返回的特性会被误用)。
|
||||
// tokio interval 首 tick 立即返回——此处先丢弃首 tick,让心跳等满首个 30s 静默期才发
|
||||
// (心跳语义是"静默期仍在跑",循环入口立即报无意义且会与 rx.recv() 抢分支错过首 chunk)。
|
||||
@@ -310,7 +310,7 @@ pub(crate) async fn stream_llm(
|
||||
let mut first_chunk_done = false;
|
||||
|
||||
loop {
|
||||
// B-260615-04:stop 即时打断。
|
||||
// stop 即时打断。
|
||||
if stop_flag.load(std::sync::atomic::Ordering::SeqCst) {
|
||||
stopped = true;
|
||||
break;
|
||||
@@ -348,7 +348,7 @@ pub(crate) async fn stream_llm(
|
||||
};
|
||||
}
|
||||
|
||||
// AR-8: 到点 flush 合批的 delta(单条 AiTextDelta 双写 emit + event_bus)。
|
||||
// 到点 flush 合批的 delta(单条 AiTextDelta 双写 emit + event_bus)。
|
||||
if let Some(deadline) = next_flush_at {
|
||||
if tokio::time::Instant::now() >= deadline {
|
||||
next_flush_at = None;
|
||||
@@ -360,7 +360,7 @@ pub(crate) async fn stream_llm(
|
||||
// 1) chunk_rx.recv():来自专用线程的 LLM chunk (15s 保底 timeout,防 select! 死等)
|
||||
// 2) heartbeat.tick():静默期发 AiHeartbeat reset 前端 watchdog
|
||||
// 3) notify.notified():用户停止即时打断
|
||||
// 4) flush 定时器(sleep_until next_flush_at):尾部 delta 不因流静默而延迟(AR-8)。
|
||||
// 4) flush 定时器(sleep_until next_flush_at):尾部 delta 不因流静默而延迟。
|
||||
let flush_deadline = next_flush_at
|
||||
.unwrap_or_else(|| tokio::time::Instant::now() + Duration::from_secs(3600));
|
||||
let flush_fut = tokio::time::sleep_until(flush_deadline);
|
||||
@@ -412,7 +412,7 @@ pub(crate) async fn stream_llm(
|
||||
idle_deadline = tokio::time::Instant::now() + STREAM_IDLE_TIMEOUT;
|
||||
if !chunk.delta.is_empty() {
|
||||
full_text.push_str(&chunk.delta);
|
||||
// AR-8: 合批入 buffer,首个入 buffer 时置 flush 时刻(到点统一 emit 单条)。
|
||||
// 合批入 buffer,首个入 buffer 时置 flush 时刻(到点统一 emit 单条)。
|
||||
pending_delta.push_str(&chunk.delta);
|
||||
if next_flush_at.is_none() {
|
||||
next_flush_at = Some(tokio::time::Instant::now() + DELTA_FLUSH_INTERVAL);
|
||||
@@ -514,20 +514,20 @@ pub(crate) async fn stream_llm(
|
||||
}
|
||||
}
|
||||
}
|
||||
// 2) 心跳:静默期 30s 发 AiHeartbeat,前端 watchdog reset(B-260615-02)
|
||||
// 2) 心跳:静默期 30s 发 AiHeartbeat,前端 watchdog reset
|
||||
_ = heartbeat.tick() => {
|
||||
emit_ai_chat_event(app_handle, AiChatEvent::AiHeartbeat {
|
||||
conversation_id: Some(conv_id.to_string()),
|
||||
});
|
||||
}
|
||||
// 3) 即时停止唤醒(B-260615-14)
|
||||
// 3) 即时停止唤醒
|
||||
_ = notify.notified() => {
|
||||
if stop_flag.load(std::sync::atomic::Ordering::SeqCst) {
|
||||
stopped = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// 4) AR-8: delta 合批到点 flush(流静默/工具执行等待期也能按时发出尾部文本)
|
||||
// 4) delta 合批到点 flush(流静默/工具执行等待期也能按时发出尾部文本)
|
||||
_ = &mut flush_fut => {
|
||||
if next_flush_at.is_some() {
|
||||
next_flush_at = None;
|
||||
@@ -589,7 +589,7 @@ pub(crate) async fn stream_llm(
|
||||
/// - timeout / connect → retryable=true(瞬态,可重试)
|
||||
/// - unknown / 其他 → retryable=true(保守可重试,避免误判 Fatal 错杀)
|
||||
///
|
||||
/// CR-30-1: 抽公共分类函数(决策 F-260616-07 a1 "复用 retry.rs 错误分类逻辑或镜像"),
|
||||
/// 抽公共分类函数(决策 F-260616-07 a1 "复用 retry.rs 错误分类逻辑或镜像"),
|
||||
/// 避免 stream_recv 与 agentic 各自重写状态码解析。
|
||||
fn classify_status_or_class(status_or_class: &str) -> bool {
|
||||
use df_ai::retry::is_status_retryable;
|
||||
@@ -612,7 +612,7 @@ fn classify_status_or_class(status_or_class: &str) -> bool {
|
||||
i += 1;
|
||||
}
|
||||
// 无三位数字码:按文本分类(timeout/connect/transport/decode 可重试,其余 unknown 保守不重试)
|
||||
// BUG-260617-01:Anthropic 中文参数错误(如 `[1214][messages 参数非法]`)无 HTTP 数字前缀,
|
||||
// Anthropic 中文参数错误(如 `[1214][messages 参数非法]`)无 HTTP 数字前缀,
|
||||
// 走此文本分支,原 || true 致 retryable=true 空耗 4 次重试浪费 token。
|
||||
// 删 || true 对齐 retry::is_status_retryable 未知码不可重试语义;timeout/connect 瞬态仍可重试。
|
||||
//
|
||||
@@ -694,7 +694,7 @@ fn classify_error_frame_retryable(msg: &str, status_or_class: &str) -> bool {
|
||||
"api-key",
|
||||
"api_key",
|
||||
"unauthorized",
|
||||
// 中文签名(BUG-260617-01:Anthropic 中文参数错误)
|
||||
// 中文签名Anthropic 中文参数错误)
|
||||
"鉴权",
|
||||
"认证失败",
|
||||
"未授权",
|
||||
@@ -746,7 +746,7 @@ mod tests {
|
||||
assert!(classify_status_or_class("connect"));
|
||||
}
|
||||
|
||||
/// unknown → 保守 retryable=false(BUG-260617-01:防 Anthropic 中文参数错误等 Fatal 空耗重试)
|
||||
/// unknown → 保守 retryable=false防 Anthropic 中文参数错误等 Fatal 空耗重试)
|
||||
#[test]
|
||||
fn classify_unknown_not_retryable() {
|
||||
assert!(!classify_status_or_class("unknown"));
|
||||
@@ -941,7 +941,7 @@ mod tests {
|
||||
/// 状态码出现在更长数字串里也不误匹配(边界判断:前后必须非数字)
|
||||
#[test]
|
||||
fn diag_does_not_match_status_inside_longer_digits() {
|
||||
// R-P2-6:边界判断后,白名单码嵌在长数字串内不再误命中(原窗口切片会从 14012 抠出 401)
|
||||
// 边界判断后,白名单码嵌在长数字串内不再误命中(原窗口切片会从 14012 抠出 401)
|
||||
let e = anyhow::anyhow!("port 14012 used");
|
||||
assert_eq!(extract_error_diag(&e).0, "unknown");
|
||||
// 纯长数字串(无白名单码)同样不命中
|
||||
@@ -957,7 +957,7 @@ mod tests {
|
||||
assert_eq!(s, "[anthropic-compat] AI 调用失败(HTTP 401): Unauthorized");
|
||||
}
|
||||
|
||||
/// R-P2-6:状态码紧贴中文字符(多字节 UTF-8)时仍能正确抠出
|
||||
/// 状态码紧贴中文字符(多字节 UTF-8)时仍能正确抠出
|
||||
/// (原按字节窗口切片在中文边界脆弱,改为 char 迭代后安全)
|
||||
#[test]
|
||||
fn diag_extracts_code_adjacent_to_multibyte_chars() {
|
||||
|
||||
Reference in New Issue
Block a user