@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.
- package/.cursor/launch.json +30 -0
- package/.cursor/settings.json +20 -0
- package/.github/workflows/branch-protection.yml +22 -0
- package/.github/workflows/ci.yml +117 -0
- package/.prettierrc +6 -0
- package/.release-it.json +4 -1
- package/.vscode/launch.json +31 -0
- package/AGENTS.md +220 -0
- package/DEVELOPING.md +105 -0
- package/README.md +671 -0
- package/eslint.config.js +80 -0
- package/package.json +28 -16
- package/scripts/generate-test/index.js +29 -3
- package/scripts/runner/index.js +26 -0
- package/scripts/simple-editor/index.js +29 -18
- package/scripts/summarize-files/index.js +28 -4
- package/src/chains/README.md +30 -0
- package/src/chains/anonymize/README.md +21 -0
- package/src/chains/anonymize/index.examples.js +75 -0
- package/src/chains/anonymize/index.js +121 -0
- package/src/chains/anonymize/index.spec.js +78 -0
- package/src/chains/bulk-central-tendency/index.examples.js +138 -0
- package/src/chains/bulk-central-tendency/index.js +91 -0
- package/src/chains/bulk-filter/README.md +21 -0
- package/src/chains/bulk-filter/index.examples.js +22 -0
- package/src/chains/bulk-filter/index.js +58 -0
- package/src/chains/bulk-filter/index.spec.js +38 -0
- package/src/chains/bulk-find/README.md +16 -0
- package/src/chains/bulk-find/index.examples.js +20 -0
- package/src/chains/bulk-find/index.js +30 -0
- package/src/chains/bulk-find/index.spec.js +26 -0
- package/src/chains/bulk-group/README.md +23 -0
- package/src/chains/bulk-group/index.examples.js +18 -0
- package/src/chains/bulk-group/index.js +34 -0
- package/src/chains/bulk-group/index.spec.js +41 -0
- package/src/chains/bulk-map/README.md +43 -0
- package/src/chains/bulk-map/index.examples.js +17 -0
- package/src/chains/bulk-map/index.js +86 -0
- package/src/chains/bulk-map/index.spec.js +44 -0
- package/src/chains/bulk-reduce/README.md +12 -0
- package/src/chains/bulk-reduce/index.examples.js +15 -0
- package/src/chains/bulk-reduce/index.js +13 -0
- package/src/chains/bulk-reduce/index.spec.js +25 -0
- package/src/chains/bulk-score/README.md +16 -0
- package/src/chains/bulk-score/bulk-score-result.json +18 -0
- package/src/chains/bulk-score/index.examples.js +22 -0
- package/src/chains/bulk-score/index.js +133 -0
- package/src/chains/bulk-score/index.spec.js +30 -0
- package/src/chains/category-samples/README.md +61 -0
- package/src/chains/category-samples/index.examples.js +103 -0
- package/src/chains/category-samples/index.js +134 -0
- package/src/chains/collect-terms/README.md +12 -0
- package/src/chains/collect-terms/index.examples.js +16 -0
- package/src/chains/collect-terms/index.js +44 -0
- package/src/chains/collect-terms/index.spec.js +25 -0
- package/src/chains/date/README.md +12 -0
- package/src/chains/date/index.examples.js +47 -0
- package/src/chains/date/index.js +74 -0
- package/src/chains/date/index.spec.js +62 -0
- package/src/chains/disambiguate/README.md +22 -0
- package/src/chains/disambiguate/disambiguate-meanings-result.json +16 -0
- package/src/chains/disambiguate/index.examples.js +18 -0
- package/src/chains/disambiguate/index.js +92 -0
- package/src/chains/disambiguate/index.spec.js +25 -0
- package/src/chains/dismantle/README.md +67 -0
- package/src/chains/dismantle/dismantle.examples.js +27 -0
- package/src/chains/dismantle/index.js +6 -17
- package/src/chains/dismantle/index.spec.js +1 -2
- package/src/chains/expect/README.md +171 -0
- package/src/chains/expect/index.examples.js +146 -0
- package/src/chains/expect/index.js +173 -0
- package/src/chains/expect/index.spec.js +324 -0
- package/src/chains/filter-ambiguous/README.md +11 -0
- package/src/chains/filter-ambiguous/index.examples.js +20 -0
- package/src/chains/filter-ambiguous/index.js +49 -0
- package/src/chains/filter-ambiguous/index.spec.js +31 -0
- package/src/chains/glossary/README.md +19 -0
- package/src/chains/glossary/index.examples.js +386 -0
- package/src/chains/glossary/index.js +75 -0
- package/src/chains/glossary/index.spec.js +19 -0
- package/src/chains/intersections/README.md +152 -0
- package/src/chains/intersections/index.examples.js +279 -0
- package/src/chains/intersections/index.js +366 -0
- package/src/chains/intersections/intersection-result.json +38 -0
- package/src/chains/list/index.examples.js +12 -16
- package/src/chains/list/index.js +106 -53
- package/src/chains/list/index.spec.js +3 -9
- package/src/chains/list/list-result.json +16 -0
- package/src/chains/llm-logger/README.md +208 -0
- package/src/chains/llm-logger/index.js +205 -0
- package/src/chains/llm-logger/index.spec.js +330 -0
- package/src/chains/questions/index.examples.js +2 -1
- package/src/chains/questions/index.js +14 -15
- package/src/chains/scan-js/index.js +6 -9
- package/src/chains/set-interval/README.md +81 -0
- package/src/chains/set-interval/index.examples.js +36 -0
- package/src/chains/set-interval/index.js +131 -0
- package/src/chains/set-interval/index.spec.js +70 -0
- package/src/chains/socratic/README.md +17 -0
- package/src/chains/socratic/index.js +64 -0
- package/src/chains/socratic/index.spec.js +24 -0
- package/src/chains/sort/index.examples.js +3 -7
- package/src/chains/sort/index.js +65 -15
- package/src/chains/sort/index.spec.js +5 -8
- package/src/chains/sort/sort-result.json +16 -0
- package/src/chains/summary-map/README.md +9 -1
- package/src/chains/summary-map/index.examples.js +9 -2
- package/src/chains/summary-map/index.js +43 -25
- package/src/chains/summary-map/index.spec.js +78 -3
- package/src/chains/test/index.js +9 -13
- package/src/chains/test-advice/index.js +4 -5
- package/src/chains/themes/README.md +20 -0
- package/src/chains/themes/index.examples.js +17 -0
- package/src/chains/themes/index.js +28 -0
- package/src/chains/themes/index.spec.js +19 -0
- package/src/chains/veiled-variants/index.examples.js +18 -0
- package/src/chains/veiled-variants/index.js +107 -0
- package/src/chains/veiled-variants/index.spec.js +40 -0
- package/src/constants/common.js +0 -2
- package/src/constants/models.js +172 -0
- package/src/index.js +178 -18
- package/src/json-schemas/README.md +13 -0
- package/src/json-schemas/index.js +8 -14
- package/src/json-schemas/schema-dot-org-photograph.json +11 -5
- package/src/json-schemas/schema-dot-org-place.json +78 -5
- package/src/lib/README.md +26 -0
- package/src/lib/bulk-filter/README.md +22 -0
- package/src/lib/bulk-filter/index.examples.js +27 -0
- package/src/lib/bulk-filter/index.js +63 -0
- package/src/lib/bulk-filter/index.spec.js +38 -0
- package/src/lib/bulk-find/README.md +18 -0
- package/src/lib/bulk-find/index.examples.js +19 -0
- package/src/lib/bulk-find/index.js +30 -0
- package/src/lib/bulk-find/index.spec.js +41 -0
- package/src/lib/chatgpt/index.js +63 -43
- package/src/lib/combinations/index.js +30 -0
- package/src/lib/combinations/index.spec.js +23 -0
- package/src/lib/functional/index.js +28 -0
- package/src/lib/logger-service/index.js +32 -0
- package/src/lib/parse-js-parts/index.js +9 -21
- package/src/lib/parse-llm-list/README.md +39 -0
- package/src/lib/parse-llm-list/index.js +54 -0
- package/src/lib/parse-llm-list/index.spec.js +59 -0
- package/src/lib/path-aliases/index.js +1 -3
- package/src/lib/path-aliases/index.spec.js +2 -8
- package/src/lib/pave/index.js +4 -4
- package/src/lib/pave/index.spec.js +6 -3
- package/src/lib/prompt-cache/index.js +14 -10
- package/src/lib/retry/index.js +11 -8
- package/src/lib/ring-buffer/README.md +460 -0
- package/src/lib/ring-buffer/index.js +1074 -0
- package/src/lib/search-best-first/city-walk.spec.js +37 -0
- package/src/lib/search-best-first/index.js +42 -11
- package/src/lib/search-best-first/index.spec.js +35 -0
- package/src/lib/search-js-files/index.js +21 -41
- package/src/lib/search-js-files/scan-file.js +10 -21
- package/src/lib/shorten-text/index.js +2 -7
- package/src/lib/shorten-text/index.spec.js +3 -3
- package/src/lib/strip-response/index.js +2 -7
- package/src/lib/template-replace/index.js +23 -0
- package/src/lib/template-replace/index.spec.js +60 -0
- package/src/lib/to-date/index.js +11 -0
- package/src/lib/to-number/index.js +1 -1
- package/src/lib/transcribe/index.js +4 -4
- package/src/prompts/README.md +3 -1
- package/src/prompts/as-object-with-schema.js +3 -8
- package/src/prompts/as-schema-org-text.js +10 -2
- package/src/prompts/code-features.js +1 -5
- package/src/prompts/constants.js +27 -27
- package/src/prompts/generate-collection.js +1 -1
- package/src/prompts/intent.js +11 -16
- package/src/prompts/select-from-threshold.js +1 -2
- package/src/prompts/sort.js +4 -8
- package/src/prompts/style.js +4 -7
- package/src/prompts/wrap-list.js +1 -4
- package/src/services/llm-model/global-overrides.spec.js +432 -0
- package/src/services/llm-model/index.js +234 -40
- package/src/services/llm-model/model.js +2 -2
- package/src/services/llm-model/negotiate.spec.js +447 -0
- package/src/services/redis/index.js +70 -7
- package/src/test/setup.js +20 -0
- package/src/verblets/README.md +26 -0
- package/src/verblets/auto/index.examples.js +12 -9
- package/src/verblets/auto/index.js +10 -10
- package/src/verblets/auto/index.spec.js +4 -6
- package/src/verblets/bool/README.md +36 -0
- package/src/verblets/bool/index.examples.js +53 -1
- package/src/verblets/bool/index.js +6 -9
- package/src/verblets/bool/index.spec.js +1 -3
- package/src/verblets/central-tendency/README.md +166 -0
- package/src/verblets/central-tendency/central-tendency-result.json +24 -0
- package/src/verblets/central-tendency/index.examples.js +196 -0
- package/src/verblets/central-tendency/index.js +171 -0
- package/src/verblets/central-tendency/index.spec.js +148 -0
- package/src/verblets/enum/index.examples.js +1 -4
- package/src/verblets/enum/index.js +7 -4
- package/src/verblets/expect/README.md +64 -0
- package/src/verblets/expect/index.examples.js +109 -0
- package/src/verblets/expect/index.js +75 -0
- package/src/verblets/expect/index.spec.js +127 -0
- package/src/verblets/intent/index.examples.js +84 -1
- package/src/verblets/intent/index.js +56 -68
- package/src/verblets/intersection/README.md +16 -0
- package/src/verblets/intersection/index.examples.js +89 -0
- package/src/verblets/intersection/index.js +84 -0
- package/src/verblets/intersection/index.spec.js +60 -0
- package/src/verblets/intersection/intersection-result.json +16 -0
- package/src/verblets/list-expand/README.md +10 -0
- package/src/verblets/list-expand/index.examples.js +14 -0
- package/src/verblets/list-expand/index.js +104 -0
- package/src/verblets/list-expand/index.spec.js +18 -0
- package/src/verblets/list-expand/list-expand-result.json +16 -0
- package/src/verblets/list-filter/README.md +22 -0
- package/src/verblets/list-filter/index.examples.js +26 -0
- package/src/verblets/list-filter/index.js +18 -0
- package/src/verblets/list-filter/index.spec.js +19 -0
- package/src/verblets/list-find/README.md +11 -0
- package/src/verblets/list-find/index.examples.js +15 -0
- package/src/verblets/list-find/index.js +17 -0
- package/src/verblets/list-find/index.spec.js +19 -0
- package/src/verblets/list-group/README.md +16 -0
- package/src/verblets/list-group/index.examples.js +16 -0
- package/src/verblets/list-group/index.js +112 -0
- package/src/verblets/list-group/index.spec.js +35 -0
- package/src/verblets/list-group/list-group-result.json +16 -0
- package/src/verblets/list-map/README.md +11 -0
- package/src/verblets/list-map/index.examples.js +15 -0
- package/src/verblets/list-map/index.js +26 -0
- package/src/verblets/list-map/index.spec.js +17 -0
- package/src/verblets/list-reduce/README.md +10 -0
- package/src/verblets/list-reduce/index.examples.js +14 -0
- package/src/verblets/list-reduce/index.js +21 -0
- package/src/verblets/list-reduce/index.spec.js +27 -0
- package/src/verblets/list-reduce/index.spec.jsx +27 -0
- package/src/verblets/name/README.md +15 -0
- package/src/verblets/name/index.examples.js +28 -0
- package/src/verblets/name/index.js +19 -0
- package/src/verblets/name/index.spec.js +33 -0
- package/src/verblets/name-similar-to/README.md +26 -0
- package/src/verblets/name-similar-to/index.examples.js +18 -0
- package/src/verblets/name-similar-to/index.js +20 -0
- package/src/verblets/name-similar-to/index.spec.js +13 -0
- package/src/verblets/number/index.examples.js +173 -7
- package/src/verblets/number/index.js +5 -2
- package/src/verblets/number/index.spec.js +1 -3
- package/src/verblets/number-with-units/index.examples.js +5 -1
- package/src/verblets/number-with-units/index.js +74 -9
- package/src/verblets/number-with-units/number-with-units-result.json +23 -0
- package/src/verblets/schema-org/index.examples.js +2 -7
- package/src/verblets/schema-org/index.js +32 -3
- package/src/verblets/sentiment/README.md +10 -0
- package/src/verblets/sentiment/index.examples.js +20 -0
- package/src/verblets/sentiment/index.js +9 -0
- package/src/verblets/sentiment/index.spec.js +20 -0
- package/src/verblets/to-object/index.js +10 -15
- package/src/verblets/to-object/index.spec.js +1 -4
- package/.eslintrc.json +0 -42
- package/docs/README.md +0 -41
- package/docs/babel.config.js +0 -3
- package/docs/blog/2019-05-28-first-blog-post.md +0 -12
- package/docs/blog/2019-05-29-long-blog-post.md +0 -44
- package/docs/blog/2021-08-01-mdx-blog-post.mdx +0 -20
- package/docs/blog/2021-08-26-welcome/docusaurus-plushie-banner.jpeg +0 -0
- package/docs/blog/2021-08-26-welcome/index.md +0 -25
- package/docs/blog/authors.yml +0 -17
- package/docs/docs/api/bool.md +0 -74
- package/docs/docs/api/search.md +0 -51
- package/docs/docs/intro.md +0 -47
- package/docs/docs/tutorial-basics/_category_.json +0 -8
- package/docs/docs/tutorial-basics/congratulations.md +0 -23
- package/docs/docs/tutorial-basics/create-a-blog-post.md +0 -34
- package/docs/docs/tutorial-basics/create-a-document.md +0 -57
- package/docs/docs/tutorial-basics/create-a-page.md +0 -43
- package/docs/docs/tutorial-basics/deploy-your-site.md +0 -31
- package/docs/docs/tutorial-basics/markdown-features.mdx +0 -152
- package/docs/docs/tutorial-extras/_category_.json +0 -7
- package/docs/docs/tutorial-extras/img/docsVersionDropdown.png +0 -0
- package/docs/docs/tutorial-extras/img/localeDropdown.png +0 -0
- package/docs/docs/tutorial-extras/manage-docs-versions.md +0 -55
- package/docs/docs/tutorial-extras/translate-your-site.md +0 -88
- package/docs/docusaurus.config.js +0 -120
- package/docs/package.json +0 -44
- package/docs/sidebars.js +0 -31
- package/docs/src/components/HomepageFeatures/index.js +0 -61
- package/docs/src/components/HomepageFeatures/styles.module.css +0 -11
- package/docs/src/css/custom.css +0 -30
- package/docs/src/pages/index.js +0 -43
- package/docs/src/pages/index.module.css +0 -23
- package/docs/src/pages/markdown-page.md +0 -7
- package/docs/static/.nojekyll +0 -0
- package/docs/static/img/docusaurus-social-card.jpg +0 -0
- package/docs/static/img/docusaurus.png +0 -0
- package/docs/static/img/favicon.ico +0 -0
- package/docs/static/img/logo.svg +0 -1
- package/docs/static/img/undraw_docusaurus_mountain.svg +0 -171
- package/docs/static/img/undraw_docusaurus_react.svg +0 -170
- package/docs/static/img/undraw_docusaurus_tree.svg +0 -40
- package/src/constants/openai.js +0 -65
- /package/{.vite.config.examples.js → .vitest.config.examples.js} +0 -0
- /package/{.vite.config.js → .vitest.config.js} +0 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import bulkFilter from './index.js';
|
|
3
|
+
import { longTestTimeout } from '../../constants/common.js';
|
|
4
|
+
|
|
5
|
+
describe('bulk-filter examples', () => {
|
|
6
|
+
it(
|
|
7
|
+
'filters items in batches',
|
|
8
|
+
async () => {
|
|
9
|
+
const entries = [
|
|
10
|
+
'Losing that match taught me the value of persistence.',
|
|
11
|
+
"I hate losing and it proves I'm worthless.",
|
|
12
|
+
'After failing my exam, I studied harder and passed the retake.',
|
|
13
|
+
"No matter what I do, I'll never succeed.",
|
|
14
|
+
];
|
|
15
|
+
const result = await bulkFilter(
|
|
16
|
+
entries,
|
|
17
|
+
'keep only reflections that show personal growth or learning from mistakes',
|
|
18
|
+
2
|
|
19
|
+
);
|
|
20
|
+
expect(result).toStrictEqual([
|
|
21
|
+
'Losing that match taught me the value of persistence.',
|
|
22
|
+
'After failing my exam, I studied harder and passed the retake.',
|
|
23
|
+
]);
|
|
24
|
+
},
|
|
25
|
+
longTestTimeout
|
|
26
|
+
);
|
|
27
|
+
});
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import listFilter from '../../verblets/list-filter/index.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Filter a list by processing newline-delimited batches with `listFilter`.
|
|
5
|
+
*
|
|
6
|
+
* @param {string[]} list - array of items to evaluate
|
|
7
|
+
* @param {string} instructions - filter instructions for `listFilter`
|
|
8
|
+
* @param {number} [chunkSize=10] - how many items per batch
|
|
9
|
+
* @returns {Promise<string[]>} filtered items preserving order
|
|
10
|
+
*/
|
|
11
|
+
export default async function bulkFilter(list, instructions, chunkSize = 10) {
|
|
12
|
+
const batches = [];
|
|
13
|
+
for (let i = 0; i < list.length; i += chunkSize) {
|
|
14
|
+
const batch = list.slice(i, i + chunkSize);
|
|
15
|
+
const filtered = await listFilter(batch, instructions);
|
|
16
|
+
batches.push(filtered);
|
|
17
|
+
}
|
|
18
|
+
return batches.flat();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Retry filtering failed batches until `maxAttempts` is reached.
|
|
23
|
+
*
|
|
24
|
+
* @param {string[]} list
|
|
25
|
+
* @param {string} instructions
|
|
26
|
+
* @param {object} [options]
|
|
27
|
+
* @param {number} [options.chunkSize=10]
|
|
28
|
+
* @param {number} [options.maxAttempts=3]
|
|
29
|
+
* @returns {Promise<string[]>}
|
|
30
|
+
*/
|
|
31
|
+
export async function bulkFilterRetry(
|
|
32
|
+
list,
|
|
33
|
+
instructions,
|
|
34
|
+
{ chunkSize = 10, maxAttempts = 3 } = {}
|
|
35
|
+
) {
|
|
36
|
+
const batches = [];
|
|
37
|
+
for (let i = 0; i < list.length; i += chunkSize) {
|
|
38
|
+
batches.push(list.slice(i, i + chunkSize));
|
|
39
|
+
}
|
|
40
|
+
const results = new Array(batches.length).fill(null);
|
|
41
|
+
let pending = batches.map((_, idx) => idx);
|
|
42
|
+
|
|
43
|
+
for (let attempt = 0; attempt < maxAttempts && pending.length > 0; attempt += 1) {
|
|
44
|
+
const newPending = [];
|
|
45
|
+
for (const idx of pending) {
|
|
46
|
+
const batch = batches[idx];
|
|
47
|
+
try {
|
|
48
|
+
const filtered = await listFilter(batch, instructions);
|
|
49
|
+
const valid = filtered.every((line) => batch.includes(line));
|
|
50
|
+
if (valid) {
|
|
51
|
+
results[idx] = filtered;
|
|
52
|
+
} else {
|
|
53
|
+
newPending.push(idx);
|
|
54
|
+
}
|
|
55
|
+
} catch {
|
|
56
|
+
newPending.push(idx);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
pending = newPending;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return results.flat().filter(Boolean);
|
|
63
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { describe, expect, it, vi, beforeEach } from 'vitest';
|
|
2
|
+
import bulkFilter, { bulkFilterRetry } from './index.js';
|
|
3
|
+
import listFilter from '../../verblets/list-filter/index.js';
|
|
4
|
+
|
|
5
|
+
vi.mock('../../verblets/list-filter/index.js', () => ({
|
|
6
|
+
default: vi.fn(async (items, instructions) => {
|
|
7
|
+
if (items.includes('FAIL')) throw new Error('fail');
|
|
8
|
+
return items.filter((l) => l.includes(instructions));
|
|
9
|
+
}),
|
|
10
|
+
}));
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
vi.clearAllMocks();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
describe('bulk-filter', () => {
|
|
17
|
+
it('filters fragments in batches', async () => {
|
|
18
|
+
const result = await bulkFilter(['aa', 'b', 'ca', 'd'], 'a', 2);
|
|
19
|
+
expect(result).toStrictEqual(['aa', 'ca']);
|
|
20
|
+
expect(listFilter).toHaveBeenCalledTimes(2);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('retries failed batches', async () => {
|
|
24
|
+
let call = 0;
|
|
25
|
+
listFilter.mockImplementation(async (items) => {
|
|
26
|
+
call += 1;
|
|
27
|
+
if (call === 1) throw new Error('fail');
|
|
28
|
+
return items.filter((l) => l.includes('a'));
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const result = await bulkFilterRetry(['aa', 'b', 'ca'], 'a', {
|
|
32
|
+
chunkSize: 2,
|
|
33
|
+
maxAttempts: 2,
|
|
34
|
+
});
|
|
35
|
+
expect(result).toStrictEqual(['aa', 'ca']);
|
|
36
|
+
expect(listFilter).toHaveBeenCalledTimes(3);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# bulk-find
|
|
2
|
+
|
|
3
|
+
Search large lists in batches using `listFind` and return the first matching item.
|
|
4
|
+
Failed batches can be retried with `bulkFindRetry`.
|
|
5
|
+
|
|
6
|
+
```javascript
|
|
7
|
+
import bulkFind, { bulkFindRetry } from './index.js';
|
|
8
|
+
|
|
9
|
+
const diaryEntries = [
|
|
10
|
+
'Hiked up the mountains today and saw breathtaking views',
|
|
11
|
+
'Visited the local market and tried a spicy stew',
|
|
12
|
+
'Spotted penguins playing on the beach this morning'
|
|
13
|
+
];
|
|
14
|
+
const penguinMoment = await bulkFindRetry(diaryEntries, 'mentions penguins', {
|
|
15
|
+
chunkSize: 2
|
|
16
|
+
});
|
|
17
|
+
// penguinMoment === 'Spotted penguins playing on the beach this morning'
|
|
18
|
+
```
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { bulkFindRetry } from './index.js';
|
|
3
|
+
import { longTestTimeout } from '../../constants/common.js';
|
|
4
|
+
|
|
5
|
+
describe('bulk-find examples', () => {
|
|
6
|
+
it(
|
|
7
|
+
'finds an item across batches',
|
|
8
|
+
async () => {
|
|
9
|
+
const diaryEntries = [
|
|
10
|
+
'Hiked up the mountains today and saw breathtaking views',
|
|
11
|
+
'Visited the local market and tried a spicy stew',
|
|
12
|
+
'Spotted penguins playing on the beach this morning',
|
|
13
|
+
];
|
|
14
|
+
const result = await bulkFindRetry(diaryEntries, 'mentions penguins');
|
|
15
|
+
expect(result).toBeDefined();
|
|
16
|
+
},
|
|
17
|
+
longTestTimeout
|
|
18
|
+
);
|
|
19
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import listFind from '../../verblets/list-find/index.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Search a list in batches using `listFind`.
|
|
5
|
+
*
|
|
6
|
+
* @param { string[] } list - array of items to search
|
|
7
|
+
* @param { string } instructions - criteria passed to `listFind`
|
|
8
|
+
* @param { number } [chunkSize=10] - how many items per batch
|
|
9
|
+
* @returns { Promise<string|undefined> } first match or undefined
|
|
10
|
+
*/
|
|
11
|
+
export default async function bulkFind(list, instructions, chunkSize = 10) {
|
|
12
|
+
for (let i = 0; i < list.length; i += chunkSize) {
|
|
13
|
+
const batch = list.slice(i, i + chunkSize);
|
|
14
|
+
try {
|
|
15
|
+
const result = await listFind(batch, instructions);
|
|
16
|
+
if (result) return result;
|
|
17
|
+
} catch {
|
|
18
|
+
// ignore and continue to next batch
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function bulkFindRetry(list, instructions, { chunkSize = 10, maxAttempts = 3 } = {}) {
|
|
25
|
+
for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
|
|
26
|
+
const result = await bulkFind(list, instructions, chunkSize);
|
|
27
|
+
if (result) return result;
|
|
28
|
+
}
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { describe, expect, it, vi, beforeEach } from 'vitest';
|
|
2
|
+
import bulkFind, { bulkFindRetry } from './index.js';
|
|
3
|
+
import listFind from '../../verblets/list-find/index.js';
|
|
4
|
+
|
|
5
|
+
vi.mock('../../verblets/list-find/index.js', () => ({
|
|
6
|
+
default: vi.fn(async (list, instructions) => list.find((l) => l.includes(instructions)) || ''),
|
|
7
|
+
}));
|
|
8
|
+
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
vi.clearAllMocks();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
describe('bulk-find', () => {
|
|
14
|
+
it('returns first match across batches', async () => {
|
|
15
|
+
const result = await bulkFind(['a', 'b', 'c', 'd'], 'c', 2);
|
|
16
|
+
expect(result).toBe('c');
|
|
17
|
+
expect(listFind).toHaveBeenCalledTimes(2);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('returns undefined when not found', async () => {
|
|
21
|
+
listFind.mockResolvedValueOnce('');
|
|
22
|
+
listFind.mockResolvedValueOnce('');
|
|
23
|
+
const result = await bulkFind(['a', 'b'], 'x', 1);
|
|
24
|
+
expect(result).toBeUndefined();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('retries until a match is found', async () => {
|
|
28
|
+
listFind.mockResolvedValueOnce('');
|
|
29
|
+
listFind.mockResolvedValueOnce('c');
|
|
30
|
+
const result = await bulkFindRetry(['a', 'b', 'c'], 'c', { chunkSize: 2, maxAttempts: 2 });
|
|
31
|
+
expect(result).toBe('c');
|
|
32
|
+
expect(listFind).toHaveBeenCalledTimes(2);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('returns undefined after maxAttempts', async () => {
|
|
36
|
+
listFind.mockResolvedValue('');
|
|
37
|
+
const result = await bulkFindRetry(['a', 'b'], 'x', { chunkSize: 1, maxAttempts: 2 });
|
|
38
|
+
expect(result).toBeUndefined();
|
|
39
|
+
expect(listFind).toHaveBeenCalledTimes(4);
|
|
40
|
+
});
|
|
41
|
+
});
|
package/src/lib/chatgpt/index.js
CHANGED
|
@@ -1,18 +1,14 @@
|
|
|
1
1
|
import fetch from 'node-fetch';
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
|
-
apiKey,
|
|
5
|
-
apiUrl,
|
|
6
4
|
debugPromptGlobally,
|
|
7
5
|
debugPromptGloballyIfChanged,
|
|
8
6
|
debugResultGlobally,
|
|
9
7
|
debugResultGloballyIfChanged,
|
|
10
|
-
|
|
8
|
+
models,
|
|
9
|
+
} from '../../constants/models.js';
|
|
11
10
|
import anySignal from '../any-signal/index.js';
|
|
12
|
-
import {
|
|
13
|
-
get as getPromptResult,
|
|
14
|
-
set as setPromptResult,
|
|
15
|
-
} from '../prompt-cache/index.js';
|
|
11
|
+
import { get as getPromptResult, set as setPromptResult } from '../prompt-cache/index.js';
|
|
16
12
|
import TimedAbortController from '../timed-abort-controller/index.js';
|
|
17
13
|
import modelService from '../../services/llm-model/index.js';
|
|
18
14
|
import { getClient as getRedis } from '../../services/redis/index.js';
|
|
@@ -20,13 +16,11 @@ import { getClient as getRedis } from '../../services/redis/index.js';
|
|
|
20
16
|
const shapeOutputDefault = (result) => {
|
|
21
17
|
// GPT-4
|
|
22
18
|
if (result.choices[0].message.tool_calls?.length) {
|
|
23
|
-
const toolCall = result.choices[0].message.tool_calls[0]
|
|
19
|
+
const toolCall = result.choices[0].message.tool_calls[0];
|
|
24
20
|
return {
|
|
25
|
-
name: toolCall
|
|
26
|
-
arguments: JSON.parse(
|
|
27
|
-
|
|
28
|
-
),
|
|
29
|
-
result: result.choices[0].message.tool_calls[0].function
|
|
21
|
+
name: toolCall.function.name,
|
|
22
|
+
arguments: JSON.parse(toolCall.function.arguments),
|
|
23
|
+
result: result.choices[0].message.tool_calls[0].function,
|
|
30
24
|
};
|
|
31
25
|
}
|
|
32
26
|
if (result.choices[0].message) {
|
|
@@ -36,30 +30,30 @@ const shapeOutputDefault = (result) => {
|
|
|
36
30
|
};
|
|
37
31
|
|
|
38
32
|
const onBeforeRequestDefault = ({ debugPrompt, isCached, prompt }) => {
|
|
39
|
-
if (
|
|
40
|
-
|
|
41
|
-
debugPromptGlobally ||
|
|
42
|
-
(debugPromptGloballyIfChanged && !isCached)
|
|
43
|
-
) {
|
|
44
|
-
console.error(`+++ DEBUG PROMPT +++`);
|
|
33
|
+
if (debugPrompt || debugPromptGlobally || (debugPromptGloballyIfChanged && !isCached)) {
|
|
34
|
+
console.error('+++ DEBUG PROMPT +++');
|
|
45
35
|
console.error(prompt);
|
|
46
36
|
console.error('+++ DEBUG PROMPT END +++');
|
|
47
37
|
}
|
|
48
38
|
};
|
|
49
39
|
|
|
50
40
|
const onAfterRequestDefault = ({ debugResult, isCached, resultShaped }) => {
|
|
51
|
-
if (
|
|
52
|
-
|
|
53
|
-
debugResultGlobally ||
|
|
54
|
-
(debugResultGloballyIfChanged && !isCached)
|
|
55
|
-
) {
|
|
56
|
-
console.error(`+++ DEBUG RESULT +++`);
|
|
41
|
+
if (debugResult || debugResultGlobally || (debugResultGloballyIfChanged && !isCached)) {
|
|
42
|
+
console.error('+++ DEBUG RESULT +++');
|
|
57
43
|
console.error(resultShaped);
|
|
58
44
|
console.error('+++ DEBUG RESULT END +++');
|
|
59
45
|
}
|
|
60
46
|
};
|
|
61
47
|
|
|
62
|
-
export const run = async (prompt,
|
|
48
|
+
export const run = async (prompt, config = {}) => {
|
|
49
|
+
// Handle config parameter - can be string (model name) or object (full options)
|
|
50
|
+
let options;
|
|
51
|
+
if (typeof config === 'string') {
|
|
52
|
+
options = { modelOptions: { modelName: config } };
|
|
53
|
+
} else {
|
|
54
|
+
options = config;
|
|
55
|
+
}
|
|
56
|
+
|
|
63
57
|
const {
|
|
64
58
|
abortSignal,
|
|
65
59
|
debugPrompt,
|
|
@@ -71,17 +65,43 @@ export const run = async (prompt, options = {}) => {
|
|
|
71
65
|
shapeOutput = shapeOutputDefault,
|
|
72
66
|
} = options;
|
|
73
67
|
|
|
74
|
-
|
|
68
|
+
// Apply global overrides to model options
|
|
69
|
+
const modelOptionsWithOverrides = modelService.applyGlobalOverrides(modelOptions);
|
|
70
|
+
|
|
71
|
+
// Check if negotiation was applied via global override
|
|
72
|
+
const negotiationFromGlobalOverride = modelService.getGlobalOverride('negotiate');
|
|
73
|
+
|
|
74
|
+
const modelNameNegotiated = modelOptionsWithOverrides.negotiate
|
|
75
|
+
? modelService.negotiateModel(
|
|
76
|
+
// If negotiation came from global override, don't use preferred model
|
|
77
|
+
negotiationFromGlobalOverride ? null : modelOptionsWithOverrides.modelName,
|
|
78
|
+
modelOptionsWithOverrides.negotiate
|
|
79
|
+
)
|
|
80
|
+
: modelOptionsWithOverrides.modelName;
|
|
81
|
+
|
|
82
|
+
const modelFound = modelService.getModel(modelNameNegotiated);
|
|
75
83
|
|
|
76
|
-
|
|
84
|
+
// Use model-specific API URL and key if defined, otherwise fall back to defaults
|
|
85
|
+
const apiUrl = modelFound?.apiUrl || models.fastGood.apiUrl;
|
|
86
|
+
const apiKey = modelFound?.apiKey || models.fastGood.apiKey;
|
|
77
87
|
|
|
78
88
|
const requestConfig = modelService.getRequestConfig({
|
|
79
89
|
prompt,
|
|
80
|
-
...
|
|
90
|
+
...modelOptionsWithOverrides,
|
|
91
|
+
modelName: modelNameNegotiated,
|
|
81
92
|
});
|
|
82
93
|
|
|
83
|
-
|
|
84
|
-
const
|
|
94
|
+
// Check if caching is disabled via environment variable
|
|
95
|
+
const cachingDisabled = process.env.DISABLE_CACHE === 'true';
|
|
96
|
+
|
|
97
|
+
let cacheResult = null;
|
|
98
|
+
let cache = null;
|
|
99
|
+
|
|
100
|
+
if (!cachingDisabled) {
|
|
101
|
+
cache = await getRedis();
|
|
102
|
+
const { result } = await getPromptResult(cache, requestConfig);
|
|
103
|
+
cacheResult = result;
|
|
104
|
+
}
|
|
85
105
|
|
|
86
106
|
onBeforeRequest({
|
|
87
107
|
isCached: !!cacheResult,
|
|
@@ -92,9 +112,12 @@ export const run = async (prompt, options = {}) => {
|
|
|
92
112
|
|
|
93
113
|
let result = cacheResult;
|
|
94
114
|
if (!cacheResult || forceQuery) {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
115
|
+
// Use custom requestTimeout from modelOptions if provided, otherwise use model default
|
|
116
|
+
const requestTimeout =
|
|
117
|
+
modelOptionsWithOverrides.requestTimeout ||
|
|
118
|
+
modelService.getModel(modelNameNegotiated).requestTimeout;
|
|
119
|
+
|
|
120
|
+
const timeoutController = new TimedAbortController(requestTimeout);
|
|
98
121
|
|
|
99
122
|
// console.log(requestConfig, `${apiUrl}${modelFound.endpoint}`)
|
|
100
123
|
|
|
@@ -111,21 +134,18 @@ export const run = async (prompt, options = {}) => {
|
|
|
111
134
|
result = await response.json();
|
|
112
135
|
|
|
113
136
|
if (!response.ok) {
|
|
114
|
-
const vars = [
|
|
115
|
-
|
|
116
|
-
`type: ${result?.error?.type}`,
|
|
117
|
-
].join(', ');
|
|
118
|
-
throw new Error(
|
|
119
|
-
`Completions request [error]: ${result?.error?.message} (${vars})`
|
|
120
|
-
);
|
|
137
|
+
const vars = [`status: ${response?.status}`, `type: ${result?.error?.type}`].join(', ');
|
|
138
|
+
throw new Error(`Completions request [error]: ${result?.error?.message} (${vars})`);
|
|
121
139
|
}
|
|
122
140
|
|
|
123
141
|
timeoutController.clearTimeout();
|
|
124
142
|
|
|
125
|
-
|
|
143
|
+
// Only cache the result if caching is not disabled
|
|
144
|
+
if (!cachingDisabled && cache) {
|
|
145
|
+
await setPromptResult(cache, requestConfig, result);
|
|
146
|
+
}
|
|
126
147
|
}
|
|
127
148
|
|
|
128
|
-
debugger;
|
|
129
149
|
const resultShaped = shapeOutput(result);
|
|
130
150
|
|
|
131
151
|
onAfterRequest({
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export default function combinations(items, size = 2) {
|
|
2
|
+
const result = [];
|
|
3
|
+
if (!Array.isArray(items) || size < 1) return result;
|
|
4
|
+
|
|
5
|
+
const combo = [];
|
|
6
|
+
const generate = (start) => {
|
|
7
|
+
if (combo.length === size) {
|
|
8
|
+
result.push([...combo]);
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
for (let i = start; i < items.length; i++) {
|
|
12
|
+
combo.push(items[i]);
|
|
13
|
+
generate(i + 1);
|
|
14
|
+
combo.pop();
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
generate(0);
|
|
19
|
+
return result;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function rangeCombinations(items, minSize = 2, maxSize = items.length) {
|
|
23
|
+
const sets = [];
|
|
24
|
+
if (!Array.isArray(items)) return sets;
|
|
25
|
+
const upper = Math.min(items.length, maxSize);
|
|
26
|
+
for (let s = minSize; s <= upper; s++) {
|
|
27
|
+
sets.push(...combinations(items, s));
|
|
28
|
+
}
|
|
29
|
+
return sets;
|
|
30
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import combinations, { rangeCombinations } from './index.js';
|
|
3
|
+
|
|
4
|
+
describe('combinations helper', () => {
|
|
5
|
+
it('generates pairwise combinations', () => {
|
|
6
|
+
const result = combinations(['a', 'b', 'c'], 2);
|
|
7
|
+
expect(result).toStrictEqual([
|
|
8
|
+
['a', 'b'],
|
|
9
|
+
['a', 'c'],
|
|
10
|
+
['b', 'c'],
|
|
11
|
+
]);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('generates combinations of varying sizes', () => {
|
|
15
|
+
const result = rangeCombinations(['a', 'b', 'c']);
|
|
16
|
+
expect(result).toStrictEqual([
|
|
17
|
+
['a', 'b'],
|
|
18
|
+
['a', 'c'],
|
|
19
|
+
['b', 'c'],
|
|
20
|
+
['a', 'b', 'c'],
|
|
21
|
+
]);
|
|
22
|
+
});
|
|
23
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export const hook = (list, f, returnVal) => {
|
|
2
|
+
list.push(f);
|
|
3
|
+
return returnVal;
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
export const hookOnce = (list, f, returnVal) => {
|
|
7
|
+
if (list.includes(f)) return returnVal;
|
|
8
|
+
list.push(f);
|
|
9
|
+
return returnVal;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const hookFn = (list, returnVal) => (f) => hook(list, f, returnVal);
|
|
13
|
+
|
|
14
|
+
export const hookOnceFn = (list, returnVal) => (f) => hookOnce(list, f, returnVal);
|
|
15
|
+
|
|
16
|
+
export const unhook = (list, f, returnVal) => {
|
|
17
|
+
const idx = list.indexOf(f);
|
|
18
|
+
if (idx === -1) return returnVal;
|
|
19
|
+
list.splice(idx, 1);
|
|
20
|
+
return returnVal;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const unhookFn = (list, returnVal) => (f) => unhook(list, f, returnVal);
|
|
24
|
+
|
|
25
|
+
export const unhookAll = (list, returnVal) => {
|
|
26
|
+
list.splice(0);
|
|
27
|
+
return returnVal;
|
|
28
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Global logger service
|
|
2
|
+
let globalLogger = null;
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Set the global logger instance
|
|
6
|
+
* @param {Object} logger - Logger instance to use globally
|
|
7
|
+
*/
|
|
8
|
+
export const setLogger = (logger) => {
|
|
9
|
+
globalLogger = logger;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Reset the global logger to null
|
|
14
|
+
*/
|
|
15
|
+
export const resetLogger = () => {
|
|
16
|
+
globalLogger = null;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Get the current global logger instance
|
|
21
|
+
* @returns {Object|null} Current logger instance or null if not set
|
|
22
|
+
*/
|
|
23
|
+
export const getLogger = () => globalLogger;
|
|
24
|
+
|
|
25
|
+
// Export convenience methods that use the global logger
|
|
26
|
+
export const log = (...args) => globalLogger?.log(...args);
|
|
27
|
+
export const info = (...args) => globalLogger?.info(...args);
|
|
28
|
+
export const warn = (...args) => globalLogger?.warn(...args);
|
|
29
|
+
export const error = (...args) => globalLogger?.error(...args);
|
|
30
|
+
export const debug = (...args) => globalLogger?.debug(...args);
|
|
31
|
+
export const trace = (...args) => globalLogger?.trace(...args);
|
|
32
|
+
export const fatal = (...args) => globalLogger?.fatal(...args);
|
|
@@ -44,9 +44,7 @@ const scanFile = (file, code) => {
|
|
|
44
44
|
return { ...s.imported }.name; // also has local name
|
|
45
45
|
});
|
|
46
46
|
const source = importNode.source.value;
|
|
47
|
-
const importKey = source.startsWith('.')
|
|
48
|
-
? convertImport(file, source)
|
|
49
|
-
: source;
|
|
47
|
+
const importKey = source.startsWith('.') ? convertImport(file, source) : source;
|
|
50
48
|
importsMap[importKey] = {
|
|
51
49
|
start: importNode.start,
|
|
52
50
|
end: importNode.end,
|
|
@@ -60,9 +58,7 @@ const scanFile = (file, code) => {
|
|
|
60
58
|
if (expNode.source) {
|
|
61
59
|
// Handle re-exports
|
|
62
60
|
const source = expNode.source.value;
|
|
63
|
-
const importKey = source.startsWith('.')
|
|
64
|
-
? convertImport(file, source)
|
|
65
|
-
: source;
|
|
61
|
+
const importKey = source.startsWith('.') ? convertImport(file, source) : source;
|
|
66
62
|
|
|
67
63
|
// Named exports
|
|
68
64
|
if (expNode.specifiers) {
|
|
@@ -128,9 +124,7 @@ const scanFile = (file, code) => {
|
|
|
128
124
|
ExportAllDeclaration(expNode) {
|
|
129
125
|
if (expNode.source) {
|
|
130
126
|
const source = expNode.source.value;
|
|
131
|
-
const importKey = source.startsWith('.')
|
|
132
|
-
? convertImport(file, source)
|
|
133
|
-
: source;
|
|
127
|
+
const importKey = source.startsWith('.') ? convertImport(file, source) : source;
|
|
134
128
|
exportsMap[expNode.exported.name] = {
|
|
135
129
|
start: expNode.start,
|
|
136
130
|
end: expNode.end,
|
|
@@ -161,7 +155,7 @@ const scanFile = (file, code) => {
|
|
|
161
155
|
end: expNode.end,
|
|
162
156
|
};
|
|
163
157
|
|
|
164
|
-
functionsMap[
|
|
158
|
+
functionsMap['ArrowFunctionExpression:default'] = {
|
|
165
159
|
start: expNode.declaration.start,
|
|
166
160
|
end: expNode.declaration.end,
|
|
167
161
|
functionName: '<default-export>',
|
|
@@ -195,9 +189,7 @@ const scanFile = (file, code) => {
|
|
|
195
189
|
});
|
|
196
190
|
|
|
197
191
|
arrowDeclarations.forEach((arrowFnNode) => {
|
|
198
|
-
functionsSeen[
|
|
199
|
-
`${arrowFnNode.init.start}:${arrowFnNode.init.end}`
|
|
200
|
-
] = true;
|
|
192
|
+
functionsSeen[`${arrowFnNode.init.start}:${arrowFnNode.init.end}`] = true;
|
|
201
193
|
|
|
202
194
|
functionsMap[`ArrowFunctionExpression:${arrowFnNode.id.name}`] = {
|
|
203
195
|
start: arrowFnNode.start,
|
|
@@ -283,16 +275,12 @@ const scanFile = (file, code) => {
|
|
|
283
275
|
const className = node.id.name;
|
|
284
276
|
node.body.body.forEach((classElement) => {
|
|
285
277
|
if (classElement.type === 'MethodDefinition') {
|
|
286
|
-
functionsSeen[
|
|
287
|
-
`${classElement.value.start}:${classElement.value.end}`
|
|
288
|
-
] = true;
|
|
278
|
+
functionsSeen[`${classElement.value.start}:${classElement.value.end}`] = true;
|
|
289
279
|
|
|
290
|
-
functionsMap[
|
|
291
|
-
`MethodDefinition:${className}.${classElement.key.name}`
|
|
292
|
-
] = {
|
|
280
|
+
functionsMap[`MethodDefinition:${className}.${classElement.key.name}`] = {
|
|
293
281
|
start: node.start,
|
|
294
282
|
end: node.end,
|
|
295
|
-
className
|
|
283
|
+
className,
|
|
296
284
|
functionName: `${className}.${classElement.key.name}`,
|
|
297
285
|
name: classElement.key.name,
|
|
298
286
|
async: classElement.value.async,
|
|
@@ -310,7 +298,7 @@ const scanFile = (file, code) => {
|
|
|
310
298
|
if (!functionsSeen[`${node.start}:${node.end}`]) {
|
|
311
299
|
functionsMap[`${node.type}:${node.start}`] = {
|
|
312
300
|
start: node.start,
|
|
313
|
-
functionName:
|
|
301
|
+
functionName: '<exp>',
|
|
314
302
|
end: node.end,
|
|
315
303
|
type: node.type,
|
|
316
304
|
async: node.async,
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Parse LLM List
|
|
2
|
+
|
|
3
|
+
A utility function for parsing lists from LLM responses that may be in JSON array or CSV format.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```javascript
|
|
8
|
+
import parseLLMList from './src/lib/parse-llm-list/index.js';
|
|
9
|
+
|
|
10
|
+
// Parse JSON array response
|
|
11
|
+
const jsonResponse = '["term1", "term2", "term3"]';
|
|
12
|
+
const terms1 = parseLLMList(jsonResponse);
|
|
13
|
+
// Returns: ["term1", "term2", "term3"]
|
|
14
|
+
|
|
15
|
+
// Parse CSV response
|
|
16
|
+
const csvResponse = 'term1, term2, term3';
|
|
17
|
+
const terms2 = parseLLMList(csvResponse);
|
|
18
|
+
// Returns: ["term1", "term2", "term3"]
|
|
19
|
+
|
|
20
|
+
// With custom options
|
|
21
|
+
const terms3 = parseLLMList(response, {
|
|
22
|
+
excludeValues: ['none', 'null', 'n/a'],
|
|
23
|
+
trimItems: true,
|
|
24
|
+
filterEmpty: true
|
|
25
|
+
});
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Options
|
|
29
|
+
|
|
30
|
+
- `excludeValues` (string[]): Values to exclude from results (case-insensitive). Default: `['none', 'null', 'undefined']`
|
|
31
|
+
- `trimItems` (boolean): Whether to trim whitespace from items. Default: `true`
|
|
32
|
+
- `filterEmpty` (boolean): Whether to filter out empty strings. Default: `true`
|
|
33
|
+
|
|
34
|
+
## Features
|
|
35
|
+
|
|
36
|
+
- Automatically detects JSON array vs CSV format
|
|
37
|
+
- Handles common LLM response patterns like `<note>` tags
|
|
38
|
+
- Filters out excluded values and empty responses
|
|
39
|
+
- Robust error handling with fallback parsing
|