@cairnvibe/sdk 0.2.12 → 0.2.13
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/dist/server.js +30 -14
- package/package.json +1 -1
- package/src/server.ts +29 -14
package/dist/server.js
CHANGED
|
@@ -223,18 +223,26 @@ class GroqVerbLLM {
|
|
|
223
223
|
return await this.attemptRespond(systemPrompt, userMessage);
|
|
224
224
|
}
|
|
225
225
|
catch (err) {
|
|
226
|
-
// Real, live
|
|
227
|
-
//
|
|
228
|
-
//
|
|
229
|
-
//
|
|
230
|
-
//
|
|
231
|
-
//
|
|
232
|
-
//
|
|
233
|
-
//
|
|
234
|
-
//
|
|
235
|
-
//
|
|
226
|
+
// Real, live bugs, not theoretical — two distinct non-deterministic
|
|
227
|
+
// failure modes from openai/gpt-oss-120b (a reasoning-capable open
|
|
228
|
+
// model), both rejected by Groq's own server-side validation before
|
|
229
|
+
// this code ever sees a real response to work with, and both found
|
|
230
|
+
// to recover cleanly on an identical retry a moment later:
|
|
231
|
+
// - "output_parse_failed": the model "thinks out loud" in plain
|
|
232
|
+
// prose instead of emitting the forced tool call.
|
|
233
|
+
// - "tool_use_failed": the model hallucinates a slightly-wrong tool
|
|
234
|
+
// name ("json", "response_with_verb" — seen live, both against
|
|
235
|
+
// the real, correctly-configured VERB_TOOL_NAME) instead of the
|
|
236
|
+
// one forced tool it was actually given. This one was the actual
|
|
237
|
+
// cause behind a real "voice keeps breaking" report — found live
|
|
238
|
+
// running the new eval harness's synthetic-voice scenario, where
|
|
239
|
+
// it surfaced as "Something went wrong on my end" with no other
|
|
240
|
+
// symptom, exactly matching what got reported.
|
|
241
|
+
// One retry — not exponential backoff, this is a latency-sensitive
|
|
242
|
+
// voice/chat path — genuinely helps rather than just delaying the
|
|
243
|
+
// same failure. Anything else still propagates to resolveVerb's own
|
|
236
244
|
// catch, unchanged.
|
|
237
|
-
if (
|
|
245
|
+
if (isRetryableToolCallFailure(err)) {
|
|
238
246
|
return await this.attemptRespond(systemPrompt, userMessage);
|
|
239
247
|
}
|
|
240
248
|
throw err;
|
|
@@ -277,13 +285,21 @@ exports.GroqVerbLLM = GroqVerbLLM;
|
|
|
277
285
|
* actually been observed to surface — a thrown APIError with a nested
|
|
278
286
|
* `.error.code`, a plain `.code`, or just the code string showing up
|
|
279
287
|
* somewhere in the message — rather than relying on exactly one of them. */
|
|
280
|
-
function
|
|
288
|
+
function isRetryableToolCallFailure(err) {
|
|
281
289
|
if (!err || typeof err !== "object")
|
|
282
290
|
return false;
|
|
283
291
|
const e = err;
|
|
284
|
-
|
|
292
|
+
const code = e.code ?? e.error?.code;
|
|
293
|
+
const message = typeof e.message === "string" ? e.message : "";
|
|
294
|
+
if (code === "output_parse_failed" || message.includes("output_parse_failed"))
|
|
285
295
|
return true;
|
|
286
|
-
|
|
296
|
+
// "attempted to call tool 'json' which was not in request.tools" — the
|
|
297
|
+
// model calling a tool name it invented instead of VERB_TOOL_NAME, the
|
|
298
|
+
// only one actually offered. See respond()'s doc comment for how this
|
|
299
|
+
// was found.
|
|
300
|
+
if (code === "tool_use_failed" && message.includes("attempted to call tool"))
|
|
301
|
+
return true;
|
|
302
|
+
return false;
|
|
287
303
|
}
|
|
288
304
|
// ---------------------------------------------------------------------------
|
|
289
305
|
// Shared tool schema / system prompt
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cairnvibe/sdk",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.13",
|
|
4
4
|
"description": "In-app AI copilot — <Copilot/> for React/Next.js, <cairn-widget> for any framework — plus the server handlers and realtime voice relay behind them.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"publishConfig": { "access": "public" },
|
package/src/server.ts
CHANGED
|
@@ -317,18 +317,26 @@ export class GroqVerbLLM implements VerbLLM {
|
|
|
317
317
|
try {
|
|
318
318
|
return await this.attemptRespond(systemPrompt, userMessage);
|
|
319
319
|
} catch (err) {
|
|
320
|
-
// Real, live
|
|
321
|
-
//
|
|
322
|
-
//
|
|
323
|
-
//
|
|
324
|
-
//
|
|
325
|
-
//
|
|
326
|
-
//
|
|
327
|
-
//
|
|
328
|
-
//
|
|
329
|
-
//
|
|
320
|
+
// Real, live bugs, not theoretical — two distinct non-deterministic
|
|
321
|
+
// failure modes from openai/gpt-oss-120b (a reasoning-capable open
|
|
322
|
+
// model), both rejected by Groq's own server-side validation before
|
|
323
|
+
// this code ever sees a real response to work with, and both found
|
|
324
|
+
// to recover cleanly on an identical retry a moment later:
|
|
325
|
+
// - "output_parse_failed": the model "thinks out loud" in plain
|
|
326
|
+
// prose instead of emitting the forced tool call.
|
|
327
|
+
// - "tool_use_failed": the model hallucinates a slightly-wrong tool
|
|
328
|
+
// name ("json", "response_with_verb" — seen live, both against
|
|
329
|
+
// the real, correctly-configured VERB_TOOL_NAME) instead of the
|
|
330
|
+
// one forced tool it was actually given. This one was the actual
|
|
331
|
+
// cause behind a real "voice keeps breaking" report — found live
|
|
332
|
+
// running the new eval harness's synthetic-voice scenario, where
|
|
333
|
+
// it surfaced as "Something went wrong on my end" with no other
|
|
334
|
+
// symptom, exactly matching what got reported.
|
|
335
|
+
// One retry — not exponential backoff, this is a latency-sensitive
|
|
336
|
+
// voice/chat path — genuinely helps rather than just delaying the
|
|
337
|
+
// same failure. Anything else still propagates to resolveVerb's own
|
|
330
338
|
// catch, unchanged.
|
|
331
|
-
if (
|
|
339
|
+
if (isRetryableToolCallFailure(err)) {
|
|
332
340
|
return await this.attemptRespond(systemPrompt, userMessage);
|
|
333
341
|
}
|
|
334
342
|
throw err;
|
|
@@ -371,11 +379,18 @@ export class GroqVerbLLM implements VerbLLM {
|
|
|
371
379
|
* actually been observed to surface — a thrown APIError with a nested
|
|
372
380
|
* `.error.code`, a plain `.code`, or just the code string showing up
|
|
373
381
|
* somewhere in the message — rather than relying on exactly one of them. */
|
|
374
|
-
function
|
|
382
|
+
function isRetryableToolCallFailure(err: unknown): boolean {
|
|
375
383
|
if (!err || typeof err !== "object") return false;
|
|
376
384
|
const e = err as { code?: unknown; error?: { code?: unknown }; message?: unknown };
|
|
377
|
-
|
|
378
|
-
|
|
385
|
+
const code = e.code ?? e.error?.code;
|
|
386
|
+
const message = typeof e.message === "string" ? e.message : "";
|
|
387
|
+
if (code === "output_parse_failed" || message.includes("output_parse_failed")) return true;
|
|
388
|
+
// "attempted to call tool 'json' which was not in request.tools" — the
|
|
389
|
+
// model calling a tool name it invented instead of VERB_TOOL_NAME, the
|
|
390
|
+
// only one actually offered. See respond()'s doc comment for how this
|
|
391
|
+
// was found.
|
|
392
|
+
if (code === "tool_use_failed" && message.includes("attempted to call tool")) return true;
|
|
393
|
+
return false;
|
|
379
394
|
}
|
|
380
395
|
|
|
381
396
|
// ---------------------------------------------------------------------------
|