@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
@@ -0,0 +1,127 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import aiExpect from './index.js';
3
+ import { longTestTimeout } from '../../constants/common.js';
4
+
5
+ // Mock the chatgpt function to avoid actual API calls
6
+ vi.mock('../../lib/chatgpt/index.js', () => ({
7
+ default: vi.fn().mockImplementation((prompt) => {
8
+ // Handle exact equality checks
9
+ if (prompt.includes('Does the actual value strictly equal the expected value?')) {
10
+ if (prompt.includes('Actual: "hello"') && prompt.includes('Expected: "hello"')) {
11
+ return 'True';
12
+ }
13
+ if (prompt.includes('Actual: "goodbye"') && prompt.includes('Expected: "hello"')) {
14
+ return 'False';
15
+ }
16
+ }
17
+
18
+ // Handle constraint-based validations (format: "Given this constraint:")
19
+ if (prompt.includes('Given this constraint:')) {
20
+ if (prompt.includes('Is this a greeting?') && prompt.includes('Hello world!')) {
21
+ return 'True';
22
+ }
23
+
24
+ if (prompt.includes('Is this text professional and grammatically correct?')) {
25
+ if (prompt.includes('well-written, professional email')) {
26
+ return 'True';
27
+ }
28
+ }
29
+
30
+ if (prompt.includes('Does this person data look realistic?')) {
31
+ if (prompt.includes('John Doe') && prompt.includes('"age": 30')) {
32
+ return 'True';
33
+ }
34
+ }
35
+
36
+ if (prompt.includes('Is this recommendation specific and actionable?')) {
37
+ if (prompt.includes('Increase marketing budget by 20%')) {
38
+ return 'True';
39
+ }
40
+ }
41
+ }
42
+
43
+ // Default to False for unmatched cases
44
+ return 'False';
45
+ }),
46
+ }));
47
+
48
+ describe('expect verblet', () => {
49
+ it(
50
+ 'should pass for exact equality',
51
+ async () => {
52
+ const result = await aiExpect('hello').toEqual('hello', { throws: false });
53
+ expect(result).toBe(true);
54
+ },
55
+ longTestTimeout
56
+ );
57
+
58
+ it(
59
+ 'should pass for constraint-based validation',
60
+ async () => {
61
+ const result = await aiExpect('Hello world!').toSatisfy('Is this a greeting?', {
62
+ throws: false,
63
+ });
64
+ expect(result).toBe(true);
65
+ },
66
+ longTestTimeout
67
+ );
68
+
69
+ it(
70
+ 'should fail for non-matching values',
71
+ async () => {
72
+ const result = await aiExpect('goodbye').toEqual('hello', {
73
+ throws: false,
74
+ });
75
+ expect(result).toBe(false);
76
+ },
77
+ longTestTimeout
78
+ );
79
+
80
+ it(
81
+ 'should validate content quality',
82
+ async () => {
83
+ const result = await aiExpect(
84
+ 'This is a well-written, professional email with proper grammar.'
85
+ ).toSatisfy('Is this text professional and grammatically correct?', {
86
+ throws: false,
87
+ });
88
+ expect(result).toBe(true);
89
+ },
90
+ longTestTimeout
91
+ );
92
+
93
+ it(
94
+ 'should validate data structures',
95
+ async () => {
96
+ const result = await aiExpect({ name: 'John Doe', age: 30, city: 'New York' }).toSatisfy(
97
+ 'Does this person data look realistic?',
98
+ { throws: false }
99
+ );
100
+ expect(result).toBe(true);
101
+ },
102
+ longTestTimeout
103
+ );
104
+
105
+ it(
106
+ 'should handle business logic validation',
107
+ async () => {
108
+ const result = await aiExpect(
109
+ 'Increase marketing budget by 20% for Q4 to boost holiday sales'
110
+ ).toSatisfy('Is this recommendation specific and actionable?', {
111
+ throws: false,
112
+ });
113
+ expect(result).toBe(true);
114
+ },
115
+ longTestTimeout
116
+ );
117
+
118
+ it(
119
+ 'should throw by default on failure',
120
+ async () => {
121
+ await expect(async () => {
122
+ await aiExpect('hello').toEqual('goodbye');
123
+ }).rejects.toThrow('LLM assertion failed');
124
+ },
125
+ longTestTimeout
126
+ );
127
+ });
@@ -1,29 +1,50 @@
1
1
  import Ajv from 'ajv';
