@recombine-ai/engine 1.0.0 → 1.1.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.
@@ -11,12 +11,12 @@ jobs:
11
11
  timeout-minutes: 10
12
12
  steps:
13
13
  - name: Checkout
14
- uses: actions/checkout@v4
14
+ uses: actions/checkout@v7
15
15
 
16
16
  - name: Setup Node.js
17
- uses: actions/setup-node@v4
17
+ uses: actions/setup-node@v6
18
18
  with:
19
- node-version: 24
19
+ node-version: '24.x'
20
20
  cache: 'npm'
21
21
 
22
22
  - name: Install dependencies
@@ -10,27 +10,27 @@ permissions:
10
10
  contents: read
11
11
 
12
12
  env:
13
- NODE_VERSION: 20
13
+ NODE_VERSION: 24
14
14
 
15
15
  jobs:
16
16
  publish:
17
17
  runs-on: ubuntu-latest
18
18
 
19
19
  steps:
20
- - uses: actions/checkout@v4
20
+ - uses: actions/checkout@v7
21
21
  with:
22
22
  fetch-depth: 0
23
23
 
24
24
  - name: 'Setup Node'
25
- uses: actions/setup-node@v4
25
+ uses: actions/setup-node@v6
26
26
  with:
27
- node-version: 20
27
+ node-version: '${{ env.NODE_VERSION }}.x'
28
28
  cache: 'npm'
29
29
  registry-url: 'https://registry.npmjs.org'
30
30
 
31
- # Ensure npm 11.5.1 or later is installed
31
+ # Keep npm on a major compatible with Node 24.
32
32
  - name: Update npm
33
- run: npm install -g npm@latest
33
+ run: npm install -g npm@^11
34
34
 
35
35
  - name: 'Install dependencies'
36
36
  run: npm ci
