@loomwork/reasoning 0.0.0-stage → 0.1.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/LICENSE +202 -0
- package/NOTICE +8 -0
- package/README.md +82 -2
- package/dist/ReasoningRuntime.d.ts +22 -0
- package/dist/ReasoningRuntime.d.ts.map +1 -0
- package/dist/ReasoningRuntime.js +200 -0
- package/dist/ReasoningRuntime.js.map +1 -0
- package/dist/confidence.d.ts +5 -0
- package/dist/confidence.d.ts.map +1 -0
- package/dist/confidence.js +38 -0
- package/dist/confidence.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/router.d.ts +39 -0
- package/dist/router.d.ts.map +1 -0
- package/dist/router.js +173 -0
- package/dist/router.js.map +1 -0
- package/dist/routing.bench.d.ts +2 -0
- package/dist/routing.bench.d.ts.map +1 -0
- package/dist/routing.bench.js +48 -0
- package/dist/routing.bench.js.map +1 -0
- package/dist/types.d.ts +134 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/package.json +54 -3
- package/src/ReasoningRuntime.ts +244 -0
- package/src/confidence.ts +37 -0
- package/src/index.ts +24 -0
- package/src/router.ts +225 -0
- package/src/routing.bench.ts +54 -0
- package/src/types.ts +178 -0
package/src/types.ts
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ContentResolveContext,
|
|
3
|
+
ContentResolver,
|
|
4
|
+
LlmContent,
|
|
5
|
+
LlmMessageInput,
|
|
6
|
+
LlmProvider,
|
|
7
|
+
LlmResponse,
|
|
8
|
+
LlmToolCall,
|
|
9
|
+
LlmToolSpecInput,
|
|
10
|
+
} from "@loomwork/llm";
|
|
11
|
+
|
|
12
|
+
// A decision sink shaped exactly like `ExecutionContext.decide` (minus opts). When a
|
|
13
|
+
// flow passes `ctx.decide`, each route/elevation becomes a durable DECISION_RECORDED
|
|
14
|
+
// event that replay serves back (P9); standalone callers get an identity decide.
|
|
15
|
+
export type DecideFn = <T = unknown>(
|
|
16
|
+
decisionId: string,
|
|
17
|
+
decider: () => T | Promise<T>
|
|
18
|
+
) => Promise<T>;
|
|
19
|
+
|
|
20
|
+
// A named model endpoint in the routing registry. `tier` orders the elevation
|
|
21
|
+
// ladder (lower = cheaper/weaker, e.g. Gemma=0 → Flash=1 → Pro=2 → Opus=3).
|
|
22
|
+
export interface ModelRoute {
|
|
23
|
+
id: string;
|
|
24
|
+
provider: LlmProvider;
|
|
25
|
+
tier: number;
|
|
26
|
+
/** Strategy hints (e.g. "planning", "coding", "review") for tag-based routing. */
|
|
27
|
+
tags?: string[];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface ReasoningRequest {
|
|
31
|
+
// The INPUT side of the prompt boundary: content may still carry taint, because
|
|
32
|
+
// `ReasoningRuntime.call` runs `guardRequest` before any provider sees it (R2).
|
|
33
|
+
// Typing these as plain strings was a claim the runtime never checked — and a
|
|
34
|
+
// tainted MCP tool description arrives through exactly such a field.
|
|
35
|
+
messages: LlmMessageInput[];
|
|
36
|
+
system?: LlmContent;
|
|
37
|
+
/** Picks a route by tag and namespaces the logged decisions. */
|
|
38
|
+
strategy?: string;
|
|
39
|
+
tools?: LlmToolSpecInput[];
|
|
40
|
+
maxTokens?: number;
|
|
41
|
+
temperature?: number;
|
|
42
|
+
signal?: AbortSignal;
|
|
43
|
+
/** Budget cap — never elevate above this tier. Default: the highest tier available. */
|
|
44
|
+
maxTier?: number;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface ReasoningTraceEntry {
|
|
48
|
+
modelId: string;
|
|
49
|
+
tier: number;
|
|
50
|
+
confidence: number;
|
|
51
|
+
elevated: boolean;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Superset of LlmResponse (so it is a drop-in for provider.complete callers) plus the
|
|
55
|
+
// route trace: which model answered, how confident, and the full elevation history.
|
|
56
|
+
export interface ReasoningResult {
|
|
57
|
+
content: string;
|
|
58
|
+
toolCalls?: LlmToolCall[];
|
|
59
|
+
stopReason: LlmResponse["stopReason"];
|
|
60
|
+
usage?: LlmResponse["usage"];
|
|
61
|
+
/**
|
|
62
|
+
* ADR 0133 - the answering route's reasoning, as the call returned it. Through an effect-backed
|
|
63
|
+
* `invoke` these are recorded `LlmReasoningPart`s; a caller keeps only those (`type: "reasoning"`).
|
|
64
|
+
*/
|
|
65
|
+
reasoning?: LlmResponse["reasoning"];
|
|
66
|
+
/** ADR 0133 D5 - the answering call's block order (positions only), so a transcript can reproduce the vendor sequence. */
|
|
67
|
+
layout?: LlmResponse["layout"];
|
|
68
|
+
modelId: string;
|
|
69
|
+
confidence: number;
|
|
70
|
+
elevated: boolean;
|
|
71
|
+
trace: ReasoningTraceEntry[];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Pluggable confidence — deliberately NOT a magic self-reported number. Swap in
|
|
75
|
+
// stop-reason, validator-pass, log-prob, or ensemble-agreement signals per stakes.
|
|
76
|
+
export interface ConfidenceSignal {
|
|
77
|
+
score(req: ReasoningRequest, res: LlmResponse): number | Promise<number>;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Pluggable routing policy. The default (tagRouter) is cheap-first.
|
|
81
|
+
export interface Router {
|
|
82
|
+
select(req: ReasoningRequest, routes: ModelRoute[]): ModelRoute;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Optional in-memory response cache (a latency-only stage; does not change the
|
|
86
|
+
// recorded outcome). Bring your own eviction.
|
|
87
|
+
export interface ReasoningCache {
|
|
88
|
+
get(key: string): LlmResponse | undefined;
|
|
89
|
+
set(key: string, value: LlmResponse): void;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* The request a {@link ModelInvoker} receives: the reasoning request minus the routing-only
|
|
94
|
+
* fields and minus `signal` (a signal is not data — an effect-backed invoker gets its
|
|
95
|
+
* cancellation from the effect context, not from here).
|
|
96
|
+
*/
|
|
97
|
+
export interface ModelCallRequest {
|
|
98
|
+
messages: LlmMessageInput[];
|
|
99
|
+
system?: LlmContent;
|
|
100
|
+
tools?: LlmToolSpecInput[];
|
|
101
|
+
maxTokens?: number;
|
|
102
|
+
temperature?: number;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* R9 — how a chosen route's model call is MADE. Without one, the runtime calls
|
|
107
|
+
* `route.provider.complete` directly, which is outside the effect ring (no K2 authorization,
|
|
108
|
+
* no metering, no kill switch). Supplying an invoker hands the call to the host so it can run
|
|
109
|
+
* as an effect: `agentLoop$` passes one that resolves each call through `ctx.execute`, so the
|
|
110
|
+
* call is authorized, metered, kill-switched and `guardRequest`-checked, and is JOURNALED —
|
|
111
|
+
* a replay serves the recorded response instead of re-calling the model.
|
|
112
|
+
*
|
|
113
|
+
* The runtime's response cache is BYPASSED when an invoker is supplied: a cache hit would skip
|
|
114
|
+
* the invoker, so whether a durable frame exists would depend on a process-local cache and a
|
|
115
|
+
* replay could diverge from the run.
|
|
116
|
+
*/
|
|
117
|
+
export type ModelInvoker = (route: ModelRoute, request: ModelCallRequest) => Promise<LlmResponse>;
|
|
118
|
+
|
|
119
|
+
export interface ReasonOptionsBase {
|
|
120
|
+
/** When provided, route/elevation choices are recorded through it (P9). */
|
|
121
|
+
decide?: DecideFn;
|
|
122
|
+
/** R9 — make each model call through the host instead of the route's provider. See {@link ModelInvoker}. */
|
|
123
|
+
invoke?: ModelInvoker;
|
|
124
|
+
/**
|
|
125
|
+
* R10 — partition the response cache. The cache key is the request as written plus the route; two
|
|
126
|
+
* callers with an identical prompt would otherwise share a cached response. The runtime already
|
|
127
|
+
* adds the `resolveContext.principal` identity (tenant, actor, onBehalfOf) when it has one; a host
|
|
128
|
+
* that cannot hand over a principal (a runner body cannot read its own) passes a stable scope
|
|
129
|
+
* string here, e.g. the tenant id. Unset on a single-tenant host = one shared cache, as before.
|
|
130
|
+
*
|
|
131
|
+
* NOTE on metering: a cache hit makes no provider call, so on the direct path (no effect ring) it
|
|
132
|
+
* is not metered. That is the cache's purpose; a host that must meter every request routes the
|
|
133
|
+
* call through an effect (`invoke`), which bypasses this cache.
|
|
134
|
+
*/
|
|
135
|
+
cacheScope?: string;
|
|
136
|
+
/**
|
|
137
|
+
* R10 — journal the live provider call. Unlike {@link invoke} (which REPLACES the call so the host
|
|
138
|
+
* can run it as an effect), this wraps it: the runtime still guards the request, checks the route's
|
|
139
|
+
* modalities and keys its response cache on the request as written, and hands the host only the
|
|
140
|
+
* part that touches the network (cache lookup, content resolution, `provider.complete`, cache
|
|
141
|
+
* fill) as `run`. The host records `run`'s result (`ctx.decide`) so a replay serves it and `run` is
|
|
142
|
+
* never called. Ignored when `invoke` is set.
|
|
143
|
+
*/
|
|
144
|
+
wrapCall?: (route: ModelRoute, run: () => Promise<LlmResponse>) => Promise<LlmResponse>;
|
|
145
|
+
signal?: AbortSignal;
|
|
146
|
+
/** Namespaces decision ids so multiple reason() calls in one execution don't collide. */
|
|
147
|
+
decisionKey?: string;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* R5 — fetch the bytes behind a media `ref` for the DIRECT (non-`invoke`) path, after the chosen
|
|
152
|
+
* route's capabilities have been checked and after the cache lookup (the cache is keyed on the
|
|
153
|
+
* request as written, i.e. on addresses, never on resolved bytes). Without it a ref is refused.
|
|
154
|
+
*
|
|
155
|
+
* `resolveContent` and `resolveContext` come TOGETHER (the type makes one without the other an
|
|
156
|
+
* error): the context is what lets a store authorize the read, and a resolver MUST deny a read whose
|
|
157
|
+
* `executionId` / `principal` it cannot attribute.
|
|
158
|
+
*/
|
|
159
|
+
export type ReasonOptions = ReasonOptionsBase &
|
|
160
|
+
(
|
|
161
|
+
| { resolveContent?: undefined; resolveContext?: undefined }
|
|
162
|
+
| {
|
|
163
|
+
resolveContent: ContentResolver;
|
|
164
|
+
resolveContext: Pick<
|
|
165
|
+
ContentResolveContext,
|
|
166
|
+
"executionId" | "workflowId" | "stepId" | "principal"
|
|
167
|
+
>;
|
|
168
|
+
}
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
export interface ReasoningRuntimeConfig {
|
|
172
|
+
routes: ModelRoute[];
|
|
173
|
+
router?: Router;
|
|
174
|
+
confidence?: ConfidenceSignal;
|
|
175
|
+
/** Elevate while confidence < threshold and a higher tier is available. Default 0.5. */
|
|
176
|
+
elevateThreshold?: number;
|
|
177
|
+
cache?: ReasoningCache;
|
|
178
|
+
}
|