@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,27 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import listReduce from './index.js';
3
+
4
+ vi.mock('../../lib/chatgpt/index.js', () => ({
5
+ default: vi.fn((prompt) => {
6
+ const listMatch = prompt.match(/<list>\n([\s\S]*?)\n<\/list>/);
7
+ const accMatch = prompt.match(/<accumulator>\n([\s\S]*?)\n<\/accumulator>/);
8
+
9
+ if (listMatch && accMatch) {
10
+ const acc = accMatch[1].trim();
11
+ const lines = listMatch[1]
12
+ .split('\n')
13
+ .map((line) => line.trim())
14
+ .filter(Boolean);
15
+ return `${acc}+${lines.join('+')}`;
16
+ }
17
+ // Return the joined result as expected
18
+ return [acc, ...lines].join('+');
19
+ }),
20
+ }));
21
+
22
+ describe('list-reduce verblet', () => {
23
+ it('reduces items using instructions', async () => {
24
+ const result = await listReduce('0', ['a', 'b', 'c'], 'join');
25
+ expect(result).toBe('0+a+b+c');
26
+ });
27
+ });
@@ -0,0 +1,15 @@
1
+ # name
2
+ Generate a short, descriptive name for any text or concept.
3
+
4
+ This verblet taps into the language model's understanding of nuance to create names that capture the essence of your content. Use it whenever a simple keyword search isn't enough and you want an evocative title.
5
+
6
+ ## Usage
7
+
8
+ ```javascript
9
+ import { name } from '@far-world-labs';
10
+
11
+ const diaryTitle = await name(
12
+ 'Voice memos from friends sharing their hopes and worries'
13
+ );
14
+ console.log(diaryTitle); // "Shared Reflections" (example)
15
+ ```
@@ -0,0 +1,28 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { longTestTimeout } from '../../constants/common.js';
3
+ import name from './index.js';
4
+
5
+ const examples = [
6
+ { got: { text: 'Chat logs for customer support' }, want: 'chatSupportLogs' },
7
+ { got: { text: 'Sensor readings from smart home devices' }, want: 'smartHomeSensorReadings' },
8
+ {
9
+ got: {
10
+ text: 'Voice memos from friends sharing their hopes and worries',
11
+ },
12
+ want: 'voiceMemos',
13
+ },
14
+ ];
15
+
16
+ describe('name verblet', () => {
17
+ examples.forEach((example) => {
18
+ it(
19
+ example.got.text,
20
+ async () => {
21
+ const result = await name(example.got.text);
22
+ expect(typeof result).toBe('string');
23
+ expect(result.length).toBeGreaterThan(0);
24
+ },
25
+ longTestTimeout
26
+ );
27
+ });
28
+ });
@@ -0,0 +1,19 @@
1
+ import chatGPT from '../../lib/chatgpt/index.js';
2
+ import stripResponse from '../../lib/strip-response/index.js';
3
+ import wrapVariable from '../../prompts/wrap-variable.js';
4
+ import { constants as promptConstants } from '../../prompts/index.js';
5
+
6
+ const { asUndefinedByDefault, contentIsQuestion } = promptConstants;
7
+
8
+ export default async function name(subject, config = {}) {
9
+ const { llm, ...options } = config;
10
+ const prompt = `${contentIsQuestion} Suggest a concise, memorable name for the <subject>.\n\n${wrapVariable(
11
+ subject,
12
+ {
13
+ tag: 'subject',
14
+ }
15
+ )} ${asUndefinedByDefault}`;
16
+ const response = await chatGPT(prompt, { modelOptions: { ...llm }, ...options });
17
+ const [firstLine] = stripResponse(response).split('\n');
18
+ return firstLine.trim();
19
+ }
@@ -0,0 +1,33 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+
3
+ import name from './index.js';
4
+
5
+ vi.mock('../../lib/chatgpt/index.js', () => ({
6
+ default: vi.fn().mockImplementation((text) => {
7
+ if (/weather pattern/i.test(text)) {
8
+ return 'BlueSkies';
9
+ }
10
+ return 'undefined';
11
+ }),
12
+ }));
13
+
14
+ const examples = [
15
+ {
16
+ name: 'Generates descriptive name',
17
+ inputs: { text: 'Dataset of weather pattern observations' },
18
+ want: { result: 'BlueSkies' },
19
+ },
20
+ {
21
+ name: 'Returns undefined when unsure',
22
+ inputs: { text: '???' },
23
+ want: { result: 'undefined' },
24
+ },
25
+ ];
26
+
27
+ describe('name verblet', () => {
28
+ examples.forEach((example) => {
29
+ it(example.name, async () => {
30
+ expect(await name(example.inputs.text)).toStrictEqual(example.want.result);
31
+ });
32
+ });
33
+ });
@@ -0,0 +1,26 @@
1
+ # name-similar-to
2
+
3
+ Generate a short, catchy name for something using the style of existing names. This verblet relies on a language model to capture the tone and brevity of your sample names.
4
+
5
+ ```javascript
6
+ import nameSimilarTo from './index.js';
7
+
8
+ const newName = await nameSimilarTo(
9
+ 'dataset of daily coffee tasting notes',
10
+ ['BeanDiary', 'RoastLog', 'BrewIndex']
11
+ );
12
+ // => 'TastingLog'
13
+ ```
14
+
15
+ ## Example: naming a hiking journal
16
+
17
+ Imagine cataloging your weekend trail adventures. You already use names like `TrailNotes`, `GearTips`, and `CampfireStories`. Ask the verblet to suggest a matching name for your wildlife sightings log:
18
+
19
+ ```javascript
20
+ const exampleNames = ['TrailNotes', 'GearTips', 'CampfireStories'];
21
+ const wildlifeLog = await nameSimilarTo(
22
+ 'journal of wildlife spotted on each hike',
23
+ exampleNames
24
+ );
25
+ // => 'WildlifeWatch'
26
+ ```
@@ -0,0 +1,18 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import nameSimilarTo from './index.js';
3
+ import { longTestTimeout } from '../../constants/common.js';
4
+
5
+ describe('nameSimilarTo examples', () => {
6
+ it(
7
+ 'suggests a matching name',
8
+ async () => {
9
+ const result = await nameSimilarTo('record of coffee tasting notes', [
10
+ 'BeanDiary',
11
+ 'RoastLog',
12
+ 'BrewIndex',
13
+ ]);
14
+ expect(typeof result).toBe('string');
15
+ },
16
+ longTestTimeout
17
+ );
18
+ });
@@ -0,0 +1,20 @@
1
+ import chatGPT from '../../lib/chatgpt/index.js';
2
+ import wrapVariable from '../../prompts/wrap-variable.js';
3
+
4
+ const buildPrompt = (description, exampleNames) => {
5
+ const descriptionBlock = wrapVariable(description, { tag: 'description' });
6
+ const exampleNamesBlock = wrapVariable(exampleNames.join('\n'), { tag: 'example-names' });
7
+
8
+ return `Generate a name similar to the <example-names> that fits the <description>. Return only the name, nothing else.
9
+
10
+ ${descriptionBlock}
11
+
12
+ ${exampleNamesBlock}`;
13
+ };
14
+
15
+ export default async function nameSimilarTo(description, exampleNames = [], config = {}) {
16
+ const { llm, ...options } = config;
17
+ const prompt = buildPrompt(description, exampleNames);
18
+ const output = await chatGPT(prompt, { modelOptions: { ...llm }, ...options });
19
+ return output.split('\n')[0].trim();
20
+ }
@@ -0,0 +1,13 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import nameSimilarTo from './index.js';
3
+
4
+ vi.mock('../../lib/chatgpt/index.js', () => ({
5
+ default: vi.fn(async () => 'freshName'),
6
+ }));
7
+
8
+ describe('nameSimilarTo verblet', () => {
9
+ it('generates a name matching the example style', async () => {
10
+ const result = await nameSimilarTo('some data about sales', ['salesStats', 'revenueReport']);
11
+ expect(result).toBe('freshName');
12
+ });
13
+ });
@@ -1,16 +1,17 @@
1
- import { describe, expect, it } from 'vitest';
1
+ import { describe, expect, it, beforeAll, afterAll } from 'vitest';
2
2
 