@@ -1 +1 @@
1
- {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../src/lib/engine.ts"],"names":[],"mappings":"AAIA,OAAO,EAEH,YAAY,EAGZ,OAAO,EAEP,WAAW,EAKd,MAAM,cAAc,CAAA;AAGrB,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAG5D;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,cAAc,CAAC,GAAG,SAAS,MAAM,EAAE,GAAG,GAAE,YAAiB,GAAG,QAAQ,CAAC,GAAG,CAAC,CA+NxF;AAuDD,wBAAgB,kBAAkB,CAAC,eAAe,GAAE,OAAO,EAAO,GAAG,YAAY,CA2ChF;AAED,wBAAgB,cAAc,CAAC,GAAG,GAAG,OAAO,KAAK,WAAW,CAAC,GAAG,CAAC,CAEhE"}
1
+ {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../src/lib/engine.ts"],"names":[],"mappings":"AAIA,OAAO,EAEH,YAAY,EAGZ,OAAO,EAEP,WAAW,EAKd,MAAM,cAAc,CAAA;AAGrB,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAK5D;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,cAAc,CAAC,GAAG,SAAS,MAAM,EAAE,GAAG,GAAE,YAAiB,GAAG,QAAQ,CAAC,GAAG,CAAC,CAkQxF;AAuDD,wBAAgB,kBAAkB,CAAC,eAAe,GAAE,OAAO,EAAO,GAAG,YAAY,CA2ChF;AAED,wBAAgB,cAAc,CAAC,GAAG,GAAG,OAAO,KAAK,WAAW,CAAC,GAAG,CAAC,CAEhE"}
@@ -45,6 +45,7 @@ const nunjucks_1 = __importDefault(require("nunjucks"));
45
45
  const Zod = __importStar(require("zod"));
46
46
  const step_tracer_1 = require("./bosun/step-tracer");
47
47
  const step_registry_1 = require("./step-registry");
48
+ const STRUCTURED_RESPONSE_MAX_ATTEMPTS = 3;
48
49
  /**
49
50
  * Creates an AI Engine with the given configuration.
50
51
  *
@@ -146,30 +147,9 @@ function createAIEngine(cfg = {}) {
146
147
  });
147
148
  stepTrace.stringifiedConversation = stringifiedMessages;
148
149
  if ('schema' in step) {
149
- const stringResponse = await runLLM(step.model, prompt, stringifiedMessages, step.schema);
150
- stepTrace.response = stringResponse;
151
- let parsedJsonResponse = undefined;
152
- try {
153
- parsedJsonResponse = JSON.parse(stringResponse);
154
- response = step.schema.parse(parsedJsonResponse);
155
- }
156
- catch (err) {
157
- if (err instanceof SyntaxError) {
158
- logger.error(`AI-generated response is not valid JSON in step ${step.name}`, {
159
- response: stringResponse,
160
- schema: Zod.toJSONSchema(step.schema),
161
- });
162
- throw new Error(`Response is not valid JSON for step ${step.name}`);
163
- }
164
- else {
165
- logger.error(`AI-generated response in step ${step.name} violates schema`, {
166
- response: stringResponse,
167
- schema: Zod.toJSONSchema(step.schema),
168
- errors: step.schema.safeParse(parsedJsonResponse).error,
169
- });
170
- throw new Error(`Response validation failed for step ${step.name}`);
171
- }
172
- }
150
+ const { parsedResponse, rawResponse } = await runStructuredStepWithRetries(step.model, prompt, stringifiedMessages, step.name, step.schema);
151
+ response = parsedResponse;
152
+ stepTrace.response = rawResponse;
173
153
  }
174
154
  else {
175
155
  response = await runLLM(step.model, prompt, stringifiedMessages, undefined);
@@ -239,6 +219,49 @@ function createAIEngine(cfg = {}) {
239
219
  });
240
220
  return nunjucks_1.default.renderString(prompt, context ?? {});
241
221
  }
222
+ async function runStructuredStepWithRetries(model, prompt, stringifiedMessages, stepName, schema) {
223
+ for (let attempt = 1; attempt <= STRUCTURED_RESPONSE_MAX_ATTEMPTS; attempt++) {
224
+ const stringResponse = await runLLM(model, prompt, stringifiedMessages, schema);
225
+ let parsedJsonResponse = undefined;
226
+ try {
227
+ parsedJsonResponse = JSON.parse(stringResponse);
228
+ }
229
+ catch {
230
+ const hasAttemptsLeft = attempt < STRUCTURED_RESPONSE_MAX_ATTEMPTS;
231
+ if (hasAttemptsLeft) {
232
+ logger.debug(`AI-generated response is not valid JSON in step ${stepName}, retry ${attempt}/${STRUCTURED_RESPONSE_MAX_ATTEMPTS}`);
233
+ continue;
234
+ }
235
+ logger.error(`AI-generated response is not valid JSON in step ${stepName}`, {
236
+ response: stringResponse,
237
+ schema: Zod.toJSONSchema(schema),
238
+ });
239
+ throw new Error(`Response is not valid JSON for step ${stepName}`);
240
+ }
241
+ const parsedResponse = schema.safeParse(parsedJsonResponse);
242
+ if (parsedResponse.success) {
243
+ return {
244
+ parsedResponse: parsedResponse.data,
245
+ rawResponse: stringResponse,
246
+ };
247
+ }
248
+ const hasAttemptsLeft = attempt < STRUCTURED_RESPONSE_MAX_ATTEMPTS;
249
+ if (hasAttemptsLeft) {
250
+ logger.debug(`AI-generated response in step ${stepName} violates schema, retry ${attempt}/${STRUCTURED_RESPONSE_MAX_ATTEMPTS}`, {
251
+ response: stringResponse,
252
+ errors: parsedResponse.error,
253
+ });
254
+ continue;
255
+ }
256
+ logger.error(`AI-generated response in step ${stepName} violates schema`, {
257
+ response: stringResponse,
258
+ schema: Zod.toJSONSchema(schema),
259
+ errors: parsedResponse.error,
260
+ });
261
+ throw new Error(`Response validation failed for step ${stepName}`);
262
+ }
263
+ throw new Error(`Response validation failed for step ${stepName}`);
264
+ }
242
265
  return {
243
266
  createWorkflow,
244
267
  createConversation,
package/changelog.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Changelog
2
2
 
3
+ ### 1.0.0 → 1.1.0
4
+
5
+ - Added retries on broken JSON in structured responses.
6
+
3
7
  ### 0.11.4 → 1.0.0
4
8
 
5
9
  - **Breaking:** `model` field on `LLMStep` is now required and must be an `LlmAdapter` (string model names removed)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@recombine-ai/engine",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Recombine AI engine for creating conversational AI agents",
5
5
  "repository": {
6
6
  "url": "git+https://github.com/recombine-ai/engine.git"