@far-world-labs/verblets 0.1.1
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/.eslintrc.json +42 -0
- package/.husky/pre-commit +4 -0
- package/.release-it.json +9 -0
- package/.vite.config.examples.js +8 -0
- package/.vite.config.js +8 -0
- package/docker-compose.yml +7 -0
- package/docs/README.md +41 -0
- package/docs/babel.config.js +3 -0
- package/docs/blog/2019-05-28-first-blog-post.md +12 -0
- package/docs/blog/2019-05-29-long-blog-post.md +44 -0
- package/docs/blog/2021-08-01-mdx-blog-post.mdx +20 -0
- package/docs/blog/2021-08-26-welcome/docusaurus-plushie-banner.jpeg +0 -0
- package/docs/blog/2021-08-26-welcome/index.md +25 -0
- package/docs/blog/authors.yml +17 -0
- package/docs/docs/api/bool.md +74 -0
- package/docs/docs/api/search.md +51 -0
- package/docs/docs/intro.md +47 -0
- package/docs/docs/tutorial-basics/_category_.json +8 -0
- package/docs/docs/tutorial-basics/congratulations.md +23 -0
- package/docs/docs/tutorial-basics/create-a-blog-post.md +34 -0
- package/docs/docs/tutorial-basics/create-a-document.md +57 -0
- package/docs/docs/tutorial-basics/create-a-page.md +43 -0
- package/docs/docs/tutorial-basics/deploy-your-site.md +31 -0
- package/docs/docs/tutorial-basics/markdown-features.mdx +152 -0
- package/docs/docs/tutorial-extras/_category_.json +7 -0
- 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 +55 -0
- package/docs/docs/tutorial-extras/translate-your-site.md +88 -0
- package/docs/docusaurus.config.js +120 -0
- package/docs/package.json +44 -0
- package/docs/sidebars.js +31 -0
- package/docs/src/components/HomepageFeatures/index.js +61 -0
- package/docs/src/components/HomepageFeatures/styles.module.css +11 -0
- package/docs/src/css/custom.css +30 -0
- package/docs/src/pages/index.js +43 -0
- package/docs/src/pages/index.module.css +23 -0
- package/docs/src/pages/markdown-page.md +7 -0
- 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 +1 -0
- package/docs/static/img/undraw_docusaurus_mountain.svg +171 -0
- package/docs/static/img/undraw_docusaurus_react.svg +170 -0
- package/docs/static/img/undraw_docusaurus_tree.svg +40 -0
- package/package.json +75 -0
- package/scripts/generate-chain/index.js +111 -0
- package/scripts/generate-lib/index.js +68 -0
- package/scripts/generate-test/index.js +111 -0
- package/scripts/generate-verblet/README.md +17 -0
- package/scripts/generate-verblet/index.js +110 -0
- package/scripts/run.sh +15 -0
- package/scripts/runner/index.js +30 -0
- package/scripts/simple-editor/README.md +34 -0
- package/scripts/simple-editor/index.js +68 -0
- package/scripts/summarize-files/index.js +46 -0
- package/src/chains/dismantle/dismantle.examples.js +0 -0
- package/src/chains/dismantle/index.examples.js +30 -0
- package/src/chains/dismantle/index.js +314 -0
- package/src/chains/dismantle/index.spec.js +33 -0
- package/src/chains/list/index.examples.js +72 -0
- package/src/chains/list/index.js +161 -0
- package/src/chains/list/index.spec.js +68 -0
- package/src/chains/list/schema.json +24 -0
- package/src/chains/questions/index.examples.js +68 -0
- package/src/chains/questions/index.js +136 -0
- package/src/chains/questions/index.spec.js +29 -0
- package/src/chains/scan-js/index.js +119 -0
- package/src/chains/sort/index.examples.js +40 -0
- package/src/chains/sort/index.js +113 -0
- package/src/chains/sort/index.spec.js +115 -0
- package/src/chains/summary-map/README.md +33 -0
- package/src/chains/summary-map/index.examples.js +57 -0
- package/src/chains/summary-map/index.js +208 -0
- package/src/chains/summary-map/index.spec.js +78 -0
- package/src/chains/test/index.js +118 -0
- package/src/chains/test-advice/index.js +36 -0
- package/src/constants/common.js +9 -0
- package/src/constants/messages.js +3 -0
- package/src/constants/openai.js +65 -0
- package/src/index.js +33 -0
- package/src/json-schemas/cars-test.json +11 -0
- package/src/json-schemas/index.js +18 -0
- package/src/json-schemas/intent.json +38 -0
- package/src/json-schemas/schema-dot-org-photograph.json +127 -0
- package/src/json-schemas/schema-dot-org-place.json +56 -0
- package/src/lib/any-signal/index.js +28 -0
- package/src/lib/chatgpt/index.js +143 -0
- package/src/lib/editor/index.js +31 -0
- package/src/lib/parse-js-parts/index.js +333 -0
- package/src/lib/parse-js-parts/index.spec.js +156 -0
- package/src/lib/path-aliases/index.js +39 -0
- package/src/lib/path-aliases/index.spec.js +70 -0
- package/src/lib/pave/index.js +34 -0
- package/src/lib/pave/index.spec.js +73 -0
- package/src/lib/prompt-cache/index.js +46 -0
- package/src/lib/retry/index.js +63 -0
- package/src/lib/retry/index.spec.js +86 -0
- package/src/lib/search-best-first/index.js +66 -0
- package/src/lib/search-js-files/code-features-property-definitions.json +123 -0
- package/src/lib/search-js-files/index.examples.js +22 -0
- package/src/lib/search-js-files/index.js +158 -0
- package/src/lib/search-js-files/index.spec.js +34 -0
- package/src/lib/search-js-files/scan-file.js +253 -0
- package/src/lib/shorten-text/index.js +30 -0
- package/src/lib/shorten-text/index.spec.js +68 -0
- package/src/lib/strip-numeric/index.js +5 -0
- package/src/lib/strip-response/index.js +35 -0
- package/src/lib/timed-abort-controller/index.js +41 -0
- package/src/lib/to-bool/index.js +8 -0
- package/src/lib/to-enum/index.js +14 -0
- package/src/lib/to-number/index.js +12 -0
- package/src/lib/to-number-with-units/index.js +51 -0
- package/src/lib/transcribe/index.js +61 -0
- package/src/prompts/README.md +15 -0
- package/src/prompts/as-enum.js +5 -0
- package/src/prompts/as-json-schema.js +9 -0
- package/src/prompts/as-object-with-schema.js +31 -0
- package/src/prompts/as-schema-org-text.js +17 -0
- package/src/prompts/as-schema-org-type.js +1 -0
- package/src/prompts/blog-post.js +7 -0
- package/src/prompts/code-features.js +28 -0
- package/src/prompts/constants.js +101 -0
- package/src/prompts/features-json-schema.js +27 -0
- package/src/prompts/generate-collection.js +26 -0
- package/src/prompts/generate-list.js +48 -0
- package/src/prompts/generate-questions.js +19 -0
- package/src/prompts/index.js +20 -0
- package/src/prompts/intent.js +66 -0
- package/src/prompts/output-succinct-names.js +3 -0
- package/src/prompts/select-from-threshold.js +18 -0
- package/src/prompts/sort.js +35 -0
- package/src/prompts/style.js +41 -0
- package/src/prompts/summarize.js +13 -0
- package/src/prompts/token-budget.js +3 -0
- package/src/prompts/wrap-list.js +14 -0
- package/src/prompts/wrap-variable.js +36 -0
- package/src/services/llm-model/index.js +114 -0
- package/src/services/llm-model/model.js +21 -0
- package/src/services/redis/index.js +84 -0
- package/src/verblets/auto/index.examples.js +28 -0
- package/src/verblets/auto/index.js +28 -0
- package/src/verblets/auto/index.spec.js +34 -0
- package/src/verblets/bool/index.examples.js +28 -0
- package/src/verblets/bool/index.js +28 -0
- package/src/verblets/bool/index.schema.json +14 -0
- package/src/verblets/bool/index.spec.js +35 -0
- package/src/verblets/enum/index.examples.js +33 -0
- package/src/verblets/enum/index.js +15 -0
- package/src/verblets/enum/index.spec.js +35 -0
- package/src/verblets/intent/index.examples.js +51 -0
- package/src/verblets/intent/index.js +72 -0
- package/src/verblets/intent/index.spec.js +31 -0
- package/src/verblets/number/index.examples.js +33 -0
- package/src/verblets/number/index.js +22 -0
- package/src/verblets/number/index.spec.js +35 -0
- package/src/verblets/number-with-units/index.examples.js +34 -0
- package/src/verblets/number-with-units/index.js +19 -0
- package/src/verblets/number-with-units/index.spec.js +46 -0
- package/src/verblets/schema-org/index.examples.js +56 -0
- package/src/verblets/schema-org/index.js +8 -0
- package/src/verblets/schema-org/index.spec.js +39 -0
- package/src/verblets/to-object/README.md +38 -0
- package/src/verblets/to-object/index.examples.js +29 -0
- package/src/verblets/to-object/index.js +136 -0
- package/src/verblets/to-object/index.spec.js +74 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import Dismantle from './index.js';
|
|
4
|
+
import { longTestTimeout } from '../../constants/common.js';
|
|
5
|
+
|
|
6
|
+
describe('Dismantle chain', () => {
|
|
7
|
+
it(
|
|
8
|
+
'2022 Aprilia Tuono 660',
|
|
9
|
+
async () => {
|
|
10
|
+
const dismantleBike = new Dismantle('2022 Aprilia Tuono 660', {
|
|
11
|
+
enhanceFixes: `
|
|
12
|
+
- IMPORTANT If the component is "Electronics", output empty results.
|
|
13
|
+
- If the component is "Dashboard", output empty results.
|
|
14
|
+
`,
|
|
15
|
+
});
|
|
16
|
+
await dismantleBike.makeSubtree({ depth: 1 });
|
|
17
|
+
await dismantleBike.attachSubtree({
|
|
18
|
+
depth: 1,
|
|
19
|
+
find: (node) => node.name === 'Fuel Injector',
|
|
20
|
+
});
|
|
21
|
+
await dismantleBike.attachSubtree({
|
|
22
|
+
depth: 1,
|
|
23
|
+
find: (node) => node.name === 'Exhaust System',
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
expect(true).toStrictEqual(true);
|
|
27
|
+
},
|
|
28
|
+
longTestTimeout
|
|
29
|
+
);
|
|
30
|
+
});
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
/* eslint-disable no-await-in-loop */
|
|
2
|
+
|
|
3
|
+
import { v4 as uuid } from 'uuid';
|
|
4
|
+
|
|
5
|
+
import chatGPT from '../../lib/chatgpt/index.js';
|
|
6
|
+
import {
|
|
7
|
+
constants as promptConstants,
|
|
8
|
+
outputSuccinctNames,
|
|
9
|
+
} from '../../prompts/index.js';
|
|
10
|
+
import modelService from '../../services/llm-model/index.js';
|
|
11
|
+
import toObject from '../../verblets/to-object/index.js';
|
|
12
|
+
|
|
13
|
+
const { onlyJSONStringArray } = promptConstants;
|
|
14
|
+
|
|
15
|
+
const subComponentsPrompt = (component, thing, fixes = '') => {
|
|
16
|
+
let focus = '';
|
|
17
|
+
if (component !== thing) {
|
|
18
|
+
focus = `"${component}" within "${thing}"`;
|
|
19
|
+
} else {
|
|
20
|
+
focus = thing;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return `
|
|
24
|
+
${onlyJSONStringArray}
|
|
25
|
+
|
|
26
|
+
Exhaustively enumerate all physical and logical subcomponents of ${focus}, including containers or abstract components.
|
|
27
|
+
|
|
28
|
+
Apply the specifics listed here when dealing with component or entity:
|
|
29
|
+
- ${outputSuccinctNames()}
|
|
30
|
+
- If some components are subcomponents of others in the list, don't include them.
|
|
31
|
+
- The output must not include "${thing}" or "${component}" in the list.
|
|
32
|
+
- Only subcomponents, no accessories.
|
|
33
|
+
${fixes}
|
|
34
|
+
|
|
35
|
+
${onlyJSONStringArray}
|
|
36
|
+
`;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const componentOptionsPrompt = (component, thing, fixes = '') => {
|
|
40
|
+
let focus = '';
|
|
41
|
+
if (component !== thing) {
|
|
42
|
+
focus = `Considering "${component}" as a separate component within "${thing}" entity`;
|
|
43
|
+
} else {
|
|
44
|
+
focus = `Considering "${component}"`;
|
|
45
|
+
}
|
|
46
|
+
return `
|
|
47
|
+
${onlyJSONStringArray}
|
|
48
|
+
|
|
49
|
+
${focus}, list specific variants for this component. Only provide known variants, don't speculate. Output an empty list if you must.
|
|
50
|
+
|
|
51
|
+
Apply the specifics listed here when dealing with component or entity:
|
|
52
|
+
- ${outputSuccinctNames()}
|
|
53
|
+
- Do not list subcomponents, that's not what this is about.
|
|
54
|
+
${fixes}
|
|
55
|
+
|
|
56
|
+
${onlyJSONStringArray}
|
|
57
|
+
`;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const defaultMatch = () => false;
|
|
61
|
+
|
|
62
|
+
const deepClone = (obj) => JSON.parse(JSON.stringify(obj));
|
|
63
|
+
|
|
64
|
+
const search = (node, { match = defaultMatch, matches = [] } = {}) => {
|
|
65
|
+
if (match(node)) {
|
|
66
|
+
matches.push(node);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (!node.children) {
|
|
70
|
+
return matches.length > 0 ? matches : undefined;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
for (const child of node.children) {
|
|
74
|
+
search(child, { match, matches });
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return matches.length > 0 ? matches : undefined;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const defaultDecompose = async ({
|
|
81
|
+
name,
|
|
82
|
+
focus,
|
|
83
|
+
rootName,
|
|
84
|
+
fixes,
|
|
85
|
+
model = modelService.getBestAvailableModel(),
|
|
86
|
+
} = {}) => {
|
|
87
|
+
const focusFormatted = focus ? `: ${focus}` : '';
|
|
88
|
+
|
|
89
|
+
const promptCreated = subComponentsPrompt(
|
|
90
|
+
`${name}${focusFormatted}`,
|
|
91
|
+
rootName,
|
|
92
|
+
fixes
|
|
93
|
+
);
|
|
94
|
+
const budget = model.budgetTokens(promptCreated);
|
|
95
|
+
return toObject(
|
|
96
|
+
await chatGPT(promptCreated, {
|
|
97
|
+
modelOptions: {
|
|
98
|
+
maxTokens: budget.completion,
|
|
99
|
+
frequencyPenalty: 0.7,
|
|
100
|
+
temperature: 0.7,
|
|
101
|
+
},
|
|
102
|
+
})
|
|
103
|
+
);
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const defaultEnhance = async ({
|
|
107
|
+
name,
|
|
108
|
+
rootName,
|
|
109
|
+
fixes,
|
|
110
|
+
model = modelService.getBestAvailableModel(),
|
|
111
|
+
} = {}) => {
|
|
112
|
+
const promptCreated = componentOptionsPrompt(name, rootName, fixes);
|
|
113
|
+
const budget = model.budgetTokens(promptCreated);
|
|
114
|
+
const options = toObject(
|
|
115
|
+
await chatGPT(promptCreated, {
|
|
116
|
+
maxTokens: budget.completion,
|
|
117
|
+
frequencyPenalty: 0.5,
|
|
118
|
+
temperature: 0.3,
|
|
119
|
+
})
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
name,
|
|
124
|
+
options,
|
|
125
|
+
topOptionName: options?.[0],
|
|
126
|
+
};
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const makeNode = async ({
|
|
130
|
+
node = {},
|
|
131
|
+
name: nameInitial,
|
|
132
|
+
rootName,
|
|
133
|
+
decompose = defaultDecompose,
|
|
134
|
+
enhance = defaultEnhance,
|
|
135
|
+
makeId = uuid,
|
|
136
|
+
enhanceFixes,
|
|
137
|
+
decomposeFixes,
|
|
138
|
+
} = {}) => {
|
|
139
|
+
const name = nameInitial ?? rootName;
|
|
140
|
+
|
|
141
|
+
let nodeNew = node;
|
|
142
|
+
|
|
143
|
+
if (!node.isEnhanced) {
|
|
144
|
+
nodeNew = await enhance({
|
|
145
|
+
name,
|
|
146
|
+
rootName,
|
|
147
|
+
fixes: enhanceFixes,
|
|
148
|
+
});
|
|
149
|
+
nodeNew.isEnhanced = true;
|
|
150
|
+
|
|
151
|
+
const focus = node.options?.[0];
|
|
152
|
+
|
|
153
|
+
const childNames = await decompose({
|
|
154
|
+
name,
|
|
155
|
+
focus,
|
|
156
|
+
rootName,
|
|
157
|
+
fixes: decomposeFixes,
|
|
158
|
+
});
|
|
159
|
+
nodeNew.children = childNames.map((childName) => ({
|
|
160
|
+
id: makeId(),
|
|
161
|
+
name: childName,
|
|
162
|
+
}));
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (!node.id) {
|
|
166
|
+
nodeNew.id = makeId();
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return {
|
|
170
|
+
...node,
|
|
171
|
+
...nodeNew,
|
|
172
|
+
};
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
const makeSubtree = async ({
|
|
176
|
+
name,
|
|
177
|
+
rootName,
|
|
178
|
+
tree: treeInitial,
|
|
179
|
+
depth = 0,
|
|
180
|
+
decompose,
|
|
181
|
+
enhance,
|
|
182
|
+
enhanceFixes,
|
|
183
|
+
decomposeFixes,
|
|
184
|
+
makeId,
|
|
185
|
+
} = {}) => {
|
|
186
|
+
let tree = { ...(treeInitial ?? {}) };
|
|
187
|
+
|
|
188
|
+
const nodeNew = await makeNode({
|
|
189
|
+
node: tree,
|
|
190
|
+
name: name ?? tree.name,
|
|
191
|
+
rootName,
|
|
192
|
+
enhance,
|
|
193
|
+
decompose,
|
|
194
|
+
makeId,
|
|
195
|
+
enhanceFixes,
|
|
196
|
+
decomposeFixes,
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
tree = {
|
|
200
|
+
...tree,
|
|
201
|
+
...nodeNew,
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
if (depth <= 0) {
|
|
205
|
+
return tree;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const children = [];
|
|
209
|
+
for (const child of tree.children) {
|
|
210
|
+
const subtree = await makeSubtree({
|
|
211
|
+
tree: child,
|
|
212
|
+
rootName,
|
|
213
|
+
decompose,
|
|
214
|
+
enhance,
|
|
215
|
+
depth: depth - 1,
|
|
216
|
+
makeId,
|
|
217
|
+
enhanceFixes,
|
|
218
|
+
decomposeFixes,
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
children.push(subtree);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
tree.children = children;
|
|
225
|
+
|
|
226
|
+
return tree;
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
export const simplifyTree = (node) => {
|
|
230
|
+
if (!node.children || node.children.length === 0) {
|
|
231
|
+
const parts = (node.children ?? []).map((child) => child.name);
|
|
232
|
+
return {
|
|
233
|
+
id: node.id,
|
|
234
|
+
name: `${node.name}${node.options?.[0] ? `: ${node.options?.[0]}` : ''}`,
|
|
235
|
+
parts: parts.length ? parts : undefined,
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const parts = node.children.map((child) => simplifyTree(child));
|
|
240
|
+
return {
|
|
241
|
+
id: node.id,
|
|
242
|
+
name: `${node.name}${node.options?.[0] ? `: ${node.options?.[0]}` : ''}`,
|
|
243
|
+
parts: parts.length ? parts : undefined,
|
|
244
|
+
};
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
class ChainTree {
|
|
248
|
+
constructor(
|
|
249
|
+
name,
|
|
250
|
+
{ decompose, enhance, makeId, enhanceFixes, decomposeFixes } = {}
|
|
251
|
+
) {
|
|
252
|
+
this.rootName = name;
|
|
253
|
+
this.tree = {};
|
|
254
|
+
this.decompose = decompose;
|
|
255
|
+
this.enhance = enhance;
|
|
256
|
+
this.makeId = makeId;
|
|
257
|
+
this.enhanceFixes = enhanceFixes;
|
|
258
|
+
this.decomposeFixes = decomposeFixes;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
getTree() {
|
|
262
|
+
return this.tree;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
async attachSubtree({ find, depth }) {
|
|
266
|
+
const clonedTree = deepClone(this.tree);
|
|
267
|
+
|
|
268
|
+
// Find the node to attach the subtree to in the cloned tree
|
|
269
|
+
const targetNodes = search(clonedTree, { match: find });
|
|
270
|
+
|
|
271
|
+
if (!targetNodes || targetNodes.length !== 1) {
|
|
272
|
+
return undefined;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const targetNode = targetNodes[0];
|
|
276
|
+
|
|
277
|
+
const newNode = await makeSubtree({
|
|
278
|
+
...this,
|
|
279
|
+
rootName: clonedTree.name,
|
|
280
|
+
tree: targetNode,
|
|
281
|
+
depth,
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
Object.assign(targetNode, newNode);
|
|
285
|
+
|
|
286
|
+
const nodeStripped = {
|
|
287
|
+
...newNode,
|
|
288
|
+
children: newNode.children.map((child) => ({
|
|
289
|
+
...child,
|
|
290
|
+
children: [],
|
|
291
|
+
})),
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
this.tree = clonedTree;
|
|
295
|
+
|
|
296
|
+
return nodeStripped;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
async makeSubtree(config = {}) {
|
|
300
|
+
this.tree = await makeSubtree({
|
|
301
|
+
...this,
|
|
302
|
+
rootName: this.tree.name || this.rootName,
|
|
303
|
+
...config,
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
return this.tree;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
export const dismantle = (text, options) => {
|
|
311
|
+
return new ChainTree(text, options);
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
export default ChainTree;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { dismantle } from './index.js';
|
|
4
|
+
|
|
5
|
+
vi.mock('../../lib/chatgpt/index.js', () => ({
|
|
6
|
+
default: vi.fn().mockImplementation((text) => {
|
|
7
|
+
if (/prompt text to match/.test(text)) {
|
|
8
|
+
return 'True';
|
|
9
|
+
}
|
|
10
|
+
return 'undefined';
|
|
11
|
+
}),
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
const examples = [
|
|
15
|
+
{
|
|
16
|
+
name: 'Basic usage',
|
|
17
|
+
inputs: { text: 'test' },
|
|
18
|
+
want: { result: {} },
|
|
19
|
+
},
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
describe('Dismantle chain', () => {
|
|
23
|
+
examples.forEach((example) => {
|
|
24
|
+
it(example.name, async () => {
|
|
25
|
+
const result = await dismantle(example.inputs.text);
|
|
26
|
+
|
|
27
|
+
if (example.want.typeOfResult) {
|
|
28
|
+
expect(JSON.stringify(result.tree))
|
|
29
|
+
.toStrictEqual(JSON.stringify(example.want.result));
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
});
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { longTestTimeout } from '../../constants/common.js';
|
|
4
|
+
import chatGPT from '../../lib/chatgpt/index.js';
|
|
5
|
+
import { asJSONSchema } from '../../prompts/index.js';
|
|
6
|
+
import toObject from '../../verblets/to-object/index.js';
|
|
7
|
+
|
|
8
|
+
import list from './index.js';
|
|
9
|
+
|
|
10
|
+
const examples = [
|
|
11
|
+
{
|
|
12
|
+
inputs: { description: '2021 EV cars' },
|
|
13
|
+
want: { minLength: 10, listContains: 'Model Y' },
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
inputs: {
|
|
17
|
+
description: '2021 EV cars',
|
|
18
|
+
jsonSchemaQuery:
|
|
19
|
+
'make, model, releaseDate (ISO),\
|
|
20
|
+
maxRange (miles), batteryCapacity (kWH), startingCost (USD)',
|
|
21
|
+
},
|
|
22
|
+
want: { minLength: 10, listModelContains: 'Model Y' },
|
|
23
|
+
},
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
describe('List verblet', () => {
|
|
27
|
+
examples.forEach((example) => {
|
|
28
|
+
let jsonSchemaDisplay = '';
|
|
29
|
+
if (example.inputs.jsonSchemaQuery) {
|
|
30
|
+
const jsonSchemaEllipsis =
|
|
31
|
+
example.inputs.jsonSchemaQuery.length > 10 ? '...' : '';
|
|
32
|
+
jsonSchemaDisplay = ` - ${example.inputs.jsonSchemaQuery.slice(
|
|
33
|
+
0,
|
|
34
|
+
10
|
|
35
|
+
)}${jsonSchemaEllipsis}`;
|
|
36
|
+
}
|
|
37
|
+
it(
|
|
38
|
+
`${example.inputs.text}${jsonSchemaDisplay}`,
|
|
39
|
+
async () => {
|
|
40
|
+
let schema;
|
|
41
|
+
if (example.inputs.jsonSchemaQuery) {
|
|
42
|
+
schema = await toObject(
|
|
43
|
+
await chatGPT(asJSONSchema(example.inputs.schemaQuery))
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const result = await list(example.inputs.description, {
|
|
48
|
+
schema,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
if (example.want.minLength) {
|
|
52
|
+
expect(result.length).gt(5);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (example.want.listContains) {
|
|
56
|
+
expect(
|
|
57
|
+
result.some((item) => item.includes(example.want.listContains))
|
|
58
|
+
).equals(true);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (example.want.listModelContains) {
|
|
62
|
+
expect(
|
|
63
|
+
result.some((item) =>
|
|
64
|
+
item.model.includes(example.want.listModelContains)
|
|
65
|
+
)
|
|
66
|
+
).equals(true);
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
longTestTimeout
|
|
70
|
+
);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/* eslint-disable no-await-in-loop */
|
|
2
|
+
|
|
3
|
+
import { operationTimeoutMultiplier } from '../../constants/openai.js';
|
|
4
|
+
import chatGPT from '../../lib/chatgpt/index.js';
|
|
5
|
+
import {
|
|
6
|
+
constants as promptConstants,
|
|
7
|
+
asObjectWithSchema as asObjectWithSchemaPrompt,
|
|
8
|
+
generateList as generateListPrompt,
|
|
9
|
+
} from '../../prompts/index.js';
|
|
10
|
+
import modelService from '../../services/llm-model/index.js';
|
|
11
|
+
import toObject from '../../verblets/to-object/index.js';
|
|
12
|
+
|
|
13
|
+
const { onlyJSON, contentIsTransformationSource } = promptConstants;
|
|
14
|
+
|
|
15
|
+
const outputTransformPrompt = (result, schema) => {
|
|
16
|
+
return `${contentIsTransformationSource} ${result}
|
|
17
|
+
|
|
18
|
+
${asObjectWithSchemaPrompt(schema)}
|
|
19
|
+
|
|
20
|
+
${onlyJSON}`;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const shouldSkipDefault = ({ result, resultsAll } = {}) => {
|
|
24
|
+
return resultsAll.includes(result);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const shouldStopDefault = ({ queryCount, startTime } = {}) => {
|
|
28
|
+
return (
|
|
29
|
+
queryCount > 5 ||
|
|
30
|
+
new Date() - startTime >
|
|
31
|
+
operationTimeoutMultiplier *
|
|
32
|
+
modelService.getBestAvailableModel().requestTimeout
|
|
33
|
+
);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const generateList = async function* generateListGenerator(
|
|
37
|
+
text,
|
|
38
|
+
options = {}
|
|
39
|
+
) {
|
|
40
|
+
const resultsAll = [];
|
|
41
|
+
const resultsAllMap = {};
|
|
42
|
+
let isDone = false;
|
|
43
|
+
const {
|
|
44
|
+
shouldSkip = shouldSkipDefault,
|
|
45
|
+
shouldStop = shouldStopDefault,
|
|
46
|
+
model = modelService.getBestAvailableModel(),
|
|
47
|
+
} = options;
|
|
48
|
+
|
|
49
|
+
const startTime = new Date();
|
|
50
|
+
let queryCount = 0;
|
|
51
|
+
|
|
52
|
+
while (!isDone) {
|
|
53
|
+
const listPrompt = generateListPrompt(text, {
|
|
54
|
+
...options,
|
|
55
|
+
existing: resultsAll,
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const budget = model.budgetTokens(listPrompt);
|
|
59
|
+
|
|
60
|
+
let resultsNew = [];
|
|
61
|
+
try {
|
|
62
|
+
const results = await chatGPT(listPrompt, {
|
|
63
|
+
modelOptions: {
|
|
64
|
+
maxTokens: budget.completion,
|
|
65
|
+
},
|
|
66
|
+
...options,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// debug helper:
|
|
70
|
+
// console.error(R.sort((a, b) => a.localeCompare(b), await toObject(results)));
|
|
71
|
+
|
|
72
|
+
resultsNew = await toObject(results);
|
|
73
|
+
} catch (error) {
|
|
74
|
+
if (/The operation was aborted/.test(error.message)) {
|
|
75
|
+
console.error('Generate list [error]: Aborted');
|
|
76
|
+
resultsNew = []; // continue
|
|
77
|
+
} else {
|
|
78
|
+
console.error(
|
|
79
|
+
`Generate list [error]: ${error.message}`,
|
|
80
|
+
listPrompt.slice(0, 100).replace('\n', '\\n')
|
|
81
|
+
);
|
|
82
|
+
isDone = true;
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const resultsNewUnique = resultsNew.filter(
|
|
88
|
+
(item) => !(item in resultsAllMap)
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
queryCount += 1;
|
|
92
|
+
|
|
93
|
+
for (const result of resultsNewUnique) {
|
|
94
|
+
const perResultControlFactors = {
|
|
95
|
+
result,
|
|
96
|
+
resultsAll,
|
|
97
|
+
resultsNew,
|
|
98
|
+
queryCount,
|
|
99
|
+
startTime,
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
if (await shouldStop(perResultControlFactors)) {
|
|
103
|
+
isDone = true;
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (!(await shouldSkip(perResultControlFactors))) {
|
|
108
|
+
resultsAllMap[result] = true;
|
|
109
|
+
resultsAll.push(result);
|
|
110
|
+
|
|
111
|
+
// debug helper:
|
|
112
|
+
// console.error(R.sort((a, b) => a.localeCompare(b), resultsAll));
|
|
113
|
+
|
|
114
|
+
yield result;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const perQueryControlFactors = {
|
|
119
|
+
result: undefined,
|
|
120
|
+
resultsAll: [],
|
|
121
|
+
resultsNew: [],
|
|
122
|
+
queryCount,
|
|
123
|
+
startTime,
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
if (await shouldStop(perQueryControlFactors)) {
|
|
127
|
+
isDone = true;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
export default async (text, options) => {
|
|
133
|
+
const generator = generateList(text, options);
|
|
134
|
+
const { schema, model = modelService.getBestAvailableModel() } =
|
|
135
|
+
options ?? {};
|
|
136
|
+
|
|
137
|
+
const results = [];
|
|
138
|
+
for await (const result of generator) {
|
|
139
|
+
results.push(result);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (!schema) {
|
|
143
|
+
return results;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const resultObjects = await Promise.all(
|
|
147
|
+
results.map(async (result) => {
|
|
148
|
+
const prompt = outputTransformPrompt(result, schema);
|
|
149
|
+
const budget = model.budgetTokens(prompt);
|
|
150
|
+
|
|
151
|
+
const resultObject = await chatGPT(prompt, {
|
|
152
|
+
maxTokens: budget.completion,
|
|
153
|
+
...options,
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
return toObject(resultObject);
|
|
157
|
+
})
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
return resultObjects;
|
|
161
|
+
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
3
|
+
|
|
4
|
+
import toObject from '../../verblets/to-object/index.js';
|
|
5
|
+
import list from './index.js';
|
|
6
|
+
|
|
7
|
+
const loadSchema = async () => {
|
|
8
|
+
const file = (
|
|
9
|
+
await fs.readFile('./src/json-schemas/cars-test.json')
|
|
10
|
+
).toString();
|
|
11
|
+
|
|
12
|
+
return toObject(file);
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
vi.mock('../../lib/chatgpt/index.js', () => ({
|
|
16
|
+
default: vi.fn().mockImplementation((text) => {
|
|
17
|
+
if (/Transform/.test(text) && /Model Y/.test(text)) {
|
|
18
|
+
return '{"make":"Tesla", "model": "Model Y"}';
|
|
19
|
+
}
|
|
20
|
+
if (/EV cars/.test(text)) {
|
|
21
|
+
return '["Tesla Model Y"]';
|
|
22
|
+
}
|
|
23
|
+
return 'undefined';
|
|
24
|
+
}),
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
const examples = [
|
|
28
|
+
{
|
|
29
|
+
name: 'Basic usage',
|
|
30
|
+
inputs: { description: '2021 EV cars' },
|
|
31
|
+
want: { listContains: /Model Y/ },
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
name: 'Basic usage with schema',
|
|
35
|
+
inputs: {
|
|
36
|
+
description: '2021 EV cars',
|
|
37
|
+
schema: loadSchema,
|
|
38
|
+
},
|
|
39
|
+
want: { listModelContains: /Model Y/ },
|
|
40
|
+
},
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
describe('List verblet', () => {
|
|
44
|
+
examples.forEach((example) => {
|
|
45
|
+
it(example.name, async () => {
|
|
46
|
+
let schema;
|
|
47
|
+
if (example.inputs.schema) {
|
|
48
|
+
schema = await example.inputs.schema();
|
|
49
|
+
}
|
|
50
|
+
const result = await list(example.inputs.description, {
|
|
51
|
+
shouldStop: ({ queryCount }) => queryCount > 1,
|
|
52
|
+
schema,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
if (example.want.listContains) {
|
|
56
|
+
expect(
|
|
57
|
+
result.some((item) => example.want.listContains.test(item))
|
|
58
|
+
).equals(true);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (example.want.listModelContains) {
|
|
62
|
+
expect(
|
|
63
|
+
result.some((item) => example.want.listModelContains.test(item.model))
|
|
64
|
+
).equals(true);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "list",
|
|
3
|
+
"description": "Generate a list based on the input text, optionally specifying a JSON schema for the subsequent output to conform to",
|
|
4
|
+
"parameters": {
|
|
5
|
+
"type": "object",
|
|
6
|
+
"properties": {
|
|
7
|
+
"name": {
|
|
8
|
+
"type": "string",
|
|
9
|
+
"description": "Name or description of the list to be generated"
|
|
10
|
+
},
|
|
11
|
+
"options": {
|
|
12
|
+
"type": "object",
|
|
13
|
+
"description": "Options for list generation and transformation",
|
|
14
|
+
"properties": {
|
|
15
|
+
"jsonSchema": {
|
|
16
|
+
"type": "object",
|
|
17
|
+
"description": "The JSONSchema used for transforming list results"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"required": []
|
|
23
|
+
}
|
|
24
|
+
}
|