@far-world-labs/verblets 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (300) hide show
  1. package/.cursor/launch.json +30 -0
  2. package/.cursor/settings.json +20 -0
  3. package/.github/workflows/branch-protection.yml +22 -0
  4. package/.github/workflows/ci.yml +120 -0
  5. package/.prettierrc +6 -0
  6. package/.release-it.json +4 -1
  7. package/.vscode/launch.json +31 -0
  8. package/AGENTS.md +220 -0
  9. package/DEVELOPING.md +105 -0
  10. package/README.md +254 -0
  11. package/eslint.config.js +80 -0
  12. package/package.json +29 -17
  13. package/scripts/generate-test/index.js +29 -3
  14. package/scripts/runner/index.js +26 -0
  15. package/scripts/simple-editor/index.js +29 -18
  16. package/scripts/summarize-files/index.js +28 -4
  17. package/src/chains/README.md +30 -0
  18. package/src/chains/anonymize/README.md +21 -0
  19. package/src/chains/anonymize/index.examples.js +75 -0
  20. package/src/chains/anonymize/index.js +121 -0
  21. package/src/chains/anonymize/index.spec.js +78 -0
  22. package/src/chains/bulk-central-tendency/index.examples.js +138 -0
  23. package/src/chains/bulk-central-tendency/index.js +91 -0
  24. package/src/chains/bulk-filter/README.md +21 -0
  25. package/src/chains/bulk-filter/index.examples.js +22 -0
  26. package/src/chains/bulk-filter/index.js +58 -0
  27. package/src/chains/bulk-filter/index.spec.js +38 -0
  28. package/src/chains/bulk-find/README.md +16 -0
  29. package/src/chains/bulk-find/index.examples.js +20 -0
  30. package/src/chains/bulk-find/index.js +30 -0
  31. package/src/chains/bulk-find/index.spec.js +26 -0
  32. package/src/chains/bulk-group/README.md +23 -0
  33. package/src/chains/bulk-group/index.examples.js +18 -0
  34. package/src/chains/bulk-group/index.js +34 -0
  35. package/src/chains/bulk-group/index.spec.js +41 -0
  36. package/src/chains/bulk-map/README.md +43 -0
  37. package/src/chains/bulk-map/index.examples.js +17 -0
  38. package/src/chains/bulk-map/index.js +86 -0
  39. package/src/chains/bulk-map/index.spec.js +44 -0
  40. package/src/chains/bulk-reduce/README.md +12 -0
  41. package/src/chains/bulk-reduce/index.examples.js +15 -0
  42. package/src/chains/bulk-reduce/index.js +13 -0
  43. package/src/chains/bulk-reduce/index.spec.js +25 -0
  44. package/src/chains/bulk-score/README.md +16 -0
  45. package/src/chains/bulk-score/bulk-score-result.json +18 -0
  46. package/src/chains/bulk-score/index.examples.js +22 -0
  47. package/src/chains/bulk-score/index.js +133 -0
  48. package/src/chains/bulk-score/index.spec.js +30 -0
  49. package/src/chains/category-samples/README.md +61 -0
  50. package/src/chains/category-samples/index.examples.js +103 -0
  51. package/src/chains/category-samples/index.js +134 -0
  52. package/src/chains/collect-terms/README.md +12 -0
  53. package/src/chains/collect-terms/index.examples.js +16 -0
  54. package/src/chains/collect-terms/index.js +44 -0
  55. package/src/chains/collect-terms/index.spec.js +25 -0
  56. package/src/chains/date/README.md +12 -0
  57. package/src/chains/date/index.examples.js +47 -0
  58. package/src/chains/date/index.js +74 -0
  59. package/src/chains/date/index.spec.js +62 -0
  60. package/src/chains/disambiguate/README.md +22 -0
  61. package/src/chains/disambiguate/disambiguate-meanings-result.json +16 -0
  62. package/src/chains/disambiguate/index.examples.js +18 -0
  63. package/src/chains/disambiguate/index.js +92 -0
  64. package/src/chains/disambiguate/index.spec.js +25 -0
  65. package/src/chains/dismantle/README.md +67 -0
  66. package/src/chains/dismantle/dismantle.examples.js +27 -0
  67. package/src/chains/dismantle/index.js +6 -17
  68. package/src/chains/dismantle/index.spec.js +1 -2
  69. package/src/chains/expect/README.md +171 -0
  70. package/src/chains/expect/index.examples.js +146 -0
  71. package/src/chains/expect/index.js +173 -0
  72. package/src/chains/expect/index.spec.js +324 -0
  73. package/src/chains/filter-ambiguous/README.md +11 -0
  74. package/src/chains/filter-ambiguous/index.examples.js +20 -0
  75. package/src/chains/filter-ambiguous/index.js +49 -0
  76. package/src/chains/filter-ambiguous/index.spec.js +31 -0
  77. package/src/chains/glossary/README.md +19 -0
  78. package/src/chains/glossary/index.examples.js +386 -0
  79. package/src/chains/glossary/index.js +75 -0
  80. package/src/chains/glossary/index.spec.js +19 -0
  81. package/src/chains/intersections/README.md +152 -0
  82. package/src/chains/intersections/index.examples.js +279 -0
  83. package/src/chains/intersections/index.js +366 -0
  84. package/src/chains/intersections/intersection-result.json +38 -0
  85. package/src/chains/list/index.examples.js +12 -16
  86. package/src/chains/list/index.js +106 -53
  87. package/src/chains/list/index.spec.js +8 -9
  88. package/src/chains/list/list-result.json +16 -0
  89. package/src/chains/llm-logger/README.md +208 -0
  90. package/src/chains/llm-logger/index.js +205 -0
  91. package/src/chains/llm-logger/index.spec.js +330 -0
  92. package/src/chains/questions/index.examples.js +2 -1
  93. package/src/chains/questions/index.js +14 -15
  94. package/src/chains/scan-js/index.js +6 -9
  95. package/src/chains/set-interval/README.md +81 -0
  96. package/src/chains/set-interval/index.examples.js +36 -0
  97. package/src/chains/set-interval/index.js +131 -0
  98. package/src/chains/set-interval/index.spec.js +70 -0
  99. package/src/chains/socratic/README.md +17 -0
  100. package/src/chains/socratic/index.js +64 -0
  101. package/src/chains/socratic/index.spec.js +24 -0
  102. package/src/chains/sort/index.examples.js +3 -7
  103. package/src/chains/sort/index.js +65 -15
  104. package/src/chains/sort/index.spec.js +5 -8
  105. package/src/chains/sort/sort-result.json +16 -0
  106. package/src/chains/summary-map/README.md +9 -1
  107. package/src/chains/summary-map/index.examples.js +9 -2
  108. package/src/chains/summary-map/index.js +43 -25
  109. package/src/chains/summary-map/index.spec.js +78 -3
  110. package/src/chains/test/index.js +9 -13
  111. package/src/chains/test-advice/index.js +4 -5
  112. package/src/chains/themes/README.md +20 -0
  113. package/src/chains/themes/index.examples.js +17 -0
  114. package/src/chains/themes/index.js +28 -0
  115. package/src/chains/themes/index.spec.js +19 -0
  116. package/src/chains/veiled-variants/index.examples.js +18 -0
  117. package/src/chains/veiled-variants/index.js +107 -0
  118. package/src/chains/veiled-variants/index.spec.js +40 -0
  119. package/src/constants/common.js +0 -2
  120. package/src/constants/models.js +172 -0
  121. package/src/index.js +178 -18
  122. package/src/json-schemas/README.md +13 -0
  123. package/src/json-schemas/index.js +8 -14
  124. package/src/json-schemas/schema-dot-org-photograph.json +11 -5
  125. package/src/json-schemas/schema-dot-org-place.json +78 -5
  126. package/src/lib/README.md +26 -0
  127. package/src/lib/bulk-filter/README.md +22 -0
  128. package/src/lib/bulk-filter/index.examples.js +27 -0
  129. package/src/lib/bulk-filter/index.js +63 -0
  130. package/src/lib/bulk-filter/index.spec.js +38 -0
  131. package/src/lib/bulk-find/README.md +18 -0
  132. package/src/lib/bulk-find/index.examples.js +19 -0
  133. package/src/lib/bulk-find/index.js +30 -0
  134. package/src/lib/bulk-find/index.spec.js +41 -0
  135. package/src/lib/chatgpt/index.js +63 -43
  136. package/src/lib/combinations/index.js +30 -0
  137. package/src/lib/combinations/index.spec.js +23 -0
  138. package/src/lib/functional/index.js +28 -0
  139. package/src/lib/logger-service/index.js +32 -0
  140. package/src/lib/parse-js-parts/index.js +9 -21
  141. package/src/lib/parse-llm-list/README.md +39 -0
  142. package/src/lib/parse-llm-list/index.js +54 -0
  143. package/src/lib/parse-llm-list/index.spec.js +59 -0
  144. package/src/lib/path-aliases/index.js +1 -3
  145. package/src/lib/path-aliases/index.spec.js +2 -8
  146. package/src/lib/pave/index.js +4 -4
  147. package/src/lib/pave/index.spec.js +6 -3
  148. package/src/lib/prompt-cache/index.js +14 -10
  149. package/src/lib/retry/index.js +11 -8
  150. package/src/lib/ring-buffer/README.md +460 -0
  151. package/src/lib/ring-buffer/index.js +1074 -0
  152. package/src/lib/search-best-first/city-walk.spec.js +37 -0
  153. package/src/lib/search-best-first/index.js +42 -11
  154. package/src/lib/search-best-first/index.spec.js +35 -0
  155. package/src/lib/search-js-files/index.js +44 -47
  156. package/src/lib/search-js-files/scan-file.js +10 -21
  157. package/src/lib/shorten-text/index.js +2 -7
  158. package/src/lib/shorten-text/index.spec.js +3 -3
  159. package/src/lib/strip-response/index.js +2 -7
  160. package/src/lib/template-replace/index.js +23 -0
  161. package/src/lib/template-replace/index.spec.js +60 -0
  162. package/src/lib/to-date/index.js +11 -0
  163. package/src/lib/to-number/index.js +1 -1
  164. package/src/lib/transcribe/index.js +26 -9
  165. package/src/prompts/README.md +3 -1
  166. package/src/prompts/as-object-with-schema.js +3 -8
  167. package/src/prompts/as-schema-org-text.js +10 -2
  168. package/src/prompts/code-features.js +1 -5
  169. package/src/prompts/constants.js +27 -27
  170. package/src/prompts/generate-collection.js +1 -1
  171. package/src/prompts/intent.js +16 -22
  172. package/src/prompts/select-from-threshold.js +1 -2
  173. package/src/prompts/sort.js +4 -8
  174. package/src/prompts/style.js +4 -7
  175. package/src/prompts/wrap-list.js +1 -4
  176. package/src/services/llm-model/global-overrides.spec.js +432 -0
  177. package/src/services/llm-model/index.js +234 -40
  178. package/src/services/llm-model/model.js +2 -2
  179. package/src/services/llm-model/negotiate.spec.js +447 -0
  180. package/src/services/redis/index.js +70 -7
  181. package/src/test/setup.js +20 -0
  182. package/src/verblets/README.md +26 -0
  183. package/src/verblets/auto/index.examples.js +12 -9
  184. package/src/verblets/auto/index.js +10 -10
  185. package/src/verblets/auto/index.spec.js +4 -6
  186. package/src/verblets/bool/README.md +36 -0
  187. package/src/verblets/bool/index.examples.js +53 -1
  188. package/src/verblets/bool/index.js +6 -9
  189. package/src/verblets/bool/index.spec.js +1 -3
  190. package/src/verblets/central-tendency/README.md +166 -0
  191. package/src/verblets/central-tendency/central-tendency-result.json +24 -0
  192. package/src/verblets/central-tendency/index.examples.js +196 -0
  193. package/src/verblets/central-tendency/index.js +171 -0
  194. package/src/verblets/central-tendency/index.spec.js +148 -0
  195. package/src/verblets/enum/index.examples.js +1 -4
  196. package/src/verblets/enum/index.js +7 -4
  197. package/src/verblets/expect/README.md +64 -0
  198. package/src/verblets/expect/index.examples.js +109 -0
  199. package/src/verblets/expect/index.js +75 -0
  200. package/src/verblets/expect/index.spec.js +127 -0
  201. package/src/verblets/intent/index.examples.js +95 -7
  202. package/src/verblets/intent/index.js +56 -68
  203. package/src/verblets/intersection/README.md +16 -0
  204. package/src/verblets/intersection/index.examples.js +89 -0
  205. package/src/verblets/intersection/index.js +84 -0
  206. package/src/verblets/intersection/index.spec.js +60 -0
  207. package/src/verblets/intersection/intersection-result.json +16 -0
  208. package/src/verblets/list-expand/README.md +10 -0
  209. package/src/verblets/list-expand/index.examples.js +14 -0
  210. package/src/verblets/list-expand/index.js +104 -0
  211. package/src/verblets/list-expand/index.spec.js +18 -0
  212. package/src/verblets/list-expand/list-expand-result.json +16 -0
  213. package/src/verblets/list-filter/README.md +22 -0
  214. package/src/verblets/list-filter/index.examples.js +26 -0
  215. package/src/verblets/list-filter/index.js +18 -0
  216. package/src/verblets/list-filter/index.spec.js +19 -0
  217. package/src/verblets/list-find/README.md +11 -0
  218. package/src/verblets/list-find/index.examples.js +15 -0
  219. package/src/verblets/list-find/index.js +17 -0
  220. package/src/verblets/list-find/index.spec.js +19 -0
  221. package/src/verblets/list-group/README.md +16 -0
  222. package/src/verblets/list-group/index.examples.js +16 -0
  223. package/src/verblets/list-group/index.js +112 -0
  224. package/src/verblets/list-group/index.spec.js +35 -0
  225. package/src/verblets/list-group/list-group-result.json +16 -0
  226. package/src/verblets/list-map/README.md +11 -0
  227. package/src/verblets/list-map/index.examples.js +15 -0
  228. package/src/verblets/list-map/index.js +26 -0
  229. package/src/verblets/list-map/index.spec.js +17 -0
  230. package/src/verblets/list-reduce/README.md +10 -0
  231. package/src/verblets/list-reduce/index.examples.js +14 -0
  232. package/src/verblets/list-reduce/index.js +21 -0
  233. package/src/verblets/list-reduce/index.spec.js +27 -0
  234. package/src/verblets/list-reduce/index.spec.jsx +27 -0
  235. package/src/verblets/name/README.md +15 -0
  236. package/src/verblets/name/index.examples.js +28 -0
  237. package/src/verblets/name/index.js +19 -0
  238. package/src/verblets/name/index.spec.js +33 -0
  239. package/src/verblets/name-similar-to/README.md +26 -0
  240. package/src/verblets/name-similar-to/index.examples.js +18 -0
  241. package/src/verblets/name-similar-to/index.js +20 -0
  242. package/src/verblets/name-similar-to/index.spec.js +13 -0
  243. package/src/verblets/number/index.examples.js +173 -7
  244. package/src/verblets/number/index.js +5 -2
  245. package/src/verblets/number/index.spec.js +1 -3
  246. package/src/verblets/number-with-units/index.examples.js +5 -1
  247. package/src/verblets/number-with-units/index.js +74 -9
  248. package/src/verblets/number-with-units/number-with-units-result.json +23 -0
  249. package/src/verblets/schema-org/index.examples.js +2 -7
  250. package/src/verblets/schema-org/index.js +32 -3
  251. package/src/verblets/sentiment/README.md +10 -0
  252. package/src/verblets/sentiment/index.examples.js +20 -0
  253. package/src/verblets/sentiment/index.js +9 -0
  254. package/src/verblets/sentiment/index.spec.js +20 -0
  255. package/src/verblets/to-object/index.js +10 -15
  256. package/src/verblets/to-object/index.spec.js +1 -4
  257. package/.eslintrc.json +0 -42
  258. package/docs/README.md +0 -41
  259. package/docs/babel.config.js +0 -3
  260. package/docs/blog/2019-05-28-first-blog-post.md +0 -12
  261. package/docs/blog/2019-05-29-long-blog-post.md +0 -44
  262. package/docs/blog/2021-08-01-mdx-blog-post.mdx +0 -20
  263. package/docs/blog/2021-08-26-welcome/docusaurus-plushie-banner.jpeg +0 -0
  264. package/docs/blog/2021-08-26-welcome/index.md +0 -25
  265. package/docs/blog/authors.yml +0 -17
  266. package/docs/docs/api/bool.md +0 -74
  267. package/docs/docs/api/search.md +0 -51
  268. package/docs/docs/intro.md +0 -47
  269. package/docs/docs/tutorial-basics/_category_.json +0 -8
  270. package/docs/docs/tutorial-basics/congratulations.md +0 -23
  271. package/docs/docs/tutorial-basics/create-a-blog-post.md +0 -34
  272. package/docs/docs/tutorial-basics/create-a-document.md +0 -57
  273. package/docs/docs/tutorial-basics/create-a-page.md +0 -43
  274. package/docs/docs/tutorial-basics/deploy-your-site.md +0 -31
  275. package/docs/docs/tutorial-basics/markdown-features.mdx +0 -152
  276. package/docs/docs/tutorial-extras/_category_.json +0 -7
  277. package/docs/docs/tutorial-extras/img/docsVersionDropdown.png +0 -0
  278. package/docs/docs/tutorial-extras/img/localeDropdown.png +0 -0
  279. package/docs/docs/tutorial-extras/manage-docs-versions.md +0 -55
  280. package/docs/docs/tutorial-extras/translate-your-site.md +0 -88
  281. package/docs/docusaurus.config.js +0 -120
  282. package/docs/package.json +0 -44
  283. package/docs/sidebars.js +0 -31
  284. package/docs/src/components/HomepageFeatures/index.js +0 -61
  285. package/docs/src/components/HomepageFeatures/styles.module.css +0 -11
  286. package/docs/src/css/custom.css +0 -30
  287. package/docs/src/pages/index.js +0 -43
  288. package/docs/src/pages/index.module.css +0 -23
  289. package/docs/src/pages/markdown-page.md +0 -7
  290. package/docs/static/.nojekyll +0 -0
  291. package/docs/static/img/docusaurus-social-card.jpg +0 -0
  292. package/docs/static/img/docusaurus.png +0 -0
  293. package/docs/static/img/favicon.ico +0 -0
  294. package/docs/static/img/logo.svg +0 -1
  295. package/docs/static/img/undraw_docusaurus_mountain.svg +0 -171
  296. package/docs/static/img/undraw_docusaurus_react.svg +0 -170
  297. package/docs/static/img/undraw_docusaurus_tree.svg +0 -40
  298. package/src/constants/openai.js +0 -65
  299. /package/{.vite.config.examples.js → .vitest.config.examples.js} +0 -0
  300. /package/{.vite.config.js → .vitest.config.js} +0 -0
