generate-lobster-engine-prompt-sql.js 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. /**
  2. * 从 LobsterEnginePromptCatalog.java 提取 reg(...) 并生成 SQL 初始化脚本。
  3. * 用法: node java/fs-service/scripts/generate-lobster-engine-prompt-sql.js
  4. */
  5. const fs = require('fs');
  6. const path = require('path');
  7. const catalogPath = path.join(__dirname, '../src/main/java/com/fs/company/service/workflow/prompt/LobsterEnginePromptCatalog.java');
  8. const guardrailsPath = path.join(__dirname, '../src/main/java/com/fs/company/service/workflow/prompt/LobsterPromptGuardrails.java');
  9. const outPath = path.join(__dirname, '../../sql/lobster_engine_prompt_init.sql');
  10. const src = fs.readFileSync(catalogPath, 'utf8');
  11. const guardrailsSrc = fs.readFileSync(guardrailsPath, 'utf8');
  12. function loadGuardrailConstants() {
  13. const map = {};
  14. const re = /public static final String (\w+) = ([\s\S]*?);/g;
  15. let m;
  16. while ((m = re.exec(guardrailsSrc)) !== null) {
  17. try {
  18. map[m[1]] = evalConcat(m[2]);
  19. } catch (_) {
  20. /* skip non-string constants */
  21. }
  22. }
  23. return map;
  24. }
  25. const GUARDRAILS = loadGuardrailConstants();
  26. function unescapeJavaString(s) {
  27. return s
  28. .replace(/\\n/g, '\n')
  29. .replace(/\\t/g, '\t')
  30. .replace(/\\"/g, '"')
  31. .replace(/\\\\/g, '\\');
  32. }
  33. function evalConcat(expr) {
  34. const parts = [];
  35. let rest = expr.trim();
  36. while (rest.length > 0) {
  37. while (rest.length && /\s/.test(rest[0])) rest = rest.slice(1);
  38. if (!rest.length) break;
  39. if (rest[0] === '+') {
  40. rest = rest.slice(1).trim();
  41. continue;
  42. }
  43. if (rest[0] === '"') {
  44. let j = 1;
  45. let buf = '';
  46. while (j < rest.length) {
  47. if (rest[j] === '\\' && j + 1 < rest.length) {
  48. const esc = rest[j + 1];
  49. if (esc === 'n') buf += '\n';
  50. else if (esc === 't') buf += '\t';
  51. else if (esc === 'r') buf += '\r';
  52. else if (esc === '"') buf += '"';
  53. else if (esc === '\\') buf += '\\';
  54. else buf += esc;
  55. j += 2;
  56. continue;
  57. }
  58. if (rest[j] === '"') break;
  59. buf += rest[j++];
  60. }
  61. parts.push(buf);
  62. rest = rest.slice(j + 1).trim();
  63. continue;
  64. }
  65. if (rest.startsWith('null')) {
  66. parts.push('');
  67. rest = rest.slice(4).trim();
  68. continue;
  69. }
  70. const guardMatch = rest.match(/^LobsterPromptGuardrails\.(\w+)/);
  71. if (guardMatch) {
  72. parts.push(GUARDRAILS[guardMatch[1]] || '');
  73. rest = rest.slice(guardMatch[0].length).trim();
  74. continue;
  75. }
  76. throw new Error('Cannot parse expr fragment: ' + rest.slice(0, 60));
  77. }
  78. return parts.join('');
  79. }
  80. function sqlEscape(s) {
  81. if (s == null) return null;
  82. return s
  83. .replace(/\\/g, '\\\\')
  84. .replace(/'/g, "''")
  85. .replace(/\n/g, '\\n')
  86. .replace(/\r/g, '\\r');
  87. }
  88. const regRe = /reg\(\s*"([^"]+)"\s*,\s*"([^"]+)"\s*,\s*"([^"]+)"\s*,\s*([\s\S]*?)\s*,\s*(null|"[^"]*"|LobsterPromptGuardrails\.\w+)\s*,\s*(null|"[^"]*")\s*,\s*(null|"[^"]*")\s*,\s*(?:\+\+o|\d+)\s*\)\s*;/g;
  89. const rows = [];
  90. let m;
  91. while ((m = regRe.exec(src)) !== null) {
  92. const [, key, name, category, contentExpr, systemRoleExpr, modelExpr] = m;
  93. const content = evalConcat(contentExpr);
  94. let systemRole = null;
  95. if (systemRoleExpr.startsWith('LobsterPromptGuardrails.')) {
  96. const gName = systemRoleExpr.replace('LobsterPromptGuardrails.', '');
  97. systemRole = GUARDRAILS[gName] || null;
  98. } else if (systemRoleExpr !== 'null') {
  99. systemRole = unescapeJavaString(systemRoleExpr.slice(1, -1));
  100. }
  101. const modelName = modelExpr === 'null' ? null : unescapeJavaString(modelExpr.slice(1, -1));
  102. rows.push({ key, name, category, content, systemRole, modelName, sort: rows.length + 1 });
  103. }
  104. if (rows.length === 0) {
  105. console.error('No prompts parsed from catalog');
  106. process.exit(1);
  107. }
  108. const lines = [];
  109. lines.push('-- 数字员工级提示词初始化(平台默认,company_id=NULL)');
  110. lines.push('-- 生成自 LobsterEnginePromptCatalog,共 ' + rows.length + ' 条');
  111. lines.push('-- 执行: mysql -u用户 -p 数据库名 < lobster_engine_prompt_init.sql');
  112. lines.push('-- 已存在相同 prompt_key 时跳过(不覆盖已编辑内容)');
  113. lines.push('');
  114. for (const r of rows) {
  115. const sys = r.systemRole != null ? `'${sqlEscape(r.systemRole)}'` : 'NULL';
  116. const model = r.modelName != null ? `'${sqlEscape(r.modelName)}'` : 'NULL';
  117. lines.push(`INSERT IGNORE INTO lobster_system_prompt (`);
  118. lines.push(` prompt_key, prompt_name, prompt_category, prompt_content,`);
  119. lines.push(` model_name, system_role, company_id, industry_type, enabled, sort_order, create_by`);
  120. lines.push(`) VALUES (`);
  121. lines.push(` '${sqlEscape(r.key)}',`);
  122. lines.push(` '${sqlEscape(r.name)}',`);
  123. lines.push(` '${sqlEscape(r.category)}',`);
  124. lines.push(" '" + sqlEscape(r.content) + "',");
  125. lines.push(` ${model},`);
  126. lines.push(` ${sys},`);
  127. lines.push(` NULL, NULL, 1, ${r.sort}, 'system'`);
  128. lines.push(`);`);
  129. lines.push('');
  130. }
  131. fs.writeFileSync(outPath, lines.join('\n'), 'utf8');
  132. console.log('Wrote ' + rows.length + ' prompts -> ' + outPath);