@far-world-labs/verblets 0.2.0 → 0.3.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/README.md +86 -213
- package/dist/index.browser.js +74 -0
- package/dist/index.js +548 -0
- package/dist/shared-C6kPWghF.js +7806 -0
- package/package.json +32 -11
- package/.cursor/launch.json +0 -30
- package/.cursor/settings.json +0 -20
- package/.github/workflows/branch-protection.yml +0 -22
- package/.github/workflows/ci.yml +0 -165
- package/.husky/pre-commit +0 -4
- package/.prettierrc +0 -6
- package/.release-it.json +0 -12
- package/.vitest.config.examples.js +0 -12
- package/.vitest.config.js +0 -8
- package/.vscode/launch.json +0 -31
- package/AGENTS.md +0 -220
- package/DEVELOPING.md +0 -105
- package/docker-compose.yml +0 -7
- package/eslint.config.js +0 -80
- package/scripts/clear-redis.js +0 -74
- package/scripts/generate-chain/index.js +0 -111
- package/scripts/generate-lib/index.js +0 -68
- package/scripts/generate-test/index.js +0 -137
- package/scripts/generate-verblet/README.md +0 -17
- package/scripts/generate-verblet/index.js +0 -110
- package/scripts/run.sh +0 -15
- package/scripts/runner/index.js +0 -56
- package/scripts/simple-editor/README.md +0 -34
- package/scripts/simple-editor/index.js +0 -79
- package/scripts/summarize-files/index.js +0 -70
- package/src/chains/README.md +0 -30
- package/src/chains/anonymize/README.md +0 -21
- package/src/chains/anonymize/index.examples.js +0 -75
- package/src/chains/anonymize/index.js +0 -121
- package/src/chains/anonymize/index.spec.js +0 -78
- package/src/chains/bulk-central-tendency/index.examples.js +0 -138
- package/src/chains/bulk-central-tendency/index.js +0 -91
- package/src/chains/bulk-filter/README.md +0 -21
- package/src/chains/bulk-filter/index.examples.js +0 -22
- package/src/chains/bulk-filter/index.js +0 -58
- package/src/chains/bulk-filter/index.spec.js +0 -38
- package/src/chains/bulk-find/README.md +0 -16
- package/src/chains/bulk-find/index.examples.js +0 -20
- package/src/chains/bulk-find/index.js +0 -30
- package/src/chains/bulk-find/index.spec.js +0 -26
- package/src/chains/bulk-group/README.md +0 -23
- package/src/chains/bulk-group/index.examples.js +0 -18
- package/src/chains/bulk-group/index.js +0 -34
- package/src/chains/bulk-group/index.spec.js +0 -41
- package/src/chains/bulk-map/README.md +0 -43
- package/src/chains/bulk-map/index.examples.js +0 -17
- package/src/chains/bulk-map/index.js +0 -86
- package/src/chains/bulk-map/index.spec.js +0 -44
- package/src/chains/bulk-reduce/README.md +0 -12
- package/src/chains/bulk-reduce/index.examples.js +0 -15
- package/src/chains/bulk-reduce/index.js +0 -13
- package/src/chains/bulk-reduce/index.spec.js +0 -25
- package/src/chains/bulk-score/README.md +0 -16
- package/src/chains/bulk-score/bulk-score-result.json +0 -18
- package/src/chains/bulk-score/index.examples.js +0 -22
- package/src/chains/bulk-score/index.js +0 -133
- package/src/chains/bulk-score/index.spec.js +0 -30
- package/src/chains/category-samples/README.md +0 -61
- package/src/chains/category-samples/index.examples.js +0 -103
- package/src/chains/category-samples/index.js +0 -134
- package/src/chains/collect-terms/README.md +0 -12
- package/src/chains/collect-terms/index.examples.js +0 -16
- package/src/chains/collect-terms/index.js +0 -44
- package/src/chains/collect-terms/index.spec.js +0 -25
- package/src/chains/conversation/README.md +0 -26
- package/src/chains/conversation/index.examples.js +0 -398
- package/src/chains/conversation/index.js +0 -126
- package/src/chains/conversation/index.spec.js +0 -148
- package/src/chains/conversation/turn-policies.js +0 -93
- package/src/chains/conversation/turn-policies.md +0 -123
- package/src/chains/conversation/turn-policies.spec.js +0 -135
- package/src/chains/date/README.md +0 -12
- package/src/chains/date/index.examples.js +0 -47
- package/src/chains/date/index.js +0 -74
- package/src/chains/date/index.spec.js +0 -62
- package/src/chains/disambiguate/README.md +0 -22
- package/src/chains/disambiguate/disambiguate-meanings-result.json +0 -16
- package/src/chains/disambiguate/index.examples.js +0 -18
- package/src/chains/disambiguate/index.js +0 -92
- package/src/chains/disambiguate/index.spec.js +0 -25
- package/src/chains/dismantle/README.md +0 -67
- package/src/chains/dismantle/dismantle.examples.js +0 -27
- package/src/chains/dismantle/index.examples.js +0 -30
- package/src/chains/dismantle/index.js +0 -303
- package/src/chains/dismantle/index.spec.js +0 -32
- package/src/chains/expect/README.md +0 -171
- package/src/chains/expect/index.examples.js +0 -146
- package/src/chains/expect/index.js +0 -207
- package/src/chains/expect/index.spec.js +0 -324
- package/src/chains/filter-ambiguous/README.md +0 -11
- package/src/chains/filter-ambiguous/index.examples.js +0 -20
- package/src/chains/filter-ambiguous/index.js +0 -49
- package/src/chains/filter-ambiguous/index.spec.js +0 -31
- package/src/chains/glossary/README.md +0 -19
- package/src/chains/glossary/index.examples.js +0 -386
- package/src/chains/glossary/index.js +0 -75
- package/src/chains/glossary/index.spec.js +0 -19
- package/src/chains/intersections/README.md +0 -166
- package/src/chains/intersections/index.examples.js +0 -280
- package/src/chains/intersections/index.js +0 -218
- package/src/chains/intersections/intersection-result.json +0 -38
- package/src/chains/list/index.examples.js +0 -68
- package/src/chains/list/index.js +0 -214
- package/src/chains/list/index.spec.js +0 -67
- package/src/chains/list/list-result.json +0 -16
- package/src/chains/list/schema.json +0 -24
- package/src/chains/llm-logger/README.md +0 -366
- package/src/chains/llm-logger/index.js +0 -591
- package/src/chains/llm-logger/index.spec.js +0 -391
- package/src/chains/llm-logger/schema.json +0 -105
- package/src/chains/questions/index.examples.js +0 -69
- package/src/chains/questions/index.js +0 -135
- package/src/chains/questions/index.spec.js +0 -29
- package/src/chains/scan-js/index.js +0 -116
- package/src/chains/set-interval/README.md +0 -81
- package/src/chains/set-interval/index.examples.js +0 -64
- package/src/chains/set-interval/index.js +0 -152
- package/src/chains/set-interval/index.spec.js +0 -70
- package/src/chains/socratic/README.md +0 -17
- package/src/chains/socratic/index.js +0 -64
- package/src/chains/socratic/index.spec.js +0 -24
- package/src/chains/sort/index.examples.js +0 -36
- package/src/chains/sort/index.js +0 -163
- package/src/chains/sort/index.spec.js +0 -112
- package/src/chains/sort/sort-result.json +0 -16
- package/src/chains/summary-map/README.md +0 -41
- package/src/chains/summary-map/index.examples.js +0 -64
- package/src/chains/summary-map/index.js +0 -226
- package/src/chains/summary-map/index.spec.js +0 -153
- package/src/chains/test/index.js +0 -114
- package/src/chains/test-advice/index.js +0 -35
- package/src/chains/themes/README.md +0 -20
- package/src/chains/themes/index.examples.js +0 -17
- package/src/chains/themes/index.js +0 -28
- package/src/chains/themes/index.spec.js +0 -19
- package/src/chains/veiled-variants/index.examples.js +0 -18
- package/src/chains/veiled-variants/index.js +0 -107
- package/src/chains/veiled-variants/index.spec.js +0 -40
- package/src/constants/common.js +0 -13
- package/src/constants/messages.js +0 -3
- package/src/constants/models.js +0 -184
- package/src/index.js +0 -203
- package/src/json-schemas/README.md +0 -13
- package/src/json-schemas/cars-test.json +0 -11
- package/src/json-schemas/index.js +0 -12
- package/src/json-schemas/intent.json +0 -38
- package/src/json-schemas/schema-dot-org-photograph.json +0 -133
- package/src/json-schemas/schema-dot-org-place.json +0 -129
- package/src/lib/README.md +0 -26
- package/src/lib/any-signal/index.js +0 -28
- package/src/lib/assert/README.md +0 -84
- package/src/lib/assert/index.js +0 -50
- package/src/lib/bulk-filter/README.md +0 -22
- package/src/lib/bulk-filter/index.examples.js +0 -27
- package/src/lib/bulk-filter/index.js +0 -63
- package/src/lib/bulk-filter/index.spec.js +0 -38
- package/src/lib/bulk-find/README.md +0 -18
- package/src/lib/bulk-find/index.examples.js +0 -19
- package/src/lib/bulk-find/index.js +0 -30
- package/src/lib/bulk-find/index.spec.js +0 -41
- package/src/lib/chatgpt/index.js +0 -163
- package/src/lib/combinations/index.js +0 -30
- package/src/lib/combinations/index.spec.js +0 -23
- package/src/lib/editor/index.js +0 -31
- package/src/lib/functional/index.js +0 -28
- package/src/lib/logger-service/index.js +0 -32
- package/src/lib/parse-js-parts/index.js +0 -321
- package/src/lib/parse-js-parts/index.spec.js +0 -156
- package/src/lib/parse-llm-list/README.md +0 -39
- package/src/lib/parse-llm-list/index.js +0 -54
- package/src/lib/parse-llm-list/index.spec.js +0 -59
- package/src/lib/path-aliases/index.js +0 -37
- package/src/lib/path-aliases/index.spec.js +0 -64
- package/src/lib/pave/index.js +0 -34
- package/src/lib/pave/index.spec.js +0 -76
- package/src/lib/prompt-cache/index.js +0 -50
- package/src/lib/retry/index.js +0 -66
- package/src/lib/retry/index.spec.js +0 -86
- package/src/lib/ring-buffer/README.md +0 -82
- package/src/lib/ring-buffer/index.js +0 -235
- package/src/lib/ring-buffer/index.spec.js +0 -388
- package/src/lib/search-best-first/city-walk.spec.js +0 -37
- package/src/lib/search-best-first/index.js +0 -97
- package/src/lib/search-best-first/index.spec.js +0 -35
- package/src/lib/search-js-files/code-features-property-definitions.json +0 -123
- package/src/lib/search-js-files/index.examples.js +0 -22
- package/src/lib/search-js-files/index.js +0 -155
- package/src/lib/search-js-files/index.spec.js +0 -34
- package/src/lib/search-js-files/scan-file.js +0 -242
- package/src/lib/shorten-text/index.js +0 -25
- package/src/lib/shorten-text/index.spec.js +0 -68
- package/src/lib/strip-numeric/index.js +0 -5
- package/src/lib/strip-response/index.js +0 -30
- package/src/lib/template-replace/index.js +0 -23
- package/src/lib/template-replace/index.spec.js +0 -60
- package/src/lib/timed-abort-controller/index.js +0 -41
- package/src/lib/to-bool/index.js +0 -8
- package/src/lib/to-date/index.js +0 -11
- package/src/lib/to-enum/index.js +0 -14
- package/src/lib/to-number/index.js +0 -12
- package/src/lib/to-number-with-units/index.js +0 -51
- package/src/lib/transcribe/index.js +0 -78
- package/src/prompts/README.md +0 -17
- package/src/prompts/as-enum.js +0 -5
- package/src/prompts/as-json-schema.js +0 -9
- package/src/prompts/as-object-with-schema.js +0 -26
- package/src/prompts/as-schema-org-text.js +0 -25
- package/src/prompts/as-schema-org-type.js +0 -1
- package/src/prompts/blog-post.js +0 -7
- package/src/prompts/code-features.js +0 -24
- package/src/prompts/constants.js +0 -101
- package/src/prompts/features-json-schema.js +0 -27
- package/src/prompts/generate-collection.js +0 -26
- package/src/prompts/generate-list.js +0 -48
- package/src/prompts/generate-questions.js +0 -19
- package/src/prompts/index.js +0 -20
- package/src/prompts/intent.js +0 -60
- package/src/prompts/output-succinct-names.js +0 -3
- package/src/prompts/select-from-threshold.js +0 -17
- package/src/prompts/sort.js +0 -31
- package/src/prompts/style.js +0 -38
- package/src/prompts/summarize.js +0 -13
- package/src/prompts/token-budget.js +0 -3
- package/src/prompts/wrap-list.js +0 -11
- package/src/prompts/wrap-variable.js +0 -36
- package/src/services/llm-model/global-overrides.spec.js +0 -432
- package/src/services/llm-model/index.js +0 -308
- package/src/services/llm-model/model.js +0 -21
- package/src/services/llm-model/negotiate.spec.js +0 -447
- package/src/services/redis/index.js +0 -147
- package/src/test/setup.js +0 -20
- package/src/verblets/README.md +0 -26
- package/src/verblets/auto/index.examples.js +0 -31
- package/src/verblets/auto/index.js +0 -28
- package/src/verblets/auto/index.spec.js +0 -32
- package/src/verblets/bool/README.md +0 -36
- package/src/verblets/bool/index.examples.js +0 -80
- package/src/verblets/bool/index.js +0 -25
- package/src/verblets/bool/index.schema.json +0 -14
- package/src/verblets/bool/index.spec.js +0 -33
- package/src/verblets/central-tendency/README.md +0 -166
- package/src/verblets/central-tendency/central-tendency-result.json +0 -24
- package/src/verblets/central-tendency/index.examples.js +0 -196
- package/src/verblets/central-tendency/index.js +0 -171
- package/src/verblets/central-tendency/index.spec.js +0 -148
- package/src/verblets/conversation-turn/README.md +0 -33
- package/src/verblets/conversation-turn/index.examples.js +0 -218
- package/src/verblets/conversation-turn/index.js +0 -68
- package/src/verblets/conversation-turn/index.spec.js +0 -77
- package/src/verblets/conversation-turn-multi/README.md +0 -31
- package/src/verblets/conversation-turn-multi/index.examples.js +0 -160
- package/src/verblets/conversation-turn-multi/index.js +0 -104
- package/src/verblets/conversation-turn-multi/index.spec.js +0 -63
- package/src/verblets/enum/index.examples.js +0 -30
- package/src/verblets/enum/index.js +0 -18
- package/src/verblets/enum/index.spec.js +0 -35
- package/src/verblets/expect/README.md +0 -64
- package/src/verblets/expect/index.examples.js +0 -109
- package/src/verblets/expect/index.js +0 -75
- package/src/verblets/expect/index.spec.js +0 -127
- package/src/verblets/intent/index.examples.js +0 -139
- package/src/verblets/intent/index.js +0 -60
- package/src/verblets/intent/index.spec.js +0 -31
- package/src/verblets/intersection/README.md +0 -16
- package/src/verblets/intersection/index.examples.js +0 -89
- package/src/verblets/intersection/index.js +0 -125
- package/src/verblets/intersection/index.spec.js +0 -60
- package/src/verblets/intersection/intersection-result.json +0 -16
- package/src/verblets/list-expand/README.md +0 -10
- package/src/verblets/list-expand/index.examples.js +0 -14
- package/src/verblets/list-expand/index.js +0 -104
- package/src/verblets/list-expand/index.spec.js +0 -18
- package/src/verblets/list-expand/list-expand-result.json +0 -16
- package/src/verblets/list-filter/README.md +0 -22
- package/src/verblets/list-filter/index.examples.js +0 -26
- package/src/verblets/list-filter/index.js +0 -18
- package/src/verblets/list-filter/index.spec.js +0 -19
- package/src/verblets/list-find/README.md +0 -11
- package/src/verblets/list-find/index.examples.js +0 -15
- package/src/verblets/list-find/index.js +0 -17
- package/src/verblets/list-find/index.spec.js +0 -19
- package/src/verblets/list-group/README.md +0 -16
- package/src/verblets/list-group/index.examples.js +0 -16
- package/src/verblets/list-group/index.js +0 -112
- package/src/verblets/list-group/index.spec.js +0 -35
- package/src/verblets/list-group/list-group-result.json +0 -16
- package/src/verblets/list-map/README.md +0 -11
- package/src/verblets/list-map/index.examples.js +0 -15
- package/src/verblets/list-map/index.js +0 -26
- package/src/verblets/list-map/index.spec.js +0 -17
- package/src/verblets/list-reduce/README.md +0 -10
- package/src/verblets/list-reduce/index.examples.js +0 -14
- package/src/verblets/list-reduce/index.js +0 -21
- package/src/verblets/list-reduce/index.spec.js +0 -27
- package/src/verblets/list-reduce/index.spec.jsx +0 -27
- package/src/verblets/name/README.md +0 -15
- package/src/verblets/name/index.examples.js +0 -28
- package/src/verblets/name/index.js +0 -19
- package/src/verblets/name/index.spec.js +0 -33
- package/src/verblets/name-similar-to/README.md +0 -26
- package/src/verblets/name-similar-to/index.examples.js +0 -18
- package/src/verblets/name-similar-to/index.js +0 -20
- package/src/verblets/name-similar-to/index.spec.js +0 -13
- package/src/verblets/number/index.examples.js +0 -199
- package/src/verblets/number/index.js +0 -25
- package/src/verblets/number/index.spec.js +0 -33
- package/src/verblets/number-with-units/index.examples.js +0 -38
- package/src/verblets/number-with-units/index.js +0 -84
- package/src/verblets/number-with-units/index.spec.js +0 -46
- package/src/verblets/number-with-units/number-with-units-result.json +0 -23
- package/src/verblets/people-list/README.md +0 -28
- package/src/verblets/people-list/index.examples.js +0 -184
- package/src/verblets/people-list/index.js +0 -44
- package/src/verblets/people-list/index.spec.js +0 -49
- package/src/verblets/schema-org/index.examples.js +0 -51
- package/src/verblets/schema-org/index.js +0 -37
- package/src/verblets/schema-org/index.spec.js +0 -39
- package/src/verblets/sentiment/README.md +0 -10
- package/src/verblets/sentiment/index.examples.js +0 -20
- package/src/verblets/sentiment/index.js +0 -9
- package/src/verblets/sentiment/index.spec.js +0 -20
- package/src/verblets/to-object/README.md +0 -38
- package/src/verblets/to-object/index.examples.js +0 -29
- package/src/verblets/to-object/index.js +0 -131
- package/src/verblets/to-object/index.spec.js +0 -71
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import chatgpt from '../../lib/chatgpt/index.js';
|
|
2
|
-
|
|
3
|
-
function buildEqualityPrompt({ actual, expected, context }) {
|
|
4
|
-
return `Does the actual value strictly equal the expected value?\n\nActual: ${JSON.stringify(
|
|
5
|
-
actual,
|
|
6
|
-
null,
|
|
7
|
-
2
|
|
8
|
-
)}\nExpected: ${JSON.stringify(expected, null, 2)}\n\n${
|
|
9
|
-
context ? `Context: ${JSON.stringify(context, null, 2)}\n` : ''
|
|
10
|
-
}Answer only "True" or "False".`;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function buildConstraintPrompt({ actual, constraint, context }) {
|
|
14
|
-
return `Given this constraint: "${constraint}"\n\nActual value: ${JSON.stringify(
|
|
15
|
-
actual,
|
|
16
|
-
null,
|
|
17
|
-
2
|
|
18
|
-
)}\n\n${
|
|
19
|
-
context ? `Additional context: ${JSON.stringify(context, null, 2)}\n` : ''
|
|
20
|
-
}Does the actual value satisfy the constraint? Answer only "True" or "False".`;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export async function llmAssert({
|
|
24
|
-
actual,
|
|
25
|
-
equals,
|
|
26
|
-
constraint,
|
|
27
|
-
context,
|
|
28
|
-
throws = true,
|
|
29
|
-
message,
|
|
30
|
-
llm = {},
|
|
31
|
-
}) {
|
|
32
|
-
if (equals === undefined && !constraint)
|
|
33
|
-
throw new TypeError('Provide either "equals" or "constraint".');
|
|
34
|
-
|
|
35
|
-
const prompt =
|
|
36
|
-
equals !== undefined
|
|
37
|
-
? buildEqualityPrompt({ actual, expected: equals, context })
|
|
38
|
-
: buildConstraintPrompt({ actual, constraint, context });
|
|
39
|
-
|
|
40
|
-
const answer = await chatgpt(prompt, { modelOptions: llm });
|
|
41
|
-
const text = typeof answer === 'string' ? answer : answer.content;
|
|
42
|
-
const passed = /^true$/i.test(text.trim());
|
|
43
|
-
|
|
44
|
-
if (!passed && throws) {
|
|
45
|
-
let msg;
|
|
46
|
-
if (typeof message === 'function') {
|
|
47
|
-
msg = message({ actual, equals, constraint });
|
|
48
|
-
} else {
|
|
49
|
-
msg = message;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (!msg) {
|
|
53
|
-
msg =
|
|
54
|
-
equals !== undefined
|
|
55
|
-
? 'LLM assertion failed: Does the actual value strictly equal the expected value?'
|
|
56
|
-
: `LLM assertion failed: ${constraint}`;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
throw new Error(msg);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
return passed;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export default function expect(actual, shared = {}) {
|
|
66
|
-
const run = (payload, opts) => llmAssert({ actual, ...payload, ...shared, ...opts });
|
|
67
|
-
return {
|
|
68
|
-
toEqual(expected, opts) {
|
|
69
|
-
return run({ equals: expected }, opts);
|
|
70
|
-
},
|
|
71
|
-
toSatisfy(constraint, opts) {
|
|
72
|
-
return run({ constraint }, opts);
|
|
73
|
-
},
|
|
74
|
-
};
|
|
75
|
-
}
|
|
@@ -1,127 +0,0 @@
|
|
|
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,139 +0,0 @@
|
|
|
1
|
-
import Ajv from 'ajv';
|
|
2
|
-
import fs from 'node:fs/promises';
|
|
3
|
-
import { describe, expect, it, beforeAll, afterAll } from 'vitest';
|
|
4
|
-
import { aiExpect } from '../../chains/expect/index.js';
|
|
5
|
-
import { longTestTimeout } from '../../constants/common.js';
|
|
6
|
-
import { fileURLToPath } from 'url';
|
|
7
|
-
import { dirname, join } from 'path';
|
|
8
|
-
|
|
9
|
-
import intent from './index.js';
|
|
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
|
-
}
|
|
17
|
-
|
|
18
|
-
const examples = [
|
|
19
|
-
{
|
|
20
|
-
inputs: { text: 'Give me a flight to Burgas' },
|
|
21
|
-
want: { resultSchema: getIntentSchema },
|
|
22
|
-
},
|
|
23
|
-
{
|
|
24
|
-
inputs: {
|
|
25
|
-
text: 'Lookup a song by the quote \
|
|
26
|
-
"I just gotta tell you how I\'m feeling"',
|
|
27
|
-
},
|
|
28
|
-
want: { resultSchema: getIntentSchema },
|
|
29
|
-
},
|
|
30
|
-
];
|
|
31
|
-
|
|
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
|
-
|
|
48
|
-
examples.forEach((example) => {
|
|
49
|
-
it(
|
|
50
|
-
example.inputs.text,
|
|
51
|
-
async () => {
|
|
52
|
-
const result = await intent({ text: example.inputs.text });
|
|
53
|
-
|
|
54
|
-
if (example.want.resultSchema) {
|
|
55
|
-
const schema = await example.want.resultSchema();
|
|
56
|
-
const ajv = new Ajv();
|
|
57
|
-
const validate = ajv.compile(schema);
|
|
58
|
-
|
|
59
|
-
const isValid = validate(result);
|
|
60
|
-
if (!isValid) {
|
|
61
|
-
console.error('Validation errors:');
|
|
62
|
-
console.error(validate.errors);
|
|
63
|
-
console.error('Returned result:');
|
|
64
|
-
console.error(JSON.stringify(result, null, 2));
|
|
65
|
-
}
|
|
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);
|
|
79
|
-
}
|
|
80
|
-
},
|
|
81
|
-
longTestTimeout
|
|
82
|
-
);
|
|
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
|
-
);
|
|
139
|
-
});
|
|
@@ -1,60 +0,0 @@
|
|
|
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 { 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
|
-
};
|
|
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}`;
|
|
54
|
-
|
|
55
|
-
const modelOptions = await createModelOptions(llm);
|
|
56
|
-
const response = await chatGPT(prompt, { modelOptions, ...options });
|
|
57
|
-
|
|
58
|
-
// With structured outputs, response should already be parsed
|
|
59
|
-
return typeof response === 'string' ? JSON.parse(response) : response;
|
|
60
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
-
|
|
3
|
-
import intent from './index.js';
|
|
4
|
-
|
|
5
|
-
vi.mock('../../lib/chatgpt/index.js', () => ({
|
|
6
|
-
default: vi.fn().mockImplementation((text) => {
|
|
7
|
-
if (/a flight to/.test(text)) {
|
|
8
|
-
return '{}';
|
|
9
|
-
}
|
|
10
|
-
return 'undefined';
|
|
11
|
-
}),
|
|
12
|
-
}));
|
|
13
|
-
|
|
14
|
-
const examples = [
|
|
15
|
-
{
|
|
16
|
-
name: 'Basic usage',
|
|
17
|
-
inputs: { text: 'Give me a flight to Burgas' },
|
|
18
|
-
want: { typeOfResult: 'object' },
|
|
19
|
-
},
|
|
20
|
-
];
|
|
21
|
-
|
|
22
|
-
describe('Intent verblet', () => {
|
|
23
|
-
examples.forEach((example) => {
|
|
24
|
-
it(example.name, async () => {
|
|
25
|
-
const result = await intent({ text: example.inputs.text });
|
|
26
|
-
if (example.want.typeOfResult) {
|
|
27
|
-
expect(typeof result).toStrictEqual(example.want.typeOfResult);
|
|
28
|
-
}
|
|
29
|
-
});
|
|
30
|
-
});
|
|
31
|
-
});
|
|
@@ -1,16 +0,0 @@
|
|
|
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
|
-
```
|
|
@@ -1,89 +0,0 @@
|
|
|
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
|
-
});
|
|
@@ -1,125 +0,0 @@
|
|
|
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
|
-
'Identify the common elements, shared features, or overlapping aspects that connect all the given items.';
|
|
58
|
-
|
|
59
|
-
return `${contentIsQuestion} ${intro}
|
|
60
|
-
|
|
61
|
-
${itemsBlock}
|
|
62
|
-
|
|
63
|
-
Provide a clear, focused list of items that represent the intersection or commonality between all the given categories.
|
|
64
|
-
|
|
65
|
-
${tryCompleteData} ${onlyJSONStringArray}`;
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
export default async function intersection(items, config = {}) {
|
|
69
|
-
if (!Array.isArray(items) || items.length === 0) {
|
|
70
|
-
return [];
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Intersection requires at least 2 items
|
|
74
|
-
if (items.length < 2) {
|
|
75
|
-
return [];
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const { llm, ...options } = config;
|
|
79
|
-
const modelOptions = await createModelOptions(llm);
|
|
80
|
-
|
|
81
|
-
const output = await chatGPT(buildPrompt(items, options), {
|
|
82
|
-
modelOptions,
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
// Handle JSON parsing with robust error handling
|
|
86
|
-
let parsed;
|
|
87
|
-
try {
|
|
88
|
-
if (typeof output === 'string') {
|
|
89
|
-
// Try to clean up common JSON issues
|
|
90
|
-
let cleanedOutput = output.trim();
|
|
91
|
-
|
|
92
|
-
// Remove any text before the first { or [
|
|
93
|
-
const jsonStart = Math.min(
|
|
94
|
-
cleanedOutput.indexOf('{') !== -1 ? cleanedOutput.indexOf('{') : Infinity,
|
|
95
|
-
cleanedOutput.indexOf('[') !== -1 ? cleanedOutput.indexOf('[') : Infinity
|
|
96
|
-
);
|
|
97
|
-
|
|
98
|
-
if (jsonStart !== Infinity && jsonStart > 0) {
|
|
99
|
-
cleanedOutput = cleanedOutput.substring(jsonStart);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// Remove any text after the last } or ]
|
|
103
|
-
const lastBrace = cleanedOutput.lastIndexOf('}');
|
|
104
|
-
const lastBracket = cleanedOutput.lastIndexOf(']');
|
|
105
|
-
const jsonEnd = Math.max(lastBrace, lastBracket);
|
|
106
|
-
|
|
107
|
-
if (jsonEnd !== -1 && jsonEnd < cleanedOutput.length - 1) {
|
|
108
|
-
cleanedOutput = cleanedOutput.substring(0, jsonEnd + 1);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
parsed = JSON.parse(cleanedOutput);
|
|
112
|
-
} else {
|
|
113
|
-
parsed = output;
|
|
114
|
-
}
|
|
115
|
-
} catch (error) {
|
|
116
|
-
console.error('Failed to parse JSON response from LLM:', error.message);
|
|
117
|
-
console.error('Raw output:', output);
|
|
118
|
-
|
|
119
|
-
return [];
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// Extract the items array from the object structure
|
|
123
|
-
const resultArray = parsed?.items || parsed;
|
|
124
|
-
return Array.isArray(resultArray) ? resultArray.filter(Boolean) : [];
|
|
125
|
-
}
|