@exellix/graph-engine 7.4.2 → 7.5.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/README.md +17 -4
- package/dist/src/index.d.ts +4 -2
- package/dist/src/index.js +2 -1
- package/dist/src/inspection/graphModelSelection.d.ts +33 -0
- package/dist/src/inspection/graphModelSelection.js +108 -0
- package/dist/src/inspection/index.d.ts +2 -0
- package/dist/src/inspection/index.js +1 -0
- package/dist/src/inspection/nodeInspection.js +1 -1
- package/dist/src/runtime/ExellixGraphRuntime.d.ts +4 -0
- package/dist/src/runtime/ExellixGraphRuntime.js +5 -2
- package/dist/src/runtime/aiTasksStrategyPhases.d.ts +2 -0
- package/dist/src/runtime/aiTasksStrategyPhases.js +1 -1
- package/dist/src/runtime/finalizers/executeFinalizer.js +7 -11
- package/dist/src/runtime/finalizers/validateFinalizer.js +79 -35
- package/dist/src/runtime/graphAiModelConfig.d.ts +5 -1
- package/dist/src/runtime/graphAiModelConfig.js +17 -6
- package/dist/src/runtime/graphModelStudioSeparation.d.ts +43 -0
- package/dist/src/runtime/graphModelStudioSeparation.js +102 -0
- package/dist/src/runtime/mergeExellixGraphRuntimeInvocation.js +1 -0
- package/dist/src/runtime/validateCanonicalGraphDocument.js +5 -0
- package/dist/src/types/options.d.ts +6 -1
- package/dist/src/types/refs.d.ts +33 -22
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -383,12 +383,25 @@ Implemented deterministic finalizer types:
|
|
|
383
383
|
|
|
384
384
|
Build an object by mapping named inputs (from `executionMemoryPath` or literals) into output keys.
|
|
385
385
|
|
|
386
|
-
#### `aggregate` — `strategy: "report-schema"` (
|
|
386
|
+
#### `aggregate` — `strategy: "report-schema"` (output object assembled from run memory)
|
|
387
387
|
|
|
388
|
-
Builds
|
|
388
|
+
Builds the finalizer output object: **`config.sections`** maps each **output field key** to a memory read, using the same `{ type, path }` idiom as finalizer `inputs` and `response.shape` selectors:
|
|
389
389
|
|
|
390
|
-
|
|
391
|
-
|
|
390
|
+
```jsonc
|
|
391
|
+
"config": {
|
|
392
|
+
"strategy": "report-schema",
|
|
393
|
+
"sections": {
|
|
394
|
+
"q1": { "type": "executionMemoryPath", "path": "answers.q1", "optional": true },
|
|
395
|
+
"summary": { "type": "outputsMemoryPath", "path": "report.summary" }
|
|
396
|
+
},
|
|
397
|
+
"collectEpistemicTags": true
|
|
398
|
+
}
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
- Each section: **`type`** (`executionMemoryPath` | `outputsMemoryPath`), **`path`**, and **`optional`** (when `true`, missing values become `null` instead of throwing).
|
|
402
|
+
- **`collectEpistemicTags`**: when `true`, walks all section values and collects unique epistemic strings among `CONFIRMED`, `INFERRED`, `ASSUMED`, `UNKNOWN` into the **`collectedTags`** output field (array).
|
|
403
|
+
|
|
404
|
+
> **Breaking (model v8):** `sections[].path: string` → `sections[].{ type, path }`; `collect_tags` → `collectEpistemicTags`; output `collected_tags` → `collectedTags`. Section `title` and the literal-merge `meta` block were **removed** — authoring titles and studio hints belong in the studio document, and literal output fields belong in `graph.response.shape`. The validator rejects the old keys with a migration message.
|
|
392
405
|
|
|
393
406
|
Bundled graphs under [`graphs/`](graphs/) (see [graphs/README.md](graphs/README.md)) use this strategy for multi-section reports, sometimes after **`scoped-answer-assembler`** and **`scoped-answer-writer`** persistence (typical store: **`x-scoped-data`** in deployments that use that collection). The graph file shape is defined in [.docs/exellix-graph-engine-format.md](.docs/exellix-graph-engine-format.md).
|
|
394
407
|
|
package/dist/src/index.d.ts
CHANGED
|
@@ -80,6 +80,8 @@ export { buildExellixGraphRuntimeObjects, setRuntimeObjectsLastJobId, summarizeR
|
|
|
80
80
|
export type { ActivixQueryableClient, LogxerQueryableClient, LogxerLogLine, PackageRuntimeObjects, RuntimeObjects, BuildExellixGraphRuntimeObjectsInput, RuntimeObjectsObservabilitySummary, } from './runtime/runtimeObjects.js';
|
|
81
81
|
export type { GetJobLogsInput, GetJobLogsResult, QueryableLogLine } from '@x12i/logxer';
|
|
82
82
|
export { assertCanonicalGraphDocument, getCanonicalGraphDocumentViolations, CANONICAL_GRAPH_TOP_LEVEL_KEYS, } from './runtime/validateCanonicalGraphDocument.js';
|
|
83
|
+
export { GRAPH_ENTRY_STUDIO_ONLY_KEYS, GRAPH_METADATA_STUDIO_ONLY_KEYS, stripGraphModelStudioFields, primaryRuntimeInputFromStudioDocument, getGraphEntryStudioOnlyKeyViolations, getGraphMetadataStudioOnlyKeyViolations, getGraphEntryEmptyInputPathViolations, } from './runtime/graphModelStudioSeparation.js';
|
|
84
|
+
export type { GraphStudioDocument } from './runtime/graphModelStudioSeparation.js';
|
|
83
85
|
export { computeGraphDocumentContentSha256, stableStringifyGraphDocument, } from './runtime/graphDocumentFingerprint.js';
|
|
84
86
|
export { migrateLegacyGraphResponse, migrateLegacyGraphResponseDefinition, } from './runtime/graphResponseMigration.js';
|
|
85
87
|
export type { MigrateGraphResponseResult } from './runtime/graphResponseMigration.js';
|
|
@@ -106,7 +108,7 @@ export type { GraphCatalogValidationIssue } from './integrations/cataloxGraphCat
|
|
|
106
108
|
export { composeEventEmitters } from './runtime/events.js';
|
|
107
109
|
export { createPlaygroundReporter } from './playground/index.js';
|
|
108
110
|
export type { CreatePlaygroundReporterOptions, PlaygroundDebugArtifact, PlaygroundDebugArtifactKind, PlaygroundDebugSnapshot, PlaygroundReporter, PlaygroundStep, } from './playground/PlaygroundReporter.js';
|
|
109
|
-
export { getNodeScopingQuestion, getNodeMemoryShape, getNodeNarrixDiscovery, fetchNodeScopingData, inspectNode, resolveNodeSkillKey, getGraphNodes, getGraphCatalogs, inspectGraph, collectPredicatePaths, executionMemoryPathTail, executionMemoryTailsMatch, getNodeExecutionMemoryWriteTails, getNodeControlDependencies, getNodeSideEffects, inspectFinalizer, inspectNodeContract, inspectGraphContracts, EXELLIX_VIRTUAL_GRAPH_ENTRY_NODE_ID, EXELLIX_VIRTUAL_GRAPH_RESPONSE_NODE_ID, EXELLIX_VIRTUAL_BOUNDARY_NODE_METADATA_KEY, EXELLIX_VIRTUAL_BOUNDARY_EDGE_FLAG_KEY, getVirtualGraphEntryLayer, getVirtualGraphResponseLayer, materializeVirtualBoundaryDiagram, stripMaterializedVirtualBoundaryDiagram, validateCatalogPlanning, isCatalogBinding, isCatalogRequestEntry, } from './inspection/index.js';
|
|
110
|
-
export type { NodeScopingQuestion, NodeScopeSource, NodeScopeTarget, NodeScopingData, NodeMemoryShape, MemoryPath, NodeNarrixDiscovery, NodeInspection, GraphInspection, NodeIOEdge, GraphCatalogs, CatalogBindingSummary, CatalogPlanningValidationIssue, GraphVirtualIO, GraphVirtualIONode, GraphVirtualIOEdge, GraphVirtualIONodeRole, VirtualGraphEntryLayer, VirtualGraphResponseLayer, MaterializeVirtualBoundaryDiagramOptions, MaterializedVirtualBoundaryDiagram, MaterializedVirtualBoundaryDiagramMeta, InspectNodeContractOptions, LocalSkillKey, NodeExecutionType, FactProvenance, ProvenancedSection, NodeInputBindingEntry, ScopedDataDependency, NodeInputContract, ExecutionMemoryWriteSpec, NodeOutputContract, NodeSideEffects, ControlBranchEntry, NodeControlContract, NodeValidationContract, FinalizerContractInspection, NodeContractInspection, GraphContractsInspection, } from './inspection/index.js';
|
|
111
|
+
export { getNodeScopingQuestion, getNodeMemoryShape, getNodeNarrixDiscovery, fetchNodeScopingData, inspectNode, resolveNodeSkillKey, getGraphNodes, getGraphCatalogs, inspectGraph, collectPredicatePaths, executionMemoryPathTail, executionMemoryTailsMatch, getNodeExecutionMemoryWriteTails, getNodeControlDependencies, getNodeSideEffects, inspectFinalizer, inspectNodeContract, inspectGraphContracts, EXELLIX_VIRTUAL_GRAPH_ENTRY_NODE_ID, EXELLIX_VIRTUAL_GRAPH_RESPONSE_NODE_ID, EXELLIX_VIRTUAL_BOUNDARY_NODE_METADATA_KEY, EXELLIX_VIRTUAL_BOUNDARY_EDGE_FLAG_KEY, getVirtualGraphEntryLayer, getVirtualGraphResponseLayer, materializeVirtualBoundaryDiagram, stripMaterializedVirtualBoundaryDiagram, validateCatalogPlanning, isCatalogBinding, isCatalogRequestEntry, inspectGraphModelSelection, } from './inspection/index.js';
|
|
112
|
+
export type { GraphModelSelectionInspection, NodeModelSelection, ModelSelectionSource, NodeScopingQuestion, NodeScopeSource, NodeScopeTarget, NodeScopingData, NodeMemoryShape, MemoryPath, NodeNarrixDiscovery, NodeInspection, GraphInspection, NodeIOEdge, GraphCatalogs, CatalogBindingSummary, CatalogPlanningValidationIssue, GraphVirtualIO, GraphVirtualIONode, GraphVirtualIOEdge, GraphVirtualIONodeRole, VirtualGraphEntryLayer, VirtualGraphResponseLayer, MaterializeVirtualBoundaryDiagramOptions, MaterializedVirtualBoundaryDiagram, MaterializedVirtualBoundaryDiagramMeta, InspectNodeContractOptions, LocalSkillKey, NodeExecutionType, FactProvenance, ProvenancedSection, NodeInputBindingEntry, ScopedDataDependency, NodeInputContract, ExecutionMemoryWriteSpec, NodeOutputContract, NodeSideEffects, ControlBranchEntry, NodeControlContract, NodeValidationContract, FinalizerContractInspection, NodeContractInspection, GraphContractsInspection, } from './inspection/index.js';
|
|
111
113
|
export type { NarrixPreProcessorConfig, WebScopeQuestion, LocalTaskHandler, LocalTaskContext } from './types/narrix.js';
|
|
112
114
|
export type { ScopedDataReaderConfig, ScopedDataReaderOutput, ScopedDataReaderPackOutput, ScopedAnswerAssemblerConfig, ScopedAnswerAssemblerOutput, ScopedAnswerWriterConfig, ScopedAnswerWriterOutput, } from './runtime/localSkills/index.js';
|
package/dist/src/index.js
CHANGED
|
@@ -59,6 +59,7 @@ export { buildTaskNodeRunTaskRequest, validateTaskNodeRunTaskConfig, validateTas
|
|
|
59
59
|
export { validateRunTaskConfig, validateRunTaskInvoke, analyzeExpectedRunTaskInput, checkExpectedInputAgainstRequest, collectSmartInputValidationIssues, buildRunTaskValidationContext, analyzeRunTaskRequest, validateParsedOutput, analyzeSkillRequest, buildSkillRequestAnalysisPacket, formatSkillRequestAnalysisMarkdown, listTokens, analyzeTemplateResolution, renderSmartInput, extractTokenNamesFromStrings, getSkillTokens, getSkillContent, } from '@exellix/ai-tasks';
|
|
60
60
|
export { buildExellixGraphRuntimeObjects, setRuntimeObjectsLastJobId, summarizeRuntimeObjectsForPlayground, EXELLIX_GRAPH_RUNTIME_PACKAGE_NAME, EXELLIX_AI_TASKS_PACKAGE_NAME, } from './runtime/runtimeObjects.js';
|
|
61
61
|
export { assertCanonicalGraphDocument, getCanonicalGraphDocumentViolations, CANONICAL_GRAPH_TOP_LEVEL_KEYS, } from './runtime/validateCanonicalGraphDocument.js';
|
|
62
|
+
export { GRAPH_ENTRY_STUDIO_ONLY_KEYS, GRAPH_METADATA_STUDIO_ONLY_KEYS, stripGraphModelStudioFields, primaryRuntimeInputFromStudioDocument, getGraphEntryStudioOnlyKeyViolations, getGraphMetadataStudioOnlyKeyViolations, getGraphEntryEmptyInputPathViolations, } from './runtime/graphModelStudioSeparation.js';
|
|
62
63
|
export { computeGraphDocumentContentSha256, stableStringifyGraphDocument, } from './runtime/graphDocumentFingerprint.js';
|
|
63
64
|
export { migrateLegacyGraphResponse, migrateLegacyGraphResponseDefinition, } from './runtime/graphResponseMigration.js';
|
|
64
65
|
export { applyAiTaskProfileWebScopingToNarrix, mapAiTaskProfileQuestionsToWebScopeQuestions, } from './runtime/applyAiTaskProfileWebScopingToNarrix.js';
|
|
@@ -81,5 +82,5 @@ export { composeEventEmitters } from './runtime/events.js';
|
|
|
81
82
|
// Playground (full-visibility MD report)
|
|
82
83
|
export { createPlaygroundReporter } from './playground/index.js';
|
|
83
84
|
// Graph Inspection API
|
|
84
|
-
export { getNodeScopingQuestion, getNodeMemoryShape, getNodeNarrixDiscovery, fetchNodeScopingData, inspectNode, resolveNodeSkillKey, getGraphNodes, getGraphCatalogs, inspectGraph, collectPredicatePaths, executionMemoryPathTail, executionMemoryTailsMatch, getNodeExecutionMemoryWriteTails, getNodeControlDependencies, getNodeSideEffects, inspectFinalizer, inspectNodeContract, inspectGraphContracts, EXELLIX_VIRTUAL_GRAPH_ENTRY_NODE_ID, EXELLIX_VIRTUAL_GRAPH_RESPONSE_NODE_ID, EXELLIX_VIRTUAL_BOUNDARY_NODE_METADATA_KEY, EXELLIX_VIRTUAL_BOUNDARY_EDGE_FLAG_KEY, getVirtualGraphEntryLayer, getVirtualGraphResponseLayer, materializeVirtualBoundaryDiagram, stripMaterializedVirtualBoundaryDiagram, validateCatalogPlanning, isCatalogBinding, isCatalogRequestEntry, } from './inspection/index.js';
|
|
85
|
+
export { getNodeScopingQuestion, getNodeMemoryShape, getNodeNarrixDiscovery, fetchNodeScopingData, inspectNode, resolveNodeSkillKey, getGraphNodes, getGraphCatalogs, inspectGraph, collectPredicatePaths, executionMemoryPathTail, executionMemoryTailsMatch, getNodeExecutionMemoryWriteTails, getNodeControlDependencies, getNodeSideEffects, inspectFinalizer, inspectNodeContract, inspectGraphContracts, EXELLIX_VIRTUAL_GRAPH_ENTRY_NODE_ID, EXELLIX_VIRTUAL_GRAPH_RESPONSE_NODE_ID, EXELLIX_VIRTUAL_BOUNDARY_NODE_METADATA_KEY, EXELLIX_VIRTUAL_BOUNDARY_EDGE_FLAG_KEY, getVirtualGraphEntryLayer, getVirtualGraphResponseLayer, materializeVirtualBoundaryDiagram, stripMaterializedVirtualBoundaryDiagram, validateCatalogPlanning, isCatalogBinding, isCatalogRequestEntry, inspectGraphModelSelection, } from './inspection/index.js';
|
|
85
86
|
// Web context rendering — consume execution.webContext → markdown for prompts (Layer 04)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Graph, GraphAiModelConfig } from '../types/refs.js';
|
|
2
|
+
/** Where the effective model-profile triplet for a node came from. */
|
|
3
|
+
export type ModelSelectionSource = 'node' | 'graph' | 'default' | 'none';
|
|
4
|
+
export type NodeModelSelection = {
|
|
5
|
+
nodeId: string;
|
|
6
|
+
kind: 'task' | 'finalizer';
|
|
7
|
+
/** False for deterministic finalizers (aggregate / select / bundle); those call no model. */
|
|
8
|
+
usesModel: boolean;
|
|
9
|
+
/** Effective default-case profile aliases for this node (undefined when `usesModel` is false). */
|
|
10
|
+
profiles?: GraphAiModelConfig;
|
|
11
|
+
source: ModelSelectionSource;
|
|
12
|
+
/** Count of conditional (`when`-gated) cases that may override the default at runtime. */
|
|
13
|
+
conditionalCaseCount: number;
|
|
14
|
+
};
|
|
15
|
+
export type GraphModelSelectionInspection = {
|
|
16
|
+
/** Graph-level default profile aliases (undefined when the graph declares none). */
|
|
17
|
+
graphDefaultProfiles?: GraphAiModelConfig;
|
|
18
|
+
nodes: NodeModelSelection[];
|
|
19
|
+
/** Distinct profile alias names referenced anywhere in the model (for `runtime.aliasConfig` wiring). */
|
|
20
|
+
aliasesUsed: string[];
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Static, runtime-free view of which model-profile aliases each node would use.
|
|
24
|
+
*
|
|
25
|
+
* Answers "where are the models (aliases) that will be used?" without executing:
|
|
26
|
+
* - task nodes resolve `node.taskConfiguration.modelConfig` (override) → graph `modelConfig` → engine default;
|
|
27
|
+
* - `synthesize` finalizers use the graph default; other finalizers call no model.
|
|
28
|
+
*
|
|
29
|
+
* Only the unconditional (`when`-less) default case is reported per tier; `conditionalCaseCount`
|
|
30
|
+
* flags nodes whose effective model may change at runtime via gated cases. Alias → concrete
|
|
31
|
+
* provider id still happens at runtime through `runtime.aliasConfig`.
|
|
32
|
+
*/
|
|
33
|
+
export declare function inspectGraphModelSelection(graph: Graph): GraphModelSelectionInspection;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { isEmptyConditionWhen, isModelConfigSelection } from '../runtime/modelConfigSelection.js';
|
|
2
|
+
import { DEFAULT_GRAPH_AI_MODEL_PROFILE_CONFIG } from '../runtime/graphAiModelConfig.js';
|
|
3
|
+
function asArrayNodes(graph) {
|
|
4
|
+
return Array.isArray(graph.nodes) ? graph.nodes : Object.values(graph.nodes ?? {});
|
|
5
|
+
}
|
|
6
|
+
/** Returns the no-`when` default case profiles from a selection, if present and valid. */
|
|
7
|
+
function defaultCaseProfiles(selection) {
|
|
8
|
+
if (!isModelConfigSelection(selection))
|
|
9
|
+
return undefined;
|
|
10
|
+
const sel = selection;
|
|
11
|
+
for (const c of sel.cases ?? []) {
|
|
12
|
+
if (isEmptyConditionWhen(c.when))
|
|
13
|
+
return c.modelConfig;
|
|
14
|
+
}
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
function conditionalCaseCount(selection) {
|
|
18
|
+
if (!isModelConfigSelection(selection))
|
|
19
|
+
return 0;
|
|
20
|
+
const sel = selection;
|
|
21
|
+
return (sel.cases ?? []).filter((c) => !isEmptyConditionWhen(c.when)).length;
|
|
22
|
+
}
|
|
23
|
+
/** True for finalizer types that invoke a model (`synthesize`); deterministic kinds do not. */
|
|
24
|
+
function finalizerUsesModel(node) {
|
|
25
|
+
return node.finalizerType === 'synthesize';
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Static, runtime-free view of which model-profile aliases each node would use.
|
|
29
|
+
*
|
|
30
|
+
* Answers "where are the models (aliases) that will be used?" without executing:
|
|
31
|
+
* - task nodes resolve `node.taskConfiguration.modelConfig` (override) → graph `modelConfig` → engine default;
|
|
32
|
+
* - `synthesize` finalizers use the graph default; other finalizers call no model.
|
|
33
|
+
*
|
|
34
|
+
* Only the unconditional (`when`-less) default case is reported per tier; `conditionalCaseCount`
|
|
35
|
+
* flags nodes whose effective model may change at runtime via gated cases. Alias → concrete
|
|
36
|
+
* provider id still happens at runtime through `runtime.aliasConfig`.
|
|
37
|
+
*/
|
|
38
|
+
export function inspectGraphModelSelection(graph) {
|
|
39
|
+
const graphDefaultProfiles = defaultCaseProfiles(graph.modelConfig);
|
|
40
|
+
const nodes = [];
|
|
41
|
+
const aliases = new Set();
|
|
42
|
+
const addAliases = (cfg) => {
|
|
43
|
+
if (cfg == null)
|
|
44
|
+
return;
|
|
45
|
+
aliases.add(cfg.preActionModel);
|
|
46
|
+
aliases.add(cfg.skillModel);
|
|
47
|
+
aliases.add(cfg.postActionModel);
|
|
48
|
+
};
|
|
49
|
+
for (const raw of asArrayNodes(graph)) {
|
|
50
|
+
const node = raw;
|
|
51
|
+
const isFinalizer = node.type === 'finalizer';
|
|
52
|
+
if (isFinalizer) {
|
|
53
|
+
const fin = raw;
|
|
54
|
+
if (!finalizerUsesModel(fin)) {
|
|
55
|
+
nodes.push({
|
|
56
|
+
nodeId: String(fin.id),
|
|
57
|
+
kind: 'finalizer',
|
|
58
|
+
usesModel: false,
|
|
59
|
+
source: 'none',
|
|
60
|
+
conditionalCaseCount: 0,
|
|
61
|
+
});
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
const profiles = graphDefaultProfiles ?? DEFAULT_GRAPH_AI_MODEL_PROFILE_CONFIG;
|
|
65
|
+
addAliases(profiles);
|
|
66
|
+
nodes.push({
|
|
67
|
+
nodeId: String(fin.id),
|
|
68
|
+
kind: 'finalizer',
|
|
69
|
+
usesModel: true,
|
|
70
|
+
profiles,
|
|
71
|
+
source: graphDefaultProfiles ? 'graph' : 'default',
|
|
72
|
+
conditionalCaseCount: 0,
|
|
73
|
+
});
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
const taskNode = raw;
|
|
77
|
+
const nodeSelection = taskNode.taskConfiguration?.modelConfig;
|
|
78
|
+
const nodeDefault = defaultCaseProfiles(nodeSelection);
|
|
79
|
+
let profiles;
|
|
80
|
+
let source;
|
|
81
|
+
if (nodeDefault) {
|
|
82
|
+
profiles = nodeDefault;
|
|
83
|
+
source = 'node';
|
|
84
|
+
}
|
|
85
|
+
else if (graphDefaultProfiles) {
|
|
86
|
+
profiles = graphDefaultProfiles;
|
|
87
|
+
source = 'graph';
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
profiles = DEFAULT_GRAPH_AI_MODEL_PROFILE_CONFIG;
|
|
91
|
+
source = 'default';
|
|
92
|
+
}
|
|
93
|
+
addAliases(profiles);
|
|
94
|
+
nodes.push({
|
|
95
|
+
nodeId: String(taskNode.id),
|
|
96
|
+
kind: 'task',
|
|
97
|
+
usesModel: true,
|
|
98
|
+
profiles,
|
|
99
|
+
source,
|
|
100
|
+
conditionalCaseCount: conditionalCaseCount(nodeSelection) + conditionalCaseCount(graph.modelConfig),
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
graphDefaultProfiles,
|
|
105
|
+
nodes,
|
|
106
|
+
aliasesUsed: [...aliases].sort(),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
@@ -15,6 +15,8 @@ export type { CatalogPlanningValidationIssue } from './validateCatalogPlanning.j
|
|
|
15
15
|
export { collectPredicatePaths, executionMemoryPathTail, executionMemoryTailsMatch, getNodeExecutionMemoryWriteTails, getNodeControlDependencies, } from './controlInspection.js';
|
|
16
16
|
export { getNodeSideEffects, inspectFinalizer, inspectNodeContract, inspectGraphContracts, } from './contractInspection.js';
|
|
17
17
|
export type { InspectNodeContractOptions } from './contractInspection.js';
|
|
18
|
+
export { inspectGraphModelSelection } from './graphModelSelection.js';
|
|
19
|
+
export type { GraphModelSelectionInspection, NodeModelSelection, ModelSelectionSource, } from './graphModelSelection.js';
|
|
18
20
|
export type { NodeScopingQuestion, NodeScopeSource, NodeScopeTarget, NodeScopingData, NodeMemoryShape, MemoryPath, NodeNarrixDiscovery, NodeInspection, GraphInspection, NodeIOEdge, GraphCatalogs, CatalogBindingSummary, GraphVirtualIO, GraphVirtualIONode, GraphVirtualIOEdge, GraphVirtualIONodeRole, VirtualGraphEntryLayer, VirtualGraphResponseLayer, MaterializeVirtualBoundaryDiagramOptions, MaterializedVirtualBoundaryDiagram, MaterializedVirtualBoundaryDiagramMeta, } from './types.js';
|
|
19
21
|
export { EXELLIX_VIRTUAL_GRAPH_ENTRY_NODE_ID, EXELLIX_VIRTUAL_GRAPH_RESPONSE_NODE_ID, EXELLIX_VIRTUAL_BOUNDARY_NODE_METADATA_KEY, EXELLIX_VIRTUAL_BOUNDARY_EDGE_FLAG_KEY, } from './types.js';
|
|
20
22
|
export type { GraphDocumentMetadata } from '../types/refs.js';
|
|
@@ -14,4 +14,5 @@ export { getGraphNodes, getGraphCatalogs, inspectGraph, getVirtualGraphEntryLaye
|
|
|
14
14
|
export { validateCatalogPlanning, isCatalogBinding, isCatalogRequestEntry, } from './validateCatalogPlanning.js';
|
|
15
15
|
export { collectPredicatePaths, executionMemoryPathTail, executionMemoryTailsMatch, getNodeExecutionMemoryWriteTails, getNodeControlDependencies, } from './controlInspection.js';
|
|
16
16
|
export { getNodeSideEffects, inspectFinalizer, inspectNodeContract, inspectGraphContracts, } from './contractInspection.js';
|
|
17
|
+
export { inspectGraphModelSelection } from './graphModelSelection.js';
|
|
17
18
|
export { EXELLIX_VIRTUAL_GRAPH_ENTRY_NODE_ID, EXELLIX_VIRTUAL_GRAPH_RESPONSE_NODE_ID, EXELLIX_VIRTUAL_BOUNDARY_NODE_METADATA_KEY, EXELLIX_VIRTUAL_BOUNDARY_EDGE_FLAG_KEY, } from './types.js';
|
|
@@ -52,7 +52,7 @@ function finalizerRuntimeReadSources(node, graph) {
|
|
|
52
52
|
for (const [key, spec] of Object.entries(cfg.sections)) {
|
|
53
53
|
if (!isPlainObject(spec))
|
|
54
54
|
continue;
|
|
55
|
-
pushDedupedRead(reads, spec.path, `config.sections.${key}`, 'executionMemory');
|
|
55
|
+
pushDedupedRead(reads, spec.path, `config.sections.${key}`, spec.type === 'outputsMemoryPath' ? 'outputsMemory' : 'executionMemory');
|
|
56
56
|
}
|
|
57
57
|
return reads;
|
|
58
58
|
}
|
|
@@ -91,6 +91,8 @@ export interface GraphRuntimeObject extends HostExecuteGraphRunOptions {
|
|
|
91
91
|
taskVariables?: Record<string, unknown>;
|
|
92
92
|
/** Runtime default model selection (`cases`); overrides GraphModelObject.modelConfig. */
|
|
93
93
|
modelConfig?: ModelConfigSelection;
|
|
94
|
+
/** Profile aliases for xynthesis when runtime.modelConfig is host-resolved concrete ids. */
|
|
95
|
+
aiProfiles?: GraphAiModelConfig;
|
|
94
96
|
/**
|
|
95
97
|
* @deprecated Unused since 7.1 — profile aliases pass through to downstream resolution.
|
|
96
98
|
*/
|
|
@@ -210,6 +212,8 @@ export declare function createExellixGraphRuntime(opts: ExellixGraphRuntimeOptio
|
|
|
210
212
|
prevNodeId?: string;
|
|
211
213
|
/** Overrides graph-level / runtime default for this node only. */
|
|
212
214
|
modelConfig?: GraphAiModelConfig;
|
|
215
|
+
/** Profile aliases for xynthesis PRE/POST when modelConfig is host-resolved concrete ids. */
|
|
216
|
+
aiProfiles?: GraphAiModelConfig;
|
|
213
217
|
llmCall?: Record<string, unknown>;
|
|
214
218
|
runTaskIdentity?: Record<string, unknown>;
|
|
215
219
|
runTaskExecutionMode?: "default" | "trace";
|
|
@@ -397,7 +397,7 @@ export function createExellixGraphRuntime(opts) {
|
|
|
397
397
|
},
|
|
398
398
|
...(input.modelConfig != null
|
|
399
399
|
? {
|
|
400
|
-
modelConfig: toRunTaskModelConfigForPhase(input.modelConfig, 'pre'),
|
|
400
|
+
modelConfig: toRunTaskModelConfigForPhase(input.modelConfig, 'pre', input.aiProfiles),
|
|
401
401
|
}
|
|
402
402
|
: {}),
|
|
403
403
|
...(effectiveLlmCall != null ? { llmCall: effectiveLlmCall } : {}),
|
|
@@ -610,7 +610,7 @@ export function createExellixGraphRuntime(opts) {
|
|
|
610
610
|
}
|
|
611
611
|
const effectiveModelConfig = input.modelConfig;
|
|
612
612
|
const mainRunTaskModelConfig = effectiveModelConfig != null
|
|
613
|
-
? toRunTaskModelConfigForPhase(effectiveModelConfig, 'main')
|
|
613
|
+
? toRunTaskModelConfigForPhase(effectiveModelConfig, 'main', input.aiProfiles)
|
|
614
614
|
: undefined;
|
|
615
615
|
// DEBUG: Verify execution object before sending
|
|
616
616
|
traceExecutionMemory('executeNode', 'Node execution object before runTask', {
|
|
@@ -722,6 +722,7 @@ export function createExellixGraphRuntime(opts) {
|
|
|
722
722
|
jobContext,
|
|
723
723
|
prevNodeId: input.prevNodeId,
|
|
724
724
|
modelConfig: effectiveModelConfig,
|
|
725
|
+
aiProfiles: input.aiProfiles,
|
|
725
726
|
llmCall: effectiveLlmCall,
|
|
726
727
|
runTaskIdentity: runTaskIdentityBase,
|
|
727
728
|
nodeTaskConfiguration: input.node?.taskConfiguration,
|
|
@@ -1023,6 +1024,7 @@ export function createExellixGraphRuntime(opts) {
|
|
|
1023
1024
|
jobContext,
|
|
1024
1025
|
prevNodeId: input.prevNodeId,
|
|
1025
1026
|
modelConfig: effectiveModelConfig,
|
|
1027
|
+
aiProfiles: input.aiProfiles,
|
|
1026
1028
|
llmCall: effectiveLlmCall,
|
|
1027
1029
|
runTaskIdentity: runTaskIdentityBase,
|
|
1028
1030
|
nodeTaskConfiguration: input.node?.taskConfiguration,
|
|
@@ -1616,6 +1618,7 @@ export function createExellixGraphRuntime(opts) {
|
|
|
1616
1618
|
variables: runtime.variables,
|
|
1617
1619
|
taskVariables: runtime.taskVariables,
|
|
1618
1620
|
modelConfig: effectiveModelConfig,
|
|
1621
|
+
aiProfiles: merged.aiProfiles,
|
|
1619
1622
|
llmCall: merged.llmCall,
|
|
1620
1623
|
runTaskIdentity: merged.runTaskIdentity,
|
|
1621
1624
|
runTaskExecutionMode: merged.runTaskExecutionMode,
|
|
@@ -27,6 +27,8 @@ export type RunEngineAiTasksStrategyPhaseArgs = {
|
|
|
27
27
|
prevNodeId?: string;
|
|
28
28
|
/** Resolved three-phase config; routed to the correct ai-tasks slot for this strategy phase. */
|
|
29
29
|
modelConfig?: GraphAiModelConfig;
|
|
30
|
+
/** Profile aliases for xynthesis slots when modelConfig is host-resolved concrete ids. */
|
|
31
|
+
aiProfiles?: GraphAiModelConfig;
|
|
30
32
|
llmCall?: RunTaskRequest['llmCall'];
|
|
31
33
|
runTaskIdentity?: Record<string, unknown>;
|
|
32
34
|
/** Task node `taskConfiguration` (identity envelope merge). */
|
|
@@ -55,7 +55,7 @@ export async function runEngineAiTasksStrategyPhase(args) {
|
|
|
55
55
|
executionPipeline: [{ phase: 'main', type: 'direct' }],
|
|
56
56
|
executionStrategies: [],
|
|
57
57
|
modelConfig: args.modelConfig != null
|
|
58
|
-
? toRunTaskModelConfigForPhase(args.modelConfig, args.phase)
|
|
58
|
+
? toRunTaskModelConfigForPhase(args.modelConfig, args.phase, args.aiProfiles)
|
|
59
59
|
: undefined,
|
|
60
60
|
llmCall: args.llmCall ?? undefined,
|
|
61
61
|
identity,
|
|
@@ -284,11 +284,12 @@ function collectEpistemicTags(value, found) {
|
|
|
284
284
|
}
|
|
285
285
|
}
|
|
286
286
|
function executeReportSchema(args) {
|
|
287
|
-
const { cfg, executionMemory } = args;
|
|
287
|
+
const { cfg, executionMemory, outputsMemory } = args;
|
|
288
288
|
const finalizerNodeId = String(args.finalizer.id);
|
|
289
289
|
const out = {};
|
|
290
290
|
for (const [key, spec] of Object.entries(cfg.sections ?? {})) {
|
|
291
|
-
|
|
291
|
+
const root = spec.type === 'outputsMemoryPath' ? outputsMemory : executionMemory;
|
|
292
|
+
let v = getByDotPath(root, spec.path);
|
|
292
293
|
if (isNodeFailureMarker(v)) {
|
|
293
294
|
if (spec.optional === true) {
|
|
294
295
|
out[key] = null;
|
|
@@ -296,7 +297,7 @@ function executeReportSchema(args) {
|
|
|
296
297
|
}
|
|
297
298
|
throw createFinalizerError({
|
|
298
299
|
code: 'GRAPH_FINALIZER_NODE_FAILED',
|
|
299
|
-
message: `report-schema: section "${key}" points at a failed graph node (
|
|
300
|
+
message: `report-schema: section "${key}" points at a failed graph node (${spec.type}="${spec.path}")`,
|
|
300
301
|
finalizerNodeId,
|
|
301
302
|
path: `config.sections.${key}`,
|
|
302
303
|
details: { spec, marker: v },
|
|
@@ -305,7 +306,7 @@ function executeReportSchema(args) {
|
|
|
305
306
|
if ((v === undefined || v === null) && spec.optional !== true) {
|
|
306
307
|
throw createFinalizerError({
|
|
307
308
|
code: 'GRAPH_FINALIZER_INPUT_MISSING',
|
|
308
|
-
message: `report-schema: required section "${key}" not found at
|
|
309
|
+
message: `report-schema: required section "${key}" not found at ${spec.type} "${spec.path}"`,
|
|
309
310
|
finalizerNodeId,
|
|
310
311
|
path: `config.sections.${key}`,
|
|
311
312
|
details: { spec },
|
|
@@ -313,16 +314,11 @@ function executeReportSchema(args) {
|
|
|
313
314
|
}
|
|
314
315
|
out[key] = v ?? null;
|
|
315
316
|
}
|
|
316
|
-
if (cfg.
|
|
317
|
+
if (cfg.collectEpistemicTags) {
|
|
317
318
|
const tags = new Set();
|
|
318
319
|
for (const v of Object.values(out))
|
|
319
320
|
collectEpistemicTags(v, tags);
|
|
320
|
-
out.
|
|
321
|
-
}
|
|
322
|
-
if (cfg.meta) {
|
|
323
|
-
for (const [k, v] of Object.entries(cfg.meta)) {
|
|
324
|
-
out[k] = v;
|
|
325
|
-
}
|
|
321
|
+
out.collectedTags = Array.from(tags);
|
|
326
322
|
}
|
|
327
323
|
return out;
|
|
328
324
|
}
|
|
@@ -55,7 +55,8 @@ export function collectRequiredFinalizerExecutionMemoryReads(finalizer, graph) {
|
|
|
55
55
|
for (const [key, spec] of Object.entries(cfg.sections)) {
|
|
56
56
|
if (!isPlainObject(spec))
|
|
57
57
|
continue;
|
|
58
|
-
|
|
58
|
+
const memory = spec.type === 'outputsMemoryPath' ? 'outputs' : 'execution';
|
|
59
|
+
pushRequiredRead(reads, memory, spec.path, `config.sections.${key}`, spec.optional);
|
|
59
60
|
}
|
|
60
61
|
return reads;
|
|
61
62
|
}
|
|
@@ -180,6 +181,46 @@ export function assertFinalizerRequiredReadsResolvable(args) {
|
|
|
180
181
|
}
|
|
181
182
|
}
|
|
182
183
|
}
|
|
184
|
+
/** Validates a `{ type: executionMemoryPath | outputsMemoryPath, path, optional? }` memory read. */
|
|
185
|
+
function validateMemoryRef(ref, path, finalizerNodeId, label) {
|
|
186
|
+
if (!isPlainObject(ref)) {
|
|
187
|
+
throw createFinalizerError({
|
|
188
|
+
code: 'GRAPH_FINALIZER_INVALID',
|
|
189
|
+
message: `${label} at ${path} must be an object { type, path }`,
|
|
190
|
+
finalizerNodeId,
|
|
191
|
+
path,
|
|
192
|
+
details: { ref },
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
const type = ref.type;
|
|
196
|
+
if (type !== 'executionMemoryPath' && type !== 'outputsMemoryPath') {
|
|
197
|
+
throw createFinalizerError({
|
|
198
|
+
code: 'GRAPH_FINALIZER_INVALID',
|
|
199
|
+
message: `${label} at ${path} must declare type "executionMemoryPath" or "outputsMemoryPath" (got ${String(type)})`,
|
|
200
|
+
finalizerNodeId,
|
|
201
|
+
path: `${path}.type`,
|
|
202
|
+
details: { ref },
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
const p = ref.path;
|
|
206
|
+
if (typeof p !== 'string' || p.length === 0) {
|
|
207
|
+
throw createFinalizerError({
|
|
208
|
+
code: 'GRAPH_FINALIZER_INVALID',
|
|
209
|
+
message: `${label} at ${path} requires a non-empty path`,
|
|
210
|
+
finalizerNodeId,
|
|
211
|
+
path: `${path}.path`,
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
const optional = ref.optional;
|
|
215
|
+
if (optional !== undefined && typeof optional !== 'boolean') {
|
|
216
|
+
throw createFinalizerError({
|
|
217
|
+
code: 'GRAPH_FINALIZER_INVALID',
|
|
218
|
+
message: `${label} at ${path} optional must be boolean when provided`,
|
|
219
|
+
finalizerNodeId,
|
|
220
|
+
path: `${path}.optional`,
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
}
|
|
183
224
|
function validateBinding(name, b, finalizerNodeId) {
|
|
184
225
|
if (!isPlainObject(b)) {
|
|
185
226
|
throw createFinalizerError({
|
|
@@ -245,54 +286,57 @@ function validateAggregateConfig(cfg, finalizerNodeId) {
|
|
|
245
286
|
return;
|
|
246
287
|
}
|
|
247
288
|
if (strategy === 'report-schema') {
|
|
248
|
-
const
|
|
249
|
-
|
|
289
|
+
const REPORT_SCHEMA_ALLOWED_KEYS = new Set(['strategy', 'sections', 'collectEpistemicTags']);
|
|
290
|
+
const REPORT_SCHEMA_REMOVED_KEYS = {
|
|
291
|
+
collect_tags: 'collectEpistemicTags (boolean)',
|
|
292
|
+
meta: 'removed — put literal output fields in graph.response.shape, and authoring titles in the studio document',
|
|
293
|
+
title: 'removed from sections — authoring titles belong in the studio document',
|
|
294
|
+
schemaVersion: 'removed — not part of the executable model',
|
|
295
|
+
skeletonFromConcept: 'removed — studio authoring hint, not part of the executable model',
|
|
296
|
+
};
|
|
297
|
+
for (const key of Object.keys(cfg)) {
|
|
298
|
+
if (REPORT_SCHEMA_ALLOWED_KEYS.has(key))
|
|
299
|
+
continue;
|
|
300
|
+
const migration = REPORT_SCHEMA_REMOVED_KEYS[key];
|
|
250
301
|
throw createFinalizerError({
|
|
251
302
|
code: 'GRAPH_FINALIZER_INVALID',
|
|
252
|
-
message:
|
|
303
|
+
message: migration
|
|
304
|
+
? `report-schema finalizer config.${key} is no longer supported. Use: ${migration}.`
|
|
305
|
+
: `report-schema finalizer has unknown config key "${key}". Allowed: ${[...REPORT_SCHEMA_ALLOWED_KEYS].join(', ')}.`,
|
|
253
306
|
finalizerNodeId,
|
|
254
|
-
path:
|
|
307
|
+
path: `config.${key}`,
|
|
255
308
|
details: { config: cfg },
|
|
256
309
|
});
|
|
257
310
|
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
if (!isPlainObject(spec) || typeof spec.path !== 'string' || spec.path.length === 0) {
|
|
261
|
-
throw createFinalizerError({
|
|
262
|
-
code: 'GRAPH_FINALIZER_INVALID',
|
|
263
|
-
message: `report-schema section "${k}" must have a non-empty path`,
|
|
264
|
-
finalizerNodeId,
|
|
265
|
-
path: `config.sections.${k}.path`,
|
|
266
|
-
details: { spec },
|
|
267
|
-
});
|
|
268
|
-
}
|
|
269
|
-
const optional = spec.optional;
|
|
270
|
-
if (optional !== undefined && typeof optional !== 'boolean') {
|
|
271
|
-
throw createFinalizerError({
|
|
272
|
-
code: 'GRAPH_FINALIZER_INVALID',
|
|
273
|
-
message: `report-schema section "${k}" optional must be boolean when provided`,
|
|
274
|
-
finalizerNodeId,
|
|
275
|
-
path: `config.sections.${k}.optional`,
|
|
276
|
-
});
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
const collectTags = cfg.collect_tags;
|
|
281
|
-
if (collectTags !== undefined && typeof collectTags !== 'boolean') {
|
|
311
|
+
const sections = cfg.sections;
|
|
312
|
+
if (!isPlainObject(sections) || Object.keys(sections).length === 0) {
|
|
282
313
|
throw createFinalizerError({
|
|
283
314
|
code: 'GRAPH_FINALIZER_INVALID',
|
|
284
|
-
message: `report-schema aggregate finalizer config.
|
|
315
|
+
message: `report-schema aggregate finalizer requires a non-empty config.sections object (output key → { type, path }).`,
|
|
285
316
|
finalizerNodeId,
|
|
286
|
-
path: 'config.
|
|
317
|
+
path: 'config.sections',
|
|
318
|
+
details: { config: cfg },
|
|
287
319
|
});
|
|
288
320
|
}
|
|
289
|
-
const
|
|
290
|
-
|
|
321
|
+
for (const [k, spec] of Object.entries(sections)) {
|
|
322
|
+
validateMemoryRef(spec, `config.sections.${k}`, finalizerNodeId, 'report-schema section');
|
|
323
|
+
const title = spec.title;
|
|
324
|
+
if (title !== undefined) {
|
|
325
|
+
throw createFinalizerError({
|
|
326
|
+
code: 'GRAPH_FINALIZER_INVALID',
|
|
327
|
+
message: `report-schema section "${k}" no longer accepts "title"; move authoring titles to the studio document.`,
|
|
328
|
+
finalizerNodeId,
|
|
329
|
+
path: `config.sections.${k}.title`,
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
const collectTags = cfg.collectEpistemicTags;
|
|
334
|
+
if (collectTags !== undefined && typeof collectTags !== 'boolean') {
|
|
291
335
|
throw createFinalizerError({
|
|
292
336
|
code: 'GRAPH_FINALIZER_INVALID',
|
|
293
|
-
message: `report-schema aggregate finalizer config.
|
|
337
|
+
message: `report-schema aggregate finalizer config.collectEpistemicTags must be boolean when provided`,
|
|
294
338
|
finalizerNodeId,
|
|
295
|
-
path: 'config.
|
|
339
|
+
path: 'config.collectEpistemicTags',
|
|
296
340
|
});
|
|
297
341
|
}
|
|
298
342
|
return;
|
|
@@ -36,5 +36,9 @@ export type EngineModelPhase = 'pre' | 'main' | 'post';
|
|
|
36
36
|
/**
|
|
37
37
|
* Maps three-phase graph config to ai-tasks slot pair for a single runTask phase.
|
|
38
38
|
* Values are forwarded as-is (profile aliases or concrete ids).
|
|
39
|
+
*
|
|
40
|
+
* When {@link aiProfiles} is set (studio/playground host pattern), xynthesis slots use profile
|
|
41
|
+
* aliases so PRE synthesis inside ai-tasks resolves via ai-profiles even when {@link config}
|
|
42
|
+
* carries host-resolved concrete provider ids on `runtime.modelConfig`.
|
|
39
43
|
*/
|
|
40
|
-
export declare function toRunTaskModelConfigForPhase(config: GraphAiModelConfig, phase: EngineModelPhase): RunTaskModelConfigWire;
|
|
44
|
+
export declare function toRunTaskModelConfigForPhase(config: GraphAiModelConfig, phase: EngineModelPhase, aiProfiles?: GraphAiModelConfig): RunTaskModelConfigWire;
|
|
@@ -79,26 +79,37 @@ export async function resolveGraphAiModelConfig(modelConfig, context) {
|
|
|
79
79
|
postActionModel: normalizeModelSlot(modelConfig.postActionModel, context),
|
|
80
80
|
};
|
|
81
81
|
}
|
|
82
|
+
function xynthesisSlotForWire(config, phase, aiProfiles) {
|
|
83
|
+
if (aiProfiles != null) {
|
|
84
|
+
return phase === 'post' ? aiProfiles.postActionModel : aiProfiles.preActionModel;
|
|
85
|
+
}
|
|
86
|
+
return phase === 'post' ? config.postActionModel : config.preActionModel;
|
|
87
|
+
}
|
|
82
88
|
/**
|
|
83
89
|
* Maps three-phase graph config to ai-tasks slot pair for a single runTask phase.
|
|
84
90
|
* Values are forwarded as-is (profile aliases or concrete ids).
|
|
91
|
+
*
|
|
92
|
+
* When {@link aiProfiles} is set (studio/playground host pattern), xynthesis slots use profile
|
|
93
|
+
* aliases so PRE synthesis inside ai-tasks resolves via ai-profiles even when {@link config}
|
|
94
|
+
* carries host-resolved concrete provider ids on `runtime.modelConfig`.
|
|
85
95
|
*/
|
|
86
|
-
export function toRunTaskModelConfigForPhase(config, phase) {
|
|
96
|
+
export function toRunTaskModelConfigForPhase(config, phase, aiProfiles) {
|
|
97
|
+
const xynthesisModel = xynthesisSlotForWire(config, phase, aiProfiles);
|
|
87
98
|
switch (phase) {
|
|
88
99
|
case 'pre':
|
|
89
100
|
return {
|
|
90
|
-
xynthesisModel
|
|
91
|
-
skillModel:
|
|
101
|
+
xynthesisModel,
|
|
102
|
+
skillModel: xynthesisModel,
|
|
92
103
|
};
|
|
93
104
|
case 'main':
|
|
94
105
|
return {
|
|
95
|
-
xynthesisModel
|
|
106
|
+
xynthesisModel,
|
|
96
107
|
skillModel: config.skillModel,
|
|
97
108
|
};
|
|
98
109
|
case 'post':
|
|
99
110
|
return {
|
|
100
|
-
xynthesisModel
|
|
101
|
-
skillModel:
|
|
111
|
+
xynthesisModel,
|
|
112
|
+
skillModel: xynthesisModel,
|
|
102
113
|
};
|
|
103
114
|
}
|
|
104
115
|
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { Graph } from '../types/refs.js';
|
|
2
|
+
/** Example payloads and playground request samples — studio DB only, not graph model. */
|
|
3
|
+
export declare const GRAPH_ENTRY_STUDIO_ONLY_KEYS: readonly ["exampleInput", "exampleInputs", "requestExample"];
|
|
4
|
+
/** Planning / UI metadata — studio DB only, not versioned executable graph model. */
|
|
5
|
+
export declare const GRAPH_METADATA_STUDIO_ONLY_KEYS: readonly ["requestId", "graphConcept", "graphsStudio"];
|
|
6
|
+
export type GraphEntryStudioOnlyKey = (typeof GRAPH_ENTRY_STUDIO_ONLY_KEYS)[number];
|
|
7
|
+
export type GraphMetadataStudioOnlyKey = (typeof GRAPH_METADATA_STUDIO_ONLY_KEYS)[number];
|
|
8
|
+
/**
|
|
9
|
+
* Studio-side companion document keyed by graph id (graphs-studio database).
|
|
10
|
+
* Not part of {@link GraphModelObject} / canonical graph JSON.
|
|
11
|
+
*/
|
|
12
|
+
export type GraphStudioDocument = {
|
|
13
|
+
graphId: string;
|
|
14
|
+
graphConcept?: Record<string, unknown>;
|
|
15
|
+
graphsStudio?: Record<string, unknown>;
|
|
16
|
+
/** Flat {@link GraphRuntimeObject.input} examples for playground and tests. */
|
|
17
|
+
exampleInputs?: Array<{
|
|
18
|
+
title?: string;
|
|
19
|
+
runtimeInput: Record<string, unknown>;
|
|
20
|
+
}>;
|
|
21
|
+
[key: string]: unknown;
|
|
22
|
+
};
|
|
23
|
+
export declare function getGraphEntryStudioOnlyKeyViolations(graphEntry: unknown): GraphEntryStudioOnlyKey[];
|
|
24
|
+
export declare function getGraphMetadataStudioOnlyKeyViolations(metadata: unknown): GraphMetadataStudioOnlyKey[];
|
|
25
|
+
/** Returns dot-paths under graphEntry.inputs with empty `path` for value kinds. */
|
|
26
|
+
export declare function getGraphEntryEmptyInputPathViolations(graphEntry: unknown): string[];
|
|
27
|
+
/**
|
|
28
|
+
* Deep-clones a graph model and removes studio-only metadata and graphEntry example fields.
|
|
29
|
+
* Use at publish time when upstream serializers still attach playground artifacts.
|
|
30
|
+
*/
|
|
31
|
+
export declare function stripGraphModelStudioFields(graph: Graph): Graph;
|
|
32
|
+
export declare function assertCanonicalGraphEntryContract(graphEntry: unknown, context: {
|
|
33
|
+
jobId?: string;
|
|
34
|
+
graphId?: string;
|
|
35
|
+
graphLabel?: string;
|
|
36
|
+
}): void;
|
|
37
|
+
export declare function assertCanonicalGraphDocumentMetadata(metadata: unknown, context: {
|
|
38
|
+
jobId?: string;
|
|
39
|
+
graphId?: string;
|
|
40
|
+
graphLabel?: string;
|
|
41
|
+
}): void;
|
|
42
|
+
/** Primary flat runtime input from a studio companion document. */
|
|
43
|
+
export declare function primaryRuntimeInputFromStudioDocument(studio: GraphStudioDocument | undefined): Record<string, unknown> | undefined;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { ExellixGraphError } from '../errors/ExellixGraphError.js';
|
|
2
|
+
import { ExellixGraphErrorCode } from '../errors/exellixGraphErrorCodes.js';
|
|
3
|
+
/** Example payloads and playground request samples — studio DB only, not graph model. */
|
|
4
|
+
export const GRAPH_ENTRY_STUDIO_ONLY_KEYS = [
|
|
5
|
+
'exampleInput',
|
|
6
|
+
'exampleInputs',
|
|
7
|
+
'requestExample',
|
|
8
|
+
];
|
|
9
|
+
/** Planning / UI metadata — studio DB only, not versioned executable graph model. */
|
|
10
|
+
export const GRAPH_METADATA_STUDIO_ONLY_KEYS = [
|
|
11
|
+
'requestId',
|
|
12
|
+
'graphConcept',
|
|
13
|
+
'graphsStudio',
|
|
14
|
+
];
|
|
15
|
+
function isPlainObject(v) {
|
|
16
|
+
return v != null && typeof v === 'object' && !Array.isArray(v);
|
|
17
|
+
}
|
|
18
|
+
function isValueInputSpec(spec) {
|
|
19
|
+
return spec.kind !== 'execution';
|
|
20
|
+
}
|
|
21
|
+
export function getGraphEntryStudioOnlyKeyViolations(graphEntry) {
|
|
22
|
+
if (!isPlainObject(graphEntry))
|
|
23
|
+
return [];
|
|
24
|
+
return GRAPH_ENTRY_STUDIO_ONLY_KEYS.filter((key) => Object.prototype.hasOwnProperty.call(graphEntry, key));
|
|
25
|
+
}
|
|
26
|
+
export function getGraphMetadataStudioOnlyKeyViolations(metadata) {
|
|
27
|
+
if (!isPlainObject(metadata))
|
|
28
|
+
return [];
|
|
29
|
+
return GRAPH_METADATA_STUDIO_ONLY_KEYS.filter((key) => Object.prototype.hasOwnProperty.call(metadata, key));
|
|
30
|
+
}
|
|
31
|
+
/** Returns dot-paths under graphEntry.inputs with empty `path` for value kinds. */
|
|
32
|
+
export function getGraphEntryEmptyInputPathViolations(graphEntry) {
|
|
33
|
+
if (!isPlainObject(graphEntry))
|
|
34
|
+
return [];
|
|
35
|
+
const inputs = graphEntry.inputs;
|
|
36
|
+
if (!Array.isArray(inputs))
|
|
37
|
+
return [];
|
|
38
|
+
const bad = [];
|
|
39
|
+
for (let i = 0; i < inputs.length; i++) {
|
|
40
|
+
const spec = inputs[i];
|
|
41
|
+
if (!isPlainObject(spec))
|
|
42
|
+
continue;
|
|
43
|
+
if (spec.kind === 'execution')
|
|
44
|
+
continue;
|
|
45
|
+
const path = typeof spec.path === 'string' ? spec.path.trim() : '';
|
|
46
|
+
if (!path)
|
|
47
|
+
bad.push(`metadata.graphEntry.inputs[${i}].path`);
|
|
48
|
+
}
|
|
49
|
+
return bad;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Deep-clones a graph model and removes studio-only metadata and graphEntry example fields.
|
|
53
|
+
* Use at publish time when upstream serializers still attach playground artifacts.
|
|
54
|
+
*/
|
|
55
|
+
export function stripGraphModelStudioFields(graph) {
|
|
56
|
+
const clone = structuredClone(graph);
|
|
57
|
+
if (isPlainObject(clone.metadata)) {
|
|
58
|
+
const meta = { ...clone.metadata };
|
|
59
|
+
for (const key of GRAPH_METADATA_STUDIO_ONLY_KEYS) {
|
|
60
|
+
delete meta[key];
|
|
61
|
+
}
|
|
62
|
+
if (isPlainObject(meta.graphEntry)) {
|
|
63
|
+
const ge = { ...meta.graphEntry };
|
|
64
|
+
for (const key of GRAPH_ENTRY_STUDIO_ONLY_KEYS) {
|
|
65
|
+
delete ge[key];
|
|
66
|
+
}
|
|
67
|
+
meta.graphEntry = ge;
|
|
68
|
+
}
|
|
69
|
+
clone.metadata = meta;
|
|
70
|
+
}
|
|
71
|
+
return clone;
|
|
72
|
+
}
|
|
73
|
+
export function assertCanonicalGraphEntryContract(graphEntry, context) {
|
|
74
|
+
const label = context.graphLabel ?? `Graph "${String(context.graphId ?? '?')}"`;
|
|
75
|
+
const studioKeys = getGraphEntryStudioOnlyKeyViolations(graphEntry);
|
|
76
|
+
if (studioKeys.length > 0) {
|
|
77
|
+
throw new ExellixGraphError(ExellixGraphErrorCode.NON_CANONICAL_GRAPH_DOCUMENT, `${label}: metadata.graphEntry must not include studio-only example field(s): ${studioKeys.join(', ')}. Store example payloads in the graphs-studio database (GraphStudioDocument.exampleInputs), not in the executable graph model.`, { ...context, graphEntryStudioOnlyKeys: studioKeys });
|
|
78
|
+
}
|
|
79
|
+
const emptyPaths = getGraphEntryEmptyInputPathViolations(graphEntry);
|
|
80
|
+
if (emptyPaths.length > 0) {
|
|
81
|
+
throw new ExellixGraphError(ExellixGraphErrorCode.NON_CANONICAL_GRAPH_DOCUMENT, `${label}: metadata.graphEntry.inputs must declare a non-empty dot-path under merged execution (e.g. "input" or "input.subnetId"); empty path at ${emptyPaths.join(', ')}.`, { ...context, graphEntryEmptyInputPaths: emptyPaths });
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
export function assertCanonicalGraphDocumentMetadata(metadata, context) {
|
|
85
|
+
const label = context.graphLabel ?? `Graph "${String(context.graphId ?? '?')}"`;
|
|
86
|
+
const studioKeys = getGraphMetadataStudioOnlyKeyViolations(metadata);
|
|
87
|
+
if (studioKeys.length > 0) {
|
|
88
|
+
throw new ExellixGraphError(ExellixGraphErrorCode.NON_CANONICAL_GRAPH_DOCUMENT, `${label}: metadata must not include studio-only field(s): ${studioKeys.join(', ')}. Move planning and UI metadata to the graphs-studio database (GraphStudioDocument), not the executable graph model.`, { ...context, metadataStudioOnlyKeys: studioKeys });
|
|
89
|
+
}
|
|
90
|
+
if (isPlainObject(metadata) && metadata.graphEntry != null) {
|
|
91
|
+
assertCanonicalGraphEntryContract(metadata.graphEntry, context);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/** Primary flat runtime input from a studio companion document. */
|
|
95
|
+
export function primaryRuntimeInputFromStudioDocument(studio) {
|
|
96
|
+
const first = studio?.exampleInputs?.[0];
|
|
97
|
+
const runtimeInput = first?.runtimeInput;
|
|
98
|
+
if (runtimeInput != null && isPlainObject(runtimeInput) && !Array.isArray(runtimeInput)) {
|
|
99
|
+
return runtimeInput;
|
|
100
|
+
}
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
@@ -16,6 +16,7 @@ export function mergeExellixGraphRuntimeInvocation(input, opts) {
|
|
|
16
16
|
playgroundMeta: input.playgroundMeta ?? opts.playgroundMeta,
|
|
17
17
|
clearSynthesizedContextPerNode: input.clearSynthesizedContextPerNode ?? opts.clearSynthesizedContextPerNode,
|
|
18
18
|
modelConfig: input.modelConfig ?? opts.modelConfig,
|
|
19
|
+
aiProfiles: input.aiProfiles ?? opts.aiProfiles,
|
|
19
20
|
aliasConfig: input.aliasConfig ?? opts.aliasConfig,
|
|
20
21
|
nodes: input.nodes ?? opts.nodes,
|
|
21
22
|
llmCall: input.llmCall ?? opts.llmCall,
|
|
@@ -5,6 +5,7 @@ import { EXELLIX_VIRTUAL_BOUNDARY_NODE_METADATA_KEY } from '../inspection/types.
|
|
|
5
5
|
import { getStructuredDataFilterPathViolations } from './dataFiltersEvaluation.js';
|
|
6
6
|
import { conditionWhenSignature, countDefaultModelConfigCases, isEmptyConditionWhen, isGraphAiModelConfig, isModelConfigSelection, } from './modelConfigSelection.js';
|
|
7
7
|
import { assertGraphAiProfileNameString } from './graphAiModelConfig.js';
|
|
8
|
+
import { assertCanonicalGraphDocumentMetadata } from './graphModelStudioSeparation.js';
|
|
8
9
|
/** Top-level keys permitted on a canonical exellix-graph executable graph JSON document. */
|
|
9
10
|
export const CANONICAL_GRAPH_TOP_LEVEL_KEYS = [
|
|
10
11
|
'id',
|
|
@@ -548,6 +549,10 @@ export function assertCanonicalGraphDocument(graph, context) {
|
|
|
548
549
|
throw new ExellixGraphError(ExellixGraphErrorCode.NON_CANONICAL_GRAPH_DOCUMENT, `Graph "${String(resolvedGraphId ?? '?')}": metadata.${key} is not part of the static metadata block. Move this field to \`${target}\`.`, { jobId: context?.jobId, graphId: resolvedGraphId, metadataKey: key });
|
|
549
550
|
}
|
|
550
551
|
}
|
|
552
|
+
assertCanonicalGraphDocumentMetadata(metadata, {
|
|
553
|
+
jobId: context?.jobId,
|
|
554
|
+
graphId: resolvedGraphId,
|
|
555
|
+
});
|
|
551
556
|
const graphEntry = metadata.graphEntry;
|
|
552
557
|
if (graphEntry != null && typeof graphEntry === 'object' && !Array.isArray(graphEntry)) {
|
|
553
558
|
const ge = graphEntry;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Activix } from '@x12i/activix';
|
|
2
2
|
import type { StackLoggingOptions } from '@x12i/logxer';
|
|
3
3
|
import type { LlmCallConfig, RunTaskRequest as AiTasksRunTaskRequest, RunTaskResponse as AiTasksRunTaskResponse } from '@exellix/ai-tasks';
|
|
4
|
-
import type { GraphModelAliasConfig, ModelConfigSelection, TaskNodeRuntimeObject } from './refs.js';
|
|
4
|
+
import type { GraphAiModelConfig, GraphModelAliasConfig, ModelConfigSelection, TaskNodeRuntimeObject } from './refs.js';
|
|
5
5
|
import type { RuntimeObjects } from '../runtime/runtimeObjects.js';
|
|
6
6
|
/**
|
|
7
7
|
* NOTE: `@exellix/ai-tasks` 5.5+ removed the exported `RunTaskDiagnostics` type.
|
|
@@ -63,6 +63,11 @@ export interface HostExecuteGraphRunOptions {
|
|
|
63
63
|
playgroundMeta?: Record<string, unknown>;
|
|
64
64
|
clearSynthesizedContextPerNode?: boolean;
|
|
65
65
|
modelConfig?: ModelConfigSelection;
|
|
66
|
+
/**
|
|
67
|
+
* Profile aliases for xynthesis PRE/POST when {@link modelConfig} carries host-resolved concrete ids.
|
|
68
|
+
* MAIN `skillModel` still comes from resolved `modelConfig`; xynthesis slots prefer these aliases.
|
|
69
|
+
*/
|
|
70
|
+
aiProfiles?: GraphAiModelConfig;
|
|
66
71
|
aliasConfig?: GraphModelAliasConfig;
|
|
67
72
|
nodes?: Record<string, TaskNodeRuntimeObject>;
|
|
68
73
|
llmCall?: LlmCallConfig;
|
package/dist/src/types/refs.d.ts
CHANGED
|
@@ -207,14 +207,28 @@ export type QuestionDrivenItemSpec = {
|
|
|
207
207
|
*/
|
|
208
208
|
optional?: boolean;
|
|
209
209
|
};
|
|
210
|
-
|
|
211
|
-
|
|
210
|
+
/**
|
|
211
|
+
* A single "read one value from run memory" reference. Same idiom as
|
|
212
|
+
* {@link FinalizerInputBinding} (minus `literal`) and {@link GraphResponseSelector}
|
|
213
|
+
* memory selectors — finalizer configs reuse it so every memory read in the model
|
|
214
|
+
* looks identical: `{ type, path }`.
|
|
215
|
+
*/
|
|
216
|
+
export type FinalizerMemoryRef = {
|
|
217
|
+
type: 'executionMemoryPath';
|
|
218
|
+
path: string;
|
|
219
|
+
optional?: boolean;
|
|
220
|
+
} | {
|
|
221
|
+
type: 'outputsMemoryPath';
|
|
212
222
|
path: string;
|
|
213
|
-
/** Human-readable title for this section. */
|
|
214
|
-
title?: string;
|
|
215
223
|
optional?: boolean;
|
|
216
224
|
};
|
|
225
|
+
/**
|
|
226
|
+
* @deprecated Renamed and unified with {@link FinalizerMemoryRef}. Sections are now
|
|
227
|
+
* `Record<outputKey, FinalizerMemoryRef>` instead of `{ path, title }`.
|
|
228
|
+
*/
|
|
229
|
+
export type ReportSchemaSectionSpec = FinalizerMemoryRef;
|
|
217
230
|
export type AggregateFinalizerConfig = {
|
|
231
|
+
/** Build the output object field-by-field from named finalizer `inputs`. */
|
|
218
232
|
strategy: 'object-map';
|
|
219
233
|
map: Record<string, AggregateExpr>;
|
|
220
234
|
omitUndefined?: boolean;
|
|
@@ -228,25 +242,23 @@ export type AggregateFinalizerConfig = {
|
|
|
228
242
|
contractVersion?: string;
|
|
229
243
|
/** Map output keys → node specs. */
|
|
230
244
|
items: Record<string, QuestionDrivenItemSpec>;
|
|
231
|
-
/** Optional extra
|
|
232
|
-
meta?: Record<string,
|
|
233
|
-
type: 'executionMemoryPath' | 'outputsMemoryPath';
|
|
234
|
-
path: string;
|
|
235
|
-
optional?: boolean;
|
|
236
|
-
}>;
|
|
245
|
+
/** Optional extra top-level fields, each read from run memory. */
|
|
246
|
+
meta?: Record<string, FinalizerMemoryRef>;
|
|
237
247
|
} | {
|
|
238
248
|
/**
|
|
239
|
-
*
|
|
240
|
-
*
|
|
241
|
-
*
|
|
249
|
+
* Build the graph output object by reading one run-memory value per output key.
|
|
250
|
+
* This **is** the finalizer's output: `sections` maps each output field to the
|
|
251
|
+
* memory path it is read from. (Authoring titles belong in the studio document,
|
|
252
|
+
* not here.)
|
|
242
253
|
*/
|
|
243
254
|
strategy: 'report-schema';
|
|
244
|
-
/**
|
|
245
|
-
sections: Record<string,
|
|
246
|
-
/**
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
255
|
+
/** Output field key → where its value is read from in run memory. */
|
|
256
|
+
sections: Record<string, FinalizerMemoryRef>;
|
|
257
|
+
/**
|
|
258
|
+
* When true, scan all section values for CONFIRMED/INFERRED/ASSUMED/UNKNOWN
|
|
259
|
+
* and emit them under the `collectedTags` output field.
|
|
260
|
+
*/
|
|
261
|
+
collectEpistemicTags?: boolean;
|
|
250
262
|
};
|
|
251
263
|
export type BundleFinalizerConfig = {
|
|
252
264
|
strategy: 'bundle';
|
|
@@ -275,8 +287,8 @@ export interface FinalizerNode {
|
|
|
275
287
|
type: 'finalizer';
|
|
276
288
|
finalizerType: 'aggregate' | 'compose' | 'select' | 'bundle' | 'synthesize';
|
|
277
289
|
inputs: Record<string, FinalizerInputBinding>;
|
|
278
|
-
/**
|
|
279
|
-
config: AggregateFinalizerConfig | BundleFinalizerConfig | SelectFinalizerConfig | SynthesizeFinalizerConfig
|
|
290
|
+
/** Strict per-`finalizerType` config. The runtime validator rejects unknown keys and legacy field names. */
|
|
291
|
+
config: AggregateFinalizerConfig | BundleFinalizerConfig | SelectFinalizerConfig | SynthesizeFinalizerConfig;
|
|
280
292
|
/** Writes selected finalizer result fields into graph-owned outputsMemory for final response assembly. */
|
|
281
293
|
outputMapping?: TaskNodeResultMapping;
|
|
282
294
|
outputSchema?: OutputSchema;
|
|
@@ -612,7 +624,6 @@ export type GraphEntryContract = {
|
|
|
612
624
|
* Optional JSON Schema (e.g. draft 2020-12) for the merged `execution` object. Validators may use it; core runtime does not.
|
|
613
625
|
*/
|
|
614
626
|
executionSchema?: Record<string, unknown>;
|
|
615
|
-
[key: string]: unknown;
|
|
616
627
|
};
|
|
617
628
|
/**
|
|
618
629
|
* @deprecated Use root-level {@link GraphModelObject.response}. The final response shape is executable
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exellix/graph-engine",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.5.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Graph executor SDK",
|
|
6
6
|
"main": "dist/src/index.js",
|
|
@@ -28,6 +28,8 @@
|
|
|
28
28
|
"test": "npm run build && npm run check:no-legacy && tsx --test --test-force-exit tests/model-alias-contract.test.ts tests/reports-fixtures-pre-synthesis.test.ts tests/passthrough-parity.test.ts tests/step-retry-llm-call.test.ts tests/run-log-diagnostics.test.ts",
|
|
29
29
|
"test:full": "npm run build && npm run check:no-legacy && tsx --test --test-force-exit tests/graph-engine.test.ts tests/passthrough-parity.test.ts tests/task-node-run-task-preflight.test.ts tests/reports-fixtures-pre-synthesis.test.ts tests/model-alias-contract.test.ts tests/step-retry-llm-call.test.ts",
|
|
30
30
|
"test:live": "npm run run:pre-synthesis",
|
|
31
|
+
"test:subnets-graph-fixture": "npm run build && node --test tests/subnets-graph.fixture.test.mjs",
|
|
32
|
+
"test:subnets-graph-live": "npm run build && node --env-file=.env --test tests/subnets-graph.live.test.mjs",
|
|
31
33
|
"run:pre-synthesis": "npm run build && node --env-file=.env scripts/run-pre-synthesis-graph.mjs",
|
|
32
34
|
"check:no-legacy": "node scripts/check-no-legacy.mjs",
|
|
33
35
|
"lint": "eslint src testkit --ext .ts",
|