@spendgraph/workflows 0.2.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.
Files changed (210) hide show
  1. package/README.md +149 -0
  2. package/dist/index.d.ts +1 -0
  3. package/dist/index.js +1 -0
  4. package/dist/noesis/attribution.d.ts +24 -0
  5. package/dist/noesis/attribution.js +43 -0
  6. package/dist/noesis/context.d.ts +18 -0
  7. package/dist/noesis/context.js +16 -0
  8. package/dist/noesis/draft/draft.d.ts +18 -0
  9. package/dist/noesis/draft/draft.js +50 -0
  10. package/dist/noesis/draft/index.d.ts +2 -0
  11. package/dist/noesis/draft/index.js +1 -0
  12. package/dist/noesis/draft/types.d.ts +27 -0
  13. package/dist/noesis/draft/types.js +1 -0
  14. package/dist/noesis/events.d.ts +158 -0
  15. package/dist/noesis/events.js +24 -0
  16. package/dist/noesis/execute/execute.d.ts +22 -0
  17. package/dist/noesis/execute/execute.js +85 -0
  18. package/dist/noesis/execute/index.d.ts +3 -0
  19. package/dist/noesis/execute/index.js +2 -0
  20. package/dist/noesis/execute/order.d.ts +11 -0
  21. package/dist/noesis/execute/order.js +28 -0
  22. package/dist/noesis/execute/types.d.ts +37 -0
  23. package/dist/noesis/execute/types.js +1 -0
  24. package/dist/noesis/gates/closure-checklist/index.d.ts +3 -0
  25. package/dist/noesis/gates/closure-checklist/index.js +2 -0
  26. package/dist/noesis/gates/closure-checklist/invoke.d.ts +5 -0
  27. package/dist/noesis/gates/closure-checklist/invoke.js +6 -0
  28. package/dist/noesis/gates/closure-checklist/prompt.d.ts +5 -0
  29. package/dist/noesis/gates/closure-checklist/prompt.js +32 -0
  30. package/dist/noesis/gates/closure-checklist/types.d.ts +20 -0
  31. package/dist/noesis/gates/closure-checklist/types.js +1 -0
  32. package/dist/noesis/gates/cqot-gate/index.d.ts +3 -0
  33. package/dist/noesis/gates/cqot-gate/index.js +2 -0
  34. package/dist/noesis/gates/cqot-gate/invoke.d.ts +5 -0
  35. package/dist/noesis/gates/cqot-gate/invoke.js +6 -0
  36. package/dist/noesis/gates/cqot-gate/prompt.d.ts +5 -0
  37. package/dist/noesis/gates/cqot-gate/prompt.js +27 -0
  38. package/dist/noesis/gates/cqot-gate/types.d.ts +23 -0
  39. package/dist/noesis/gates/cqot-gate/types.js +1 -0
  40. package/dist/noesis/gates/premortem/index.d.ts +3 -0
  41. package/dist/noesis/gates/premortem/index.js +2 -0
  42. package/dist/noesis/gates/premortem/invoke.d.ts +5 -0
  43. package/dist/noesis/gates/premortem/invoke.js +6 -0
  44. package/dist/noesis/gates/premortem/prompt.d.ts +5 -0
  45. package/dist/noesis/gates/premortem/prompt.js +37 -0
  46. package/dist/noesis/gates/premortem/types.d.ts +28 -0
  47. package/dist/noesis/gates/premortem/types.js +1 -0
  48. package/dist/noesis/gates/red-team/index.d.ts +3 -0
  49. package/dist/noesis/gates/red-team/index.js +2 -0
  50. package/dist/noesis/gates/red-team/invoke.d.ts +5 -0
  51. package/dist/noesis/gates/red-team/invoke.js +6 -0
  52. package/dist/noesis/gates/red-team/prompt.d.ts +5 -0
  53. package/dist/noesis/gates/red-team/prompt.js +25 -0
  54. package/dist/noesis/gates/red-team/types.d.ts +21 -0
  55. package/dist/noesis/gates/red-team/types.js +1 -0
  56. package/dist/noesis/gates/standards-review/index.d.ts +3 -0
  57. package/dist/noesis/gates/standards-review/index.js +2 -0
  58. package/dist/noesis/gates/standards-review/invoke.d.ts +5 -0
  59. package/dist/noesis/gates/standards-review/invoke.js +6 -0
  60. package/dist/noesis/gates/standards-review/prompt.d.ts +5 -0
  61. package/dist/noesis/gates/standards-review/prompt.js +26 -0
  62. package/dist/noesis/gates/standards-review/types.d.ts +20 -0
  63. package/dist/noesis/gates/standards-review/types.js +1 -0
  64. package/dist/noesis/gates/think-checkpoint/index.d.ts +3 -0
  65. package/dist/noesis/gates/think-checkpoint/index.js +2 -0
  66. package/dist/noesis/gates/think-checkpoint/invoke.d.ts +5 -0
  67. package/dist/noesis/gates/think-checkpoint/invoke.js +6 -0
  68. package/dist/noesis/gates/think-checkpoint/prompt.d.ts +5 -0
  69. package/dist/noesis/gates/think-checkpoint/prompt.js +28 -0
  70. package/dist/noesis/gates/think-checkpoint/types.d.ts +21 -0
  71. package/dist/noesis/gates/think-checkpoint/types.js +1 -0
  72. package/dist/noesis/index.d.ts +51 -0
  73. package/dist/noesis/index.js +27 -0
  74. package/dist/noesis/producers/cove-answers/index.d.ts +3 -0
  75. package/dist/noesis/producers/cove-answers/index.js +2 -0
  76. package/dist/noesis/producers/cove-answers/invoke.d.ts +5 -0
  77. package/dist/noesis/producers/cove-answers/invoke.js +6 -0
  78. package/dist/noesis/producers/cove-answers/prompt.d.ts +5 -0
  79. package/dist/noesis/producers/cove-answers/prompt.js +27 -0
  80. package/dist/noesis/producers/cove-answers/types.d.ts +32 -0
  81. package/dist/noesis/producers/cove-answers/types.js +1 -0
  82. package/dist/noesis/producers/cove-questions/index.d.ts +3 -0
  83. package/dist/noesis/producers/cove-questions/index.js +2 -0
  84. package/dist/noesis/producers/cove-questions/invoke.d.ts +5 -0
  85. package/dist/noesis/producers/cove-questions/invoke.js +6 -0
  86. package/dist/noesis/producers/cove-questions/prompt.d.ts +5 -0
  87. package/dist/noesis/producers/cove-questions/prompt.js +26 -0
  88. package/dist/noesis/producers/cove-questions/types.d.ts +17 -0
  89. package/dist/noesis/producers/cove-questions/types.js +1 -0
  90. package/dist/noesis/producers/decompose/index.d.ts +3 -0
  91. package/dist/noesis/producers/decompose/index.js +2 -0
  92. package/dist/noesis/producers/decompose/invoke.d.ts +5 -0
  93. package/dist/noesis/producers/decompose/invoke.js +6 -0
  94. package/dist/noesis/producers/decompose/prompt.d.ts +22 -0
  95. package/dist/noesis/producers/decompose/prompt.js +68 -0
  96. package/dist/noesis/producers/decompose/types.d.ts +41 -0
  97. package/dist/noesis/producers/decompose/types.js +1 -0
  98. package/dist/noesis/producers/discriminating-test/index.d.ts +3 -0
  99. package/dist/noesis/producers/discriminating-test/index.js +2 -0
  100. package/dist/noesis/producers/discriminating-test/invoke.d.ts +5 -0
  101. package/dist/noesis/producers/discriminating-test/invoke.js +6 -0
  102. package/dist/noesis/producers/discriminating-test/prompt.d.ts +5 -0
  103. package/dist/noesis/producers/discriminating-test/prompt.js +67 -0
  104. package/dist/noesis/producers/discriminating-test/types.d.ts +32 -0
  105. package/dist/noesis/producers/discriminating-test/types.js +1 -0
  106. package/dist/noesis/producers/plan-candidates/index.d.ts +3 -0
  107. package/dist/noesis/producers/plan-candidates/index.js +2 -0
  108. package/dist/noesis/producers/plan-candidates/invoke.d.ts +5 -0
  109. package/dist/noesis/producers/plan-candidates/invoke.js +6 -0
  110. package/dist/noesis/producers/plan-candidates/prompt.d.ts +5 -0
  111. package/dist/noesis/producers/plan-candidates/prompt.js +46 -0
  112. package/dist/noesis/producers/plan-candidates/types.d.ts +32 -0
  113. package/dist/noesis/producers/plan-candidates/types.js +1 -0
  114. package/dist/noesis/producers/qn-brief/index.d.ts +3 -0
  115. package/dist/noesis/producers/qn-brief/index.js +2 -0
  116. package/dist/noesis/producers/qn-brief/invoke.d.ts +5 -0
  117. package/dist/noesis/producers/qn-brief/invoke.js +6 -0
  118. package/dist/noesis/producers/qn-brief/prompt.d.ts +12 -0
  119. package/dist/noesis/producers/qn-brief/prompt.js +65 -0
  120. package/dist/noesis/producers/qn-brief/types.d.ts +42 -0
  121. package/dist/noesis/producers/qn-brief/types.js +1 -0
  122. package/dist/noesis/producers/toulmin/answer.d.ts +18 -0
  123. package/dist/noesis/producers/toulmin/answer.js +87 -0
  124. package/dist/noesis/producers/toulmin/index.d.ts +5 -0
  125. package/dist/noesis/producers/toulmin/index.js +4 -0
  126. package/dist/noesis/producers/toulmin/invoke.d.ts +18 -0
  127. package/dist/noesis/producers/toulmin/invoke.js +53 -0
  128. package/dist/noesis/producers/toulmin/prompt.d.ts +12 -0
  129. package/dist/noesis/producers/toulmin/prompt.js +60 -0
  130. package/dist/noesis/producers/toulmin/render.d.ts +14 -0
  131. package/dist/noesis/producers/toulmin/render.js +82 -0
  132. package/dist/noesis/producers/toulmin/types.d.ts +126 -0
  133. package/dist/noesis/producers/toulmin/types.js +1 -0
  134. package/dist/noesis/question.d.ts +24 -0
  135. package/dist/noesis/question.js +37 -0
  136. package/dist/noesis/repair/reflection/index.d.ts +3 -0
  137. package/dist/noesis/repair/reflection/index.js +2 -0
  138. package/dist/noesis/repair/reflection/invoke.d.ts +5 -0
  139. package/dist/noesis/repair/reflection/invoke.js +6 -0
  140. package/dist/noesis/repair/reflection/prompt.d.ts +5 -0
  141. package/dist/noesis/repair/reflection/prompt.js +26 -0
  142. package/dist/noesis/repair/reflection/types.d.ts +17 -0
  143. package/dist/noesis/repair/reflection/types.js +1 -0
  144. package/dist/noesis/retry.d.ts +25 -0
  145. package/dist/noesis/retry.js +60 -0
  146. package/dist/noesis/route.d.ts +29 -0
  147. package/dist/noesis/route.js +69 -0
  148. package/dist/noesis/router/triage/index.d.ts +3 -0
  149. package/dist/noesis/router/triage/index.js +2 -0
  150. package/dist/noesis/router/triage/invoke.d.ts +31 -0
  151. package/dist/noesis/router/triage/invoke.js +76 -0
  152. package/dist/noesis/router/triage/prompt.d.ts +11 -0
  153. package/dist/noesis/router/triage/prompt.js +34 -0
  154. package/dist/noesis/router/triage/types.d.ts +70 -0
  155. package/dist/noesis/router/triage/types.js +1 -0
  156. package/dist/noesis/run/artifacts.d.ts +12 -0
  157. package/dist/noesis/run/artifacts.js +31 -0
  158. package/dist/noesis/run/errors.d.ts +38 -0
  159. package/dist/noesis/run/errors.js +55 -0
  160. package/dist/noesis/run/index.d.ts +6 -0
  161. package/dist/noesis/run/index.js +4 -0
  162. package/dist/noesis/run/ledger.d.ts +110 -0
  163. package/dist/noesis/run/ledger.js +184 -0
  164. package/dist/noesis/run/loop.d.ts +15 -0
  165. package/dist/noesis/run/loop.js +91 -0
  166. package/dist/noesis/run/plan.d.ts +18 -0
  167. package/dist/noesis/run/plan.js +39 -0
  168. package/dist/noesis/run/produce.d.ts +50 -0
  169. package/dist/noesis/run/produce.js +114 -0
  170. package/dist/noesis/run/redo.d.ts +24 -0
  171. package/dist/noesis/run/redo.js +41 -0
  172. package/dist/noesis/run/result.d.ts +5 -0
  173. package/dist/noesis/run/result.js +51 -0
  174. package/dist/noesis/run/run.d.ts +4 -0
  175. package/dist/noesis/run/run.js +73 -0
  176. package/dist/noesis/run/stages/classify.d.ts +7 -0
  177. package/dist/noesis/run/stages/classify.js +23 -0
  178. package/dist/noesis/run/stages/direct.d.ts +6 -0
  179. package/dist/noesis/run/stages/direct.js +8 -0
  180. package/dist/noesis/run/stages/full.d.ts +14 -0
  181. package/dist/noesis/run/stages/full.js +21 -0
  182. package/dist/noesis/run/stages/index.d.ts +5 -0
  183. package/dist/noesis/run/stages/index.js +5 -0
  184. package/dist/noesis/run/stages/retrieve.d.ts +13 -0
  185. package/dist/noesis/run/stages/retrieve.js +38 -0
  186. package/dist/noesis/run/stages/standard.d.ts +12 -0
  187. package/dist/noesis/run/stages/standard.js +19 -0
  188. package/dist/noesis/run/state.d.ts +26 -0
  189. package/dist/noesis/run/state.js +13 -0
  190. package/dist/noesis/run/stream.d.ts +28 -0
  191. package/dist/noesis/run/stream.js +33 -0
  192. package/dist/noesis/run/verify.d.ts +27 -0
  193. package/dist/noesis/run/verify.js +78 -0
  194. package/dist/noesis/slugs.d.ts +16 -0
  195. package/dist/noesis/slugs.js +46 -0
  196. package/dist/noesis/stage.d.ts +56 -0
  197. package/dist/noesis/stage.js +52 -0
  198. package/dist/noesis/started.d.ts +18 -0
  199. package/dist/noesis/started.js +39 -0
  200. package/dist/noesis/types.d.ts +167 -0
  201. package/dist/noesis/types.js +1 -0
  202. package/dist/noesis/usage.d.ts +49 -0
  203. package/dist/noesis/usage.js +56 -0
  204. package/dist/noesis/verify/index.d.ts +2 -0
  205. package/dist/noesis/verify/index.js +1 -0
  206. package/dist/noesis/verify/types.d.ts +54 -0
  207. package/dist/noesis/verify/types.js +1 -0
  208. package/dist/noesis/verify/verify.d.ts +36 -0
  209. package/dist/noesis/verify/verify.js +94 -0
  210. package/package.json +59 -0