@@ -1,15 +1,10 @@
1
- /* eslint-disable no-await-in-loop */
2
-
3
1
  import chatGPT from '../../lib/chatgpt/index.js';
4
2
  import pave from '../../lib/pave/index.js';
5
3
  import shortenText from '../../lib/shorten-text/index.js';
6
- import {
7
- summarize as basicSummarize,
8
- tokenBudget,
9
- } from '../../prompts/index.js';
4
+ import { summarize as basicSummarize, tokenBudget } from '../../prompts/index.js';
10
5
  import modelService from '../../services/llm-model/index.js';
11
6
 
12
- const summarize = ({ budget, type, value, fixes = [] }) => {
7
+ const summarize = ({ budget, type, value, fixes = [], modelOptions, privacy }) => {
13
8
  if (budget) {
14
9
  fixes.push(tokenBudget(budget));
15
10
  }
@@ -22,9 +17,19 @@ const summarize = ({ budget, type, value, fixes = [] }) => {
22
17
  fixes.push('Remove the function header if it exists.');
23
18
  }
24
19
 
20
+ if (privacy?.whitelist) {
21
+ fixes.push(`Only share information matching: ${privacy.whitelist}.`);
22
+ }
23
+
24
+ if (privacy?.blacklist) {
25
+ fixes.push(`Do not share information matching: ${privacy.blacklist}.`);
26
+ }
27
+
25
28
  const fixesAsBullets = fixes.map((fix) => ` - ${fix}`);
26
29
 
27
- return chatGPT(basicSummarize(value, `${fixesAsBullets.join('\n')}`));
30
+ return chatGPT(basicSummarize(value, `${fixesAsBullets.join('\n')}`), {
31
+ modelOptions,
32
+ });
28
33
  };
29
34
 
30
35
  /**
@@ -34,7 +39,8 @@ const summarize = ({ budget, type, value, fixes = [] }) => {
34
39
  export default class SummaryMap extends Map {
35
40
  constructor({
36
41
  maxTokensPerValue,
37
- model = modelService.getBestAvailableModel(),
42
+ model = modelService.getBestPublicModel(),
43
+ modelOptions = { modelName: model.name },
38
44
  promptText,
39
45
  targetTokens,
40
46
  // used with promptText, when targetTokens isn't supplied
@@ -45,6 +51,7 @@ export default class SummaryMap extends Map {
45
51
  this.data = new Map();
46
52
  this.isCacheValid = false;
47
53
  this.maxTokensPerValue = maxTokensPerValue ?? model.maxTokens;
54
+ this.modelOptions = { modelName: model.name, ...modelOptions };
48
55
 
49
56
  if (targetTokens) {
50
57
  this.targetTokens = targetTokens;
@@ -52,13 +59,9 @@ export default class SummaryMap extends Map {
52
59
  this.promptTokens = model.toTokens(promptText).length;
53
60
  const maxModelTokens = model.maxTokens;
54
61
  const remainingTokens = maxModelTokens - this.promptTokens;
55
- this.targetTokens = Math.floor(
56
- remainingTokens - remainingTokens * targetTokensTotalRatio
57
- );
62
+ this.targetTokens = Math.floor(remainingTokens - remainingTokens * targetTokensTotalRatio);
58
63
  } else {
59
- throw new Error(
60
- 'Either "promptText" and "model" or "targetTokens" must be provided.'
61
- );
64
+ throw new Error('Either "promptText" and "model" or "targetTokens" must be provided.');
62
65
  }
63
66
  }
64
67
 
@@ -68,16 +71,12 @@ export default class SummaryMap extends Map {
68
71
  .reduce((sum, valueObject) => {
69
72
  return sum + (valueObject.weight ?? 1) * valueObject.value.length;
70
73
  }, 0);
71
- const sortedEntries = [...this.data.entries()].sort(
72
- (a, b) => a[1].weight - b[1].weight
73
- );
74
+ const sortedEntries = [...this.data.entries()].sort((a, b) => a[1].weight - b[1].weight);
74
75
 
75
76
  const budgets = [];
76
77
  for (const [entryKey, valueObject] of sortedEntries) {
77
78
  const sizeWeight = valueObject.value.length * (valueObject.weight ?? 1);
78
- const budget = Math.floor(
79
- (sizeWeight / totalSizeWeight) * this.targetTokens
80
- );
79
+ const budget = Math.floor((sizeWeight / totalSizeWeight) * this.targetTokens);
81
80
 
82
81
  if (valueObject.weight) {
83
82
  budgets.push({ key: entryKey, budget });
@@ -95,16 +94,38 @@ export default class SummaryMap extends Map {
95
94
  for (const { key, budget } of budgets) {
96
95
  const valueObject = this.data.get(key);
97
96
 
97
+ const entryModelOptions = {
98
+ ...this.modelOptions,
99
+ ...valueObject.modelOptions,
100
+ };
101
+
102
+ if (valueObject.privacy?.whitelist || valueObject.privacy?.blacklist) {
103
+ entryModelOptions.modelName = 'privacy';
104
+ }
105
+
98
106
  const value = shortenText(valueObject.value, {
99
107
  targetTokenCount: this.maxTokensPerValue,
108
+ model: modelService.getModel(entryModelOptions.modelName),
100
109
  });
101
110
 
102
111
  // omit weight to skip summarization
103
112
  let summarizedValue = value;
104
113
  if (budget) {
114
+ const summarizeModelOptions = {
115
+ ...this.modelOptions,
116
+ ...valueObject.modelOptions,
117
+ };
118
+
119
+ if (valueObject.privacy?.whitelist || valueObject.privacy?.blacklist) {
120
+ summarizeModelOptions.modelName = 'privacy';
121
+ }
122
+
123
+ // eslint-disable-next-line no-await-in-loop
105
124
  summarizedValue = await summarize({
106
125
  budget,
107
126
  fixes: valueObject.fixes,
127
+ modelOptions: summarizeModelOptions,
128
+ privacy: valueObject.privacy,
108
129
  type: valueObject.type,
109
130
  value,
110
131
  });
@@ -189,10 +210,7 @@ export default class SummaryMap extends Map {
189
210
  }
190
211
 
191
212
  pavedSummaryResultStale() {
192
- return Array.from(this.entriesStale()).reduce(
193
- (acc, [k, v]) => pave(acc, k, v),
194
- {}
195
- );
213
+ return Array.from(this.entriesStale()).reduce((acc, [k, v]) => pave(acc, k, v), {});
196
214
  }
197
215
 
198
216
  pavedSummaryResult() {
@@ -1,7 +1,51 @@
1
1
  import { describe, expect, it, vi } from 'vitest';
2
-
3
- import SummaryMap from './index.js';
4
2
  import pave from '../../lib/pave/index.js';
3
+ import SummaryMap from './index.js';
4
+ import chatGPT from '../../lib/chatgpt/index.js';
5
+
6
+ vi.mock('../../services/llm-model/index.js', () => ({
7
+ default: {
8
+ negotiateModel: vi.fn().mockReturnValue('fastGood'),
9
+ getBestPublicModel: vi.fn().mockReturnValue({
10
+ name: 'fastGood',
11
+ tokenizer: (text) => text.split(' '),
12
+ maxContextWindow: 128000,
13
+ maxOutputTokens: 16384,
14
+ toTokens(text) {
15
+ return this.tokenizer(text);
16
+ },
17
+ budgetTokens(text, { completionMax = Infinity } = {}) {
18
+ const prompt = this.toTokens(text).length;
19
+ const total = this.maxContextWindow;
20
+ const completion = Math.min(Math.min(total - prompt, this.maxOutputTokens), completionMax);
21
+ return {
22
+ completion,
23
+ prompt,
24
+ total,
25
+ };
26
+ },
27
+ }),
28
+ getModel: vi.fn().mockReturnValue({
29
+ name: 'fastGood',
30
+ tokenizer: (text) => text.split(' '),
31
+ maxContextWindow: 128000,
32
+ maxOutputTokens: 16384,
33
+ toTokens(text) {
34
+ return this.tokenizer(text);
35
+ },
36
+ budgetTokens(text, { completionMax = Infinity } = {}) {
37
+ const prompt = this.toTokens(text).length;
38
+ const total = this.maxContextWindow;
39
+ const completion = Math.min(Math.min(total - prompt, this.maxOutputTokens), completionMax);
40
+ return {
41
+ completion,
42
+ prompt,
43
+ total,
44
+ };
45
+ },
46
+ }),
47
+ },
48
+ }));
5
49
 
6
50
  vi.mock('../../lib/chatgpt/index.js', () => ({
7
51
  default: vi.fn().mockImplementation((text) => {
@@ -15,7 +59,8 @@ vi.mock('../../lib/chatgpt/index.js', () => ({
15
59
  }),
16
60
  }));
17
61
 
18
- const legalText = `Pursuant to the adjudication of a force majeure clause within the context of contractual`;
62
+ const legalText =
63
+ 'Pursuant to the adjudication of a force majeure clause within the context of contractual';
19
64
 
20
65
  const codeText = `import numpy as np
21
66
 
@@ -37,13 +82,36 @@ const examples = [
37
82
  { key: 'example.code', resultLength: 25, budget: [20, 40] },
38
83
  ],
39
84
  },
85
+ {
86
+ name: 'Model options and privacy',
87
+ inputs: {
88
+ targetTokens: 50,
89
+ modelOptions: { modelName: 'fastGood' },
90
+ keys: [
91
+ {
92
+ key: 'example.text',
93
+ value: legalText,
94
+ weight: 1,
95
+ type: 'text',
96
+ privacy: { blacklist: 'names' },
97
+ },
98
+ { key: 'example.code', value: codeText, weight: 0.5, type: 'code' },
99
+ ],
100
+ },
101
+ wants: [
102
+ { key: 'example.text', resultLength: 50 },
103
+ { key: 'example.code', resultLength: 25 },
104
+ ],
105
+ },
40
106
  ];
41
107
 
42
108
  describe('Summary map', () => {
43
109
  examples.forEach((example) => {
44
110
  it(example.name, async () => {
111
+ vi.clearAllMocks();
45
112
  const map = new SummaryMap({
46
113
  targetTokens: example.inputs.targetTokens,
114
+ ...(example.inputs.modelOptions && { modelOptions: example.inputs.modelOptions }),
47
115
  });
48
116
 
49
117
  for (const input of example.inputs.keys) {
@@ -73,6 +141,13 @@ describe('Summary map', () => {
73
141
  expect(found.budget).lt(want.budget[1]);
74
142
  }
75
143
  }
144
+
145
+ if (example.name === 'Model options and privacy') {
146
+ const callWithPrivacy = chatGPT.mock.calls.find(
147
+ (c) => c[1]?.modelOptions?.modelName === 'privacy'
148
+ );
149
+ expect(callWithPrivacy).toBeTruthy();
150
+ }
76
151
  });
77
152
  });
78
153
  });
@@ -3,10 +3,7 @@ import path from 'node:path';
3
3
 
4
4
  import { errorRunningTests } from '../../constants/messages.js';
5
5
  import chatGPT from '../../lib/chatgpt/index.js';
6
- import {
7
- constants as promptConstants,
8
- wrapVariable,
9
- } from '../../prompts/index.js';
6
+ import { constants as promptConstants, wrapVariable } from '../../prompts/index.js';
10
7
  import modelService from '../../services/llm-model/index.js';
11
8
  import toObject from '../../verblets/to-object/index.js';
12
9
 
@@ -19,11 +16,9 @@ const {
19
16
  useLineNumber,
20
17
  } = promptConstants;
21
18
 
22
- const contentIsChecksExamined =
23
- 'These items were checked in an examination of the text:';
19
+ const contentIsChecksExamined = 'These items were checked in an examination of the text:';
24
20
  const contentIsExamined = 'The text examined:';
25
- const findCodeImprovements =
26
- 'Find specific improvements in the following code, not nitpicks.';
21
+ const findCodeImprovements = 'Find specific improvements in the following code, not nitpicks.';
27
22
  const gatherAsTestJSON =
28
23
  'Gather these discovered issues into a JSON format my tests module can consume.';
29
24
 
@@ -67,11 +62,8 @@ ${contentIsExample} ${wrapVariable(testExamplesJSON, { tag: 'example' })}
67
62
  ${onlyJSONArray}
68
63
  `;
69
64
 
70
- export default async (
71
- filePath,
72
- instructions = findCodeImprovements,
73
- model = modelService.getBestAvailableModel()
74
- ) => {
65
+ export default async (filePath, instructions = findCodeImprovements, config = {}) => {
66
+ const { model = modelService.getBestPublicModel(), llm, ...options } = config;
75
67
  const enableRegex = new RegExp(process.env.ENABLE_AI_TESTS ?? '^$');
76
68
  if (!enableRegex.test(filePath)) {
77
69
  return [];
@@ -87,7 +79,9 @@ export default async (
87
79
  const checksResult = await chatGPT(checksPromptCreated, {
88
80
  modelOptions: {
89
81
  maxTokens: checksBudget.completion,
82
+ ...llm,
90
83
  },
84
+ ...options,
91
85
  });
92
86
 
93
87
  const testsPromptCreated = testsPrompt(text, instructions, checksResult);
@@ -97,7 +91,9 @@ export default async (
97
91
  await chatGPT(testsPromptCreated, {
98
92
  modelOptions: {
99
93
  maxTokens: testsBudget.completion,
94
+ ...llm,
100
95
  },
96
+ ...options,
101
97
  })
102
98
  );
103
99
 
@@ -1,18 +1,17 @@
1
1
  import test from '../test/index.js';
2
2
 
3
- const boundaryIssues =
4
- 'Run the code with 5 boundary value test cases and report any that fail';
3
+ const boundaryIssues = 'Run the code with 5 boundary value test cases and report any that fail';
5
4
 
6
5
  const successIssues =
7
6
  'Identify 5 passing scenarios and significant boundary conditions in this code. Provide minimal input examples for each scenario to demonstrate correctness.';
8
7
 
9
- const failureIssues = `Identify 5 failing scenarios and significant boundary conditions in this code. Provide minimal input examples for each scenario to demonstrate the failure. Assume DBC, and don't complain when types are specified in jsDoc.`;
8
+ const failureIssues =
9
+ "Identify 5 failing scenarios and significant boundary conditions in this code. Provide minimal input examples for each scenario to demonstrate the failure. Assume DBC, and don't complain when types are specified in jsDoc.";
10
10
 
11
11
  const defectIssues =
12
12
  'Identify 5 defects in this code. Provide minimal input examples to demonstrate each defect.';
13
13
 
14
- const bestPracticesIssues =
15
- 'Suggest 5 best practices improvements for this code.';
14
+ const bestPracticesIssues = 'Suggest 5 best practices improvements for this code.';
16
15
 
17
16
  const cleanCodeIssues = 'Suggest 5 "clean code" improvements for this code.';
18
17
 
@@ -0,0 +1,20 @@
1
+ # themes
2
+
3
+ Reveal a text's key themes and map them back to the sentences where they appear. The chain first scans fragments in batches to collect possible themes, then runs a consolidation step to normalize and deduplicate them. Optionally it returns a per-sentence map showing which themes surface in each line.
4
+
5
+ ```javascript
6
+ import themes from './index.js';
7
+
8
+ const news = `The storm toppled trees and damaged homes. Volunteers quickly arrived with food and tools. Their kindness inspired hope throughout the town.`;
9
+
10
+ const result = await themes(news, { sentenceMap: true });
11
+ /* {
12
+ * themes: ['disaster recovery', 'community', 'hope'],
13
+ * sentenceThemes: [
14
+ * [0, ['disaster recovery']],
15
+ * [44, ['community']],
16
+ * [114, ['hope']]
17
+ * ]
18
+ */
19
+ ```
20
+
@@ -0,0 +1,17 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import themes from './index.js';
3
+ import { longTestTimeout } from '../../constants/common.js';
4
+
5
+ describe('themes chain', () => {
6
+ it(
7
+ 'extracts key themes',
8
+ async () => {
9
+ const text = `Coffee shops are opening all over town. People love the
10
+ new flavors but complain about long lines. Local farmers provide beans while
11
+ young entrepreneurs drive innovation.`;
12
+ const result = await themes(text, { topN: 2 });
13
+ expect(Array.isArray(result)).toBe(true);
14
+ },
15
+ longTestTimeout
16
+ );
17
+ });
@@ -0,0 +1,28 @@
1
+ import bulkReduce from '../bulk-reduce/index.js';
2
+ import shuffle from 'lodash/shuffle.js';
3
+
4
+ const splitText = (text) =>
5
+ text
6
+ .split(/\n{2,}/)
7
+ .map((p) => p.trim())
8
+ .filter(Boolean);
9
+
10
+ export default async function themes(text, config = {}) {
11
+ const { chunkSize = 5, topN, llm, ...options } = config;
12
+ const pieces = splitText(text);
13
+ const reducePrompt =
14
+ 'Update the accumulator with short themes from this text. Avoid duplicates. Return a comma-separated list of themes.';
15
+ const firstPass = await bulkReduce(shuffle(pieces), reducePrompt, { chunkSize, llm, ...options });
16
+ const rawThemes = firstPass
17
+ .split(',')
18
+ .map((t) => t.trim())
19
+ .filter(Boolean);
20
+
21
+ const limitText = topN ? `Limit to the top ${topN} themes.` : 'Return all meaningful themes.';
22
+ const refinePrompt = `Refine the accumulator by merging similar themes. ${limitText} Return a comma-separated list.`;
23
+ const final = await bulkReduce(rawThemes, refinePrompt, { chunkSize, llm, ...options });
24
+ return final
25
+ .split(',')
26
+ .map((t) => t.trim())
27
+ .filter(Boolean);
28
+ }
@@ -0,0 +1,19 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import themes from './index.js';
3
+ import bulkReduce from '../bulk-reduce/index.js';
4
+
5
+ vi.mock('../bulk-reduce/index.js');
6
+
7
+ beforeEach(() => {
8
+ vi.clearAllMocks();
9
+ });
10
+
11
+ describe('themes chain', () => {
12
+ it('reduces in two passes', async () => {
13
+ bulkReduce.mockResolvedValueOnce('a, b, c').mockResolvedValueOnce('a, c');
14
+ const text = 'x\n\ny';
15
+ const result = await themes(text, { chunkSize: 1, topN: 2 });
16
+ expect(result).toStrictEqual(['a', 'c']);
17
+ expect(bulkReduce).toHaveBeenCalledTimes(2);
18
+ });
19
+ });
@@ -0,0 +1,18 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import veiledVariants from './index.js';
3
+ import { longTestTimeout } from '../../constants/common.js';
4
+
5
+ describe('veiledVariants example', () => {
6
+ it(
7
+ 'obscures a sensitive query',
8
+ async () => {
9
+ const result = await veiledVariants({
10
+ prompt:
11
+ 'If pigeons are government spies, how do I ask for counter-surveillance tips without sounding paranoid?',
12
+ });
13
+ expect(Array.isArray(result)).toBe(true);
14
+ expect(result.length, `Saw: "${result.join('", "')}"`).toBe(15);
15
+ },
16
+ longTestTimeout
17
+ );
18
+ });
@@ -0,0 +1,107 @@
1
+ import { run } from '../../lib/chatgpt/index.js';
2
+ import { constants as promptConstants, wrapVariable } from '../../prompts/index.js';
3
+
4
+ const { onlyJSONStringArray } = promptConstants;
5
+
6
+ export const scientificFramingPrompt = (prompt) => `${onlyJSONStringArray}
7
+
8
+ Recast the intent as if asked by a scientific researcher. Generate exactly 5 masked alternatives.
9
+
10
+ Apply these requirements:
11
+ - Replace casual terms with academic phrasing
12
+ - Invoke terminology from biology, epidemiology, diagnostics, or public health
13
+ - Never use slang, simplifications, or direct synonyms of the original prompt
14
+ - Frame each as a legitimate research query
15
+ - You MUST generate exactly 5 alternatives. No more, no less.
16
+ - Output must be a JSON array of exactly 5 strings
17
+
18
+ ${wrapVariable(prompt, { tag: 'intent' })}
19
+
20
+ ${onlyJSONStringArray}`;
21
+
22
+ export const causalFramePrompt = (prompt) => `${onlyJSONStringArray}
23
+
24
+ Generate queries that explore causes, co-conditions, or plausible consequences of the prompt topic. Generate exactly 5 masked alternatives.
25
+
26
+ Apply these requirements:
27
+ - Focus on surrounding or adjacent issues rather than the central sensitive term
28
+ - Frame each as a legitimate research query
29
+ - Explore what leads to, accompanies, or results from the topic
30
+ - You MUST generate exactly 5 alternatives. No more, no less.
31
+ - Output must be a JSON array of exactly 5 strings
32
+
33
+ ${wrapVariable(prompt, { tag: 'intent' })}
34
+
35
+ ${onlyJSONStringArray}`;
36
+
37
+ export const softCoverPrompt = (prompt) => `${onlyJSONStringArray}
38
+
39
+ Reframe the prompt as general wellness or diagnostic concerns. Generate exactly 5 masked alternatives.
40
+
41
+ Apply these requirements:
42
+ - Avoid direct synonyms or sensitive key terms
43
+ - Use a clinical and approachable tone that is safe for open searches
44
+ - Frame as health, wellness, or general diagnostic queries
45
+ - You MUST generate exactly 5 alternatives. No more, no less.
46
+ - Output must be a JSON array of exactly 5 strings
47
+
48
+ ${wrapVariable(prompt, { tag: 'intent' })}
49
+
50
+ ${onlyJSONStringArray}`;
51
+
52
+ const veiledVariants = async ({ prompt, modelName = 'privacy' }) => {
53
+ const prompts = [
54
+ scientificFramingPrompt(prompt),
55
+ causalFramePrompt(prompt),
56
+ softCoverPrompt(prompt),
57
+ ];
58
+ const options = { modelOptions: { modelName } };
59
+ const results = await Promise.all(prompts.map((p) => run(p, options)));
60
+ return results
61
+ .map((r) => {
62
+ try {
63
+ // First try to extract JSON array from response
64
+ const jsonMatch = r.match(/\[[\s\S]*?\]/);
65
+ if (jsonMatch) {
66
+ const parsed = JSON.parse(jsonMatch[0]);
67
+ if (Array.isArray(parsed) && parsed.length > 0) {
68
+ return parsed;
69
+ }
70
+ }
71
+
72
+ // If no valid JSON array found, try to parse the entire response
73
+ const parsed = JSON.parse(r);
74
+ if (Array.isArray(parsed)) {
75
+ return parsed;
76
+ }
77
+
78
+ // If response is not an array, wrap it in an array
79
+ return [parsed];
80
+ } catch (error) {
81
+ // If JSON parsing fails completely, try to extract meaningful content
82
+ const trimmed = r.trim();
83
+
84
+ // If it's a long prose response, try to extract sentences or phrases
85
+ if (trimmed.length > 200) {
86
+ // Split by sentences and take meaningful ones
87
+ const sentences = trimmed.split(/[.!?]+/).filter((s) => s.trim().length > 20);
88
+ if (sentences.length >= 3) {
89
+ return sentences.slice(0, 5).map((s) => s.trim());
90
+ }
91
+ }
92
+
93
+ // If it contains quoted strings, extract them
94
+ const quotes = trimmed.match(/"([^"]+)"/g);
95
+ if (quotes && quotes.length > 0) {
96
+ return quotes.map((q) => q.replace(/"/g, ''));
97
+ }
98
+
99
+ // Fallback: return the raw response as a single item
100
+ console.warn('Failed to parse JSON response, using raw text:', error.message);
101
+ return [trimmed];
102
+ }
103
+ })
104
+ .flat();
105
+ };
106
+
107
+ export default veiledVariants;
@@ -0,0 +1,40 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import veiledVariants from './index.js';
3
+
4
+ let call = 0;
5
+
6
+ const runMock = vi.fn().mockImplementation(() => {
7
+ call += 1;
8
+ if (call === 1) {
9
+ return '["s1","s2","s3","s4","s5"]';
10
+ }
11
+ if (call === 2) {
12
+ return '["c1","c2","c3","c4","c5"]';
13
+ }
14
+ return '["w1","w2","w3","w4","w5"]';
15
+ });
16
+
17
+ vi.mock('../../lib/chatgpt/index.js', () => ({
18
+ run: (...args) => runMock(...args),
19
+ }));
20
+
21
+ describe('veiledVariants', () => {
22
+ it('returns 15 masked queries', async () => {
23
+ const result = await veiledVariants({ prompt: 'secret' });
24
+ expect(Array.isArray(result)).toBe(true);
25
+ expect(result.length).toBe(15);
26
+ expect(runMock).toHaveBeenCalledTimes(3);
27
+ runMock.mock.calls.forEach((callArgs) => {
28
+ expect(callArgs[1]).toStrictEqual({ modelOptions: { modelName: 'privacy' } });
29
+ });
30
+ });
31
+
32
+ it('allows overriding model name', async () => {
33
+ runMock.mockClear();
34
+ call = 0;
35
+ await veiledVariants({ prompt: 'secret', modelName: 'fastGood' });
36
+ runMock.mock.calls.forEach((callArgs) => {
37
+ expect(callArgs[1]).toStrictEqual({ modelOptions: { modelName: 'fastGood' } });
38
+ });
39
+ });
40
+ });
@@ -1,5 +1,3 @@
1
- /* eslint-disable import/prefer-default-export */
2
-
3
1
  export const longTestTimeout = 120000;
4
2
 
5
3
  export const maxRetries = 3;