@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,134 @@
1
+ import list from '../list/index.js';
2
+ import retry from '../../lib/retry/index.js';
3
+ import modelService from '../../services/llm-model/index.js';
4
+
5
+ /**
6
+ * Core prompt template for sample generation using cognitive science principles
7
+ */
8
+ export const SAMPLE_GENERATION_PROMPT = `Generate sample items for the category "{categoryName}" using cognitive science principles.
9
+
10
+ {context}
11
+
12
+ COGNITIVE PRINCIPLES:
13
+ 1. Prototype Theory: Include items across the typicality spectrum
14
+ 2. Family Resemblance: Ensure items share overlapping but not identical features
15
+ 3. Category Structure: {diversityInstructions}
16
+
17
+ REQUIREMENTS:
18
+ - Include highly typical/prototypical members
19
+ - Include moderately typical members
20
+ - {diversityRequirement}
21
+ - Ensure good coverage of category space
22
+ - Avoid redundant or near-identical items
23
+
24
+ IMPORTANT: Return only clean item names without numbering, descriptions, or explanations.`;
25
+
26
+ /**
27
+ * Build sample generation prompt with specific parameters
28
+ * @param {string} categoryName - Name of the category
29
+ * @param {Object} config - Configuration options
30
+ * @returns {string} Complete prompt for sample generation
31
+ */
32
+ export function buildSeedGenerationPrompt(
33
+ categoryName,
34
+ { context = '', diversityLevel = 'balanced' } = {}
35
+ ) {
36
+ const diversityInstructions = {
37
+ high: 'Include very diverse examples spanning edge cases and borderline members',
38
+ balanced: 'Include a mix of typical, moderately typical, and some atypical members',
39
+ focused: 'Focus on highly typical, central members with clear category membership',
40
+ };
41
+
42
+ const diversityRequirement = {
43
+ high: 'Include many atypical but valid members',
44
+ balanced: 'Include some moderately atypical members',
45
+ focused: 'Focus primarily on typical members',
46
+ };
47
+
48
+ const contextLine = context ? `Context: ${context}` : '';
49
+
50
+ return SAMPLE_GENERATION_PROMPT.replace('{categoryName}', categoryName)
51
+ .replace('{context}', contextLine)
52
+ .replace(
53
+ '{diversityInstructions}',
54
+ diversityInstructions[diversityLevel] || diversityInstructions.balanced
55
+ )
56
+ .replace(
57
+ '{diversityRequirement}',
58
+ diversityRequirement[diversityLevel] || diversityRequirement.balanced
59
+ );
60
+ }
61
+
62
+ /**
63
+ * Generate sample items for a category using cognitive science principles.
64
+ * Creates diverse, representative examples across the typicality spectrum.
65
+ *
66
+ * @param {string} categoryName - Name of the category
67
+ * @param {Object} [options={}] - Configuration options
68
+ * @param {string} [options.context=''] - Context for sample generation
69
+ * @param {number} [options.count=30] - Number of sample items to generate
70
+ * @param {string} [options.diversityLevel='balanced'] - 'high', 'balanced', or 'focused'
71
+ * @param {string|Object} [options.llm='fastGoodCheap'] - LLM model to use
72
+ * @param {number} [options.maxRetries=3] - Maximum number of retry attempts
73
+ * @param {number} [options.retryDelay=1000] - Delay between retries in milliseconds
74
+ * @returns {Promise<string[]>}
75
+ */
76
+ export default async function categorySamples(categoryName, options = {}) {
77
+ if (!categoryName || typeof categoryName !== 'string') {
78
+ throw new Error('categoryName must be a non-empty string');
79
+ }
80
+
81
+ const {
82
+ context = '',
83
+ count = 30,
84
+ diversityLevel = 'balanced',
85
+ llm = 'fastGoodCheap',
86
+ maxRetries = 3,
87
+ retryDelay = 1000,
88
+ } = options;
89
+
90
+ const generateWithRetry = async () => {
91
+ const prompt = buildSeedGenerationPrompt(categoryName, { context, diversityLevel });
92
+
93
+ // Get the model object from the model service
94
+ const model = typeof llm === 'string' ? modelService.getModel(llm) : llm;
95
+
96
+ const results = await list(prompt, {
97
+ llm: model,
98
+ shouldStop: ({ resultsAll }) => resultsAll.length >= count,
99
+ });
100
+
101
+ if (!results || results.length === 0) {
102
+ throw new Error(`No sample items generated for category: ${categoryName}`);
103
+ }
104
+
105
+ // Return only the requested count
106
+ return results.slice(0, count);
107
+ };
108
+
109
+ return await retry(generateWithRetry, {
110
+ maxRetries,
111
+ retryDelay,
112
+ retryCondition: (error) => {
113
+ // Retry on network errors, timeouts, or empty results
114
+ return (
115
+ error.message.includes('No sample items generated') ||
116
+ error.message.includes('timeout') ||
117
+ error.message.includes('network') ||
118
+ error.message.includes('ECONNRESET')
119
+ );
120
+ },
121
+ });
122
+ }
123
+
124
+ /**
125
+ * Generate sample items for a category using list generation
126
+ * @param {string} category - The category to generate samples for
127
+ * @param {number} _count - Target number of samples (unused, kept for API compatibility)
128
+ * @param {Object} options - Additional options
129
+ * @returns {Promise<string[]>} Array of sample items
130
+ */
131
+ export function categorySamplesList(category, _count = 10, options = {}) {
132
+ // Use the list chain to generate samples for the category
133
+ return list(category, options);
134
+ }
@@ -0,0 +1,12 @@
1
+ # collect-terms
2
+
3
+ Extract the most difficult or technical terms from any passage. Useful for building a glossary or highlighting vocabulary that needs clarification.
4
+
5
+ ## Usage
6
+
7
+ ```javascript
8
+ import collectTerms from './collect-terms/index.js';
9
+
10
+ const terms = await collectTerms(longText, { topN: 15 });
11
+ // => ['usufructuary rights', 'riparian', 'hydrological cycle', ...]
12
+ ```
@@ -0,0 +1,16 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { longTestTimeout } from '../../constants/common.js';
3
+ import collectTerms from './index.js';
4
+
5
+ const sample = `Quantum entanglement and the observer effect are central to modern physics. Understanding Heisenberg's uncertainty principle alongside quantum decoherence helps explain particle behavior at subatomic scales.`;
6
+
7
+ describe('collectTerms chain', () => {
8
+ it(
9
+ 'Example',
10
+ async () => {
11
+ const terms = await collectTerms(sample, { topN: 5 });
12
+ expect(Array.isArray(terms)).toBe(true);
13
+ },
14
+ longTestTimeout
15
+ );
16
+ });
@@ -0,0 +1,44 @@
1
+ import list from '../list/index.js';
2
+ import listReduce from '../../verblets/list-reduce/index.js';
3
+
4
+ const splitIntoChunks = (text, maxLen) => {
5
+ const words = text.split(/\s+/);
6
+ const chunks = [];
7
+ let current = '';
8
+ for (const word of words) {
9
+ if (current.length + word.length + 1 > maxLen) {
10
+ if (current) chunks.push(current.trim());
11
+ current = word;
12
+ } else {
13
+ current += (current ? ' ' : '') + word;
14
+ }
15
+ }
16
+ if (current) chunks.push(current.trim());
17
+ return chunks;
18
+ };
19
+
20
+ export default async function collectTerms(text, config = {}) {
21
+ const { chunkLen = 1000, topN = 20, llm, ...options } = config;
22
+ const chunks = splitIntoChunks(text, chunkLen);
23
+ const allTerms = [];
24
+ for (const chunk of chunks) {
25
+ const terms = await list(`important complex or technical terms from: ${chunk}`, {
26
+ llm,
27
+ ...options,
28
+ });
29
+ allTerms.push(...terms);
30
+ }
31
+ const uniqueTerms = Array.from(new Set(allTerms.map((t) => t.trim())));
32
+ if (uniqueTerms.length <= topN) return uniqueTerms;
33
+ const reduced = await listReduce(
34
+ '',
35
+ uniqueTerms,
36
+ `Return the top ${topN} terms as a comma-separated list`,
37
+ { llm, ...options }
38
+ );
39
+ return reduced
40
+ .split(',')
41
+ .map((t) => t.trim())
42
+ .filter(Boolean)
43
+ .slice(0, topN);
44
+ }
@@ -0,0 +1,25 @@
1
+ import { describe, expect, it, beforeEach, vi } from 'vitest';
2
+ import collectTerms from './index.js';
3
+ import list from '../list/index.js';
4
+ import listReduce from '../../verblets/list-reduce/index.js';
5
+
6
+ vi.mock('../list/index.js');
7
+ vi.mock('../../verblets/list-reduce/index.js');
8
+
9
+ beforeEach(() => {
10
+ vi.resetAllMocks();
11
+ });
12
+
13
+ describe('collectTerms chain', () => {
14
+ it('deduplicates and reduces to top terms', async () => {
15
+ list.mockResolvedValueOnce(['alpha', 'beta']).mockResolvedValueOnce(['beta', 'gamma']);
16
+ listReduce.mockResolvedValue('alpha, beta, gamma');
17
+
18
+ const text = 'p1\n\np2';
19
+ const result = await collectTerms(text, { chunkLen: 2, topN: 2 });
20
+
21
+ expect(list).toHaveBeenCalledTimes(2);
22
+ expect(result).toStrictEqual(['alpha', 'beta']);
23
+ expect(listReduce).toHaveBeenCalled();
24
+ });
25
+ });
@@ -0,0 +1,12 @@
1
+ # date
2
+
3
+ Iteratively refine LLM answers until they produce a valid JavaScript `Date` object that satisfies the prompt. Before asking for a date, the chain asks the language model to suggest a few quick checks that a correct answer should satisfy. Each returned date is evaluated against those expectations with the `bool` verblt and retried if any fail.
4
+
5
+ ```javascript
6
+ import date from './index.js';
7
+
8
+ const release = await date('When was the original Star Wars film released?');
9
+ // => new Date('1977-05-25')
10
+ ```
11
+
12
+ The chain asks for a date using prompt constants shared with primitive verblets. It then verifies the result with the `bool` verblet and retries until the date is deemed correct or attempts run out.
@@ -0,0 +1,47 @@
1
+ import { describe, expect, it, beforeAll, afterAll } from 'vitest';
2
+ import date from './index.js';
3
+ import { expect as llmExpect } from '../../chains/expect/index.js';
4
+ import { longTestTimeout } from '../../constants/common.js';
5
+
6
+ describe('date examples', () => {
7
+ const originalMode = process.env.LLM_EXPECT_MODE;
8
+
9
+ beforeAll(() => {
10
+ process.env.LLM_EXPECT_MODE = 'none';
11
+ });
12
+
13
+ afterAll(() => {
14
+ if (originalMode !== undefined) {
15
+ process.env.LLM_EXPECT_MODE = originalMode;
16
+ } else {
17
+ delete process.env.LLM_EXPECT_MODE;
18
+ }
19
+ });
20
+
21
+ it(
22
+ 'gets Star Wars release date',
23
+ async () => {
24
+ const result = await date('When was the original Star Wars released?');
25
+ expect(result instanceof Date).toBe(true);
26
+ const [reasonable] = await llmExpect(
27
+ `Star Wars release date: ${result.toISOString()}`,
28
+ undefined,
29
+ 'Is this close to the actual release date of the first Star Wars movie?'
30
+ );
31
+ expect(reasonable).toBe(true);
32
+ },
33
+ longTestTimeout
34
+ );
35
+
36
+ it(
37
+ 'finds Christmas 2025',
38
+ async () => {
39
+ const result = await date('When is Christmas Day in 2025?');
40
+ expect(result instanceof Date).toBe(true);
41
+ expect(result.getUTCFullYear()).toBe(2025);
42
+ expect(result.getUTCMonth()).toBe(11); // December
43
+ expect(result.getUTCDate()).toBe(25);
44
+ },
45
+ longTestTimeout
46
+ );
47
+ });
@@ -0,0 +1,74 @@
1
+ import chatGPT from '../../lib/chatgpt/index.js';
2
+ import stripResponse from '../../lib/strip-response/index.js';
3
+ import toDate from '../../lib/to-date/index.js';
4
+ import toObject from '../../verblets/to-object/index.js';
5
+ import bool from '../../verblets/bool/index.js';
6
+ import { constants as promptConstants } from '../../prompts/index.js';
7
+
8
+ const {
9
+ asDate,
10
+ asUndefinedByDefault,
11
+ contentIsQuestion,
12
+ explainAndSeparate,
13
+ explainAndSeparatePrimitive,
14
+ onlyJSONArray,
15
+ } = promptConstants;
16
+
17
+ const expectationPrompt = (question) => `${contentIsQuestion} ${question}
18
+
19
+ List up to three short yes/no checks that would confirm a date answer is correct. If nothing specific comes to mind, respond with ["The result is a valid date"].
20
+
21
+ ${onlyJSONArray}`;
22
+
23
+ const buildCheckPrompt = (dateValue, check) => {
24
+ const iso = dateValue.toISOString();
25
+ const human = dateValue.toUTCString();
26
+ const utcDate = new Date(
27
+ Date.UTC(dateValue.getUTCFullYear(), dateValue.getUTCMonth(), dateValue.getUTCDate())
28
+ );
29
+ return `Date in ISO: ${iso} (UTC: ${human}, UTC date: ${utcDate.toISOString()}). Does this satisfy "${check}"?`;
30
+ };
31
+
32
+ export default async function date(text, config = {}) {
33
+ const { maxAttempts = 3, llm, ...options } = config;
34
+ const llmExpectations = (await toObject(
35
+ await chatGPT(expectationPrompt(text), { modelOptions: { ...llm }, ...options }),
36
+ null,
37
+ { llm, ...options }
38
+ )) || ['The result is a valid date'];
39
+
40
+ let attemptText = text;
41
+ let response;
42
+ for (let i = 0; i < maxAttempts; i += 1) {
43
+ const datePrompt = `${contentIsQuestion} ${attemptText}\n\n${explainAndSeparate} ${explainAndSeparatePrimitive}\n\n${asDate} ${asUndefinedByDefault}`;
44
+ // eslint-disable-next-line no-await-in-loop
45
+ response = await chatGPT(datePrompt, { modelOptions: { ...llm }, ...options });
46
+ const value = toDate(stripResponse(response));
47
+ if (value === undefined) return undefined;
48
+
49
+ // Convert to UTC date for consistent checks
50
+ const utcValue = new Date(
51
+ Date.UTC(value.getUTCFullYear(), value.getUTCMonth(), value.getUTCDate())
52
+ );
53
+
54
+ let failedCheck;
55
+ for (const check of llmExpectations) {
56
+ // eslint-disable-next-line no-await-in-loop
57
+ const passed = await bool(buildCheckPrompt(utcValue, check), { llm, ...options });
58
+ if (!passed) {
59
+ failedCheck = check;
60
+ break;
61
+ }
62
+ }
63
+
64
+ if (!failedCheck) return utcValue;
65
+
66
+ attemptText = `${text} The previous answer (${utcValue.toISOString()}) failed to satisfy: "${failedCheck}". Try again.`;
67
+ }
68
+ const finalValue = toDate(stripResponse(response));
69
+ return finalValue
70
+ ? new Date(
71
+ Date.UTC(finalValue.getUTCFullYear(), finalValue.getUTCMonth(), finalValue.getUTCDate())
72
+ )
73
+ : undefined;
74
+ }
@@ -0,0 +1,62 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
+ import date from './index.js';
3
+ import bool from '../../verblets/bool/index.js';
4
+
5
+ vi.mock('../../lib/chatgpt/index.js', () => ({
6
+ default: vi.fn(),
7
+ }));
8
+
9
+ vi.mock('../../verblets/bool/index.js', () => ({
10
+ default: vi.fn(),
11
+ }));
12
+
13
+ vi.mock('../../verblets/to-object/index.js', () => ({
14
+ default: vi.fn(),
15
+ }));
16
+
17
+ const chatGPT = (await import('../../lib/chatgpt/index.js')).default;
18
+ const toObjectMock = (await import('../../verblets/to-object/index.js')).default;
19
+
20
+ describe('date chain', () => {
21
+ beforeEach(() => {
22
+ vi.clearAllMocks();
23
+ });
24
+ it('returns a date when bool approves', async () => {
25
+ chatGPT.mockResolvedValueOnce('["check"]');
26
+ toObjectMock.mockResolvedValueOnce(['check']);
27
+ chatGPT.mockResolvedValueOnce('2023-01-02');
28
+ bool.mockResolvedValueOnce(true);
29
+ const result = await date('When is tomorrow?');
30
+ expect(result instanceof Date).toBe(true);
31
+ expect(result.toISOString().startsWith('2023-01-02')).toBe(true);
32
+ expect(chatGPT).toHaveBeenCalledTimes(2);
33
+ expect(toObjectMock).toHaveBeenCalledTimes(1);
34
+ expect(bool).toHaveBeenCalledTimes(1);
35
+ });
36
+
37
+ it('retries until bool approves', async () => {
38
+ chatGPT.mockResolvedValueOnce('["check"]');
39
+ toObjectMock.mockResolvedValueOnce(['check']);
40
+ chatGPT.mockResolvedValueOnce('2023-01-02');
41
+ chatGPT.mockResolvedValueOnce('2023-02-03');
42
+ bool.mockResolvedValueOnce(false);
43
+ bool.mockResolvedValueOnce(true);
44
+
45
+ const result = await date('When is tomorrow?', { maxAttempts: 2 });
46
+ expect(result.toISOString().startsWith('2023-02-03')).toBe(true);
47
+ expect(chatGPT).toHaveBeenCalledTimes(3);
48
+ expect(toObjectMock).toHaveBeenCalledTimes(1);
49
+ expect(bool).toHaveBeenCalledTimes(2);
50
+ });
51
+
52
+ it('returns undefined when chatGPT says undefined', async () => {
53
+ chatGPT.mockResolvedValueOnce('["check"]');
54
+ toObjectMock.mockResolvedValueOnce(['check']);
55
+ chatGPT.mockResolvedValueOnce('undefined');
56
+ const result = await date('Unknown date');
57
+ expect(result).toBeUndefined();
58
+ expect(chatGPT).toHaveBeenCalledTimes(2);
59
+ expect(toObjectMock).toHaveBeenCalledTimes(1);
60
+ expect(bool).not.toHaveBeenCalled();
61
+ });
62
+ });
@@ -0,0 +1,22 @@
1
+ # disambiguate
2
+
3
+ Determine the intended meaning of a polysemous word or short phrase based on surrounding context.
4
+ The chain lists common meanings and filters them down to the one that fits best.
5
+
6
+ ```javascript
7
+ import disambiguate from './index.js';
8
+
9
+ const result = await disambiguate({
10
+ term: 'bat',
11
+ context: 'The child swung the bat at the baseball.'
12
+ });
13
+
14
+ console.log(result.meaning);
15
+ // => "a club used in sports like baseball"
16
+ ```
17
+
18
+ ## Use case: clarifying travel conversations
19
+
20
+ When a traveler says, "I spoke with the coach about my seat," it helps to know
21
+ whether they mean a sports instructor or an airline seating class. This chain
22
+ uses language model reasoning to resolve that ambiguity automatically.
@@ -0,0 +1,16 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "type": "object",
4
+ "properties": {
5
+ "meanings": {
6
+ "type": "array",
7
+ "description": "Array of distinct dictionary meanings or common uses of a term",
8
+ "items": {
9
+ "type": "string",
10
+ "description": "A distinct meaning or common use of the term"
11
+ }
12
+ }
13
+ },
14
+ "required": ["meanings"],
15
+ "additionalProperties": false
16
+ }
@@ -0,0 +1,18 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import disambiguate from './index.js';
3
+ import { longTestTimeout } from '../../constants/common.js';
4
+
5
+ describe('Disambiguate chain', () => {
6
+ it(
7
+ 'contextual meaning: bank',
8
+ async () => {
9
+ const result = await disambiguate({
10
+ term: 'bank',
11
+ context: 'She waited in line at the bank to deposit her paycheck.',
12
+ });
13
+ expect(typeof result.meaning).toBe('string');
14
+ expect(result.meaning.length).toBeGreaterThan(0);
15
+ },
16
+ longTestTimeout
17
+ );
18
+ });
@@ -0,0 +1,92 @@
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 listFilter from '../../verblets/list-filter/index.js';
6
+ import { constants as promptConstants } from '../../prompts/index.js';
7
+ import modelService from '../../services/llm-model/index.js';
8
+
9
+ const { onlyJSONStringArray } = promptConstants;
10
+
11
+ // Get the directory of this module
12
+ const __filename = fileURLToPath(import.meta.url);
13
+ const __dirname = path.dirname(__filename);
14
+
15
+ /**
16
+ * Load the JSON schema for disambiguate meanings results
17
+ * @returns {Promise<Object>} JSON schema for validation
18
+ */
19
+ async function getDisambiguateMeaningsSchema() {
20
+ const schemaPath = path.join(__dirname, 'disambiguate-meanings-result.json');
21
+ return JSON.parse(await fs.readFile(schemaPath, 'utf8'));
22
+ }
23
+
24
+ /**
25
+ * Create model options for structured outputs
26
+ * @param {string|Object} llm - LLM model name or configuration object
27
+ * @returns {Promise<Object>} Model options for chatGPT
28
+ */
29
+ async function createModelOptions(llm = 'fastGoodCheap') {
30
+ const schema = await getDisambiguateMeaningsSchema();
31
+
32
+ const responseFormat = {
33
+ type: 'json_schema',
34
+ json_schema: {
35
+ name: 'disambiguate_meanings_result',
36
+ schema,
37
+ },
38
+ };
39
+
40
+ if (typeof llm === 'string') {
41
+ return {
42
+ modelName: llm,
43
+ response_format: responseFormat,
44
+ };
45
+ } else {
46
+ return {
47
+ ...llm,
48
+ response_format: responseFormat,
49
+ };
50
+ }
51
+ }
52
+
53
+ const meaningsPrompt = (term) => {
54
+ return `${onlyJSONStringArray}
55
+ List all distinct dictionary meanings or common uses of "${term}".
56
+ Return a JSON object with a "meanings" array containing the distinct meanings.
57
+ ${onlyJSONStringArray}`;
58
+ };
59
+
60
+ export const getMeanings = async (term, config = {}) => {
61
+ const { model = modelService.getBestPublicModel(), llm, ...options } = config;
62
+ const prompt = meaningsPrompt(term);
63
+ const budget = model.budgetTokens(prompt);
64
+ const modelOptions = await createModelOptions(llm);
65
+ const response = await chatGPT(prompt, {
66
+ maxTokens: budget.completion,
67
+ modelOptions,
68
+ ...options,
69
+ });
70
+
71
+ // With structured outputs, response should already be parsed and validated
72
+ const parsed = typeof response === 'string' ? JSON.parse(response) : response;
73
+ // Extract meanings from the object structure
74
+ const resultArray = parsed?.meanings || parsed;
75
+ return Array.isArray(resultArray) ? resultArray.filter(Boolean) : [];
76
+ };
77
+
78
+ export default async function disambiguate({
79
+ term,
80
+ context,
81
+ model = modelService.getBestPublicModel(),
82
+ ...config
83
+ } = {}) {
84
+ const { llm, ...options } = config;
85
+ const meanings = await getMeanings(term, { model, llm, ...options });
86
+ const best = await listFilter(
87
+ meanings,
88
+ `the meaning of "${term}" in context: ${context}. Keep only the single best matching meaning.`,
89
+ { llm, ...options }
90
+ );
91
+ return { meaning: best[0], meanings };
92
+ }
@@ -0,0 +1,25 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import disambiguate from './index.js';
3
+
4
+ vi.mock('../../lib/chatgpt/index.js', () => ({
5
+ default: vi.fn(async (prompt) => {
6
+ if (/List all distinct dictionary meanings/.test(prompt)) {
7
+ return '["financial institution","edge of a river"]';
8
+ }
9
+ return '[]';
10
+ }),
11
+ }));
12
+
13
+ vi.mock('../../verblets/list-filter/index.js', () => ({
14
+ default: vi.fn(async (list) => {
15
+ return [list[0]];
16
+ }),
17
+ }));
18
+
19
+ describe('disambiguate chain', () => {
20
+ it('selects meaning based on context', async () => {
21
+ const result = await disambiguate({ term: 'bank', context: 'withdraw money' });
22
+ expect(result.meaning).toBe('financial institution');
23
+ expect(result.meanings).toStrictEqual(['financial institution', 'edge of a river']);
24
+ });
25
+ });
@@ -0,0 +1,67 @@
1
+ # dismantle
2
+
3
+ Break down complex systems into a tree of components using an LLM. `dismantle` creates a `ChainTree` that you can grow to any depth or inspect piece by piece.
4
+
5
+ ## Example
6
+
7
+ ```javascript
8
+ import { dismantle, simplifyTree } from './index.js';
9
+
10
+ // Break down complex systems into components
11
+ const chain = dismantle('AirPods Pro');
12
+ await chain.makeSubtree({ depth: 1 });
13
+ console.log(simplifyTree(chain.getTree()));
14
+ /* Returns:
15
+ {
16
+ id: '...',
17
+ name: 'AirPods Pro',
18
+ parts: [
19
+ { id: '...', name: 'H2 Chip' },
20
+ { id: '...', name: 'Drivers: custom_dynamic' },
21
+ { id: '...', name: 'Noise Sensors' },
22
+ { id: '...', name: 'Battery: lithium_ion' }
23
+ ]
24
+ }
25
+ */
26
+ ```
27
+
28
+ ## API
29
+
30
+ ### `dismantle(name, [options])`
31
+ Returns a `ChainTree` for `name`. `options` lets you override how components are discovered:
32
+
33
+ - `decompose(component)` – return an array of subcomponent names.
34
+ ```javascript
35
+ const chain = dismantle('Web App', {
36
+ decompose: async ({ name }) => fetch(`/api/parts?for=${name}`).then(r => r.json())
37
+ });
38
+ ```
39
+ - `enhance(component)` – enrich a node with metadata or known variants.
40
+ ```javascript
41
+ const chain = dismantle('Server', {
42
+ enhance: async ({ name }) => ({ name, options: await lookUpVariants(name) })
43
+ });
44
+ ```
45
+ - `makeId()` – create unique IDs (defaults to `uuid.v4`).
46
+ - `enhanceFixes` – text appended to LLM prompts used by `enhance`.
47
+ ```javascript
48
+ const chain = dismantle('Bike', { enhanceFixes: 'Prefer standard part names.' });
49
+ ```
50
+ - `decomposeFixes` – text appended to prompts used by `decompose`.
51
+
52
+ ### `ChainTree`
53
+ Returned by `dismantle`. Methods:
54
+
55
+ - `makeSubtree({ depth })` – grow the tree from the root for a number of levels.
56
+ ```javascript
57
+ await chain.makeSubtree({ depth: 2 });
58
+ ```
59
+ - `attachSubtree({ find, depth })` – expand a node that matches a predicate.
60
+ ```javascript
61
+ await chain.attachSubtree({ find: (n) => n.name === 'Battery', depth: 1 });
62
+ ```
63
+ - `getTree()` – access the internal detailed tree.
64
+
65
+ ### `simplifyTree(node)`
66
+ Convert a detailed node or tree into `{ id, name, parts }` for easy consumption.
67
+