@exellix/graph-engine 7.8.2 → 8.0.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/CHANGELOG.md +27 -0
- package/README.md +16 -13
- package/dist/src/adapters/compileExellixExecutablePlan.d.ts +8 -0
- package/dist/src/adapters/compileExellixExecutablePlan.js +18 -0
- package/dist/src/adapters/migrateExellixGraphModelToAuthoring.d.ts +6 -0
- package/dist/src/adapters/migrateExellixGraphModelToAuthoring.js +273 -0
- package/dist/src/adapters/patchFinalizerPlans.d.ts +7 -0
- package/dist/src/adapters/patchFinalizerPlans.js +63 -0
- package/dist/src/errors/exellixGraphErrorCodes.d.ts +5 -0
- package/dist/src/errors/exellixGraphErrorCodes.js +5 -0
- package/dist/src/index.d.ts +9 -2
- package/dist/src/index.js +6 -2
- package/dist/src/plan/aiModelSelectionWire.d.ts +11 -0
- package/dist/src/plan/aiModelSelectionWire.js +39 -0
- package/dist/src/plan/applyNodePlanInvoke.d.ts +10 -0
- package/dist/src/plan/applyNodePlanInvoke.js +67 -0
- package/dist/src/plan/embeddedGraphToExellixGraph.d.ts +5 -0
- package/dist/src/plan/embeddedGraphToExellixGraph.js +131 -0
- package/dist/src/plan/planDeferredGates.d.ts +16 -0
- package/dist/src/plan/planDeferredGates.js +118 -0
- package/dist/src/plan/planExecuteEntry.d.ts +12 -0
- package/dist/src/plan/planExecuteEntry.js +73 -0
- package/dist/src/plan/planExecutionPipeline.d.ts +11 -0
- package/dist/src/plan/planExecutionPipeline.js +54 -0
- package/dist/src/plan/planModelConfig.d.ts +10 -0
- package/dist/src/plan/planModelConfig.js +46 -0
- package/dist/src/runtime/ExellixGraphRuntime.d.ts +9 -6
- package/dist/src/runtime/ExellixGraphRuntime.js +134 -96
- package/dist/src/runtime/executionMatrixHost.js +2 -1
- package/dist/src/runtime/studioGraphExecuteRequest.d.ts +51 -0
- package/dist/src/runtime/studioGraphExecuteRequest.js +78 -0
- package/dist/testkit/buildExecuteGraphInput.d.ts +4 -0
- package/dist/testkit/buildExecuteGraphInput.js +8 -0
- package/dist/testkit/index.d.ts +1 -0
- package/dist/testkit/index.js +1 -0
- package/dist/testkit/testModelAliasRuntime.js +2 -2
- package/package.json +7 -1
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
import { createExecutionTrace, appendExecutionEvent, validateExecutionTrace } from "@x12i/graphenix-trace-format";
|
|
2
|
+
import { preparePlanExecuteEntry, computePlanAudit } from "../plan/planExecuteEntry.js";
|
|
3
|
+
import { embeddedGraphToExellixGraph } from "../plan/embeddedGraphToExellixGraph.js";
|
|
4
|
+
import { graphAiModelConfigFromFinalizerPlan, graphAiModelConfigFromNodePlan } from "../plan/planModelConfig.js";
|
|
5
|
+
import { executionPipelineFromNodePlan } from "../plan/planExecutionPipeline.js";
|
|
6
|
+
import { applyNodePlanInvoke, finalizerPlanForId, nodePlanForId } from "../plan/applyNodePlanInvoke.js";
|
|
7
|
+
import { evaluateDeferredNodeGate, evaluateDeferredEdgeGate, nodeDeferredGate, hasPlanConditionalEdges, evaluatePlanEntryGates, planNeedsRunx, } from "../plan/planDeferredGates.js";
|
|
1
8
|
import { mergeExecutionObject, copyExecutionContextFields, } from "./memory.js";
|
|
2
9
|
import { buildAiTasksRunTaskRequest, extractRunTaskStrategyOverrides, resolveJobTypeId, } from "./buildAiTasksRunTaskRequest.js";
|
|
3
10
|
import { runTaskResponseSucceeded } from "./runTaskResponse.js";
|
|
@@ -16,11 +23,9 @@ import { resolveNarrixForTaskNode } from "./resolveNarrixForTaskNode.js";
|
|
|
16
23
|
import { assertFinalizerRequiredReadsResolvable, validateGraphFinalizer, } from "./finalizers/validateFinalizer.js";
|
|
17
24
|
import { executeDeterministicFinalizer, executeSynthesizeFinalizer } from "./finalizers/executeFinalizer.js";
|
|
18
25
|
import { createFinalizerError } from "./finalizers/errors.js";
|
|
19
|
-
import { assertCanonicalGraphDocument } from "./validateCanonicalGraphDocument.js";
|
|
20
26
|
import { assertCanonicalGraphRuntimeObject } from "./validateCanonicalGraphRuntime.js";
|
|
21
27
|
import { applyGraphResponseDefinition } from "./graphResponseMapping.js";
|
|
22
28
|
import { migrateLegacyGraphResponse } from "./graphResponseMigration.js";
|
|
23
|
-
import { computeGraphDocumentContentSha256 } from "./graphDocumentFingerprint.js";
|
|
24
29
|
import { buildAiTasksObservabilityRecord } from "./aiTasksObservability.js";
|
|
25
30
|
import { buildMainLlmCallForRunTask, buildRunTaskIdentityEnvelope, shouldForwardRunTaskTraceMode, } from "./runTaskAugments.js";
|
|
26
31
|
import { buildRunLog, extractLogxerCorrelationFromMetadata, extractTaskRunLogFromMetadata, resolveRunLogLimits, } from "./buildRunLog.js";
|
|
@@ -33,9 +38,6 @@ import { buildPredicateEvalContextForNode, mirrorTaskVariablesOnExecution, readE
|
|
|
33
38
|
import { mergeExellixGraphRuntimeInvocation } from "./mergeExellixGraphRuntimeInvocation.js";
|
|
34
39
|
import { resolveStepRetryPolicy, runRunTaskWithRetry } from "./stepRetry.js";
|
|
35
40
|
import { isLocalSkillKey, runScopedDataReader, runDeterministicRule, runScopedAnswerWriter, runScopedAnswerAssembler, } from "./localSkills/index.js";
|
|
36
|
-
import { evaluateGraphPredicate } from "./predicates.js";
|
|
37
|
-
import { evaluateStructuredDataFilters } from "./dataFiltersEvaluation.js";
|
|
38
|
-
import { evaluateTaskNodeConditions } from "./taskNodeConditionsEvaluation.js";
|
|
39
41
|
import { resolveModelConfigForNode } from "./resolveModelConfigForNode.js";
|
|
40
42
|
import { toRunTaskModelConfig } from "./graphAiModelConfig.js";
|
|
41
43
|
import { isGraphAiModelConfig } from "./modelConfigSelection.js";
|
|
@@ -1155,36 +1157,45 @@ export function createExellixGraphRuntime(opts) {
|
|
|
1155
1157
|
};
|
|
1156
1158
|
}
|
|
1157
1159
|
async function executeGraph(input) {
|
|
1158
|
-
const {
|
|
1159
|
-
const {
|
|
1160
|
+
const { plan: suppliedPlan, runtime } = input;
|
|
1161
|
+
const { plan } = preparePlanExecuteEntry(suppliedPlan, runtime);
|
|
1162
|
+
const materialized = embeddedGraphToExellixGraph(plan);
|
|
1163
|
+
const graph = migrateLegacyGraphResponse(materialized).graph;
|
|
1160
1164
|
const merged = mergeExellixGraphRuntimeInvocation(runtime, opts);
|
|
1161
|
-
const
|
|
1165
|
+
const scheduling = plan.schedulingPolicy;
|
|
1166
|
+
const plannerMode = scheduling?.mode ?? runtime.mode;
|
|
1167
|
+
const plannerGoalNodeId = scheduling?.goalNodeId ?? runtime.goalNodeId;
|
|
1168
|
+
const failFast = scheduling?.failFast ?? merged.failFast;
|
|
1162
1169
|
const debugMode = runtime.debugMode === true;
|
|
1163
1170
|
const eventEmitter = runtime.eventEmitter ?? opts.eventEmitter;
|
|
1164
1171
|
const jobId = assertHostJobId(runtime.jobId);
|
|
1165
1172
|
const graphTaskId = newGraphRunTaskId();
|
|
1166
1173
|
const job = { ...(runtime.job ?? {}), id: jobId, jobId };
|
|
1167
|
-
if (
|
|
1174
|
+
if (plannerMode === "backward" && !plannerGoalNodeId) {
|
|
1168
1175
|
const err = new Error(`BACKWARD_GOAL_REQUIRED: mode=backward requires goalNodeId`);
|
|
1169
1176
|
err.code = "BACKWARD_GOAL_REQUIRED";
|
|
1170
1177
|
throw err;
|
|
1171
1178
|
}
|
|
1172
|
-
const
|
|
1173
|
-
if (resolvedGraphIdRaw == null || resolvedGraphIdRaw === '') {
|
|
1174
|
-
throw new Error('GRAPH_ID_REQUIRED: execution request model must include a non-empty id');
|
|
1175
|
-
}
|
|
1176
|
-
const resolvedGraphId = String(resolvedGraphIdRaw);
|
|
1179
|
+
const resolvedGraphId = String(plan.source.graphId);
|
|
1177
1180
|
const runLogxer = createGraphEngineLogxer({ logging: merged.logging });
|
|
1181
|
+
const executionTrace = createExecutionTrace(plan, runtime);
|
|
1182
|
+
appendExecutionEvent(executionTrace, {
|
|
1183
|
+
id: `evt:${graphTaskId}:graph.started`,
|
|
1184
|
+
ts: new Date().toISOString(),
|
|
1185
|
+
level: "info",
|
|
1186
|
+
type: "graph.started",
|
|
1187
|
+
message: "Graph execution started.",
|
|
1188
|
+
});
|
|
1189
|
+
const planAudit = computePlanAudit(plan);
|
|
1178
1190
|
return runWithAiTasksStackLogging(merged.logging, () => runGraphWithLogContext({ jobId, taskId: graphTaskId, graphId: resolvedGraphId, runId: graphTaskId }, async () => {
|
|
1179
1191
|
bindGraphEngineRunLogxer(runLogxer);
|
|
1180
1192
|
try {
|
|
1181
|
-
assertCanonicalGraphDocument(graph, { jobId, graphId: resolvedGraphId }, { mode: 'execute' });
|
|
1182
1193
|
assertCanonicalGraphRuntimeObject(runtime, { jobId, graphId: resolvedGraphId });
|
|
1183
1194
|
let runxClient = opts.runx;
|
|
1184
|
-
if (
|
|
1195
|
+
if (planNeedsRunx(plan)) {
|
|
1185
1196
|
if (!runxClient) {
|
|
1186
1197
|
if (!opts.runxCreateOptions) {
|
|
1187
|
-
const err = new Error("RUNX_REQUIRED:
|
|
1198
|
+
const err = new Error("RUNX_REQUIRED: executable plan deferred gates require runx. Pass runx or runxCreateOptions on createExellixGraphRuntime.");
|
|
1188
1199
|
err.code = "RUNX_REQUIRED";
|
|
1189
1200
|
throw err;
|
|
1190
1201
|
}
|
|
@@ -1198,10 +1209,6 @@ export function createExellixGraphRuntime(opts) {
|
|
|
1198
1209
|
await runxClient.reload();
|
|
1199
1210
|
}
|
|
1200
1211
|
}
|
|
1201
|
-
const graphAudit = {
|
|
1202
|
-
source: 'model',
|
|
1203
|
-
contentSha256: computeGraphDocumentContentSha256(suppliedGraph),
|
|
1204
|
-
};
|
|
1205
1212
|
const graphDocumentModel = mergeGraphDocumentModel(graph);
|
|
1206
1213
|
const graphExecution = graphDocumentModel.graphExecution;
|
|
1207
1214
|
const nodeResponseKeys = resolveNodeResponseKeys(graphExecution);
|
|
@@ -1215,8 +1222,8 @@ export function createExellixGraphRuntime(opts) {
|
|
|
1215
1222
|
const { finalizer } = validateGraphFinalizer(graph);
|
|
1216
1223
|
const engine = opts.engineFactory.create({
|
|
1217
1224
|
graph,
|
|
1218
|
-
mode:
|
|
1219
|
-
goalNodeId:
|
|
1225
|
+
mode: plannerMode,
|
|
1226
|
+
goalNodeId: plannerGoalNodeId,
|
|
1220
1227
|
dimension: runtime.dimension,
|
|
1221
1228
|
initialState: runtime.initialState,
|
|
1222
1229
|
initialVariables: runtime.initialVariables,
|
|
@@ -1226,19 +1233,15 @@ export function createExellixGraphRuntime(opts) {
|
|
|
1226
1233
|
const errors = [];
|
|
1227
1234
|
const finalizerNodeId = String(finalizer.id);
|
|
1228
1235
|
const jobCorrelation = job?.jobType != null ? { jobType: job.jobType } : undefined;
|
|
1229
|
-
|
|
1230
|
-
const rawEdges = Array.isArray(graph.edges) ? graph.edges : [];
|
|
1231
|
-
const hasConditionalEdges = rawEdges.some((e) => e && typeof e === "object" && e.when != null);
|
|
1236
|
+
const hasConditionalEdges = hasPlanConditionalEdges(plan);
|
|
1232
1237
|
const incomingByTo = new Map();
|
|
1233
1238
|
if (hasConditionalEdges) {
|
|
1234
|
-
for (const
|
|
1235
|
-
|
|
1236
|
-
continue;
|
|
1237
|
-
const to = e.to;
|
|
1239
|
+
for (const edgeRef of plan.topology.edges) {
|
|
1240
|
+
const to = edgeRef.to.nodeId;
|
|
1238
1241
|
if (typeof to !== "string" || to.length === 0)
|
|
1239
1242
|
continue;
|
|
1240
1243
|
const list = incomingByTo.get(to) ?? [];
|
|
1241
|
-
list.push(
|
|
1244
|
+
list.push(edgeRef);
|
|
1242
1245
|
incomingByTo.set(to, list);
|
|
1243
1246
|
}
|
|
1244
1247
|
}
|
|
@@ -1397,50 +1400,67 @@ export function createExellixGraphRuntime(opts) {
|
|
|
1397
1400
|
? currentExecution.input
|
|
1398
1401
|
: {};
|
|
1399
1402
|
}
|
|
1400
|
-
const
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1403
|
+
const entryGateEv = await evaluatePlanEntryGates(plan.deferredGates.entryGates, recordForStructuredDataFilters(), {
|
|
1404
|
+
executionMemory: currentExecution,
|
|
1405
|
+
jobMemory: currentJobMemory,
|
|
1406
|
+
taskMemory: currentTaskMemory,
|
|
1407
|
+
job,
|
|
1408
|
+
}, runxClient);
|
|
1409
|
+
if (!entryGateEv.ok) {
|
|
1410
|
+
const err = new Error("GRAPH_ENTRY_DATA_FILTERS_REJECTED: plan entry gates rejected runtime.executionMemory.input");
|
|
1411
|
+
err.code = ExellixGraphErrorCode.GRAPH_ENTRY_DATA_FILTERS_REJECTED;
|
|
1412
|
+
logGraphEngineErrorCode(ExellixGraphErrorCode.GRAPH_ENTRY_DATA_FILTERS_REJECTED, err.message, {
|
|
1413
|
+
graphId: resolvedGraphId,
|
|
1414
|
+
jobId,
|
|
1415
|
+
taskId: graphTaskId,
|
|
1416
|
+
error: err,
|
|
1417
|
+
});
|
|
1418
|
+
const finalOutput = buildFinalOutputFromGraphResponse();
|
|
1419
|
+
appendExecutionEvent(executionTrace, {
|
|
1420
|
+
id: `evt:${graphTaskId}:graph.failed`,
|
|
1421
|
+
ts: new Date().toISOString(),
|
|
1422
|
+
level: "error",
|
|
1423
|
+
type: "graph.failed",
|
|
1424
|
+
message: err.message,
|
|
1425
|
+
});
|
|
1426
|
+
const result = {
|
|
1427
|
+
jobId,
|
|
1428
|
+
taskId: graphTaskId,
|
|
1429
|
+
graphId: resolvedGraphId,
|
|
1430
|
+
status: "failed",
|
|
1431
|
+
outputsByNodeId,
|
|
1432
|
+
stepsResponses,
|
|
1433
|
+
engineSnapshot: engine.snapshot(),
|
|
1434
|
+
...(finalOutput !== undefined ? { finalOutput } : {}),
|
|
1435
|
+
errors: [...errors, { error: err }],
|
|
1436
|
+
planAudit,
|
|
1437
|
+
trace: executionTrace,
|
|
1438
|
+
...finalizeGraphPayload("failed"),
|
|
1439
|
+
};
|
|
1440
|
+
const debug = buildDebugTrace();
|
|
1441
|
+
if (debug)
|
|
1442
|
+
result.debug = debug;
|
|
1443
|
+
if (eventEmitter) {
|
|
1444
|
+
eventEmitter.emit(createGraphFailEvent(jobId, resolvedGraphId, graphTaskId, err, {
|
|
1445
|
+
finalMemory: { jobMemory: currentJobMemory, taskMemory: currentTaskMemory, execution: currentExecution, outputsMemory: currentOutputsMemory },
|
|
1446
|
+
...(jobCorrelation ?? {}),
|
|
1447
|
+
}));
|
|
1437
1448
|
}
|
|
1449
|
+
return result;
|
|
1438
1450
|
}
|
|
1439
1451
|
while (true) {
|
|
1440
|
-
const
|
|
1441
|
-
if (
|
|
1452
|
+
const wavePlan = engine.plan();
|
|
1453
|
+
if (wavePlan.status === "completed") {
|
|
1442
1454
|
const graphStatus = errors.length ? "failed" : "completed";
|
|
1443
1455
|
const finalOutput = buildFinalOutputFromGraphResponse();
|
|
1456
|
+
appendExecutionEvent(executionTrace, {
|
|
1457
|
+
id: `evt:${graphTaskId}:graph.${graphStatus}`,
|
|
1458
|
+
ts: new Date().toISOString(),
|
|
1459
|
+
level: graphStatus === "completed" ? "info" : "error",
|
|
1460
|
+
type: graphStatus === "completed" ? "graph.completed" : "graph.failed",
|
|
1461
|
+
message: `Graph execution ${graphStatus}.`,
|
|
1462
|
+
});
|
|
1463
|
+
validateExecutionTrace(executionTrace, plan);
|
|
1444
1464
|
const result = {
|
|
1445
1465
|
jobId,
|
|
1446
1466
|
taskId: graphTaskId,
|
|
@@ -1452,7 +1472,8 @@ export function createExellixGraphRuntime(opts) {
|
|
|
1452
1472
|
...(finalOutput !== undefined ? { finalOutput } : {}),
|
|
1453
1473
|
...(errors.length === 0 ? { finalizerNodeId, finalizerType: finalizer.finalizerType } : {}),
|
|
1454
1474
|
errors: errors.length ? errors : undefined,
|
|
1455
|
-
|
|
1475
|
+
planAudit,
|
|
1476
|
+
trace: executionTrace,
|
|
1456
1477
|
...finalizeGraphPayload(graphStatus),
|
|
1457
1478
|
};
|
|
1458
1479
|
const debug = buildDebugTrace();
|
|
@@ -1476,8 +1497,8 @@ export function createExellixGraphRuntime(opts) {
|
|
|
1476
1497
|
}
|
|
1477
1498
|
return result;
|
|
1478
1499
|
}
|
|
1479
|
-
if (
|
|
1480
|
-
const err = new Error(`GRAPH_BLOCKED: status=${
|
|
1500
|
+
if (wavePlan.status !== "continue") {
|
|
1501
|
+
const err = new Error(`GRAPH_BLOCKED: status=${wavePlan.status}`);
|
|
1481
1502
|
err.code = "GRAPH_BLOCKED";
|
|
1482
1503
|
const finalOutput = buildFinalOutputFromGraphResponse();
|
|
1483
1504
|
const result = {
|
|
@@ -1490,7 +1511,8 @@ export function createExellixGraphRuntime(opts) {
|
|
|
1490
1511
|
engineSnapshot: engine.snapshot(),
|
|
1491
1512
|
...(finalOutput !== undefined ? { finalOutput } : {}),
|
|
1492
1513
|
errors: [...errors, { error: err }],
|
|
1493
|
-
|
|
1514
|
+
planAudit,
|
|
1515
|
+
trace: executionTrace,
|
|
1494
1516
|
...finalizeGraphPayload("failed"),
|
|
1495
1517
|
};
|
|
1496
1518
|
const debug = buildDebugTrace();
|
|
@@ -1504,7 +1526,7 @@ export function createExellixGraphRuntime(opts) {
|
|
|
1504
1526
|
}
|
|
1505
1527
|
return result;
|
|
1506
1528
|
}
|
|
1507
|
-
let runnableNodes =
|
|
1529
|
+
let runnableNodes = wavePlan.nextNodes ?? [];
|
|
1508
1530
|
// Conditional edge filtering: a node with incoming edges that are *all* conditional must
|
|
1509
1531
|
// have at least one incoming edge whose `when` evaluates true to be runnable this round.
|
|
1510
1532
|
// Roots and nodes with at least one unconditional incoming edge always pass through.
|
|
@@ -1521,7 +1543,7 @@ export function createExellixGraphRuntime(opts) {
|
|
|
1521
1543
|
const incoming = incomingByTo.get(id) ?? [];
|
|
1522
1544
|
if (incoming.length === 0)
|
|
1523
1545
|
return true;
|
|
1524
|
-
const hasUnconditional = incoming.some((e) => e?.
|
|
1546
|
+
const hasUnconditional = incoming.some((e) => !e?.hasDeferredGate);
|
|
1525
1547
|
if (hasUnconditional)
|
|
1526
1548
|
return true;
|
|
1527
1549
|
const planningContext = buildPredicateEvalContextForNode({
|
|
@@ -1531,7 +1553,10 @@ export function createExellixGraphRuntime(opts) {
|
|
|
1531
1553
|
node: n,
|
|
1532
1554
|
runtimeTaskVariables: runtime.taskVariables,
|
|
1533
1555
|
});
|
|
1534
|
-
return incoming.some((
|
|
1556
|
+
return incoming.some((edgeRef) => {
|
|
1557
|
+
const gate = plan.deferredGates.edgeGates[edgeRef.edgeId];
|
|
1558
|
+
return evaluateDeferredEdgeGate(gate, planningContext);
|
|
1559
|
+
});
|
|
1535
1560
|
});
|
|
1536
1561
|
}
|
|
1537
1562
|
const dataFiltersRecord = recordForStructuredDataFilters();
|
|
@@ -1555,7 +1580,7 @@ export function createExellixGraphRuntime(opts) {
|
|
|
1555
1580
|
node: n,
|
|
1556
1581
|
runtimeTaskVariables: runtime.taskVariables,
|
|
1557
1582
|
});
|
|
1558
|
-
const condEv = await
|
|
1583
|
+
const condEv = await evaluateDeferredNodeGate(nodeDeferredGate(plan, String(n.id)), { ...conditionCtxBase, ...nodePredicateCtx }, dataFiltersRecord, runxClient);
|
|
1559
1584
|
if (!condEv.ok) {
|
|
1560
1585
|
skippedByConditions.push({
|
|
1561
1586
|
node: n,
|
|
@@ -1605,27 +1630,32 @@ export function createExellixGraphRuntime(opts) {
|
|
|
1605
1630
|
context: { model: graph, runtime, graphId: resolvedGraphId, jobId, taskId: graphTaskId, node: node },
|
|
1606
1631
|
});
|
|
1607
1632
|
const nodeTaskMemory = isPlainRecord(currentTaskMemory) ? { ...currentTaskMemory } : currentTaskMemory;
|
|
1608
|
-
const
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
node: node,
|
|
1616
|
-
runtimeTaskVariables: runtime.taskVariables,
|
|
1617
|
-
}),
|
|
1618
|
-
executionInput: dataFiltersRecord,
|
|
1619
|
-
runx: runxClient,
|
|
1620
|
-
graphId: resolvedGraphId,
|
|
1621
|
-
nodeId: typeof node.id === "string" ? node.id : String(node.id),
|
|
1622
|
-
jobId,
|
|
1633
|
+
const nodeIdStr = String(node.id);
|
|
1634
|
+
appendExecutionEvent(executionTrace, {
|
|
1635
|
+
id: `evt:${graphTaskId}:node.started:${nodeIdStr}`,
|
|
1636
|
+
ts: new Date().toISOString(),
|
|
1637
|
+
level: "info",
|
|
1638
|
+
type: "node.started",
|
|
1639
|
+
nodeId: nodeIdStr,
|
|
1623
1640
|
});
|
|
1641
|
+
const nodePlanEntryResolved = nodePlanForId(plan, nodeIdStr);
|
|
1642
|
+
const finalizerPlanEntryResolved = node.type === "finalizer"
|
|
1643
|
+
? finalizerPlanForId(plan, nodeIdStr)
|
|
1644
|
+
: undefined;
|
|
1645
|
+
const taskNodeForExecute = nodePlanEntryResolved && node.type !== "finalizer"
|
|
1646
|
+
? applyNodePlanInvoke(node, nodePlanEntryResolved)
|
|
1647
|
+
: node;
|
|
1648
|
+
const effectiveModelConfig = finalizerPlanEntryResolved != null
|
|
1649
|
+
? graphAiModelConfigFromFinalizerPlan(finalizerPlanEntryResolved)
|
|
1650
|
+
: nodePlanEntryResolved && node.type !== "finalizer"
|
|
1651
|
+
? graphAiModelConfigFromNodePlan(nodePlanEntryResolved)
|
|
1652
|
+
: undefined;
|
|
1653
|
+
const nodeExecutionPipeline = nodePlanEntryResolved != null ? executionPipelineFromNodePlan(nodePlanEntryResolved) : undefined;
|
|
1624
1654
|
const r = await executeNode({
|
|
1625
1655
|
graphId: resolvedGraphId,
|
|
1626
1656
|
graph,
|
|
1627
1657
|
graphRunTaskId: graphTaskId,
|
|
1628
|
-
node,
|
|
1658
|
+
node: taskNodeForExecute,
|
|
1629
1659
|
job,
|
|
1630
1660
|
jobMemory: currentJobMemory,
|
|
1631
1661
|
taskMemory: nodeTaskMemory,
|
|
@@ -1639,7 +1669,7 @@ export function createExellixGraphRuntime(opts) {
|
|
|
1639
1669
|
runTaskIdentity: merged.runTaskIdentity,
|
|
1640
1670
|
runTaskExecutionMode: merged.runTaskExecutionMode,
|
|
1641
1671
|
runTaskDiagnostics: merged.runTaskDiagnostics,
|
|
1642
|
-
graphExecutionPipeline: merged.executionPipeline,
|
|
1672
|
+
graphExecutionPipeline: nodeExecutionPipeline ?? merged.executionPipeline,
|
|
1643
1673
|
skillKeyResolution: merged.skillKeyResolution,
|
|
1644
1674
|
nodeTimeoutMs: merged.nodeTimeoutMs,
|
|
1645
1675
|
clearSynthesizedContextPerNode: merged.clearSynthesizedContextPerNode,
|
|
@@ -1651,6 +1681,13 @@ export function createExellixGraphRuntime(opts) {
|
|
|
1651
1681
|
outputsByNodeId[r.nodeId] = r.output;
|
|
1652
1682
|
appendStepResponse(node, r.output);
|
|
1653
1683
|
engine.commit({ nodeId: r.nodeId, output: r.output });
|
|
1684
|
+
appendExecutionEvent(executionTrace, {
|
|
1685
|
+
id: `evt:${graphTaskId}:node.completed:${nodeIdStr}`,
|
|
1686
|
+
ts: new Date().toISOString(),
|
|
1687
|
+
level: "info",
|
|
1688
|
+
type: "node.completed",
|
|
1689
|
+
nodeId: nodeIdStr,
|
|
1690
|
+
});
|
|
1654
1691
|
const trl = r.taskRunLog;
|
|
1655
1692
|
if (trl?.length)
|
|
1656
1693
|
taskRunLogBuffer.push(...trl);
|
|
@@ -1722,7 +1759,8 @@ export function createExellixGraphRuntime(opts) {
|
|
|
1722
1759
|
engineSnapshot: engine.snapshot(),
|
|
1723
1760
|
...(finalOutput !== undefined ? { finalOutput } : {}),
|
|
1724
1761
|
errors,
|
|
1725
|
-
|
|
1762
|
+
planAudit,
|
|
1763
|
+
trace: executionTrace,
|
|
1726
1764
|
...finalizeGraphPayload("failed"),
|
|
1727
1765
|
};
|
|
1728
1766
|
const debug = buildDebugTrace();
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* a `{ model, runtime }` request with per-graph `GraphEntryContract` + seeded
|
|
6
6
|
* `runtime.executionMemory` objects built here.
|
|
7
7
|
*/
|
|
8
|
+
import { compileExellixExecutablePlan } from '../adapters/compileExellixExecutablePlan.js';
|
|
8
9
|
import { mergeGraphDocumentModel } from '../types/refs.js';
|
|
9
10
|
import { createExellixGraphRuntime } from './ExellixGraphRuntime.js';
|
|
10
11
|
import { selectByPath, writeByPath } from './pathExpr.js';
|
|
@@ -76,7 +77,7 @@ export function buildExecutionSeedFromGraphEntry(graphEntry, sources = {}) {
|
|
|
76
77
|
/** Builds the canonical graph-engine execution request for matrix-style hosts. */
|
|
77
78
|
export function buildGraphExecutionRequest(input) {
|
|
78
79
|
return {
|
|
79
|
-
|
|
80
|
+
plan: compileExellixExecutablePlan(input.model, input.runtime),
|
|
80
81
|
runtime: input.runtime,
|
|
81
82
|
};
|
|
82
83
|
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { Graph } from '../types/refs.js';
|
|
2
|
+
import type { ExecuteGraphInput, GraphRuntimeObject } from './ExellixGraphRuntime.js';
|
|
3
|
+
/**
|
|
4
|
+
* Removed from the graphs-studio execute envelope (7.8.3+). No reader, no fallback.
|
|
5
|
+
* Default model selection belongs on {@link Graph}.modelConfig only.
|
|
6
|
+
*/
|
|
7
|
+
export declare const STUDIO_GRAPH_EXECUTE_REMOVED_KEYS: readonly ["graphDefaultModel"];
|
|
8
|
+
export type StudioGraphExecuteRemovedKey = (typeof STUDIO_GRAPH_EXECUTE_REMOVED_KEYS)[number];
|
|
9
|
+
/**
|
|
10
|
+
* Graphs-studio / playground graph execute envelope before host adaptation to
|
|
11
|
+
* {@link ExecuteGraphInput}. Credential and simulate-only fields are host concerns;
|
|
12
|
+
* graph-engine validates shape and forbidden keys only.
|
|
13
|
+
*/
|
|
14
|
+
export type StudioGraphExecuteRequest = {
|
|
15
|
+
mode: 'graph';
|
|
16
|
+
graph: Graph;
|
|
17
|
+
jobId: string;
|
|
18
|
+
runtime: Pick<GraphRuntimeObject, 'input' | 'inputs' | 'jobMemory' | 'taskMemory' | 'executionMemory' | 'outputsMemory' | 'variables' | 'jobVariables' | 'taskVariables' | 'nodes'>;
|
|
19
|
+
studioId?: string;
|
|
20
|
+
executionMode?: string;
|
|
21
|
+
runLogMode?: GraphRuntimeObject['runLogMode'];
|
|
22
|
+
maxRunLogEntries?: number;
|
|
23
|
+
maxRunLogDataJsonChars?: number;
|
|
24
|
+
graphExecution?: {
|
|
25
|
+
mode?: GraphRuntimeObject['mode'];
|
|
26
|
+
goalNodeId?: string;
|
|
27
|
+
dimension?: string;
|
|
28
|
+
};
|
|
29
|
+
runTaskDiagnostics?: GraphRuntimeObject['runTaskDiagnostics'];
|
|
30
|
+
exampleIndex?: number;
|
|
31
|
+
validateGraphEntrySeed?: boolean;
|
|
32
|
+
openrouterApiKey?: string;
|
|
33
|
+
};
|
|
34
|
+
export declare function getStudioGraphExecuteRemovedKeyViolations(request: unknown): StudioGraphExecuteRemovedKey[];
|
|
35
|
+
/**
|
|
36
|
+
* Rejects removed studio execute keys (notably `graphDefaultModel`).
|
|
37
|
+
* Hosts must persist model defaults on `graph.modelConfig` and stop sending request-level mirrors.
|
|
38
|
+
*/
|
|
39
|
+
export declare function assertCanonicalStudioGraphExecuteRequest(request: unknown, context?: {
|
|
40
|
+
jobId?: string;
|
|
41
|
+
graphId?: string;
|
|
42
|
+
}): asserts request is StudioGraphExecuteRequest;
|
|
43
|
+
export type BuildGraphExecutionRequestFromStudioExecuteOptions = {
|
|
44
|
+
agentId?: string;
|
|
45
|
+
jobTypeId?: string;
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Maps a validated studio execute envelope to {@link ExecuteGraphInput}.
|
|
49
|
+
* Does not read or merge removed request-level model defaults — graph.modelConfig is authoritative.
|
|
50
|
+
*/
|
|
51
|
+
export declare function buildGraphExecutionRequestFromStudioExecute(request: StudioGraphExecuteRequest, options?: BuildGraphExecutionRequestFromStudioExecuteOptions): ExecuteGraphInput;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { compileExellixExecutablePlan } from '../adapters/compileExellixExecutablePlan.js';
|
|
2
|
+
import { ExellixGraphError } from '../errors/ExellixGraphError.js';
|
|
3
|
+
import { ExellixGraphErrorCode } from '../errors/exellixGraphErrorCodes.js';
|
|
4
|
+
/**
|
|
5
|
+
* Removed from the graphs-studio execute envelope (7.8.3+). No reader, no fallback.
|
|
6
|
+
* Default model selection belongs on {@link Graph}.modelConfig only.
|
|
7
|
+
*/
|
|
8
|
+
export const STUDIO_GRAPH_EXECUTE_REMOVED_KEYS = ['graphDefaultModel'];
|
|
9
|
+
function isPlainObject(v) {
|
|
10
|
+
return v != null && typeof v === 'object' && !Array.isArray(v);
|
|
11
|
+
}
|
|
12
|
+
export function getStudioGraphExecuteRemovedKeyViolations(request) {
|
|
13
|
+
if (!isPlainObject(request))
|
|
14
|
+
return [];
|
|
15
|
+
return STUDIO_GRAPH_EXECUTE_REMOVED_KEYS.filter((key) => Object.prototype.hasOwnProperty.call(request, key));
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Rejects removed studio execute keys (notably `graphDefaultModel`).
|
|
19
|
+
* Hosts must persist model defaults on `graph.modelConfig` and stop sending request-level mirrors.
|
|
20
|
+
*/
|
|
21
|
+
export function assertCanonicalStudioGraphExecuteRequest(request, context) {
|
|
22
|
+
const removed = getStudioGraphExecuteRemovedKeyViolations(request);
|
|
23
|
+
if (removed.length > 0) {
|
|
24
|
+
throw new ExellixGraphError(ExellixGraphErrorCode.NON_CANONICAL_STUDIO_GRAPH_EXECUTE_REQUEST, `Studio graph execute request must not include removed field(s): ${removed.join(', ')}. Model defaults belong on graph.modelConfig only; request-level graphDefaultModel was removed with no legacy fallback.`, { ...context, removedKeys: removed });
|
|
25
|
+
}
|
|
26
|
+
if (!isPlainObject(request)) {
|
|
27
|
+
throw new ExellixGraphError(ExellixGraphErrorCode.NON_CANONICAL_STUDIO_GRAPH_EXECUTE_REQUEST, 'Studio graph execute request must be a plain object.', context);
|
|
28
|
+
}
|
|
29
|
+
if (request.mode !== 'graph') {
|
|
30
|
+
throw new ExellixGraphError(ExellixGraphErrorCode.NON_CANONICAL_STUDIO_GRAPH_EXECUTE_REQUEST, 'Studio graph execute request mode must be "graph".', context);
|
|
31
|
+
}
|
|
32
|
+
const jobId = typeof request.jobId === 'string' ? request.jobId.trim() : '';
|
|
33
|
+
if (!jobId) {
|
|
34
|
+
throw new ExellixGraphError(ExellixGraphErrorCode.NON_CANONICAL_STUDIO_GRAPH_EXECUTE_REQUEST, 'Studio graph execute request requires a non-empty jobId.', context);
|
|
35
|
+
}
|
|
36
|
+
if (!isPlainObject(request.graph)) {
|
|
37
|
+
throw new ExellixGraphError(ExellixGraphErrorCode.NON_CANONICAL_STUDIO_GRAPH_EXECUTE_REQUEST, 'Studio graph execute request requires graph (GraphModelObject).', { ...context, jobId });
|
|
38
|
+
}
|
|
39
|
+
if (!isPlainObject(request.runtime)) {
|
|
40
|
+
throw new ExellixGraphError(ExellixGraphErrorCode.NON_CANONICAL_STUDIO_GRAPH_EXECUTE_REQUEST, 'Studio graph execute request requires runtime.', { ...context, jobId });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Maps a validated studio execute envelope to {@link ExecuteGraphInput}.
|
|
45
|
+
* Does not read or merge removed request-level model defaults — graph.modelConfig is authoritative.
|
|
46
|
+
*/
|
|
47
|
+
export function buildGraphExecutionRequestFromStudioExecute(request, options) {
|
|
48
|
+
assertCanonicalStudioGraphExecuteRequest(request);
|
|
49
|
+
const jobId = request.jobId.trim();
|
|
50
|
+
const graphId = request.graph != null && typeof request.graph === 'object' && typeof request.graph.id === 'string'
|
|
51
|
+
? request.graph.id
|
|
52
|
+
: undefined;
|
|
53
|
+
const runtime = {
|
|
54
|
+
jobId,
|
|
55
|
+
job: {
|
|
56
|
+
id: jobId,
|
|
57
|
+
jobId,
|
|
58
|
+
agentId: options?.agentId ?? 'standalone-agent',
|
|
59
|
+
jobTypeId: options?.jobTypeId ?? 'exellix-graph-job',
|
|
60
|
+
},
|
|
61
|
+
...request.runtime,
|
|
62
|
+
...(request.graphExecution?.mode != null ? { mode: request.graphExecution.mode } : {}),
|
|
63
|
+
...(request.graphExecution?.goalNodeId != null
|
|
64
|
+
? { goalNodeId: request.graphExecution.goalNodeId }
|
|
65
|
+
: {}),
|
|
66
|
+
...(request.graphExecution?.dimension != null ? { dimension: request.graphExecution.dimension } : {}),
|
|
67
|
+
...(request.runLogMode != null ? { runLogMode: request.runLogMode } : {}),
|
|
68
|
+
...(request.maxRunLogEntries != null ? { maxRunLogEntries: request.maxRunLogEntries } : {}),
|
|
69
|
+
...(request.maxRunLogDataJsonChars != null
|
|
70
|
+
? { maxRunLogDataJsonChars: request.maxRunLogDataJsonChars }
|
|
71
|
+
: {}),
|
|
72
|
+
...(request.runTaskDiagnostics != null ? { runTaskDiagnostics: request.runTaskDiagnostics } : {}),
|
|
73
|
+
};
|
|
74
|
+
return {
|
|
75
|
+
plan: compileExellixExecutablePlan(request.graph, runtime),
|
|
76
|
+
runtime,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { Graph, GraphModelObject } from '../src/types/refs.js';
|
|
2
|
+
import type { ExecuteGraphInput, GraphRuntimeObject } from '../src/runtime/ExellixGraphRuntime.js';
|
|
3
|
+
/** Builds `{ plan, runtime }` for tests and hosts that still author exellix GraphModelObject JSON. */
|
|
4
|
+
export declare function buildExecuteGraphInput(model: Graph | GraphModelObject, runtime: GraphRuntimeObject): ExecuteGraphInput;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { compileExellixExecutablePlan } from '../src/adapters/compileExellixExecutablePlan.js';
|
|
2
|
+
/** Builds `{ plan, runtime }` for tests and hosts that still author exellix GraphModelObject JSON. */
|
|
3
|
+
export function buildExecuteGraphInput(model, runtime) {
|
|
4
|
+
return {
|
|
5
|
+
plan: compileExellixExecutablePlan(model, runtime),
|
|
6
|
+
runtime,
|
|
7
|
+
};
|
|
8
|
+
}
|
package/dist/testkit/index.d.ts
CHANGED
|
@@ -2,4 +2,5 @@ export { InMemoryGraphLoader } from './inMemoryGraphLoader.js';
|
|
|
2
2
|
export { DepGraphEngineFactory } from './depGraphEngineFactory.js';
|
|
3
3
|
export { RealTasksClient } from './RealTasksClient.js';
|
|
4
4
|
export { createTestExellixGraphRuntime } from './testModelAliasRuntime.js';
|
|
5
|
+
export { buildExecuteGraphInput } from './buildExecuteGraphInput.js';
|
|
5
6
|
export { tryLoadExellixAiTasksRuntimeSubtree, loadExellixGraphRuntimeObjects, } from './exellixRuntimeObjects.js';
|
package/dist/testkit/index.js
CHANGED
|
@@ -2,4 +2,5 @@ export { InMemoryGraphLoader } from './inMemoryGraphLoader.js';
|
|
|
2
2
|
export { DepGraphEngineFactory } from './depGraphEngineFactory.js';
|
|
3
3
|
export { RealTasksClient } from './RealTasksClient.js';
|
|
4
4
|
export { createTestExellixGraphRuntime } from './testModelAliasRuntime.js';
|
|
5
|
+
export { buildExecuteGraphInput } from './buildExecuteGraphInput.js';
|
|
5
6
|
export { tryLoadExellixAiTasksRuntimeSubtree, loadExellixGraphRuntimeObjects, } from './exellixRuntimeObjects.js';
|
|
@@ -9,12 +9,12 @@ export function createTestExellixGraphRuntime(opts) {
|
|
|
9
9
|
const base = input.runtime ?? {};
|
|
10
10
|
const jobId = typeof base.jobId === 'string' && base.jobId.trim() !== ''
|
|
11
11
|
? base.jobId
|
|
12
|
-
: `job-${input.
|
|
12
|
+
: `job-${input.plan.source.graphId}`;
|
|
13
13
|
const job = base.job != null && typeof base.job === 'object'
|
|
14
14
|
? base.job
|
|
15
15
|
: { agentId: 'test-agent', jobType: 'unit-test' };
|
|
16
16
|
return executeGraph({
|
|
17
|
-
|
|
17
|
+
plan: input.plan,
|
|
18
18
|
runtime: { ...base, jobId, job },
|
|
19
19
|
});
|
|
20
20
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exellix/graph-engine",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "8.0.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Graph executor SDK",
|
|
6
6
|
"main": "dist/src/index.js",
|
|
@@ -58,7 +58,12 @@
|
|
|
58
58
|
"@x12i/env": "4.0.1",
|
|
59
59
|
"@x12i/funcx": "4.4.4",
|
|
60
60
|
"@x12i/graphenix": "2.5.0",
|
|
61
|
+
"@x12i/graphenix-executable-contracts": "^1.0.0",
|
|
62
|
+
"@x12i/graphenix-execute-envelope": "^1.0.0",
|
|
61
63
|
"@x12i/graphenix-format": "2.0.0",
|
|
64
|
+
"@x12i/graphenix-plan-format": "^1.0.0",
|
|
65
|
+
"@x12i/graphenix-plan-compiler": "^1.0.0",
|
|
66
|
+
"@x12i/graphenix-trace-format": "^1.0.0",
|
|
62
67
|
"@x12i/logxer": "^4.6.0",
|
|
63
68
|
"@x12i/memorix-descriptors": "1.6.0",
|
|
64
69
|
"@x12i/memorix-retrieval": "1.11.2",
|
|
@@ -68,6 +73,7 @@
|
|
|
68
73
|
},
|
|
69
74
|
"devDependencies": {
|
|
70
75
|
"@types/node": "25.9.1",
|
|
76
|
+
"@x12i/graphenix-authoring-format": "^1.0.1",
|
|
71
77
|
"tsx": "4.22.3",
|
|
72
78
|
"typescript": "6.0.3"
|
|
73
79
|
}
|