@far-world-labs/verblets 0.1.1 → 0.1.2

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 +117 -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 +671 -0
  11. package/eslint.config.js +80 -0
  12. package/package.json +28 -16
  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 +3 -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 +21 -41
  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 +4 -4
  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 +11 -16
  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 +84 -1
  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
@@ -0,0 +1,43 @@
1
+ # bulk-map
2
+
3
+ Chunk large lists and map each chunk with `listMap`. Failed chunks can be retried.
4
+
5
+ ## Usage
6
+
7
+ ```javascript
8
+ import { bulkMap } from '../../index.js';
9
+
10
+ const films = [
11
+ 'sci-fi epic',
12
+ 'romantic comedy',
13
+ 'time-travel thriller',
14
+ // ...more titles
15
+ ];
16
+ const results = await bulkMap(films, 'Describe each as a Shakespearean play', { chunkSize: 5 });
17
+ // results[0] === 'A saga among the stars'
18
+ // results[1] === 'Where hearts and humor entwine'
19
+ ```
20
+
21
+ ## API
22
+
23
+ ### `bulkMap(list, instructions, [chunkSize])`
24
+
25
+ Break `list` into batches and map each batch using `listMap`.
26
+
27
+ - `list` (`string[]`): fragments to process.
28
+ - `instructions` (`string`): mapping instructions.
29
+ - `chunkSize` (`number`, default `10`): number of items per batch.
30
+
31
+ Returns `Promise<(string|undefined)[]>` where undefined entries represent failed items.
32
+
33
+ ### `bulkMapRetry(list, instructions, [options])`
34
+
35
+ Retry undefined entries from `bulkMap` until `maxAttempts` is reached.
36
+
37
+ - `list` (`string[]`): fragments to process.
38
+ - `instructions` (`string`): mapping instructions.
39
+ - `options.chunkSize` (`number`, default `10`): size of each batch.
40
+ - `options.maxAttempts` (`number`, default `3`): number of passes over failed items.
41
+
42
+ Returns `Promise<(string|undefined)[]>` aligned with input order.
43
+
@@ -0,0 +1,17 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import bulkMap from './index.js';
3
+ import { longTestTimeout } from '../../constants/common.js';
4
+
5
+ describe('bulkmap examples', () => {
6
+ it(
7
+ 'maps with listMap',
8
+ async () => {
9
+ const animals = ['dog', 'cat', 'cow', 'sheep', 'duck'];
10
+ const result = await bulkMap(animals, 'Return the sound each animal makes', { chunkSize: 3 });
11
+ // e.g. result[0] === 'bark'
12
+ // result[2] === 'moo'
13
+ expect(result.length).toBe(5);
14
+ },
15
+ longTestTimeout
16
+ );
17
+ });
@@ -0,0 +1,86 @@
1
+ import listMap from '../../verblets/list-map/index.js';
2
+
3
+ /**
4
+ * Map over a list of fragments by calling `listMap` on newline-delimited batches.
5
+ * Missing or mismatched output results in `undefined` entries so callers can
6
+ * selectively retry.
7
+ *
8
+ * @param { string[] } list - array of fragments to process
9
+ * @param { string } instructions - mapping instructions passed to `listMap`
10
+ * @param { object } [config={}] - configuration options
11
+ * @param { number } [config.chunkSize=10] - how many items to send per batch
12
+ * @param { object } [config.llm] - LLM configuration
13
+ * @returns { Promise<(string|undefined)[]> } results aligned with input order
14
+ */
15
+ const bulkMap = async function (list, instructions, config = {}) {
16
+ const { chunkSize = 10, llm, ...options } = config;
17
+ const results = new Array(list.length);
18
+ const promises = [];
19
+
20
+ for (let i = 0; i < list.length; i += chunkSize) {
21
+ const batch = list.slice(i, i + chunkSize);
22
+ const startIndex = i;
23
+
24
+ const p = Promise.resolve()
25
+ .then(() => listMap(batch, instructions, { llm, ...options }))
26
+ .then((output) => {
27
+ if (output.length !== batch.length) {
28
+ for (let j = 0; j < batch.length; j += 1) {
29
+ results[startIndex + j] = undefined;
30
+ }
31
+ return;
32
+ }
33
+ output.forEach((line, j) => {
34
+ results[startIndex + j] = line;
35
+ });
36
+ })
37
+ .catch(() => {
38
+ for (let j = 0; j < batch.length; j += 1) {
39
+ results[startIndex + j] = undefined;
40
+ }
41
+ });
42
+ promises.push(p);
43
+ }
44
+
45
+ await Promise.all(promises);
46
+ return results;
47
+ };
48
+
49
+ /**
50
+ * Retry only the undefined results from `map` until maxAttempts is reached.
51
+ *
52
+ * @param { string[] } list - array of fragments
53
+ * @param { string } instructions - mapping instructions passed to `listMap`
54
+ * @param { object } [config={}] - configuration options
55
+ * @param { number } [config.chunkSize=10]
56
+ * @param { number } [config.maxAttempts=3]
57
+ * @param { object } [config.llm] - LLM configuration
58
+ * @returns { Promise<(string|undefined)[]> }
59
+ */
60
+ export const bulkMapRetry = async function (list, instructions, config = {}) {
61
+ const { chunkSize = 10, maxAttempts = 3, llm, ...options } = config;
62
+ const results = await bulkMap(list, instructions, { chunkSize, llm, ...options });
63
+ for (let attempt = 1; attempt < maxAttempts; attempt += 1) {
64
+ const missingIdx = [];
65
+ const missingFragments = [];
66
+ results.forEach((val, idx) => {
67
+ if (val === undefined) {
68
+ missingIdx.push(idx);
69
+ missingFragments.push(list[idx]);
70
+ }
71
+ });
72
+ if (missingFragments.length === 0) break;
73
+ // eslint-disable-next-line no-await-in-loop
74
+ const retryResults = await bulkMap(missingFragments, instructions, {
75
+ chunkSize,
76
+ llm,
77
+ ...options,
78
+ });
79
+ retryResults.forEach((val, i) => {
80
+ results[missingIdx[i]] = val;
81
+ });
82
+ }
83
+ return results;
84
+ };
85
+
86
+ export default bulkMap;
@@ -0,0 +1,44 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
+ import bulkMap, { bulkMapRetry } from './index.js';
3
+ import listMap from '../../verblets/list-map/index.js';
4
+
5
+ vi.mock('../../verblets/list-map/index.js', () => ({
6
+ default: vi.fn(async (items, instructions) => {
7
+ if (items.includes('FAIL')) throw new Error('fail');
8
+ return items.map((i) => `${i}-${instructions}`);
9
+ }),
10
+ }));
11
+
12
+ beforeEach(() => {
13
+ vi.clearAllMocks();
14
+ });
15
+
16
+ describe('bulkmap', () => {
17
+ it('maps fragments in batches', async () => {
18
+ const result = await bulkMap(['a', 'b', 'c'], 'x', { chunkSize: 2 });
19
+ expect(result).toStrictEqual(['a-x', 'b-x', 'c-x']);
20
+ expect(listMap).toHaveBeenCalledTimes(2);
21
+ });
22
+
23
+ it('leaves undefined on error', async () => {
24
+ listMap.mockRejectedValueOnce(new Error('fail'));
25
+ const result = await bulkMap(['FAIL', 'oops'], 'x', { chunkSize: 2 });
26
+ expect(result).toStrictEqual([undefined, undefined]);
27
+ });
28
+
29
+ it('retries only failed fragments', async () => {
30
+ let call = 0;
31
+ listMap.mockImplementation(async (items) => {
32
+ call += 1;
33
+ if (call === 1) throw new Error('fail');
34
+ return items.map((l) => l.toUpperCase());
35
+ });
36
+
37
+ const result = await bulkMapRetry(['alpha', 'beta'], 'upper', {
38
+ chunkSize: 2,
39
+ maxAttempts: 2,
40
+ });
41
+ expect(result).toStrictEqual(['ALPHA', 'BETA']);
42
+ expect(listMap).toHaveBeenCalledTimes(2);
43
+ });
44
+ });
@@ -0,0 +1,12 @@
1
+ # bulk-reduce
2
+
3
+ Reduce long lists by processing them in smaller batches. Each batch is combined
4
+ with the accumulated result using `listReduce`.
5
+
6
+ ```javascript
7
+ import bulkReduce from './index.js';
8
+
9
+ const logs = ['step one', 'step two', 'step three'];
10
+ const result = await bulkReduce(logs, 'summarize');
11
+ // => 'summary of steps'
12
+ ```
@@ -0,0 +1,15 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import bulkReduce from './index.js';
3
+ import { longTestTimeout } from '../../constants/common.js';
4
+
5
+ describe('bulk-reduce examples', () => {
6
+ it(
7
+ 'reduces a long list sequentially',
8
+ async () => {
9
+ const items = ['one', 'two', 'three', 'four'];
10
+ const result = await bulkReduce(items, 'concatenate', { chunkSize: 2 });
11
+ expect(result).toBeDefined();
12
+ },
13
+ longTestTimeout
14
+ );
15
+ });
@@ -0,0 +1,13 @@
1
+ import listReduce from '../../verblets/list-reduce/index.js';
2
+
3
+ export default async function bulkReduce(list, instructions, config = {}) {
4
+ const { chunkSize = 10, initial, llm, ...options } = config;
5
+ let acc = initial;
6
+ for (let i = 0; i < list.length; i += chunkSize) {
7
+ const batch = list.slice(i, i + chunkSize);
8
+
9
+ // eslint-disable-next-line no-await-in-loop
10
+ acc = await listReduce(acc, batch, instructions, { llm, ...options });
11
+ }
12
+ return acc;
13
+ }
@@ -0,0 +1,25 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
+ import bulkReduce from './index.js';
3
+ import listReduce from '../../verblets/list-reduce/index.js';
4
+
5
+ vi.mock('../../verblets/list-reduce/index.js', () => ({
6
+ default: vi.fn(async (acc, list) => [acc, ...list].filter(Boolean).join('-')),
7
+ }));
8
+
9
+ beforeEach(() => {
10
+ vi.clearAllMocks();
11
+ });
12
+
13
+ describe('bulk-reduce chain', () => {
14
+ it('reduces in batches', async () => {
15
+ const result = await bulkReduce(['a', 'b', 'c', 'd'], 'join', { chunkSize: 2 });
16
+ expect(result).toBe('a-b-c-d');
17
+ expect(listReduce).toHaveBeenCalledTimes(2);
18
+ });
19
+
20
+ it('uses initial value', async () => {
21
+ const result = await bulkReduce(['x', 'y'], 'join', { initial: '0', chunkSize: 2 });
22
+ expect(result).toBe('0-x-y');
23
+ expect(listReduce).toHaveBeenCalledTimes(1);
24
+ });
25
+ });
@@ -0,0 +1,16 @@
1
+ # bulk-score
2
+
3
+ Score lines of text on a 0–10 scale with automatic calibration. Each batch returns a JSON array so parsing stays reliable even with long lists. The chain first scores everything, then rescors a few low, middle, and high examples to calibrate. Those references feed a second scoring pass so every item is ranked consistently using OpenAI's JSON schema enforcement.
4
+
5
+ ```javascript
6
+ import bulkScore from './index.js';
7
+
8
+ const slogans = [
9
+ 'Amazing deals every day!',
10
+ 'Unlock a world of wonder',
11
+ 'Buy stuff now',
12
+ ];
13
+
14
+ const { scores } = await bulkScore(slogans, 'How catchy is this marketing slogan?');
15
+ // scores like [6, 9, 2]
16
+ ```
@@ -0,0 +1,18 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "type": "object",
4
+ "properties": {
5
+ "scores": {
6
+ "type": "array",
7
+ "description": "Array of numeric scores corresponding to input items",
8
+ "items": {
9
+ "type": "number",
10
+ "minimum": 0,
11
+ "maximum": 10,
12
+ "description": "Score from 0 (worst) to 10 (best)"
13
+ }
14
+ }
15
+ },
16
+ "required": ["scores"],
17
+ "additionalProperties": false
18
+ }
@@ -0,0 +1,22 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { longTestTimeout } from '../../constants/common.js';
3
+ import bulkScore from './index.js';
4
+
5
+ describe('bulkScore examples', () => {
6
+ it(
7
+ 'ranks jokes by humor',
8
+ async () => {
9
+ const jokes = [
10
+ 'Why did the chicken cross the road? To get to the other side!',
11
+ "Parallel lines have so much in common. It's a shame they'll never meet.",
12
+ "I told my computer I needed a break, and it said 'I'll go to sleep.'",
13
+ ];
14
+
15
+ const { scores } = await bulkScore(jokes, 'How funny is this joke?');
16
+
17
+ expect(scores).toHaveLength(jokes.length);
18
+ scores.forEach((s) => expect(typeof s).toBe('number'));
19
+ },
20
+ longTestTimeout
21
+ );
22
+ });
@@ -0,0 +1,133 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import chatGPT from '../../lib/chatgpt/index.js';
5
+ import wrapVariable from '../../prompts/wrap-variable.js';
6
+ import { constants as promptConstants } from '../../prompts/index.js';
7
+
8
+ const { onlyJSONArray } = promptConstants;
9
+
10
+ // Get the directory of this module
11
+ const __filename = fileURLToPath(import.meta.url);
12
+ const __dirname = path.dirname(__filename);
13
+
14
+ /**
15
+ * Load the JSON schema for bulk score results
16
+ * @returns {Promise<Object>} JSON schema for validation
17
+ */
18
+ async function getBulkScoreSchema() {
19
+ const schemaPath = path.join(__dirname, 'bulk-score-result.json');
20
+ return JSON.parse(await fs.readFile(schemaPath, 'utf8'));
21
+ }
22
+
23
+ /**
24
+ * Create model options for structured outputs
25
+ * @param {string|Object} llm - LLM model name or configuration object
26
+ * @returns {Promise<Object>} Model options for chatGPT
27
+ */
28
+ async function createModelOptions(llm = 'fastGoodCheap') {
29
+ const schema = await getBulkScoreSchema();
30
+
31
+ const responseFormat = {
32
+ type: 'json_schema',
33
+ json_schema: {
34
+ name: 'bulk_score_result',
35
+ schema,
36
+ },
37
+ };
38
+
39
+ if (typeof llm === 'string') {
40
+ return {
41
+ modelName: llm,
42
+ response_format: responseFormat,
43
+ };
44
+ } else {
45
+ return {
46
+ ...llm,
47
+ response_format: responseFormat,
48
+ };
49
+ }
50
+ }
51
+
52
+ async function scoreBatch(items, instructions, reference = [], config = {}) {
53
+ const { llm, ...options } = config;
54
+ const listBlock = wrapVariable(items.join('\n'), { tag: 'items' });
55
+ const refBlock = reference.length
56
+ ? `\nCalibration examples (score - text):\n${wrapVariable(
57
+ reference.map((r) => `${r.score} - ${r.item}`).join('\n'),
58
+ { tag: 'reference' }
59
+ )}`
60
+ : '';
61
+
62
+ const prompt =
63
+ `Score each line in <items> from 0 (worst) to 10 (best) based on: ${instructions}.` +
64
+ `\nRespond with a JSON object containing a "scores" array of numbers in the same order.` +
65
+ `${refBlock}\n${onlyJSONArray}\n${listBlock}`;
66
+
67
+ const modelOptions = await createModelOptions(llm);
68
+ const response = await chatGPT(prompt, {
69
+ modelOptions,
70
+ ...options,
71
+ });
72
+
73
+ // With structured outputs, response should already be parsed and validated
74
+ const parsed = typeof response === 'string' ? JSON.parse(response) : response;
75
+ // Extract scores from the object structure
76
+ const arr = parsed?.scores || parsed;
77
+
78
+ if (!Array.isArray(arr) || arr.length !== items.length) {
79
+ throw new Error('Score batch mismatch');
80
+ }
81
+ return arr.map((n) => Number(n));
82
+ }
83
+
84
+ export default async function bulkScore(list, instructions, config = {}) {
85
+ const { chunkSize = 10, examples, llm, ...options } = config;
86
+ if (!Array.isArray(list) || list.length === 0) {
87
+ return { scores: [], reference: [] };
88
+ }
89
+
90
+ const firstScores = [];
91
+ for (let i = 0; i < list.length; i += chunkSize) {
92
+ // eslint-disable-next-line no-await-in-loop
93
+ const scores = await scoreBatch(list.slice(i, i + chunkSize), instructions, [], {
94
+ llm,
95
+ ...options,
96
+ });
97
+ firstScores.push(...scores);
98
+ }
99
+
100
+ const scored = list.map((item, idx) => ({ item, score: firstScores[idx] }));
101
+
102
+ let reference = examples;
103
+ if (!reference) {
104
+ const valid = scored.filter((s) => Number.isFinite(s.score));
105
+ if (valid.length) {
106
+ valid.sort((a, b) => a.score - b.score);
107
+ const lows = valid.slice(0, 3);
108
+ const highs = valid.slice(-3);
109
+ const midStart = Math.max(0, Math.floor(valid.length / 2) - 1);
110
+ const mids = valid.slice(midStart, midStart + 3);
111
+ reference = [...lows, ...mids, ...highs];
112
+ const refItems = reference.map((r) => r.item);
113
+ const rescored = await scoreBatch(refItems, instructions, [], { llm, ...options });
114
+ rescored.forEach((score, idx) => {
115
+ reference[idx].score = score;
116
+ });
117
+ } else {
118
+ reference = [];
119
+ }
120
+ }
121
+
122
+ const finalScores = [];
123
+ for (let i = 0; i < list.length; i += chunkSize) {
124
+ // eslint-disable-next-line no-await-in-loop
125
+ const scores = await scoreBatch(list.slice(i, i + chunkSize), instructions, reference, {
126
+ llm,
127
+ ...options,
128
+ });
129
+ finalScores.push(...scores);
130
+ }
131
+
132
+ return { scores: finalScores, reference };
133
+ }
@@ -0,0 +1,30 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
+ import bulkScore from './index.js';
3
+ import chatGPT from '../../lib/chatgpt/index.js';
4
+
5
+ vi.mock('../../lib/chatgpt/index.js', () => ({
6
+ default: vi.fn(),
7
+ }));
8
+
9
+ beforeEach(() => {
10
+ vi.clearAllMocks();
11
+ });
12
+
13
+ describe('bulkScore chain', () => {
14
+ it('scores items using two passes', async () => {
15
+ chatGPT
16
+ .mockResolvedValueOnce('[1,2,3]')
17
+ .mockResolvedValueOnce('[1,2,3,4,5,6,7,8,9]')
18
+ .mockResolvedValueOnce('[1,2,3]');
19
+ const { scores, reference } = await bulkScore(['a', 'bb', 'ccc'], 'length');
20
+ expect(scores).toStrictEqual([1, 2, 3]);
21
+ expect(reference.length).toBeGreaterThan(0);
22
+ expect(chatGPT).toHaveBeenCalled();
23
+ });
24
+
25
+ it('uses provided examples', async () => {
26
+ chatGPT.mockResolvedValueOnce('[1]').mockResolvedValueOnce('[1]');
27
+ const { scores } = await bulkScore(['x'], 'length', { examples: [{ item: 'y', score: 2 }] });
28
+ expect(scores[0]).toBe(1);
29
+ });
30
+ });
@@ -0,0 +1,61 @@
1
+ # Category Samples Chain
2
+
3
+ Generate diverse, representative examples for any category. This chain applies prototype theory and related cognitive science principles to output a well-rounded set of sample items.
4
+
5
+ ## Features
6
+
7
+ - **Cognitive Science Foundation**: Uses prototype theory and family resemblance principles
8
+ - **Diversity Control**: Configurable diversity levels (focused, balanced, high)
9
+ - **Context Awareness**: Supports contextual constraints for targeted generation
10
+ - **Robust Retry Logic**: Built-in retry mechanisms for reliable generation
11
+ - **Scalable Architecture**: Leverages the list chain infrastructure for efficient processing
12
+
13
+ ## Usage
14
+
15
+ ### Basic Usage
16
+
17
+ ```javascript
18
+ import categorySamples from './src/chains/category-samples/index.js';
19
+
20
+ // Generate basic fruit samples
21
+ const fruitSamples = await categorySamples('fruit', {
22
+ count: 5,
23
+ diversityLevel: 'balanced'
24
+ });
25
+ // Result: ['apple', 'orange', 'durian', 'banana', 'kiwi']
26
+ ```
27
+
28
+ ### With Context
29
+
30
+ ```javascript
31
+ // Generate contextually relevant samples
32
+ const birdSamples = await categorySamples('bird', {
33
+ context: 'Common backyard birds in North America',
34
+ count: 4,
35
+ diversityLevel: 'focused'
36
+ });
37
+ // Result: ['robin', 'cardinal', 'blue jay', 'sparrow']
38
+ ```
39
+
40
+ ### High Diversity Generation
41
+
42
+ ```javascript
43
+ // Generate diverse vehicle types
44
+ const vehicleSamples = await categorySamples('vehicle', {
45
+ count: 6,
46
+ diversityLevel: 'high'
47
+ });
48
+ // Result: ['car', 'bicycle', 'helicopter', 'submarine', 'skateboard', 'spaceship']
49
+ ```
50
+
51
+ ## API Reference
52
+
53
+ ### `categorySamples(categoryName, options)`
54
+
55
+ Returns an array of sample items for the given category. Options let you control diversity, add context, and configure retry logic.
56
+
57
+ **Common Options**
58
+
59
+ - `count` (number): How many samples to return (default: 10)
60
+ - `context` (string): Extra context to guide generation
61
+ - `diversityLevel` ('focused' | 'balanced' | 'high'): Adjusts how typical or atypical the samples are
@@ -0,0 +1,103 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { longTestTimeout } from '../../constants/common.js';
3
+ import { expect as aiExpect } from '../expect/index.js';
4
+ import categorySamples from './index.js';
5
+
6
+ describe('Category Samples Chain', () => {
7
+ it(
8
+ 'generates basic seed items for a category',
9
+ async () => {
10
+ const seeds = await categorySamples('fruit', {
11
+ count: 5,
12
+ diversityLevel: 'balanced',
13
+ });
14
+
15
+ expect(seeds).toHaveLength(5);
16
+ expect(seeds.every((seed) => typeof seed === 'string')).toBe(true);
17
+ expect(seeds.every((seed) => seed.length > 0)).toBe(true);
18
+
19
+ // Use expect-chain for loose verification
20
+ const [isValidFruitList] = await aiExpect(
21
+ seeds,
22
+ undefined,
23
+ 'Are these reasonable fruit names that represent a balanced mix of typical and moderately typical fruits?'
24
+ );
25
+ expect(isValidFruitList).toBe(true);
26
+ },
27
+ longTestTimeout
28
+ );
29
+
30
+ it(
31
+ 'generates seeds with context',
32
+ async () => {
33
+ const seeds = await categorySamples('bird', {
34
+ context: 'Common backyard birds in North America',
35
+ count: 4,
36
+ diversityLevel: 'focused',
37
+ });
38
+
39
+ expect(seeds).toHaveLength(4);
40
+ expect(seeds.every((seed) => typeof seed === 'string')).toBe(true);
41
+
42
+ // Use expect-chain for loose verification
43
+ const [isValidBirdList] = await aiExpect(
44
+ seeds,
45
+ undefined,
46
+ 'Are these reasonable names of common backyard birds that would be found in North America?'
47
+ );
48
+ expect(isValidBirdList).toBe(true);
49
+ },
50
+ longTestTimeout
51
+ );
52
+
53
+ it(
54
+ 'generates diverse seeds with high diversity level',
55
+ async () => {
56
+ const seeds = await categorySamples('vehicle', {
57
+ count: 6,
58
+ diversityLevel: 'high',
59
+ });
60
+
61
+ expect(seeds).toHaveLength(6);
62
+ expect(seeds.every((seed) => typeof seed === 'string')).toBe(true);
63
+
64
+ // Use expect-chain for loose verification - check for diversity without being overly specific
65
+ const [isValidVehicleList] = await aiExpect(
66
+ seeds,
67
+ undefined,
68
+ 'Are these vehicle names reasonably diverse, showing variety in the types of vehicles represented?'
69
+ );
70
+ expect(isValidVehicleList).toBe(true);
71
+ },
72
+ longTestTimeout
73
+ );
74
+
75
+ it('throws error for invalid category name', async () => {
76
+ await expect(categorySamples('')).rejects.toThrow('categoryName must be a non-empty string');
77
+ await expect(categorySamples(null)).rejects.toThrow('categoryName must be a non-empty string');
78
+ });
79
+
80
+ it(
81
+ 'handles retry logic on failures',
82
+ async () => {
83
+ // This test ensures the retry mechanism works
84
+ const seeds = await categorySamples('animal', {
85
+ count: 3,
86
+ maxRetries: 2,
87
+ retryDelay: 100,
88
+ });
89
+
90
+ expect(seeds).toHaveLength(3);
91
+ expect(seeds.every((seed) => typeof seed === 'string')).toBe(true);
92
+
93
+ // Use expect-chain for loose verification
94
+ const [isValidAnimalList] = await aiExpect(
95
+ seeds,
96
+ undefined,
97
+ 'Are these reasonable animal names?'
98
+ );
99
+ expect(isValidAnimalList).toBe(true);
100
+ },
101
+ longTestTimeout
102
+ );
103
+ });