@noetic-tools/core 5.7.0 → 5.9.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/dist/adapters/openrouter.d.ts +1 -0
- package/dist/adapters/openrouter.js +1 -0
- package/dist/builders/channel-builder.d.ts +18 -0
- package/dist/builders/control-flow-builders.d.ts +17 -30
- package/dist/builders/decide-builders.d.ts +5 -5
- package/dist/builders/decide-builders.js +4 -0
- package/dist/builders/every.d.ts +2 -2
- package/dist/builders/every.js +2 -0
- package/dist/builders/loop-builder.d.ts +1 -1
- package/dist/builders/loop-builder.js +1 -1
- package/dist/builders/provide-builder.d.ts +3 -2
- package/dist/builders/provide-builder.js +3 -0
- package/dist/builders/spawn-builder.d.ts +2 -3
- package/dist/builders/spawn-builder.js +2 -1
- package/dist/builders/step-builders.d.ts +8 -9
- package/dist/builders/step-builders.js +0 -1
- package/dist/builders/tool-builder.d.ts +2 -0
- package/dist/builders/tool-builder.js +2 -0
- package/dist/builders/workflow-hydrator.js +21 -14
- package/dist/builders/workflow-replay-key.d.ts +15 -0
- package/dist/builders/workflow-replay-key.js +46 -0
- package/dist/harness/agent-harness.d.ts +43 -11
- package/dist/harness/agent-harness.js +121 -40
- package/dist/harness/deps/interpreter.d.ts +1 -0
- package/dist/harness/deps/interpreter.js +1 -0
- package/dist/harness/deps/runtime.d.ts +3 -2
- package/dist/harness/deps/runtime.js +2 -2
- package/dist/harness/model-call.d.ts +3 -0
- package/dist/harness/model-call.js +19 -5
- package/dist/index.d.ts +7 -5
- package/dist/index.js +3 -3
- package/dist/interpreter/action-deps.d.ts +1 -1
- package/dist/interpreter/action-deps.js +1 -1
- package/dist/interpreter/child-span.d.ts +17 -0
- package/dist/interpreter/child-span.js +51 -0
- package/dist/interpreter/collect-tools.js +19 -54
- package/dist/interpreter/execute-action.js +30 -17
- package/dist/interpreter/execute-control.js +67 -9
- package/dist/interpreter/execute-decide.js +9 -27
- package/dist/interpreter/execute.d.ts +10 -0
- package/dist/interpreter/execute.js +69 -46
- package/dist/interpreter/model-error.d.ts +9 -0
- package/dist/interpreter/model-error.js +32 -0
- package/dist/interpreter/step-walk.d.ts +11 -0
- package/dist/interpreter/step-walk.js +41 -0
- package/dist/observability/genai-attributes.d.ts +52 -29
- package/dist/observability/genai-attributes.js +27 -1
- package/dist/runtime/channel-store.d.ts +73 -1
- package/dist/runtime/channel-store.js +179 -63
- package/dist/runtime/context-impl.d.ts +36 -32
- package/dist/runtime/context-impl.js +80 -45
- package/dist/runtime/durable/harness-checkpoints.d.ts +33 -16
- package/dist/runtime/durable/harness-checkpoints.js +74 -52
- package/dist/runtime/durable/step-ledger.d.ts +15 -5
- package/dist/runtime/durable/step-ledger.js +23 -6
- package/dist/schemas/workflow.d.ts +1 -1
- package/dist/types/checkpoint.d.ts +31 -0
- package/dist/types/checkpoint.js +29 -0
- package/dist/until/combinators.d.ts +10 -0
- package/dist/until/combinators.js +37 -13
- package/dist/until/predicates.js +4 -2
- package/package.json +3 -3
- package/schema/noetic-workflow.schema.json +432 -0
package/dist/index.js
CHANGED
|
@@ -70,7 +70,7 @@ export { createMessage, estimateTokens, recordUsage } from '@noetic-tools/types'
|
|
|
70
70
|
//#endregion
|
|
71
71
|
//#region Observability
|
|
72
72
|
/** @public */
|
|
73
|
-
export { GenAI, NoeticAttr, ToolAttr } from './observability/genai-attributes.js';
|
|
73
|
+
export { GenAI, NoeticAttr, NoeticEvent, ToolAttr } from './observability/genai-attributes.js';
|
|
74
74
|
/** @public */
|
|
75
75
|
export { InMemoryExporter, NoopExporter } from './observability/trace-exporter.js';
|
|
76
76
|
/** @public */
|
|
@@ -90,13 +90,13 @@ export { defaultItemSchemaRegistry, ItemSchema, ItemSchemaRegistry } from '@noet
|
|
|
90
90
|
/** @public */
|
|
91
91
|
export { MergeStrategySchema, UntilPredicateSchema, validateWorkflow, WorkflowDocumentSchema, WorkflowNodeSchema, walkWorkflow, workflowDepth, } from './schemas/workflow.js';
|
|
92
92
|
/** @public */
|
|
93
|
-
export { isServerToolSpec, isStandardJsonSchema, isZodSchema, standardIssuesToZodError, validateSchema, } from '@noetic-tools/types';
|
|
93
|
+
export { isServerToolSpec, isStandardJsonSchema, isZodSchema, ServiceTier, standardIssuesToZodError, validateSchema, } from '@noetic-tools/types';
|
|
94
94
|
/** @public */
|
|
95
95
|
export { getToolCwd, setToolCwd, snapshotCwdState } from './runtime/cwd-helpers.js';
|
|
96
96
|
/** @public */
|
|
97
97
|
export { DetachedStatus } from '@noetic-tools/types';
|
|
98
98
|
/** @public */
|
|
99
|
-
export { CheckpointSchemaVersion, CheckpointSnapshotSchema, CwdSnapshotSchema, FrontierFrameSchema, ItemLogSnapshotSchema, PendingAskUserSnapshotSchema, } from './types/checkpoint.js';
|
|
99
|
+
export { ChannelSnapshotSchema, CheckpointSchemaVersion, CheckpointSnapshotSchema, CwdSnapshotSchema, FrontierFrameSchema, ItemLogSnapshotSchema, PendingAskUserSnapshotSchema, } from './types/checkpoint.js';
|
|
100
100
|
/** @public */
|
|
101
101
|
export { Slot } from '@noetic-tools/context';
|
|
102
102
|
/** @public */
|
|
@@ -3,6 +3,6 @@ export { allocateBudgets, assembleView, churnFor, churnRate, commitLayerUsage, c
|
|
|
3
3
|
export type { ItemSchemaRegistry } from '@noetic-tools/types';
|
|
4
4
|
export { defaultItemSchemaRegistry } from '@noetic-tools/types';
|
|
5
5
|
export { emitFrameworkEvent, getBroadcaster, shouldEmit } from '../runtime/broadcaster-utils.js';
|
|
6
|
-
export { ContextImpl } from '../runtime/context-impl.js';
|
|
6
|
+
export { ContextImpl, laneBase } from '../runtime/context-impl.js';
|
|
7
7
|
export { snapshotCwdState } from '../runtime/cwd-helpers.js';
|
|
8
8
|
export { buildToolExecutionContext } from '../runtime/tool-context.js';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { allocateBudgets, assembleView, churnFor, churnRate, commitLayerUsage, computeLayerUsage, contextToExecCtx, createContextCacheStore, DECISION_PROJECTION, DEFAULT_PROJECTION, lineageKey, noteCacheOutcome, openEpoch, pin, pinKey, reanchorReason, resolveCacheConfig, resolveLayerTools, returnLayers, spawnLayers, } from '@noetic-tools/context';
|
|
2
2
|
export { defaultItemSchemaRegistry } from '@noetic-tools/types';
|
|
3
3
|
export { emitFrameworkEvent, getBroadcaster, shouldEmit } from '../runtime/broadcaster-utils.js';
|
|
4
|
-
export { ContextImpl } from '../runtime/context-impl.js';
|
|
4
|
+
export { ContextImpl, laneBase } from '../runtime/context-impl.js';
|
|
5
5
|
export { snapshotCwdState } from '../runtime/cwd-helpers.js';
|
|
6
6
|
export { buildToolExecutionContext } from '../runtime/tool-context.js';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spans for child contexts (`spawn` children and `inParallel` paths). A child
|
|
3
|
+
* context carries its own span, so events its steps add to `ctx.span` (e.g.
|
|
4
|
+
* `loop.verdict`) and the model/tool spans they open nest under the child
|
|
5
|
+
* rather than being lost to a no-op span.
|
|
6
|
+
*/
|
|
7
|
+
import type { Context, Span } from '@noetic-tools/types';
|
|
8
|
+
/** Opens a span named `name` under `parent.span`, stamped with the owning step id. */
|
|
9
|
+
export declare function openChildSpan(parent: Context, name: string, stepId: string): Span;
|
|
10
|
+
/**
|
|
11
|
+
* Runs `run` inside `span`, then ends the span and exports it, whether `run`
|
|
12
|
+
* returns or throws. A failure stamps `noetic.error.kind`; any failure but
|
|
13
|
+
* cancellation also stamps `error: true`.
|
|
14
|
+
*/
|
|
15
|
+
export declare function traceChild<T>(parent: Context, span: Span, run: () => Promise<T>): Promise<T>;
|
|
16
|
+
/** Ends a span whose child never ran: it was cancelled before it started. */
|
|
17
|
+
export declare function closeUnstartedSpan(parent: Context, span: Span): Promise<void>;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spans for child contexts (`spawn` children and `inParallel` paths). A child
|
|
3
|
+
* context carries its own span, so events its steps add to `ctx.span` (e.g.
|
|
4
|
+
* `loop.verdict`) and the model/tool spans they open nest under the child
|
|
5
|
+
* rather than being lost to a no-op span.
|
|
6
|
+
*/
|
|
7
|
+
import { isNoeticError } from '@noetic-tools/types';
|
|
8
|
+
import { NoeticAttr } from '../observability/genai-attributes.js';
|
|
9
|
+
/** Opens a span named `name` under `parent.span`, stamped with the owning step id. */
|
|
10
|
+
export function openChildSpan(parent, name, stepId) {
|
|
11
|
+
const span = parent.harness.createSpan(name, parent.span);
|
|
12
|
+
span.setAttribute(NoeticAttr.NODE_ID, stepId);
|
|
13
|
+
return span;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Runs `run` inside `span`, then ends the span and exports it, whether `run`
|
|
17
|
+
* returns or throws. A failure stamps `noetic.error.kind`; any failure but
|
|
18
|
+
* cancellation also stamps `error: true`.
|
|
19
|
+
*/
|
|
20
|
+
export async function traceChild(parent, span, run) {
|
|
21
|
+
try {
|
|
22
|
+
return await run();
|
|
23
|
+
}
|
|
24
|
+
catch (e) {
|
|
25
|
+
markFailed(span, e);
|
|
26
|
+
throw e;
|
|
27
|
+
}
|
|
28
|
+
finally {
|
|
29
|
+
await closeChildSpan(parent, span);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/** Ends a span whose child never ran: it was cancelled before it started. */
|
|
33
|
+
export async function closeUnstartedSpan(parent, span) {
|
|
34
|
+
span.setAttribute(NoeticAttr.ERROR_KIND, 'cancelled');
|
|
35
|
+
await closeChildSpan(parent, span);
|
|
36
|
+
}
|
|
37
|
+
function markFailed(span, error) {
|
|
38
|
+
const kind = isNoeticError(error) ? error.noeticError.kind : undefined;
|
|
39
|
+
if (kind !== undefined) {
|
|
40
|
+
span.setAttribute(NoeticAttr.ERROR_KIND, kind);
|
|
41
|
+
}
|
|
42
|
+
if (kind !== 'cancelled') {
|
|
43
|
+
span.setAttribute('error', true);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
async function closeChildSpan(parent, span) {
|
|
47
|
+
span.end();
|
|
48
|
+
await parent.harness.traceExporter.export([
|
|
49
|
+
span,
|
|
50
|
+
]);
|
|
51
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { frameworkCast, isServerToolSpec } from '@noetic-tools/types';
|
|
2
|
+
import { stepChildren } from './step-walk.js';
|
|
2
3
|
/**
|
|
3
4
|
* Recursively walks a step tree and collects all Tool instances
|
|
4
5
|
* declared on LLM steps. Used to build the unified tool set
|
|
@@ -26,59 +27,23 @@ export function deduplicateTools(tools) {
|
|
|
26
27
|
];
|
|
27
28
|
}
|
|
28
29
|
function walkStep(step, out) {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
case 'decide':
|
|
48
|
-
// An ACP agent runs its own tools inside the external agent process; it
|
|
49
|
-
// contributes nothing to the host unified tool set.
|
|
50
|
-
case 'acp-agent':
|
|
51
|
-
return;
|
|
52
|
-
case 'conditional':
|
|
53
|
-
if (step._optimizable) {
|
|
54
|
-
for (const child of step._optimizable) {
|
|
55
|
-
walkStep(child, out);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
return;
|
|
59
|
-
case 'inParallel':
|
|
60
|
-
if (step._optimizable) {
|
|
61
|
-
for (const child of step._optimizable) {
|
|
62
|
-
walkStep(child, out);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
return;
|
|
66
|
-
case 'withContext':
|
|
67
|
-
walkStep(step.child, out);
|
|
68
|
-
return;
|
|
69
|
-
case 'spawn':
|
|
70
|
-
walkStep(step.child, out);
|
|
71
|
-
return;
|
|
72
|
-
case 'loop':
|
|
73
|
-
for (const child of step.steps) {
|
|
74
|
-
walkStep(child, out);
|
|
75
|
-
}
|
|
76
|
-
return;
|
|
77
|
-
case 'schedule':
|
|
78
|
-
walkStep(step.step, out);
|
|
79
|
-
return;
|
|
80
|
-
default:
|
|
81
|
-
step;
|
|
82
|
-
return;
|
|
30
|
+
if (step.kind !== 'callModel') {
|
|
31
|
+
for (const child of stepChildren(step)) {
|
|
32
|
+
walkStep(child, out);
|
|
33
|
+
}
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
// Skip function-form tools: they're resolved per execution from `ctx`
|
|
37
|
+
// and cannot contribute to the pre-computed unified set. Consumers that
|
|
38
|
+
// need those tools in the pool should register them via
|
|
39
|
+
// `AgentHarness.tools` instead. Inline server-tool specs (web search/
|
|
40
|
+
// fetch) are not client `Tool`s — they never enter the unified pool.
|
|
41
|
+
if (!Array.isArray(step.tools)) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
for (const entry of step.tools) {
|
|
45
|
+
if (!isServerToolSpec(entry)) {
|
|
46
|
+
out.push(entry);
|
|
47
|
+
}
|
|
83
48
|
}
|
|
84
49
|
}
|
|
@@ -5,10 +5,12 @@ import { createMessage, estimateTokens, extractAssistantText, frameworkCast, isN
|
|
|
5
5
|
import { emitToolUi } from '../runtime/tool-ui.js';
|
|
6
6
|
import { allocateBudgets, assembleView, buildToolExecutionContext, ContextImpl, commitLayerUsage, computeLayerUsage, contextToExecCtx, createContextCacheStore, DEFAULT_PROJECTION, defaultItemSchemaRegistry, emitFrameworkEvent, getBroadcaster, lineageKey, noteCacheOutcome, resolveCacheConfig, resolveLayerTools, returnLayers, shouldEmit, snapshotCwdState, spawnLayers, } from './action-deps.js';
|
|
7
7
|
import { isServerToolSpec, SteeringAction } from './action-types.js';
|
|
8
|
+
import { openChildSpan, traceChild } from './child-span.js';
|
|
8
9
|
import { cloneWithGuard } from './clone-guard.js';
|
|
9
10
|
import { collectAllTools, deduplicateTools } from './collect-tools.js';
|
|
10
11
|
import { prepareBandedView, stampAnchoringAttributes } from './context-assembly.js';
|
|
11
12
|
import { trackUsage } from './message-helpers.js';
|
|
13
|
+
import { wrapModelError } from './model-error.js';
|
|
12
14
|
import { parseStructuredOutput } from './structured-output.js';
|
|
13
15
|
import { getContextChannelStore, isContextImpl, isFunctionCall, isMutableContext, } from './typeguards.js';
|
|
14
16
|
//#region run
|
|
@@ -433,6 +435,7 @@ function recordTurnOutcome(params) {
|
|
|
433
435
|
toolCalls: toolCalls.length > 0 ? toolCalls : undefined,
|
|
434
436
|
usage: response.usage,
|
|
435
437
|
cost: response.cost,
|
|
438
|
+
serviceTier: response.serviceTier,
|
|
436
439
|
responseItems: response.items,
|
|
437
440
|
};
|
|
438
441
|
if (isMutableContext(ctx)) {
|
|
@@ -546,7 +549,9 @@ export async function executeCallModel(step, input, ctx, layers) {
|
|
|
546
549
|
serverToolSpecs,
|
|
547
550
|
assembledItems,
|
|
548
551
|
});
|
|
549
|
-
const response = await baseCtx.harness.callModel(request)
|
|
552
|
+
const response = await baseCtx.harness.callModel(request).catch((e) => {
|
|
553
|
+
throw wrapModelError(e, step.id, baseCtx);
|
|
554
|
+
});
|
|
550
555
|
// Track tokens/cost for EVERY model call, including responses steering
|
|
551
556
|
// subsequently rejects (Deny throw / Guide retry below) — the spend is
|
|
552
557
|
// real, and until.maxCost / HarnessResponse.usage must see it (spec 07:
|
|
@@ -705,9 +710,11 @@ export async function executeSpawn(step, input, ctx, executeStep, opts) {
|
|
|
705
710
|
// `id: childId` keeps the ContextImpl's id in sync with the executionId
|
|
706
711
|
// used by spawnLayers/returnLayers in the layer-state store, so writes via
|
|
707
712
|
// `ctx.context[layerId]` and spawn-boundary hooks read from the same key.
|
|
713
|
+
const span = openChildSpan(baseCtx, 'spawn', step.id);
|
|
708
714
|
const childCtx = new ContextImpl({
|
|
709
715
|
harness: baseCtx.harness,
|
|
710
716
|
parent: baseCtx,
|
|
717
|
+
span,
|
|
711
718
|
items: childItems,
|
|
712
719
|
state: cloneWithGuard(baseCtx.state, `Spawn '${step.id}'`),
|
|
713
720
|
threadId: baseCtx.threadId,
|
|
@@ -723,23 +730,29 @@ export async function executeSpawn(step, input, ctx, executeStep, opts) {
|
|
|
723
730
|
ledger: baseCtx instanceof ContextImpl ? baseCtx.ledger : undefined,
|
|
724
731
|
});
|
|
725
732
|
try {
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
733
|
+
return await traceChild(baseCtx, span, async () => {
|
|
734
|
+
// Execute the child step. Its token/cost usage needs no merge here: every
|
|
735
|
+
// charge recorded on `childCtx` is also charged up its `usageParent`
|
|
736
|
+
// chain as it is spent, so a child that throws still bills the parent.
|
|
737
|
+
const childOutput = await executeStep(step.child, input, frameworkCast(childCtx));
|
|
738
|
+
// Pipeline result through layer onReturn hooks
|
|
739
|
+
if (!hasLayers || !parentExecutionCtx) {
|
|
740
|
+
return childOutput;
|
|
741
|
+
}
|
|
742
|
+
return returnLayers({
|
|
743
|
+
layers,
|
|
744
|
+
stepId: step.id,
|
|
745
|
+
parentCtx: parentExecutionCtx,
|
|
746
|
+
// `childExecutionCtx` predates the spawn span; trace onto that span.
|
|
747
|
+
childCtx: {
|
|
748
|
+
...childExecutionCtx,
|
|
749
|
+
trace: span,
|
|
750
|
+
},
|
|
751
|
+
childLog: childCtx.itemLog,
|
|
752
|
+
result: childOutput,
|
|
753
|
+
store: layerStore,
|
|
754
|
+
});
|
|
741
755
|
});
|
|
742
|
-
return pipelinedResult;
|
|
743
756
|
}
|
|
744
757
|
finally {
|
|
745
758
|
// Clean up child execution state to prevent memory leaks — runs on success and error
|
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
* Control-flow step handlers: conditional, inParallel, loop, schedule.
|
|
3
3
|
*/
|
|
4
4
|
import { createMessage, frameworkCast, isNoeticError, NoeticErrorImpl } from '@noetic-tools/types';
|
|
5
|
+
import { NoeticAttr, NoeticEvent } from '../observability/genai-attributes.js';
|
|
5
6
|
import { ContextImpl } from '../runtime/context-impl.js';
|
|
6
7
|
import { snapshotCwdState } from '../runtime/cwd-helpers.js';
|
|
7
8
|
import { contextToExecCtx, returnLayers, spawnLayers } from './action-deps.js';
|
|
9
|
+
import { closeUnstartedSpan, openChildSpan, traceChild } from './child-span.js';
|
|
8
10
|
import { cloneWithGuard } from './clone-guard.js';
|
|
9
11
|
import { getContextChannelStore, isContextImpl, isMutableContext } from './typeguards.js';
|
|
10
12
|
//#region conditional
|
|
@@ -19,17 +21,20 @@ export async function executeConditional(step, input, ctx, executeStep) {
|
|
|
19
21
|
}
|
|
20
22
|
//#endregion
|
|
21
23
|
//#region inParallel
|
|
22
|
-
function createChildContexts(ctx,
|
|
24
|
+
function createChildContexts(ctx, paths, stepId) {
|
|
23
25
|
const threadId = isContextImpl(ctx) ? ctx.threadId : crypto.randomUUID();
|
|
24
26
|
const resourceId = isContextImpl(ctx) ? ctx.resourceId : undefined;
|
|
25
27
|
const channelStore = getContextChannelStore(ctx);
|
|
26
28
|
const parentPath = isContextImpl(ctx) ? ctx.currentPath() : '';
|
|
27
29
|
const parentLedger = isContextImpl(ctx) ? ctx.ledger : undefined;
|
|
28
|
-
return
|
|
29
|
-
length: count,
|
|
30
|
-
}, (_unused, index) => new ContextImpl({
|
|
30
|
+
return paths.map((path, index) => new ContextImpl({
|
|
31
31
|
harness: ctx.harness,
|
|
32
32
|
parent: ctx,
|
|
33
|
+
span: openPathSpan(ctx, {
|
|
34
|
+
stepId,
|
|
35
|
+
pathId: path.id,
|
|
36
|
+
index,
|
|
37
|
+
}),
|
|
33
38
|
/* Ledger paths must stay unique across sibling paths: a child ContextImpl
|
|
34
39
|
* starts with an empty frontier, so without a prefix every path would
|
|
35
40
|
* restart at the root and collide with its siblings. */
|
|
@@ -52,7 +57,36 @@ function createChildContexts(ctx, count, stepId) {
|
|
|
52
57
|
unifiedTools: ctx.unifiedTools,
|
|
53
58
|
}));
|
|
54
59
|
}
|
|
55
|
-
function
|
|
60
|
+
function openPathSpan(ctx, { stepId, pathId, index, }) {
|
|
61
|
+
const span = openChildSpan(ctx, 'inParallel.path', pathId);
|
|
62
|
+
span.setAttribute(NoeticAttr.PARALLEL_ID, stepId);
|
|
63
|
+
span.setAttribute(NoeticAttr.PARALLEL_INDEX, index);
|
|
64
|
+
return span;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Ends each path's span when the path settles. A path that never starts (queued
|
|
68
|
+
* behind a fail-fast abort, or unstarted when a race settles) has no run to
|
|
69
|
+
* wrap, so `closeUnstarted` ends its span as cancelled.
|
|
70
|
+
*/
|
|
71
|
+
function tracePaths(ctx, executeStep, childContexts) {
|
|
72
|
+
const unstarted = new Set(childContexts);
|
|
73
|
+
return {
|
|
74
|
+
executeStep: (step, input, childCtx) => {
|
|
75
|
+
const child = frameworkCast(childCtx);
|
|
76
|
+
if (!unstarted.delete(child)) {
|
|
77
|
+
return executeStep(step, input, childCtx);
|
|
78
|
+
}
|
|
79
|
+
return traceChild(ctx, child.span, () => executeStep(step, input, childCtx));
|
|
80
|
+
},
|
|
81
|
+
async closeUnstarted() {
|
|
82
|
+
for (const child of unstarted) {
|
|
83
|
+
await closeUnstartedSpan(ctx, child.span);
|
|
84
|
+
}
|
|
85
|
+
unstarted.clear();
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
function createForkLayerBridge({ stepId, ctx, childContexts, opts, }) {
|
|
56
90
|
const layers = ctx.layers;
|
|
57
91
|
const layerStore = opts?.layerStore;
|
|
58
92
|
if (!layers || layers.length === 0 || !layerStore) {
|
|
@@ -79,6 +113,7 @@ function createForkLayerBridge({ ctx, childContexts, opts, }) {
|
|
|
79
113
|
settle(index, result) {
|
|
80
114
|
return serialize(() => returnLayers({
|
|
81
115
|
layers: layers ?? [],
|
|
116
|
+
stepId,
|
|
82
117
|
parentCtx: parentExecCtx,
|
|
83
118
|
childCtx: childExecCtxs[index],
|
|
84
119
|
childLog: childContexts[index].itemLog,
|
|
@@ -136,13 +171,15 @@ export async function executeInParallel(step, input, ctx, rawExecuteStep, opts)
|
|
|
136
171
|
failed: [],
|
|
137
172
|
});
|
|
138
173
|
}
|
|
139
|
-
const childContexts = createChildContexts(baseCtx, paths
|
|
174
|
+
const childContexts = createChildContexts(baseCtx, paths, step.id);
|
|
140
175
|
const concurrency = step.concurrency ?? paths.length;
|
|
141
|
-
const
|
|
176
|
+
const traced = tracePaths(baseCtx, withForkLayers(rawExecuteStep, childContexts, createForkLayerBridge({
|
|
177
|
+
stepId: step.id,
|
|
142
178
|
ctx: baseCtx,
|
|
143
179
|
childContexts,
|
|
144
180
|
opts,
|
|
145
|
-
}));
|
|
181
|
+
})), childContexts);
|
|
182
|
+
const executeStep = traced.executeStep;
|
|
146
183
|
try {
|
|
147
184
|
switch (step.mode) {
|
|
148
185
|
case 'all':
|
|
@@ -168,6 +205,7 @@ export async function executeInParallel(step, input, ctx, rawExecuteStep, opts)
|
|
|
168
205
|
for (const child of childContexts) {
|
|
169
206
|
child.detachFromParent();
|
|
170
207
|
}
|
|
208
|
+
await traced.closeUnstarted();
|
|
171
209
|
}
|
|
172
210
|
}
|
|
173
211
|
async function runWithConcurrency(tasks, concurrency) {
|
|
@@ -368,6 +406,10 @@ async function executeSettle(step, paths, input, ctx, childContexts, executeStep
|
|
|
368
406
|
});
|
|
369
407
|
return step.merge(results, ctx);
|
|
370
408
|
}
|
|
409
|
+
//#endregion
|
|
410
|
+
//#region loop
|
|
411
|
+
/** History entries a loop keeps when `maxHistorySize` is unset; oldest drop first. */
|
|
412
|
+
const DEFAULT_MAX_HISTORY = 100;
|
|
371
413
|
function hasTextField(value) {
|
|
372
414
|
return typeof value === 'object' && value !== null && 'text' in value;
|
|
373
415
|
}
|
|
@@ -409,7 +451,7 @@ export async function executeLoop(step, input, ctx, executeStep) {
|
|
|
409
451
|
const startTime = Date.now();
|
|
410
452
|
let stepCount = 0;
|
|
411
453
|
const maxIterations = step.maxIterations ?? 1e3;
|
|
412
|
-
const maxHistory = step.maxHistorySize ??
|
|
454
|
+
const maxHistory = step.maxHistorySize ?? DEFAULT_MAX_HISTORY;
|
|
413
455
|
let totalIterations = 0;
|
|
414
456
|
// Validate maxIterations
|
|
415
457
|
if (!Number.isFinite(maxIterations) || maxIterations < 1) {
|
|
@@ -515,6 +557,7 @@ export async function executeLoop(step, input, ctx, executeStep) {
|
|
|
515
557
|
reason: `Predicate error: ${predicateError instanceof Error ? predicateError.message : String(predicateError)}`,
|
|
516
558
|
};
|
|
517
559
|
}
|
|
560
|
+
recordVerdict(ctx, step.id, totalIterations, verdict);
|
|
518
561
|
if (verdict.stop) {
|
|
519
562
|
if (lastOutput === undefined) {
|
|
520
563
|
throw new NoeticErrorImpl({
|
|
@@ -614,6 +657,21 @@ function park(parkCtx) {
|
|
|
614
657
|
});
|
|
615
658
|
});
|
|
616
659
|
}
|
|
660
|
+
/**
|
|
661
|
+
* Adds a `loop.verdict` event to the enclosing span; loops open no span of
|
|
662
|
+
* their own. Leaves out `feedback`, which can be large or sensitive.
|
|
663
|
+
*/
|
|
664
|
+
function recordVerdict(ctx, loop, iteration, verdict) {
|
|
665
|
+
const attributes = {
|
|
666
|
+
loop,
|
|
667
|
+
iteration,
|
|
668
|
+
stop: verdict.stop,
|
|
669
|
+
};
|
|
670
|
+
if (verdict.reason !== undefined) {
|
|
671
|
+
attributes.reason = verdict.reason;
|
|
672
|
+
}
|
|
673
|
+
ctx.span.addEvent(NoeticEvent.LOOP_VERDICT, attributes);
|
|
674
|
+
}
|
|
617
675
|
function recordIterationError(ctx, error) {
|
|
618
676
|
const message = error instanceof Error ? error.message : String(error);
|
|
619
677
|
const stack = error instanceof Error && error.stack ? error.stack : '';
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { choiceValues, DecideOutputError, frameworkCast, hashString,
|
|
1
|
+
import { choiceValues, DecideOutputError, frameworkCast, hashString, NoeticConfigError, NoeticErrorImpl, scoreLevels, } from '@noetic-tools/types';
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import { GenAI, NoeticAttr } from '../observability/genai-attributes.js';
|
|
4
|
-
import {
|
|
5
|
-
import { allocateBudgets, assembleView, DECISION_PROJECTION } from './action-deps.js';
|
|
4
|
+
import { allocateBudgets, assembleView, DECISION_PROJECTION, laneBase } from './action-deps.js';
|
|
6
5
|
import { resolveLazy } from './execute-action.js';
|
|
7
6
|
import { trackUsage } from './message-helpers.js';
|
|
7
|
+
import { wrapModelError } from './model-error.js';
|
|
8
8
|
import { isContextImpl, isMutableContext } from './typeguards.js';
|
|
9
9
|
//#region Public API
|
|
10
10
|
/**
|
|
@@ -110,10 +110,12 @@ export async function executeDecide(step, input, ctx, layers) {
|
|
|
110
110
|
*/
|
|
111
111
|
const ANSWERED = new WeakMap();
|
|
112
112
|
function answered(ctx) {
|
|
113
|
-
|
|
113
|
+
// Every step runs on its own lane of the context; the answers belong to the context.
|
|
114
|
+
const key = laneBase(ctx);
|
|
115
|
+
let map = ANSWERED.get(key);
|
|
114
116
|
if (!map) {
|
|
115
117
|
map = new Map();
|
|
116
|
-
ANSWERED.set(
|
|
118
|
+
ANSWERED.set(key, map);
|
|
117
119
|
}
|
|
118
120
|
return map;
|
|
119
121
|
}
|
|
@@ -150,16 +152,7 @@ async function askHarness(step, ctx, request) {
|
|
|
150
152
|
return await ctx.harness.decide(request);
|
|
151
153
|
}
|
|
152
154
|
catch (e) {
|
|
153
|
-
if (
|
|
154
|
-
throw e;
|
|
155
|
-
}
|
|
156
|
-
if (ctx.aborted) {
|
|
157
|
-
throw new NoeticErrorImpl({
|
|
158
|
-
kind: 'cancelled',
|
|
159
|
-
reason: ctx.abortReason ?? 'context aborted',
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
if (e instanceof DecideOutputError) {
|
|
155
|
+
if (e instanceof DecideOutputError && !ctx.aborted) {
|
|
163
156
|
if (e.usage) {
|
|
164
157
|
trackUsage(ctx, {
|
|
165
158
|
items: [],
|
|
@@ -175,18 +168,7 @@ async function askHarness(step, ctx, request) {
|
|
|
175
168
|
zodError: e.zodError,
|
|
176
169
|
});
|
|
177
170
|
}
|
|
178
|
-
|
|
179
|
-
throw new NoeticErrorImpl({
|
|
180
|
-
kind: 'model_rate_limit',
|
|
181
|
-
stepId: step.id,
|
|
182
|
-
});
|
|
183
|
-
}
|
|
184
|
-
throw new NoeticErrorImpl({
|
|
185
|
-
kind: 'step_failed',
|
|
186
|
-
stepId: step.id,
|
|
187
|
-
cause: e instanceof Error ? e : new Error(String(e)),
|
|
188
|
-
retriesExhausted: false,
|
|
189
|
-
});
|
|
171
|
+
throw wrapModelError(e, step.id, ctx);
|
|
190
172
|
}
|
|
191
173
|
}
|
|
192
174
|
//#endregion
|
|
@@ -13,6 +13,16 @@ import type { Context, Step } from '@noetic-tools/types';
|
|
|
13
13
|
* continue to work.
|
|
14
14
|
*/
|
|
15
15
|
export declare function executeNoAdapter<TContext, I, O>(step: Step<TContext, I, O>, input: I, ctx: Context<TContext>): Promise<O>;
|
|
16
|
+
/**
|
|
17
|
+
* @internal
|
|
18
|
+
* The ledger key this dispatch records and replays under, or undefined when the
|
|
19
|
+
* step bypasses the ledger (spec 23a § Identity). A `durable: false` step and a
|
|
20
|
+
* `schedule` (which never completes) are neither replayed nor recorded; the opt-out
|
|
21
|
+
* covers this step only, so its children still resolve their own keys. A
|
|
22
|
+
* `replayKey` replaces the positional path with `key:<key>`. A throwing `replayKey`
|
|
23
|
+
* costs this dispatch its durability, not its run: the step executes unrecorded.
|
|
24
|
+
*/
|
|
25
|
+
export declare function resolveLedgerKey<TContext, I, O>(step: Step<TContext, I, O>, input: I, ctx: Context<TContext>): string | undefined;
|
|
16
26
|
/**
|
|
17
27
|
* Executes a step within the interpreter, dispatching to the appropriate handler by step kind.
|
|
18
28
|
*
|