分析计划、分块与进度管理
本文说明文档分析如何建立本地分段计划,以及当前 useAnalysis 全量章节管道如何切片、批处理并报告进度。两条路径使用不同的分块表示:不要把计划中的 ChunkPlan 误认为章节管道实际发送的 RAG 文本片段。
Purpose and Scope
本页聚焦 analysisWorkflow.ts 的计划与提示词辅助函数,及 useAnalysis.ts 中已读到的章节管道、批次进度与错误降级。文档提取、模型供应商配置、图片后处理、结果归档和 RAG 嵌入的内部实现属于相邻主题;这里仅解释它们与分块工作流接触的边界。特别注意:已读取的 useAnalysis 片段导入了图片辅助函数,而没有导入 buildLocalWorkflowPlan 或 buildChunkPlan;因此不能断言章节管道直接执行计划器。
Overview
计划辅助函数可按页文本建立 ChunkPlan[],以最多四页或累计至少 7000 字符为分段边界,页文本缺失时按字符长度退化切分;它还能把规划 JSON 与本地默认计划合并,并构造分段写作及最终整合提示词。另一方面,runSectionPipeline 调用 chunkText 产生更小的片段,对每个章节任务遍历全部片段,按字符预算打包送往模型;其进度由任务和批次位置推算,而非模型完成百分比。计划构建 · 章节管道。
Architecture
Sources: analysisWorkflow.ts, useAnalysis.ts
图上两组没有连接线:源码中确实存在计划、分段与合成辅助函数,但本次查看的章节管道直接使用 chunkText,并未展示调用这些计划函数。Parse 到两个提示词构造器的箭头表示计划数据可作为它们的参数,不表示已经证实有运行时调用链。章节管道的模型设置来自调用参数 settings,currentProvider 则取自 store。调用签名 · 模型调用。
计划生成与解析
本地计划与边界选择
buildLocalWorkflowPlan(bundle, fileName, fileType) 首先用 buildChunkPlan 生成分段,再按 bundle.images 形成 imagePlacementHints,每张图从对应页文本推断建议章节:含“例题/习题/答案”标作“典型例题”,否则标作“基础知识”。它生成概览以及保留定义、公式、图片和推导的完成要求。页数大于零时概览记录页数与段数,否则说明按字符分段。本地计划 · 章节推断。
buildChunkPlan 在 pageTexts 为空时直接转向文本回退;否则按页累积文本长度,并在页数达到 4、字符数达到 7000 或到达最后一页时封段。每段的起止页连续递增,图片页码从该范围内的 bundle.images 取得,关注点来自 inferChunkFocus;图片页码可能因多张同页图片重复出现(源码未去重)。字符阈值是封段触发条件,不是硬上限:一页超过阈值仍会作为完整页纳入该段。分段算法。
1const PAGE_IMAGE_TOKEN_PREFIX = "[[PAGE_IMAGE_";
2const DEFAULT_PAGE_CHUNK_SIZE = 4;
3const DEFAULT_CHAR_CHUNK_SIZE = 7000;
4
5export const WORKFLOW_PLANNER_MODEL = "openai/gpt-oss-120b:free";
6export const WORKFLOW_COMPOSER_MODEL = "minimax/minimax-m2.5:free";Source: analysisWorkflow.ts
这两个模型常量在辅助模块中声明;在已读取的章节管道里,实际请求使用 settings.model,不能把这些常量当作该管道运行时的默认模型。模型请求。
文本退化切分与规划结果容错
splitTextIntoChunks(text, maxChunkLength = 7000) 先去首尾空白;空字符串返回 [],短文本返回单元素数组。长文本尝试在当前长度上限之前、且超过本段半个预算处的最后一个空段落处切开,否则直接按长度截断;每片再次 trim() 并忽略空片。buildTextFallbackChunkPlan 将每片转换为顺序编号的段,合成的 pageStart/pageEnd 是片段序号,并非真实页码。退化切分。
parseWorkflowPlan(content, fallbackPlan) 先剥除完整 Markdown 代码围栏,空文本直接返回回退计划;解析成功时合并顶层属性,并对 chunks、图片提示和完成要求作字段级回退/类型清洗;解析失败记录 console.warn 后返回回退计划。它对 pageStart/pageEnd 使用 Number(value) || fallback || 1,并没有在这些行进行页范围有序性或文档页数约束检查,调用方不应视之为严格验证器。解析逻辑 · 字段合并。
1 if (bundle.pageTexts.length === 0) {
2 return buildTextFallbackChunkPlan(bundle.text);
3 }
4
5 const chunks: ChunkPlan[] = [];
6 let startPage = 1;
7 let currentLength = 0;Source: analysisWorkflow.ts
分段写作和整合提示词
buildChunkPrompt 接收源提示词、计划、当前段、当前文字、文件名及图片 token;提示模型只写当前页范围,保留原文专业术语、公式与例题步骤。includeImages 默认为 true,关闭时省略图片 token 提示。buildComposerPrompt 则收集所有段落结果,以 \n\n---\n\n 分隔,要求按顺序整合为 Markdown;打开图片选项时给出 token 到 localPath || dataUrl 的映射。这些是提示词构造器,是否被调用及最终模型行为取决于外部调用点。分段提示 · 整合提示。
Core Flow:全量章节管道与进度
Source: useAnalysis.ts
这里的 400/60/60 是传给 chunkText 的 targetChunkSize/overlapChars/minChunkSize,并不是 buildChunkPlan 的四页/7000 字符阈值;chunkText 的内部切割细节未在已读源码中核实。分块调用。
每个章节任务都取 effectiveChunks = allChunks,没有关键字过滤。管道先预计算批次数用于进度,再以相同的 MAX_CHARS_PER_BATCH = 8000 规则顺序打包;只有当前批非空时才溢出到下一批,因此单个超过 8000 字符的片段不会被此循环再次拆开。片段附带 [片段N(第X页)] 标记。批次提示中的图片标签仅根据这些页码标记选取同页图片,减少无关图片标签。全部片段 · 图片范围。
进度在嵌入阶段设置为 progressStart,章节起点从区间 [progressStart, progressEnd] 的 5% 处开始,每任务按 taskIdx / totalSteps 递进,批内按 batchIdx / max(totalBatches, 1) 在该任务对应进度区间插值。故进度显示是阶段/批次估算,而不是按完成 token 或最终字数计量。计算位置 · 批次插值。
Usage Examples:源码中的调用方式
以下示例均摘自现有实现;它们展示工作流内部的真实用法,而不是另外编造的外部调用程序。
基于文档内容生成待处理片段
1 const allChunks = chunkText(analysisBundle.text, analysisBundle.pageTexts, {
2 targetChunkSize: 400,
3 overlapChars: 60,
4 minChunkSize: 60,
5 });Source: useAnalysis.ts
这一步只负责取得章节管道的片段集合。嵌入向量的生成可用时会尝试一次,但后续任务仍遍历 allChunks;嵌入失败仅记警告,不中断此处已读取的流程。嵌入降级。
根据当前章节和批次更新进度
1 const batchProgressBase = Math.round(sectionProgressStart + (taskIdx / totalSteps) * (sectionProgressEnd - sectionProgressStart));
2 const batchProgressNext = Math.round(sectionProgressStart + ((taskIdx + 1) / totalSteps) * (sectionProgressEnd - sectionProgressStart));
3 setAnalysisProgress({
4 percentage: Math.round(batchProgressBase + (batchIdx / Math.max(totalBatches, 1)) * (batchProgressNext - batchProgressBase)),
5 message: `正在生成「${sectionHeader}」批次 ${batchIdx + 1}/${totalBatches}...`,
6 });Source: useAnalysis.ts
按章节顺序合并非空输出
1 const assembledParts: string[] = [];
2 for (const header of sectionHeaders) {
3 const output = sectionOutputs[header];
4 if (output) {
5 assembledParts.push(`# ${header}\n\n${output}`);
6 }
7 }
8
9 let finalContent = assembledParts.join("\n\n");Source: useAnalysis.ts
未获得内容的章节不被加入最终文本;此处不会生成空标题。图片插入在这一函数之外处理,源码注释指出在 analyzeDocument 完成管道之后进行。图片处理边界。
Configuration Options
| 选项/参数 | 类型 | 已读源码中的值/默认值 | 作用 |
|---|---|---|---|
DEFAULT_PAGE_CHUNK_SIZE | number | 4 | 本地页码计划的页数封段阈值。 |
DEFAULT_CHAR_CHUNK_SIZE | number | 7000 | 本地页码计划的累计字符阈值,也是文本退化切分默认长度。 |
targetChunkSize / overlapChars / minChunkSize | number | 调用处为 400 / 60 / 60 | 传入 chunkText 的章节管道切片参数;不应当作其内部默认值。 |
MAX_CHARS_PER_BATCH | number | 8000 | 章节模型请求批次的累计字符预算,定义在任务循环内。 |
progressStart / progressEnd | number | 函数参数默认 20 / 85 | 章节管道估算进度的起止区间。 |
includeImages | boolean | 提示词构造器参数默认 true | 决定是否向分段/整合提示词加入图片相关内容。 |
来源:阈值 · 切片与进度默认值 · 批次阈值 · 图片选项。这些值是源码常量、函数默认值或调用参数;已读取代码没有证明它们都可在 UI 中修改。
API Reference
| API | 输入 | 返回/行为 |
|---|---|---|
buildLocalWorkflowPlan(bundle: DocumentAnalysisBundle, fileName: string, fileType: SupportedFileType): AnalysisWorkflowPlan | 文档分析数据、文件名、文件类型 | 生成本地段计划、图片定位提示与完成要求。 |
buildChunkPlan(bundle: DocumentAnalysisBundle): ChunkPlan[] | 文档分析数据 | 有页文本按页封段,无页文本按字符退化;无内容可能为空数组。 |
parseWorkflowPlan(content: string, fallbackPlan: AnalysisWorkflowPlan): AnalysisWorkflowPlan | 模型计划文本与回退计划 | 空文本或 JSON 解析失败返回回退计划;成功则清洗并合并字段。 |
splitTextIntoChunks(text: string, maxChunkLength = DEFAULT_CHAR_CHUNK_SIZE): string[] | 待切文本、可选长度 | 返回裁剪空白后的片段;空输入返回空数组。 |
buildChunkPrompt({...}): string | 源提示、计划、段、文本、文件名、图片 token、可选 includeImages | 返回当前分段写作提示文本。 |
buildComposerPrompt({...}): string | 源提示、计划、结果片段、文件名、图片清单、可选 includeImages | 返回整合提示文本。 |
runSectionPipeline(...) : Promise<string> | 文件、章节提示配置、模型连接设置、分析数据、可选进度区间 | 按 chunkTasks 依次生成章节并返回组装的 Markdown;为 useAnalysis 内部函数。 |
签名及行为:计划/解析 · 提示词 · 内部章节管道。已读取实现没有为这些函数声明专门的异常类型;runSectionPipeline 批次调用会在内部捕获模型错误,但不要据此推断所有外部依赖都不会抛错。
Failure Modes, Edge Cases & Concurrency
- 计划解析失败:
JSON.parse出错后告警并整体回退;字段清洗使用宽松转换,而非严格 schema 校验。解析。 - 模型输出为空:先剥离思考标签、代码围栏和格式标记,再移除重复章节标题;若为空,用更明确的提示词重试一次。重试仍空或重试抛错仅记警告,本批不加入输出。主调用抛错也仅记录批次失败。因而最终结果可能缺失某些批次或整个章节。模型处理。
- 嵌入不可用或失败:不可用时跳过,失败时记警告;主章节流程继续,且不根据相似度选 top-K。嵌入与全量覆盖 · 全量循环。
- 顺序与并发:任务循环、批次发送都使用串行
await;已读管道没有显示并发批次请求。两轮遍历(预计算批次与真正发送)使用同一预算规则,批次结果按返回顺序追加。任务/批次遍历 · 实际刷新。 - 启动校验:
analyzeDocument在无文件或需要但未提供 API Key 时 toast 提示并返回;通过后才设置isAnalyzing(true)。多文件情况下源码为每文件计算fileBase/fileSpan;本页所读截断于后续进度映射之前,不推断整体百分比计算方式。入口校验。
Performance, Operations & Extension Points
每个 chunkTasks 都覆盖全部片段,模型调用次数随章节任务数与每任务批次数增长;顺序调用可保留片段和章节顺序,但延长总等待时间。批次只带本批页码对应的图片标签以减少提示词冗余;图片本身不传给模型,而在后处理插入。调用次数结构 · 图片处理说明。嵌入记录 durationMs、模型、片段数及免费额度信息;章节也记录任务、总片段、每章节批次和产出数量,便于定位降级与缺失内容。日志 · 章节日志。
若要改变本地计划的分段粒度,修改 analysisWorkflow 的两个阈值或独立传给 splitTextIntoChunks 的长度;若要改变实际章节管道输入粒度,应查看 chunkText 参数与 MAX_CHARS_PER_BATCH,不能只调本地计划阈值。章节任务和标题来自 promptConfig.chunkTasks/sectionHeaders,实际模型由传入 settings 决定。规划阈值 · 管道参数 · 任务配置使用。