@exellix/graph-engine 7.8.2 → 8.0.1
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 +35 -0
- package/README.md +26 -24
- package/dist/src/compile/compileExellixExecutablePlan.d.ts +9 -0
- package/dist/src/compile/compileExellixExecutablePlan.js +24 -0
- package/dist/src/errors/exellixGraphErrorCodes.d.ts +5 -0
- package/dist/src/errors/exellixGraphErrorCodes.js +5 -0
- package/dist/src/index.d.ts +10 -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 -97
- package/dist/src/runtime/executionMatrixHost.d.ts +1 -1
- package/dist/src/runtime/executionMatrixHost.js +3 -2
- package/dist/src/runtime/studioGraphExecuteRequest.d.ts +51 -0
- package/dist/src/runtime/studioGraphExecuteRequest.js +78 -0
- package/dist/src/types/refs.d.ts +1 -1
- 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 +8 -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,8 @@ 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
|
-
import { migrateLegacyGraphResponse } from "./graphResponseMigration.js";
|
|
23
|
-
import { computeGraphDocumentContentSha256 } from "./graphDocumentFingerprint.js";
|
|
24
28
|
import { buildAiTasksObservabilityRecord } from "./aiTasksObservability.js";
|
|
25
29
|
import { buildMainLlmCallForRunTask, buildRunTaskIdentityEnvelope, shouldForwardRunTaskTraceMode, } from "./runTaskAugments.js";
|
|
26
30
|
import { buildRunLog, extractLogxerCorrelationFromMetadata, extractTaskRunLogFromMetadata, resolveRunLogLimits, } from "./buildRunLog.js";
|
|
@@ -33,9 +37,6 @@ import { buildPredicateEvalContextForNode, mirrorTaskVariablesOnExecution, readE
|
|
|
33
37
|
import { mergeExellixGraphRuntimeInvocation } from "./mergeExellixGraphRuntimeInvocation.js";
|
|
34
38
|
import { resolveStepRetryPolicy, runRunTaskWithRetry } from "./stepRetry.js";
|
|
35
39
|
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
40
|
import { resolveModelConfigForNode } from "./resolveModelConfigForNode.js";
|
|
40
41
|
import { toRunTaskModelConfig } from "./graphAiModelConfig.js";
|
|
41
42
|
import { isGraphAiModelConfig } from "./modelConfigSelection.js";
|
|
@@ -1155,36 +1156,45 @@ export function createExellixGraphRuntime(opts) {
|
|
|
1155
1156
|
};
|
|
1156
1157
|
}
|
|
1157
1158
|
async function executeGraph(input) {
|
|
1158
|
-
const {
|
|
1159
|
-
const {
|
|
1159
|
+
const { plan: suppliedPlan, runtime } = input;
|
|
1160
|
+
const { plan } = preparePlanExecuteEntry(suppliedPlan, runtime);
|
|
1161
|
+
const materialized = embeddedGraphToExellixGraph(plan);
|
|
1162
|
+
const graph = materialized;
|
|
1160
1163
|
const merged = mergeExellixGraphRuntimeInvocation(runtime, opts);
|
|
1161
|
-
const
|
|
1164
|
+
const scheduling = plan.schedulingPolicy;
|
|
1165
|
+
const plannerMode = scheduling?.mode ?? runtime.mode;
|
|
1166
|
+
const plannerGoalNodeId = scheduling?.goalNodeId ?? runtime.goalNodeId;
|
|
1167
|
+
const failFast = scheduling?.failFast ?? merged.failFast;
|
|
1162
1168
|
const debugMode = runtime.debugMode === true;
|
|
1163
1169
|
const eventEmitter = runtime.eventEmitter ?? opts.eventEmitter;
|
|
1164
1170
|
const jobId = assertHostJobId(runtime.jobId);
|
|
1165
1171
|
const graphTaskId = newGraphRunTaskId();
|
|
1166
1172
|
const job = { ...(runtime.job ?? {}), id: jobId, jobId };
|
|
1167
|
-
if (
|
|
1173
|
+
if (plannerMode === "backward" && !plannerGoalNodeId) {
|
|
1168
1174
|
const err = new Error(`BACKWARD_GOAL_REQUIRED: mode=backward requires goalNodeId`);
|
|
1169
1175
|
err.code = "BACKWARD_GOAL_REQUIRED";
|
|
1170
1176
|
throw err;
|
|
1171
1177
|
}
|
|
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);
|
|
1178
|
+
const resolvedGraphId = String(plan.source.graphId);
|
|
1177
1179
|
const runLogxer = createGraphEngineLogxer({ logging: merged.logging });
|
|
1180
|
+
const executionTrace = createExecutionTrace(plan, runtime);
|
|
1181
|
+
appendExecutionEvent(executionTrace, {
|
|
1182
|
+
id: `evt:${graphTaskId}:graph.started`,
|
|
1183
|
+
ts: new Date().toISOString(),
|
|
1184
|
+
level: "info",
|
|
1185
|
+
type: "graph.started",
|
|
1186
|
+
message: "Graph execution started.",
|
|
1187
|
+
});
|
|
1188
|
+
const planAudit = computePlanAudit(plan);
|
|
1178
1189
|
return runWithAiTasksStackLogging(merged.logging, () => runGraphWithLogContext({ jobId, taskId: graphTaskId, graphId: resolvedGraphId, runId: graphTaskId }, async () => {
|
|
1179
1190
|
bindGraphEngineRunLogxer(runLogxer);
|
|
1180
1191
|
try {
|
|
1181
|
-
assertCanonicalGraphDocument(graph, { jobId, graphId: resolvedGraphId }, { mode: 'execute' });
|
|
1182
1192
|
assertCanonicalGraphRuntimeObject(runtime, { jobId, graphId: resolvedGraphId });
|
|
1183
1193
|
let runxClient = opts.runx;
|
|
1184
|
-
if (
|
|
1194
|
+
if (planNeedsRunx(plan)) {
|
|
1185
1195
|
if (!runxClient) {
|
|
1186
1196
|
if (!opts.runxCreateOptions) {
|
|
1187
|
-
const err = new Error("RUNX_REQUIRED:
|
|
1197
|
+
const err = new Error("RUNX_REQUIRED: executable plan deferred gates require runx. Pass runx or runxCreateOptions on createExellixGraphRuntime.");
|
|
1188
1198
|
err.code = "RUNX_REQUIRED";
|
|
1189
1199
|
throw err;
|
|
1190
1200
|
}
|
|
@@ -1198,10 +1208,6 @@ export function createExellixGraphRuntime(opts) {
|
|
|
1198
1208
|
await runxClient.reload();
|
|
1199
1209
|
}
|
|
1200
1210
|
}
|
|
1201
|
-
const graphAudit = {
|
|
1202
|
-
source: 'model',
|
|
1203
|
-
contentSha256: computeGraphDocumentContentSha256(suppliedGraph),
|
|
1204
|
-
};
|
|
1205
1211
|
const graphDocumentModel = mergeGraphDocumentModel(graph);
|
|
1206
1212
|
const graphExecution = graphDocumentModel.graphExecution;
|
|
1207
1213
|
const nodeResponseKeys = resolveNodeResponseKeys(graphExecution);
|
|
@@ -1215,8 +1221,8 @@ export function createExellixGraphRuntime(opts) {
|
|
|
1215
1221
|
const { finalizer } = validateGraphFinalizer(graph);
|
|
1216
1222
|
const engine = opts.engineFactory.create({
|
|
1217
1223
|
graph,
|
|
1218
|
-
mode:
|
|
1219
|
-
goalNodeId:
|
|
1224
|
+
mode: plannerMode,
|
|
1225
|
+
goalNodeId: plannerGoalNodeId,
|
|
1220
1226
|
dimension: runtime.dimension,
|
|
1221
1227
|
initialState: runtime.initialState,
|
|
1222
1228
|
initialVariables: runtime.initialVariables,
|
|
@@ -1226,19 +1232,15 @@ export function createExellixGraphRuntime(opts) {
|
|
|
1226
1232
|
const errors = [];
|
|
1227
1233
|
const finalizerNodeId = String(finalizer.id);
|
|
1228
1234
|
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);
|
|
1235
|
+
const hasConditionalEdges = hasPlanConditionalEdges(plan);
|
|
1232
1236
|
const incomingByTo = new Map();
|
|
1233
1237
|
if (hasConditionalEdges) {
|
|
1234
|
-
for (const
|
|
1235
|
-
|
|
1236
|
-
continue;
|
|
1237
|
-
const to = e.to;
|
|
1238
|
+
for (const edgeRef of plan.topology.edges) {
|
|
1239
|
+
const to = edgeRef.to.nodeId;
|
|
1238
1240
|
if (typeof to !== "string" || to.length === 0)
|
|
1239
1241
|
continue;
|
|
1240
1242
|
const list = incomingByTo.get(to) ?? [];
|
|
1241
|
-
list.push(
|
|
1243
|
+
list.push(edgeRef);
|
|
1242
1244
|
incomingByTo.set(to, list);
|
|
1243
1245
|
}
|
|
1244
1246
|
}
|
|
@@ -1397,50 +1399,67 @@ export function createExellixGraphRuntime(opts) {
|
|
|
1397
1399
|
? currentExecution.input
|
|
1398
1400
|
: {};
|
|
1399
1401
|
}
|
|
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
|
-
|
|
1402
|
+
const entryGateEv = await evaluatePlanEntryGates(plan.deferredGates.entryGates, recordForStructuredDataFilters(), {
|
|
1403
|
+
executionMemory: currentExecution,
|
|
1404
|
+
jobMemory: currentJobMemory,
|
|
1405
|
+
taskMemory: currentTaskMemory,
|
|
1406
|
+
job,
|
|
1407
|
+
}, runxClient);
|
|
1408
|
+
if (!entryGateEv.ok) {
|
|
1409
|
+
const err = new Error("GRAPH_ENTRY_DATA_FILTERS_REJECTED: plan entry gates rejected runtime.executionMemory.input");
|
|
1410
|
+
err.code = ExellixGraphErrorCode.GRAPH_ENTRY_DATA_FILTERS_REJECTED;
|
|
1411
|
+
logGraphEngineErrorCode(ExellixGraphErrorCode.GRAPH_ENTRY_DATA_FILTERS_REJECTED, err.message, {
|
|
1412
|
+
graphId: resolvedGraphId,
|
|
1413
|
+
jobId,
|
|
1414
|
+
taskId: graphTaskId,
|
|
1415
|
+
error: err,
|
|
1416
|
+
});
|
|
1417
|
+
const finalOutput = buildFinalOutputFromGraphResponse();
|
|
1418
|
+
appendExecutionEvent(executionTrace, {
|
|
1419
|
+
id: `evt:${graphTaskId}:graph.failed`,
|
|
1420
|
+
ts: new Date().toISOString(),
|
|
1421
|
+
level: "error",
|
|
1422
|
+
type: "graph.failed",
|
|
1423
|
+
message: err.message,
|
|
1424
|
+
});
|
|
1425
|
+
const result = {
|
|
1426
|
+
jobId,
|
|
1427
|
+
taskId: graphTaskId,
|
|
1428
|
+
graphId: resolvedGraphId,
|
|
1429
|
+
status: "failed",
|
|
1430
|
+
outputsByNodeId,
|
|
1431
|
+
stepsResponses,
|
|
1432
|
+
engineSnapshot: engine.snapshot(),
|
|
1433
|
+
...(finalOutput !== undefined ? { finalOutput } : {}),
|
|
1434
|
+
errors: [...errors, { error: err }],
|
|
1435
|
+
planAudit,
|
|
1436
|
+
trace: executionTrace,
|
|
1437
|
+
...finalizeGraphPayload("failed"),
|
|
1438
|
+
};
|
|
1439
|
+
const debug = buildDebugTrace();
|
|
1440
|
+
if (debug)
|
|
1441
|
+
result.debug = debug;
|
|
1442
|
+
if (eventEmitter) {
|
|
1443
|
+
eventEmitter.emit(createGraphFailEvent(jobId, resolvedGraphId, graphTaskId, err, {
|
|
1444
|
+
finalMemory: { jobMemory: currentJobMemory, taskMemory: currentTaskMemory, execution: currentExecution, outputsMemory: currentOutputsMemory },
|
|
1445
|
+
...(jobCorrelation ?? {}),
|
|
1446
|
+
}));
|
|
1437
1447
|
}
|
|
1448
|
+
return result;
|
|
1438
1449
|
}
|
|
1439
1450
|
while (true) {
|
|
1440
|
-
const
|
|
1441
|
-
if (
|
|
1451
|
+
const wavePlan = engine.plan();
|
|
1452
|
+
if (wavePlan.status === "completed") {
|
|
1442
1453
|
const graphStatus = errors.length ? "failed" : "completed";
|
|
1443
1454
|
const finalOutput = buildFinalOutputFromGraphResponse();
|
|
1455
|
+
appendExecutionEvent(executionTrace, {
|
|
1456
|
+
id: `evt:${graphTaskId}:graph.${graphStatus}`,
|
|
1457
|
+
ts: new Date().toISOString(),
|
|
1458
|
+
level: graphStatus === "completed" ? "info" : "error",
|
|
1459
|
+
type: graphStatus === "completed" ? "graph.completed" : "graph.failed",
|
|
1460
|
+
message: `Graph execution ${graphStatus}.`,
|
|
1461
|
+
});
|
|
1462
|
+
validateExecutionTrace(executionTrace, plan);
|
|
1444
1463
|
const result = {
|
|
1445
1464
|
jobId,
|
|
1446
1465
|
taskId: graphTaskId,
|
|
@@ -1452,7 +1471,8 @@ export function createExellixGraphRuntime(opts) {
|
|
|
1452
1471
|
...(finalOutput !== undefined ? { finalOutput } : {}),
|
|
1453
1472
|
...(errors.length === 0 ? { finalizerNodeId, finalizerType: finalizer.finalizerType } : {}),
|
|
1454
1473
|
errors: errors.length ? errors : undefined,
|
|
1455
|
-
|
|
1474
|
+
planAudit,
|
|
1475
|
+
trace: executionTrace,
|
|
1456
1476
|
...finalizeGraphPayload(graphStatus),
|
|
1457
1477
|
};
|
|
1458
1478
|
const debug = buildDebugTrace();
|
|
@@ -1476,8 +1496,8 @@ export function createExellixGraphRuntime(opts) {
|
|
|
1476
1496
|
}
|
|
1477
1497
|
return result;
|
|
1478
1498
|
}
|
|
1479
|
-
if (
|
|
1480
|
-
const err = new Error(`GRAPH_BLOCKED: status=${
|
|
1499
|
+
if (wavePlan.status !== "continue") {
|
|
1500
|
+
const err = new Error(`GRAPH_BLOCKED: status=${wavePlan.status}`);
|
|
1481
1501
|
err.code = "GRAPH_BLOCKED";
|
|
1482
1502
|
const finalOutput = buildFinalOutputFromGraphResponse();
|
|
1483
1503
|
const result = {
|
|
@@ -1490,7 +1510,8 @@ export function createExellixGraphRuntime(opts) {
|
|
|
1490
1510
|
engineSnapshot: engine.snapshot(),
|
|
1491
1511
|
...(finalOutput !== undefined ? { finalOutput } : {}),
|
|
1492
1512
|
errors: [...errors, { error: err }],
|
|
1493
|
-
|
|
1513
|
+
planAudit,
|
|
1514
|
+
trace: executionTrace,
|
|
1494
1515
|
...finalizeGraphPayload("failed"),
|
|
1495
1516
|
};
|
|
1496
1517
|
const debug = buildDebugTrace();
|
|
@@ -1504,7 +1525,7 @@ export function createExellixGraphRuntime(opts) {
|
|
|
1504
1525
|
}
|
|
1505
1526
|
return result;
|
|
1506
1527
|
}
|
|
1507
|
-
let runnableNodes =
|
|
1528
|
+
let runnableNodes = wavePlan.nextNodes ?? [];
|
|
1508
1529
|
// Conditional edge filtering: a node with incoming edges that are *all* conditional must
|
|
1509
1530
|
// have at least one incoming edge whose `when` evaluates true to be runnable this round.
|
|
1510
1531
|
// Roots and nodes with at least one unconditional incoming edge always pass through.
|
|
@@ -1521,7 +1542,7 @@ export function createExellixGraphRuntime(opts) {
|
|
|
1521
1542
|
const incoming = incomingByTo.get(id) ?? [];
|
|
1522
1543
|
if (incoming.length === 0)
|
|
1523
1544
|
return true;
|
|
1524
|
-
const hasUnconditional = incoming.some((e) => e?.
|
|
1545
|
+
const hasUnconditional = incoming.some((e) => !e?.hasDeferredGate);
|
|
1525
1546
|
if (hasUnconditional)
|
|
1526
1547
|
return true;
|
|
1527
1548
|
const planningContext = buildPredicateEvalContextForNode({
|
|
@@ -1531,7 +1552,10 @@ export function createExellixGraphRuntime(opts) {
|
|
|
1531
1552
|
node: n,
|
|
1532
1553
|
runtimeTaskVariables: runtime.taskVariables,
|
|
1533
1554
|
});
|
|
1534
|
-
return incoming.some((
|
|
1555
|
+
return incoming.some((edgeRef) => {
|
|
1556
|
+
const gate = plan.deferredGates.edgeGates[edgeRef.edgeId];
|
|
1557
|
+
return evaluateDeferredEdgeGate(gate, planningContext);
|
|
1558
|
+
});
|
|
1535
1559
|
});
|
|
1536
1560
|
}
|
|
1537
1561
|
const dataFiltersRecord = recordForStructuredDataFilters();
|
|
@@ -1555,7 +1579,7 @@ export function createExellixGraphRuntime(opts) {
|
|
|
1555
1579
|
node: n,
|
|
1556
1580
|
runtimeTaskVariables: runtime.taskVariables,
|
|
1557
1581
|
});
|
|
1558
|
-
const condEv = await
|
|
1582
|
+
const condEv = await evaluateDeferredNodeGate(nodeDeferredGate(plan, String(n.id)), { ...conditionCtxBase, ...nodePredicateCtx }, dataFiltersRecord, runxClient);
|
|
1559
1583
|
if (!condEv.ok) {
|
|
1560
1584
|
skippedByConditions.push({
|
|
1561
1585
|
node: n,
|
|
@@ -1605,27 +1629,32 @@ export function createExellixGraphRuntime(opts) {
|
|
|
1605
1629
|
context: { model: graph, runtime, graphId: resolvedGraphId, jobId, taskId: graphTaskId, node: node },
|
|
1606
1630
|
});
|
|
1607
1631
|
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,
|
|
1632
|
+
const nodeIdStr = String(node.id);
|
|
1633
|
+
appendExecutionEvent(executionTrace, {
|
|
1634
|
+
id: `evt:${graphTaskId}:node.started:${nodeIdStr}`,
|
|
1635
|
+
ts: new Date().toISOString(),
|
|
1636
|
+
level: "info",
|
|
1637
|
+
type: "node.started",
|
|
1638
|
+
nodeId: nodeIdStr,
|
|
1623
1639
|
});
|
|
1640
|
+
const nodePlanEntryResolved = nodePlanForId(plan, nodeIdStr);
|
|
1641
|
+
const finalizerPlanEntryResolved = node.type === "finalizer"
|
|
1642
|
+
? finalizerPlanForId(plan, nodeIdStr)
|
|
1643
|
+
: undefined;
|
|
1644
|
+
const taskNodeForExecute = nodePlanEntryResolved && node.type !== "finalizer"
|
|
1645
|
+
? applyNodePlanInvoke(node, nodePlanEntryResolved)
|
|
1646
|
+
: node;
|
|
1647
|
+
const effectiveModelConfig = finalizerPlanEntryResolved != null
|
|
1648
|
+
? graphAiModelConfigFromFinalizerPlan(finalizerPlanEntryResolved)
|
|
1649
|
+
: nodePlanEntryResolved && node.type !== "finalizer"
|
|
1650
|
+
? graphAiModelConfigFromNodePlan(nodePlanEntryResolved)
|
|
1651
|
+
: undefined;
|
|
1652
|
+
const nodeExecutionPipeline = nodePlanEntryResolved != null ? executionPipelineFromNodePlan(nodePlanEntryResolved) : undefined;
|
|
1624
1653
|
const r = await executeNode({
|
|
1625
1654
|
graphId: resolvedGraphId,
|
|
1626
1655
|
graph,
|
|
1627
1656
|
graphRunTaskId: graphTaskId,
|
|
1628
|
-
node,
|
|
1657
|
+
node: taskNodeForExecute,
|
|
1629
1658
|
job,
|
|
1630
1659
|
jobMemory: currentJobMemory,
|
|
1631
1660
|
taskMemory: nodeTaskMemory,
|
|
@@ -1639,7 +1668,7 @@ export function createExellixGraphRuntime(opts) {
|
|
|
1639
1668
|
runTaskIdentity: merged.runTaskIdentity,
|
|
1640
1669
|
runTaskExecutionMode: merged.runTaskExecutionMode,
|
|
1641
1670
|
runTaskDiagnostics: merged.runTaskDiagnostics,
|
|
1642
|
-
graphExecutionPipeline: merged.executionPipeline,
|
|
1671
|
+
graphExecutionPipeline: nodeExecutionPipeline ?? merged.executionPipeline,
|
|
1643
1672
|
skillKeyResolution: merged.skillKeyResolution,
|
|
1644
1673
|
nodeTimeoutMs: merged.nodeTimeoutMs,
|
|
1645
1674
|
clearSynthesizedContextPerNode: merged.clearSynthesizedContextPerNode,
|
|
@@ -1651,6 +1680,13 @@ export function createExellixGraphRuntime(opts) {
|
|
|
1651
1680
|
outputsByNodeId[r.nodeId] = r.output;
|
|
1652
1681
|
appendStepResponse(node, r.output);
|
|
1653
1682
|
engine.commit({ nodeId: r.nodeId, output: r.output });
|
|
1683
|
+
appendExecutionEvent(executionTrace, {
|
|
1684
|
+
id: `evt:${graphTaskId}:node.completed:${nodeIdStr}`,
|
|
1685
|
+
ts: new Date().toISOString(),
|
|
1686
|
+
level: "info",
|
|
1687
|
+
type: "node.completed",
|
|
1688
|
+
nodeId: nodeIdStr,
|
|
1689
|
+
});
|
|
1654
1690
|
const trl = r.taskRunLog;
|
|
1655
1691
|
if (trl?.length)
|
|
1656
1692
|
taskRunLogBuffer.push(...trl);
|
|
@@ -1722,7 +1758,8 @@ export function createExellixGraphRuntime(opts) {
|
|
|
1722
1758
|
engineSnapshot: engine.snapshot(),
|
|
1723
1759
|
...(finalOutput !== undefined ? { finalOutput } : {}),
|
|
1724
1760
|
errors,
|
|
1725
|
-
|
|
1761
|
+
planAudit,
|
|
1762
|
+
trace: executionTrace,
|
|
1726
1763
|
...finalizeGraphPayload("failed"),
|
|
1727
1764
|
};
|
|
1728
1765
|
const debug = buildDebugTrace();
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Host helpers for execution-matrix style orchestrators (e.g. `@exellix/exellix-runtime`).
|
|
3
3
|
*
|
|
4
4
|
* Graph-engine does not call matrix claim APIs; the host injects `executeGraph` and supplies
|
|
5
|
-
* a `{
|
|
5
|
+
* a `{ plan, runtime }` request with per-graph `GraphEntryContract` + seeded
|
|
6
6
|
* `runtime.executionMemory` objects built here.
|
|
7
7
|
*/
|
|
8
8
|
import type { Graph, GraphEntryContract, GraphEntryExecutionInputSpec, Job } from '../types/refs.js';
|
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
* Host helpers for execution-matrix style orchestrators (e.g. `@exellix/exellix-runtime`).
|
|
3
3
|
*
|
|
4
4
|
* Graph-engine does not call matrix claim APIs; the host injects `executeGraph` and supplies
|
|
5
|
-
* a `{
|
|
5
|
+
* a `{ plan, runtime }` request with per-graph `GraphEntryContract` + seeded
|
|
6
6
|
* `runtime.executionMemory` objects built here.
|
|
7
7
|
*/
|
|
8
|
+
import { compileExellixExecutablePlan } from '../compile/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 '../compile/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
|
+
}
|
package/dist/src/types/refs.d.ts
CHANGED
|
@@ -680,7 +680,7 @@ export interface GraphDocumentMetadata {
|
|
|
680
680
|
graphResponse?: GraphResponseContract;
|
|
681
681
|
/**
|
|
682
682
|
* Graph execution defaults and labels.
|
|
683
|
-
* Planner mode is still selected by `executeGraph({
|
|
683
|
+
* Planner mode is still selected by `executeGraph({ plan, runtime }).runtime.mode` (or plan.schedulingPolicy) and defaults to `forward`;
|
|
684
684
|
* output labels in this block shape `stepsResponses`, not `ExecuteGraphResult.finalOutput`.
|
|
685
685
|
*/
|
|
686
686
|
graphExecution?: GraphExecutionDefaults;
|
|
@@ -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/compile/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.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Graph executor SDK",
|
|
6
6
|
"main": "dist/src/index.js",
|
|
@@ -58,7 +58,14 @@
|
|
|
58
58
|
"@x12i/env": "4.0.1",
|
|
59
59
|
"@x12i/funcx": "4.4.4",
|
|
60
60
|
"@x12i/graphenix": "2.5.0",
|
|
61
|
+
"@x12i/graphenix-authoring-format": "^1.0.2",
|
|
62
|
+
"@x12i/graphenix-executable-contracts": "^1.0.0",
|
|
63
|
+
"@x12i/graphenix-executable-format": "^2.0.1",
|
|
64
|
+
"@x12i/graphenix-execute-envelope": "^1.0.0",
|
|
61
65
|
"@x12i/graphenix-format": "2.0.0",
|
|
66
|
+
"@x12i/graphenix-plan-compiler": "^1.0.1",
|
|
67
|
+
"@x12i/graphenix-plan-format": "^1.0.0",
|
|
68
|
+
"@x12i/graphenix-trace-format": "^1.0.0",
|
|
62
69
|
"@x12i/logxer": "^4.6.0",
|
|
63
70
|
"@x12i/memorix-descriptors": "1.6.0",
|
|
64
71
|
"@x12i/memorix-retrieval": "1.11.2",
|