@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.
- 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 +120 -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 +254 -0
- package/eslint.config.js +80 -0
- package/package.json +29 -17
- 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 +8 -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 +44 -47
- 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 +26 -9
- 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 +16 -22
- 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 +95 -7
- 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,173 @@
|
|
|
1
|
+
import chatgpt from '../../lib/chatgpt/index.js';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Get the calling file and line number from stack trace
|
|
7
|
+
*/
|
|
8
|
+
function getCallerInfo() {
|
|
9
|
+
const { stack } = new Error();
|
|
10
|
+
const lines = stack.split('\n');
|
|
11
|
+
|
|
12
|
+
// Find the first line that's not this file
|
|
13
|
+
for (let i = 2; i < lines.length; i++) {
|
|
14
|
+
const line = lines[i];
|
|
15
|
+
if (line.includes('at ') && !line.includes('expect')) {
|
|
16
|
+
const match = line.match(/at .* \((.+):(\d+):\d+\)/);
|
|
17
|
+
if (match) {
|
|
18
|
+
return { file: match[1], line: parseInt(match[2]) };
|
|
19
|
+
}
|
|
20
|
+
// Handle cases without parentheses
|
|
21
|
+
const simpleMatch = line.match(/at (.+):(\d+):\d+/);
|
|
22
|
+
if (simpleMatch) {
|
|
23
|
+
return { file: simpleMatch[1], line: parseInt(simpleMatch[2]) };
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return { file: 'unknown', line: 0 };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Read code context around the assertion
|
|
32
|
+
*/
|
|
33
|
+
function getCodeContext(filePath, lineNumber) {
|
|
34
|
+
try {
|
|
35
|
+
if (!fs.existsSync(filePath)) return null;
|
|
36
|
+
|
|
37
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
38
|
+
const lines = content.split('\n');
|
|
39
|
+
|
|
40
|
+
// Get 400 lines before and 100 lines after
|
|
41
|
+
const start = Math.max(0, lineNumber - 400);
|
|
42
|
+
const end = Math.min(lines.length, lineNumber + 100);
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
lines: lines.slice(start, end),
|
|
46
|
+
startLine: start + 1,
|
|
47
|
+
assertionLine: lineNumber,
|
|
48
|
+
};
|
|
49
|
+
} catch {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Generate intelligent advice for failed assertions
|
|
56
|
+
*/
|
|
57
|
+
async function generateAdvice(actual, expected, constraint, codeContext) {
|
|
58
|
+
const contextInfo = codeContext
|
|
59
|
+
? `
|
|
60
|
+
Code context around assertion (line ${codeContext.assertionLine}):
|
|
61
|
+
\`\`\`
|
|
62
|
+
${codeContext.lines.join('\n')}
|
|
63
|
+
\`\`\`
|
|
64
|
+
`
|
|
65
|
+
: '';
|
|
66
|
+
|
|
67
|
+
const prompt = `You are a debugging assistant helping with a failed LLM assertion.
|
|
68
|
+
|
|
69
|
+
ASSERTION DETAILS:
|
|
70
|
+
- Actual value: ${JSON.stringify(actual, null, 2)}
|
|
71
|
+
- Expected value: ${expected ? JSON.stringify(expected, null, 2) : 'N/A'}
|
|
72
|
+
- Constraint: ${constraint || 'N/A'}
|
|
73
|
+
|
|
74
|
+
${contextInfo}
|
|
75
|
+
|
|
76
|
+
Provide structured debugging advice in this format:
|
|
77
|
+
|
|
78
|
+
ISSUE: [Brief description of why the assertion failed]
|
|
79
|
+
FIX: [Specific actionable steps to resolve the issue]
|
|
80
|
+
CONTEXT: [Additional context about the problem and potential root causes]
|
|
81
|
+
|
|
82
|
+
Keep your response concise but actionable. Focus on practical solutions.`;
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
return await chatgpt(prompt);
|
|
86
|
+
} catch {
|
|
87
|
+
return 'Unable to generate debugging advice due to LLM error.';
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Enhanced LLM expectation with debugging features
|
|
93
|
+
*/
|
|
94
|
+
export async function expect(actual, expected, constraint) {
|
|
95
|
+
const mode = process.env.LLM_EXPECT_MODE || 'none';
|
|
96
|
+
const callerInfo = getCallerInfo();
|
|
97
|
+
|
|
98
|
+
// Build the assertion prompt
|
|
99
|
+
let prompt;
|
|
100
|
+
if (constraint && expected === undefined) {
|
|
101
|
+
// Constraint-only mode
|
|
102
|
+
prompt = `Given this constraint: "${constraint}"
|
|
103
|
+
|
|
104
|
+
Actual value: ${JSON.stringify(actual, null, 2)}
|
|
105
|
+
|
|
106
|
+
Does the actual value satisfy the constraint? Answer only "True" or "False".`;
|
|
107
|
+
} else if (constraint && expected !== undefined) {
|
|
108
|
+
// Both expected and constraint provided - use constraint
|
|
109
|
+
prompt = `Given this constraint: "${constraint}"
|
|
110
|
+
|
|
111
|
+
Actual value: ${JSON.stringify(actual, null, 2)}
|
|
112
|
+
Expected value: ${JSON.stringify(expected, null, 2)}
|
|
113
|
+
|
|
114
|
+
Does the actual value satisfy the constraint? Answer only "True" or "False".`;
|
|
115
|
+
} else if (expected !== undefined) {
|
|
116
|
+
// Expected value only
|
|
117
|
+
prompt = `Does the actual value strictly equal the expected value?
|
|
118
|
+
|
|
119
|
+
Actual: ${JSON.stringify(actual, null, 2)}
|
|
120
|
+
Expected: ${JSON.stringify(expected, null, 2)}
|
|
121
|
+
|
|
122
|
+
Answer only "True" or "False".`;
|
|
123
|
+
} else {
|
|
124
|
+
throw new Error('Either expected value or constraint must be provided');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
try {
|
|
128
|
+
const response = await chatgpt(prompt);
|
|
129
|
+
const passed = response.trim().toLowerCase() === 'true';
|
|
130
|
+
|
|
131
|
+
// Prepare result structure
|
|
132
|
+
const result = {
|
|
133
|
+
passed,
|
|
134
|
+
advice: null,
|
|
135
|
+
file: callerInfo.file,
|
|
136
|
+
line: callerInfo.line,
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
// Handle failure cases based on mode
|
|
140
|
+
if (!passed) {
|
|
141
|
+
if (mode === 'info' || mode === 'error') {
|
|
142
|
+
const codeContext = getCodeContext(callerInfo.file, callerInfo.line);
|
|
143
|
+
result.advice = await generateAdvice(actual, expected, constraint, codeContext);
|
|
144
|
+
|
|
145
|
+
const message = `LLM Assertion Failed at ${path.basename(callerInfo.file)}:${
|
|
146
|
+
callerInfo.line
|
|
147
|
+
}
|
|
148
|
+
${result.advice}`;
|
|
149
|
+
|
|
150
|
+
if (mode === 'error') {
|
|
151
|
+
throw new Error(message);
|
|
152
|
+
} else if (mode === 'info') {
|
|
153
|
+
console.info(message);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return [passed, result];
|
|
159
|
+
} catch (error) {
|
|
160
|
+
if (error.message.includes('LLM Assertion Failed')) {
|
|
161
|
+
throw error; // Re-throw our custom errors
|
|
162
|
+
}
|
|
163
|
+
throw new Error(`LLM expectation failed due to error: ${error.message}`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Simple LLM expectation (backward compatibility)
|
|
169
|
+
*/
|
|
170
|
+
export default async function expectSimple(actual, expected, constraint) {
|
|
171
|
+
const [passed] = await expect(actual, expected, constraint);
|
|
172
|
+
return passed;
|
|
173
|
+
}
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
import { describe, expect as vitestExpect, it, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import aiExpectSimple, { expect as 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
|
+
if (prompt.includes('Actual: "hello"') && prompt.includes('Expected: "goodbye"')) {
|
|
17
|
+
return 'False';
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Handle constraint-based validations (format: "Given this constraint:")
|
|
22
|
+
if (prompt.includes('Given this constraint:')) {
|
|
23
|
+
if (prompt.includes('Is this a greeting?') && prompt.includes('Hello world!')) {
|
|
24
|
+
return 'True';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (prompt.includes('Is this text professional and grammatically correct?')) {
|
|
28
|
+
if (prompt.includes('well-written, professional email')) {
|
|
29
|
+
return 'True';
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (prompt.includes('Does this person data look realistic?')) {
|
|
34
|
+
if (prompt.includes('John Doe') && prompt.includes('"age": 30')) {
|
|
35
|
+
return 'True';
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (prompt.includes('Is this recommendation specific and actionable?')) {
|
|
40
|
+
if (prompt.includes('Increase marketing budget by 20%')) {
|
|
41
|
+
return 'True';
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (prompt.includes('Does this profile represent an experienced software developer')) {
|
|
46
|
+
if (prompt.includes('Alice Johnson') && prompt.includes('JavaScript')) {
|
|
47
|
+
return 'True';
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (prompt.includes('Is this story opening engaging')) {
|
|
52
|
+
if (prompt.includes('Once upon a time')) {
|
|
53
|
+
return 'True';
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Default to False for unmatched cases
|
|
59
|
+
return 'False';
|
|
60
|
+
}),
|
|
61
|
+
}));
|
|
62
|
+
|
|
63
|
+
describe('expect chain', () => {
|
|
64
|
+
let originalEnv;
|
|
65
|
+
|
|
66
|
+
beforeEach(() => {
|
|
67
|
+
originalEnv = process.env.LLM_EXPECT_MODE;
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
afterEach(() => {
|
|
71
|
+
if (originalEnv !== undefined) {
|
|
72
|
+
process.env.LLM_EXPECT_MODE = originalEnv;
|
|
73
|
+
} else {
|
|
74
|
+
delete process.env.LLM_EXPECT_MODE;
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
describe('Enhanced API', () => {
|
|
79
|
+
it(
|
|
80
|
+
'should return structured results in none mode',
|
|
81
|
+
async () => {
|
|
82
|
+
process.env.LLM_EXPECT_MODE = 'none';
|
|
83
|
+
|
|
84
|
+
const [passed, details] = await aiExpect('hello', 'hello');
|
|
85
|
+
|
|
86
|
+
vitestExpect(passed).toBe(true);
|
|
87
|
+
vitestExpect(details).toHaveProperty('passed', true);
|
|
88
|
+
vitestExpect(details).toHaveProperty('advice');
|
|
89
|
+
vitestExpect(details).toHaveProperty('file');
|
|
90
|
+
vitestExpect(details).toHaveProperty('line');
|
|
91
|
+
},
|
|
92
|
+
longTestTimeout
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
it(
|
|
96
|
+
'should handle failed assertions in none mode',
|
|
97
|
+
async () => {
|
|
98
|
+
process.env.LLM_EXPECT_MODE = 'none';
|
|
99
|
+
|
|
100
|
+
const [passed, details] = await aiExpect('hello', 'goodbye');
|
|
101
|
+
|
|
102
|
+
vitestExpect(passed).toBe(false);
|
|
103
|
+
vitestExpect(details.passed).toBe(false);
|
|
104
|
+
vitestExpect(details).toHaveProperty('advice');
|
|
105
|
+
vitestExpect(details).toHaveProperty('file');
|
|
106
|
+
vitestExpect(details).toHaveProperty('line');
|
|
107
|
+
},
|
|
108
|
+
longTestTimeout
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
it(
|
|
112
|
+
'should throw errors in error mode',
|
|
113
|
+
async () => {
|
|
114
|
+
process.env.LLM_EXPECT_MODE = 'error';
|
|
115
|
+
|
|
116
|
+
await vitestExpect(async () => {
|
|
117
|
+
await aiExpect('hello', 'goodbye');
|
|
118
|
+
}).rejects.toThrow('LLM Assertion Failed');
|
|
119
|
+
},
|
|
120
|
+
longTestTimeout
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
it(
|
|
124
|
+
'should log in info mode',
|
|
125
|
+
async () => {
|
|
126
|
+
process.env.LLM_EXPECT_MODE = 'info';
|
|
127
|
+
const consoleSpy = vi.spyOn(console, 'info').mockImplementation(() => {});
|
|
128
|
+
|
|
129
|
+
const [passed] = await aiExpect('hello', 'goodbye');
|
|
130
|
+
|
|
131
|
+
vitestExpect(passed).toBe(false);
|
|
132
|
+
vitestExpect(consoleSpy).toHaveBeenCalledWith(
|
|
133
|
+
vitestExpect.stringContaining('LLM Assertion Failed')
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
consoleSpy.mockRestore();
|
|
137
|
+
},
|
|
138
|
+
longTestTimeout
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
it(
|
|
142
|
+
'should handle constraint-based validation',
|
|
143
|
+
async () => {
|
|
144
|
+
process.env.LLM_EXPECT_MODE = 'none';
|
|
145
|
+
|
|
146
|
+
const [passed, details] = await aiExpect('Hello world!', undefined, 'Is this a greeting?');
|
|
147
|
+
|
|
148
|
+
vitestExpect(passed).toBe(true);
|
|
149
|
+
vitestExpect(details.passed).toBe(true);
|
|
150
|
+
},
|
|
151
|
+
longTestTimeout
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
it(
|
|
155
|
+
'should validate content quality',
|
|
156
|
+
async () => {
|
|
157
|
+
const [passed, details] = await aiExpect(
|
|
158
|
+
'This is a well-written, professional email with proper grammar and clear intent.',
|
|
159
|
+
undefined,
|
|
160
|
+
'Is this text professional and grammatically correct?'
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
vitestExpect(passed).toBe(true);
|
|
164
|
+
vitestExpect(details).toHaveProperty('file');
|
|
165
|
+
vitestExpect(details).toHaveProperty('line');
|
|
166
|
+
},
|
|
167
|
+
longTestTimeout
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
it(
|
|
171
|
+
'should validate data structures',
|
|
172
|
+
async () => {
|
|
173
|
+
const [passed] = await aiExpect(
|
|
174
|
+
{ name: 'John Doe', age: 30, city: 'New York' },
|
|
175
|
+
undefined,
|
|
176
|
+
'Does this person data look realistic?'
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
vitestExpect(passed).toBe(true);
|
|
180
|
+
},
|
|
181
|
+
longTestTimeout
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
it(
|
|
185
|
+
'should handle business logic validation',
|
|
186
|
+
async () => {
|
|
187
|
+
const [passed] = await aiExpect(
|
|
188
|
+
'Increase marketing budget by 20% for Q4 to boost holiday sales',
|
|
189
|
+
undefined,
|
|
190
|
+
'Is this recommendation specific and actionable?'
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
vitestExpect(passed).toBe(true);
|
|
194
|
+
},
|
|
195
|
+
longTestTimeout
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
it('should throw error when neither expected nor constraint provided', async () => {
|
|
199
|
+
await vitestExpect(async () => {
|
|
200
|
+
await aiExpect('test value');
|
|
201
|
+
}).rejects.toThrow('Either expected value or constraint must be provided');
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
describe('Simple API (backward compatibility)', () => {
|
|
206
|
+
it(
|
|
207
|
+
'should pass for exact equality',
|
|
208
|
+
async () => {
|
|
209
|
+
const result = await aiExpectSimple('hello', 'hello');
|
|
210
|
+
vitestExpect(result).toBe(true);
|
|
211
|
+
},
|
|
212
|
+
longTestTimeout
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
it(
|
|
216
|
+
'should pass for constraint-based validation',
|
|
217
|
+
async () => {
|
|
218
|
+
const result = await aiExpectSimple('Hello world!', undefined, 'Is this a greeting?');
|
|
219
|
+
vitestExpect(result).toBe(true);
|
|
220
|
+
},
|
|
221
|
+
longTestTimeout
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
it(
|
|
225
|
+
'should fail for non-matching values',
|
|
226
|
+
async () => {
|
|
227
|
+
const result = await aiExpectSimple('goodbye', 'hello');
|
|
228
|
+
vitestExpect(result).toBe(false);
|
|
229
|
+
},
|
|
230
|
+
longTestTimeout
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
it(
|
|
234
|
+
'should validate content quality',
|
|
235
|
+
async () => {
|
|
236
|
+
const result = await aiExpectSimple(
|
|
237
|
+
'This is a well-written, professional email with proper grammar.',
|
|
238
|
+
undefined,
|
|
239
|
+
'Is this text professional and grammatically correct?'
|
|
240
|
+
);
|
|
241
|
+
vitestExpect(result).toBe(true);
|
|
242
|
+
},
|
|
243
|
+
longTestTimeout
|
|
244
|
+
);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
describe('Environment variable handling', () => {
|
|
248
|
+
it(
|
|
249
|
+
'should default to none mode when env var is not set',
|
|
250
|
+
async () => {
|
|
251
|
+
delete process.env.LLM_EXPECT_MODE;
|
|
252
|
+
|
|
253
|
+
const [passed] = await aiExpect('hello', 'goodbye');
|
|
254
|
+
vitestExpect(passed).toBe(false);
|
|
255
|
+
// Should not throw in none mode
|
|
256
|
+
},
|
|
257
|
+
longTestTimeout
|
|
258
|
+
);
|
|
259
|
+
|
|
260
|
+
it(
|
|
261
|
+
'should handle invalid env var values',
|
|
262
|
+
async () => {
|
|
263
|
+
process.env.LLM_EXPECT_MODE = 'invalid';
|
|
264
|
+
|
|
265
|
+
const [passed] = await aiExpect('hello', 'goodbye');
|
|
266
|
+
vitestExpect(passed).toBe(false);
|
|
267
|
+
// Should default to none mode and not throw
|
|
268
|
+
},
|
|
269
|
+
longTestTimeout
|
|
270
|
+
);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
describe('Advanced features', () => {
|
|
274
|
+
it(
|
|
275
|
+
'should provide file and line information',
|
|
276
|
+
async () => {
|
|
277
|
+
const [, details] = await aiExpect('hello', 'hello');
|
|
278
|
+
|
|
279
|
+
vitestExpect(details.file).toBeDefined();
|
|
280
|
+
vitestExpect(details.line).toBeTypeOf('number');
|
|
281
|
+
vitestExpect(details.line).toBeGreaterThan(0);
|
|
282
|
+
},
|
|
283
|
+
longTestTimeout
|
|
284
|
+
);
|
|
285
|
+
|
|
286
|
+
it(
|
|
287
|
+
'should handle complex object comparisons',
|
|
288
|
+
async () => {
|
|
289
|
+
const userProfile = {
|
|
290
|
+
name: 'Alice Johnson',
|
|
291
|
+
skills: ['JavaScript', 'Python', 'React'],
|
|
292
|
+
experience: '5 years',
|
|
293
|
+
level: 'Senior Developer',
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
const [passed] = await aiExpect(
|
|
297
|
+
userProfile,
|
|
298
|
+
undefined,
|
|
299
|
+
'Does this profile represent an experienced software developer with modern skills?'
|
|
300
|
+
);
|
|
301
|
+
|
|
302
|
+
vitestExpect(passed).toBe(true);
|
|
303
|
+
},
|
|
304
|
+
longTestTimeout
|
|
305
|
+
);
|
|
306
|
+
|
|
307
|
+
it(
|
|
308
|
+
'should validate creative content',
|
|
309
|
+
async () => {
|
|
310
|
+
const storyOpening =
|
|
311
|
+
'Once upon a time, in a land far away, there lived a brave knight who embarked on a quest to save the kingdom from an ancient curse.';
|
|
312
|
+
|
|
313
|
+
const [passed] = await aiExpect(
|
|
314
|
+
storyOpening,
|
|
315
|
+
undefined,
|
|
316
|
+
'Is this story opening engaging and sets up a clear adventure narrative?'
|
|
317
|
+
);
|
|
318
|
+
|
|
319
|
+
vitestExpect(passed).toBe(true);
|
|
320
|
+
},
|
|
321
|
+
longTestTimeout
|
|
322
|
+
);
|
|
323
|
+
});
|
|
324
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# filter-ambiguous
|
|
2
|
+
|
|
3
|
+
Identify ambiguous words or phrases in text. Sentences are first scored for overall uncertainty and the highest scoring ones are examined for unclear terms. Each term is scored in context so you can zero in on the most confusing language.
|
|
4
|
+
|
|
5
|
+
```javascript
|
|
6
|
+
import filterAmbiguous from './index.js';
|
|
7
|
+
|
|
8
|
+
const text = `I saw her duck\nThe magician made a little girl disappear\nHe fed her dog food`;
|
|
9
|
+
const result = await filterAmbiguous(text, { topN: 3 });
|
|
10
|
+
// => [{ term: 'duck', sentence: 'I saw her duck', score: 9 }, ...]
|
|
11
|
+
```
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { longTestTimeout } from '../../constants/common.js';
|
|
3
|
+
import filterAmbiguous from './index.js';
|
|
4
|
+
|
|
5
|
+
describe('filterAmbiguous examples', () => {
|
|
6
|
+
it(
|
|
7
|
+
'highlights unclear language',
|
|
8
|
+
async () => {
|
|
9
|
+
const text = `I saw her duck\nThe magician made a little girl disappear\nHe fed her dog food`;
|
|
10
|
+
const result = await filterAmbiguous(text, { topN: 2 });
|
|
11
|
+
expect(result.length).toBeGreaterThan(0);
|
|
12
|
+
result.forEach((r) => {
|
|
13
|
+
expect(r).toHaveProperty('term');
|
|
14
|
+
expect(r).toHaveProperty('sentence');
|
|
15
|
+
expect(typeof r.score).toBe('number');
|
|
16
|
+
});
|
|
17
|
+
},
|
|
18
|
+
longTestTimeout
|
|
19
|
+
);
|
|
20
|
+
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import list from '../list/index.js';
|
|
2
|
+
import bulkScore from '../bulk-score/index.js';
|
|
3
|
+
|
|
4
|
+
export default async function filterAmbiguous(text, config = {}) {
|
|
5
|
+
const { topN = 10, chunkSize = 5, llm, ...options } = config;
|
|
6
|
+
if (!text) return [];
|
|
7
|
+
const sentences = text
|
|
8
|
+
.split('\n')
|
|
9
|
+
.map((s) => s.trim())
|
|
10
|
+
.filter(Boolean);
|
|
11
|
+
if (sentences.length === 0) return [];
|
|
12
|
+
|
|
13
|
+
const { scores: sentenceScores } = await bulkScore(
|
|
14
|
+
sentences,
|
|
15
|
+
'How ambiguous or easily misinterpreted is this sentence?',
|
|
16
|
+
{ chunkSize, llm, ...options }
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
const rankedSentences = sentences
|
|
20
|
+
.map((s, i) => ({ sentence: s, score: sentenceScores[i] ?? 0 }))
|
|
21
|
+
.sort((a, b) => b.score - a.score)
|
|
22
|
+
.slice(0, topN);
|
|
23
|
+
|
|
24
|
+
const termPairs = [];
|
|
25
|
+
for (const { sentence } of rankedSentences) {
|
|
26
|
+
// eslint-disable-next-line no-await-in-loop
|
|
27
|
+
const terms = await list('Ambiguous words or short phrases', {
|
|
28
|
+
attachments: { text: sentence },
|
|
29
|
+
targetNewItemsCount: 5,
|
|
30
|
+
llm,
|
|
31
|
+
...options,
|
|
32
|
+
});
|
|
33
|
+
terms.forEach((term) => {
|
|
34
|
+
termPairs.push({ term, sentence });
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (termPairs.length === 0) return [];
|
|
39
|
+
|
|
40
|
+
const { scores } = await bulkScore(
|
|
41
|
+
termPairs.map((p) => `${p.term} | ${p.sentence}`),
|
|
42
|
+
'Score how ambiguous the term is within the sentence.',
|
|
43
|
+
{ chunkSize, llm, ...options }
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
const scored = termPairs.map((p, i) => ({ ...p, score: scores[i] }));
|
|
47
|
+
scored.sort((a, b) => (b.score || 0) - (a.score || 0));
|
|
48
|
+
return scored.slice(0, topN);
|
|
49
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import filterAmbiguous from './index.js';
|
|
3
|
+
import bulkScore from '../bulk-score/index.js';
|
|
4
|
+
import list from '../list/index.js';
|
|
5
|
+
|
|
6
|
+
vi.mock('../bulk-score/index.js', () => ({
|
|
7
|
+
default: vi.fn(),
|
|
8
|
+
}));
|
|
9
|
+
|
|
10
|
+
vi.mock('../list/index.js', () => ({
|
|
11
|
+
default: vi.fn(),
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
vi.clearAllMocks();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
describe('filterAmbiguous chain', () => {
|
|
19
|
+
it('returns scored ambiguous terms', async () => {
|
|
20
|
+
bulkScore
|
|
21
|
+
.mockResolvedValueOnce({ scores: [1, 9], reference: [] })
|
|
22
|
+
.mockResolvedValueOnce({ scores: [8, 3], reference: [] });
|
|
23
|
+
list.mockResolvedValueOnce(['alpha', 'beta']).mockResolvedValueOnce([]);
|
|
24
|
+
|
|
25
|
+
const result = await filterAmbiguous('s1\ns2', { topN: 1 });
|
|
26
|
+
|
|
27
|
+
expect(result).toStrictEqual([{ term: 'alpha', sentence: 's2', score: 8 }]);
|
|
28
|
+
expect(bulkScore).toHaveBeenCalled();
|
|
29
|
+
expect(list).toHaveBeenCalled();
|
|
30
|
+
});
|
|
31
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# glossary
|
|
2
|
+
|
|
3
|
+
Identify difficult or technical terms in any text so you can explain them later.
|
|
4
|
+
The chain breaks long passages into paragraphs and uses the `bulk-map` retry
|
|
5
|
+
utility to collect candidate terms from each chunk. Any failed paragraphs are
|
|
6
|
+
automatically retried. Finally the terms are ranked by importance using `sort`.
|
|
7
|
+
|
|
8
|
+
```javascript
|
|
9
|
+
import glossary from './index.js';
|
|
10
|
+
|
|
11
|
+
const blog = `The chef explained how umami develops through the Maillard reaction alongside sous-vide techniques.`;
|
|
12
|
+
|
|
13
|
+
const terms = await glossary(blog, { maxTerms: 3 });
|
|
14
|
+
console.log(terms);
|
|
15
|
+
// => ['Maillard reaction', 'sous-vide', 'umami']
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
This is handy when you want to add a quick glossary sidebar to a dense article
|
|
19
|
+
so everyday readers aren't left guessing what key terms mean.
|