@llm4ts/flow 2.29.0 → 2.30.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/dist/PlanExecution.d.ts +7 -1
- package/dist/PlanExecution.d.ts.map +1 -1
- package/dist/PlanExecution.js +8 -2
- package/dist/PlanExecution.js.map +1 -1
- package/dist/Review.d.ts.map +1 -1
- package/dist/Review.js +5 -3
- package/dist/Review.js.map +1 -1
- package/dist/Spans.d.ts +63 -0
- package/dist/Spans.d.ts.map +1 -0
- package/dist/Spans.js +104 -0
- package/dist/Spans.js.map +1 -0
- package/dist/Stories.d.ts.map +1 -1
- package/dist/Stories.js +61 -23
- package/dist/Stories.js.map +1 -1
- package/dist/Timing.d.ts +21 -1
- package/dist/Timing.d.ts.map +1 -1
- package/dist/Timing.js +218 -18
- package/dist/Timing.js.map +1 -1
- package/package.json +3 -2
- package/src/PlanExecution.ts +21 -10
- package/src/Review.ts +18 -12
- package/src/Spans.ts +150 -0
- package/src/Stories.ts +88 -34
- package/src/Timing.ts +343 -16
package/src/Timing.ts
CHANGED
|
@@ -2,20 +2,164 @@ import * as Clock from "effect/Clock"
|
|
|
2
2
|
import * as Context from "effect/Context"
|
|
3
3
|
import * as Effect from "effect/Effect"
|
|
4
4
|
import * as Exit from "effect/Exit"
|
|
5
|
+
import * as Metric from "effect/Metric"
|
|
6
|
+
import * as Option from "effect/Option"
|
|
5
7
|
import * as Ref from "effect/Ref"
|
|
6
8
|
import * as Stream from "effect/Stream"
|
|
9
|
+
import type * as Tracer from "effect/Tracer"
|
|
7
10
|
import type { JudgmentShape } from "@llm4ts/core/judgment/Judgment"
|
|
8
11
|
import type { LlmServiceShape } from "@llm4ts/core/LlmService"
|
|
9
|
-
import type
|
|
12
|
+
import { Message, type LlmChunk, type TokenUsage } from "@llm4ts/core/Models"
|
|
13
|
+
import { redactText } from "@llm4ts/core/observability/Redaction"
|
|
14
|
+
import { toolCategory } from "./Activity.ts"
|
|
15
|
+
import { estimateUsage, estimatedModelLabel, type EstimatedUsageOptions } from "./EstimatedUsage.ts"
|
|
10
16
|
import { Timed, type FlowEventsShape } from "./FlowEvents.ts"
|
|
17
|
+
import {
|
|
18
|
+
attr,
|
|
19
|
+
currentKindSpan,
|
|
20
|
+
kindAttribute,
|
|
21
|
+
usageAttributes,
|
|
22
|
+
withKindSpan,
|
|
23
|
+
type OtelContent
|
|
24
|
+
} from "./Spans.ts"
|
|
11
25
|
|
|
12
26
|
/**
|
|
13
27
|
* Where a run's time goes. `timedSeat` decorates a seat so every
|
|
14
28
|
* call publishes a `Timed{kind:"model"}` when it ends — its wall time, when
|
|
15
29
|
* its first output came, and the API and tool time the backend reported, if
|
|
16
30
|
* it did. The events carry the seat's role, never the prompt or the reply.
|
|
31
|
+
*
|
|
32
|
+
* The same decorator makes every call an LLM span (ADR 0026) with the GenAI
|
|
33
|
+
* usage attributes, each harness tool call a TOOL child of it, and the four
|
|
34
|
+
* counters below; content joins the spans only when `content` says so.
|
|
17
35
|
*/
|
|
18
36
|
|
|
37
|
+
export interface TimedSeatOptions {
|
|
38
|
+
/** Who serves the seat, read at each call (a roster hands the coder over). */
|
|
39
|
+
readonly executor?: Effect.Effect<string | undefined>
|
|
40
|
+
/** What content the spans may carry (ADR 0026); off by default. */
|
|
41
|
+
readonly content?: OtelContent
|
|
42
|
+
/** When set, a call the backend reports no usage for gets an estimate on its span, flagged. */
|
|
43
|
+
readonly estimate?: EstimatedUsageOptions
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const tokensCounter = Metric.counter("llm4ts.tokens", {
|
|
47
|
+
description: "tokens by model, role, executor and direction",
|
|
48
|
+
incremental: true
|
|
49
|
+
})
|
|
50
|
+
const costCounter = Metric.counter("llm4ts.cost.usd", {
|
|
51
|
+
description: "cost the backend reported, USD",
|
|
52
|
+
incremental: true
|
|
53
|
+
})
|
|
54
|
+
const callsCounter = Metric.counter("llm4ts.model.calls", {
|
|
55
|
+
description: "model calls by role and executor",
|
|
56
|
+
incremental: true
|
|
57
|
+
})
|
|
58
|
+
const toolCallsCounter = Metric.counter("llm4ts.tool.calls", {
|
|
59
|
+
description: "harness tool calls by kind of work",
|
|
60
|
+
incremental: true
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
const inputChars = 64_000
|
|
64
|
+
const partChars = 4_000
|
|
65
|
+
|
|
66
|
+
const clean = (text: string, limit: number): string => redactText(text, { maxLength: limit })
|
|
67
|
+
|
|
68
|
+
/** The prompt as content attributes, or nothing: the system prompt only under `full`. */
|
|
69
|
+
const promptContent = (
|
|
70
|
+
content: OtelContent,
|
|
71
|
+
messages: ReadonlyArray<Message>
|
|
72
|
+
): Record<string, unknown> => {
|
|
73
|
+
if (content === "off") {
|
|
74
|
+
return {}
|
|
75
|
+
}
|
|
76
|
+
const shown = messages.filter((message) => content === "full" || message.role !== "System")
|
|
77
|
+
return {
|
|
78
|
+
[attr.input]: clean(
|
|
79
|
+
shown.map((message) => `${message.role}: ${message.content}`).join("\n\n"),
|
|
80
|
+
inputChars
|
|
81
|
+
),
|
|
82
|
+
[attr.inputMessages]: JSON.stringify(
|
|
83
|
+
shown.map((message) => ({
|
|
84
|
+
role: message.role.toLowerCase(),
|
|
85
|
+
content: clean(message.content, inputChars)
|
|
86
|
+
}))
|
|
87
|
+
)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const replyContent = (content: OtelContent, text: string): Record<string, unknown> =>
|
|
92
|
+
content === "off"
|
|
93
|
+
? {}
|
|
94
|
+
: {
|
|
95
|
+
[attr.output]: clean(text, inputChars),
|
|
96
|
+
[attr.outputMessages]: JSON.stringify([
|
|
97
|
+
{ role: "assistant", content: clean(text, inputChars) }
|
|
98
|
+
])
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const count = (
|
|
102
|
+
counter: Metric.Counter<number>,
|
|
103
|
+
attributes: Record<string, string>,
|
|
104
|
+
by: number
|
|
105
|
+
): Effect.Effect<void> =>
|
|
106
|
+
by === 0 ? Effect.void : Metric.update(by)(Metric.withAttributes(attributes)(counter))
|
|
107
|
+
|
|
108
|
+
/** The usage and the model a call ends with: measured from the chunks, else estimated when asked. */
|
|
109
|
+
const settleUsage = (
|
|
110
|
+
measured: TokenUsage | undefined,
|
|
111
|
+
model: string | undefined,
|
|
112
|
+
promptChars: number,
|
|
113
|
+
completionChars: number,
|
|
114
|
+
estimate: EstimatedUsageOptions | undefined
|
|
115
|
+
): {
|
|
116
|
+
readonly usage: TokenUsage | undefined
|
|
117
|
+
readonly model: string | undefined
|
|
118
|
+
readonly estimated: boolean
|
|
119
|
+
} =>
|
|
120
|
+
measured !== undefined
|
|
121
|
+
? { usage: measured, model, estimated: false }
|
|
122
|
+
: estimate === undefined
|
|
123
|
+
? { usage: undefined, model, estimated: false }
|
|
124
|
+
: {
|
|
125
|
+
usage: estimateUsage(promptChars, completionChars, estimate),
|
|
126
|
+
model: model ?? estimatedModelLabel(estimate.referenceModel),
|
|
127
|
+
estimated: true
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const recordCall = (
|
|
131
|
+
role: string,
|
|
132
|
+
executor: string | undefined,
|
|
133
|
+
settled: ReturnType<typeof settleUsage>,
|
|
134
|
+
failed: boolean
|
|
135
|
+
): Effect.Effect<void> => {
|
|
136
|
+
const base = {
|
|
137
|
+
role,
|
|
138
|
+
executor: executor ?? "",
|
|
139
|
+
model: settled.model ?? "",
|
|
140
|
+
estimated: String(settled.estimated)
|
|
141
|
+
}
|
|
142
|
+
return Effect.all(
|
|
143
|
+
[
|
|
144
|
+
count(callsCounter, { role, executor: executor ?? "", failed: String(failed) }, 1),
|
|
145
|
+
settled.usage === undefined
|
|
146
|
+
? Effect.void
|
|
147
|
+
: Effect.all([
|
|
148
|
+
count(tokensCounter, { ...base, direction: "input" }, settled.usage.prompt),
|
|
149
|
+
count(tokensCounter, { ...base, direction: "output" }, settled.usage.completion),
|
|
150
|
+
settled.estimated || settled.usage.costUsd === undefined
|
|
151
|
+
? Effect.void
|
|
152
|
+
: count(
|
|
153
|
+
costCounter,
|
|
154
|
+
{ role, executor: executor ?? "", model: settled.model ?? "" },
|
|
155
|
+
settled.usage.costUsd
|
|
156
|
+
)
|
|
157
|
+
])
|
|
158
|
+
],
|
|
159
|
+
{ discard: true }
|
|
160
|
+
)
|
|
161
|
+
}
|
|
162
|
+
|
|
19
163
|
/**
|
|
20
164
|
* The role a call is made for, when it is not the seat's own: one seat
|
|
21
165
|
* serves the reviewer, the story judge and the BLOCKED_ON verifier when no
|
|
@@ -53,18 +197,145 @@ export const timeEffect = <A, E, R>(
|
|
|
53
197
|
)
|
|
54
198
|
)
|
|
55
199
|
|
|
200
|
+
/**
|
|
201
|
+
* `timeEffect` under an LLM span named for the role; `usageOf` reads what
|
|
202
|
+
* the result says about tokens and model, when it says anything.
|
|
203
|
+
*/
|
|
204
|
+
const tracedEffect = <A, E, R>(
|
|
205
|
+
events: FlowEventsShape,
|
|
206
|
+
label: string,
|
|
207
|
+
options: TimedSeatOptions,
|
|
208
|
+
effect: Effect.Effect<A, E, R>,
|
|
209
|
+
timed: (ms: number, failed: boolean, role: string | undefined) => Timed,
|
|
210
|
+
usageOf: (value: A) => {
|
|
211
|
+
readonly usage: TokenUsage | undefined
|
|
212
|
+
readonly model: string | undefined
|
|
213
|
+
}
|
|
214
|
+
): Effect.Effect<A, E, R> =>
|
|
215
|
+
Effect.gen(function* () {
|
|
216
|
+
const role = (yield* TimedRole) ?? label
|
|
217
|
+
const executor = options.executor === undefined ? undefined : yield* options.executor
|
|
218
|
+
return yield* withKindSpan(
|
|
219
|
+
role,
|
|
220
|
+
{
|
|
221
|
+
kind: "LLM",
|
|
222
|
+
attributes: {
|
|
223
|
+
[attr.role]: role,
|
|
224
|
+
[attr.operation]: "generate",
|
|
225
|
+
...(executor === undefined ? {} : { [attr.executor]: executor })
|
|
226
|
+
}
|
|
227
|
+
},
|
|
228
|
+
timeEffect(events, effect, timed).pipe(
|
|
229
|
+
Effect.onExit((exit) => {
|
|
230
|
+
const known = Exit.isSuccess(exit)
|
|
231
|
+
? usageOf(exit.value)
|
|
232
|
+
: { usage: undefined, model: undefined }
|
|
233
|
+
const settled = settleUsage(known.usage, known.model, 0, 0, undefined)
|
|
234
|
+
return Effect.all(
|
|
235
|
+
[
|
|
236
|
+
Effect.annotateCurrentSpan(
|
|
237
|
+
usageAttributes(settled.model, settled.usage, settled.estimated)
|
|
238
|
+
),
|
|
239
|
+
recordCall(role, executor, settled, Exit.isFailure(exit))
|
|
240
|
+
],
|
|
241
|
+
{ discard: true }
|
|
242
|
+
)
|
|
243
|
+
})
|
|
244
|
+
)
|
|
245
|
+
)
|
|
246
|
+
})
|
|
247
|
+
|
|
56
248
|
const timedStream = <E>(
|
|
57
249
|
events: FlowEventsShape,
|
|
58
250
|
label: string,
|
|
251
|
+
options: TimedSeatOptions,
|
|
252
|
+
messages: ReadonlyArray<Message>,
|
|
59
253
|
stream: Stream.Stream<LlmChunk, E>
|
|
60
254
|
): Stream.Stream<LlmChunk, E> =>
|
|
61
255
|
Stream.unwrap(
|
|
62
256
|
Effect.gen(function* () {
|
|
63
257
|
const start = yield* Clock.currentTimeMillis
|
|
64
|
-
const role = yield* TimedRole
|
|
258
|
+
const role = (yield* TimedRole) ?? label
|
|
259
|
+
const executor = options.executor === undefined ? undefined : yield* options.executor
|
|
260
|
+
const content = options.content ?? "off"
|
|
65
261
|
const first = yield* Ref.make<number | undefined>(undefined)
|
|
66
262
|
const reported = yield* Ref.make<{ apiMs?: number; toolMs?: number }>({})
|
|
67
263
|
const failed = yield* Ref.make(false)
|
|
264
|
+
const usage = yield* Ref.make<TokenUsage | undefined>(undefined)
|
|
265
|
+
const model = yield* Ref.make<string | undefined>(undefined)
|
|
266
|
+
const reply = yield* Ref.make("")
|
|
267
|
+
// The call's LLM span (ADR 0026), parented to the nearest kind span and
|
|
268
|
+
// ended when the stream does; each harness tool call is a TOOL child,
|
|
269
|
+
// opened on `tool_use` and closed on `tool_result`.
|
|
270
|
+
const parent = yield* currentKindSpan
|
|
271
|
+
const span = yield* Effect.makeSpan(role, {
|
|
272
|
+
kind: "client",
|
|
273
|
+
sampled: true,
|
|
274
|
+
...(Option.isSome(parent) ? { parent: parent.value } : {}),
|
|
275
|
+
attributes: {
|
|
276
|
+
[kindAttribute]: "LLM",
|
|
277
|
+
[attr.role]: role,
|
|
278
|
+
[attr.operation]: "chat",
|
|
279
|
+
...(executor === undefined ? {} : { [attr.executor]: executor }),
|
|
280
|
+
...promptContent(content, messages)
|
|
281
|
+
}
|
|
282
|
+
})
|
|
283
|
+
const openTools = yield* Ref.make<
|
|
284
|
+
ReadonlyArray<{ readonly id: string | undefined; readonly span: Tracer.Span }>
|
|
285
|
+
>([])
|
|
286
|
+
const toolStarted = (chunk: LlmChunk) =>
|
|
287
|
+
Effect.gen(function* () {
|
|
288
|
+
const tool = (chunk.metadata.tool_name ?? chunk.metadata.toolName ?? "").trim()
|
|
289
|
+
if (tool.length === 0) {
|
|
290
|
+
return
|
|
291
|
+
}
|
|
292
|
+
const args = chunk.metadata.tool_input ?? chunk.metadata.toolInput ?? ""
|
|
293
|
+
const category = toolCategory(tool, args)
|
|
294
|
+
// `Effect.makeSpan`, not the tracer directly: the scope's annotations
|
|
295
|
+
// (story, epic, session) land on it like on every other span.
|
|
296
|
+
const child = yield* Effect.makeSpan(tool, {
|
|
297
|
+
parent: span,
|
|
298
|
+
kind: "internal",
|
|
299
|
+
sampled: true,
|
|
300
|
+
attributes: {
|
|
301
|
+
[kindAttribute]: "TOOL",
|
|
302
|
+
[attr.toolCategory]: category,
|
|
303
|
+
"tool.name": tool,
|
|
304
|
+
...(content === "off" ? {} : { [attr.input]: clean(args, partChars) })
|
|
305
|
+
}
|
|
306
|
+
})
|
|
307
|
+
const id = chunk.metadata.tool_id ?? chunk.metadata.toolId
|
|
308
|
+
yield* Ref.update(openTools, (open) => [
|
|
309
|
+
...open,
|
|
310
|
+
{ id: id === undefined || id.length === 0 ? undefined : id, span: child }
|
|
311
|
+
])
|
|
312
|
+
yield* count(toolCallsCounter, { category }, 1)
|
|
313
|
+
})
|
|
314
|
+
const toolEnded = (chunk: LlmChunk) =>
|
|
315
|
+
Effect.gen(function* () {
|
|
316
|
+
const id = chunk.metadata.tool_id ?? chunk.metadata.toolId
|
|
317
|
+
const found = yield* Ref.modify(openTools, (open) => {
|
|
318
|
+
const index =
|
|
319
|
+
id === undefined || id.length === 0 ? 0 : open.findIndex((entry) => entry.id === id)
|
|
320
|
+
const entry = index < 0 ? undefined : open[index]
|
|
321
|
+
return [
|
|
322
|
+
entry,
|
|
323
|
+
entry === undefined ? open : [...open.slice(0, index), ...open.slice(index + 1)]
|
|
324
|
+
] as const
|
|
325
|
+
})
|
|
326
|
+
if (found === undefined) {
|
|
327
|
+
return
|
|
328
|
+
}
|
|
329
|
+
const toolFailed =
|
|
330
|
+
chunk.metadata.tool_failed === "true" || chunk.metadata.tool_status === "error"
|
|
331
|
+
if (content !== "off") {
|
|
332
|
+
found.span.attribute(attr.output, clean(chunk.metadata.tool_content ?? "", partChars))
|
|
333
|
+
}
|
|
334
|
+
found.span.end(
|
|
335
|
+
yield* Clock.currentTimeNanos,
|
|
336
|
+
toolFailed ? Exit.fail("tool failed") : Exit.void
|
|
337
|
+
)
|
|
338
|
+
})
|
|
68
339
|
return stream.pipe(
|
|
69
340
|
Stream.tap((chunk) =>
|
|
70
341
|
Effect.gen(function* () {
|
|
@@ -79,6 +350,20 @@ const timedStream = <E>(
|
|
|
79
350
|
...(toolMs === undefined ? {} : { toolMs })
|
|
80
351
|
}))
|
|
81
352
|
}
|
|
353
|
+
if (chunk.usage !== undefined) {
|
|
354
|
+
yield* Ref.set(usage, chunk.usage)
|
|
355
|
+
}
|
|
356
|
+
if (chunk.metadata.model !== undefined) {
|
|
357
|
+
yield* Ref.set(model, chunk.metadata.model)
|
|
358
|
+
}
|
|
359
|
+
if (chunk.delta.length > 0) {
|
|
360
|
+
yield* Ref.update(reply, (text) => text + chunk.delta)
|
|
361
|
+
}
|
|
362
|
+
if (chunk.metadata.event === "tool_use") {
|
|
363
|
+
yield* toolStarted(chunk)
|
|
364
|
+
} else if (chunk.metadata.event === "tool_result") {
|
|
365
|
+
yield* toolEnded(chunk)
|
|
366
|
+
}
|
|
82
367
|
})
|
|
83
368
|
),
|
|
84
369
|
Stream.tapError(() => Ref.set(failed, true)),
|
|
@@ -86,16 +371,37 @@ const timedStream = <E>(
|
|
|
86
371
|
Effect.gen(function* () {
|
|
87
372
|
const end = yield* Clock.currentTimeMillis
|
|
88
373
|
const firstMs = yield* Ref.get(first)
|
|
374
|
+
const didFail = yield* Ref.get(failed)
|
|
89
375
|
yield* events.publish(
|
|
90
376
|
Timed.make({
|
|
91
377
|
kind: "model",
|
|
92
|
-
label: role
|
|
378
|
+
label: role,
|
|
93
379
|
ms: end - start,
|
|
94
380
|
...(firstMs === undefined ? {} : { firstMs }),
|
|
95
381
|
...(yield* Ref.get(reported)),
|
|
96
|
-
...(
|
|
382
|
+
...(didFail ? { failed: true } : {})
|
|
97
383
|
})
|
|
98
384
|
)
|
|
385
|
+
const text = yield* Ref.get(reply)
|
|
386
|
+
const settled = settleUsage(
|
|
387
|
+
yield* Ref.get(usage),
|
|
388
|
+
yield* Ref.get(model),
|
|
389
|
+
messages.reduce((sum, message) => sum + message.content.length, 0),
|
|
390
|
+
text.length,
|
|
391
|
+
options.estimate
|
|
392
|
+
)
|
|
393
|
+
for (const [key, value] of Object.entries({
|
|
394
|
+
...usageAttributes(settled.model, settled.usage, settled.estimated),
|
|
395
|
+
...replyContent(content, text)
|
|
396
|
+
})) {
|
|
397
|
+
span.attribute(key, value)
|
|
398
|
+
}
|
|
399
|
+
yield* recordCall(role, executor, settled, didFail)
|
|
400
|
+
const nanos = yield* Clock.currentTimeNanos
|
|
401
|
+
for (const entry of yield* Ref.get(openTools)) {
|
|
402
|
+
entry.span.end(nanos, Exit.void)
|
|
403
|
+
}
|
|
404
|
+
span.end(nanos, didFail ? Exit.fail("stream failed") : Exit.void)
|
|
99
405
|
})
|
|
100
406
|
)
|
|
101
407
|
)
|
|
@@ -105,27 +411,48 @@ const timedStream = <E>(
|
|
|
105
411
|
export const timedSeat = (
|
|
106
412
|
service: LlmServiceShape,
|
|
107
413
|
events: FlowEventsShape,
|
|
108
|
-
label: string
|
|
414
|
+
label: string,
|
|
415
|
+
options: TimedSeatOptions = {}
|
|
109
416
|
): LlmServiceShape => {
|
|
110
417
|
const model = (ms: number, failed: boolean, role: string | undefined): Timed =>
|
|
111
418
|
Timed.make({ kind: "model", label: role ?? label, ms, ...(failed ? { failed: true } : {}) })
|
|
419
|
+
const nothing = () => ({ usage: undefined, model: undefined })
|
|
420
|
+
const prompt = (text: string): ReadonlyArray<Message> => [
|
|
421
|
+
Message.make({ role: "User", content: text })
|
|
422
|
+
]
|
|
112
423
|
const batched = service.scoreLabelSequence
|
|
113
424
|
return {
|
|
114
|
-
executeStream: (
|
|
425
|
+
executeStream: (text) =>
|
|
426
|
+
timedStream(events, label, options, prompt(text), service.executeStream(text)),
|
|
115
427
|
executeStreamWithHistory: (messages) =>
|
|
116
|
-
timedStream(events, label, service.executeStreamWithHistory(messages)),
|
|
117
|
-
executeWithTools: (
|
|
118
|
-
|
|
119
|
-
executeStructured: (
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
428
|
+
timedStream(events, label, options, messages, service.executeStreamWithHistory(messages)),
|
|
429
|
+
executeWithTools: (text, tools) =>
|
|
430
|
+
tracedEffect(events, label, options, service.executeWithTools(text, tools), model, nothing),
|
|
431
|
+
executeStructured: (text, schema, jsonSchema) =>
|
|
432
|
+
tracedEffect(
|
|
433
|
+
events,
|
|
434
|
+
label,
|
|
435
|
+
options,
|
|
436
|
+
service.executeStructured(text, schema, jsonSchema),
|
|
437
|
+
model,
|
|
438
|
+
nothing
|
|
439
|
+
),
|
|
440
|
+
executeStructuredWithUsage: (text, schema, jsonSchema) =>
|
|
441
|
+
tracedEffect(
|
|
442
|
+
events,
|
|
443
|
+
label,
|
|
444
|
+
options,
|
|
445
|
+
service.executeStructuredWithUsage(text, schema, jsonSchema),
|
|
446
|
+
model,
|
|
447
|
+
([, usage, modelName]) => ({ usage, model: modelName })
|
|
448
|
+
),
|
|
449
|
+
scoreLabels: (text, labels) =>
|
|
450
|
+
tracedEffect(events, label, options, service.scoreLabels(text, labels), model, nothing),
|
|
124
451
|
...(batched === undefined
|
|
125
452
|
? {}
|
|
126
453
|
: {
|
|
127
|
-
scoreLabelSequence: (
|
|
128
|
-
|
|
454
|
+
scoreLabelSequence: (text: string, labelSets: ReadonlyArray<ReadonlyArray<string>>) =>
|
|
455
|
+
tracedEffect(events, label, options, batched(text, labelSets), model, nothing)
|
|
129
456
|
}),
|
|
130
457
|
isAvailable: service.isAvailable
|
|
131
458
|
}
|