@@ -0,0 +1,46 @@
1
+ /** Stage 3A. Pulled by slug, so an edit needs no release here. */
2
+ export const PLAN_SLUG = "ctr-3a-plan-candidates-898095";
3
+ /** What the reply must be, as the prompt itself specifies it. */
4
+ export const PLAN_SCHEMA = {
5
+ type: "object",
6
+ properties: {
7
+ candidates: {
8
+ type: "array",
9
+ items: {
10
+ type: "object",
11
+ properties: {
12
+ id: { type: "string" },
13
+ approach: { type: "string" },
14
+ strongest_reason: { type: "string" },
15
+ fatal_risk: { type: "string" },
16
+ cost: { type: "string" },
17
+ },
18
+ required: ["id", "approach", "strongest_reason", "fatal_risk", "cost"],
19
+ },
20
+ },
21
+ chosen: { type: "string" },
22
+ rationale: { type: "string" },
23
+ switch_trigger: {
24
+ type: "object",
25
+ properties: {
26
+ observation: { type: "string" },
27
+ switch_to: { type: "string" },
28
+ },
29
+ required: ["observation", "switch_to"],
30
+ },
31
+ steps: {
32
+ type: "array",
33
+ items: {
34
+ type: "object",
35
+ properties: {
36
+ n: { type: "number" },
37
+ action: { type: "string" },
38
+ predicted_outcome: { type: "string" },
39
+ tolerance: { type: "string" },
40
+ },
41
+ required: ["n", "action", "predicted_outcome", "tolerance"],
42
+ },
43
+ },
44
+ },
45
+ required: ["candidates", "chosen", "rationale", "switch_trigger", "steps"],
46
+ };
@@ -0,0 +1,32 @@
1
+ import type { Asked } from "../../context.js";
2
+ import type { StageOptions, StageOutcome } from "../../stage.js";
3
+ /** One way the question could be approached. */
4
+ export interface PlanCandidate {
5
+ id: string;
6
+ approach: string;
7
+ strongest_reason: string;
8
+ fatal_risk: string;
9
+ cost: string;
10
+ }
11
+ /** What would make the chosen approach the wrong one, and what to move to. */
12
+ export interface SwitchTrigger {
13
+ observation: string;
14
+ switch_to: string;
15
+ }
16
+ export interface PlanStep {
17
+ n: number;
18
+ action: string;
19
+ predicted_outcome: string;
20
+ /** How far the actual result may differ before the loop must re-plan. */
21
+ tolerance: string;
22
+ }
23
+ export interface PlanCandidates {
24
+ candidates: PlanCandidate[];
25
+ chosen: string;
26
+ rationale: string;
27
+ switch_trigger: SwitchTrigger;
28
+ steps: PlanStep[];
29
+ }
30
+ export type PlanCandidatesInput = Asked;
31
+ export type PlanCandidatesOptions = StageOptions;
32
+ export type PlanCandidatesResult = StageOutcome<PlanCandidates>;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,3 @@
1
+ export { invoke } from "./invoke.js";
2
+ export { BRIEF_SCHEMA, BRIEF_SLUG } from "./prompt.js";
3
+ export type { BriefInput, BriefOptions, BriefResult, KeyConcept, QuestionBrief, Viewpoint, } from "./types.js";
@@ -0,0 +1,2 @@
1
+ export { invoke } from "./invoke.js";
2
+ export { BRIEF_SCHEMA, BRIEF_SLUG } from "./prompt.js";
@@ -0,0 +1,5 @@
1
+ import type { Llm } from "@spendgraph/llms";
2
+ import type { PromptClient } from "@spendgraph/prompt";
3
+ import type { BriefInput, BriefOptions, BriefResult } from "./types.js";
4
+ /** Stage 1. Interrogates the question and hands back the brief, never an answer. */
5
+ export declare function invoke(client: PromptClient, llm: Llm, input: BriefInput, opts?: BriefOptions): Promise<BriefResult>;
@@ -0,0 +1,6 @@
1
+ import { runStage } from "../../stage.js";
2
+ import { BRIEF_SCHEMA, BRIEF_SLUG } from "./prompt.js";
3
+ /** Stage 1. Interrogates the question and hands back the brief, never an answer. */
4
+ export function invoke(client, llm, input, opts = {}) {
5
+ return runStage(client, llm, BRIEF_SLUG, BRIEF_SCHEMA, input, opts);
6
+ }
@@ -0,0 +1,12 @@
1
+ import type { JsonSchema } from "@spendgraph/llms";
2
+ /** The stored question-brief prompt. Pulled by slug, so an edit needs no release here. */
3
+ export declare const BRIEF_SLUG = "ctr-1-question-brief-f2w9fd";
4
+ /**
5
+ * What the reply must be, as the prompt itself specifies it.
6
+ *
7
+ * Every key is required, including the two that are usually null: the prompt
8
+ * returns `better_question` and `interpretation` as null rather than omitting
9
+ * them, and a missing key would read downstream as "nothing was ambiguous"
10
+ * instead of "the stage did not say".
11
+ */
12
+ export declare const BRIEF_SCHEMA: JsonSchema;
@@ -0,0 +1,65 @@
1
+ /** The stored question-brief prompt. Pulled by slug, so an edit needs no release here. */
2
+ export const BRIEF_SLUG = "ctr-1-question-brief-f2w9fd";
3
+ const STRINGS = { type: "array", items: { type: "string" } };
4
+ const NULLABLE_STRING = { type: ["string", "null"] };
5
+ /**
6
+ * What the reply must be, as the prompt itself specifies it.
7
+ *
8
+ * Every key is required, including the two that are usually null: the prompt
9
+ * returns `better_question` and `interpretation` as null rather than omitting
10
+ * them, and a missing key would read downstream as "nothing was ambiguous"
11
+ * instead of "the stage did not say".
12
+ */
13
+ export const BRIEF_SCHEMA = {
14
+ type: "object",
15
+ properties: {
16
+ purpose: { type: "string" },
17
+ restated_question: { type: "string" },
18
+ restatement_differs: { type: "boolean" },
19
+ information_given: STRINGS,
20
+ information_needed: STRINGS,
21
+ key_concepts: {
22
+ type: "array",
23
+ items: {
24
+ type: "object",
25
+ properties: {
26
+ term: { type: "string" },
27
+ definition: { type: "string" },
28
+ },
29
+ required: ["term", "definition"],
30
+ },
31
+ },
32
+ assumptions: STRINGS,
33
+ viewpoints: {
34
+ type: "array",
35
+ items: {
36
+ type: "object",
37
+ properties: {
38
+ stakeholder: { type: "string" },
39
+ position: { type: "string" },
40
+ },
41
+ required: ["stakeholder", "position"],
42
+ },
43
+ },
44
+ implications_if_wrong: { type: "string" },
45
+ is_right_question: { type: "boolean" },
46
+ better_question: NULLABLE_STRING,
47
+ interpretation: NULLABLE_STRING,
48
+ ruled_out: STRINGS,
49
+ },
50
+ required: [
51
+ "purpose",
52
+ "restated_question",
53
+ "restatement_differs",
54
+ "information_given",
55
+ "information_needed",
56
+ "key_concepts",
57
+ "assumptions",
58
+ "viewpoints",
59
+ "implications_if_wrong",
60
+ "is_right_question",
61
+ "better_question",
62
+ "interpretation",
63
+ "ruled_out",
64
+ ],
65
+ };
@@ -0,0 +1,42 @@
1
+ import type { Asked } from "../../context.js";
2
+ import type { StageOptions, StageOutcome } from "../../stage.js";
3
+ /** A term whose definition changes the answer. */
4
+ export interface KeyConcept {
5
+ term: string;
6
+ definition: string;
7
+ }
8
+ /** Whose interests the answer touches, and where they pull against each other. */
9
+ export interface Viewpoint {
10
+ stakeholder: string;
11
+ position: string;
12
+ }
13
+ /**
14
+ * The brief every later stage reads before it works.
15
+ *
16
+ * Every slot is filled or the stage has failed: the prompt is told an empty slot
17
+ * is a finding rather than a blank, so `information_needed` naming a gap is the
18
+ * answer, not the absence of one.
19
+ */
20
+ export interface QuestionBrief {
21
+ purpose: string;
22
+ restated_question: string;
23
+ /** True where the restatement changed the meaning, not merely the wording. */
24
+ restatement_differs: boolean;
25
+ information_given: string[];
26
+ /** Each item is a research subtask for the stages downstream. */
27
+ information_needed: string[];
28
+ key_concepts: KeyConcept[];
29
+ assumptions: string[];
30
+ viewpoints: Viewpoint[];
31
+ implications_if_wrong: string;
32
+ /** False where a different question would serve the purpose better. */
33
+ is_right_question: boolean;
34
+ /** The replacement question. Null while `is_right_question` holds. */
35
+ better_question: string | null;
36
+ /** The one reading being proceeded under. Null where nothing was ambiguous. */
37
+ interpretation: string | null;
38
+ ruled_out: string[];
39
+ }
40
+ export type BriefInput = Asked;
41
+ export type BriefOptions = StageOptions;
42
+ export type BriefResult = StageOutcome<QuestionBrief>;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,18 @@
1
+ import type { Llm } from "@spendgraph/llms";
2
+ import type { PromptClient } from "@spendgraph/prompt";
3
+ import type { AnswerOptions, AnswerResult, Artifacts } from "./types.js";
4
+ /** Every tier fell over, so there is no answer to render. */
5
+ export declare class NoAnswerError extends Error {
6
+ readonly escalated: string[];
7
+ constructor(escalated: string[]);
8
+ }
9
+ /**
10
+ * Stage 6C, cheapest model first.
11
+ *
12
+ * A tier is accepted when it committed to a claim and its own calibrated
13
+ * confidence clears the bar; an abstention escalates whatever the bar is,
14
+ * because a stage that could not answer has not answered. Nothing is rewritten
15
+ * on the way up — each rung answers from the artifacts again, so an escalation
16
+ * is a second opinion rather than a polish of the first.
17
+ */
18
+ export declare function answer(client: PromptClient, llm: Llm, artifacts: Artifacts, opts?: AnswerOptions): Promise<AnswerResult>;
@@ -0,0 +1,87 @@
1
+ import { cascade } from "@spendgraph/harness";
2
+ import { addUsage, totalCost, NO_USAGE } from "../../usage.js";
3
+ import { invoke } from "./invoke.js";
4
+ import { render } from "./render.js";
5
+ const DEFAULT_MIN_CONFIDENCE = 0.6;
6
+ /**
7
+ * The tiers to run, when the caller named none.
8
+ *
9
+ * One tier, and it is the model the `Llm` was built with. A default list of
10
+ * model ids chosen here would answer on a model the caller never picked, and on
11
+ * a provider that has never heard of those ids it fails outright — at the last
12
+ * stage, after every earlier one has been paid for.
13
+ *
14
+ * So a cascade is something you ask for. Naming two tiers is what buys the
15
+ * second opinion; naming none gets the model already configured, once.
16
+ */
17
+ const tiersFor = (llm, named) => named?.length ? named : [{ model: llm.model }];
18
+ /** Every tier fell over, so there is no answer to render. */
19
+ export class NoAnswerError extends Error {
20
+ escalated;
21
+ constructor(escalated) {
22
+ super(`No tier produced an answer (tried ${escalated.join(", ") || "none"}).`);
23
+ this.escalated = escalated;
24
+ this.name = "NoAnswerError";
25
+ }
26
+ }
27
+ /**
28
+ * Stage 6C, cheapest model first.
29
+ *
30
+ * A tier is accepted when it committed to a claim and its own calibrated
31
+ * confidence clears the bar; an abstention escalates whatever the bar is,
32
+ * because a stage that could not answer has not answered. Nothing is rewritten
33
+ * on the way up — each rung answers from the artifacts again, so an escalation
34
+ * is a second opinion rather than a polish of the first.
35
+ */
36
+ export async function answer(client, llm, artifacts, opts = {}) {
37
+ const tiers = tiersFor(llm, opts.tiers);
38
+ const floor = opts.minConfidence ?? DEFAULT_MIN_CONFIDENCE;
39
+ const rolloutIds = [];
40
+ const pricings = [];
41
+ let usage = NO_USAGE;
42
+ const result = await cascade({}, {
43
+ maxTokens: opts.maxTokens,
44
+ tiers: tiers.map((tier) => ({
45
+ name: tier.model,
46
+ run: async () => {
47
+ const reply = await invoke(client, llm, artifacts, {
48
+ model: tier.model,
49
+ params: tier.params,
50
+ emit: opts.emit,
51
+ attempts: opts.attempts,
52
+ });
53
+ rolloutIds.push(reply.rolloutId);
54
+ usage = addUsage(usage, reply);
55
+ if (reply.pricing)
56
+ pricings.push(reply.pricing);
57
+ return {
58
+ output: JSON.stringify(reply.answer),
59
+ model: reply.model,
60
+ inputTokens: reply.inputTokens,
61
+ outputTokens: reply.outputTokens,
62
+ data: reply.answer,
63
+ };
64
+ },
65
+ })),
66
+ accept: (produced) => {
67
+ const toulmin = produced.data;
68
+ if (!toulmin || toulmin.abstained)
69
+ return false;
70
+ return (toulmin.qualifier?.confidence ?? 0) >= floor;
71
+ },
72
+ });
73
+ const toulmin = result.data;
74
+ if (!toulmin)
75
+ throw new NoAnswerError(result.escalated);
76
+ return {
77
+ ...usage,
78
+ ...(pricings.length ? { pricing: Promise.all(pricings).then(totalCost) } : {}),
79
+ text: render(toulmin, opts),
80
+ answer: toulmin,
81
+ model: result.tier ?? "unknown",
82
+ accepted: result.accepted,
83
+ escalated: result.escalated,
84
+ rolloutIds,
85
+ latencyMs: result.latencyMs,
86
+ };
87
+ }
@@ -0,0 +1,5 @@
1
+ export { answer, NoAnswerError } from "./answer.js";
2
+ export { invoke, ToulminReplyError } from "./invoke.js";
3
+ export { TOULMIN_SCHEMA, TOULMIN_SLUG } from "./prompt.js";
4
+ export { render } from "./render.js";
5
+ export type { AnswerOptions, AnswerResult, AnswerTier, Artifacts, Ground, Qualifier, Rebuttal, ToulminAnswer, ToulminOptions, ToulminResult, } from "./types.js";
@@ -0,0 +1,4 @@
1
+ export { answer, NoAnswerError } from "./answer.js";
2
+ export { invoke, ToulminReplyError } from "./invoke.js";
3
+ export { TOULMIN_SCHEMA, TOULMIN_SLUG } from "./prompt.js";
4
+ export { render } from "./render.js";
@@ -0,0 +1,18 @@
1
+ import type { Llm } from "@spendgraph/llms";
2
+ import type { PromptClient } from "@spendgraph/prompt";
3
+ import type { Artifacts, ToulminOptions, ToulminResult } from "./types.js";
4
+ /** The reply came back, but not as the object the stage is supposed to return. */
5
+ export declare class ToulminReplyError extends Error {
6
+ readonly raw: string;
7
+ constructor(raw: string);
8
+ }
9
+ /**
10
+ * Stage 6C. Compiles the artifacts into the final answer and its qualifier.
11
+ *
12
+ * The artifacts are the request, so they go through `{question}` like every
13
+ * other stage's. They used to arrive as a history turn instead, which worked
14
+ * only while the prompt's question box was empty: `renderMessages` puts history
15
+ * between the system turn and the question, so anything saved in that box would
16
+ * have been the last thing the model read.
17
+ */
18
+ export declare function invoke(client: PromptClient, llm: Llm, artifacts: Artifacts, opts?: ToulminOptions): Promise<ToulminResult>;
@@ -0,0 +1,53 @@
1
+ import { askThrough, QUESTION_FIELD } from "../../question.js";
2
+ import { attempting } from "../../retry.js";
3
+ import { announce, failed } from "../../started.js";
4
+ import { usageOf } from "../../usage.js";
5
+ import { TOULMIN_SCHEMA, TOULMIN_SLUG } from "./prompt.js";
6
+ /** The reply came back, but not as the object the stage is supposed to return. */
7
+ export class ToulminReplyError extends Error {
8
+ raw;
9
+ constructor(raw) {
10
+ super("Toulmin answer returned no object matching its schema.");
11
+ this.raw = raw;
12
+ this.name = "ToulminReplyError";
13
+ }
14
+ }
15
+ function asText(artifacts) {
16
+ return typeof artifacts === "string" ? artifacts : JSON.stringify(artifacts, null, 2);
17
+ }
18
+ /**
19
+ * Stage 6C. Compiles the artifacts into the final answer and its qualifier.
20
+ *
21
+ * The artifacts are the request, so they go through `{question}` like every
22
+ * other stage's. They used to arrive as a history turn instead, which worked
23
+ * only while the prompt's question box was empty: `renderMessages` puts history
24
+ * between the system turn and the question, so anything saved in that box would
25
+ * have been the last thing the model read.
26
+ */
27
+ export async function invoke(client, llm, artifacts, opts = {}) {
28
+ announce(opts.emit, TOULMIN_SLUG, opts.model);
29
+ return attempting(async () => {
30
+ const prompt = await client.pull(TOULMIN_SLUG);
31
+ let reply;
32
+ const question = asText(artifacts);
33
+ const outcome = await prompt.call({ [QUESTION_FIELD]: question }, async ({ messages }) => {
34
+ reply = await llm.call(askThrough(messages, question, TOULMIN_SLUG), {
35
+ model: opts.model,
36
+ schema: TOULMIN_SCHEMA,
37
+ trace: false,
38
+ params: opts.params,
39
+ });
40
+ return reply;
41
+ });
42
+ if (reply?.data === undefined)
43
+ throw new ToulminReplyError(outcome.output);
44
+ return {
45
+ ...usageOf(outcome),
46
+ ...(outcome.pricing ? { pricing: outcome.pricing } : {}),
47
+ answer: reply.data,
48
+ rolloutId: outcome.rolloutId,
49
+ model: outcome.model,
50
+ latencyMs: reply.latencyMs ?? 0,
51
+ };
52
+ }, { attempts: opts.attempts, onFailure: (err) => failed(opts.emit, TOULMIN_SLUG, err) });
53
+ }
@@ -0,0 +1,12 @@
1
+ import type { JsonSchema } from "@spendgraph/llms";
2
+ /** The stored answer-assembly prompt. Pulled by slug, so an edit needs no release here. */
3
+ export declare const TOULMIN_SLUG = "ctr-6c-toulmin-answer-d28bb8";
4
+ /**
5
+ * What the reply must be, as the prompt itself specifies it.
6
+ *
7
+ * `rebuttals` and `would_change_my_mind` are required rather than optional on
8
+ * purpose: an empty list is a claim the stage may make, but a missing key would
9
+ * let a surviving objection be dropped silently, which is what this stage
10
+ * exists to prevent.
11
+ */
12
+ export declare const TOULMIN_SCHEMA: JsonSchema;
@@ -0,0 +1,60 @@
1
+ /** The stored answer-assembly prompt. Pulled by slug, so an edit needs no release here. */
2
+ export const TOULMIN_SLUG = "ctr-6c-toulmin-answer-d28bb8";
3
+ const UNIT = { type: "number", minimum: 0, maximum: 1 };
4
+ /**
5
+ * What the reply must be, as the prompt itself specifies it.
6
+ *
7
+ * `rebuttals` and `would_change_my_mind` are required rather than optional on
8
+ * purpose: an empty list is a claim the stage may make, but a missing key would
9
+ * let a surviving objection be dropped silently, which is what this stage
10
+ * exists to prevent.
11
+ */
12
+ export const TOULMIN_SCHEMA = {
13
+ type: "object",
14
+ properties: {
15
+ claim: { type: "string" },
16
+ grounds: {
17
+ type: "array",
18
+ items: {
19
+ type: "object",
20
+ properties: {
21
+ evidence: { type: "string" },
22
+ source_stage: { type: "string" },
23
+ },
24
+ required: ["evidence", "source_stage"],
25
+ },
26
+ },
27
+ warrant: { type: "string" },
28
+ qualifier: {
29
+ type: "object",
30
+ properties: {
31
+ confidence: UNIT,
32
+ holds_when: { type: "string" },
33
+ does_not_apply_to: { type: "string" },
34
+ },
35
+ required: ["confidence", "holds_when", "does_not_apply_to"],
36
+ },
37
+ rebuttals: {
38
+ type: "array",
39
+ items: {
40
+ type: "object",
41
+ properties: {
42
+ objection: { type: "string" },
43
+ defeats_claim_when: { type: "string" },
44
+ },
45
+ required: ["objection", "defeats_claim_when"],
46
+ },
47
+ },
48
+ would_change_my_mind: { type: "string" },
49
+ abstained: { type: "boolean" },
50
+ },
51
+ required: [
52
+ "claim",
53
+ "grounds",
54
+ "warrant",
55
+ "qualifier",
56
+ "rebuttals",
57
+ "would_change_my_mind",
58
+ "abstained",
59
+ ],
60
+ };
@@ -0,0 +1,14 @@
1
+ import type { AnswerOptions, ToulminAnswer } from "./types.js";
2
+ /**
3
+ * The answer as a person reads it.
4
+ *
5
+ * Pure, and deliberately not a seventh prompt. Every part of a `ToulminAnswer`
6
+ * is something the stage was forbidden to soften — a surviving objection, a
7
+ * calibrated number, an abstention — and handing the object to a model to write
8
+ * up returns each of those chances on the last call before anyone sees it.
9
+ *
10
+ * Objections are never dropped. Where something has to give, grounds go first,
11
+ * which is why they are off by default: they are the audit trail, and the
12
+ * rollout already has them.
13
+ */
14
+ export declare function render(answer: ToulminAnswer, opts?: AnswerOptions): string;
@@ -0,0 +1,82 @@
1
+ function confidencePhrase(confidence) {
2
+ if (confidence >= 0.9)
3
+ return "very high";
4
+ if (confidence >= 0.75)
5
+ return "high";
6
+ if (confidence >= 0.5)
7
+ return "moderate";
8
+ if (confidence >= 0.25)
9
+ return "low";
10
+ return "very low";
11
+ }
12
+ /**
13
+ * Whether the model actually said this.
14
+ *
15
+ * Takes `string | undefined` although `ToulminAnswer` types every field as a
16
+ * string, because nothing enforces that: the schema's `required` list is sent
17
+ * to the provider and never checked on the way back, so a field the model
18
+ * omitted arrives as `undefined` and reads here as unsaid.
19
+ */
20
+ function said(value) {
21
+ return !!value && value.trim().length > 0;
22
+ }
23
+ /**
24
+ * The model writes these as sentences, and the label already supplies the
25
+ * conjunction — without this they read "holds when when the trace shows".
26
+ */
27
+ function clause(text) {
28
+ return text.trim().replace(/^(when|if)\s+/i, "");
29
+ }
30
+ function qualifierLine(answer) {
31
+ const { confidence, holds_when, does_not_apply_to } = answer.qualifier ?? {};
32
+ const parts = typeof confidence === "number"
33
+ ? [`Confidence: ${confidencePhrase(confidence)} (${confidence.toFixed(2)}).`]
34
+ : [];
35
+ if (said(holds_when))
36
+ parts.push(`Holds when ${clause(holds_when)}.`);
37
+ if (said(does_not_apply_to))
38
+ parts.push(`Does not apply to ${clause(does_not_apply_to)}.`);
39
+ return parts.join(" ");
40
+ }
41
+ function rebuttalLines(rebuttals) {
42
+ const lines = (rebuttals ?? [])
43
+ .filter((r) => said(r.objection))
44
+ .map((r) => said(r.defeats_claim_when)
45
+ ? `- ${r.objection} — defeats this when ${clause(r.defeats_claim_when)}`
46
+ : `- ${r.objection}`);
47
+ return lines.length > 0 ? `Objections that stand:\n${lines.join("\n")}` : "";
48
+ }
49
+ function groundLines(answer) {
50
+ const lines = (answer.grounds ?? [])
51
+ .filter((g) => said(g.evidence))
52
+ .map((g) => (said(g.source_stage) ? `- ${g.evidence} (${g.source_stage})` : `- ${g.evidence}`));
53
+ const grounds = lines.length > 0 ? `Grounds:\n${lines.join("\n")}` : "";
54
+ const warrant = said(answer.warrant) ? `Warrant: ${answer.warrant}` : "";
55
+ return [grounds, warrant].filter(said).join("\n\n");
56
+ }
57
+ /**
58
+ * The answer as a person reads it.
59
+ *
60
+ * Pure, and deliberately not a seventh prompt. Every part of a `ToulminAnswer`
61
+ * is something the stage was forbidden to soften — a surviving objection, a
62
+ * calibrated number, an abstention — and handing the object to a model to write
63
+ * up returns each of those chances on the last call before anyone sees it.
64
+ *
65
+ * Objections are never dropped. Where something has to give, grounds go first,
66
+ * which is why they are off by default: they are the audit trail, and the
67
+ * rollout already has them.
68
+ */
69
+ export function render(answer, opts = {}) {
70
+ const rebuttals = rebuttalLines(answer.rebuttals);
71
+ const changes = said(answer.would_change_my_mind)
72
+ ? `What would change this: ${answer.would_change_my_mind}`
73
+ : "";
74
+ if (answer.abstained) {
75
+ return ["Not enough to answer.", answer.claim, rebuttals, changes].filter(said).join("\n\n");
76
+ }
77
+ const sections = [answer.claim, qualifierLine(answer), rebuttals];
78
+ if (opts.showGrounds)
79
+ sections.push(groundLines(answer));
80
+ sections.push(changes);
81
+ return sections.filter(said).join("\n\n");
82
+ }