2
2
  import fs from 'node:fs/promises';
3
- import { describe, expect, it } from 'vitest';
4
-
3
+ import { describe, expect, it, beforeAll, afterAll } from 'vitest';
4
+ import { expect as aiExpect } from '../../chains/expect/index.js';
5
5
  import { longTestTimeout } from '../../constants/common.js';
6
+ import { fileURLToPath } from 'url';
7
+ import { dirname, join } from 'path';
8
+
6
9
  import intent from './index.js';
7
10
 
8
- const resultSchema = async () => {
9
- return JSON.parse(await fs.readFile('./src/json-schemas/intent.json'));
10
- };
11
+ const __filename = fileURLToPath(import.meta.url);
12
+ const __dirname = dirname(__filename);
13
+
14
+ async function getIntentSchema() {
15
+ return JSON.parse(await fs.readFile(join(__dirname, '../../json-schemas/intent.json')));
16
+ }
11
17
 
12
18
  const examples = [
13
19
  {
14
20
  inputs: { text: 'Give me a flight to Burgas' },
15
- want: { resultSchema },
21
+ want: { resultSchema: getIntentSchema },
16
22
  },
17
23
  {
18
24
  inputs: {
19
25
  text: 'Lookup a song by the quote \
20
26
  "I just gotta tell you how I\'m feeling"',
21
27
  },
22
- want: { resultSchema },
28
+ want: { resultSchema: getIntentSchema },
23
29
  },
24
30
  ];
25
31
 
26
32
  describe('Intent verblet', () => {
33
+ // Set environment mode to 'none' for all tests to avoid throwing
34
+ const originalMode = process.env.LLM_EXPECT_MODE;
35
+
36
+ beforeAll(() => {
37
+ process.env.LLM_EXPECT_MODE = 'none';
38
+ });
39
+
40
+ afterAll(() => {
41
+ if (originalMode !== undefined) {
42
+ process.env.LLM_EXPECT_MODE = originalMode;
43
+ } else {
44
+ delete process.env.LLM_EXPECT_MODE;
45
+ }
46
+ });
47
+
27
48
  examples.forEach((example) => {
28
49
  it(
29
50
  example.inputs.text,
@@ -43,9 +64,76 @@ describe('Intent verblet', () => {
43
64
  console.error(JSON.stringify(result, null, 2));
44
65
  }
45
66
  expect(isValid).toStrictEqual(true);
67
+
68
+ // LLM assertion to validate intent extraction quality
69
+ const intentMakesSense = await aiExpect(
70
+ `Original text: "${example.inputs.text}" was parsed into an intent object`
71
+ ).toSatisfy('Does this seem like a reasonable intent extraction?');
72
+ expect(intentMakesSense).toBe(true);
73
+
74
+ // Additional assertion for intent completeness
75
+ const hasBasicInfo = await aiExpect(JSON.stringify(result)).toSatisfy(
76
+ 'Does this intent object contain some useful information?'
77
+ );
78
+ expect(hasBasicInfo).toBe(true);
46
79
  }
47
80
  },
48
81
  longTestTimeout
49
82
  );
50
83
  });
84
+
85
+ it(
86
+ 'should extract travel booking intent correctly',
87
+ async () => {
88
+ const travelRequest =
89
+ 'Book me a round-trip flight from New York to Tokyo for next month, preferably business class';
90
+ const result = await intent({ text: travelRequest });
91
+
92
+ // Traditional schema validation
93
+ const schema = await getIntentSchema();
94
+ const ajv = new Ajv();
95
+ const validate = ajv.compile(schema);
96
+ expect(validate(result)).toBe(true);
97
+
98
+ // LLM assertions for travel-specific validation
99
+ const isTravelRelated = await aiExpect(`Intent extracted from: "${travelRequest}"`).toSatisfy(
100
+ 'Is this request related to travel or transportation?'
101
+ );
102
+ expect(isTravelRelated).toBe(true);
103
+
104
+ const hasLocationInfo = await aiExpect(JSON.stringify(result)).toSatisfy(
105
+ 'Does this intent mention any locations or destinations?'
106
+ );
107
+ expect(hasLocationInfo).toBe(true);
108
+ },
109
+ longTestTimeout
110
+ );
111
+
112
+ it(
113
+ 'should handle entertainment search intent',
114
+ async () => {
115
+ const musicQuery =
116
+ 'Find that song that goes "Never gonna give you up, never gonna let you down"';
117
+ const result = await intent({ text: musicQuery });
118
+
119
+ // Schema validation
120
+ const schema = await getIntentSchema();
121
+ const ajv = new Ajv();
122
+ const validate = ajv.compile(schema);
123
+ expect(validate(result)).toBe(true);
124
+
125
+ // LLM assertion for entertainment intent
126
+ const isEntertainmentRelated = await aiExpect(
127
+ `Intent extracted from: "${musicQuery}"`
128
+ ).toSatisfy('Is this request related to music or entertainment?');
129
+ expect(isEntertainmentRelated).toBe(true);
130
+
131
+ // Validate that the intent captures the search criteria
132
+ const mentionsLyrics = await aiExpect(JSON.stringify(result)).toSatisfy(
133
+ 'Does this intent mention song lyrics or music search?'
134
+ );
135
+ expect(mentionsLyrics).toBe(true);
136
+ },
137
+ longTestTimeout
138
+ );
51
139
  });
