Skip to content

Commit 5b30585

Browse files
committed
feat(ai): 精修可到达度——策略层声明化(JSON v3)+ 笔记级精修 + 流式帧管道(REQ-245~247)
1 parent a96cbbd commit 5b30585

16 files changed

Lines changed: 1191 additions & 89 deletions

‎app/src-tauri/prompts/note_refine.json‎

Lines changed: 136 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"version": 2,
2+
"version": 3,
33
"core_instruction": "精修=整理不创作:去除非知识内容(寒暄/废话/重复/口头禅),按层级组织为结构化笔记;不增补课程外事实、不编造、不修正技术内容(错词/术语属书面化域不归你管);沿用输入中的章节层级与术语表,不自行发明章节;技术术语/数字/专有名词即使片段也保留。F3 v2:输入含 slice_index/slice_total/prev_summary/next_summary——你是长笔记的第 N 片:只整理本片 content 对应内容,禁止重复输出全局章节标题(沿用 chapters 全局章节),片首/片尾与相邻片衔接处不重复内容;输入中的配图行(- ![画面 N](session-images/...))必须以 image 块原样保留(content 完整路径,不得改写)。",
44
"styles": {
55
"lecture": {
@@ -21,6 +21,10 @@
2121
"meeting": {
2222
"name": "纪要式",
2323
"system": "你是会议纪要整理助手。整理为结构化纪要:决议/行动项/要点分层,行动项用 list 呈现,删除闲谈与重复。"
24+
},
25+
"handwritten": {
26+
"name": "笔记式",
27+
"system": "你是学习笔记整理助手。把零散的原始笔记整理成结构化知识笔记:合并碎片化条目、按逻辑层级组织(概念/步骤/要点/结论)、保留原文信息与措辞含义,删除重复与冗余;不补全笔记中缺失的知识(补全是另一项能力,不属于整理)。"
2428
}
2529
},
2630
"profile_style": {
@@ -36,7 +40,8 @@
3640
"exercise": "lecture",
3741
"follow-along": "hands-on",
3842
"coding": "hands-on",
39-
"unknown": "lecture"
43+
"unknown": "lecture",
44+
"handwritten": "handwritten"
4045
},
4146
"fallback_style": "lecture",
4247
"few_shot": [
@@ -45,5 +50,133 @@
4550
"output": "{\"schemaVersion\":2,\"sections\":[{\"heading\":\"第一章 引言\",\"blocks\":[{\"type\":\"paragraph\",\"content\":\"卷积神经网络的核心概念:卷积与池化。\",\"anchor_ref\":\"第一章 引言\"},{\"type\":\"term\",\"content\":\"卷积:对输入进行特征提取的运算。\",\"anchor_ref\":null},{\"type\":\"term\",\"content\":\"池化:对特征图进行下采样。\",\"anchor_ref\":null},{\"type\":\"highlight\",\"content\":\"重要:本节为核心概念。\",\"anchor_ref\":null},{\"type\":\"image\",\"content\":\"session-images/5/full/30000.webp\",\"anchor_ref\":null}]}]}"
4651
}
4752
],
48-
"output_format": "只输出 JSON,不要任何其他文字、解释或代码块标记。格式:{\"schemaVersion\":2,\"sections\":[{\"heading\":\"<章节标题,沿用输入章节或按内容归纳>\",\"blocks\":[{\"type\":\"paragraph|list|term|highlight|quote|image\",\"content\":\"<内容>\",\"anchor_ref\":\"<可空:引用的章节标题>\"}]}]}。要求:schemaVersion 必须为 2;sections 非空;每节至少 1 个块;content 非空且不超过 4000 字;总块数不超过 200;type 只能取六个枚举值之一;image 块 content 必须以 session-images/ 开头(配图原样保留,禁止改写路径);每节 image 块不超过 5 个。"
53+
"output_format": "只输出 JSON,不要任何其他文字、解释或代码块标记。格式:{\"schemaVersion\":2,\"sections\":[{\"heading\":\"<章节标题,沿用输入章节或按内容归纳>\",\"blocks\":[{\"type\":\"paragraph|list|term|highlight|quote|image\",\"content\":\"<内容>\",\"anchor_ref\":\"<可空:引用的章节标题>\"}]}]}。要求:schemaVersion 必须为 2;sections 非空;每节至少 1 个块;content 非空且不超过 4000 字;总块数不超过 200;type 只能取六个枚举值之一;image 块 content 必须以 session-images/ 开头(配图原样保留,禁止改写路径);每节 image 块不超过 5 个。",
54+
"strategy_dims": [
55+
{
56+
"key": "examples",
57+
"label": "例子密度",
58+
"options": [
59+
{ "value": "keep_all", "label": "全保留", "instruction": "例子全部保留。" },
60+
{ "value": "standard", "label": "标准", "instruction": "例子保留典型示例。" },
61+
{ "value": "condensed", "label": "浓缩", "instruction": "例子只保留一个典型示例并压缩说明。" }
62+
],
63+
"default": "standard"
64+
},
65+
{
66+
"key": "concept",
67+
"label": "概念表达",
68+
"options": [
69+
{ "value": "original", "label": "原文术语", "instruction": "概念保留原文表达与术语。" },
70+
{ "value": "academic", "label": "学术书面", "instruction": "概念用学术书面语表达:术语保留、定义清晰、表述严谨。" },
71+
{ "value": "plain", "label": "通俗白话", "instruction": "概念用通俗白话转述,可用生活化类比,但术语与数据保持原文。" }
72+
],
73+
"default": "original"
74+
},
75+
{
76+
"key": "colloquial",
77+
"label": "口语保留",
78+
"options": [
79+
{ "value": "purified", "label": "纯知识", "instruction": "删除所有口语痕迹,保留书面知识表述。" },
80+
{ "value": "light", "label": "轻度保留", "instruction": "轻度保留口语痕迹(语气词与口语化表达少量保留),无意义填充词仍删除。" },
81+
{ "value": "verbatim", "label": "原文保真", "instruction": "保留原文口吻与句式(仅合并碎句、删除填充词)。" }
82+
],
83+
"default": "purified"
84+
},
85+
{
86+
"key": "emotion",
87+
"label": "情绪内容",
88+
"options": [
89+
{ "value": "drop", "label": "全删", "instruction": "删除自嘲、调侃、情绪化表达,只留知识。" },
90+
{ "value": "light", "label": "轻度保留", "instruction": "轻度保留情绪化表达(自嘲/调侃金句),不干扰知识密度。" },
91+
{ "value": "keep", "label": "保留", "instruction": "保留原文情绪化表达与共鸣句,知识密度允许略降。" }
92+
],
93+
"default": "drop"
94+
},
95+
{
96+
"key": "conclusion",
97+
"label": "结论优先级",
98+
"options": [
99+
{ "value": "as_is", "label": "原文顺序", "instruction": "按原文顺序组织,不调整内容顺序。" },
100+
{ "value": "highlight_top", "label": "金句置顶", "instruction": "每节开头用 highlight 置顶本节核心结论(从原文提炼,不得虚构)。" },
101+
{ "value": "summary_top", "label": "摘要+置顶", "instruction": "每节开头加一句概括导语,并置顶核心结论(从原文提炼,不得虚构)。" }
102+
],
103+
"default": "as_is"
104+
},
105+
{
106+
"key": "sections",
107+
"label": "章节结构",
108+
"options": [
109+
{ "value": "inherit", "label": "沿用", "instruction": "沿用输入章节结构,不自行发明章节。" },
110+
{ "value": "ai", "label": "AI 归纳", "instruction": "按内容逻辑归纳章节标题(可合并/归纳,不增补章节内容)。" }
111+
],
112+
"default": "inherit"
113+
}
114+
],
115+
"ladder_presets": [
116+
{
117+
"id": "faithful",
118+
"name": "忠实整理",
119+
"desc": "仅去口癖/碎句合并,句序句风原样",
120+
"instruction": "本次为忠实整理档:仅做轻度整理(删除无意义填充词与重复、合并碎句),保留原句顺序与口吻,不做归纳改写。",
121+
"dimValues": { "examples": "keep_all", "concept": "original", "colloquial": "verbatim", "emotion": "keep", "conclusion": "as_is", "sections": "inherit" }
122+
},
123+
{
124+
"id": "standard",
125+
"name": "标准精修",
126+
"desc": "去非知识+结构化(现状行为)",
127+
"instruction": "",
128+
"dimValues": {}
129+
},
130+
{
131+
"id": "deep",
132+
"name": "深度改写",
133+
"desc": "通俗转述+金句置顶+每节导语",
134+
"instruction": "本次为深度改写档:允许表达转述与概括提炼(通俗化/金句置顶/导语),但事实红线不动——不新增课程外具体事实/数字/概念,术语与数据保持原文,不确定时保留原文表达。",
135+
"dimValues": { "concept": "plain", "conclusion": "summary_top", "colloquial": "light", "emotion": "light" }
136+
},
137+
{
138+
"id": "minimal",
139+
"name": "极简提取",
140+
"desc": "只留骨架:概念/步骤/结论",
141+
"instruction": "本次为极简提取档:每节只保留核心概念/步骤/结论,删除例子与铺垫,单节内容块不超过 3 个,直接呈现骨架。",
142+
"dimValues": { "examples": "condensed", "colloquial": "purified", "emotion": "drop", "conclusion": "as_is", "sections": "inherit" }
143+
}
144+
],
145+
"intents": [
146+
{
147+
"id": "verbatim",
148+
"label": "原文保真",
149+
"keywords": ["原样", "保真", "不改", "忠实"],
150+
"instruction": "本次为原文保真档:仅做轻度整理(删除无意义填充词与重复、合并碎句),保留原句顺序与口吻,不做归纳改写。",
151+
"dimValues": { "examples": "keep_all", "concept": "original", "colloquial": "verbatim", "emotion": "keep", "conclusion": "as_is", "sections": "inherit" }
152+
},
153+
{
154+
"id": "exam",
155+
"label": "考点浓缩",
156+
"keywords": ["背", "考点", "复习", "考试", "背诵"],
157+
"instruction": "本次为考点浓缩档:按考点浓缩知识——只留概念/公式/步骤/结论骨架,删除例子与铺垫,每节内容块不超过 3 个。",
158+
"dimValues": { "examples": "condensed", "colloquial": "purified", "emotion": "drop", "conclusion": "as_is", "sections": "inherit" }
159+
},
160+
{
161+
"id": "plain",
162+
"label": "通俗转述",
163+
"keywords": ["通俗", "说人话", "白话", "易懂", "好懂"],
164+
"instruction": "本次为通俗转述档:允许表达转述与概括提炼(通俗化/金句置顶/导语),但事实红线不动——不新增课程外具体事实/数字/概念,术语与数据保持原文,不确定时保留原文表达。",
165+
"dimValues": { "concept": "plain", "conclusion": "summary_top", "colloquial": "light", "emotion": "light" }
166+
},
167+
{
168+
"id": "quickref",
169+
"label": "速查纲要",
170+
"keywords": ["速查", "快查", "纲要", "检索", "速览"],
171+
"instruction": "本次为速查纲要档:每节组织结构清晰,只留要点与结论,删除非必要解释,例子压缩。",
172+
"dimValues": { "examples": "condensed", "conclusion": "highlight_top" }
173+
},
174+
{
175+
"id": "golden",
176+
"label": "金句摘录",
177+
"keywords": ["金句", "观点", "语录", "精辟"],
178+
"instruction": "本次为金句摘录档:保留原句表达与情绪共鸣,结论金句置顶,例子保留完整。",
179+
"dimValues": { "conclusion": "summary_top", "emotion": "keep", "examples": "keep_all" }
180+
}
181+
]
49182
}

‎app/src-tauri/src/ai_note_refine.rs‎

Lines changed: 48 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,46 @@
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
1216
use serde::{Deserialize, Serialize};
1317

1418
use crate::ai_client::{AiClient, AiClientError, parse_json_object};
1519
use 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)]
1924
pub 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

Comments
 (0)