3
3
  import number from './index.js';
4
+ import aiExpect from '../expect/index.js';
4
5
  import { longTestTimeout } from '../../constants/common.js';
5
6
 
6
7
  const examples = [
7
8
  {
8
9
  inputs: { text: 'What is the height of Everest in feet' },
9
- want: { result: 29029 },
10
+ want: { range: [29000, 29100] }, // Tolerant range around 29032
10
11
  },
11
12
  {
12
13
  inputs: { text: 'What is the length of the Nile in km' },
13
- want: { result: 6650 },
14
+ want: { range: [6000, 7000] }, // Tolerant range around 6650
14
15
  },
15
16
  {
16
17
  inputs: { text: 'What is the my age in years' },
@@ -19,15 +20,180 @@ const examples = [
19
20
  ];
20
21
 
21
22
  describe('Number verblet', () => {
23
+ // Set environment mode to 'none' for all tests to avoid throwing
24
+ const originalMode = process.env.LLM_EXPECT_MODE;
25
+
26
+ beforeAll(() => {
27
+ process.env.LLM_EXPECT_MODE = 'none';
28
+ });
29
+
30
+ afterAll(() => {
31
+ if (originalMode !== undefined) {
32
+ process.env.LLM_EXPECT_MODE = originalMode;
33
+ } else {
34
+ delete process.env.LLM_EXPECT_MODE;
35
+ }
36
+ });
37
+
22
38
  examples.forEach((example) => {
23
39
  it(
24
- example.inputs.text,
40
+ `${example.inputs.text}`,
25
41
  async () => {
26
- expect(await number(example.inputs.text)).toStrictEqual(
27
- example.want.result
28
- );
42
+ const result = await number(example.inputs.text);
43
+
44
+ if (example.want.range) {
45
+ expect(result).toBeGreaterThanOrEqual(example.want.range[0]);
46
+ expect(result).toBeLessThanOrEqual(example.want.range[1]);
47
+
48
+ // LLM assertion for range validation
49
+ const isReasonableValue = await aiExpect(
50
+ `Question: "${example.inputs.text}" Answer: ${result}`
51
+ ).toSatisfy('Is this a reasonable numeric answer for a geographic question?');
52
+ expect(isReasonableValue).toBe(true);
53
+ } else if (example.want.result !== undefined) {
54
+ expect(result).toStrictEqual(example.want.result);
55
+ } else {
56
+ expect(result).toStrictEqual(example.want.result);
57
+
58
+ // LLM assertion for undefined results
59
+ if (example.want.result === undefined) {
60
+ const shouldBeUndefined = await aiExpect(
61
+ `Question: "${example.inputs.text}"`
62
+ ).toSatisfy('Does this question lack enough context to give a specific number?');
63
+ expect(shouldBeUndefined).toBe(true);
64
+ }
65
+ }
29
66
  },
30
67
  longTestTimeout
31
68
  );
32
69
  });
70
+
71
+ it(
72
+ 'should extract numbers from recipe contexts',
73
+ async () => {
74
+ const recipeText =
75
+ 'What temperature should I bake the cookies at? They need to be baked for 12 minutes until golden brown';
76
+ const result = await number(recipeText);
77
+
78
+ expect(typeof result).toBe('number');
79
+ expect(result).toBeGreaterThan(0);
80
+
81
+ // LLM assertion to validate recipe number extraction
82
+ const isCorrectBakeTime = await aiExpect(
83
+ `Recipe: "${recipeText}" Extracted number: ${result}`
84
+ ).toSatisfy('Is this number related to baking time or temperature?');
85
+ expect(isCorrectBakeTime).toBe(true);
86
+
87
+ // LLM assertion to validate temperature unit
88
+ const hasTemperatureUnit = await aiExpect(
89
+ `Recipe text: "${recipeText}" Extracted number: ${result}`
90
+ ).toSatisfy('Is this number likely a temperature in Fahrenheit (e.g. 350°F)?');
91
+ expect(hasTemperatureUnit).toBe(true);
92
+
93
+ // Additional assertion about reasonableness
94
+ const isReasonableBakeTime = await aiExpect(
95
+ `Extracted number: ${result} from a baking recipe`
96
+ ).toSatisfy('Is this a reasonable number for cooking?');
97
+ expect(isReasonableBakeTime).toBe(true);
98
+ },
99
+ longTestTimeout
100
+ );
101
+
102
+ it(
103
+ 'should handle financial calculations',
104
+ async () => {
105
+ const financialQuery =
106
+ 'If I invest $1000 at 5% annual compound interest for 10 years, how much will I have?';
107
+ const result = await number(financialQuery);
108
+
109
+ expect(typeof result).toBe('number');
110
+ expect(result).toBeGreaterThan(1000); // Should be more than principal
111
+
112
+ // LLM assertion for financial calculation accuracy
113
+ const isReasonableReturn = await aiExpect(
114
+ `Investment question about $1000 at 5% for 10 years. Answer: $${result}`
115
+ ).toSatisfy('Is this a reasonable amount for a 10-year investment?');
116
+ expect(isReasonableReturn).toBe(true);
117
+
118
+ // Validate the calculation makes financial sense
119
+ const followsCompoundInterest = await aiExpect(
120
+ `Starting with $1000, ending with $${result} after 10 years`
121
+ ).toSatisfy('Does this show reasonable investment growth?');
122
+ expect(followsCompoundInterest).toBe(true);
123
+ },
124
+ longTestTimeout
125
+ );
126
+
127
+ it(
128
+ 'should handle financial calculations with compound interest',
129
+ async () => {
130
+ const result = await number(
131
+ 'If I invest $1000 at 5% annual interest for 10 years, what will be the final amount?'
132
+ );
133
+
134
+ expect(typeof result).toBe('number');
135
+ // Simple interest would be 1000 + (1000 * 0.05 * 10) = 1500
136
+ expect(result).toBeGreaterThan(1500);
137
+
138
+ // LLM assertion for financial calculation accuracy
139
+ const isReasonableReturn = await aiExpect(
140
+ `Investment: $1000 at 5% for 10 years. Final amount: $${result}`
141
+ ).toSatisfy(
142
+ 'Is this a reasonable final amount for compound interest over 10 years? It should be significantly more than the principal due to compound growth.',
143
+ {
144
+ message: `Expected reasonable compound interest growth, but got: $${result}`,
145
+ }
146
+ );
147
+ expect(isReasonableReturn).toBe(true);
148
+
149
+ // LLM assertion for compound interest validation
150
+ const followsCompoundInterest = await aiExpect(
151
+ `Starting with $1000 at 5% annual interest for 10 years, the final amount is $${result}`
152
+ ).toSatisfy(
153
+ 'Does this final amount follow the expected pattern of compound interest growth? It should be more than simple interest (which would be $1500) and less than exponential growth.',
154
+ {
155
+ message: `Expected compound interest growth pattern, but got: $${result}`,
156
+ }
157
+ );
158
+ expect(followsCompoundInterest).toBe(true);
159
+ },
160
+ longTestTimeout
161
+ );
162
+
163
+ it(
164
+ 'should handle financial calculations with compound interest',
165
+ async () => {
166
+ const result = await number(
167
+ 'If I invest $1000 at 5% annual interest for 10 years, what will be the final amount?'
168
+ );
169
+
170
+ expect(typeof result).toBe('number');
171
+ // Simple interest would be 1000 + (1000 * 0.05 * 10) = 1500
172
+ // Compound interest should be more than simple interest
173
+ expect(result).toBeGreaterThan(1500);
174
+
175
+ // LLM assertion for financial calculation accuracy
176
+ const isReasonableReturn = await aiExpect(
177
+ `Investment: $1000 at 5% for 10 years. Final amount: $${result}`
178
+ ).toSatisfy(
179
+ 'Is this a reasonable final amount for compound interest over 10 years? It should be significantly more than the principal due to compound growth.',
180
+ {
181
+ message: `Expected reasonable compound interest growth, but got: $${result}`,
182
+ }
183
+ );
184
+ expect(isReasonableReturn).toBe(true);
185
+
186
+ // LLM assertion for compound interest validation
187
+ const followsCompoundInterest = await aiExpect(
188
+ `Starting with $1000 at 5% annual interest for 10 years, the final amount is $${result}`
189
+ ).toSatisfy(
190
+ 'Does this final amount follow the expected pattern of compound interest growth? It should be more than simple interest (which would be $1500) and less than exponential growth.',
191
+ {
192
+ message: `Expected compound interest growth pattern, but got: $${result}`,
193
+ }
194
+ );
195
+ expect(followsCompoundInterest).toBe(true);
196
+ },
197
+ longTestTimeout
198
+ );
33
199
  });
@@ -11,12 +11,15 @@ const {
11
11
  explainAndSeparatePrimitive,
12
12
  } = promptConstants;
13
13
 
14
- export default async (text) => {
14
+ export default async (text, config = {}) => {
15
+ const { llm, ...options } = config;
15
16
  const numberText = `${contentIsQuestion} ${text}
16
17
 
17
18
  ${explainAndSeparate} ${explainAndSeparatePrimitive}
18
19
 
19
20
  ${asNumber} ${asUndefinedByDefault}`;
20
21
 
21
- return toNumber(stripResponse(await chatGPT(numberText)));
22
+ return toNumber(
23
+ stripResponse(await chatGPT(numberText, { modelOptions: { ...llm }, ...options }))
24
+ );
22
25
  };
@@ -27,9 +27,7 @@ const examples = [
27
27
  describe('Number verblet', () => {
28
28
  examples.forEach((example) => {
29
29
  it(example.name, async () => {
30
- expect(await number(example.inputs.text)).toStrictEqual(
31
- example.want.result
32
- );
30
+ expect(await number(example.inputs.text)).toStrictEqual(example.want.result);
33
31
  });
34
32
  });
35
33
  });
@@ -6,7 +6,7 @@ import { longTestTimeout } from '../../constants/common.js';
6
6
  const examples = [
7
7
  {
8
8
  inputs: { text: 'What is the height of Everest in feet' },
9
- want: { value: 29029, unit: 'feet' },
9
+ want: { valueRange: [29029, 29032], unit: 'feet' },
10
10
  },
11
11
  {
12
12
  inputs: { text: 'What is my age in years' },
@@ -24,6 +24,10 @@ describe('Number with units verblet', () => {
24
24
  if (example.want.value) {
25
25
  expect(result?.value).toStrictEqual(example.want.value);
26
26
  }
27
+ if (example.want.valueRange) {
28
+ expect(result?.value).toBeGreaterThanOrEqual(example.want.valueRange[0]);
29
+ expect(result?.value).toBeLessThanOrEqual(example.want.valueRange[1]);
30
+ }
27
31
  if (example.want.unit) {
28
32
  expect(result?.unit).toStrictEqual(example.want.unit);
29
33
  }
@@ -1,19 +1,84 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
1
4
  import chatGPT from '../../lib/chatgpt/index.js';
2
- import stripResponse from '../../lib/strip-response/index.js';
3
5
  import toNumberWithUnits from '../../lib/to-number-with-units/index.js';
4
6
  import { constants as promptConstants } from '../../prompts/index.js';
5
7
 
6
- const {
7
- asNumberWithUnits,
8
- contentIsQuestion,
9
- explainAndSeparate,
10
- explainAndSeparateJSON,
11
- } = promptConstants;
8
+ const { asNumberWithUnits, contentIsQuestion, explainAndSeparate, explainAndSeparateJSON } =
9
+ promptConstants;
12
10
 
13
- export default async (text) => {
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 number with units results
17
+ * @returns {Promise<Object>} JSON schema for validation
18
+ */
19
+ async function getNumberWithUnitsSchema() {
20
+ const schemaPath = path.join(__dirname, 'number-with-units-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 getNumberWithUnitsSchema();
31
+
32
+ const responseFormat = {
33
+ type: 'json_schema',
34
+ json_schema: {
35
+ name: 'number_with_units_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
+ export default async (text, config = {}) => {
54
+ const { llm, ...options } = config;
14
55
  const numberText = `${contentIsQuestion} ${text} \n\n${explainAndSeparate} ${explainAndSeparateJSON}
15
56
 
57
+ Extract the numeric value and unit from the question. If you cannot determine the specific numeric value, set "value" to null but still identify the unit being asked for.
58
+
16
59
  ${asNumberWithUnits}`;
17
60
 
18
- return toNumberWithUnits(stripResponse(await chatGPT(numberText)));
61
+ const modelOptions = await createModelOptions(llm);
62
+ const response = await chatGPT(numberText, { modelOptions, ...options });
63
+
64
+ // With structured outputs, response should already be parsed, but we still use
65
+ // toNumberWithUnits for additional validation and processing
66
+ let rawResult;
67
+ if (typeof response === 'string') {
68
+ // Handle the case where response might be "undefined" or other non-JSON strings
69
+ if (response.trim() === 'undefined') {
70
+ rawResult = { value: undefined, unit: undefined };
71
+ } else {
72
+ try {
73
+ rawResult = JSON.parse(response);
74
+ } catch {
75
+ // If parsing fails, create a default structure
76
+ rawResult = { value: undefined, unit: undefined };
77
+ }
78
+ }
79
+ } else {
80
+ rawResult = response;
81
+ }
82
+
83
+ return toNumberWithUnits(JSON.stringify(rawResult));
19
84
  };
@@ -0,0 +1,23 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "type": "object",
4
+ "properties": {
5
+ "value": {
6
+ "oneOf": [
7
+ { "type": "number" },
8
+ { "type": "string" },
9
+ { "type": "null" }
10
+ ],
11
+ "description": "The numeric value extracted from the text"
12
+ },
13
+ "unit": {
14
+ "oneOf": [
15
+ { "type": "string" },
16
+ { "type": "null" }
17
+ ],
18
+ "description": "The unit of measurement, if any"
19
+ }
20
+ },
21
+ "required": ["value", "unit"],
22
+ "additionalProperties": false
23
+ }
@@ -7,9 +7,7 @@ import schemaOrg from './index.js';
7
7
 
8
8
  const resultSchemaWith = (type) => async () => {
9
9
  return JSON.parse(
10
- await fs.readFile(
11
- `./src/json-schemas/schema-dot-org-${type.toLowerCase()}.json`
12
- )
10
+ await fs.readFile(`./src/json-schemas/schema-dot-org-${type.toLowerCase()}.json`)
13
11
  );
14
12
  };
15
13
 
@@ -30,10 +28,7 @@ describe('Schema.org verblet', () => {
30
28
  it(
31
29
  `${example.inputs.text}${typeDisplay}`,
32
30
  async () => {
33
- const result = await schemaOrg(
34
- example.inputs.text,
35
- example.inputs.type
36
- );
31
+ const result = await schemaOrg(example.inputs.text, example.inputs.type);
37
32
 
38
33
  if (example.want.resultSchema) {
39
34
  const schema = await example.want.resultSchema();
@@ -1,8 +1,37 @@
1
+ import fs from 'node:fs/promises';
1
2
  import chatGPT from '../../lib/chatgpt/index.js';
2
3
  import stripResponse from '../../lib/strip-response/index.js';
3
4
  import { asSchemaOrgText } from '../../prompts/index.js';
4
- import toObject from '../to-object/index.js';
5
5
 
6
- export default async (text, type) => {
7
- return toObject(stripResponse(await chatGPT(asSchemaOrgText(text, type))));
6
+ const getSchema = async (type) => {
7
+ return JSON.parse(
8
+ await fs.readFile(`./src/json-schemas/schema-dot-org-${type.toLowerCase()}.json`)
9
+ );
10
+ };
11
+
12
+ export default async (text, type, config = {}) => {
13
+ const { llm, ...options } = config;
14
+ const schema = type ? await getSchema(type) : undefined;
15
+
16
+ const modelOptions = schema
17
+ ? {
18
+ response_format: {
19
+ type: 'json_schema',
20
+ json_schema: {
21
+ name: `schema_org_${type.toLowerCase()}`,
22
+ schema,
23
+ },
24
+ },
25
+ ...llm,
26
+ }
27
+ : {
28
+ response_format: { type: 'json_object' },
29
+ ...llm,
30
+ };
31
+
32
+ const response = await chatGPT(asSchemaOrgText(text, type, schema), {
33
+ modelOptions,
34
+ ...options,
35
+ });
36
+ return JSON.parse(stripResponse(response));
8
37
  };
@@ -0,0 +1,10 @@
1
+ # sentiment
2
+
3
+ Determine whether a piece of text expresses a positive, negative, or neutral tone.
4
+
5
+ ```javascript
6
+ import sentiment from './index.js';
7
+
8
+ const mood = await sentiment('I feel fantastic about the future!');
9
+ // mood === 'positive'
10
+ ```