@spendgraph/workflows 0.2.1 → 0.3.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/README.md +53 -0
- package/dist/noesis/execute/execute.js +2 -0
- package/dist/noesis/execute/index.d.ts +2 -0
- package/dist/noesis/execute/index.js +1 -0
- package/dist/noesis/execute/spend.d.ts +45 -0
- package/dist/noesis/execute/spend.js +87 -0
- package/dist/noesis/execute/types.d.ts +2 -0
- package/dist/noesis/index.d.ts +4 -3
- package/dist/noesis/index.js +2 -2
- package/dist/noesis/run/index.d.ts +2 -2
- package/dist/noesis/run/index.js +1 -1
- package/dist/noesis/run/ledger.d.ts +57 -1
- package/dist/noesis/run/ledger.js +19 -0
- package/dist/noesis/run/loop.js +4 -3
- package/dist/noesis/run/options.d.ts +24 -0
- package/dist/noesis/run/options.js +26 -0
- package/dist/noesis/run/plan.js +4 -3
- package/dist/noesis/run/produce.js +10 -4
- package/dist/noesis/run/result.js +1 -2
- package/dist/noesis/run/run.js +5 -0
- package/dist/noesis/run/stages/classify.js +2 -1
- package/dist/noesis/run/stages/direct.js +2 -1
- package/dist/noesis/run/stages/retrieve.js +6 -2
- package/dist/noesis/run/state.d.ts +5 -0
- package/dist/noesis/run/state.js +5 -0
- package/dist/noesis/run/verify.js +5 -4
- package/dist/noesis/types.d.ts +17 -5
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -70,6 +70,59 @@ answer. Stage 5 pairs 5A's claims back to 5B's blind answers, and it has to be
|
|
|
70
70
|
code: whatever does it sees both the claim and the answer written without it,
|
|
71
71
|
and a model given both is no longer a blind check of anything.
|
|
72
72
|
|
|
73
|
+
## Choosing models
|
|
74
|
+
|
|
75
|
+
`stage` sets the model every stage runs on. `stages` overrides it by name, for
|
|
76
|
+
the stages worth paying more or less for.
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
await run(client, llm, question, {
|
|
80
|
+
stage: { model: "claude-haiku-4-5" },
|
|
81
|
+
stages: {
|
|
82
|
+
plan_candidates: { model: "claude-sonnet-4-6" },
|
|
83
|
+
red_team: { model: "claude-opus-4-8" },
|
|
84
|
+
answer: { model: "claude-sonnet-4-6" },
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
The keys are the stage names from the table above with the spaces closed up —
|
|
90
|
+
`triage`, `question_brief`, `plan_candidates`, `red_team`, `answer` — and
|
|
91
|
+
`STAGE_KEYS` maps every id to its own. `triage` and `answer` also have
|
|
92
|
+
`opts.triage` and `opts.answer`, which
|
|
93
|
+
take more than a model — a current task, and the answer's escalating tiers —
|
|
94
|
+
and win where both are set.
|
|
95
|
+
|
|
96
|
+
**`params` does not follow a model it was not written for.** A stage naming its
|
|
97
|
+
own `model` gets its own `params` or none: Haiku takes `temperature`, Sonnet 5
|
|
98
|
+
rejects it outright, and one shared request body fails on whichever it was not
|
|
99
|
+
written for. A stage overriding only `params` still merges over `stage` — except
|
|
100
|
+
`answer`, which escalates through tiers, so its `params` reach it only alongside
|
|
101
|
+
a `model`.
|
|
102
|
+
|
|
103
|
+
## What a run cost
|
|
104
|
+
|
|
105
|
+
`usage` is the model spend: six numbers rather than two, summed across every
|
|
106
|
+
stage and every retry, split `byStage` and `byModel` because the answer can
|
|
107
|
+
cascade. A back edge refunds nothing.
|
|
108
|
+
|
|
109
|
+
`usage.tools` is what the tools reported, and it is deliberately **not** folded
|
|
110
|
+
in — a tool call is somebody else's API at somebody else's rates and is no
|
|
111
|
+
rollout of ours.
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
result.usage.inputTokens; // the model side
|
|
115
|
+
result.usage.byModel["claude-sonnet-4-6"];
|
|
116
|
+
|
|
117
|
+
result.usage.tools.calls; // the tool side
|
|
118
|
+
result.usage.tools.costMicros; // where a tool priced itself
|
|
119
|
+
result.usage.tools.byTool.web_search; // tokens, keyed by the tool that ran
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Bill both or you undercharge every run that touched the web: `web_search`
|
|
123
|
+
reports Perplexity's four token counts and `deep_recall` reports micro-USD,
|
|
124
|
+
and stage 4 records zero tokens of its own.
|
|
125
|
+
|
|
73
126
|
## The ledger
|
|
74
127
|
|
|
75
128
|
Every stage files its result, its rollouts and its tokens in one place. That
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { order } from "./order.js";
|
|
2
|
+
import { toolSpendOf } from "./spend.js";
|
|
2
3
|
export const DEFAULT_TOOLS = {
|
|
3
4
|
calculator: { name: "calculate", arg: "expression" },
|
|
4
5
|
deeprecall: { name: "deep_recall", arg: "question" },
|
|
@@ -63,6 +64,7 @@ export async function execute(bus, plan, opts = {}) {
|
|
|
63
64
|
return {
|
|
64
65
|
answers,
|
|
65
66
|
unresolved,
|
|
67
|
+
spend: toolSpendOf(answers, wiring),
|
|
66
68
|
toolMs: answers.reduce((total, answer) => total + answer.latencyMs, 0),
|
|
67
69
|
};
|
|
68
70
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
export { DEFAULT_TOOLS, execute } from "./execute.js";
|
|
2
2
|
export { order } from "./order.js";
|
|
3
3
|
export type { ExecuteOptions, Execution, SubAnswer, Waves, Wiring } from "./types.js";
|
|
4
|
+
export type { ToolSpend, ToolUsage } from "./spend.js";
|
|
5
|
+
export { addToolSpend, NO_TOOL_SPEND, readToolUsage, recorded, toolSpendOf } from "./spend.js";
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { Execution, SubAnswer, Wiring } from "./types.js";
|
|
2
|
+
import type { SubQuestionTool } from "../producers/decompose/index.js";
|
|
3
|
+
/** What one tool reported it cost, keyed by the tool that reported it. */
|
|
4
|
+
export interface ToolSpend {
|
|
5
|
+
/** Tool calls that ran, whether or not the tool priced itself. */
|
|
6
|
+
calls: number;
|
|
7
|
+
/** Summed where a tool priced itself. Undefined where none did. */
|
|
8
|
+
costMicros?: number;
|
|
9
|
+
byTool: Record<string, ToolUsage>;
|
|
10
|
+
}
|
|
11
|
+
export interface ToolUsage {
|
|
12
|
+
calls: number;
|
|
13
|
+
model?: string;
|
|
14
|
+
inputTokens: number;
|
|
15
|
+
outputTokens: number;
|
|
16
|
+
citationTokens: number;
|
|
17
|
+
reasoningTokens: number;
|
|
18
|
+
costMicros?: number;
|
|
19
|
+
}
|
|
20
|
+
export declare const NO_TOOL_SPEND: ToolSpend;
|
|
21
|
+
/**
|
|
22
|
+
* What a tool result says it cost, read by duck typing.
|
|
23
|
+
*
|
|
24
|
+
* A tool is somebody else's API and prices itself however it likes: `web_search`
|
|
25
|
+
* reports Perplexity's four token counts, `deep_recall` reports micro-USD. Both
|
|
26
|
+
* are read where present and nothing is invented where absent.
|
|
27
|
+
*/
|
|
28
|
+
export declare function readToolUsage(result: unknown): Omit<ToolUsage, "calls"> | null;
|
|
29
|
+
/**
|
|
30
|
+
* Every tool call in one execution, summed under the tool that actually ran.
|
|
31
|
+
*
|
|
32
|
+
* Keyed by the bus tool's name rather than the sub-question's, because that is
|
|
33
|
+
* the one a rate applies to and the one the bus offered.
|
|
34
|
+
*/
|
|
35
|
+
export declare function toolSpendOf(answers: SubAnswer[], wiring?: Partial<Record<SubQuestionTool, Wiring>>): ToolSpend;
|
|
36
|
+
/** Two executions' tool spend, added. A retry does not refund the first attempt. */
|
|
37
|
+
export declare function addToolSpend(a: ToolSpend, b: ToolSpend): ToolSpend;
|
|
38
|
+
/**
|
|
39
|
+
* The execution as the ledger should keep it: everything but the billing.
|
|
40
|
+
*
|
|
41
|
+
* Stage 4's artifact is serialized into every later stage's prompt and into the
|
|
42
|
+
* answer's evidence, so what a tool cost would ride along as citable material
|
|
43
|
+
* and be paid for again in tokens at each stage that reads it.
|
|
44
|
+
*/
|
|
45
|
+
export declare function recorded(execution: Execution): Omit<Execution, "spend">;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
export const NO_TOOL_SPEND = { calls: 0, byTool: {} };
|
|
2
|
+
const numberAt = (source, field) => {
|
|
3
|
+
const value = source[field];
|
|
4
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
5
|
+
};
|
|
6
|
+
export function readToolUsage(result) {
|
|
7
|
+
if (!result || typeof result !== "object")
|
|
8
|
+
return null;
|
|
9
|
+
const shape = result;
|
|
10
|
+
const usage = (shape.usage ?? {});
|
|
11
|
+
const tokens = {
|
|
12
|
+
inputTokens: numberAt(usage, "inputTokens"),
|
|
13
|
+
outputTokens: numberAt(usage, "outputTokens"),
|
|
14
|
+
citationTokens: numberAt(usage, "citationTokens"),
|
|
15
|
+
reasoningTokens: numberAt(usage, "reasoningTokens"),
|
|
16
|
+
};
|
|
17
|
+
const priced = typeof shape.costMicroUsd === "number" ? shape.costMicroUsd : undefined;
|
|
18
|
+
const model = typeof shape.model === "string" ? shape.model : undefined;
|
|
19
|
+
const spent = tokens.inputTokens + tokens.outputTokens + tokens.citationTokens + tokens.reasoningTokens;
|
|
20
|
+
if (spent === 0 && priced === undefined)
|
|
21
|
+
return null;
|
|
22
|
+
return { ...tokens, ...(model ? { model } : {}), ...(priced !== undefined ? { costMicros: priced } : {}) };
|
|
23
|
+
}
|
|
24
|
+
export function toolSpendOf(answers, wiring = {}) {
|
|
25
|
+
const byTool = {};
|
|
26
|
+
let calls = 0;
|
|
27
|
+
let costMicros;
|
|
28
|
+
for (const answer of answers) {
|
|
29
|
+
if (answer.status === "answered")
|
|
30
|
+
calls++;
|
|
31
|
+
const one = readToolUsage(answer.result);
|
|
32
|
+
if (!one)
|
|
33
|
+
continue;
|
|
34
|
+
const named = wiring[answer.tool]?.name ?? answer.tool;
|
|
35
|
+
const prior = byTool[named] ?? {
|
|
36
|
+
calls: 0,
|
|
37
|
+
inputTokens: 0,
|
|
38
|
+
outputTokens: 0,
|
|
39
|
+
citationTokens: 0,
|
|
40
|
+
reasoningTokens: 0,
|
|
41
|
+
};
|
|
42
|
+
const merged = {
|
|
43
|
+
calls: prior.calls + 1,
|
|
44
|
+
inputTokens: prior.inputTokens + one.inputTokens,
|
|
45
|
+
outputTokens: prior.outputTokens + one.outputTokens,
|
|
46
|
+
citationTokens: prior.citationTokens + one.citationTokens,
|
|
47
|
+
reasoningTokens: prior.reasoningTokens + one.reasoningTokens,
|
|
48
|
+
...(one.model ?? prior.model ? { model: one.model ?? prior.model } : {}),
|
|
49
|
+
};
|
|
50
|
+
if (one.costMicros !== undefined || prior.costMicros !== undefined) {
|
|
51
|
+
merged.costMicros = (prior.costMicros ?? 0) + (one.costMicros ?? 0);
|
|
52
|
+
costMicros = (costMicros ?? 0) + (one.costMicros ?? 0);
|
|
53
|
+
}
|
|
54
|
+
byTool[named] = merged;
|
|
55
|
+
}
|
|
56
|
+
return { calls, byTool, ...(costMicros !== undefined ? { costMicros } : {}) };
|
|
57
|
+
}
|
|
58
|
+
export function addToolSpend(a, b) {
|
|
59
|
+
const byTool = { ...a.byTool };
|
|
60
|
+
for (const [tool, one] of Object.entries(b.byTool)) {
|
|
61
|
+
const prior = byTool[tool];
|
|
62
|
+
byTool[tool] = prior
|
|
63
|
+
? {
|
|
64
|
+
calls: prior.calls + one.calls,
|
|
65
|
+
inputTokens: prior.inputTokens + one.inputTokens,
|
|
66
|
+
outputTokens: prior.outputTokens + one.outputTokens,
|
|
67
|
+
citationTokens: prior.citationTokens + one.citationTokens,
|
|
68
|
+
reasoningTokens: prior.reasoningTokens + one.reasoningTokens,
|
|
69
|
+
...(one.model ?? prior.model ? { model: one.model ?? prior.model } : {}),
|
|
70
|
+
...(prior.costMicros !== undefined || one.costMicros !== undefined
|
|
71
|
+
? { costMicros: (prior.costMicros ?? 0) + (one.costMicros ?? 0) }
|
|
72
|
+
: {}),
|
|
73
|
+
}
|
|
74
|
+
: one;
|
|
75
|
+
}
|
|
76
|
+
const costMicros = a.costMicros !== undefined || b.costMicros !== undefined
|
|
77
|
+
? (a.costMicros ?? 0) + (b.costMicros ?? 0)
|
|
78
|
+
: undefined;
|
|
79
|
+
return { calls: a.calls + b.calls, byTool, ...(costMicros !== undefined ? { costMicros } : {}) };
|
|
80
|
+
}
|
|
81
|
+
export function recorded(execution) {
|
|
82
|
+
return {
|
|
83
|
+
answers: execution.answers,
|
|
84
|
+
unresolved: execution.unresolved,
|
|
85
|
+
toolMs: execution.toolMs,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ToolSpend } from "./spend.js";
|
|
1
2
|
import type { Emit } from "../events.js";
|
|
2
3
|
import type { SubQuestion, SubQuestionTool } from "../producers/decompose/index.js";
|
|
3
4
|
/** Which tool answers a sub-question, and the argument its question fills. */
|
|
@@ -27,6 +28,7 @@ export interface Execution {
|
|
|
27
28
|
answers: SubAnswer[];
|
|
28
29
|
/** Ids that could never run: a cycle, or a dependency nothing created. */
|
|
29
30
|
unresolved: string[];
|
|
31
|
+
spend: ToolSpend;
|
|
30
32
|
/** Time spent inside tools. Not wall clock — a wave runs together. */
|
|
31
33
|
toolMs: number;
|
|
32
34
|
}
|
package/dist/noesis/index.d.ts
CHANGED
|
@@ -6,7 +6,8 @@ export { assemble, render as renderDraft } from "./draft/index.js";
|
|
|
6
6
|
export type { Emit, EventBase, NoesisEvent, NoesisEventInput } from "./events.js";
|
|
7
7
|
export { emitter } from "./events.js";
|
|
8
8
|
export type { ExecuteOptions, Execution, SubAnswer, Waves, Wiring, } from "./execute/index.js";
|
|
9
|
-
export {
|
|
9
|
+
export type { ToolSpend, ToolUsage } from "./execute/index.js";
|
|
10
|
+
export { addToolSpend, DEFAULT_TOOLS, execute, NO_TOOL_SPEND, order, readToolUsage, recorded, toolSpendOf, } from "./execute/index.js";
|
|
10
11
|
export type { ClosureChecklist, ClosureChecklistInput, ClosureChecklistOptions, ClosureChecklistResult, ClosureGate, } from "./gates/closure-checklist/index.js";
|
|
11
12
|
export { invoke as closureChecklist } from "./gates/closure-checklist/index.js";
|
|
12
13
|
export type { CqotGate, CqotGateInput, CqotGateOptions, CqotGateResult, Gate, GateQuestion, GateVerdict, } from "./gates/cqot-gate/index.js";
|
|
@@ -38,8 +39,8 @@ export { invoke as reflection } from "./repair/reflection/index.js";
|
|
|
38
39
|
export { routeFor } from "./route.js";
|
|
39
40
|
export type { Difficulty, Stakes, TriageEstimates, TriageInput, TriageOptions, TriageResult, } from "./router/triage/index.js";
|
|
40
41
|
export { invoke as triage, TriageReplyError } from "./router/triage/index.js";
|
|
41
|
-
export type { Entry, Escalation, Ledger, NoesisStream, Recorded, StageId, StageUsage, } from "./run/index.js";
|
|
42
|
-
export { NoToolsError, newLedger, RouteFailedError, RouteNotBuiltError, run, runStream, STAGE_NAMES, STAGES, } from "./run/index.js";
|
|
42
|
+
export type { Entry, Escalation, Ledger, NoesisStream, Recorded, StageId, StageKey, StageName, StageUsage, } from "./run/index.js";
|
|
43
|
+
export { NoToolsError, newLedger, RouteFailedError, RouteNotBuiltError, run, runStream, STAGE_KEYS, STAGE_NAMES, STAGES, } from "./run/index.js";
|
|
43
44
|
export { dropPoint, parseStage, resumeAt } from "./run/redo.js";
|
|
44
45
|
export { STAGE_OF_SLUG } from "./slugs.js";
|
|
45
46
|
export type { StageOptions, StageOutcome } from "./stage.js";
|
package/dist/noesis/index.js
CHANGED
|
@@ -2,7 +2,7 @@ export { stageCited, unattributed } from "./attribution.js";
|
|
|
2
2
|
export { asked } from "./context.js";
|
|
3
3
|
export { assemble, render as renderDraft } from "./draft/index.js";
|
|
4
4
|
export { emitter } from "./events.js";
|
|
5
|
-
export { DEFAULT_TOOLS, execute, order } from "./execute/index.js";
|
|
5
|
+
export { addToolSpend, DEFAULT_TOOLS, execute, NO_TOOL_SPEND, order, readToolUsage, recorded, toolSpendOf, } from "./execute/index.js";
|
|
6
6
|
export { invoke as closureChecklist } from "./gates/closure-checklist/index.js";
|
|
7
7
|
export { invoke as cqotGate } from "./gates/cqot-gate/index.js";
|
|
8
8
|
export { invoke as premortem } from "./gates/premortem/index.js";
|
|
@@ -19,7 +19,7 @@ export { answer, invoke as toulmin, NoAnswerError, render, ToulminReplyError, }
|
|
|
19
19
|
export { invoke as reflection } from "./repair/reflection/index.js";
|
|
20
20
|
export { routeFor } from "./route.js";
|
|
21
21
|
export { invoke as triage, TriageReplyError } from "./router/triage/index.js";
|
|
22
|
-
export { NoToolsError, newLedger, RouteFailedError, RouteNotBuiltError, run, runStream, STAGE_NAMES, STAGES, } from "./run/index.js";
|
|
22
|
+
export { NoToolsError, newLedger, RouteFailedError, RouteNotBuiltError, run, runStream, STAGE_KEYS, STAGE_NAMES, STAGES, } from "./run/index.js";
|
|
23
23
|
export { dropPoint, parseStage, resumeAt } from "./run/redo.js";
|
|
24
24
|
export { STAGE_OF_SLUG } from "./slugs.js";
|
|
25
25
|
export { runStage, StageReplyError } from "./stage.js";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { NoToolsError, RouteFailedError, RouteNotBuiltError } from "./errors.js";
|
|
2
|
-
export type { Entry, Escalation, Ledger, Recorded, StageId, StageUsage } from "./ledger.js";
|
|
3
|
-
export { newLedger, STAGE_NAMES, STAGES } from "./ledger.js";
|
|
2
|
+
export type { Entry, Escalation, Ledger, Recorded, StageId, StageKey, StageName, StageUsage } from "./ledger.js";
|
|
3
|
+
export { newLedger, STAGE_KEYS, STAGE_NAMES, STAGES } from "./ledger.js";
|
|
4
4
|
export { run } from "./run.js";
|
|
5
5
|
export type { NoesisStream } from "./stream.js";
|
|
6
6
|
export { runStream } from "./stream.js";
|
package/dist/noesis/run/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { NoToolsError, RouteFailedError, RouteNotBuiltError } from "./errors.js";
|
|
2
|
-
export { newLedger, STAGE_NAMES, STAGES } from "./ledger.js";
|
|
2
|
+
export { newLedger, STAGE_KEYS, STAGE_NAMES, STAGES } from "./ledger.js";
|
|
3
3
|
export { run } from "./run.js";
|
|
4
4
|
export { runStream } from "./stream.js";
|
|
@@ -4,7 +4,62 @@ import { type PartialUsage, type Usage } from "../usage.js";
|
|
|
4
4
|
export declare const STAGES: readonly ["0", "1", "2", "3A", "3B", "3C", "4", "4A", "4B", "5A", "5B", "5", "5C", "6A", "6B", "6C", "R"];
|
|
5
5
|
export type StageId = (typeof STAGES)[number];
|
|
6
6
|
/** What a stage is called where a person or a model has to read it. */
|
|
7
|
-
export declare const STAGE_NAMES:
|
|
7
|
+
export declare const STAGE_NAMES: {
|
|
8
|
+
readonly "0": "triage";
|
|
9
|
+
readonly "1": "question brief";
|
|
10
|
+
readonly "2": "decomposition";
|
|
11
|
+
readonly "3A": "plan candidates";
|
|
12
|
+
readonly "3B": "premortem";
|
|
13
|
+
readonly "3C": "cqot gate";
|
|
14
|
+
readonly "4": "execution";
|
|
15
|
+
readonly "4A": "think checkpoint";
|
|
16
|
+
readonly "4B": "discriminating test";
|
|
17
|
+
readonly "5A": "verification questions";
|
|
18
|
+
readonly "5B": "verification answers";
|
|
19
|
+
readonly "5": "verification";
|
|
20
|
+
readonly "5C": "standards review";
|
|
21
|
+
readonly "6A": "closure checklist";
|
|
22
|
+
readonly "6B": "red team";
|
|
23
|
+
readonly "6C": "answer";
|
|
24
|
+
readonly R: "root cause";
|
|
25
|
+
};
|
|
26
|
+
/** A stage by the name it announces itself under. */
|
|
27
|
+
export type StageName = (typeof STAGE_NAMES)[StageId];
|
|
28
|
+
/**
|
|
29
|
+
* What a stage is called where it is a key rather than prose.
|
|
30
|
+
*
|
|
31
|
+
* `STAGE_NAMES` with the spaces closed up, kept apart from it because those
|
|
32
|
+
* names are read back out of the artifacts by the answer and cannot be renamed
|
|
33
|
+
* for a config's convenience.
|
|
34
|
+
*/
|
|
35
|
+
export declare const STAGE_KEYS: {
|
|
36
|
+
readonly "0": "triage";
|
|
37
|
+
readonly "1": "question_brief";
|
|
38
|
+
readonly "2": "decomposition";
|
|
39
|
+
readonly "3A": "plan_candidates";
|
|
40
|
+
readonly "3B": "premortem";
|
|
41
|
+
readonly "3C": "cqot_gate";
|
|
42
|
+
readonly "4": "execution";
|
|
43
|
+
readonly "4A": "think_checkpoint";
|
|
44
|
+
readonly "4B": "discriminating_test";
|
|
45
|
+
readonly "5A": "verification_questions";
|
|
46
|
+
readonly "5B": "verification_answers";
|
|
47
|
+
readonly "5": "verification";
|
|
48
|
+
readonly "5C": "standards_review";
|
|
49
|
+
readonly "6A": "closure_checklist";
|
|
50
|
+
readonly "6B": "red_team";
|
|
51
|
+
readonly "6C": "answer";
|
|
52
|
+
readonly R: "root_cause";
|
|
53
|
+
};
|
|
54
|
+
/** Stages that dispatch or pair rather than call a model. */
|
|
55
|
+
type CodeOnly = "execution" | "verification" | "discriminating_test";
|
|
56
|
+
/**
|
|
57
|
+
* A stage by the key a config names it under.
|
|
58
|
+
*
|
|
59
|
+
* The code-only stages are excluded: naming a model for the tool dispatcher or
|
|
60
|
+
* the blind-check pairing would typecheck and do nothing.
|
|
61
|
+
*/
|
|
62
|
+
export type StageKey = Exclude<(typeof STAGE_KEYS)[StageId], CodeOnly>;
|
|
8
63
|
/** What one stage left behind, and what it cost. */
|
|
9
64
|
export interface Entry extends Usage {
|
|
10
65
|
stage: StageId;
|
|
@@ -108,3 +163,4 @@ export interface Ledger {
|
|
|
108
163
|
* failing once each are the same amount of a run going nowhere.
|
|
109
164
|
*/
|
|
110
165
|
export declare function newLedger(maxRetries?: number, emit?: Emit): Ledger;
|
|
166
|
+
export {};
|
|
@@ -38,6 +38,25 @@ export const STAGE_NAMES = {
|
|
|
38
38
|
"6C": "answer",
|
|
39
39
|
R: "root cause",
|
|
40
40
|
};
|
|
41
|
+
export const STAGE_KEYS = {
|
|
42
|
+
"0": "triage",
|
|
43
|
+
"1": "question_brief",
|
|
44
|
+
"2": "decomposition",
|
|
45
|
+
"3A": "plan_candidates",
|
|
46
|
+
"3B": "premortem",
|
|
47
|
+
"3C": "cqot_gate",
|
|
48
|
+
"4": "execution",
|
|
49
|
+
"4A": "think_checkpoint",
|
|
50
|
+
"4B": "discriminating_test",
|
|
51
|
+
"5A": "verification_questions",
|
|
52
|
+
"5B": "verification_answers",
|
|
53
|
+
"5": "verification",
|
|
54
|
+
"5C": "standards_review",
|
|
55
|
+
"6A": "closure_checklist",
|
|
56
|
+
"6B": "red_team",
|
|
57
|
+
"6C": "answer",
|
|
58
|
+
R: "root_cause",
|
|
59
|
+
};
|
|
41
60
|
const DEFAULT_MAX_RETRIES = 5;
|
|
42
61
|
const rolloutsOf = (result) => "rolloutIds" in result ? result.rolloutIds : [result.rolloutId];
|
|
43
62
|
export function newLedger(maxRetries = DEFAULT_MAX_RETRIES, emit) {
|
package/dist/noesis/run/loop.js
CHANGED
|
@@ -4,6 +4,7 @@ import { invoke as standardsReview } from "../gates/standards-review/index.js";
|
|
|
4
4
|
import { STAGE_NAMES } from "./ledger.js";
|
|
5
5
|
import { dropPoint, resumeAt } from "./redo.js";
|
|
6
6
|
import { checks, reflect } from "./verify.js";
|
|
7
|
+
import { stageOptionsFor } from "./options.js";
|
|
7
8
|
async function stepBack(client, llm, question, opts, state, gates, gate, reason, hint) {
|
|
8
9
|
if (!state.ledger.retry(gate)) {
|
|
9
10
|
state.ledger.escalate(gate, `Out of retries. ${gate} last said: ${reason}`);
|
|
@@ -16,7 +17,7 @@ async function stepBack(client, llm, question, opts, state, gates, gate, reason,
|
|
|
16
17
|
}
|
|
17
18
|
async function judge(client, llm, opts, state, checked, gates) {
|
|
18
19
|
if (gates.standards) {
|
|
19
|
-
const review = state.ledger.record("5C", await standardsReview(client, llm, { question: checked, ...opts.context }, opts
|
|
20
|
+
const review = state.ledger.record("5C", await standardsReview(client, llm, { question: checked, ...opts.context }, stageOptionsFor(opts, "5C")));
|
|
20
21
|
if (review.overall !== "accept") {
|
|
21
22
|
return {
|
|
22
23
|
stage: "5C",
|
|
@@ -25,7 +26,7 @@ async function judge(client, llm, opts, state, checked, gates) {
|
|
|
25
26
|
}
|
|
26
27
|
state.emit?.({ type: "gate.passed", stage: "5C", name: STAGE_NAMES["5C"] });
|
|
27
28
|
}
|
|
28
|
-
const closure = state.ledger.record("6A", await closureChecklist(client, llm, { question: checked, ...opts.context }, opts
|
|
29
|
+
const closure = state.ledger.record("6A", await closureChecklist(client, llm, { question: checked, ...opts.context }, stageOptionsFor(opts, "6A")));
|
|
29
30
|
if (closure.gate !== "close") {
|
|
30
31
|
return {
|
|
31
32
|
stage: "6A",
|
|
@@ -36,7 +37,7 @@ async function judge(client, llm, opts, state, checked, gates) {
|
|
|
36
37
|
}
|
|
37
38
|
state.emit?.({ type: "gate.passed", stage: "6A", name: STAGE_NAMES["6A"] });
|
|
38
39
|
if (gates.redTeam) {
|
|
39
|
-
const attacked = state.ledger.record("6B", await redTeam(client, llm, { question: checked, ...opts.context }, opts
|
|
40
|
+
const attacked = state.ledger.record("6B", await redTeam(client, llm, { question: checked, ...opts.context }, stageOptionsFor(opts, "6B")));
|
|
40
41
|
if (attacked.verdict !== "survives") {
|
|
41
42
|
return { stage: "6B", reason: `${attacked.verdict}: ${attacked.would_break_it}` };
|
|
42
43
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { AnswerOptions } from "../producers/toulmin/index.js";
|
|
2
|
+
import type { StageOptions } from "../stage.js";
|
|
3
|
+
import type { NoesisOptions } from "../types.js";
|
|
4
|
+
import { type StageId } from "./ledger.js";
|
|
5
|
+
/**
|
|
6
|
+
* What one stage runs on: `stages[id]` over `stage`, with `params` dropped
|
|
7
|
+
* rather than inherited when the stage names a model of its own.
|
|
8
|
+
*/
|
|
9
|
+
export declare function stageOptionsFor(opts: NoesisOptions, id: StageId): StageOptions;
|
|
10
|
+
/**
|
|
11
|
+
* Triage's options: `stages.triage` under `opts.triage`.
|
|
12
|
+
*
|
|
13
|
+
* `stage` is deliberately not a base here — it is the model for the stages
|
|
14
|
+
* between triage and the answer, and folding it in would send triage a body
|
|
15
|
+
* written for a different model.
|
|
16
|
+
*/
|
|
17
|
+
export declare function triageOptions(opts: NoesisOptions): StageOptions;
|
|
18
|
+
/**
|
|
19
|
+
* The answer's tiers, defaulting to whatever `stages.answer` named.
|
|
20
|
+
*
|
|
21
|
+
* `params` reaches the answer only alongside a `model`, because the answer
|
|
22
|
+
* escalates through tiers and a tier carries its own params.
|
|
23
|
+
*/
|
|
24
|
+
export declare function answerOptions(opts: NoesisOptions): AnswerOptions;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { STAGE_KEYS } from "./ledger.js";
|
|
2
|
+
export function stageOptionsFor(opts, id) {
|
|
3
|
+
const base = opts.stage ?? {};
|
|
4
|
+
const per = opts.stages?.[STAGE_KEYS[id]];
|
|
5
|
+
if (!per)
|
|
6
|
+
return base;
|
|
7
|
+
const ownModel = per.model !== undefined && per.model !== base.model;
|
|
8
|
+
const params = ownModel ? per.params : { ...base.params, ...per.params };
|
|
9
|
+
const rest = Object.fromEntries(Object.entries({ ...base, ...per }).filter(([field]) => field !== "params"));
|
|
10
|
+
return params && Object.keys(params).length > 0 ? { ...rest, params } : rest;
|
|
11
|
+
}
|
|
12
|
+
export function triageOptions(opts) {
|
|
13
|
+
const per = opts.stages?.[STAGE_KEYS["0"]] ?? {};
|
|
14
|
+
const own = opts.triage ?? {};
|
|
15
|
+
const ownModel = own.model !== undefined && own.model !== per.model;
|
|
16
|
+
const params = ownModel ? own.params : { ...per.params, ...own.params };
|
|
17
|
+
const rest = Object.fromEntries(Object.entries({ ...per, ...own }).filter(([field]) => field !== "params"));
|
|
18
|
+
return params && Object.keys(params).length > 0 ? { ...rest, params } : rest;
|
|
19
|
+
}
|
|
20
|
+
export function answerOptions(opts) {
|
|
21
|
+
const base = opts.answer ?? {};
|
|
22
|
+
const sixC = opts.stages?.[STAGE_KEYS["6C"]];
|
|
23
|
+
if (base.tiers || !sixC?.model)
|
|
24
|
+
return base;
|
|
25
|
+
return { ...base, tiers: [{ model: sixC.model, ...(sixC.params ? { params: sixC.params } : {}) }] };
|
|
26
|
+
}
|
package/dist/noesis/run/plan.js
CHANGED
|
@@ -2,20 +2,21 @@ import { invoke as cqotGate } from "../gates/cqot-gate/index.js";
|
|
|
2
2
|
import { invoke as premortem } from "../gates/premortem/index.js";
|
|
3
3
|
import { invoke as planCandidates } from "../producers/plan-candidates/index.js";
|
|
4
4
|
import { soFar } from "./produce.js";
|
|
5
|
+
import { stageOptionsFor } from "./options.js";
|
|
5
6
|
export async function plan(client, llm, question, opts, state, audits) {
|
|
6
7
|
for (;;) {
|
|
7
8
|
state.ledger.dropFrom("3A");
|
|
8
|
-
state.ledger.record("3A", await planCandidates(client, llm, { question: soFar(state, question), ...opts.context }, opts
|
|
9
|
+
state.ledger.record("3A", await planCandidates(client, llm, { question: soFar(state, question), ...opts.context }, stageOptionsFor(opts, "3A")));
|
|
9
10
|
if (!audits)
|
|
10
11
|
return;
|
|
11
|
-
const dead = state.ledger.record("3B", await premortem(client, llm, { question: soFar(state, question), ...opts.context }, opts
|
|
12
|
+
const dead = state.ledger.record("3B", await premortem(client, llm, { question: soFar(state, question), ...opts.context }, stageOptionsFor(opts, "3B")));
|
|
12
13
|
if (dead.fatal || dead.verdict === "replan") {
|
|
13
14
|
if (state.ledger.retry("3B"))
|
|
14
15
|
continue;
|
|
15
16
|
state.ledger.escalate("3B", "Out of retries: the premortem keeps killing the plan.");
|
|
16
17
|
return;
|
|
17
18
|
}
|
|
18
|
-
const gate = state.ledger.record("3C", await cqotGate(client, llm, { question: soFar(state, question), ...opts.context }, opts
|
|
19
|
+
const gate = state.ledger.record("3C", await cqotGate(client, llm, { question: soFar(state, question), ...opts.context }, stageOptionsFor(opts, "3C")));
|
|
19
20
|
if (gate.gate === "pass")
|
|
20
21
|
return;
|
|
21
22
|
if (state.ledger.retry("3C"))
|
|
@@ -6,9 +6,13 @@ import { invoke as brief } from "../producers/qn-brief/index.js";
|
|
|
6
6
|
import { digest } from "./artifacts.js";
|
|
7
7
|
import { NoToolsError } from "./errors.js";
|
|
8
8
|
import { plan } from "./plan.js";
|
|
9
|
+
import { stageOptionsFor } from "./options.js";
|
|
10
|
+
import { NO_TOOL_SPEND } from "../execute/spend.js";
|
|
11
|
+
import { recorded } from "../execute/index.js";
|
|
12
|
+
import { recordToolSpend } from "./state.js";
|
|
9
13
|
export const soFar = (state, question) => JSON.stringify({ question, ...digest(state.ledger.artifacts()) }, null, 2);
|
|
10
14
|
export async function frame(client, llm, question, opts, state) {
|
|
11
|
-
const brief1 = state.ledger.record("1", await brief(client, llm, { question, ...opts.context }, opts
|
|
15
|
+
const brief1 = state.ledger.record("1", await brief(client, llm, { question, ...opts.context }, stageOptionsFor(opts, "1")));
|
|
12
16
|
let asked = question;
|
|
13
17
|
if (!brief1.is_right_question && brief1.better_question && opts.onAsk) {
|
|
14
18
|
state.emit?.({
|
|
@@ -25,15 +29,16 @@ export async function frame(client, llm, question, opts, state) {
|
|
|
25
29
|
state.emit?.({ type: "ask.answered", replacement: replacement ?? null });
|
|
26
30
|
asked = replacement ?? question;
|
|
27
31
|
}
|
|
28
|
-
const split = state.ledger.record("2", await decompose(client, llm, { question: asked, ...opts.context }, opts
|
|
32
|
+
const split = state.ledger.record("2", await decompose(client, llm, { question: asked, ...opts.context }, stageOptionsFor(opts, "2")));
|
|
29
33
|
return { asked, split };
|
|
30
34
|
}
|
|
31
35
|
export async function work(client, llm, question, opts, state, split) {
|
|
32
36
|
if (!opts.tools)
|
|
33
37
|
throw new NoToolsError();
|
|
34
38
|
const execution = await execute(opts.tools, split.sub_questions, { emit: state.emit });
|
|
39
|
+
recordToolSpend(state, execution.spend);
|
|
35
40
|
state.ledger.record("4", {
|
|
36
|
-
data: execution,
|
|
41
|
+
data: recorded(execution),
|
|
37
42
|
rolloutIds: [],
|
|
38
43
|
model: "",
|
|
39
44
|
inputTokens: 0,
|
|
@@ -46,7 +51,7 @@ export async function work(client, llm, question, opts, state, split) {
|
|
|
46
51
|
findings: draft.findings.length,
|
|
47
52
|
missing: draft.missing.map((one) => ({ id: one.id, reason: one.reason })),
|
|
48
53
|
});
|
|
49
|
-
const checkpoint = state.ledger.record("4A", await thinkCheckpoint(client, llm, { question: soFar(state, question), ...opts.context }, opts
|
|
54
|
+
const checkpoint = state.ledger.record("4A", await thinkCheckpoint(client, llm, { question: soFar(state, question), ...opts.context }, stageOptionsFor(opts, "4A")));
|
|
50
55
|
if (checkpoint.next_action === "replan")
|
|
51
56
|
return { draft, action: "replan" };
|
|
52
57
|
if (checkpoint.next_action === "escalate")
|
|
@@ -61,6 +66,7 @@ export async function produce(client, llm, question, opts, state, audits) {
|
|
|
61
66
|
return assemble(asked, split, {
|
|
62
67
|
answers: [],
|
|
63
68
|
unresolved: split.sub_questions.map((sub) => sub.id),
|
|
69
|
+
spend: NO_TOOL_SPEND,
|
|
64
70
|
toolMs: 0,
|
|
65
71
|
});
|
|
66
72
|
}
|
|
@@ -11,6 +11,7 @@ function usageOfRun(state) {
|
|
|
11
11
|
...state.ledger.usage(),
|
|
12
12
|
byStage,
|
|
13
13
|
byModel: state.ledger.byModel(),
|
|
14
|
+
tools: state.toolSpend,
|
|
14
15
|
calls: byStage.reduce((total, stage) => total + stage.calls, 0),
|
|
15
16
|
};
|
|
16
17
|
}
|
|
@@ -36,8 +37,6 @@ export function finish(decided, state, latencyMs) {
|
|
|
36
37
|
rolloutIds: ledger.rolloutIds,
|
|
37
38
|
unattributed: state.unattributed ?? [],
|
|
38
39
|
usage,
|
|
39
|
-
inputTokens: usage.inputTokens,
|
|
40
|
-
outputTokens: usage.outputTokens,
|
|
41
40
|
latencyMs,
|
|
42
41
|
};
|
|
43
42
|
}
|
package/dist/noesis/run/run.js
CHANGED
|
@@ -10,6 +10,11 @@ const watched = (opts, emit) => ({
|
|
|
10
10
|
triage: { ...opts.triage, emit },
|
|
11
11
|
stage: { ...opts.stage, emit },
|
|
12
12
|
answer: { ...opts.answer, emit },
|
|
13
|
+
...(opts.stages
|
|
14
|
+
? {
|
|
15
|
+
stages: Object.fromEntries(Object.entries(opts.stages).map(([id, one]) => [id, { ...one, emit }])),
|
|
16
|
+
}
|
|
17
|
+
: {}),
|
|
13
18
|
});
|
|
14
19
|
export async function run(client, llm, question, options = {}) {
|
|
15
20
|
const startedAt = Date.now();
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { routeFor } from "../../route.js";
|
|
2
2
|
import { invoke as triage } from "../../router/triage/index.js";
|
|
3
3
|
import { nextTask } from "../state.js";
|
|
4
|
+
import { triageOptions } from "../options.js";
|
|
4
5
|
export const classifyStage = (client, llm, question, opts, state) => async () => {
|
|
5
|
-
const stage = await triage(client, llm, { ...opts.triage, currentTask: state.currentTask, question },
|
|
6
|
+
const stage = await triage(client, llm, { ...opts.triage, currentTask: state.currentTask, question }, triageOptions(opts));
|
|
6
7
|
state.estimates = state.ledger.record("0", { ...stage, data: stage.estimates });
|
|
7
8
|
state.routing = routeFor(stage.estimates);
|
|
8
9
|
state.currentTask = nextTask(stage.estimates, state.currentTask);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { answer } from "../../producers/toulmin/index.js";
|
|
2
|
+
import { answerOptions } from "../options.js";
|
|
2
3
|
export const directStage = (client, llm, question, opts, state) => async () => {
|
|
3
|
-
const answered = await answer(client, llm, { question, ...state.ledger.artifacts() }, opts
|
|
4
|
+
const answered = await answer(client, llm, { question, ...state.ledger.artifacts() }, answerOptions(opts));
|
|
4
5
|
state.answered = answered;
|
|
5
6
|
state.text = state.ledger.record("6C", { ...answered, data: answered.answer }).claim;
|
|
6
7
|
return state.text;
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { execute } from "../../execute/index.js";
|
|
2
2
|
import { answer } from "../../producers/toulmin/index.js";
|
|
3
3
|
import { NoToolsError } from "../errors.js";
|
|
4
|
+
import { answerOptions } from "../options.js";
|
|
5
|
+
import { recorded } from "../../execute/index.js";
|
|
6
|
+
import { recordToolSpend } from "../state.js";
|
|
4
7
|
const DEFAULT_TOOL = "deeprecall";
|
|
5
8
|
const lookup = (question, tool) => ({
|
|
6
9
|
id: "q",
|
|
@@ -15,15 +18,16 @@ export const retrieveStage = (client, llm, question, opts, state) => async () =>
|
|
|
15
18
|
if (!opts.tools)
|
|
16
19
|
throw new NoToolsError();
|
|
17
20
|
const execution = await execute(opts.tools, [lookup(question, opts.retrieve?.tool ?? DEFAULT_TOOL)], { emit: state.emit });
|
|
21
|
+
recordToolSpend(state, execution.spend);
|
|
18
22
|
state.ledger.record("4", {
|
|
19
|
-
data: execution,
|
|
23
|
+
data: recorded(execution),
|
|
20
24
|
rolloutIds: [],
|
|
21
25
|
model: "",
|
|
22
26
|
inputTokens: 0,
|
|
23
27
|
outputTokens: 0,
|
|
24
28
|
latencyMs: execution.toolMs,
|
|
25
29
|
});
|
|
26
|
-
const answered = await answer(client, llm, { question, ...state.ledger.artifacts() }, opts
|
|
30
|
+
const answered = await answer(client, llm, { question, ...state.ledger.artifacts() }, answerOptions(opts));
|
|
27
31
|
state.answered = answered;
|
|
28
32
|
state.text = state.ledger.record("6C", { ...answered, data: answered.answer }).claim;
|
|
29
33
|
return state.text;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type ToolSpend } from "../execute/spend.js";
|
|
1
2
|
import type { Emit } from "../events.js";
|
|
2
3
|
import type { AnswerResult } from "../producers/toulmin/index.js";
|
|
3
4
|
import type { TriageEstimates } from "../router/triage/index.js";
|
|
@@ -5,6 +6,8 @@ import type { Routing } from "../types.js";
|
|
|
5
6
|
import { type Ledger } from "./ledger.js";
|
|
6
7
|
/** What each branch writes down, since `route()` hands back only its output. */
|
|
7
8
|
export interface RunState {
|
|
9
|
+
/** What the tools reported they cost, which no stage's tokens include. */
|
|
10
|
+
toolSpend: ToolSpend;
|
|
8
11
|
/** Every stage's result and what it cost, in one place. */
|
|
9
12
|
ledger: Ledger;
|
|
10
13
|
/** Where the run layer says what it is doing. Undefined when nobody listens. */
|
|
@@ -19,6 +22,8 @@ export interface RunState {
|
|
|
19
22
|
unattributed?: string[];
|
|
20
23
|
}
|
|
21
24
|
export declare const newRunState: (currentTask?: string, emit?: Emit, maxRetries?: number) => RunState;
|
|
25
|
+
/** Adds one execution's tool spend to the run's. A retry refunds nothing. */
|
|
26
|
+
export declare function recordToolSpend(state: RunState, spend: ToolSpend): void;
|
|
22
27
|
/**
|
|
23
28
|
* The open task after triage has read the request: the router's summary when it
|
|
24
29
|
* calls the request new, otherwise the task carried in.
|
package/dist/noesis/run/state.js
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
|
+
import { addToolSpend, NO_TOOL_SPEND } from "../execute/spend.js";
|
|
1
2
|
import { newLedger } from "./ledger.js";
|
|
2
3
|
export const newRunState = (currentTask, emit, maxRetries) => ({
|
|
4
|
+
toolSpend: NO_TOOL_SPEND,
|
|
3
5
|
ledger: newLedger(maxRetries, emit),
|
|
4
6
|
emit,
|
|
5
7
|
currentTask,
|
|
6
8
|
});
|
|
9
|
+
export function recordToolSpend(state, spend) {
|
|
10
|
+
state.toolSpend = addToolSpend(state.toolSpend, spend);
|
|
11
|
+
}
|
|
7
12
|
export function nextTask(estimates, current) {
|
|
8
13
|
return estimates.is_new_task ? estimates.summary : (current ?? estimates.summary);
|
|
9
14
|
}
|
|
@@ -7,11 +7,12 @@ import { invoke as reflection } from "../repair/reflection/index.js";
|
|
|
7
7
|
import { blind, compare, render as renderChecks } from "../verify/index.js";
|
|
8
8
|
import { soFar } from "./produce.js";
|
|
9
9
|
import { parseStage, resumeAt } from "./redo.js";
|
|
10
|
+
import { answerOptions, stageOptionsFor } from "./options.js";
|
|
10
11
|
export async function checks(client, llm, opts, state, draft) {
|
|
11
12
|
const drafted = renderDraft(draft);
|
|
12
|
-
const questions = state.ledger.record("5A", await coveQuestions(client, llm, { question: drafted, ...opts.context }, opts
|
|
13
|
+
const questions = state.ledger.record("5A", await coveQuestions(client, llm, { question: drafted, ...opts.context }, stageOptionsFor(opts, "5A")));
|
|
13
14
|
const answers = questions.questions.length
|
|
14
|
-
? state.ledger.record("5B", await coveAnswers(client, llm, { question: blind(questions), ...opts.context }, opts
|
|
15
|
+
? state.ledger.record("5B", await coveAnswers(client, llm, { question: blind(questions), ...opts.context }, stageOptionsFor(opts, "5B")))
|
|
15
16
|
: { answers: [] };
|
|
16
17
|
const verification = compare(questions, answers, opts.verify?.confidenceFloor);
|
|
17
18
|
state.emit?.({
|
|
@@ -34,7 +35,7 @@ export async function checks(client, llm, opts, state, draft) {
|
|
|
34
35
|
return `${drafted}\n\n${renderChecks(verification)}`;
|
|
35
36
|
}
|
|
36
37
|
export async function reflect(client, llm, question, opts, state, gate, reason, hint) {
|
|
37
|
-
const looked = state.ledger.record("R", await reflection(client, llm, { question: `${soFar(state, question)}\n\nThe gate said: ${reason}`, ...opts.context }, opts
|
|
38
|
+
const looked = state.ledger.record("R", await reflection(client, llm, { question: `${soFar(state, question)}\n\nThe gate said: ${reason}`, ...opts.context }, stageOptionsFor(opts, "R")));
|
|
38
39
|
const redoFrom = parseStage(looked.redo_from_stage) ?? parseStage(hint ?? "");
|
|
39
40
|
state.emit?.({
|
|
40
41
|
type: "reflect.pointed",
|
|
@@ -46,7 +47,7 @@ export async function reflect(client, llm, question, opts, state, gate, reason,
|
|
|
46
47
|
return redoFrom;
|
|
47
48
|
}
|
|
48
49
|
export async function compile(client, llm, question, opts, state) {
|
|
49
|
-
const answered = await answer(client, llm, { question, ...state.ledger.artifacts() }, opts
|
|
50
|
+
const answered = await answer(client, llm, { question, ...state.ledger.artifacts() }, answerOptions(opts));
|
|
50
51
|
state.answered = answered;
|
|
51
52
|
const loose = unattributed(answered.answer, state.ledger.byStage().map((one) => one.stage));
|
|
52
53
|
state.unattributed = loose;
|
package/dist/noesis/types.d.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import type { ToolBus } from "@spendgraph/tools";
|
|
2
|
+
import type { ToolSpend } from "./execute/spend.js";
|
|
2
3
|
import type { Asked } from "./context.js";
|
|
3
4
|
import type { NoesisEvent } from "./events.js";
|
|
4
5
|
import type { SubQuestionTool } from "./producers/decompose/index.js";
|
|
5
6
|
import type { AnswerOptions, AnswerResult } from "./producers/toulmin/index.js";
|
|
6
7
|
import type { TriageEstimates, TriageInput, TriageOptions } from "./router/triage/index.js";
|
|
7
|
-
import type { Escalation, StageUsage } from "./run/ledger.js";
|
|
8
|
+
import type { Escalation, StageKey, StageUsage } from "./run/ledger.js";
|
|
8
9
|
import type { StageOptions } from "./stage.js";
|
|
9
10
|
import type { Usage } from "./usage.js";
|
|
10
11
|
/**
|
|
@@ -78,6 +79,13 @@ export interface NoesisOptions {
|
|
|
78
79
|
context?: Omit<Asked, "question">;
|
|
79
80
|
/** Model and params for every stage between triage and the answer. */
|
|
80
81
|
stage?: StageOptions;
|
|
82
|
+
/**
|
|
83
|
+
* Per-stage overrides, keyed by the stage's key, winning over `stage`.
|
|
84
|
+
*
|
|
85
|
+
* `params` is not inherited by a stage that names its own `model`: sampling
|
|
86
|
+
* knobs are per model, and a body written for one is rejected by another.
|
|
87
|
+
*/
|
|
88
|
+
stages?: Partial<Record<StageKey, StageOptions>>;
|
|
81
89
|
/**
|
|
82
90
|
* Reached when a stage wants a person before it can go on.
|
|
83
91
|
*
|
|
@@ -152,13 +160,17 @@ export interface NoesisResult {
|
|
|
152
160
|
* each inside its own output under the stage `4` artifact.
|
|
153
161
|
*/
|
|
154
162
|
usage: RunUsage;
|
|
155
|
-
/** @deprecated Read `usage.inputTokens`. Kept so existing callers still work. */
|
|
156
|
-
inputTokens: number;
|
|
157
|
-
/** @deprecated Read `usage.outputTokens`. Kept so existing callers still work. */
|
|
158
|
-
outputTokens: number;
|
|
159
163
|
latencyMs: number;
|
|
160
164
|
}
|
|
161
165
|
export interface RunUsage extends Usage {
|
|
166
|
+
/**
|
|
167
|
+
* What the tools reported, which the numbers above deliberately exclude.
|
|
168
|
+
*
|
|
169
|
+
* A tool call is somebody else's API at somebody else's rates and is no
|
|
170
|
+
* rollout of ours, so it is never folded into the model spend — but it is
|
|
171
|
+
* real money, so it is reachable rather than buried in an artifact.
|
|
172
|
+
*/
|
|
173
|
+
tools: ToolSpend;
|
|
162
174
|
/** Summed over every time the loop ran each stage. */
|
|
163
175
|
byStage: StageUsage[];
|
|
164
176
|
byModel: Record<string, Usage>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spendgraph/workflows",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Ready-made workflows assembled from the spendgraph packages.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -33,10 +33,10 @@
|
|
|
33
33
|
"README.md"
|
|
34
34
|
],
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@spendgraph/harness": "^0.
|
|
37
|
-
"@spendgraph/llms": "^0.
|
|
38
|
-
"@spendgraph/prompt": "^0.
|
|
39
|
-
"@spendgraph/tools": "^0.
|
|
36
|
+
"@spendgraph/harness": "^0.3.1",
|
|
37
|
+
"@spendgraph/llms": "^0.3.1",
|
|
38
|
+
"@spendgraph/prompt": "^0.3.1",
|
|
39
|
+
"@spendgraph/tools": "^0.3.1"
|
|
40
40
|
},
|
|
41
41
|
"scripts": {
|
|
42
42
|
"build": "tsc -p tsconfig.json --emitDeclarationOnly && tsc -p tsconfig.json --declaration false --removeComments",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"@locusgraph/client": "^0.8.1",
|
|
50
|
-
"@spendgraph/evals": "^0.
|
|
50
|
+
"@spendgraph/evals": "^0.3.1",
|
|
51
51
|
"typescript": "^5"
|
|
52
52
|
},
|
|
53
53
|
"engines": {
|