@effect-uai/core 0.11.0 → 0.12.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/dist/{Items-BWNu6VGp.d.mts → Items-BKQgqJ8A.d.mts} +21 -8
- package/dist/Items-BKQgqJ8A.d.mts.map +1 -0
- package/dist/{Tool-CXFHCQrT.d.mts → Tool-u5bag8SO.d.mts} +2 -2
- package/dist/{Tool-CXFHCQrT.d.mts.map → Tool-u5bag8SO.d.mts.map} +1 -1
- package/dist/{ToolEvent-nVijCrvm.d.mts → ToolEvent-BBfOdhF_.d.mts} +2 -2
- package/dist/{ToolEvent-nVijCrvm.d.mts.map → ToolEvent-BBfOdhF_.d.mts.map} +1 -1
- package/dist/{ToolResult-CQqKkcDa.d.mts → ToolResult-CRpmkBXR.d.mts} +2 -2
- package/dist/{ToolResult-CQqKkcDa.d.mts.map → ToolResult-CRpmkBXR.d.mts.map} +1 -1
- package/dist/{Toolkit-BYPwwu-j.d.mts → Toolkit-D4mQvhjq.d.mts} +5 -5
- package/dist/{Toolkit-BYPwwu-j.d.mts.map → Toolkit-D4mQvhjq.d.mts.map} +1 -1
- package/dist/browser/BrowserTool.d.mts +1 -1
- package/dist/domain/Items.d.mts +1 -1
- package/dist/domain/Items.mjs +15 -1
- package/dist/domain/Items.mjs.map +1 -1
- package/dist/domain/Research.d.mts +1 -1
- package/dist/domain/Turn.d.mts +2 -1
- package/dist/domain/Turn.d.mts.map +1 -1
- package/dist/index.d.mts +5 -5
- package/dist/language-model/LanguageModel.d.mts +2 -2
- package/dist/observability/Metrics.d.mts +13 -5
- package/dist/observability/Metrics.d.mts.map +1 -1
- package/dist/observability/Metrics.mjs +30 -7
- package/dist/observability/Metrics.mjs.map +1 -1
- package/dist/observability/Metrics.test.mjs +42 -0
- package/dist/observability/Metrics.test.mjs.map +1 -1
- package/dist/retry/Retry.d.mts +3 -3
- package/dist/retry/Retry.d.mts.map +1 -1
- package/dist/retry/Retry.mjs +30 -1
- package/dist/retry/Retry.mjs.map +1 -1
- package/dist/testing/MockProvider.d.mts +1 -1
- package/dist/testing/MockSandbox.d.mts +1 -1
- package/dist/tool/Approval.d.mts +3 -3
- package/dist/tool/HistoryCheck.d.mts +2 -2
- package/dist/tool/Tool.d.mts +1 -1
- package/dist/tool/ToolEvent.d.mts +1 -1
- package/dist/tool/ToolResult.d.mts +1 -1
- package/dist/tool/Toolkit.d.mts +1 -1
- package/dist/web-read/WebReadTool.d.mts +1 -1
- package/dist/web-search/WebSearchTool.d.mts +1 -1
- package/package.json +1 -1
- package/src/domain/Items.ts +18 -0
- package/src/observability/Metrics.test.ts +65 -0
- package/src/observability/Metrics.ts +53 -11
- package/src/retry/Retry.ts +44 -3
- package/dist/Items-BWNu6VGp.d.mts.map +0 -1
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
Stream,
|
|
13
13
|
} from "effect"
|
|
14
14
|
import type { Usage } from "../domain/Items.js"
|
|
15
|
-
import type
|
|
15
|
+
import { type Turn, TurnEvent } from "../domain/Turn.js"
|
|
16
16
|
|
|
17
17
|
// ---------------------------------------------------------------------------
|
|
18
18
|
// Self-describing measurements (the exporter-facing payload)
|
|
@@ -126,6 +126,7 @@ const MetricName = {
|
|
|
126
126
|
totalTokens: "effect_uai_total_tokens",
|
|
127
127
|
reasoningTokens: "effect_uai_reasoning_tokens",
|
|
128
128
|
cachedInputTokens: "effect_uai_cached_input_tokens",
|
|
129
|
+
cacheWriteTokens: "effect_uai_cache_write_tokens",
|
|
129
130
|
} as const
|
|
130
131
|
|
|
131
132
|
/** Read a structural `_tag` off any value without narrowing to a closed union. */
|
|
@@ -141,6 +142,34 @@ const contentDeltaKind = (ev: unknown): Option.Option<"text" | "reasoning"> => {
|
|
|
141
142
|
: Option.none()
|
|
142
143
|
}
|
|
143
144
|
|
|
145
|
+
const isTextDelta = TurnEvent.$is("TextDelta")
|
|
146
|
+
const isReasoningDelta = TurnEvent.$is("ReasoningDelta")
|
|
147
|
+
const isRefusalDelta = TurnEvent.$is("RefusalDelta")
|
|
148
|
+
const isToolCallArgsDelta = TurnEvent.$is("ToolCallArgsDelta")
|
|
149
|
+
|
|
150
|
+
/** A delta carrying generated output, whatever the model was producing. */
|
|
151
|
+
export type OutputDelta = Extract<
|
|
152
|
+
TurnEvent,
|
|
153
|
+
{ readonly _tag: "TextDelta" | "ReasoningDelta" | "RefusalDelta" | "ToolCallArgsDelta" }
|
|
154
|
+
>
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Narrow to a delta that contributes to the model's output stream. Tool-call
|
|
158
|
+
* arguments count: an agent turn can be almost entirely `ToolCallArgsDelta`,
|
|
159
|
+
* and measuring only prose reports a near-zero rate for it.
|
|
160
|
+
*
|
|
161
|
+
* The single place the output-delta tag list lives — the throughput
|
|
162
|
+
* accumulator and `unitCount` both read it. Distinct from `contentDeltaKind`,
|
|
163
|
+
* which answers a narrower question for first-token latency.
|
|
164
|
+
*/
|
|
165
|
+
const outputDelta = (ev: unknown): Option.Option<OutputDelta> =>
|
|
166
|
+
isTextDelta(ev) || isReasoningDelta(ev) || isRefusalDelta(ev) || isToolCallArgsDelta(ev)
|
|
167
|
+
? Option.some(ev)
|
|
168
|
+
: Option.none()
|
|
169
|
+
|
|
170
|
+
const outputTextOf = (delta: OutputDelta): string =>
|
|
171
|
+
isToolCallArgsDelta(delta) ? delta.delta : delta.text
|
|
172
|
+
|
|
144
173
|
const isTurnCompleteEvent = (ev: unknown): boolean => tagOf(ev) === "TurnComplete"
|
|
145
174
|
|
|
146
175
|
const turnOf = (ev: unknown): Turn =>
|
|
@@ -154,6 +183,10 @@ const addUsage = (a: Usage, b: Usage): Usage => {
|
|
|
154
183
|
a.input_tokens_details?.cached_tokens,
|
|
155
184
|
b.input_tokens_details?.cached_tokens,
|
|
156
185
|
)
|
|
186
|
+
const cacheWrite = sumOptional(
|
|
187
|
+
a.input_tokens_details?.cache_write_tokens,
|
|
188
|
+
b.input_tokens_details?.cache_write_tokens,
|
|
189
|
+
)
|
|
157
190
|
const reasoning = sumOptional(
|
|
158
191
|
a.output_tokens_details?.reasoning_tokens,
|
|
159
192
|
b.output_tokens_details?.reasoning_tokens,
|
|
@@ -162,7 +195,13 @@ const addUsage = (a: Usage, b: Usage): Usage => {
|
|
|
162
195
|
input_tokens: sumOptional(a.input_tokens, b.input_tokens),
|
|
163
196
|
output_tokens: sumOptional(a.output_tokens, b.output_tokens),
|
|
164
197
|
total_tokens: sumOptional(a.total_tokens, b.total_tokens),
|
|
165
|
-
input_tokens_details:
|
|
198
|
+
input_tokens_details:
|
|
199
|
+
cached === undefined && cacheWrite === undefined
|
|
200
|
+
? undefined
|
|
201
|
+
: {
|
|
202
|
+
...(cached !== undefined && { cached_tokens: cached }),
|
|
203
|
+
...(cacheWrite !== undefined && { cache_write_tokens: cacheWrite }),
|
|
204
|
+
},
|
|
166
205
|
output_tokens_details: reasoning === undefined ? undefined : { reasoning_tokens: reasoning },
|
|
167
206
|
}
|
|
168
207
|
}
|
|
@@ -175,6 +214,7 @@ const usageMeasurements = (usage: Usage): ReadonlyArray<Measurement> => {
|
|
|
175
214
|
[MetricName.totalTokens, usage.total_tokens],
|
|
176
215
|
[MetricName.reasoningTokens, usage.output_tokens_details?.reasoning_tokens],
|
|
177
216
|
[MetricName.cachedInputTokens, usage.input_tokens_details?.cached_tokens],
|
|
217
|
+
[MetricName.cacheWriteTokens, usage.input_tokens_details?.cache_write_tokens],
|
|
178
218
|
]
|
|
179
219
|
return Arr.filterMap(pairs, ([name, value]) =>
|
|
180
220
|
value === undefined
|
|
@@ -349,8 +389,11 @@ export type ThroughputOptions = {
|
|
|
349
389
|
readonly every?: Duration.Input
|
|
350
390
|
/** What a "unit" is. Default `"char"` (exact and chunking-independent). */
|
|
351
391
|
readonly unit?: "char" | "token" | "event"
|
|
352
|
-
/**
|
|
353
|
-
|
|
392
|
+
/**
|
|
393
|
+
* Token counter for `unit: "token"`. Called only for deltas that carry
|
|
394
|
+
* generated output. Absent => approximate 1 per such delta.
|
|
395
|
+
*/
|
|
396
|
+
readonly tokenizer?: (event: OutputDelta) => Effect.Effect<number>
|
|
354
397
|
/** Rate definition. Default `"windowed"` (current speed, no cold-start bias). */
|
|
355
398
|
readonly mode?: "windowed" | "cumulative"
|
|
356
399
|
/** EWMA smoothing. Omitted => off; `"default"` => a sensible factor. */
|
|
@@ -434,14 +477,13 @@ const unitCount = (
|
|
|
434
477
|
unit: "char" | "token" | "event",
|
|
435
478
|
tokenizer: ThroughputOptions["tokenizer"],
|
|
436
479
|
): Effect.Effect<number> => {
|
|
437
|
-
|
|
438
|
-
|
|
480
|
+
const output = outputDelta(ev)
|
|
481
|
+
if (Option.isNone(output)) return Effect.succeed(0)
|
|
482
|
+
const delta = output.value
|
|
439
483
|
return Match.value(unit).pipe(
|
|
440
|
-
Match.when("char", () => Effect.succeed([...
|
|
484
|
+
Match.when("char", () => Effect.succeed([...outputTextOf(delta)].length)),
|
|
441
485
|
Match.when("event", () => Effect.succeed(1)),
|
|
442
|
-
Match.when("token", () =>
|
|
443
|
-
tokenizer === undefined ? Effect.succeed(1) : tokenizer(ev as TurnEvent),
|
|
444
|
-
),
|
|
486
|
+
Match.when("token", () => (tokenizer === undefined ? Effect.succeed(1) : tokenizer(delta))),
|
|
445
487
|
Match.exhaustive,
|
|
446
488
|
)
|
|
447
489
|
}
|
|
@@ -465,7 +507,7 @@ export const throughput =
|
|
|
465
507
|
Stream.tap((ev) =>
|
|
466
508
|
isTurnCompleteEvent(ev)
|
|
467
509
|
? Ref.update(ref, resetTurn)
|
|
468
|
-
:
|
|
510
|
+
: Option.isSome(outputDelta(ev))
|
|
469
511
|
? Effect.flatMap(Clock.currentTimeMillis, (now) =>
|
|
470
512
|
Effect.flatMap(unitCount(ev, unit, tokenizer), (n) =>
|
|
471
513
|
Ref.update(ref, (state) => addUnits(state, n, now)),
|
package/src/retry/Retry.ts
CHANGED
|
@@ -29,14 +29,55 @@ import type * as AiError from "../domain/AiError.js"
|
|
|
29
29
|
* variants if needed (e.g. a custom schedule that observes `cause`).
|
|
30
30
|
*/
|
|
31
31
|
export class Retryable extends Data.TaggedError("RetryableAi")<{
|
|
32
|
-
readonly cause:
|
|
32
|
+
readonly cause:
|
|
33
|
+
| AiError.RateLimited
|
|
34
|
+
| AiError.Unavailable
|
|
35
|
+
| AiError.Timeout
|
|
36
|
+
| AiError.InvalidRequest
|
|
33
37
|
}> {}
|
|
34
38
|
|
|
39
|
+
/**
|
|
40
|
+
* `InvalidRequest` payloads that are a transient fault on the provider's side
|
|
41
|
+
* rather than a defect in the request.
|
|
42
|
+
*
|
|
43
|
+
* The class exists because a provider can only answer within its own error
|
|
44
|
+
* vocabulary: a constrained-decoding grammar that times out while compiling is
|
|
45
|
+
* a cold-start timeout, but it arrives as `invalid_request_error` because that
|
|
46
|
+
* is where the vendor files schema problems. Replaying it succeeds — the same
|
|
47
|
+
* schema compiles on the next call.
|
|
48
|
+
*
|
|
49
|
+
* An allow-list rather than a general softening of `InvalidRequest`, and it
|
|
50
|
+
* must stay one. The whole point of that tag being fatal is that a genuinely
|
|
51
|
+
* malformed request retried on a schedule burns quota to arrive at the same
|
|
52
|
+
* rejection, slowly. An entry earns its place by having been observed to
|
|
53
|
+
* succeed on replay.
|
|
54
|
+
*/
|
|
55
|
+
const TRANSIENT_INVALID_REQUEST: ReadonlyArray<RegExp> = [/grammar compilation timed out/i]
|
|
56
|
+
|
|
57
|
+
/** The payload as text, for matching. `raw` is whatever the provider handed
|
|
58
|
+
* back — usually its response body as a string, sometimes a parsed object,
|
|
59
|
+
* and occasionally something that will not serialise. */
|
|
60
|
+
const rawText = (raw: unknown): string => {
|
|
61
|
+
if (typeof raw === "string") return raw
|
|
62
|
+
try {
|
|
63
|
+
return JSON.stringify(raw) ?? ""
|
|
64
|
+
} catch {
|
|
65
|
+
return String(raw)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const isTransientInvalidRequest = (e: AiError.AiError): boolean =>
|
|
70
|
+
e._tag === "InvalidRequest" &&
|
|
71
|
+
TRANSIENT_INVALID_REQUEST.some((pattern) => pattern.test(rawText(e.raw)))
|
|
72
|
+
|
|
35
73
|
/** Type-narrowing predicate for the retryable subset. */
|
|
36
74
|
export const isRetryable = (
|
|
37
75
|
e: AiError.AiError,
|
|
38
|
-
): e is AiError.RateLimited | AiError.Unavailable | AiError.Timeout =>
|
|
39
|
-
e._tag === "RateLimited" ||
|
|
76
|
+
): e is AiError.RateLimited | AiError.Unavailable | AiError.Timeout | AiError.InvalidRequest =>
|
|
77
|
+
e._tag === "RateLimited" ||
|
|
78
|
+
e._tag === "Unavailable" ||
|
|
79
|
+
e._tag === "Timeout" ||
|
|
80
|
+
isTransientInvalidRequest(e)
|
|
40
81
|
|
|
41
82
|
// Lift events to Items, non-retryable failures to Terminal values
|
|
42
83
|
// (escape retry), retryable failures to wrapped errors (only thing
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Items-BWNu6VGp.d.mts","names":[],"sources":["../src/domain/Items.ts"],"mappings":"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"}
|