bidAnalysisTask.cjs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. const stableSystemPrompt = `你是专业的招标文件分析助手。请严格基于用户提供的招标文件原文完成提取和总结。
  2. 通用要求:
  3. 1. 保持信息全面、准确,尽量使用原文内容,不要自行编造。
  4. 2. 如果原文没有提及,明确写“没有提及”或“原文未提及”。
  5. 3. 只输出最终结果,不输出过程、提示语或客套话。
  6. 4. 始终使用简体中文。`;
  7. function jsonTask(title, goals, outputJson) {
  8. return `任务:${title}
  9. 目标:${goals}
  10. 约束:
  11. 1. 输出格式必须为 JSON。
  12. 2. 严格按照以下 JSON 格式输出,只修改 value,禁止修改 key 和结构。
  13. 3. 原文中没有的字段填充“没有提及”。
  14. JSON 格式:
  15. ${outputJson}
  16. 仅输出 JSON,不要输出其他内容。`;
  17. }
  18. const tasks = [
  19. {
  20. id: 'projectOverview', label: '项目概述', required: true, output: 'markdown', description: '提取项目基本信息、背景目的、规模预算、时间安排、实施内容和技术特点。',
  21. prompt: () => `任务:提取并总结项目概述信息。
  22. 请重点关注项目名称、基本信息、背景目的、规模预算、时间安排、实施内容、技术特点和其他关键要求。
  23. 工作要求:保持信息全面准确,尽量使用原文内容;只关注与项目实施有关的内容,不提取商务信息;直接返回整理好的项目概述。`,
  24. },
  25. {
  26. id: 'techRequirements', label: '技术评分要求', required: true, output: 'markdown', description: '提取技术评分项、权重分值、评分标准和原文位置。',
  27. prompt: () => `任务:提取技术评分要求。
  28. 重点识别“技术评分”“评标方法”“评分标准”“技术参数”“技术要求”“技术方案”“技术部分”“评审要素”相关章节,不要提取商务、价格、资质等无关条目。
  29. 每一项按以下结构输出:
  30. 【评分项名称】:<原文描述,保留专业术语>
  31. 【权重/分值】:<具体分值或占比>
  32. 【评分标准】:<详细规则>
  33. 【数据来源】:<章节、条款、页码或表格位置>
  34. 若没有明确技术评分表,请根据上下文判断技术评分相关内容。直接返回提取结果。`,
  35. },
  36. { id: 'projectInfo', label: '项目信息', required: false, output: 'json', description: '项目名称、编号、类型、预算和地址。', prompt: () => jsonTask('提取项目信息', '提取项目名称、项目编号、项目类型、项目预算、项目地址。', `{"project_name":"项目名称","project_number":"项目编号","project_type":"项目类型","project_budget":"项目预算","project_address":"项目地址"}`) },
  37. { id: 'partAInfo', label: '甲方信息', required: false, output: 'json', description: '招标人公司、地址、联系人和电话。', prompt: () => jsonTask('提取甲方信息', '提取公司名称、地址、联系人、联系电话。', `{"company_name":"公司名称","address":"地址","contact_person":"联系人","contact_phone":"联系电话"}`) },
  38. { id: 'agentInfo', label: '代理机构信息', required: false, output: 'json', description: '代理机构联系方式和账户信息。', prompt: () => jsonTask('提取代理机构信息', '提取代理机构名称、地址、联系人、电话、邮箱和银行账户信息。', `{"company_name":"公司名称","address":"地址","contact_person":"联系人","contact_phone":"联系电话","email":"联系邮箱","bank_account_name":"银行账户名称","bank_account_number":"银行账户账号","bank_account_address":"银行账户开户行","bank_account_address_detail":"银行账户开户行地址"}`) },
  39. { id: 'keyInfo', label: '投标关键节点', required: false, output: 'json', description: '公告、获取文件、递交、截止和开标信息。', prompt: () => jsonTask('提取投标关键节点', '提取招标公告发布日期、招标文件获取方式、售价、获取时间、提交地点、截止时间、开标时间、开标地点和其他注意事项。', `{"bid_announcement_time":"招标公告发布日期","bid_file_get_way":"招标文件获取方式","bid_file_price":"招标文件售价","get_bid_file_time":"获取招标文件时间","bid_document_submission_location":"投标文件提交地点","bid_submission_deadline":"投标截止时间","bid_opening_time":"开标时间","bid_opening_address":"开标地点","other_notes":"其他注意事项"}`) },
  40. { id: 'marginInfo', label: '投标保证金', required: false, output: 'json', description: '保证金金额、方式、截止和退还条件。', prompt: () => jsonTask('提取投标保证金信息', '提取投标保证金、缴纳方式、截止日期、退还条件、不予退还情形和其他注意事项。', `{"bidding_deposit":"投标保证金","payment_method":"缴纳方式","due_date":"截止日期","refund_conditions":"退还条件","non_refundable_conditions":"不予退还的情形","other_notes":"其他注意事项"}`) },
  41. { id: 'qualificationReview', label: '资格性审查', required: false, output: 'markdown', description: '投标人资格条件和资格审查要求。', prompt: () => '任务:提取招标文件中关于投标人资格性审查的信息。整理成方便阅读的 Markdown,不要使用表格;如果原文是表格,请转换为列表。仅输出整理结果。' },
  42. { id: 'complianceCheck', label: '符合性检查', required: false, output: 'markdown', description: '文件完整性、有效性、规范和偏差处理要求。', prompt: () => '任务:总结招标文件中关于符合性检查的信息,包括文件完整性、文件有效性、文件规范、偏差处理等。整理成 Markdown,不要使用表格。仅输出整理结果。' },
  43. { id: 'openBid', label: '开标要求', required: false, output: 'json', description: '开标时间地点、参与要求、无效标和流程。', prompt: () => jsonTask('提取开标信息', '提取时间地点、参与要求、无效标认定、异议处理、开标流程。', `{"time_place":"时间地点","part_req":"参与要求","invalid_bid":"无效标认定","objection":"异议处理","bid_process":"开标流程"}`) },
  44. { id: 'evaluationBid', label: '评标要求', required: false, output: 'json', description: '评标委员会、评分构成、方法和原则。', prompt: () => jsonTask('提取评标信息', '提取评标委员会组成、职责、评分构成、评标方法类型、评标原则和方法细节、其他评标相关说明。', `{"committee":"评标委员会组成","duties":"评标委员会职责","scoring":"评分构成","method":"评标方法类型","principles":"评标原则和方法细节","others":"其他和评标相关的说明"}`) },
  45. { id: 'businessScoring', label: '商务评分要求', required: false, output: 'markdown', description: '商务评分因素,为商务方案准备。', prompt: () => '任务:提取招标文件中的商务评分因素,为编写投标文件中的商务方案做准备。整理成 Markdown,不要使用表格。仅输出整理结果。' },
  46. { id: 'discardedBids', label: '无效标与废标项', required: false, output: 'markdown', description: '投标无效、废标相关风险项。', prompt: () => '任务:提取招标文件中与投标无效、废标项相关的信息。以列表形式输出 Markdown,不要使用表格。仅输出整理结果。' },
  47. { id: 'signingProcess', label: '合同授予与签订', required: false, output: 'json', description: '中标公示、合同签订、履约保证金和合同文本。', prompt: () => jsonTask('提取合同授予和签订流程', '提取中标公示、合同签订、履约保证金、合同文本等信息。', `{"bid_notice":"中标公示","contract_sign":"合同签订","performance_bond":"履约保证金","contract_text":"合同文本"}`) },
  48. { id: 'terminationCondition', label: '合同解除和终止', required: false, output: 'json', description: '违约解除、不可抗力、合同终止和争议解决。', prompt: () => jsonTask('提取合同解除和终止条件', '提取违约解除、不可抗力、合同终止、争议解决等信息。', `{"breach_termination":"违约解除","force_majeure":"不可抗力","contract_termination":"合同终止","dispute_resolution":"争议解决"}`) },
  49. ];
  50. function getBidAnalysisTasks(mode) {
  51. return mode === 'key' ? tasks.filter((task) => task.required) : tasks;
  52. }
  53. function buildMessages(fileContent, task) {
  54. return [
  55. { role: 'system', content: stableSystemPrompt },
  56. { role: 'user', content: `以下是完整招标文件 Markdown 原文。后续任务必须仅基于这份原文完成:\n\n${fileContent}` },
  57. { role: 'user', content: task.prompt() },
  58. ];
  59. }
  60. async function runBidAnalysisTask({ aiService, workspaceStore, updateTask, payload }) {
  61. const mode = payload.mode || 'key';
  62. const selectedTasks = getBidAnalysisTasks(mode);
  63. const realTimeRender = payload.real_time_render !== false && payload.realTimeRender !== false;
  64. const initialLogs = realTimeRender
  65. ? ['开始解析招标文件。']
  66. : ['开始解析招标文件。', '实时渲染已关闭,每项解析完成后再刷新结果。'];
  67. let technicalPlan = workspaceStore.updateTechnicalPlan({ bidAnalysisMode: mode, bidAnalysisTask: updateTask({ status: 'running', progress: 0, logs: initialLogs }) });
  68. const currentTasks = technicalPlan.bidAnalysisTasks || {};
  69. const tasksToRun = selectedTasks.filter((task) => currentTasks[task.id]?.status !== 'success');
  70. function doneProgress(nextTasks) {
  71. const done = selectedTasks.filter((task) => ['success', 'error'].includes(nextTasks[task.id]?.status)).length;
  72. return Math.round((done / selectedTasks.length) * 100);
  73. }
  74. async function runOne(task) {
  75. let content = '';
  76. workspaceStore.updateTechnicalPlan({
  77. bidAnalysisTasks: { ...(workspaceStore.loadTechnicalPlan()?.bidAnalysisTasks || {}), [task.id]: { id: task.id, label: task.label, status: 'running', content: '' } },
  78. });
  79. await aiService.streamChat({ messages: buildMessages(payload.fileContent, task), temperature: 0.1, response_format: task.output === 'json' ? { type: 'json_object' } : undefined }, (event) => {
  80. if (event.type === 'chunk' && event.chunk) {
  81. content += event.chunk;
  82. if (!realTimeRender) {
  83. return;
  84. }
  85. const prev = workspaceStore.loadTechnicalPlan() || {};
  86. const nextTasks = { ...(prev.bidAnalysisTasks || {}), [task.id]: { id: task.id, label: task.label, status: 'running', content } };
  87. technicalPlan = workspaceStore.updateTechnicalPlan({ bidAnalysisTasks: nextTasks, bidAnalysisProgress: doneProgress(nextTasks) });
  88. updateTask({ status: 'running', progress: technicalPlan.bidAnalysisProgress || 0 }, technicalPlan);
  89. }
  90. });
  91. const prev = workspaceStore.loadTechnicalPlan() || {};
  92. const nextTasks = { ...(prev.bidAnalysisTasks || {}), [task.id]: { id: task.id, label: task.label, status: 'success', content } };
  93. const partial = { bidAnalysisTasks: nextTasks, bidAnalysisProgress: doneProgress(nextTasks) };
  94. if (task.id === 'projectOverview') partial.projectOverview = content;
  95. if (task.id === 'techRequirements') partial.techRequirements = content;
  96. technicalPlan = workspaceStore.updateTechnicalPlan(partial);
  97. updateTask({ status: 'running', progress: technicalPlan.bidAnalysisProgress || 0 }, technicalPlan);
  98. }
  99. await Promise.all(tasksToRun.map((task) => runOne(task).catch((error) => {
  100. const prev = workspaceStore.loadTechnicalPlan() || {};
  101. const nextTasks = { ...(prev.bidAnalysisTasks || {}), [task.id]: { id: task.id, label: task.label, status: 'error', content: prev.bidAnalysisTasks?.[task.id]?.content || '', error: error.message || '解析失败' } };
  102. technicalPlan = workspaceStore.updateTechnicalPlan({ bidAnalysisTasks: nextTasks, bidAnalysisProgress: doneProgress(nextTasks) });
  103. updateTask({ status: 'running', progress: technicalPlan.bidAnalysisProgress || 0, logs: [`${task.label}解析失败:${error.message || '未知错误'}`] }, technicalPlan);
  104. })));
  105. technicalPlan = workspaceStore.updateTechnicalPlan({ bidAnalysisTask: updateTask({ status: 'success', progress: 100, logs: ['招标文件解析完成。'] }) });
  106. updateTask({ status: 'success', progress: 100 }, technicalPlan);
  107. }
  108. module.exports = {
  109. getBidAnalysisTasks,
  110. runBidAnalysisTask,
  111. };