@@ -221,14 +221,16 @@ impl AiNoteRefineAdapter {
221221 let mut system = self . prompt . build_system ( & request. profile , dims) ;
222222 system. push_str ( "\n \n " ) ;
223223 system. push_str ( crate :: ai_refine_protocol:: NDJSON_SYSTEM_SUFFIX ) ;
224- // REQ-290② 预算(与 refine_vision 同口径——流式不豁免上限)
224+ // REQ-290② 预算(与 refine_vision 同口径——流式不豁免上限;含引导段 )
225225 let budget = crate :: refine_budget:: output_budget (
226226 dims. map ( |d| d. preset_id . as_str ( ) ) . unwrap_or ( "standard" ) ,
227227 request. content . chars ( ) . count ( ) ,
228228 ) ;
229+ system. push_str ( "\n \n " ) ;
230+ system. push_str ( & crate :: refine_budget:: guidance_suffix ( & budget) ) ;
229231 let user = serde_json:: to_string ( request)
230232 . map_err ( |e| AiClientError :: Parse ( format ! ( "精修请求序列化失败: {}" , e) ) ) ?;
231- let payload = if images. is_empty ( ) {
233+ let mut payload = if images. is_empty ( ) {
232234 crate :: ai_client:: build_chat_payload (
233235 & self . client . config . model , & system, & user, budget. max_tokens ,
234236 )
@@ -237,6 +239,11 @@ impl AiNoteRefineAdapter {
237239 & self . client . config . model , & system, & user, images, budget. max_tokens ,
238240 )
239241 } ;
242+ // B1(审查):json_object=单一 JSON 值约束与 NDJSON 多行对象冲突——
243+ // 流式拍必须去除 response_format(非流式回退路径保留既有约束)
244+ if let Some ( o) = payload. as_object_mut ( ) {
245+ o. remove ( "response_format" ) ;
246+ }
240247 let mut sections: Vec < crate :: ai_refine_protocol:: AiRefineSection > = Vec :: new ( ) ;
241248 let mut pending = String :: new ( ) ;
242249 let outcome = crate :: ai_chat_stream:: stream_sse_content (
@@ -258,16 +265,47 @@ impl AiNoteRefineAdapter {
258265 sections. push ( sec. clone ( ) ) ;
259266 on_section ( sec) ;
260267 }
261- let _ = outcome. content ;
268+ // B2(审查):未收到 [DONE] 即断流——已累积节不可信(尾节可能丢失),
269+ // 整体视作失败走同拍非流式回退(禁止静默截断当成功)
270+ if !outcome. completed {
271+ return Err ( AiClientError :: Network (
272+ "流式响应未正常收尾(SSE 中断)——回退非流式" . to_string ( ) ,
273+ ) ) ;
274+ }
275+ // B3(审查):整包回退承诺兑现——模型输出完整数组(紧凑/pretty)时
276+ // 对已累积全文做整包解析(零额外请求;仅此路径无 Delta 帧)
277+ if sections. is_empty ( ) {
278+ let text = outcome. content . trim ( ) ;
279+ if !text. is_empty ( ) {
280+ let v: serde_json:: Value = serde_json:: from_str ( text)
281+ . map_err ( |e| AiClientError :: Parse ( format ! ( "流式整包解析失败: {}" , e) ) ) ?;
282+ let arr = v
283+ . get ( "sections" )
284+ . cloned ( )
285+ . unwrap_or_else ( || v. clone ( ) ) ;
286+ let parsed: Vec < crate :: ai_refine_protocol:: AiRefineSection > =
287+ serde_json:: from_value ( arr) . map_err ( |e| {
288+ AiClientError :: Parse ( format ! ( "流式整包结构非法: {}" , e) )
289+ } ) ?;
290+ if !parsed. is_empty ( ) {
291+ sections = parsed;
292+ }
293+ }
294+ }
262295 if sections. is_empty ( ) {
263296 return Err ( AiClientError :: Parse (
264297 "流式响应未解析出章节(模型未按逐节输出——回退非流式)" . to_string ( ) ,
265298 ) ) ;
266299 }
267- Ok ( crate :: ai_refine_protocol:: AiRefineResponse {
300+ let resp = crate :: ai_refine_protocol:: AiRefineResponse {
268301 schema_version : crate :: ai_refine_protocol:: SCHEMA_VERSION_V2 ,
269302 sections,
270- } )
303+ } ;
304+ // B6(审查):与 refine_vision 同口径——组装后过整包校验
305+ resp. validate ( ) . map_err ( |e| {
306+ AiClientError :: Parse ( format ! ( "流式响应校验失败(已丢弃回退): {}" , e) )
307+ } ) ?;
308+ Ok ( resp)
271309 }
272310}
273311
0 commit comments