@recombine-ai/engine 0.1.1 → 0.2.0
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/build/lib/ai.d.ts +25 -14
- package/build/lib/ai.d.ts.map +1 -1
- package/build/lib/ai.js +48 -32
- package/package.json +2 -2
- package/readme.md +1 -1
package/build/lib/ai.d.ts
CHANGED
|
@@ -1,16 +1,31 @@
|
|
|
1
1
|
import { ZodSchema } from 'zod';
|
|
2
2
|
import { Logger, Message } from './interfaces';
|
|
3
3
|
import { SendAction } from './bosun/action';
|
|
4
|
+
export type Models = 'o3-mini-2025-01-31' | 'o1-preview-2024-09-12' | 'gpt-4o-2024-11-20' | 'o1-2024-12-17';
|
|
5
|
+
export interface BasicStep {
|
|
6
|
+
/** Step name (used mainly for debugging) */
|
|
7
|
+
name: string;
|
|
8
|
+
/** Check a condition, whether the whole step should be run or not */
|
|
9
|
+
runIf?: (messages: Messages) => boolean | Promise<boolean>;
|
|
10
|
+
/** Use when you need to do some action when LLM's response received */
|
|
11
|
+
execute: () => Promise<unknown>;
|
|
12
|
+
/** Error handler called if an error occurred during LLM API call or in `execute` function */
|
|
13
|
+
onError: (error: string) => Promise<unknown>;
|
|
14
|
+
}
|
|
4
15
|
export interface Step {
|
|
5
16
|
/** Step name (used mainly for debugging) */
|
|
6
17
|
name: string;
|
|
18
|
+
/** Check a condition, whether the whole step should be run or not */
|
|
19
|
+
runIf?: (messages: Messages) => boolean | Promise<boolean>;
|
|
20
|
+
/** Specify AI Model. Default gpt-4o */
|
|
21
|
+
model?: Models;
|
|
7
22
|
/**
|
|
8
23
|
* Prompt can be a simple string or a link to a file, loaded with `loadFile` function which
|
|
9
24
|
* takes a path to the file relative to `src/use-cases` directory.
|
|
10
25
|
*/
|
|
11
26
|
prompt: string | File;
|
|
12
27
|
/**
|
|
13
|
-
* In case you want a structured from LLM, define a schema using {@link zod https://zod.dev/}
|
|
28
|
+
* In case you want a structured output from LLM, define a schema using {@link zod https://zod.dev/}
|
|
14
29
|
* library.
|
|
15
30
|
*/
|
|
16
31
|
schema?: ZodSchema;
|
|
@@ -18,25 +33,21 @@ export interface Step {
|
|
|
18
33
|
ignoreDirectives?: boolean;
|
|
19
34
|
/** Additional data to be inserted into prompt */
|
|
20
35
|
context?: Record<string, unknown>;
|
|
21
|
-
/** Check a condition, whether the `execute` function should run or not */
|
|
22
|
-
shouldExecute?: (reply: string) => boolean | Promise<boolean>;
|
|
23
|
-
/** Check a condition, whether the whole step should be run or not */
|
|
24
|
-
runIf?: (messages: Messages) => boolean | Promise<boolean>;
|
|
25
36
|
/** Use when you need to do some action when LLM's response received */
|
|
26
37
|
execute: (reply: string) => Promise<unknown>;
|
|
27
|
-
/**
|
|
28
|
-
|
|
38
|
+
/**
|
|
39
|
+
* Check a condition, whether the `execute` function should run or not
|
|
40
|
+
* @deprecated use `runIf` to check if the step should be run, use if in `execute` to check
|
|
41
|
+
* if it should be executed
|
|
42
|
+
**/
|
|
43
|
+
shouldExecute?: (reply: string) => boolean | Promise<boolean>;
|
|
29
44
|
/**
|
|
30
45
|
* When provided, throws an error if the step is invoked more times than `maxAttempts`.
|
|
31
46
|
* Number of attempts taken is reset when `shouldExecute` returns `false`. Useful to limit
|
|
32
47
|
* rewinds by reviewers. NOTE that it doesn't work on steps without `shouldExecute` method.
|
|
33
48
|
*/
|
|
34
49
|
maxAttempts?: number;
|
|
35
|
-
|
|
36
|
-
export interface DumbStep {
|
|
37
|
-
name: string;
|
|
38
|
-
runIf?: () => boolean | Promise<boolean>;
|
|
39
|
-
execute: () => Promise<unknown>;
|
|
50
|
+
/** Error handler called if an error occurred during LLM API call or in `execute` function */
|
|
40
51
|
onError: (error: string) => Promise<unknown>;
|
|
41
52
|
}
|
|
42
53
|
export type CreateStep = ReturnType<typeof createAIEngine>['createStep'];
|
|
@@ -73,13 +84,13 @@ export interface EngineConfig {
|
|
|
73
84
|
sendAction?: SendAction;
|
|
74
85
|
}
|
|
75
86
|
export declare function createAIEngine(cfg?: EngineConfig): {
|
|
76
|
-
createWorkflow: (...steps: Array<Step |
|
|
87
|
+
createWorkflow: (...steps: Array<Step | BasicStep>) => Promise<{
|
|
77
88
|
terminate: () => void;
|
|
78
89
|
run: (messages: Messages) => Promise<string | null>;
|
|
79
90
|
rewindTo: (step: Step) => void;
|
|
80
91
|
beforeEach(callback: () => Promise<unknown>): void;
|
|
81
92
|
}>;
|
|
82
|
-
createStep: <T extends Step |
|
|
93
|
+
createStep: <T extends Step | BasicStep>(step: T) => T;
|
|
83
94
|
loadFile: (path: string) => {
|
|
84
95
|
content: () => Promise<string>;
|
|
85
96
|
};
|
package/build/lib/ai.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ai.d.ts","sourceRoot":"","sources":["../../src/lib/ai.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,SAAS,EAAE,MAAM,KAAK,CAAA;AAE/B,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAC9C,OAAO,EAAc,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAGvD,MAAM,WAAW,
|
|
1
|
+
{"version":3,"file":"ai.d.ts","sourceRoot":"","sources":["../../src/lib/ai.ts"],"names":[],"mappings":"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"}
|
package/build/lib/ai.js
CHANGED
|
@@ -47,7 +47,7 @@ function createAIEngine(cfg = {}) {
|
|
|
47
47
|
.join('\n') + (proposedReply ? `\n${proposedFormatter(proposedReply)}` : ''),
|
|
48
48
|
addMessage: (sender, text) => messages.push({ sender, text }),
|
|
49
49
|
addDirective: (message) => {
|
|
50
|
-
logger.debug(`AI
|
|
50
|
+
logger.debug(`AI Engine: add directive: ${message}`);
|
|
51
51
|
messages.push({ sender: 'system', text: message });
|
|
52
52
|
},
|
|
53
53
|
directiveFormat: (formatter) => {
|
|
@@ -75,7 +75,7 @@ function createAIEngine(cfg = {}) {
|
|
|
75
75
|
const attempts = new Map();
|
|
76
76
|
return {
|
|
77
77
|
terminate: () => {
|
|
78
|
-
logger.debug('AI
|
|
78
|
+
logger.debug('AI Engine: Terminating conversation...');
|
|
79
79
|
shouldRun = false;
|
|
80
80
|
},
|
|
81
81
|
run: async (messages) => {
|
|
@@ -88,12 +88,12 @@ function createAIEngine(cfg = {}) {
|
|
|
88
88
|
if (!step.runIf || (await step.runIf(messages))) {
|
|
89
89
|
const action = (0, action_1.makeAction)(cfg.sendAction, 'AI', step.name);
|
|
90
90
|
await action('started');
|
|
91
|
-
logger.debug(`AI
|
|
91
|
+
logger.debug(`AI Engine: Step: ${step.name}`);
|
|
92
92
|
if ('prompt' in step) {
|
|
93
93
|
await runStep(step, messages);
|
|
94
94
|
}
|
|
95
95
|
else {
|
|
96
|
-
await runDumbStep(step);
|
|
96
|
+
await runDumbStep(step, messages);
|
|
97
97
|
}
|
|
98
98
|
await action('completed');
|
|
99
99
|
}
|
|
@@ -121,30 +121,30 @@ function createAIEngine(cfg = {}) {
|
|
|
121
121
|
try {
|
|
122
122
|
let response = null;
|
|
123
123
|
let prompt = typeof step.prompt === 'string' ? step.prompt : await step.prompt.content();
|
|
124
|
-
logger.debug('AI
|
|
125
|
-
logger.debug('AI
|
|
124
|
+
logger.debug('AI Engine: context', step.context);
|
|
125
|
+
logger.debug('AI Engine: messages', messages.toString(step.ignoreDirectives || false));
|
|
126
126
|
if (step.context) {
|
|
127
127
|
nunjucks_1.default.configure({ autoescape: true, trimBlocks: true, lstripBlocks: true });
|
|
128
128
|
prompt = nunjucks_1.default.renderString(prompt, step.context);
|
|
129
129
|
}
|
|
130
|
-
response = await runLLM(apiKey, prompt, messages.toString(step.ignoreDirectives || false), step.schema);
|
|
130
|
+
response = await runLLM(apiKey, prompt, messages.toString(step.ignoreDirectives || false), step.schema, step.model);
|
|
131
131
|
if (!response) {
|
|
132
132
|
throw new Error('No response from OpenAI');
|
|
133
133
|
}
|
|
134
|
-
logger.debug(`AI
|
|
134
|
+
logger.debug(`AI Engine: response: ${response}`);
|
|
135
135
|
if (typeof step.shouldExecute === 'function') {
|
|
136
136
|
if (await step.shouldExecute(response)) {
|
|
137
|
-
logger.debug(`AI
|
|
137
|
+
logger.debug(`AI Engine: executing`);
|
|
138
138
|
checkAttempts(step);
|
|
139
139
|
await step.execute(response);
|
|
140
140
|
}
|
|
141
141
|
else {
|
|
142
142
|
resetAttempts(step);
|
|
143
|
-
logger.debug(`AI
|
|
143
|
+
logger.debug(`AI Engine: skipping`);
|
|
144
144
|
}
|
|
145
145
|
}
|
|
146
146
|
else {
|
|
147
|
-
logger.debug(`AI
|
|
147
|
+
logger.debug(`AI Engine: replying`);
|
|
148
148
|
await step.execute(response);
|
|
149
149
|
}
|
|
150
150
|
}
|
|
@@ -154,14 +154,14 @@ function createAIEngine(cfg = {}) {
|
|
|
154
154
|
shouldRun = false;
|
|
155
155
|
}
|
|
156
156
|
}
|
|
157
|
-
async function runDumbStep(step) {
|
|
157
|
+
async function runDumbStep(step, messages) {
|
|
158
158
|
try {
|
|
159
|
-
if (!step.runIf || (await step.runIf())) {
|
|
159
|
+
if (!step.runIf || (await step.runIf(messages))) {
|
|
160
160
|
await step.execute();
|
|
161
161
|
}
|
|
162
162
|
}
|
|
163
163
|
catch (error) {
|
|
164
|
-
console.error(`AI
|
|
164
|
+
console.error(`AI Engine: error in dumb step ${step.name}: ${error.message}`);
|
|
165
165
|
await step.onError(error.message);
|
|
166
166
|
shouldRun = false;
|
|
167
167
|
}
|
|
@@ -181,7 +181,11 @@ function createAIEngine(cfg = {}) {
|
|
|
181
181
|
attempts.set(step, 0);
|
|
182
182
|
}
|
|
183
183
|
}
|
|
184
|
-
async function runLLM(apiKey, systemPrompt, messages, schema) {
|
|
184
|
+
async function runLLM(apiKey, systemPrompt, messages, schema, model = 'gpt-4o-2024-11-20') {
|
|
185
|
+
logger.debug('AI Engine: model:', model);
|
|
186
|
+
logger.debug('----------- RENDERED PROMPT ---------------');
|
|
187
|
+
logger.debug(systemPrompt);
|
|
188
|
+
logger.debug('-------------------------------------------');
|
|
185
189
|
if (apiKey === '__TESTING__') {
|
|
186
190
|
await (0, core_1.sleep)(100);
|
|
187
191
|
return schema
|
|
@@ -189,27 +193,12 @@ function createAIEngine(cfg = {}) {
|
|
|
189
193
|
: 'canned response';
|
|
190
194
|
}
|
|
191
195
|
const client = new openai_1.default({ apiKey });
|
|
192
|
-
logger.log('----------- RENDERED PROMPT ---------------');
|
|
193
|
-
logger.log(systemPrompt);
|
|
194
|
-
logger.log('---------------------------------------');
|
|
195
|
-
let format = { type: 'text' };
|
|
196
|
-
if (schema) {
|
|
197
|
-
format = {
|
|
198
|
-
type: 'json_schema',
|
|
199
|
-
json_schema: {
|
|
200
|
-
name: 'detector_response',
|
|
201
|
-
schema: (0, zod_to_json_schema_1.zodToJsonSchema)(schema),
|
|
202
|
-
},
|
|
203
|
-
};
|
|
204
|
-
}
|
|
205
196
|
const response = await client.chat.completions.create({
|
|
206
197
|
messages: [
|
|
207
198
|
{ role: 'system', content: systemPrompt },
|
|
208
199
|
{ role: 'user', content: messages },
|
|
209
200
|
],
|
|
210
|
-
model
|
|
211
|
-
response_format: format,
|
|
212
|
-
temperature: 0.1,
|
|
201
|
+
...getOpenAiOptions(model, schema),
|
|
213
202
|
});
|
|
214
203
|
if (!response.choices[0].message.content) {
|
|
215
204
|
throw new Error('No response from OpenAI');
|
|
@@ -220,7 +209,7 @@ function createAIEngine(cfg = {}) {
|
|
|
220
209
|
// NOTE: there probably will be S3 loading stuff here
|
|
221
210
|
return {
|
|
222
211
|
content: async () => {
|
|
223
|
-
logger.debug('AI
|
|
212
|
+
logger.debug('AI Engine: loading prompt:', path);
|
|
224
213
|
return fs_1.default.promises.readFile((0, path_1.join)(basePath, path), 'utf-8');
|
|
225
214
|
},
|
|
226
215
|
};
|
|
@@ -232,3 +221,30 @@ function createAIEngine(cfg = {}) {
|
|
|
232
221
|
makeMessagesList,
|
|
233
222
|
};
|
|
234
223
|
}
|
|
224
|
+
function getOpenAiOptions(model, schema) {
|
|
225
|
+
const options = {
|
|
226
|
+
model,
|
|
227
|
+
};
|
|
228
|
+
const isReasoningModel = ['o3-', 'o1-', 'o1-preview-'].some((m) => model.startsWith(m));
|
|
229
|
+
if (isReasoningModel) {
|
|
230
|
+
if (!model.startsWith('o1-preview-')) {
|
|
231
|
+
options.reasoning_effort = 'high';
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
options.temperature = 0.1;
|
|
236
|
+
}
|
|
237
|
+
if (schema) {
|
|
238
|
+
options.response_format = {
|
|
239
|
+
type: 'json_schema',
|
|
240
|
+
json_schema: {
|
|
241
|
+
name: 'detector_response',
|
|
242
|
+
schema: (0, zod_to_json_schema_1.zodToJsonSchema)(schema),
|
|
243
|
+
},
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
options.response_format = { type: 'text' };
|
|
248
|
+
}
|
|
249
|
+
return options;
|
|
250
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@recombine-ai/engine",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Recombine AI engine for creating conversational AI agents",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"dependencies": {
|
|
24
24
|
"nunjucks": "^3.2.4",
|
|
25
25
|
"openai": "^4.68.4",
|
|
26
|
-
"zod": "
|
|
26
|
+
"zod": "3.23.8",
|
|
27
27
|
"zod-to-json-schema": "^3.23.5"
|
|
28
28
|
}
|
|
29
29
|
}
|