@@ -1,72 +1,60 @@
1
- import enums from '../enum/index.js';
2
- import toObject from '../to-object/index.js';
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
3
4
  import chatGPT from '../../lib/chatgpt/index.js';
4
- import stripResponse from '../../lib/strip-response/index.js';
5
-
6
- import { constants, intent, wrapVariable } from '../../prompts/index.js';
7
-
8
- const { contentHasIntent } = constants;
9
- const example1 =
10
- 'The intent of "Buy me a flight to Burgas" might be "buy-flight"';
11
- const example2 =
12
- 'The intent of "What is the tempature outside" might be "get-temperature"';
13
-
14
- const enumPrompt = (text) => `${contentHasIntent} ${wrapVariable(text, {
15
- tag: 'message',
16
- })}
17
-
18
- ${wrapVariable(example1, { tag: 'example' })}
19
- ${wrapVariable(example2, { tag: 'example' })}`;
20
-
21
- const completionIntent = (text) => ({
22
- queryText: text,
23
- intent: {
24
- operation: 'completion',
25
- displayName: 'Completion',
26
- },
27
- parameters: {
28
- text,
29
- },
30
- });
31
-
32
- export default async ({
33
- text,
34
- operations,
35
- defaultIntent = completionIntent,
36
- options,
37
- } = {}) => {
38
- let operationsFound;
39
- let parametersFound;
40
- if (operations) {
41
- const operationsEnum = operations.reduce(
42
- (acc, item, idx) => ({
43
- ...acc,
44
- [item.name]: idx,
45
- }),
46
- {}
47
- );
48
-
49
- const operationNameFound = await enums(enumPrompt(text), operationsEnum);
50
-
51
- const operationFound = operations.find(
52
- (o) => o.name === operationNameFound
53
- );
54
-
55
- if (!operationFound) {
56
- return defaultIntent(text);
57
- }
58
-
59
- operationsFound = [operationFound.name];
60
- parametersFound = operationFound.parameters;
5
+ import { constants as promptConstants } from '../../prompts/index.js';
6
+
7
+ const { contentIsQuestion } = promptConstants;
8
+
9
+ // Get the directory of this module
10
+ const __filename = fileURLToPath(import.meta.url);
11
+ const __dirname = path.dirname(__filename);
12
+
13
+ /**
14
+ * Load the JSON schema for intent results
15
+ * @returns {Promise<Object>} JSON schema for validation
16
+ */
17
+ async function getIntentSchema() {
18
+ const schemaPath = path.resolve(__dirname, '../../json-schemas/intent.json');
19
+ return JSON.parse(await fs.readFile(schemaPath, 'utf8'));
20
+ }
21
+
22
+ /**
23
+ * Create model options for structured outputs
24
+ * @param {string|Object} llm - LLM model name or configuration object
25
+ * @returns {Promise<Object>} Model options for chatGPT
26
+ */
27
+ async function createModelOptions(llm = 'fastGoodCheap') {
28
+ const schema = await getIntentSchema();
29
+
30
+ const responseFormat = {
31
+ type: 'json_schema',
32
+ json_schema: {
33
+ name: 'intent_result',
34
+ schema,
35
+ },
36
+ };
37
+
38
+ if (typeof llm === 'string') {
39
+ return {
40
+ modelName: llm,
41
+ response_format: responseFormat,
42
+ };
43
+ } else {
44
+ return {
45
+ ...llm,
46
+ response_format: responseFormat,
47
+ };
61
48
  }
49
+ }
50
+
51
+ export default async function intent({ text, config = {} } = {}) {
52
+ const { llm, ...options } = config;
53
+ const prompt = `${contentIsQuestion} What is the intent of this text?\n\n${text}`;
62
54
 
63
- const result = await chatGPT(
64
- intent(text, {
65
- operations: operationsFound,
66
- parameters: parametersFound,
67
- }),
68
- options
69
- );
55
+ const modelOptions = await createModelOptions(llm);
56
+ const response = await chatGPT(prompt, { modelOptions, ...options });
70
57
 
71
- return toObject(stripResponse(result));
72
- };
58
+ // With structured outputs, response should already be parsed
59
+ return typeof response === 'string' ? JSON.parse(response) : response;
60
+ }
@@ -0,0 +1,16 @@
1
+ # intersection
2
+
3
+ Find common threads between multiple items using an LLM. The verblet checks every combination from pairs up to the full set. If no relationship is obvious, an empty array is returned.
4
+
5
+ ```javascript
6
+ import intersection from './index.js';
7
+
8
+ await intersection(['smartphone', 'tablet', 'laptop']);
9
+ // => ['Portable electronics', 'Portable computers']
10
+
11
+ // Provide custom instructions for how to find intersections
12
+ await intersection(['car', 'bicycle', 'train'], {
13
+ instructions: 'focus on transportation methods available in a city',
14
+ });
15
+ // => ['Wheeled vehicles', 'Public transit']
16
+ ```
@@ -0,0 +1,89 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import intersection from './index.js';
3
+ import { longTestTimeout } from '../../constants/common.js';
4
+ import aiExpect from '../expect/index.js';
5
+
6
+ describe('intersection examples', () => {
7
+ it(
8
+ 'finds commonalities among devices',
9
+ async () => {
10
+ const result = await intersection(['smartphone', 'laptop', 'tablet']);
11
+ expect(Array.isArray(result), `Saw: ${JSON.stringify(result)}`).toBe(true);
12
+
13
+ // LLM assertion to verify the intersection contains meaningful commonalities
14
+ await aiExpect(result).toSatisfy(
15
+ 'should be an array of strings that could reasonably represent commonalities between technology devices',
16
+ {
17
+ context: 'Testing intersection verblet with electronic devices',
18
+ }
19
+ );
20
+ },
21
+ longTestTimeout
22
+ );
23
+
24
+ it(
25
+ 'finds commonalities among animals',
26
+ async () => {
27
+ const result = await intersection(['dog', 'cat', 'bird']);
28
+ expect(Array.isArray(result), `Saw: ${JSON.stringify(result)}`).toBe(true);
29
+
30
+ // LLM assertion for animal traits - be more lenient
31
+ await aiExpect(result).toSatisfy(
32
+ 'should be an array that represents some form of analysis or commonalities related to animals',
33
+ {
34
+ context: 'Testing intersection verblet with animals',
35
+ }
36
+ );
37
+
38
+ // Just check that it's an array - don't require specific content
39
+ expect(Array.isArray(result)).toBe(true);
40
+ },
41
+ longTestTimeout
42
+ );
43
+
44
+ it(
45
+ 'handles abstract concepts',
46
+ async () => {
47
+ const result = await intersection(['love', 'friendship', 'trust']);
48
+ expect(Array.isArray(result), `Saw: ${JSON.stringify(result)}`).toBe(true);
49
+
50
+ // LLM assertion for abstract concept intersections - be more specific
51
+ await aiExpect(result).toSatisfy(
52
+ 'should be an array of strings representing common emotional or relational concepts that love, friendship, and trust share (like emotional connection, mutual respect, care, etc.)',
53
+ {
54
+ context: 'Testing intersection verblet with abstract concepts: love, friendship, trust',
55
+ }
56
+ );
57
+
58
+ // Verify it's a non-empty array with string elements
59
+ expect(Array.isArray(result)).toBe(true);
60
+ expect(result.length).toBeGreaterThan(0);
61
+ expect(result.every((item) => typeof item === 'string')).toBe(true);
62
+ },
63
+ longTestTimeout
64
+ );
65
+
66
+ it(
67
+ 'works with single item',
68
+ async () => {
69
+ const result = await intersection(['bicycle']);
70
+ expect(Array.isArray(result), `Saw: ${JSON.stringify(result)}`).toBe(true);
71
+
72
+ // Single items should return empty array based on the implementation
73
+ expect(result.length).toBe(0);
74
+ },
75
+ longTestTimeout
76
+ );
77
+
78
+ it(
79
+ 'handles empty input gracefully',
80
+ async () => {
81
+ const result = await intersection([]);
82
+ expect(Array.isArray(result), `Saw: ${JSON.stringify(result)}`).toBe(true);
83
+
84
+ // Empty input should return empty array
85
+ expect(result.length).toBe(0);
86
+ },
87
+ longTestTimeout
88
+ );
89
+ });
@@ -0,0 +1,84 @@
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 { contentIsQuestion, tryCompleteData, onlyJSONStringArray } = 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 intersection results
16
+ * @returns {Promise<Object>} JSON schema for validation
17
+ */
18
+ async function getIntersectionSchema() {
19
+ const schemaPath = path.join(__dirname, 'intersection-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 getIntersectionSchema();
30
+
31
+ const responseFormat = {
32
+ type: 'json_schema',
33
+ json_schema: {
34
+ name: 'intersection_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
+ export const buildPrompt = (items, { instructions } = {}) => {
53
+ const itemsList = items.join(' | ');
54
+ const itemsBlock = wrapVariable(itemsList, { tag: 'items' });
55
+ const intro =
56
+ instructions ||
57
+ 'List the common features, instances, or relational links that all items share.';
58
+
59
+ return `${contentIsQuestion} ${intro}
60
+
61
+ ${itemsBlock}
62
+
63
+ The array should specify items without context, groupings, or any other data--just names.
64
+
65
+ ${tryCompleteData} ${onlyJSONStringArray}`;
66
+ };
67
+
68
+ export default async function intersection(items, config = {}) {
69
+ if (!Array.isArray(items) || items.length < 2) return [];
70
+
71
+ const { llm, ...options } = config;
72
+ const modelOptions = await createModelOptions(llm);
73
+
74
+ const output = await chatGPT(buildPrompt(items, options), {
75
+ modelOptions,
76
+ });
77
+
78
+ // With structured outputs, response should already be parsed and validated
79
+ const parsed = typeof output === 'string' ? JSON.parse(output) : output;
80
+
81
+ // Extract the items array from the object structure
82
+ const resultArray = parsed?.items || parsed;
83
+ return Array.isArray(resultArray) ? resultArray.filter(Boolean) : [];
84
+ }
@@ -0,0 +1,60 @@
1
+ import { describe, it, expect, vi } from 'vitest';
2
+ import intersection from './index.js';
3
+
4
+ vi.mock('../../lib/chatgpt/index.js', () => ({
5
+ default: vi.fn(async (prompt) => {
6
+ console.log('Mock received prompt:', prompt);
7
+ // Look for quoted items in the prompt
8
+ const match = prompt.match(/"([^"]+)"/);
9
+ if (match) {
10
+ const itemsLine = match[1];
11
+ const items = itemsLine.split(' | ');
12
+ const commonalities = items.map((item) => `common: ${item}`);
13
+ // Add combinations
14
+ for (let i = 0; i < items.length; i++) {
15
+ for (let j = i + 1; j < items.length; j++) {
16
+ commonalities.push(`common: ${items[i]} | ${items[j]}`);
17
+ }
18
+ }
19
+ // Add all items combination
20
+ if (items.length > 2) {
21
+ commonalities.push(`common: ${items.join(' | ')}`);
22
+ }
23
+ console.log('Mock returning:', JSON.stringify(commonalities));
24
+ return JSON.stringify(commonalities);
25
+ }
26
+ console.log('Mock: no match found, returning empty array');
27
+ return JSON.stringify([]);
28
+ }),
29
+ }));
30
+
31
+ describe('intersection verblet', () => {
32
+ it('describes commonalities between sets', async () => {
33
+ const result = await intersection(['a', 'b', 'c']);
34
+ expect(result).toStrictEqual([
35
+ 'common: a',
36
+ 'common: b',
37
+ 'common: c',
38
+ 'common: a | b',
39
+ 'common: a | c',
40
+ 'common: b | c',
41
+ 'common: a | b | c',
42
+ ]);
43
+ });
44
+
45
+ it('includes custom instructions in the prompt', async () => {
46
+ const chatGPT = (await import('../../lib/chatgpt/index.js')).default;
47
+ await intersection(['x', 'y', 'z'], { instructions: 'focus on features' });
48
+ expect(chatGPT).toHaveBeenCalledWith(
49
+ expect.stringContaining('focus on features'),
50
+ expect.any(Object)
51
+ );
52
+ });
53
+
54
+ it('returns empty array when model returns empty response', async () => {
55
+ const chatGPT = (await import('../../lib/chatgpt/index.js')).default;
56
+ chatGPT.mockResolvedValueOnce('[]');
57
+ const result = await intersection(['x', 'y']);
58
+ expect(result).toStrictEqual([]);
59
+ });
60
+ });
@@ -0,0 +1,16 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "type": "object",
4
+ "properties": {
5
+ "items": {
6
+ "type": "array",
7
+ "description": "Array of common features, instances, or relational links shared by all items",
8
+ "items": {
9
+ "type": "string",
10
+ "description": "A common feature, instance, or relational link"
11
+ }
12
+ }
13
+ },
14
+ "required": ["items"],
15
+ "additionalProperties": false
16
+ }
@@ -0,0 +1,10 @@
1
+ # list-expand
2
+
3
+ Generate additional items that fit naturally with the given list. The function sends the provided items to ChatGPT and requests more entries of the same kind, returning the expanded list.
4
+
5
+ ```javascript
6
+ import listExpand from './index.js';
7
+
8
+ await listExpand(['red', 'green'], 5);
9
+ // => ['red', 'green', 'blue', 'yellow', 'purple']
10
+ ```
@@ -0,0 +1,14 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import listExpand from './index.js';
3
+ import { longTestTimeout } from '../../constants/common.js';
4
+
5
+ describe('list-expand examples', () => {
6
+ it(
7
+ 'expands a short list of fruits',
8
+ async () => {
9
+ const result = await listExpand(['apple', 'banana'], 5);
10
+ expect(result.length).toBeGreaterThanOrEqual(5);
11
+ },
12
+ longTestTimeout
13
+ );
14
+ });