@@ -10,6 +10,50 @@ function formatOldOutlineForPrompt(oldOutline) {
1010 return typeof oldOutline === 'string' ? oldOutline : JSON . stringify ( oldOutline , null , 2 ) ;
1111}
1212
13+ function formatOutlineItemLabel ( item , fallback = '未命名目录' ) {
14+ const id = String ( item ?. id || '' ) . trim ( ) ;
15+ const title = String ( item ?. title || '' ) . trim ( ) || fallback ;
16+ return id ? `${ id } ${ title } ` : title ;
17+ }
18+
19+ function childrenOutlineJsonExample ( parentId ) {
20+ const id = String ( parentId || '1' ) . trim ( ) || '1' ;
21+ return `{
22+ "children": [
23+ {
24+ "id": "${ id } .1",
25+ "title": "二级目录标题",
26+ "description": "二级目录说明",
27+ "children": [
28+ {
29+ "id": "${ id } .1.1",
30+ "title": "三级目录标题",
31+ "description": "三级目录说明"
32+ },
33+ {
34+ "id": "${ id } .1.2",
35+ "title": "三级目录标题",
36+ "description": "三级目录说明"
37+ }
38+ ]
39+ }
40+ ]
41+ }` ;
42+ }
43+
44+ function childrenOutlineStructureRules ( parentId ) {
45+ const id = String ( parentId || '1' ) . trim ( ) || '1' ;
46+ return `结构要求:
47+ 1. 顶层 children 只能放当前一级目录的直接子目录,也就是二级目录。
48+ 2. 每个二级目录都必须包含非空 children 数组,children 内是三级目录。
49+ 3. 不要把评分细项直接作为没有子节点的二级目录;应先归纳二级主题,再在其下展开三级响应要点、实施措施、证明材料或验收标准。
50+ 4. 三级目录只包含 id、title、description,不要继续包含 children。
51+ 5. 编号必须以当前一级目录编号 ${ id } 为前缀,例如二级 ${ id } .1,三级 ${ id } .1.1。
52+
53+ 返回示例:
54+ ${ childrenOutlineJsonExample ( id ) } `;
55+ }
56+
1357const KNOWLEDGE_RESUME_MAX_CHARS = 220 ;
1458const MAX_KNOWLEDGE_ADDITIONS = 30 ;
1559
@@ -277,10 +321,10 @@ function generateAlignedChildrenMessages({ overview, requirements, parentItem, g
2773211. 一级目录标题和顺序已经固定,不能修改、重命名、合并或删除一级目录
2783222. 只输出当前一级目录下的二级和三级目录,不要重复输出一级目录本身
2793233. 二级和三级目录要覆盖当前技术评分大类及其细项,不能越界写入其他评分大类内容
280- 4. 返回标准 JSON,格式为 {"children": [...]},children 中只能包含当前一级目录的直接子目录
281- 5. 每个节点必须包含 id、title、description,三级目录继续使用 children 字段
282- 6. 章节编号必须以给定的一级目录编号为前缀,例如父级是 2,则二级目录编号从 2.1 开始,三级目录编号从 2.1.1 开始
283- 7. 除了 JSON 结果外,不要输出任何其他内容 `;
324+ 4. 返回标准 JSON,格式为 {"children": [...]},每个节点必须包含 id、title、description
325+ 5. 除了 JSON 结果外,不要输出任何其他内容
326+
327+ ${ childrenOutlineStructureRules ( parentItem . id ) } `;
284328 const messages = [
285329 { role : 'system' , content : systemPrompt } ,
286330 { role : 'user' , content : `项目概述:\n${ overview } ` } ,
@@ -290,9 +334,9 @@ function generateAlignedChildrenMessages({ overview, requirements, parentItem, g
290334 ] ;
291335 if ( formattedOldOutline ) {
292336 messages . push ( { role : 'user' , content : `用户自己编写的目录参考:\n${ formattedOldOutline } ` } ) ;
293- messages . push ( { role : 'user' , content : `请在覆盖当前技术评分大类细项的前提下,参考用户目录优化当前一级目录下的二级、三级目录,但不得修改当前一级目录标题 ,返回格式必须是 {"children": [...]}。${ formatSuggestions ( suggestions ) } ` } ) ;
337+ messages . push ( { role : 'user' , content : `请在覆盖当前技术评分大类细项的前提下,参考用户目录优化当前一级目录下的二级、三级目录;每个二级目录必须包含三级目录,不得修改当前一级目录标题 ,返回格式必须是 {"children": [...]}。${ formatSuggestions ( suggestions ) } ` } ) ;
294338 } else {
295- messages . push ( { role : 'user' , content : `请仅生成该一级目录下的二级、三级目录,一级目录标题必须保持为当前给定标题,返回格式必须是 {"children": [...]}。${ formatSuggestions ( suggestions ) } ` } ) ;
339+ messages . push ( { role : 'user' , content : `请仅生成该一级目录下的二级、三级目录;每个二级目录必须包含三级目录 ,一级目录标题必须保持为当前给定标题,返回格式必须是 {"children": [...]}。${ formatSuggestions ( suggestions ) } ` } ) ;
296340 }
297341 return messages ;
298342}
@@ -303,11 +347,10 @@ function generateChildrenMessages({ overview, requirements, parentItem, oldOutli
303347
304348要求:
3053491. 只输出当前一级目录下的二级和三级目录,不要重复输出一级目录本身
306- 2. 返回标准 JSON,格式为 {"children": [...]}
307- 3. children 中只能包含当前一级目录的直接子目录,每个节点必须包含 id、title、description
308- 4. 二级目录下如有三级目录,同样使用 children 字段
309- 5. 章节编号必须以给定的一级目录编号为前缀,例如父级是 2,则二级目录编号从 2.1 开始,三级目录编号从 2.1.1 开始
310- 6. 除了 JSON 结果外,不要输出任何其他内容` ;
350+ 2. 返回标准 JSON,格式为 {"children": [...]},每个节点必须包含 id、title、description
351+ 3. 除了 JSON 结果外,不要输出任何其他内容
352+
353+ ${ childrenOutlineStructureRules ( parentItem . id ) } `;
311354 const messages = [
312355 { role : 'system' , content : systemPrompt } ,
313356 { role : 'user' , content : `项目概述:\n${ overview } ` } ,
@@ -316,13 +359,53 @@ function generateChildrenMessages({ overview, requirements, parentItem, oldOutli
316359 ] ;
317360 if ( formattedOldOutline ) {
318361 messages . push ( { role : 'user' , content : `用户自己编写的目录:\n${ formattedOldOutline } ` } ) ;
319- messages . push ( { role : 'user' , content : `请在满足技术评分要求的前提下,充分结合用户自己编写的目录,仅生成该一级目录下的二级、三级目录,返回格式必须是 {"children": [...]}。${ formatSuggestions ( suggestions ) } ` } ) ;
362+ messages . push ( { role : 'user' , content : `请在满足技术评分要求的前提下,充分结合用户自己编写的目录,仅生成该一级目录下的二级、三级目录;每个二级目录必须包含三级目录 ,返回格式必须是 {"children": [...]}。${ formatSuggestions ( suggestions ) } ` } ) ;
320363 } else {
321- messages . push ( { role : 'user' , content : `请仅生成该一级目录下的二级、三级目录,返回格式必须是 {"children": [...]}。${ formatSuggestions ( suggestions ) } ` } ) ;
364+ messages . push ( { role : 'user' , content : `请仅生成该一级目录下的二级、三级目录;每个二级目录必须包含三级目录 ,返回格式必须是 {"children": [...]}。${ formatSuggestions ( suggestions ) } ` } ) ;
322365 }
323366 return messages ;
324367}
325368
369+ function generateChildrenStructureRepairMessages ( { invalidContent, issues } , parentItem , group ) {
370+ const detailLines = ( group ?. detail_points || [ ] )
371+ . filter ( ( item ) => typeof item === 'string' && item . trim ( ) )
372+ . map ( ( item ) => `- ${ item } ` )
373+ . join ( '\n' ) ;
374+ const groupBlock = group ? `
375+ 当前对应的技术评分大类:
376+ requirement_id:${ group . requirement_id || '' }
377+ 标题:${ group . title || '' }
378+ 描述:${ group . description || '' }
379+ 细项:
380+ ${ detailLines || '- 未提供明确细项' } ` : '' ;
381+ return [
382+ {
383+ role : 'user' ,
384+ content : `你是一个严格的 JSON 修复器。请把模型输出修复为“当前一级目录下的二级和三级目录”JSON。
385+
386+ 必须满足:
387+ 1. 顶层只能有 children 数组,不要输出一级目录本身
388+ 2. 顶层 children 是二级目录,每个二级目录都必须包含非空 children 数组
389+ 3. 二级目录的 children 内是三级目录,三级目录只包含 id、title、description,不要继续包含 children
390+ 4. 优先保留原结果中的二级目录标题、说明和顺序,只在每个二级目录下补齐合理三级目录
391+ 5. 不要把评分细项直接作为没有子节点的二级目录
392+ 6. 只返回 JSON,不要输出解释文字
393+
394+ ${ childrenOutlineStructureRules ( parentItem ?. id ) } `,
395+ } ,
396+ { role : 'user' , content : `当前一级目录:
397+ 编号:${ parentItem ?. id || '' }
398+ 标题:${ parentItem ?. title || '' }
399+ 描述:${ parentItem ?. description || '' } ${ groupBlock } ` } ,
400+ { role : 'user' , content : `错误列表:
401+ ${ ( issues || [ ] ) . map ( ( item , index ) => `${ index + 1 } . ${ item } ` ) . join ( '\n' ) } ` } ,
402+ { role : 'user' , content : `待修复内容:
403+ \`\`\`json
404+ ${ String ( invalidContent || '' ) . slice ( 0 , 60000 ) }
405+ \`\`\`` } ,
406+ ] ;
407+ }
408+
326409function reviewOutlineMessages ( { overview, requirements, outline } ) {
327410 const systemPrompt = `你是一个严格的招标文件目录审核专家。请审核目录是否符合项目概述和技术评分要求。
328411
@@ -695,18 +778,34 @@ function outlineDepth(items) {
695778 return items ?. length ? 1 + Math . max ( ...items . map ( ( item ) => outlineDepth ( item . children || [ ] ) ) ) : 0 ;
696779}
697780
781+ function formatMissingOutlineLabels ( items , limit = 8 ) {
782+ const labels = ( items || [ ] ) . map ( ( item , index ) => formatOutlineItemLabel ( item , `第 ${ index + 1 } 个目录` ) ) ;
783+ const visible = labels . slice ( 0 , limit ) . join ( '、' ) ;
784+ return labels . length > limit ? `${ visible } 等 ${ labels . length } 个目录` : visible ;
785+ }
786+
698787function validateCompleteOutline ( payload ) {
699788 const outline = payload . outline || [ ] ;
700789 if ( ! outline . length ) throw new Error ( '目录不能为空' ) ;
701790 if ( outlineDepth ( outline ) < 3 ) throw new Error ( '完整目录至少需要三级结构' ) ;
791+ const shallowItems = outline . filter ( ( item ) => outlineDepth ( [ item ] ) < 3 ) ;
792+ if ( shallowItems . length ) {
793+ throw new Error ( `完整目录至少需要三级结构,以下一级目录缺少三级目录:${ formatMissingOutlineLabels ( shallowItems ) } ` ) ;
794+ }
702795}
703796
704797function validateTopLevelOutline ( payload ) {
705798 if ( ! ( payload . outline || [ ] ) . length ) throw new Error ( '一级目录不能为空' ) ;
706799}
707800
708801function validateChildrenOutline ( payload ) {
709- if ( ! ( payload . children || [ ] ) . length ) throw new Error ( '二级目录不能为空' ) ;
802+ const children = payload . children || [ ] ;
803+ if ( ! children . length ) throw new Error ( '二级目录不能为空' ) ;
804+ const secondLevelWithoutThird = children . filter ( ( item ) => ! ( item . children || [ ] ) . length ) ;
805+ if ( secondLevelWithoutThird . length ) {
806+ throw new Error ( `二级目录必须包含三级目录,缺失三级目录:${ formatMissingOutlineLabels ( secondLevelWithoutThird ) } ` ) ;
807+ }
808+ if ( outlineDepth ( children ) < 2 ) throw new Error ( '二级目录必须包含三级目录' ) ;
710809}
711810
712811function validateRequirementGroups ( payload ) {
@@ -861,6 +960,7 @@ async function generateChildren(aiService, payload, parentItem, suggestions, log
861960 temperature : 0.7 ,
862961 normalizer : ( value ) => normalizeChildrenResponse ( value , new Set ( ) ) ,
863962 validator : validateChildrenOutline ,
963+ repairMessagesBuilder : ( context ) => generateChildrenStructureRepairMessages ( context , parentItem ) ,
864964 progressCallback : ( message ) => log ( message , progress ) ,
865965 progressLabel : `章节 ${ parentItem . title || '未命名章节' } 子目录` ,
866966 failureMessage : '模型返回的目录数据格式无效' ,
@@ -973,6 +1073,7 @@ async function generateAlignedChildrenForGroup(aiService, payload, parentItem, g
9731073 temperature : 0.7 ,
9741074 normalizer : ( value ) => normalizeChildrenResponse ( value , new Set ( ) ) ,
9751075 validator : validateChildrenOutline ,
1076+ repairMessagesBuilder : ( context ) => generateChildrenStructureRepairMessages ( context , parentItem , group ) ,
9761077 progressCallback : ( message ) => log ( message , progress ) ,
9771078 progressLabel : `章节 ${ parentItem . title || '未命名章节' } 子目录` ,
9781079 failureMessage : '模型返回的目录数据格式无效' ,
0 commit comments