@joshuaswarren/openclaw-engram 9.0.77 → 9.0.78
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/index.js +147 -18
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2177,6 +2177,14 @@ var FallbackLlmClient = class {
|
|
|
2177
2177
|
* Returns parsed JSON or null on failure.
|
|
2178
2178
|
*/
|
|
2179
2179
|
async parseWithSchema(messages, schema, options = {}) {
|
|
2180
|
+
const detailed = await this.parseWithSchemaDetailed(messages, schema, options);
|
|
2181
|
+
return detailed?.result ?? null;
|
|
2182
|
+
}
|
|
2183
|
+
/**
|
|
2184
|
+
* Like parseWithSchema but also returns the model that was used,
|
|
2185
|
+
* so callers can emit accurate trace events.
|
|
2186
|
+
*/
|
|
2187
|
+
async parseWithSchemaDetailed(messages, schema, options = {}) {
|
|
2180
2188
|
const response = await this.chatCompletion(messages, options);
|
|
2181
2189
|
if (!response?.content) return null;
|
|
2182
2190
|
try {
|
|
@@ -2184,7 +2192,7 @@ var FallbackLlmClient = class {
|
|
|
2184
2192
|
for (const c of candidates) {
|
|
2185
2193
|
try {
|
|
2186
2194
|
const parsed = JSON.parse(c);
|
|
2187
|
-
return schema.parse(parsed);
|
|
2195
|
+
return { result: schema.parse(parsed), modelUsed: response.modelUsed };
|
|
2188
2196
|
} catch {
|
|
2189
2197
|
}
|
|
2190
2198
|
}
|
|
@@ -3234,18 +3242,18 @@ var ExtractionEngine = class {
|
|
|
3234
3242
|
};
|
|
3235
3243
|
}
|
|
3236
3244
|
async parseWithGatewayFallback(traceId, operation, startedAtMs, schema, messages, options = {}) {
|
|
3237
|
-
const
|
|
3238
|
-
if (result) {
|
|
3245
|
+
const detailed = await this.fallbackLlm.parseWithSchemaDetailed(messages, schema, options);
|
|
3246
|
+
if (detailed?.result) {
|
|
3239
3247
|
const durationMs = Date.now() - startedAtMs;
|
|
3240
3248
|
this.emit({
|
|
3241
3249
|
kind: "llm_end",
|
|
3242
3250
|
traceId,
|
|
3243
|
-
model:
|
|
3251
|
+
model: detailed.modelUsed,
|
|
3244
3252
|
operation,
|
|
3245
3253
|
durationMs,
|
|
3246
|
-
output: JSON.stringify(result).slice(0, 2e3)
|
|
3254
|
+
output: JSON.stringify(detailed.result).slice(0, 2e3)
|
|
3247
3255
|
});
|
|
3248
|
-
return result;
|
|
3256
|
+
return detailed.result;
|
|
3249
3257
|
}
|
|
3250
3258
|
return null;
|
|
3251
3259
|
}
|
|
@@ -3267,7 +3275,11 @@ var ExtractionEngine = class {
|
|
|
3267
3275
|
const lastTurnTs = boundedTurns.length > 0 ? new Date(boundedTurns[boundedTurns.length - 1].timestamp) : void 0;
|
|
3268
3276
|
const messageTimestamp = lastTurnTs && !isNaN(lastTurnTs.getTime()) ? lastTurnTs : void 0;
|
|
3269
3277
|
const traceId = crypto.randomUUID();
|
|
3270
|
-
|
|
3278
|
+
const emittedDirectStart = !!(this.client || this.config.localLlmEnabled);
|
|
3279
|
+
if (emittedDirectStart) {
|
|
3280
|
+
this.emit({ kind: "llm_start", traceId, model: this.config.model, operation: "extraction", input: conversation });
|
|
3281
|
+
}
|
|
3282
|
+
let closedDirectTrace = false;
|
|
3271
3283
|
const startTime = Date.now();
|
|
3272
3284
|
if (this.config.localLlmEnabled) {
|
|
3273
3285
|
try {
|
|
@@ -3302,34 +3314,75 @@ var ExtractionEngine = class {
|
|
|
3302
3314
|
const sanitized = this.sanitizeExtractionResult(directResult, messageTimestamp);
|
|
3303
3315
|
return await this.applyProactiveQuestionPass(conversation, sanitized);
|
|
3304
3316
|
}
|
|
3317
|
+
try {
|
|
3318
|
+
this.emit({
|
|
3319
|
+
kind: "llm_error",
|
|
3320
|
+
traceId,
|
|
3321
|
+
model: this.config.model,
|
|
3322
|
+
operation: "extraction",
|
|
3323
|
+
durationMs: Date.now() - startTime,
|
|
3324
|
+
error: "direct client returned no result"
|
|
3325
|
+
});
|
|
3326
|
+
} catch {
|
|
3327
|
+
}
|
|
3328
|
+
closedDirectTrace = true;
|
|
3305
3329
|
log.info("extraction: direct client returned no result, falling back to gateway AI");
|
|
3306
3330
|
} catch (err) {
|
|
3331
|
+
try {
|
|
3332
|
+
this.emit({
|
|
3333
|
+
kind: "llm_error",
|
|
3334
|
+
traceId,
|
|
3335
|
+
model: this.config.model,
|
|
3336
|
+
operation: "extraction",
|
|
3337
|
+
durationMs: Date.now() - startTime,
|
|
3338
|
+
error: String(err)
|
|
3339
|
+
});
|
|
3340
|
+
} catch {
|
|
3341
|
+
}
|
|
3342
|
+
closedDirectTrace = true;
|
|
3307
3343
|
log.info("extraction: direct client failed, falling back to gateway AI:", err);
|
|
3308
3344
|
}
|
|
3309
3345
|
}
|
|
3346
|
+
if (emittedDirectStart && !closedDirectTrace) {
|
|
3347
|
+
try {
|
|
3348
|
+
this.emit({
|
|
3349
|
+
kind: "llm_error",
|
|
3350
|
+
traceId,
|
|
3351
|
+
model: this.config.model,
|
|
3352
|
+
operation: "extraction",
|
|
3353
|
+
durationMs: Date.now() - startTime,
|
|
3354
|
+
error: "local LLM failed, handing off to gateway fallback"
|
|
3355
|
+
});
|
|
3356
|
+
} catch {
|
|
3357
|
+
}
|
|
3358
|
+
}
|
|
3359
|
+
const fallbackTraceId = crypto.randomUUID();
|
|
3360
|
+
const fallbackStartTime = Date.now();
|
|
3310
3361
|
log.info("extraction: falling back to gateway default AI");
|
|
3311
3362
|
try {
|
|
3312
3363
|
const messages = [
|
|
3313
3364
|
{ role: "system", content: this.buildExtractionInstructions(existingEntities) },
|
|
3314
3365
|
{ role: "user", content: conversation }
|
|
3315
3366
|
];
|
|
3316
|
-
|
|
3367
|
+
this.emit({ kind: "llm_start", traceId: fallbackTraceId, model: "fallback", operation: "extraction", input: conversation });
|
|
3368
|
+
const detailed = await this.fallbackLlm.parseWithSchemaDetailed(
|
|
3317
3369
|
messages,
|
|
3318
3370
|
ExtractionResultSchema,
|
|
3319
3371
|
{ temperature: 0.3, maxTokens: 4096 }
|
|
3320
3372
|
);
|
|
3321
|
-
const
|
|
3322
|
-
if (result && Array.isArray(result.facts)) {
|
|
3373
|
+
const fallbackDurationMs = Date.now() - fallbackStartTime;
|
|
3374
|
+
if (detailed?.result && Array.isArray(detailed.result.facts)) {
|
|
3375
|
+
const result = detailed.result;
|
|
3323
3376
|
this.emit({
|
|
3324
3377
|
kind: "llm_end",
|
|
3325
|
-
traceId,
|
|
3326
|
-
model:
|
|
3378
|
+
traceId: fallbackTraceId,
|
|
3379
|
+
model: detailed.modelUsed,
|
|
3327
3380
|
operation: "extraction",
|
|
3328
|
-
durationMs,
|
|
3381
|
+
durationMs: fallbackDurationMs,
|
|
3329
3382
|
output: JSON.stringify(result).slice(0, 2e3)
|
|
3330
3383
|
});
|
|
3331
3384
|
log.debug(
|
|
3332
|
-
`extracted ${result.facts.length} facts, ${result.entities.length} entities, ${(result.questions ?? []).length} questions via fallback`
|
|
3385
|
+
`extracted ${result.facts.length} facts, ${result.entities.length} entities, ${(result.questions ?? []).length} questions via fallback (${detailed.modelUsed})`
|
|
3333
3386
|
);
|
|
3334
3387
|
const sanitized = this.sanitizeExtractionResult({
|
|
3335
3388
|
...result,
|
|
@@ -3338,15 +3391,23 @@ var ExtractionEngine = class {
|
|
|
3338
3391
|
}, messageTimestamp);
|
|
3339
3392
|
return await this.applyProactiveQuestionPass(conversation, sanitized);
|
|
3340
3393
|
}
|
|
3394
|
+
this.emit({
|
|
3395
|
+
kind: "llm_error",
|
|
3396
|
+
traceId: fallbackTraceId,
|
|
3397
|
+
model: "fallback",
|
|
3398
|
+
operation: "extraction",
|
|
3399
|
+
durationMs: fallbackDurationMs,
|
|
3400
|
+
error: "fallback returned no parsed output"
|
|
3401
|
+
});
|
|
3341
3402
|
log.warn("extraction fallback returned no parsed output");
|
|
3342
3403
|
return { facts: [], profileUpdates: [], entities: [], questions: [] };
|
|
3343
3404
|
} catch (err) {
|
|
3344
3405
|
this.emit({
|
|
3345
3406
|
kind: "llm_error",
|
|
3346
|
-
traceId,
|
|
3407
|
+
traceId: fallbackTraceId,
|
|
3347
3408
|
model: "fallback",
|
|
3348
3409
|
operation: "extraction",
|
|
3349
|
-
durationMs: Date.now() -
|
|
3410
|
+
durationMs: Date.now() - fallbackStartTime,
|
|
3350
3411
|
error: String(err)
|
|
3351
3412
|
});
|
|
3352
3413
|
log.error("extraction fallback failed", err);
|
|
@@ -40009,11 +40070,34 @@ async function postSpanBatch(cfg, spans, log2) {
|
|
|
40009
40070
|
log2.debug?.(`[opik-exporter] span batch error: ${err}`);
|
|
40010
40071
|
}
|
|
40011
40072
|
}
|
|
40073
|
+
async function postTraceBatch(cfg, traces, log2) {
|
|
40074
|
+
const url = `${cfg.apiUrl.replace(/\/$/, "")}/v1/private/traces/batch`;
|
|
40075
|
+
try {
|
|
40076
|
+
const res = await fetch(url, {
|
|
40077
|
+
method: "POST",
|
|
40078
|
+
headers: buildHeaders(cfg),
|
|
40079
|
+
body: JSON.stringify({ traces })
|
|
40080
|
+
});
|
|
40081
|
+
if (!res.ok) {
|
|
40082
|
+
const text = await res.text().catch(() => "");
|
|
40083
|
+
log2.debug?.(`[opik-exporter] trace batch failed ${res.status}: ${text}`);
|
|
40084
|
+
return false;
|
|
40085
|
+
}
|
|
40086
|
+
return true;
|
|
40087
|
+
} catch (err) {
|
|
40088
|
+
log2.debug?.(`[opik-exporter] trace batch error: ${err}`);
|
|
40089
|
+
return false;
|
|
40090
|
+
}
|
|
40091
|
+
}
|
|
40012
40092
|
var OpikExporter = class _OpikExporter {
|
|
40013
40093
|
cfg;
|
|
40014
40094
|
log;
|
|
40015
40095
|
_handler;
|
|
40016
40096
|
inFlight = /* @__PURE__ */ new Map();
|
|
40097
|
+
/** Track which trace_ids we have already created parent trace objects for. */
|
|
40098
|
+
createdTraces = /* @__PURE__ */ new Set();
|
|
40099
|
+
/** In-flight ensureTrace promises keyed by traceId — concurrent callers await the same promise. */
|
|
40100
|
+
pendingTraces = /* @__PURE__ */ new Map();
|
|
40017
40101
|
/** TTL for in-flight LLM entries (ms). Entries older than this are discarded. */
|
|
40018
40102
|
static IN_FLIGHT_TTL_MS = 5 * 60 * 1e3;
|
|
40019
40103
|
// 5 minutes
|
|
@@ -40116,9 +40200,11 @@ var OpikExporter = class _OpikExporter {
|
|
|
40116
40200
|
metadata.identityInjectedChars = evt.identityInjectedChars;
|
|
40117
40201
|
}
|
|
40118
40202
|
if (evt.timings) metadata.timings = evt.timings;
|
|
40203
|
+
const traceId = evt.sessionKey ? this.sessionToTraceId(evt.sessionKey) : uuidV7();
|
|
40204
|
+
await this.ensureTrace(traceId, evt.sessionKey ?? "engram:recall", startTime, endTime);
|
|
40119
40205
|
const span = {
|
|
40120
40206
|
id: uuidV7(),
|
|
40121
|
-
trace_id:
|
|
40207
|
+
trace_id: traceId,
|
|
40122
40208
|
project_name: this.cfg.projectName,
|
|
40123
40209
|
name: "engram:recall",
|
|
40124
40210
|
type: "general",
|
|
@@ -40162,9 +40248,11 @@ var OpikExporter = class _OpikExporter {
|
|
|
40162
40248
|
if (evt.tokenUsage?.input != null) usage.prompt_tokens = evt.tokenUsage.input;
|
|
40163
40249
|
if (evt.tokenUsage?.output != null) usage.completion_tokens = evt.tokenUsage.output;
|
|
40164
40250
|
if (evt.tokenUsage?.total != null) usage.total_tokens = evt.tokenUsage.total;
|
|
40251
|
+
const traceId = state?.traceId ?? uuidV7();
|
|
40252
|
+
await this.ensureTrace(traceId, `engram:${evt.operation}`, startTime, endTime);
|
|
40165
40253
|
const span = {
|
|
40166
40254
|
id: state?.spanId ?? uuidV7(),
|
|
40167
|
-
trace_id:
|
|
40255
|
+
trace_id: traceId,
|
|
40168
40256
|
project_name: this.cfg.projectName,
|
|
40169
40257
|
name: `engram:${evt.operation}`,
|
|
40170
40258
|
type: "llm",
|
|
@@ -40186,6 +40274,47 @@ var OpikExporter = class _OpikExporter {
|
|
|
40186
40274
|
// -------------------------------------------------------------------------
|
|
40187
40275
|
// Helpers
|
|
40188
40276
|
// -------------------------------------------------------------------------
|
|
40277
|
+
/**
|
|
40278
|
+
* Create a parent trace object in Opik if we haven't already.
|
|
40279
|
+
* Opik does NOT auto-create traces from spans, so without this
|
|
40280
|
+
* the spans are orphaned and don't show up in the trace list UI.
|
|
40281
|
+
*/
|
|
40282
|
+
async ensureTrace(traceId, name, startTime, endTime) {
|
|
40283
|
+
if (this.createdTraces.has(traceId)) return;
|
|
40284
|
+
const existing = this.pendingTraces.get(traceId);
|
|
40285
|
+
if (existing) {
|
|
40286
|
+
await existing;
|
|
40287
|
+
if (this.createdTraces.has(traceId)) return;
|
|
40288
|
+
const retrying = this.pendingTraces.get(traceId);
|
|
40289
|
+
if (retrying) {
|
|
40290
|
+
await retrying;
|
|
40291
|
+
if (this.createdTraces.has(traceId)) return;
|
|
40292
|
+
}
|
|
40293
|
+
}
|
|
40294
|
+
const work = (async () => {
|
|
40295
|
+
const trace = {
|
|
40296
|
+
id: traceId,
|
|
40297
|
+
project_name: this.cfg.projectName,
|
|
40298
|
+
name,
|
|
40299
|
+
start_time: startTime,
|
|
40300
|
+
end_time: endTime,
|
|
40301
|
+
tags: ["engram"]
|
|
40302
|
+
};
|
|
40303
|
+
const ok = await postTraceBatch(this.cfg, [trace], this.log);
|
|
40304
|
+
if (!ok) {
|
|
40305
|
+
this.pendingTraces.delete(traceId);
|
|
40306
|
+
return;
|
|
40307
|
+
}
|
|
40308
|
+
this.createdTraces.add(traceId);
|
|
40309
|
+
this.pendingTraces.delete(traceId);
|
|
40310
|
+
if (this.createdTraces.size > 1e4) {
|
|
40311
|
+
const first = this.createdTraces.values().next().value;
|
|
40312
|
+
if (first) this.createdTraces.delete(first);
|
|
40313
|
+
}
|
|
40314
|
+
})();
|
|
40315
|
+
this.pendingTraces.set(traceId, work);
|
|
40316
|
+
await work;
|
|
40317
|
+
}
|
|
40189
40318
|
/**
|
|
40190
40319
|
* Convert a sessionKey to a stable, deterministic UUID v7-shaped ID so that
|
|
40191
40320
|
* spans for the same session share a trace_id and are threaded in Opik.
|