@ultimat3/ai 27.8.1 → 27.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +137 -137
- package/README.md +95 -4
- package/package.json +11 -11
- package/src/agent-continue.ts +434 -0
- package/src/agent-declared.ts +157 -0
- package/src/agent-def.ts +134 -0
- package/src/agent-errors.ts +69 -0
- package/src/agent-guards.ts +114 -0
- package/src/agent-system.ts +37 -0
- package/src/agent-turn.ts +54 -0
- package/src/agent-usage.ts +63 -0
- package/src/agent.ts +190 -250
- package/src/answer-errors.ts +88 -0
- package/src/billed.ts +87 -0
- package/src/budget.ts +40 -12
- package/src/cost.ts +146 -0
- package/src/echo-provider.ts +3 -1
- package/src/errors-retry.ts +14 -0
- package/src/errors.ts +26 -19
- package/src/failure-disclosure.ts +37 -1
- package/src/gateway.ts +62 -9
- package/src/in-process-tool.ts +77 -0
- package/src/index.ts +47 -3
- package/src/llm-def.ts +97 -0
- package/src/llm.ts +89 -66
- package/src/models.ts +45 -1
- package/src/openai-body.ts +56 -4
- package/src/openai-messages.ts +57 -5
- package/src/openai-provider.ts +26 -5
- package/src/openai-wire.ts +184 -69
- package/src/prompt-cache-blocks.ts +114 -0
- package/src/prompt-cache.ts +189 -0
- package/src/provider.ts +71 -45
- package/src/run-meter.ts +147 -0
- package/src/sampling.ts +86 -0
- package/src/tool-arguments.ts +88 -0
- package/src/tools.ts +51 -6
- package/src/wire.ts +15 -14
package/src/gateway.ts
CHANGED
|
@@ -16,7 +16,8 @@ import {
|
|
|
16
16
|
stringField,
|
|
17
17
|
} from '@ultimat3/core';
|
|
18
18
|
import type { Money } from '@ultimat3/money';
|
|
19
|
-
import
|
|
19
|
+
import { billedOf } from './billed';
|
|
20
|
+
import type { BudgetKeys, BudgetLimits, BudgetReservation, BudgetStore } from './budget';
|
|
20
21
|
import {
|
|
21
22
|
assertFiniteLimits,
|
|
22
23
|
BudgetLedger,
|
|
@@ -28,6 +29,7 @@ import { AiProviderUnavailableError } from './errors';
|
|
|
28
29
|
import { resolveModel } from './model-resolve';
|
|
29
30
|
import type { ModelId } from './models';
|
|
30
31
|
import type { GenerateRequest, GenerateResult, Provider, StreamChunk } from './provider';
|
|
32
|
+
import type { SamplingWire } from './sampling';
|
|
31
33
|
|
|
32
34
|
/**
|
|
33
35
|
* Response cache. Structurally satisfied by `@ultimat3/cache`'s memo/LRU tiers; declared as
|
|
@@ -82,6 +84,8 @@ export interface ProviderGatewayInput {
|
|
|
82
84
|
}
|
|
83
85
|
|
|
84
86
|
export interface Gateway {
|
|
87
|
+
/** The wire formats of the providers serving `model` — what `llm.sampling.ignored` is read from. */
|
|
88
|
+
samplingWires?(model: ModelId): readonly SamplingWire[];
|
|
85
89
|
/**
|
|
86
90
|
* The model a call with none declared runs on — `providerGateway({ defaultModel })`. Read by
|
|
87
91
|
* `llm()` and `agent()` after the declaration and its prompt, so the app's choice is the one
|
|
@@ -180,17 +184,29 @@ class GatewayImpl implements Gateway {
|
|
|
180
184
|
// The estimate is DEBITED here, not merely checked: three concurrent calls under one ledger
|
|
181
185
|
// all read the same `spent()` otherwise, all pass, and all three record against a ceiling
|
|
182
186
|
// only one of them fitted.
|
|
183
|
-
const reservation = await ledger?.reserve(estimateSpend(resolved));
|
|
187
|
+
const reservation = await ledger?.reserve(estimateSpend(resolved, this.marks(model)));
|
|
184
188
|
|
|
185
189
|
let result: GenerateResult;
|
|
186
190
|
try {
|
|
187
191
|
result = await this.attempt(model, (provider) => provider.generate(resolved));
|
|
188
192
|
} catch (error) {
|
|
189
|
-
// A call that never landed must not go on holding its reservation
|
|
190
|
-
|
|
193
|
+
// A call that never landed must not go on holding its reservation — and one that DID land
|
|
194
|
+
// and still failed (`billedOf`: the provider answered with something unusable) is recorded
|
|
195
|
+
// as what it cost. Releasing it told the ledger a billed call was free.
|
|
196
|
+
await settleFailure(ledger, reservation, error);
|
|
191
197
|
throw error;
|
|
192
198
|
}
|
|
193
|
-
|
|
199
|
+
// The provider was PAID by now. Nothing below may turn that answer into a failure: a ledger
|
|
200
|
+
// that cannot book it gives the reservation back and says so, and the answer is returned.
|
|
201
|
+
try {
|
|
202
|
+
await ledger?.record(result.usage, result.costExact ?? result.cost, reservation);
|
|
203
|
+
} catch (error) {
|
|
204
|
+
await ledger?.release(reservation).catch(() => undefined);
|
|
205
|
+
logger.warn('ai.budget.record_failed', {
|
|
206
|
+
provider: result.provider ?? 'unknown',
|
|
207
|
+
error: renderThrowable(error),
|
|
208
|
+
});
|
|
209
|
+
}
|
|
194
210
|
// A refusal is not an answer, so it is not cached. Storing one would keep serving a decision
|
|
195
211
|
// the classifier might not make twice, long after the prompt that provoked it was fixed.
|
|
196
212
|
if (result.stopReason !== 'refusal') await this.remember(cacheKey, result);
|
|
@@ -230,7 +246,7 @@ class GatewayImpl implements Gateway {
|
|
|
230
246
|
const provider = this.providerFor(model);
|
|
231
247
|
|
|
232
248
|
const ledger = currentBudget() ?? this.callLedger({});
|
|
233
|
-
const reservation = await ledger?.reserve(estimateSpend(resolved));
|
|
249
|
+
const reservation = await ledger?.reserve(estimateSpend(resolved, this.marks(model)));
|
|
234
250
|
let settled = false;
|
|
235
251
|
try {
|
|
236
252
|
for await (const chunk of provider.stream(resolved)) {
|
|
@@ -240,17 +256,40 @@ class GatewayImpl implements Gateway {
|
|
|
240
256
|
}
|
|
241
257
|
// Settled only once `record` has LANDED. Marking it first left a store that threw here
|
|
242
258
|
// holding the reservation and half the record — the `finally` saw a settled stream.
|
|
243
|
-
|
|
259
|
+
const { usage, cost, costExact } = chunk.result;
|
|
260
|
+
await ledger?.record(usage, costExact ?? cost, reservation);
|
|
244
261
|
settled = true;
|
|
245
262
|
yield { type: 'done', result: { ...chunk.result, provider: provider.name } };
|
|
246
263
|
}
|
|
264
|
+
} catch (error) {
|
|
265
|
+
// Thrown after the provider reported usage: the bill is recorded, exactly as `generate` does.
|
|
266
|
+
// Only when nothing settled yet — a provider that throws AFTER its `done` chunk was already
|
|
267
|
+
// recorded, and releasing again would credit the ledger a second time.
|
|
268
|
+
if (!settled) {
|
|
269
|
+
settled = true;
|
|
270
|
+
await settleFailure(ledger, reservation, error);
|
|
271
|
+
}
|
|
272
|
+
throw error;
|
|
247
273
|
} finally {
|
|
248
|
-
// A stream
|
|
249
|
-
//
|
|
274
|
+
// A stream its consumer abandoned never reached `done` — so nothing reconciled the
|
|
275
|
+
// reservation and it would hold the ceiling for the rest of the window.
|
|
250
276
|
if (!settled) await ledger?.release(reservation);
|
|
251
277
|
}
|
|
252
278
|
}
|
|
253
279
|
|
|
280
|
+
samplingWires(model: ModelId): readonly SamplingWire[] {
|
|
281
|
+
return this.config.providers
|
|
282
|
+
.filter((p) => p.models.includes(model))
|
|
283
|
+
.flatMap((p) => (p.wire === undefined ? [] : [p.wire]));
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/** Whether any provider serving `model` would send a hinted request with a cache mark. */
|
|
287
|
+
private marks(model: ModelId): boolean {
|
|
288
|
+
return this.config.providers.some(
|
|
289
|
+
(p) => p.models.includes(model) && p.marksPromptCache?.(model) === true,
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
|
|
254
293
|
private providerFor(model: ModelId): Provider {
|
|
255
294
|
const provider = this.config.providers.find((p) => p.models.includes(model));
|
|
256
295
|
if (provider === undefined) {
|
|
@@ -319,6 +358,17 @@ class GatewayImpl implements Gateway {
|
|
|
319
358
|
}
|
|
320
359
|
}
|
|
321
360
|
|
|
361
|
+
/** A failed call's reservation: reconciled against its bill when the failure carries one, else given back. */
|
|
362
|
+
async function settleFailure(
|
|
363
|
+
ledger: BudgetLedger | undefined,
|
|
364
|
+
reservation: BudgetReservation | undefined,
|
|
365
|
+
error: unknown,
|
|
366
|
+
): Promise<void> {
|
|
367
|
+
const billed = billedOf(error);
|
|
368
|
+
if (billed === undefined) return ledger?.release(reservation);
|
|
369
|
+
await ledger?.record(billed.usage, billed.cost, reservation);
|
|
370
|
+
}
|
|
371
|
+
|
|
322
372
|
/**
|
|
323
373
|
* The request's completion ceiling, screened at the one seam every model call in an app passes
|
|
324
374
|
* through — `llm()`, `agent()`, an eval, a judge and a hand-built `generate()` alike.
|
|
@@ -416,5 +466,8 @@ export function promptCacheKey(request: GenerateRequest): string {
|
|
|
416
466
|
thinking: request.thinking ?? 'adaptive',
|
|
417
467
|
tools: request.tools ?? [],
|
|
418
468
|
stop: request.stopSequences ?? [],
|
|
469
|
+
// Only when asked for: an absent knob must leave every key written before it existed as it was.
|
|
470
|
+
...(request.temperature === undefined ? {} : { temperature: request.temperature }),
|
|
471
|
+
...(request.seed === undefined ? {} : { seed: request.seed }),
|
|
419
472
|
});
|
|
420
473
|
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// Single responsibility: `inProcessTool()` — a tool that exists only for an agent in this
|
|
2
|
+
// process. A name, a description, an input schema and an `execute`; nothing an MCP surface, an
|
|
3
|
+
// HTTP route, `openapi.json` or the manifest ever sees.
|
|
4
|
+
//
|
|
5
|
+
// Not a ninth primitive and not an action: it is a function the agent's own action may call, as
|
|
6
|
+
// code in that action's `handle` would be. The AGENT is the operation with a policy; this runs as
|
|
7
|
+
// `ctx.actor` inside it, and anything it reaches that needs its own authorization is an `action()`
|
|
8
|
+
// it calls — whose policy then decides, as always.
|
|
9
|
+
//
|
|
10
|
+
// It exists because the other way to give an agent a tool was to declare an action
|
|
11
|
+
// `mcp: { expose: true }` — the same flag that serves it to every external MCP client. A tool that
|
|
12
|
+
// must never be an MCP tool had to claim it was one.
|
|
13
|
+
|
|
14
|
+
import { InputInvalidError } from '@ultimat3/action';
|
|
15
|
+
import type { Actor } from '@ultimat3/core';
|
|
16
|
+
import type { InferOutput, StandardSchemaV1 } from '@ultimat3/schema';
|
|
17
|
+
import { formatIssues, toWireSchema, validateAsync } from '@ultimat3/schema';
|
|
18
|
+
import { AiRequestInvalidError } from './errors';
|
|
19
|
+
import { RESPOND } from './respond';
|
|
20
|
+
import type { ProjectableAction } from './tools';
|
|
21
|
+
|
|
22
|
+
export interface InProcessToolDef<TInput extends StandardSchemaV1> {
|
|
23
|
+
/** What the model calls it. Letters, digits, `_` and `-`, at most 64 — what every provider takes. */
|
|
24
|
+
readonly name: string;
|
|
25
|
+
/** What the model reads to decide when to call it. Required: a tool with none is never chosen well. */
|
|
26
|
+
readonly description: string;
|
|
27
|
+
/** Parses what the model sent before `execute` sees it; projected as the tool's input schema. */
|
|
28
|
+
readonly input: TInput;
|
|
29
|
+
/**
|
|
30
|
+
* Runs as the request's own actor — never one the model named. Throw an `UltimateError` to refuse:
|
|
31
|
+
* its code, cause and fix are what the model reads (`discloseErrors`).
|
|
32
|
+
*/
|
|
33
|
+
execute(args: { readonly input: InferOutput<TInput>; readonly actor: Actor }): unknown;
|
|
34
|
+
/** `false` withholds this tool's refusals from the model as a projected tool's are. Default `true`. */
|
|
35
|
+
readonly discloseErrors?: boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const NAME = /^[A-Za-z0-9_-]{1,64}$/;
|
|
39
|
+
|
|
40
|
+
/** A tool for `agent({ tools })` that is this process's alone. Declared once, at module scope. */
|
|
41
|
+
export function inProcessTool<TInput extends StandardSchemaV1>(
|
|
42
|
+
def: InProcessToolDef<TInput>,
|
|
43
|
+
): ProjectableAction {
|
|
44
|
+
if (!NAME.test(def.name) || def.name === RESPOND) {
|
|
45
|
+
throw new AiRequestInvalidError({
|
|
46
|
+
detail: `inProcessTool() was given the name ${JSON.stringify(def.name)}; a tool name is 1 to 64 letters, digits, "_" or "-", and "${RESPOND}" is the answer tool's`,
|
|
47
|
+
fix: "inProcessTool({ name: 'kbRead', … }) # letters, digits, _ and - only",
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
if (def.description.trim() === '') {
|
|
51
|
+
throw new AiRequestInvalidError({
|
|
52
|
+
detail: `inProcessTool("${def.name}") has an empty description, and the description is all the model has to decide when to call it`,
|
|
53
|
+
fix: `inProcessTool({ name: '${def.name}', description: 'Read one document by id.', … })`,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
name: def.name,
|
|
58
|
+
description: def.description,
|
|
59
|
+
// A schema the wire cannot express throws HERE, at declaration — as `respondFor`'s does.
|
|
60
|
+
inputJsonSchema: toWireSchema(def.input),
|
|
61
|
+
inProcess: true,
|
|
62
|
+
...(def.discloseErrors === undefined ? {} : { discloseErrors: def.discloseErrors }),
|
|
63
|
+
async run({ input, actor }) {
|
|
64
|
+
const parsed = await validateAsync(def.input, input);
|
|
65
|
+
if (parsed.issues !== undefined) {
|
|
66
|
+
// The line only: the structured issues are `@ultimat3/action`'s own shape, for a form.
|
|
67
|
+
throw new InputInvalidError(
|
|
68
|
+
def.name,
|
|
69
|
+
formatIssues(parsed.issues).join('; '),
|
|
70
|
+
undefined,
|
|
71
|
+
'tool',
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
return def.execute({ input: parsed.value, actor });
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -4,12 +4,38 @@
|
|
|
4
4
|
/** Re-exported so an `llm` file needs one import, not two. Same object as schema's. */
|
|
5
5
|
export type { Infer } from '@ultimat3/schema';
|
|
6
6
|
export { t } from '@ultimat3/schema';
|
|
7
|
-
export type { AgentBudget, AgentDef, AgentTurn, AgentVarsArgs } from './agent';
|
|
8
7
|
export { agent } from './agent';
|
|
8
|
+
export type {
|
|
9
|
+
AgentAction,
|
|
10
|
+
AgentContinuation,
|
|
11
|
+
AgentRun,
|
|
12
|
+
AgentTranscript,
|
|
13
|
+
} from './agent-continue';
|
|
14
|
+
export {
|
|
15
|
+
MAX_TRANSCRIPT_CHARS,
|
|
16
|
+
TRANSCRIPT_META,
|
|
17
|
+
TRANSCRIPT_VERSION,
|
|
18
|
+
transcriptOf,
|
|
19
|
+
} from './agent-continue';
|
|
20
|
+
export type { AgentBudget, AgentDef, AgentTurn, AgentVarsArgs } from './agent-def';
|
|
21
|
+
export {
|
|
22
|
+
AgentDeadlineError,
|
|
23
|
+
AgentToolLoopError,
|
|
24
|
+
AgentTranscriptInvalidError,
|
|
25
|
+
} from './agent-errors';
|
|
9
26
|
export type { AgentBudgetFact, AgentFact } from './agent-facts';
|
|
10
27
|
export { describeAgents, resetAgents } from './agent-facts';
|
|
11
28
|
export type { AgentJobOptions } from './agent-job';
|
|
12
29
|
export { agentJob } from './agent-job';
|
|
30
|
+
export type { AgentSystem } from './agent-system';
|
|
31
|
+
export type { AgentRunUsage } from './agent-usage';
|
|
32
|
+
export {
|
|
33
|
+
AiProviderUnexpectedError,
|
|
34
|
+
LlmToolArgumentsError,
|
|
35
|
+
LlmTruncatedError,
|
|
36
|
+
} from './answer-errors';
|
|
37
|
+
export type { Billed } from './billed';
|
|
38
|
+
export { BILLED_META, billedOf } from './billed';
|
|
13
39
|
export type {
|
|
14
40
|
BudgetKeys,
|
|
15
41
|
BudgetLedger,
|
|
@@ -45,6 +71,7 @@ export {
|
|
|
45
71
|
} from './content-blocks';
|
|
46
72
|
export type { ContentRefusal } from './content-errors';
|
|
47
73
|
export { AiContentUnsupportedError } from './content-errors';
|
|
74
|
+
export { COST_SCALE_DIGITS, costScale, exactCostOf, roundUpCost } from './cost';
|
|
48
75
|
export type { EchoProvider, EchoProviderInput } from './echo-provider';
|
|
49
76
|
export { echoProvider } from './echo-provider';
|
|
50
77
|
export type { Embedder, HashEmbedder, HashEmbedderInput } from './embeddings';
|
|
@@ -76,7 +103,6 @@ export {
|
|
|
76
103
|
LlmOutputInvalidError,
|
|
77
104
|
LlmRefusedError,
|
|
78
105
|
LlmStreamInvalidError,
|
|
79
|
-
LlmTruncatedError,
|
|
80
106
|
VectorDimMismatchError,
|
|
81
107
|
VectorScopeWidenedError,
|
|
82
108
|
} from './errors';
|
|
@@ -126,7 +152,9 @@ export type { HiveDef, HiveSplitArgs } from './hive';
|
|
|
126
152
|
export { hive } from './hive';
|
|
127
153
|
export { HiveEmptyError } from './hive-errors';
|
|
128
154
|
export type { HiveMember, HiveMemberError, HiveOutput, HiveResult } from './hive-result';
|
|
129
|
-
export type {
|
|
155
|
+
export type { InProcessToolDef } from './in-process-tool';
|
|
156
|
+
export { inProcessTool } from './in-process-tool';
|
|
157
|
+
export type { LlmAction, LlmBudget, LlmCall, LlmDef, LlmVarsArgs } from './llm';
|
|
130
158
|
export { llm } from './llm';
|
|
131
159
|
export type { LlmCache, LlmScopeArgs, LlmSemanticCache } from './llm-cache';
|
|
132
160
|
export type { LlmStreamChunk } from './llm-stream';
|
|
@@ -177,6 +205,21 @@ export {
|
|
|
177
205
|
promptVersions,
|
|
178
206
|
resetPrompts,
|
|
179
207
|
} from './prompt';
|
|
208
|
+
export type {
|
|
209
|
+
CacheWire,
|
|
210
|
+
PromptCacheHint,
|
|
211
|
+
PromptCacheOption,
|
|
212
|
+
PromptCacheRule,
|
|
213
|
+
SystemParts,
|
|
214
|
+
SystemPrompt,
|
|
215
|
+
} from './prompt-cache';
|
|
216
|
+
export {
|
|
217
|
+
OPENAI_CACHE_KEY_MAX,
|
|
218
|
+
PROMPT_CACHE_RULES,
|
|
219
|
+
promptCacheRule,
|
|
220
|
+
SESSION_KEY_MAX,
|
|
221
|
+
systemText,
|
|
222
|
+
} from './prompt-cache';
|
|
180
223
|
export { promptFences } from './prompt-fence';
|
|
181
224
|
export type {
|
|
182
225
|
AiContentBlock,
|
|
@@ -230,6 +273,7 @@ export {
|
|
|
230
273
|
configureAi,
|
|
231
274
|
semanticCacheFor,
|
|
232
275
|
} from './runtime';
|
|
276
|
+
export type { Sampling } from './sampling';
|
|
233
277
|
export type { Scorer } from './scorers';
|
|
234
278
|
export {
|
|
235
279
|
contains,
|
package/src/llm-def.ts
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// Single responsibility: what an `llm()` DECLARATION is — its options, and the one event its
|
|
2
|
+
// observer is handed. Types only; ./llm owns the call that reads them. `agent-def.ts`'s twin.
|
|
3
|
+
|
|
4
|
+
import type { ActionMcp, ActionPolicy } from '@ultimat3/action';
|
|
5
|
+
import type { Ctx } from '@ultimat3/core';
|
|
6
|
+
import type { Money } from '@ultimat3/money';
|
|
7
|
+
import type { InferOutput, StandardSchemaV1 } from '@ultimat3/schema';
|
|
8
|
+
import type { AgentRunUsage } from './agent-usage';
|
|
9
|
+
import type { LlmCache } from './llm-cache';
|
|
10
|
+
import type { ModelId } from './models';
|
|
11
|
+
import type { Prompt, PromptVars } from './prompt';
|
|
12
|
+
import type { PromptCacheOption } from './prompt-cache';
|
|
13
|
+
import type { StopReason, TokenUsage } from './provider';
|
|
14
|
+
|
|
15
|
+
/** Per-call ceilings, checked before the provider is reached. Never truncates — refuses. */
|
|
16
|
+
export interface LlmBudget {
|
|
17
|
+
/** Prompt tokens. */
|
|
18
|
+
readonly tokensIn?: number;
|
|
19
|
+
/** Worst-case price of one call, integer minor units. */
|
|
20
|
+
readonly costPerCall?: Money;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface LlmVarsArgs<TInput extends StandardSchemaV1> {
|
|
24
|
+
readonly input: InferOutput<TInput>;
|
|
25
|
+
readonly ctx: Ctx;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface LlmDef<
|
|
29
|
+
TInput extends StandardSchemaV1,
|
|
30
|
+
TOutput extends StandardSchemaV1,
|
|
31
|
+
V extends PromptVars,
|
|
32
|
+
> {
|
|
33
|
+
/**
|
|
34
|
+
* Overrides the prompt artifact's own model. The prompt's is part of its content hash and
|
|
35
|
+
* travels with it; this one is the deployment decision, so it wins.
|
|
36
|
+
*/
|
|
37
|
+
readonly model?: ModelId;
|
|
38
|
+
readonly input: TInput;
|
|
39
|
+
readonly output: TOutput;
|
|
40
|
+
readonly prompt: Prompt<V>;
|
|
41
|
+
/**
|
|
42
|
+
* Everything the prompt is allowed to see, derived from the parsed input. Required, and
|
|
43
|
+
* async, because the input is usually an id and the prompt needs the row behind it — this
|
|
44
|
+
* is the one declared place a model call loads data, so a reader can see what was sent.
|
|
45
|
+
*/
|
|
46
|
+
vars(args: LlmVarsArgs<TInput>): V | Promise<V>;
|
|
47
|
+
readonly cache?: LlmCache<InferOutput<TInput>>;
|
|
48
|
+
/**
|
|
49
|
+
* PROVIDER prompt caching (`./prompt-cache`) — not `cache`, which is this package's own semantic
|
|
50
|
+
* cache. Default `'off'`: a one-shot call marked for caching pays the write premium (1.25x on
|
|
51
|
+
* Anthropic) for a prefix nothing may ever read back. `'auto'` is for a call made many times
|
|
52
|
+
* inside the cache's lifetime with one long `system`; the repair turn reads it back either way.
|
|
53
|
+
*/
|
|
54
|
+
readonly promptCache?: PromptCacheOption<LlmVarsArgs<TInput>>;
|
|
55
|
+
readonly budget?: LlmBudget;
|
|
56
|
+
readonly policy: ActionPolicy;
|
|
57
|
+
readonly mcp?: ActionMcp;
|
|
58
|
+
/** Enforced completion ceiling. The model never sees it, so it can be cut off mid-answer. */
|
|
59
|
+
readonly maxTokens?: number;
|
|
60
|
+
/**
|
|
61
|
+
* `temperature` (0 to 2) and `seed` (a whole number), for a call that should answer the same way
|
|
62
|
+
* twice — a verifier, a classifier. Sent only to a model whose row takes them
|
|
63
|
+
* (`registerModel({ sampling })`); to any other they are NOT sent, because a reasoning model
|
|
64
|
+
* answers one with a 400 — the `llm.sampling.ignored` span attribute names what was left out.
|
|
65
|
+
* `seed` exists on the chat-completions wire only, and is best effort where it exists.
|
|
66
|
+
*/
|
|
67
|
+
readonly temperature?: number;
|
|
68
|
+
readonly seed?: number;
|
|
69
|
+
/**
|
|
70
|
+
* Called once per PROVIDER call this invocation makes — the answer, and the repair turn when
|
|
71
|
+
* there is one — before the answer is read. What `agent()`'s `onTurn` is to a run: the usage and
|
|
72
|
+
* cost of each call, and on a repair call the issues the model was told to fix.
|
|
73
|
+
*
|
|
74
|
+
* Observation only. Awaited, and a throw from it FAILS the call, for the reason `onTurn` gives.
|
|
75
|
+
* Not called on a semantic-cache hit: no provider call was made, and nothing was billed.
|
|
76
|
+
*/
|
|
77
|
+
onCall?(event: LlmCall): void | Promise<void>;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** One completed provider call of an `llm()` invocation, as an observer sees it. Facts only. */
|
|
81
|
+
export interface LlmCall {
|
|
82
|
+
/** 1 is the answer; 2 is the repair turn. A streamed call is always 1. */
|
|
83
|
+
readonly attempt: number;
|
|
84
|
+
readonly model: ModelId;
|
|
85
|
+
/** Which provider answered, as the gateway stamped it. */
|
|
86
|
+
readonly provider: string | undefined;
|
|
87
|
+
readonly stopReason: StopReason;
|
|
88
|
+
readonly usage: TokenUsage;
|
|
89
|
+
/** This call's cost alone, integer minor units, rounded up. */
|
|
90
|
+
readonly cost: Money;
|
|
91
|
+
/** This call's cost exactly (`Money` at a finer `scale`); absent when the provider stated none. */
|
|
92
|
+
readonly costExact?: Money;
|
|
93
|
+
/** On the repair turn: the schema issues of the first answer — what the model was told to fix. */
|
|
94
|
+
readonly repairOf?: readonly string[];
|
|
95
|
+
/** The invocation so far, this call included — priced once from the summed usage. */
|
|
96
|
+
readonly run: AgentRunUsage;
|
|
97
|
+
}
|