1- //! AI 笔记精修适配器(REQ-141,v0.8.0 M2)。
1+ //! AI 笔记精修适配器(REQ-141,v0.8.0 M2;v0.17.0 策略层透传 )。
22//!
33//! @ai-context: 输入=note_filter 规则草稿(markdown+档案+术语表+章节)→
4- //! 按档案提示词模板(prompts/note_refine.json 编译期捆绑)→
5- //! 云端结构化精修 → 强校验(AiRefineResponse::validate)→
6- //! 失败回退纯规则(不丢不假,本地优先铁律;错误经 AiClientError
7- //! 归一为任务失败四类原因)。
4+ //! 按档案提示词模板(prompts/note_refine.json 编译期捆绑,v3
5+ //! 含策略声明)→ 云端结构化精修 → 强校验(AiRefineResponse::
6+ //! validate)→ 失败回退纯规则(不丢不假,本地优先铁律;错误经
7+ //! AiClientError 归一为任务失败四类原因)。
88//! @ai-context: 档案分组:网课=讲义式/实操=步骤式/口播=摘要式/访谈=问答式/
99//! 会议=纪要式 + 扩展类(直播/白板/题目/跟练/编程…)回退讲义式
10- //! ——profile_style 映射表可校准;模型切换零代码改动。
10+ //! + v0.17.0 handwritten(笔记式——仅笔记级精修请求使用,
11+ //! 采集端零改动)——profile_style 映射表可校准;模型切换零代码改动。
12+ //! @ai-context: 策略层(REQ-245):build_system 增 ResolvedDims(可选)——
13+ //! 空指令段落 → 不追加(standard 档与 v0.16.1 逐字节一致);
14+ //! 策略只改提示词,协议与校验零改动(ADR-026-1)。
1115
1216use serde:: { Deserialize , Serialize } ;
1317
1418use crate :: ai_client:: { AiClient , AiClientError , parse_json_object} ;
1519use crate :: ai_refine_protocol:: { AiRefineRequest , AiRefineResponse } ;
20+ use crate :: ai_strategy:: { IntentPreset , LadderPreset , ResolvedDims , StrategyDim } ;
1621
17- /// 提示词模板(prompts/note_refine.json 结构)。
22+ /// 提示词模板(prompts/note_refine.json v3 结构)。
1823#[ derive( Debug , Clone , PartialEq , Serialize , Deserialize ) ]
1924pub struct NoteRefinePrompt {
2025 pub version : u32 ,
2126 /// 核心指令(精修=整理不创作——所有档案共享)
2227 pub core_instruction : String ,
23- /// 五种风格模板 (讲义/步骤/摘要/问答/纪要)
28+ /// 风格模板 (讲义/步骤/摘要/问答/纪要/笔记式——v0.17.0 增 handwritten )
2429 pub styles : std:: collections:: HashMap < String , NoteRefineStyle > ,
2530 /// 档案 → 风格映射(扩展类回退讲义式)
2631 pub profile_style : std:: collections:: HashMap < String , String > ,
2732 pub fallback_style : String ,
2833 pub few_shot : Vec < PromptExample > ,
2934 pub output_format : String ,
35+ /// 策略维度声明(REQ-245:旋钮——声明进 JSON 可校准)
36+ #[ serde( default ) ]
37+ pub strategy_dims : Vec < StrategyDim > ,
38+ /// 档位预设(阶梯:L1 忠实/L2 标准/L3 深度/L4 极简)
39+ #[ serde( default ) ]
40+ pub ladder_presets : Vec < LadderPreset > ,
41+ /// 目标意图预设(chips + 自由输入关键词映射)
42+ #[ serde( default ) ]
43+ pub intents : Vec < IntentPreset > ,
3044}
3145
3246/// 单风格模板(style 名 + system 提示词)。
@@ -70,7 +84,7 @@ impl NoteRefinePrompt {
7084 let mut profile_style = std:: collections:: HashMap :: new ( ) ;
7185 profile_style. insert ( "lecture" . to_string ( ) , "lecture" . to_string ( ) ) ;
7286 Self {
73- version : 2 ,
87+ version : 3 ,
7488 core_instruction : "精修=整理不创作:去噪 + 结构化,不增补课程外事实。"
7589 . to_string ( ) ,
7690 styles,
@@ -79,6 +93,9 @@ impl NoteRefinePrompt {
7993 few_shot : Vec :: new ( ) ,
8094 output_format : "只输出 JSON:{\" schemaVersion\" :2,\" sections\" :[{\" heading\" :\" ..\" ,\" blocks\" :[{\" type\" :\" paragraph|list|term|highlight|quote|image\" ,\" content\" :\" ..\" ,\" anchor_ref\" :null}]}]}"
8195 . to_string ( ) ,
96+ strategy_dims : Vec :: new ( ) ,
97+ ladder_presets : Vec :: new ( ) ,
98+ intents : Vec :: new ( ) ,
8299 }
83100 }
84101
@@ -96,9 +113,19 @@ impl NoteRefinePrompt {
96113 . unwrap_or_else ( || "你是课堂笔记整理助手。" . to_string ( ) )
97114 }
98115
99- /// 组装 system 提示词(核心指令 + 档案风格 + few-shot + 输出约束)。
100- pub fn build_system ( & self , profile : & str ) -> String {
116+ /// 组装 system 提示词(核心指令 + 档案风格 + 策略段落 + few-shot + 输出约束)。
117+ ///
118+ /// @ai-context: 策略段落插在 few_shot 前(spec 管线顺序);dims=None 或
119+ /// 指令段落为空 → 与 v0.16.1 输出逐字节一致(零变化保证——
120+ /// standard 档/旧调用路径不扰动既有行为)。
121+ pub fn build_system ( & self , profile : & str , dims : Option < & ResolvedDims > ) -> String {
101122 let mut s = format ! ( "{}\n {}" , self . core_instruction, self . style_system( profile) ) ;
123+ if let Some ( d) = dims {
124+ let strategy = crate :: ai_strategy:: strategy_instructions ( d, self ) ;
125+ if !strategy. is_empty ( ) {
126+ s. push_str ( & format ! ( "\n {}" , strategy) ) ;
127+ }
128+ }
102129 for ex in & self . few_shot {
103130 s. push_str ( & format ! ( "\n 示例输入: {}\n 示例输出: {}" , ex. input, ex. output) ) ;
104131 }
@@ -119,30 +146,30 @@ impl AiNoteRefineAdapter {
119146 Self { client, prompt : NoteRefinePrompt :: bundled ( ) }
120147 }
121148
122- /// 单次精修(一请求=一切片;长笔记切片由 command 层任务编排)。
123- ///
124- /// @ai-context: 强校验在此层做(validate 失败 → Parse 错误 → 调用方
125- /// 回退纯规则,非法响应不进入笔记管线——防御性编程铁律)。
126- /// @ai-context: F3 v2:请求携带片间上下文(slice_index/total/prev/next
127- /// summary)——提示词据此约束"只整理本片、沿用全局章节";
128- /// 响应 schema_version=2(向后兼容:旧响应缺省 1 仍可解析)。
129- pub fn refine ( & self , request : & AiRefineRequest ) -> Result < AiRefineResponse , AiClientError > {
149+ /// 单次精修(纯文本路径;dims=None=标准档/路径,行为与 v0.16.1 一致)。
150+ pub fn refine (
151+ & self ,
152+ request : & AiRefineRequest ,
153+ dims : Option < & ResolvedDims > ,
154+ ) -> Result < AiRefineResponse , AiClientError > {
130155 // v0.12.0 M5:单一实现——refine = refine_vision(request, &[])(纯文本,
131156 // 与既有行为逐字节一致;画面理解是可选增强,不改变默认链)
132- self . refine_vision ( request, & [ ] )
157+ self . refine_vision ( request, & [ ] , dims )
133158 }
134159
135160 /// 单次精修(多模态分支,v0.12.0 M5:vision-exp 视觉提取)。
136161 ///
137162 /// @ai-context: images 非空 → 屏卡图随切片请求送 vision-exp(content 数组
138163 /// 多模态,chat_vision);空 → 纯文本(chat_json,零变化)。
139164 /// 精修输出直接含画面要点;校验/回退逻辑与 refine 共用。
165+ /// v0.17.0:dims 透传提示词策略段落(协议零改动)。
140166 pub fn refine_vision (
141167 & self ,
142168 request : & AiRefineRequest ,
143169 images : & [ String ] ,
170+ dims : Option < & ResolvedDims > ,
144171 ) -> Result < AiRefineResponse , AiClientError > {
145- let system = self . prompt . build_system ( & request. profile ) ;
172+ let system = self . prompt . build_system ( & request. profile , dims ) ;
146173 let user = serde_json:: to_string ( request)
147174 . map_err ( |e| AiClientError :: Parse ( format ! ( "精修请求序列化失败: {}" , e) ) ) ?;
148175 let v = if images. is_empty ( ) {
0 commit comments