@aid-framework/runtime 0.0.0-stage → 0.1.39
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 +201 -0
- package/README.md +52 -2
- package/dist/.tsbuildinfo +1 -0
- package/dist/agents/deep_agents.d.ts +78 -0
- package/dist/agents/deep_agents.d.ts.map +1 -0
- package/dist/agents/deep_agents.js +477 -0
- package/dist/agents/deep_agents.js.map +1 -0
- package/dist/agents/engine.d.ts +85 -0
- package/dist/agents/engine.d.ts.map +1 -0
- package/dist/agents/engine.js +129 -0
- package/dist/agents/engine.js.map +1 -0
- package/dist/agents/gateway_chat_model.d.ts +151 -0
- package/dist/agents/gateway_chat_model.d.ts.map +1 -0
- package/dist/agents/gateway_chat_model.js +350 -0
- package/dist/agents/gateway_chat_model.js.map +1 -0
- package/dist/agents/guardrail_middleware.d.ts +57 -0
- package/dist/agents/guardrail_middleware.d.ts.map +1 -0
- package/dist/agents/guardrail_middleware.js +85 -0
- package/dist/agents/guardrail_middleware.js.map +1 -0
- package/dist/agents/guardrails.d.ts +124 -0
- package/dist/agents/guardrails.d.ts.map +1 -0
- package/dist/agents/guardrails.js +336 -0
- package/dist/agents/guardrails.js.map +1 -0
- package/dist/agents/index.d.ts +15 -0
- package/dist/agents/index.d.ts.map +1 -0
- package/dist/agents/index.js +15 -0
- package/dist/agents/index.js.map +1 -0
- package/dist/agents/ir.d.ts +144 -0
- package/dist/agents/ir.d.ts.map +1 -0
- package/dist/agents/ir.js +513 -0
- package/dist/agents/ir.js.map +1 -0
- package/dist/agents/loop.d.ts +145 -0
- package/dist/agents/loop.d.ts.map +1 -0
- package/dist/agents/loop.js +1025 -0
- package/dist/agents/loop.js.map +1 -0
- package/dist/agents/memory.d.ts +115 -0
- package/dist/agents/memory.d.ts.map +1 -0
- package/dist/agents/memory.js +258 -0
- package/dist/agents/memory.js.map +1 -0
- package/dist/agents/state.d.ts +137 -0
- package/dist/agents/state.d.ts.map +1 -0
- package/dist/agents/state.js +255 -0
- package/dist/agents/state.js.map +1 -0
- package/dist/concurrency.d.ts +36 -0
- package/dist/concurrency.d.ts.map +1 -0
- package/dist/concurrency.js +86 -0
- package/dist/concurrency.js.map +1 -0
- package/dist/control.d.ts +116 -0
- package/dist/control.d.ts.map +1 -0
- package/dist/control.js +281 -0
- package/dist/control.js.map +1 -0
- package/dist/cost.d.ts +95 -0
- package/dist/cost.d.ts.map +1 -0
- package/dist/cost.js +161 -0
- package/dist/cost.js.map +1 -0
- package/dist/errors.d.ts +213 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +316 -0
- package/dist/errors.js.map +1 -0
- package/dist/evals/dataset.d.ts +45 -0
- package/dist/evals/dataset.d.ts.map +1 -0
- package/dist/evals/dataset.js +147 -0
- package/dist/evals/dataset.js.map +1 -0
- package/dist/evals/gate.d.ts +236 -0
- package/dist/evals/gate.d.ts.map +1 -0
- package/dist/evals/gate.js +572 -0
- package/dist/evals/gate.js.map +1 -0
- package/dist/evals/metrics.d.ts +141 -0
- package/dist/evals/metrics.d.ts.map +1 -0
- package/dist/evals/metrics.js +364 -0
- package/dist/evals/metrics.js.map +1 -0
- package/dist/evals/predicate.d.ts +79 -0
- package/dist/evals/predicate.d.ts.map +1 -0
- package/dist/evals/predicate.js +178 -0
- package/dist/evals/predicate.js.map +1 -0
- package/dist/evals/runner.d.ts +94 -0
- package/dist/evals/runner.d.ts.map +1 -0
- package/dist/evals/runner.js +202 -0
- package/dist/evals/runner.js.map +1 -0
- package/dist/gateway.d.ts +118 -0
- package/dist/gateway.d.ts.map +1 -0
- package/dist/gateway.js +250 -0
- package/dist/gateway.js.map +1 -0
- package/dist/index.d.ts +83 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +59 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/json.d.ts +44 -0
- package/dist/internal/json.d.ts.map +1 -0
- package/dist/internal/json.js +90 -0
- package/dist/internal/json.js.map +1 -0
- package/dist/internal/text.d.ts +17 -0
- package/dist/internal/text.d.ts.map +1 -0
- package/dist/internal/text.js +22 -0
- package/dist/internal/text.js.map +1 -0
- package/dist/pipeline.d.ts +80 -0
- package/dist/pipeline.d.ts.map +1 -0
- package/dist/pipeline.js +159 -0
- package/dist/pipeline.js.map +1 -0
- package/dist/policy.d.ts +52 -0
- package/dist/policy.d.ts.map +1 -0
- package/dist/policy.js +199 -0
- package/dist/policy.js.map +1 -0
- package/dist/prompts.d.ts +86 -0
- package/dist/prompts.d.ts.map +1 -0
- package/dist/prompts.js +337 -0
- package/dist/prompts.js.map +1 -0
- package/dist/providers/base.d.ts +19 -0
- package/dist/providers/base.d.ts.map +1 -0
- package/dist/providers/base.js +13 -0
- package/dist/providers/base.js.map +1 -0
- package/dist/providers/fake.d.ts +98 -0
- package/dist/providers/fake.d.ts.map +1 -0
- package/dist/providers/fake.js +181 -0
- package/dist/providers/fake.js.map +1 -0
- package/dist/providers/http.d.ts +62 -0
- package/dist/providers/http.d.ts.map +1 -0
- package/dist/providers/http.js +245 -0
- package/dist/providers/http.js.map +1 -0
- package/dist/retrieval/chunking.d.ts +88 -0
- package/dist/retrieval/chunking.d.ts.map +1 -0
- package/dist/retrieval/chunking.js +188 -0
- package/dist/retrieval/chunking.js.map +1 -0
- package/dist/retrieval/citations.d.ts +98 -0
- package/dist/retrieval/citations.d.ts.map +1 -0
- package/dist/retrieval/citations.js +134 -0
- package/dist/retrieval/citations.js.map +1 -0
- package/dist/retrieval/connectors.d.ts +74 -0
- package/dist/retrieval/connectors.d.ts.map +1 -0
- package/dist/retrieval/connectors.js +218 -0
- package/dist/retrieval/connectors.js.map +1 -0
- package/dist/retrieval/embedding.d.ts +116 -0
- package/dist/retrieval/embedding.d.ts.map +1 -0
- package/dist/retrieval/embedding.js +282 -0
- package/dist/retrieval/embedding.js.map +1 -0
- package/dist/retrieval/gate.d.ts +52 -0
- package/dist/retrieval/gate.d.ts.map +1 -0
- package/dist/retrieval/gate.js +91 -0
- package/dist/retrieval/gate.js.map +1 -0
- package/dist/retrieval/hybrid.d.ts +76 -0
- package/dist/retrieval/hybrid.d.ts.map +1 -0
- package/dist/retrieval/hybrid.js +150 -0
- package/dist/retrieval/hybrid.js.map +1 -0
- package/dist/retrieval/metrics.d.ts +126 -0
- package/dist/retrieval/metrics.d.ts.map +1 -0
- package/dist/retrieval/metrics.js +200 -0
- package/dist/retrieval/metrics.js.map +1 -0
- package/dist/retrieval/pgvector.d.ts +62 -0
- package/dist/retrieval/pgvector.d.ts.map +1 -0
- package/dist/retrieval/pgvector.js +262 -0
- package/dist/retrieval/pgvector.js.map +1 -0
- package/dist/retrieval/rerank.d.ts +61 -0
- package/dist/retrieval/rerank.d.ts.map +1 -0
- package/dist/retrieval/rerank.js +105 -0
- package/dist/retrieval/rerank.js.map +1 -0
- package/dist/retrieval/retriever.d.ts +125 -0
- package/dist/retrieval/retriever.d.ts.map +1 -0
- package/dist/retrieval/retriever.js +281 -0
- package/dist/retrieval/retriever.js.map +1 -0
- package/dist/retrieval/stores.d.ts +106 -0
- package/dist/retrieval/stores.d.ts.map +1 -0
- package/dist/retrieval/stores.js +142 -0
- package/dist/retrieval/stores.js.map +1 -0
- package/dist/retrieval/types.d.ts +314 -0
- package/dist/retrieval/types.d.ts.map +1 -0
- package/dist/retrieval/types.js +388 -0
- package/dist/retrieval/types.js.map +1 -0
- package/dist/steps.d.ts +156 -0
- package/dist/steps.d.ts.map +1 -0
- package/dist/steps.js +199 -0
- package/dist/steps.js.map +1 -0
- package/dist/structured.d.ts +91 -0
- package/dist/structured.d.ts.map +1 -0
- package/dist/structured.js +155 -0
- package/dist/structured.js.map +1 -0
- package/dist/tools.d.ts +100 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +201 -0
- package/dist/tools.js.map +1 -0
- package/dist/tracing.d.ts +104 -0
- package/dist/tracing.d.ts.map +1 -0
- package/dist/tracing.js +155 -0
- package/dist/tracing.js.map +1 -0
- package/dist/types.d.ts +240 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +357 -0
- package/dist/types.js.map +1 -0
- package/package.json +77 -3
package/dist/steps.js
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Leaf pipeline steps: `generate`, `retrieve`, `tool` and `emit`.
|
|
3
|
+
*
|
|
4
|
+
* `pipeline.ts` runs steps and `control.ts` composes them; this module is the third
|
|
5
|
+
* part of the family and builds the leaves a pipeline actually reaches the outside
|
|
6
|
+
* world with. Each factory returns an ordinary {@link CompositeStep}, so a leaf
|
|
7
|
+
* composes with a composite by the same rule.
|
|
8
|
+
*
|
|
9
|
+
* Why these exist at all: a generated application inlines its steps as hand-written
|
|
10
|
+
* async functions, which leaves a caller who is *not* the generator -- a test, a
|
|
11
|
+
* conformance runner, a hand-built pipeline -- with no way to drive one step kind
|
|
12
|
+
* without re-writing the primitive's call and its binding rule. That duplication is
|
|
13
|
+
* where the two disagree. A step here is the same primitive the generator calls:
|
|
14
|
+
*
|
|
15
|
+
* - `retrieve` calls {@link RetrieverSeam.retrieve} and binds the `RetrievalResult`.
|
|
16
|
+
* - `tool` calls {@link ToolExecutor.execute} and binds what the tool returned,
|
|
17
|
+
* refusing an error outcome and parsing the content.
|
|
18
|
+
* - `generate` renders a prompt, calls {@link ModelGateway.generate}, and runs the
|
|
19
|
+
* structured path when the caller names an output type.
|
|
20
|
+
* - `emit` projects the context into the event fields a caller names.
|
|
21
|
+
*
|
|
22
|
+
* Every factory takes its inputs **already resolved**: the query string, the argument
|
|
23
|
+
* mapping, the prompt values. Interpolating a spec template against the context is the
|
|
24
|
+
* *emitted step body's* job, and a second implementation of it here would be a second
|
|
25
|
+
* thing to keep in step with the generator's. What is not duplicated is the part that
|
|
26
|
+
* is the same for every caller: the primitive call, the error contract, the parse, and
|
|
27
|
+
* the name the result is bound under.
|
|
28
|
+
*
|
|
29
|
+
* `emit` is the one leaf whose whole behaviour is reading the context, so
|
|
30
|
+
* {@link resolveSource} lives here rather than in a caller: a field's source is a
|
|
31
|
+
* binding name, optionally followed by one attribute, which is exactly the reference
|
|
32
|
+
* path the generator derives for the same field.
|
|
33
|
+
*/
|
|
34
|
+
import { PipelineError } from './errors.js';
|
|
35
|
+
import { repr } from './internal/json.js';
|
|
36
|
+
import { parseStructured, runStructured } from './structured.js';
|
|
37
|
+
import { CompletionRequest, Message, ToolCall } from './types.js';
|
|
38
|
+
/**
|
|
39
|
+
* The context key an `emit` step binds its projected payload under.
|
|
40
|
+
*
|
|
41
|
+
* Deliberately *not* in `RESERVED_KEYS`. That set is mirrored in the generator and
|
|
42
|
+
* pinned against the runtime by a generator test, so a seventh entry would have to be
|
|
43
|
+
* emitted as well -- and the generated applications do not compose their response
|
|
44
|
+
* through this step yet. A generated route keeps building its event object itself;
|
|
45
|
+
* adding the key here would reserve a spec name for a step the emitted apps never
|
|
46
|
+
* call. {@link emitStep} takes the key as an option so the generator can adopt it
|
|
47
|
+
* under its own name when it does.
|
|
48
|
+
*/
|
|
49
|
+
export const EMITTED_KEY = 'emitted';
|
|
50
|
+
/**
|
|
51
|
+
* Name a step's callable, so a trace and {@link Pipeline.of} identify it.
|
|
52
|
+
*
|
|
53
|
+
* `Pipeline.of` reads `step.name`, so this is the same mechanism as Python assigning
|
|
54
|
+
* `__name__` -- the two runtimes name a leaf identically rather than one of them
|
|
55
|
+
* requiring an explicit name at construction.
|
|
56
|
+
*/
|
|
57
|
+
function named(step, name) {
|
|
58
|
+
if (name !== undefined) {
|
|
59
|
+
Object.defineProperty(step, 'name', { value: name, configurable: true });
|
|
60
|
+
}
|
|
61
|
+
return step;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* One `emit` field's value: a binding, then at most one attribute below it.
|
|
65
|
+
*
|
|
66
|
+
* The one-segment form is the request event's or a scalar binding's whole value; the
|
|
67
|
+
* two-segment form is a struct's field. Deeper paths are not accepted, because the
|
|
68
|
+
* spec layer's reference paths stop at one field and this resolver exists to read
|
|
69
|
+
* exactly what the generator reads.
|
|
70
|
+
*
|
|
71
|
+
* A missing binding is a {@link PipelineError} naming the key -- the same refusal
|
|
72
|
+
* {@link PipelineContext.require} makes -- and a missing attribute is one naming both
|
|
73
|
+
* the source and the field, because "the field is not there" and "the binding is not
|
|
74
|
+
* there" are different mistakes and both mean the response would be wrong.
|
|
75
|
+
*/
|
|
76
|
+
export function resolveSource(context, source) {
|
|
77
|
+
const dot = source.indexOf('.');
|
|
78
|
+
const root = dot === -1 ? source : source.slice(0, dot);
|
|
79
|
+
const attribute = dot === -1 ? '' : source.slice(dot + 1);
|
|
80
|
+
const value = context.require(root);
|
|
81
|
+
if (attribute === '')
|
|
82
|
+
return value;
|
|
83
|
+
if (attribute.includes('.')) {
|
|
84
|
+
throw new PipelineError(`emit source ${repr(source)} names more than one attribute below ${repr(root)}; ` +
|
|
85
|
+
'a field is read from a binding or one struct below it');
|
|
86
|
+
}
|
|
87
|
+
// An *own* property: that is where a declared field lands both in an object parsed
|
|
88
|
+
// from JSON and in a class that assigns its fields, and it keeps `toString` and
|
|
89
|
+
// `constructor` from reading as fields of every struct.
|
|
90
|
+
const fields = value;
|
|
91
|
+
if (typeof value !== 'object' || fields === null || !Object.hasOwn(fields, attribute)) {
|
|
92
|
+
throw new PipelineError(`emit source ${repr(source)} names the field ${repr(attribute)}, which the ` +
|
|
93
|
+
`${repr(root)} binding does not carry`);
|
|
94
|
+
}
|
|
95
|
+
return fields[attribute];
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Build the event payload from the context, field by field.
|
|
99
|
+
*
|
|
100
|
+
* `fields` maps each event field to its source, in the order the caller declared
|
|
101
|
+
* them: an insertion-ordered mapping keeps the payload's own order stable, so a
|
|
102
|
+
* projection that reorders its fields is observable rather than invisible. Each source
|
|
103
|
+
* is a binding name or a `binding.attribute` path, which is the shape the generator
|
|
104
|
+
* derives for the same field via `responsePlan`.
|
|
105
|
+
*
|
|
106
|
+
* The step **binds** its payload and does not stop the pipeline. Stopping would lose
|
|
107
|
+
* it: both runtimes carry the *pre-step* context out of a stop, so a stopping step
|
|
108
|
+
* cannot bind anything. A generated route does not stop at its emit either -- it runs
|
|
109
|
+
* every step and only then builds the response object -- so a later step still runs
|
|
110
|
+
* after this one, which is the behaviour a pipeline that composed it would see.
|
|
111
|
+
*/
|
|
112
|
+
export function emitStep(fields, options = {}) {
|
|
113
|
+
const key = options.key ?? EMITTED_KEY;
|
|
114
|
+
const ordered = Object.entries(fields);
|
|
115
|
+
return named(async (context) => {
|
|
116
|
+
const payload = {};
|
|
117
|
+
for (const [field, source] of ordered)
|
|
118
|
+
payload[field] = resolveSource(context, source);
|
|
119
|
+
return context.withValues({ [key]: payload });
|
|
120
|
+
}, options.name);
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Search `retriever` and bind the cited passages under `binding`.
|
|
124
|
+
*
|
|
125
|
+
* `topK` is passed through rather than resolved here, so `undefined` keeps meaning
|
|
126
|
+
* "the binding's own ceiling" exactly as `Retriever.retrieve` defines it.
|
|
127
|
+
*/
|
|
128
|
+
export function retrieveStep(retriever, options) {
|
|
129
|
+
return named(async (context) => {
|
|
130
|
+
const found = await retriever.retrieve(options.query, {
|
|
131
|
+
topK: options.topK,
|
|
132
|
+
});
|
|
133
|
+
return context.withValues({ [options.binding]: found });
|
|
134
|
+
}, options.name);
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Call `tool` and bind what it returned, refusing an error outcome.
|
|
138
|
+
*
|
|
139
|
+
* The call id is the binding name, which makes a trace of a tool step name the value
|
|
140
|
+
* it produced. An error outcome throws rather than binding a message: a tool the model
|
|
141
|
+
* called and that failed is not a value the next step can meaningfully read, and the
|
|
142
|
+
* generator's own step throws for the same reason.
|
|
143
|
+
*
|
|
144
|
+
* `output` is the struct to parse the content into when the spec declared a named
|
|
145
|
+
* output; without one the content is bound as parsed JSON, so a tool declaring
|
|
146
|
+
* `string` binds the string it returned rather than a re-encoded copy.
|
|
147
|
+
*/
|
|
148
|
+
export function toolStep(tools, options) {
|
|
149
|
+
return named(async (context) => {
|
|
150
|
+
const outcome = await tools.execute(new ToolCall({
|
|
151
|
+
id: options.binding,
|
|
152
|
+
name: options.tool,
|
|
153
|
+
arguments: { ...options.arguments },
|
|
154
|
+
}));
|
|
155
|
+
if (outcome.isError) {
|
|
156
|
+
throw new PipelineError(`tool ${options.tool} failed: ${outcome.content}`);
|
|
157
|
+
}
|
|
158
|
+
const value = options.output === undefined
|
|
159
|
+
? JSON.parse(outcome.content)
|
|
160
|
+
: parseStructured(outcome.content, options.output);
|
|
161
|
+
return context.withValues({ [options.binding]: value });
|
|
162
|
+
}, options.name);
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Render `prompt`, ask `model` for one completion, and bind the answer.
|
|
166
|
+
*
|
|
167
|
+
* The call is built once and handed to the structured path as its `call`, so a repair
|
|
168
|
+
* round re-sends an already-rendered prompt instead of rendering again -- the same
|
|
169
|
+
* closure the generated step emits, and the reason a retry cannot silently re-read a
|
|
170
|
+
* binding that changed underneath it.
|
|
171
|
+
*
|
|
172
|
+
* `values` are the prompt's own values and are passed to the registry unrendered:
|
|
173
|
+
* {@link PromptRegistry.render} is the one place a template becomes text. With
|
|
174
|
+
* `structured` the answer is parsed into that type, and a parse failure throws after
|
|
175
|
+
* the repair rounds rather than binding a half-valid object.
|
|
176
|
+
*/
|
|
177
|
+
export function generateStep(gateway, prompts, options) {
|
|
178
|
+
return named(async (context) => {
|
|
179
|
+
const rendered = prompts.render(options.prompt, options.values ?? {}, options.version);
|
|
180
|
+
const call = async (text) => {
|
|
181
|
+
const request = options.temperature === undefined && options.maxOutputTokens === undefined
|
|
182
|
+
? CompletionRequest.prompt(text)
|
|
183
|
+
: new CompletionRequest({
|
|
184
|
+
messages: [Message.user(text)],
|
|
185
|
+
temperature: options.temperature,
|
|
186
|
+
maxOutputTokens: options.maxOutputTokens,
|
|
187
|
+
});
|
|
188
|
+
const response = await gateway.generate(options.model, request, {
|
|
189
|
+
fallbacks: options.fallbacks ?? [],
|
|
190
|
+
});
|
|
191
|
+
return response.text;
|
|
192
|
+
};
|
|
193
|
+
const value = options.structured === undefined
|
|
194
|
+
? await call(rendered.text)
|
|
195
|
+
: await runStructured(call, options.structured, rendered.text);
|
|
196
|
+
return context.withValues({ [options.binding]: value });
|
|
197
|
+
}, options.name);
|
|
198
|
+
}
|
|
199
|
+
//# sourceMappingURL=steps.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"steps.js","sourceRoot":"","sources":["../src/steps.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structured-output enforcement with a parse-repair loop.
|
|
3
|
+
*
|
|
4
|
+
* This is the only place zod is used, and it is used for what it is good at: getting
|
|
5
|
+
* a typed object out of an untrusted model response. Pipeline control flow is plain
|
|
6
|
+
* async; zod is a validation dependency here, not an orchestration one.
|
|
7
|
+
*
|
|
8
|
+
* Two entry points:
|
|
9
|
+
*
|
|
10
|
+
* - {@link parseStructured} coerces already-received text into a typed value.
|
|
11
|
+
* - {@link runStructured} drives a call-repair-retry loop, feeding the validation
|
|
12
|
+
* failure back to the model. This is the difference between "the model must be
|
|
13
|
+
* right the first time" and "the model must eventually be right", and only the
|
|
14
|
+
* second is an achievable contract.
|
|
15
|
+
*/
|
|
16
|
+
import { z } from 'zod';
|
|
17
|
+
import { StructuredOutputError } from './errors.js';
|
|
18
|
+
/** The correction turn appended after a failed parse. */
|
|
19
|
+
export declare const REPAIR_INSTRUCTION: string;
|
|
20
|
+
/**
|
|
21
|
+
* A named output schema.
|
|
22
|
+
*
|
|
23
|
+
* The name exists because Python's reference implementation reads
|
|
24
|
+
* `output_type.__name__` and every diagnostic it produces names the type. A zod
|
|
25
|
+
* schema has no runtime name, so the name is carried alongside it rather than
|
|
26
|
+
* inferred.
|
|
27
|
+
*/
|
|
28
|
+
export interface StructuredOutput<T> {
|
|
29
|
+
readonly name: string;
|
|
30
|
+
readonly schema: z.ZodType<T>;
|
|
31
|
+
}
|
|
32
|
+
/** Name a schema so diagnostics can say which type failed. */
|
|
33
|
+
export declare function structuredOutput<T>(name: string, schema: z.ZodType<T>): StructuredOutput<T>;
|
|
34
|
+
/** The members of a zod issue this module reads. */
|
|
35
|
+
interface Issue {
|
|
36
|
+
readonly path: readonly PropertyKey[];
|
|
37
|
+
readonly message: string;
|
|
38
|
+
}
|
|
39
|
+
/** Flatten validation issues into `location: message` strings. */
|
|
40
|
+
export declare function formatProblems(issues: readonly Issue[]): string[];
|
|
41
|
+
/**
|
|
42
|
+
* Validate `text` as `outputType`.
|
|
43
|
+
*
|
|
44
|
+
* Every problem is reported, not just the first: feeding one error back at a time
|
|
45
|
+
* turns a two-round repair into a five-round one.
|
|
46
|
+
*/
|
|
47
|
+
export declare function parseStructured<T>(text: string, outputType: StructuredOutput<T>): T;
|
|
48
|
+
/**
|
|
49
|
+
* Call, parse, and on failure ask again with the validation errors attached.
|
|
50
|
+
*
|
|
51
|
+
* `retries` counts *repair* attempts beyond the first call, so `retries: 2` means at
|
|
52
|
+
* most three calls. Throwing after the final attempt is correct: an endpoint that
|
|
53
|
+
* returns a half-valid object is worse than one that fails loudly.
|
|
54
|
+
*/
|
|
55
|
+
export declare function runStructured<T>(call: (prompt: string) => Promise<string>, outputType: StructuredOutput<T>, prompt: string, options?: {
|
|
56
|
+
readonly retries?: number;
|
|
57
|
+
}): Promise<T>;
|
|
58
|
+
/** The corrective turn for a failed parse. */
|
|
59
|
+
export declare function repairPrompt(error: StructuredOutputError): string;
|
|
60
|
+
/** The seam a `buildAgent` caller supplies: one prompt in, one model answer out. */
|
|
61
|
+
export interface AgentModel {
|
|
62
|
+
complete(prompt: string): Promise<string>;
|
|
63
|
+
}
|
|
64
|
+
/** A typed agent: `run` drives the repair loop and returns the parsed value. */
|
|
65
|
+
export declare class StructuredAgent<T> {
|
|
66
|
+
private readonly _model;
|
|
67
|
+
private readonly _outputType;
|
|
68
|
+
private readonly _systemPrompt;
|
|
69
|
+
private readonly _retries;
|
|
70
|
+
constructor(model: AgentModel, outputType: StructuredOutput<T>, options?: {
|
|
71
|
+
readonly systemPrompt?: string;
|
|
72
|
+
readonly retries?: number;
|
|
73
|
+
});
|
|
74
|
+
/** Run one prompt through the model, repairing until the schema validates. */
|
|
75
|
+
run(prompt: string): Promise<T>;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Build a typed agent over `outputType`.
|
|
79
|
+
*
|
|
80
|
+
* The reference runtime delegates this to Pydantic AI's `Agent`. The TypeScript
|
|
81
|
+
* runtime has no equivalent, so this is the same contract implemented against the
|
|
82
|
+
* repair loop above -- typed output with retries -- rather than a new orchestration
|
|
83
|
+
* layer.
|
|
84
|
+
*/
|
|
85
|
+
export declare function buildAgent<T>(model: AgentModel, outputType: StructuredOutput<T>, options?: {
|
|
86
|
+
readonly systemPrompt?: string;
|
|
87
|
+
readonly retries?: number;
|
|
88
|
+
}): StructuredAgent<T>;
|
|
89
|
+
/** Re-export zod so generated code can declare schemas without a second dependency. */
|
|
90
|
+
export { z };
|
|
91
|
+
//# sourceMappingURL=structured.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"structured.d.ts","sourceRoot":"","sources":["../src/structured.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAEpD,yDAAyD;AACzD,eAAO,MAAM,kBAAkB,QAE6B,CAAC;AAE7D;;;;;;;GAOG;AACH,MAAM,WAAW,gBAAgB,CAAC,CAAC;IACjC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;CAC/B;AAED,8DAA8D;AAC9D,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,gBAAgB,CAAC,CAAC,CAAC,CAG3F;AAED,oDAAoD;AACpD,UAAU,KAAK;IACb,QAAQ,CAAC,IAAI,EAAE,SAAS,WAAW,EAAE,CAAC;IACtC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,kEAAkE;AAClE,wBAAgB,cAAc,CAAC,MAAM,EAAE,SAAS,KAAK,EAAE,GAAG,MAAM,EAAE,CAKjE;AA0BD;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,gBAAgB,CAAC,CAAC,CAAC,GAAG,CAAC,CAqBnF;AAED;;;;;;GAMG;AACH,wBAAsB,aAAa,CAAC,CAAC,EACnC,IAAI,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,EACzC,UAAU,EAAE,gBAAgB,CAAC,CAAC,CAAC,EAC/B,MAAM,EAAE,MAAM,EACd,OAAO,GAAE;IAAE,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAO,GAC1C,OAAO,CAAC,CAAC,CAAC,CAuBZ;AAED,8CAA8C;AAC9C,wBAAgB,YAAY,CAAC,KAAK,EAAE,qBAAqB,GAAG,MAAM,CAGjE;AAED,oFAAoF;AACpF,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CAC3C;AAED,gFAAgF;AAChF,qBAAa,eAAe,CAAC,CAAC;IAC5B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAa;IACpC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAsB;IAClD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAqB;IACnD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAElC,YACE,KAAK,EAAE,UAAU,EACjB,UAAU,EAAE,gBAAgB,CAAC,CAAC,CAAC,EAC/B,OAAO,GAAE;QAAE,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAO,EAM5E;IAED,8EAA8E;IACxE,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAMpC;CACF;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAC1B,KAAK,EAAE,UAAU,EACjB,UAAU,EAAE,gBAAgB,CAAC,CAAC,CAAC,EAC/B,OAAO,GAAE;IAAE,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAO,GAC1E,eAAe,CAAC,CAAC,CAAC,CAEpB;AAED,uFAAuF;AACvF,OAAO,EAAE,CAAC,EAAE,CAAC"}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structured-output enforcement with a parse-repair loop.
|
|
3
|
+
*
|
|
4
|
+
* This is the only place zod is used, and it is used for what it is good at: getting
|
|
5
|
+
* a typed object out of an untrusted model response. Pipeline control flow is plain
|
|
6
|
+
* async; zod is a validation dependency here, not an orchestration one.
|
|
7
|
+
*
|
|
8
|
+
* Two entry points:
|
|
9
|
+
*
|
|
10
|
+
* - {@link parseStructured} coerces already-received text into a typed value.
|
|
11
|
+
* - {@link runStructured} drives a call-repair-retry loop, feeding the validation
|
|
12
|
+
* failure back to the model. This is the difference between "the model must be
|
|
13
|
+
* right the first time" and "the model must eventually be right", and only the
|
|
14
|
+
* second is an achievable contract.
|
|
15
|
+
*/
|
|
16
|
+
import { z } from 'zod';
|
|
17
|
+
import { StructuredOutputError } from './errors.js';
|
|
18
|
+
/** The correction turn appended after a failed parse. */
|
|
19
|
+
export const REPAIR_INSTRUCTION = 'Your previous response could not be parsed. Reply with corrected JSON only, ' +
|
|
20
|
+
'matching the required schema exactly. Errors: {problems}';
|
|
21
|
+
/** Name a schema so diagnostics can say which type failed. */
|
|
22
|
+
export function structuredOutput(name, schema) {
|
|
23
|
+
if (name.length === 0)
|
|
24
|
+
throw new RangeError('a structured output type needs a name');
|
|
25
|
+
return { name, schema };
|
|
26
|
+
}
|
|
27
|
+
/** Flatten validation issues into `location: message` strings. */
|
|
28
|
+
export function formatProblems(issues) {
|
|
29
|
+
return issues.map((issue) => {
|
|
30
|
+
const location = issue.path.map((part) => String(part)).join('.') || '<root>';
|
|
31
|
+
return `${location}: ${issue.message}`;
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Remove a ```json fence if the model wrapped its answer in one.
|
|
36
|
+
*
|
|
37
|
+
* Models do this constantly when asked for JSON. Treating a valid payload as invalid
|
|
38
|
+
* because of presentation is a self-inflicted repair loop.
|
|
39
|
+
*/
|
|
40
|
+
function stripCodeFence(text) {
|
|
41
|
+
const stripped = text.trim();
|
|
42
|
+
if (!stripped.startsWith('```'))
|
|
43
|
+
return stripped;
|
|
44
|
+
const lines = stripped.split('\n');
|
|
45
|
+
if (lines.length < 2)
|
|
46
|
+
return stripped;
|
|
47
|
+
const body = lines.slice(1);
|
|
48
|
+
const last = body.at(-1);
|
|
49
|
+
if (last?.trim().startsWith('```'))
|
|
50
|
+
body.pop();
|
|
51
|
+
return body.join('\n');
|
|
52
|
+
}
|
|
53
|
+
function invalidJson(outputType) {
|
|
54
|
+
return new StructuredOutputError(`output did not validate as ${outputType.name}`, {
|
|
55
|
+
attempts: 1,
|
|
56
|
+
problems: ['<root>: invalid JSON'],
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Validate `text` as `outputType`.
|
|
61
|
+
*
|
|
62
|
+
* Every problem is reported, not just the first: feeding one error back at a time
|
|
63
|
+
* turns a two-round repair into a five-round one.
|
|
64
|
+
*/
|
|
65
|
+
export function parseStructured(text, outputType) {
|
|
66
|
+
const stripped = stripCodeFence(text).trim();
|
|
67
|
+
if (stripped.length === 0) {
|
|
68
|
+
throw new StructuredOutputError(`model returned empty output for ${outputType.name}`, {
|
|
69
|
+
attempts: 1,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
let parsed;
|
|
73
|
+
try {
|
|
74
|
+
parsed = JSON.parse(stripped);
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
throw invalidJson(outputType);
|
|
78
|
+
}
|
|
79
|
+
const result = outputType.schema.safeParse(parsed);
|
|
80
|
+
if (!result.success) {
|
|
81
|
+
throw new StructuredOutputError(`output did not validate as ${outputType.name}`, {
|
|
82
|
+
attempts: 1,
|
|
83
|
+
problems: formatProblems(result.error.issues),
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
return result.data;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Call, parse, and on failure ask again with the validation errors attached.
|
|
90
|
+
*
|
|
91
|
+
* `retries` counts *repair* attempts beyond the first call, so `retries: 2` means at
|
|
92
|
+
* most three calls. Throwing after the final attempt is correct: an endpoint that
|
|
93
|
+
* returns a half-valid object is worse than one that fails loudly.
|
|
94
|
+
*/
|
|
95
|
+
export async function runStructured(call, outputType, prompt, options = {}) {
|
|
96
|
+
const retries = options.retries ?? 2;
|
|
97
|
+
if (retries < 0)
|
|
98
|
+
throw new RangeError('retries must not be negative');
|
|
99
|
+
let current = prompt;
|
|
100
|
+
let problems = [];
|
|
101
|
+
for (let attempt = 1; attempt <= retries + 1; attempt += 1) {
|
|
102
|
+
const text = await call(current);
|
|
103
|
+
try {
|
|
104
|
+
return parseStructured(text, outputType);
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
if (!(error instanceof StructuredOutputError))
|
|
108
|
+
throw error;
|
|
109
|
+
problems = [...error.problems];
|
|
110
|
+
if (attempt <= retries) {
|
|
111
|
+
current = `${prompt}\n\n${repairPrompt(error)}`;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
throw new StructuredOutputError(`${outputType.name} not produced from ${retries + 1} attempt(s)`, { attempts: retries + 1, problems });
|
|
116
|
+
}
|
|
117
|
+
/** The corrective turn for a failed parse. */
|
|
118
|
+
export function repairPrompt(error) {
|
|
119
|
+
const problems = error.problems.length > 0 ? error.problems.join('; ') : error.message;
|
|
120
|
+
return REPAIR_INSTRUCTION.replace('{problems}', () => problems);
|
|
121
|
+
}
|
|
122
|
+
/** A typed agent: `run` drives the repair loop and returns the parsed value. */
|
|
123
|
+
export class StructuredAgent {
|
|
124
|
+
_model;
|
|
125
|
+
_outputType;
|
|
126
|
+
_systemPrompt;
|
|
127
|
+
_retries;
|
|
128
|
+
constructor(model, outputType, options = {}) {
|
|
129
|
+
this._model = model;
|
|
130
|
+
this._outputType = outputType;
|
|
131
|
+
this._systemPrompt = options.systemPrompt;
|
|
132
|
+
this._retries = options.retries ?? 2;
|
|
133
|
+
}
|
|
134
|
+
/** Run one prompt through the model, repairing until the schema validates. */
|
|
135
|
+
async run(prompt) {
|
|
136
|
+
const composed = this._systemPrompt === undefined ? prompt : `${this._systemPrompt}\n\n${prompt}`;
|
|
137
|
+
return runStructured((current) => this._model.complete(current), this._outputType, composed, {
|
|
138
|
+
retries: this._retries,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Build a typed agent over `outputType`.
|
|
144
|
+
*
|
|
145
|
+
* The reference runtime delegates this to Pydantic AI's `Agent`. The TypeScript
|
|
146
|
+
* runtime has no equivalent, so this is the same contract implemented against the
|
|
147
|
+
* repair loop above -- typed output with retries -- rather than a new orchestration
|
|
148
|
+
* layer.
|
|
149
|
+
*/
|
|
150
|
+
export function buildAgent(model, outputType, options = {}) {
|
|
151
|
+
return new StructuredAgent(model, outputType, options);
|
|
152
|
+
}
|
|
153
|
+
/** Re-export zod so generated code can declare schemas without a second dependency. */
|
|
154
|
+
export { z };
|
|
155
|
+
//# sourceMappingURL=structured.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"structured.js","sourceRoot":"","sources":["../src/structured.ts"],"names":[],"mappings":"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"}
|
package/dist/tools.d.ts
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool dispatch with schema validation.
|
|
3
|
+
*
|
|
4
|
+
* The one place this module cannot mirror the reference runtime is the seam itself:
|
|
5
|
+
* Python infers a tool's JSON schema from `inspect.signature` and a docstring, and
|
|
6
|
+
* JavaScript has neither. So the schema is declared explicitly and validated against
|
|
7
|
+
* the tool set here instead -- the runtime's job is to refuse a *wrong* invocation,
|
|
8
|
+
* and that job is identical either way.
|
|
9
|
+
*
|
|
10
|
+
* The validator implements a deliberate subset of JSON Schema: `type`, `required`
|
|
11
|
+
* and `additionalProperties: false`. That subset is documented rather than silently
|
|
12
|
+
* partial, and an unknown `type` value is treated as unconstrained instead of
|
|
13
|
+
* failing, because a schema written for a newer draft must not reject every call.
|
|
14
|
+
*/
|
|
15
|
+
import type { ConcurrencyLimiter } from './concurrency.js';
|
|
16
|
+
import { type ToolCall, ToolDefinition, ToolResult } from './types.js';
|
|
17
|
+
/** A handler receives validated arguments and may return anything serialisable. */
|
|
18
|
+
export type ToolHandler = (args: Record<string, unknown>) => unknown | Promise<unknown>;
|
|
19
|
+
/** The JSON-Schema subset this validator enforces. */
|
|
20
|
+
export interface ToolParameters {
|
|
21
|
+
readonly type?: string;
|
|
22
|
+
readonly properties?: Readonly<Record<string, {
|
|
23
|
+
readonly type?: string;
|
|
24
|
+
}>>;
|
|
25
|
+
readonly required?: readonly string[];
|
|
26
|
+
readonly additionalProperties?: boolean;
|
|
27
|
+
}
|
|
28
|
+
/** A registered tool: its public definition plus the code behind it. */
|
|
29
|
+
export declare class Tool {
|
|
30
|
+
readonly definition_: ToolDefinition;
|
|
31
|
+
readonly handler: ToolHandler;
|
|
32
|
+
constructor(init: {
|
|
33
|
+
readonly name: string;
|
|
34
|
+
readonly handler: ToolHandler;
|
|
35
|
+
readonly description?: string;
|
|
36
|
+
readonly parameters?: ToolParameters;
|
|
37
|
+
});
|
|
38
|
+
/**
|
|
39
|
+
* Declare a tool.
|
|
40
|
+
*
|
|
41
|
+
* `parameters` is required where Python would infer it. A schema whose `required`
|
|
42
|
+
* list names a property it does not define is a wiring mistake, and it is reported
|
|
43
|
+
* here rather than at the first call: that is the same failure Python reports as
|
|
44
|
+
* "not accepted by the handler", just discovered from the other direction.
|
|
45
|
+
*/
|
|
46
|
+
static of(init: {
|
|
47
|
+
readonly name: string;
|
|
48
|
+
readonly handler: ToolHandler;
|
|
49
|
+
readonly description?: string;
|
|
50
|
+
readonly parameters?: ToolParameters;
|
|
51
|
+
}): Tool;
|
|
52
|
+
get name(): string;
|
|
53
|
+
get definition(): ToolDefinition;
|
|
54
|
+
private checkSchema;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Validate `args` against `schema`, returning every problem rather than the first.
|
|
58
|
+
*
|
|
59
|
+
* Returning all problems is what makes a single repair turn enough: a model told
|
|
60
|
+
* about one bad field per round trips once per field.
|
|
61
|
+
*/
|
|
62
|
+
export declare function validateArguments(schema: ToolParameters, args: unknown): string[];
|
|
63
|
+
/**
|
|
64
|
+
* A set of tools, and the dispatcher for them.
|
|
65
|
+
*
|
|
66
|
+
* `strict` decides what a crashing handler means. The default is to hand the model
|
|
67
|
+
* an error result so it can react; strict mode rethrows, because a caller that has
|
|
68
|
+
* already decided a crash is a bug should not have to inspect a result to find out.
|
|
69
|
+
*/
|
|
70
|
+
export declare class ToolExecutor {
|
|
71
|
+
private readonly _tools;
|
|
72
|
+
private readonly _strict;
|
|
73
|
+
private readonly _limiter;
|
|
74
|
+
constructor(tools?: Iterable<Tool>, options?: {
|
|
75
|
+
readonly strict?: boolean;
|
|
76
|
+
readonly limiter?: ConcurrencyLimiter;
|
|
77
|
+
});
|
|
78
|
+
/** The registered names, sorted. */
|
|
79
|
+
names(): string[];
|
|
80
|
+
/** Add a tool. A duplicate name replaces, because a registry is a map. */
|
|
81
|
+
register(tool: Tool): void;
|
|
82
|
+
/** One tool's public definition. */
|
|
83
|
+
definition(name: string): ToolDefinition;
|
|
84
|
+
/** Every registered definition, ordered by name. */
|
|
85
|
+
definitions(): ToolDefinition[];
|
|
86
|
+
/** Whether a name is registered. */
|
|
87
|
+
has(name: string): boolean;
|
|
88
|
+
/**
|
|
89
|
+
* Run one call.
|
|
90
|
+
*
|
|
91
|
+
* An unknown tool throws: that is a wiring error in generated code, not something
|
|
92
|
+
* a model can fix by trying again with the same name.
|
|
93
|
+
*/
|
|
94
|
+
execute(call: ToolCall): Promise<ToolResult>;
|
|
95
|
+
private dispatch;
|
|
96
|
+
/** Run calls in order. Sequential on purpose: a tool set is a transcript, not a race. */
|
|
97
|
+
executeAll(calls: Iterable<ToolCall>): Promise<ToolResult[]>;
|
|
98
|
+
private require;
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=tools.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"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"}
|