@llm4ts/core 2.54.1 → 3.1.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/README.md +1 -1
- package/dist/Capability.d.ts +0 -9
- package/dist/Capability.d.ts.map +1 -1
- package/dist/Capability.js +0 -38
- package/dist/Capability.js.map +1 -1
- package/dist/Connector.d.ts +2 -3
- package/dist/Connector.d.ts.map +1 -1
- package/dist/Connector.js +0 -5
- package/dist/Connector.js.map +1 -1
- package/dist/ConnectorRegistry.d.ts +0 -3
- package/dist/ConnectorRegistry.d.ts.map +1 -1
- package/dist/ConnectorRegistry.js +0 -3
- package/dist/ConnectorRegistry.js.map +1 -1
- package/dist/HttpClient.d.ts +0 -2
- package/dist/HttpClient.d.ts.map +1 -1
- package/dist/HttpClient.js +0 -2
- package/dist/HttpClient.js.map +1 -1
- package/dist/LlmService.d.ts +1 -2
- package/dist/LlmService.d.ts.map +1 -1
- package/dist/LlmService.js.map +1 -1
- package/dist/Models.d.ts +0 -21
- package/dist/Models.d.ts.map +1 -1
- package/dist/Models.js +0 -19
- package/dist/Models.js.map +1 -1
- package/dist/Streaming.d.ts +4 -11
- package/dist/Streaming.d.ts.map +1 -1
- package/dist/Streaming.js +2 -29
- package/dist/Streaming.js.map +1 -1
- package/dist/providers/AnthropicModels.d.ts +0 -43
- package/dist/providers/AnthropicModels.d.ts.map +1 -1
- package/dist/providers/AnthropicModels.js +0 -39
- package/dist/providers/AnthropicModels.js.map +1 -1
- package/dist/providers/AnthropicProvider.d.ts +2 -3
- package/dist/providers/AnthropicProvider.d.ts.map +1 -1
- package/dist/providers/AnthropicProvider.js +2 -49
- package/dist/providers/AnthropicProvider.js.map +1 -1
- package/dist/providers/GeminiApiProvider.d.ts +2 -3
- package/dist/providers/GeminiApiProvider.d.ts.map +1 -1
- package/dist/providers/GeminiApiProvider.js +2 -44
- package/dist/providers/GeminiApiProvider.js.map +1 -1
- package/dist/providers/GeminiModels.d.ts +0 -48
- package/dist/providers/GeminiModels.d.ts.map +1 -1
- package/dist/providers/GeminiModels.js +0 -40
- package/dist/providers/GeminiModels.js.map +1 -1
- package/dist/providers/LmStudioProvider.d.ts.map +1 -1
- package/dist/providers/LmStudioProvider.js +1 -4
- package/dist/providers/LmStudioProvider.js.map +1 -1
- package/dist/providers/MlxLmProvider.d.ts.map +1 -1
- package/dist/providers/MlxLmProvider.js +1 -2
- package/dist/providers/MlxLmProvider.js.map +1 -1
- package/dist/providers/MockProvider.d.ts.map +1 -1
- package/dist/providers/MockProvider.js +1 -6
- package/dist/providers/MockProvider.js.map +1 -1
- package/dist/providers/OllamaProvider.d.ts.map +1 -1
- package/dist/providers/OllamaProvider.js +1 -4
- package/dist/providers/OllamaProvider.js.map +1 -1
- package/dist/providers/OpenAIModels.d.ts +0 -58
- package/dist/providers/OpenAIModels.d.ts.map +1 -1
- package/dist/providers/OpenAIModels.js +0 -51
- package/dist/providers/OpenAIModels.js.map +1 -1
- package/dist/providers/OpenAIProvider.d.ts +2 -3
- package/dist/providers/OpenAIProvider.d.ts.map +1 -1
- package/dist/providers/OpenAIProvider.js +2 -46
- package/dist/providers/OpenAIProvider.js.map +1 -1
- package/package.json +2 -14
- package/src/Capability.ts +0 -50
- package/src/Connector.ts +2 -20
- package/src/ConnectorRegistry.ts +0 -15
- package/src/HttpClient.ts +0 -15
- package/src/LlmService.ts +1 -13
- package/src/Models.ts +0 -22
- package/src/Streaming.ts +3 -99
- package/src/providers/AnthropicModels.ts +0 -47
- package/src/providers/AnthropicProvider.ts +1 -87
- package/src/providers/GeminiApiProvider.ts +2 -83
- package/src/providers/GeminiModels.ts +0 -46
- package/src/providers/LmStudioProvider.ts +1 -7
- package/src/providers/MlxLmProvider.ts +1 -3
- package/src/providers/MockProvider.ts +0 -9
- package/src/providers/OllamaProvider.ts +1 -7
- package/src/providers/OpenAIModels.ts +0 -64
- package/src/providers/OpenAIProvider.ts +2 -83
- package/dist/ContextManagement.d.ts +0 -78
- package/dist/ContextManagement.d.ts.map +0 -1
- package/dist/ContextManagement.js +0 -222
- package/dist/ContextManagement.js.map +0 -1
- package/dist/Conversation.d.ts +0 -18
- package/dist/Conversation.d.ts.map +0 -1
- package/dist/Conversation.js +0 -17
- package/dist/Conversation.js.map +0 -1
- package/dist/RateLimiter.d.ts +0 -44
- package/dist/RateLimiter.d.ts.map +0 -1
- package/dist/RateLimiter.js +0 -155
- package/dist/RateLimiter.js.map +0 -1
- package/dist/observability/MeteredLlmService.d.ts +0 -16
- package/dist/observability/MeteredLlmService.d.ts.map +0 -1
- package/dist/observability/MeteredLlmService.js +0 -72
- package/dist/observability/MeteredLlmService.js.map +0 -1
- package/dist/observability/Metrics.d.ts +0 -76
- package/dist/observability/Metrics.d.ts.map +0 -1
- package/dist/observability/Metrics.js +0 -152
- package/dist/observability/Metrics.js.map +0 -1
- package/dist/observability/StructuredLogger.d.ts +0 -31
- package/dist/observability/StructuredLogger.d.ts.map +0 -1
- package/dist/observability/StructuredLogger.js +0 -39
- package/dist/observability/StructuredLogger.js.map +0 -1
- package/dist/observability/Tracing.d.ts +0 -38
- package/dist/observability/Tracing.d.ts.map +0 -1
- package/dist/observability/Tracing.js +0 -80
- package/dist/observability/Tracing.js.map +0 -1
- package/dist/providers/OpenCodeModels.d.ts +0 -61
- package/dist/providers/OpenCodeModels.d.ts.map +0 -1
- package/dist/providers/OpenCodeModels.js +0 -53
- package/dist/providers/OpenCodeModels.js.map +0 -1
- package/dist/providers/OpenCodeProvider.d.ts +0 -10
- package/dist/providers/OpenCodeProvider.d.ts.map +0 -1
- package/dist/providers/OpenCodeProvider.js +0 -154
- package/dist/providers/OpenCodeProvider.js.map +0 -1
- package/dist/tools/Tool.d.ts +0 -83
- package/dist/tools/Tool.d.ts.map +0 -1
- package/dist/tools/Tool.js +0 -278
- package/dist/tools/Tool.js.map +0 -1
- package/dist/tools/ToolCallingExecutor.d.ts +0 -18
- package/dist/tools/ToolCallingExecutor.d.ts.map +0 -1
- package/dist/tools/ToolCallingExecutor.js +0 -64
- package/dist/tools/ToolCallingExecutor.js.map +0 -1
- package/dist/tools/ToolRegistry.d.ts +0 -45
- package/dist/tools/ToolRegistry.d.ts.map +0 -1
- package/dist/tools/ToolRegistry.js +0 -133
- package/dist/tools/ToolRegistry.js.map +0 -1
- package/src/ContextManagement.ts +0 -367
- package/src/Conversation.ts +0 -21
- package/src/RateLimiter.ts +0 -238
- package/src/observability/MeteredLlmService.ts +0 -168
- package/src/observability/Metrics.ts +0 -200
- package/src/observability/StructuredLogger.ts +0 -67
- package/src/observability/Tracing.ts +0 -146
- package/src/providers/OpenCodeModels.ts +0 -60
- package/src/providers/OpenCodeProvider.ts +0 -278
- package/src/tools/Tool.ts +0 -376
- package/src/tools/ToolCallingExecutor.ts +0 -106
- package/src/tools/ToolRegistry.ts +0 -224
package/src/RateLimiter.ts
DELETED
|
@@ -1,238 +0,0 @@
|
|
|
1
|
-
import * as Clock from "effect/Clock"
|
|
2
|
-
import * as Context from "effect/Context"
|
|
3
|
-
import * as Duration from "effect/Duration"
|
|
4
|
-
import * as Effect from "effect/Effect"
|
|
5
|
-
import * as Layer from "effect/Layer"
|
|
6
|
-
import * as Ref from "effect/Ref"
|
|
7
|
-
import * as Schema from "effect/Schema"
|
|
8
|
-
import type { LlmConfig } from "./Models.ts"
|
|
9
|
-
|
|
10
|
-
export class RateLimiterAcquireTimeout extends Schema.TaggedError<RateLimiterAcquireTimeout>()(
|
|
11
|
-
"AcquireTimeout",
|
|
12
|
-
{
|
|
13
|
-
timeout: Schema.Duration
|
|
14
|
-
}
|
|
15
|
-
) {}
|
|
16
|
-
|
|
17
|
-
export class RateLimiterInvalidConfig extends Schema.TaggedError<RateLimiterInvalidConfig>()(
|
|
18
|
-
"InvalidConfig",
|
|
19
|
-
{
|
|
20
|
-
details: Schema.String
|
|
21
|
-
}
|
|
22
|
-
) {}
|
|
23
|
-
|
|
24
|
-
export const RateLimiterError = Schema.Union([RateLimiterAcquireTimeout, RateLimiterInvalidConfig])
|
|
25
|
-
export type RateLimiterError = typeof RateLimiterError.Type
|
|
26
|
-
|
|
27
|
-
export class RateLimiterConfig extends Schema.Class<RateLimiterConfig>("RateLimiterConfig")({
|
|
28
|
-
requestsPerMinute: Schema.Int.pipe(Schema.withConstructorDefault(Effect.succeed(60))),
|
|
29
|
-
burstSize: Schema.Int.pipe(Schema.withConstructorDefault(Effect.succeed(10))),
|
|
30
|
-
acquireTimeout: Schema.Duration.pipe(
|
|
31
|
-
Schema.withConstructorDefault(Effect.succeed(Duration.seconds(30)))
|
|
32
|
-
)
|
|
33
|
-
}) {}
|
|
34
|
-
|
|
35
|
-
export const rateLimiterConfigFromLlmConfig = (config: LlmConfig): RateLimiterConfig =>
|
|
36
|
-
RateLimiterConfig.make({
|
|
37
|
-
requestsPerMinute: config.requestsPerMinute,
|
|
38
|
-
burstSize: config.burstSize,
|
|
39
|
-
acquireTimeout: config.acquireTimeout
|
|
40
|
-
})
|
|
41
|
-
|
|
42
|
-
export class RateLimiterMetrics extends Schema.Class<RateLimiterMetrics>("RateLimiterMetrics")({
|
|
43
|
-
totalRequests: Schema.Int,
|
|
44
|
-
throttledRequests: Schema.Int,
|
|
45
|
-
currentTokens: Schema.Int
|
|
46
|
-
}) {}
|
|
47
|
-
|
|
48
|
-
export interface RateLimiterShape {
|
|
49
|
-
readonly acquire: Effect.Effect<void, RateLimiterError>
|
|
50
|
-
readonly tryAcquire: Effect.Effect<boolean>
|
|
51
|
-
readonly metrics: Effect.Effect<RateLimiterMetrics>
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export class RateLimiter extends Context.Service<RateLimiter, RateLimiterShape>()(
|
|
55
|
-
"@llm4ts/core/RateLimiter"
|
|
56
|
-
) {}
|
|
57
|
-
|
|
58
|
-
interface BucketState {
|
|
59
|
-
readonly tokens: number
|
|
60
|
-
readonly lastRefillNanos: bigint
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
interface TakeResult {
|
|
64
|
-
readonly acquired: boolean
|
|
65
|
-
readonly tokensAfter: number
|
|
66
|
-
readonly deficit: number
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const nanosPerSecond = 1_000_000_000
|
|
70
|
-
|
|
71
|
-
const configDetails = (config: RateLimiterConfig): string =>
|
|
72
|
-
`requestsPerMinute=${config.requestsPerMinute}, burstSize=${config.burstSize}, ` +
|
|
73
|
-
`acquireTimeout=${Duration.format(config.acquireTimeout)}`
|
|
74
|
-
|
|
75
|
-
const isConfigValid = (config: RateLimiterConfig): boolean =>
|
|
76
|
-
config.requestsPerMinute > 0 &&
|
|
77
|
-
config.burstSize > 0 &&
|
|
78
|
-
Duration.toMillis(config.acquireTimeout) > 0
|
|
79
|
-
|
|
80
|
-
export const makeRateLimiter = Effect.fn("@llm4ts/core/RateLimiter.make")(function* (
|
|
81
|
-
config: RateLimiterConfig
|
|
82
|
-
) {
|
|
83
|
-
const initialNanos = yield* Clock.currentTimeNanos
|
|
84
|
-
const state = yield* Ref.make<BucketState>({
|
|
85
|
-
tokens: config.burstSize,
|
|
86
|
-
lastRefillNanos: initialNanos
|
|
87
|
-
})
|
|
88
|
-
const metrics = yield* Ref.make(
|
|
89
|
-
RateLimiterMetrics.make({
|
|
90
|
-
totalRequests: 0,
|
|
91
|
-
throttledRequests: 0,
|
|
92
|
-
currentTokens: config.burstSize
|
|
93
|
-
})
|
|
94
|
-
)
|
|
95
|
-
const capacity = config.burstSize
|
|
96
|
-
const ratePerSecond = config.requestsPerMinute / 60
|
|
97
|
-
|
|
98
|
-
const takeToken: Effect.Effect<TakeResult> = Effect.gen(function* () {
|
|
99
|
-
const now = yield* Clock.currentTimeNanos
|
|
100
|
-
|
|
101
|
-
return yield* Ref.modify(state, (current) => {
|
|
102
|
-
const elapsedNanos = now - current.lastRefillNanos
|
|
103
|
-
const tokensToAdd =
|
|
104
|
-
elapsedNanos <= 0n ? 0 : (Number(elapsedNanos) / nanosPerSecond) * ratePerSecond
|
|
105
|
-
const refilled =
|
|
106
|
-
tokensToAdd <= 0 ? current.tokens : Math.min(capacity, current.tokens + tokensToAdd)
|
|
107
|
-
const lastRefillNanos = tokensToAdd <= 0 ? current.lastRefillNanos : now
|
|
108
|
-
|
|
109
|
-
if (refilled >= 1) {
|
|
110
|
-
const remaining = refilled - 1
|
|
111
|
-
return [
|
|
112
|
-
{
|
|
113
|
-
acquired: true,
|
|
114
|
-
tokensAfter: remaining,
|
|
115
|
-
deficit: 0
|
|
116
|
-
},
|
|
117
|
-
{
|
|
118
|
-
tokens: remaining,
|
|
119
|
-
lastRefillNanos
|
|
120
|
-
}
|
|
121
|
-
]
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
return [
|
|
125
|
-
{
|
|
126
|
-
acquired: false,
|
|
127
|
-
tokensAfter: refilled,
|
|
128
|
-
deficit: 1 - refilled
|
|
129
|
-
},
|
|
130
|
-
{
|
|
131
|
-
tokens: refilled,
|
|
132
|
-
lastRefillNanos
|
|
133
|
-
}
|
|
134
|
-
]
|
|
135
|
-
})
|
|
136
|
-
})
|
|
137
|
-
|
|
138
|
-
const updateCurrentTokens = (tokens: number): Effect.Effect<void> =>
|
|
139
|
-
Ref.update(metrics, (current) =>
|
|
140
|
-
RateLimiterMetrics.make({
|
|
141
|
-
...current,
|
|
142
|
-
currentTokens: Math.trunc(tokens)
|
|
143
|
-
})
|
|
144
|
-
)
|
|
145
|
-
|
|
146
|
-
const waitForToken = (deficit: number): Effect.Effect<void, RateLimiterInvalidConfig> => {
|
|
147
|
-
if (ratePerSecond <= 0) {
|
|
148
|
-
return Effect.fail(
|
|
149
|
-
RateLimiterInvalidConfig.make({
|
|
150
|
-
details: configDetails(config)
|
|
151
|
-
})
|
|
152
|
-
)
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
const nanoseconds = BigInt(Math.max(0, Math.ceil((deficit / ratePerSecond) * nanosPerSecond)))
|
|
156
|
-
return Effect.sleep(Duration.nanos(nanoseconds))
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
const acquireLoop = (alreadyThrottled: boolean): Effect.Effect<void, RateLimiterError> =>
|
|
160
|
-
Effect.gen(function* () {
|
|
161
|
-
const result = yield* takeToken
|
|
162
|
-
yield* updateCurrentTokens(result.tokensAfter)
|
|
163
|
-
|
|
164
|
-
if (result.acquired) {
|
|
165
|
-
return
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
if (!alreadyThrottled) {
|
|
169
|
-
yield* Ref.update(metrics, (current) =>
|
|
170
|
-
RateLimiterMetrics.make({
|
|
171
|
-
...current,
|
|
172
|
-
throttledRequests: current.throttledRequests + 1
|
|
173
|
-
})
|
|
174
|
-
)
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
yield* waitForToken(result.deficit)
|
|
178
|
-
return yield* Effect.suspend(() => acquireLoop(true))
|
|
179
|
-
})
|
|
180
|
-
|
|
181
|
-
const acquire: Effect.Effect<void, RateLimiterError> = Effect.suspend(() => {
|
|
182
|
-
if (!isConfigValid(config)) {
|
|
183
|
-
return Effect.fail(
|
|
184
|
-
RateLimiterInvalidConfig.make({
|
|
185
|
-
details: configDetails(config)
|
|
186
|
-
})
|
|
187
|
-
)
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
return Ref.update(metrics, (current) =>
|
|
191
|
-
RateLimiterMetrics.make({
|
|
192
|
-
...current,
|
|
193
|
-
totalRequests: current.totalRequests + 1
|
|
194
|
-
})
|
|
195
|
-
).pipe(
|
|
196
|
-
Effect.andThen(acquireLoop(false)),
|
|
197
|
-
Effect.timeoutOrElse({
|
|
198
|
-
duration: config.acquireTimeout,
|
|
199
|
-
orElse: () =>
|
|
200
|
-
Effect.fail(
|
|
201
|
-
RateLimiterAcquireTimeout.make({
|
|
202
|
-
timeout: config.acquireTimeout
|
|
203
|
-
})
|
|
204
|
-
)
|
|
205
|
-
})
|
|
206
|
-
)
|
|
207
|
-
})
|
|
208
|
-
|
|
209
|
-
const tryAcquire: Effect.Effect<boolean> = Effect.suspend(() => {
|
|
210
|
-
if (!isConfigValid(config)) {
|
|
211
|
-
return Effect.logWarning(`Rate limiter config invalid: ${configDetails(config)}`).pipe(
|
|
212
|
-
Effect.as(false)
|
|
213
|
-
)
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
return Effect.gen(function* () {
|
|
217
|
-
const result = yield* takeToken
|
|
218
|
-
yield* updateCurrentTokens(result.tokensAfter)
|
|
219
|
-
yield* Ref.update(metrics, (current) =>
|
|
220
|
-
RateLimiterMetrics.make({
|
|
221
|
-
...current,
|
|
222
|
-
totalRequests: current.totalRequests + 1,
|
|
223
|
-
throttledRequests: current.throttledRequests + (result.acquired ? 0 : 1)
|
|
224
|
-
})
|
|
225
|
-
)
|
|
226
|
-
return result.acquired
|
|
227
|
-
})
|
|
228
|
-
})
|
|
229
|
-
|
|
230
|
-
return RateLimiter.of({
|
|
231
|
-
acquire,
|
|
232
|
-
tryAcquire,
|
|
233
|
-
metrics: Ref.get(metrics)
|
|
234
|
-
})
|
|
235
|
-
})
|
|
236
|
-
|
|
237
|
-
export const rateLimiterLayer = (config: RateLimiterConfig): Layer.Layer<RateLimiter> =>
|
|
238
|
-
Layer.effect(RateLimiter, makeRateLimiter(config))
|
|
@@ -1,168 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @deprecated Since 2.30.0 (ADR 0026): llm4ts exports OpenTelemetry spans and
|
|
3
|
-
* metrics over OTLP from `@llm4ts/runner/Otel`; this in-memory module is not
|
|
4
|
-
* used by flow or runner and will be removed in the next major.
|
|
5
|
-
*/
|
|
6
|
-
import * as Clock from "effect/Clock"
|
|
7
|
-
import * as Effect from "effect/Effect"
|
|
8
|
-
import * as Ref from "effect/Ref"
|
|
9
|
-
import * as Schema from "effect/Schema"
|
|
10
|
-
import * as Stream from "effect/Stream"
|
|
11
|
-
import type { LlmError } from "../Errors.ts"
|
|
12
|
-
import type { LlmServiceShape, StructuredResult } from "../LlmService.ts"
|
|
13
|
-
import type { LlmChunk, TokenUsage } from "../Models.ts"
|
|
14
|
-
import { RequestMetrics } from "./Metrics.ts"
|
|
15
|
-
import type { MetricsCollectorShape, RequestLabels } from "./Metrics.ts"
|
|
16
|
-
|
|
17
|
-
export class LlmPricing extends Schema.Class<LlmPricing>("LlmPricing")({
|
|
18
|
-
inputUsdPer1k: Schema.Number,
|
|
19
|
-
outputUsdPer1k: Schema.Number
|
|
20
|
-
}) {}
|
|
21
|
-
|
|
22
|
-
export interface MeteredLlmOptions {
|
|
23
|
-
readonly labels: RequestLabels
|
|
24
|
-
readonly pricing?: LlmPricing
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const estimatedCost = (
|
|
28
|
-
usage: TokenUsage | undefined,
|
|
29
|
-
pricing: LlmPricing | undefined
|
|
30
|
-
): number | undefined =>
|
|
31
|
-
usage === undefined || pricing === undefined
|
|
32
|
-
? undefined
|
|
33
|
-
: (usage.prompt / 1_000) * pricing.inputUsdPer1k +
|
|
34
|
-
(usage.completion / 1_000) * pricing.outputUsdPer1k
|
|
35
|
-
|
|
36
|
-
const complete = (
|
|
37
|
-
collector: MetricsCollectorShape,
|
|
38
|
-
options: MeteredLlmOptions,
|
|
39
|
-
startedAt: number,
|
|
40
|
-
endedAt: number,
|
|
41
|
-
success: boolean,
|
|
42
|
-
errorType: string | undefined,
|
|
43
|
-
usage: TokenUsage | undefined
|
|
44
|
-
): Effect.Effect<void> => {
|
|
45
|
-
const cost = estimatedCost(usage, options.pricing)
|
|
46
|
-
return collector.recordCompleted(
|
|
47
|
-
RequestMetrics.make({
|
|
48
|
-
labels: options.labels,
|
|
49
|
-
latencyMs: Math.max(0, endedAt - startedAt),
|
|
50
|
-
success,
|
|
51
|
-
...(errorType === undefined ? {} : { errorType }),
|
|
52
|
-
...(usage === undefined ? {} : { tokenUsage: usage }),
|
|
53
|
-
...(cost === undefined ? {} : { estimatedCostUsd: cost })
|
|
54
|
-
})
|
|
55
|
-
)
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const meterEffect = <A, R>(
|
|
59
|
-
effect: Effect.Effect<A, LlmError, R>,
|
|
60
|
-
collector: MetricsCollectorShape,
|
|
61
|
-
options: MeteredLlmOptions,
|
|
62
|
-
usageOf: (value: A) => TokenUsage | undefined
|
|
63
|
-
): Effect.Effect<A, LlmError, R> =>
|
|
64
|
-
Effect.gen(function* () {
|
|
65
|
-
const startedAt = yield* Clock.currentTimeMillis
|
|
66
|
-
yield* collector.markRequestStarted(options.labels)
|
|
67
|
-
const result = yield* Effect.result(effect)
|
|
68
|
-
const endedAt = yield* Clock.currentTimeMillis
|
|
69
|
-
yield* complete(
|
|
70
|
-
collector,
|
|
71
|
-
options,
|
|
72
|
-
startedAt,
|
|
73
|
-
endedAt,
|
|
74
|
-
result._tag === "Success",
|
|
75
|
-
result._tag === "Failure" ? result.failure._tag : undefined,
|
|
76
|
-
result._tag === "Success" ? usageOf(result.success) : undefined
|
|
77
|
-
)
|
|
78
|
-
return yield* Effect.fromResult(result)
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
interface StreamMeasurement {
|
|
82
|
-
readonly success: boolean
|
|
83
|
-
readonly errorType: string | undefined
|
|
84
|
-
readonly usage: TokenUsage | undefined
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
const meterStream = (
|
|
88
|
-
stream: Stream.Stream<LlmChunk, LlmError>,
|
|
89
|
-
collector: MetricsCollectorShape,
|
|
90
|
-
options: MeteredLlmOptions
|
|
91
|
-
): Stream.Stream<LlmChunk, LlmError> =>
|
|
92
|
-
Stream.unwrap(
|
|
93
|
-
Effect.gen(function* () {
|
|
94
|
-
const startedAt = yield* Clock.currentTimeMillis
|
|
95
|
-
yield* collector.markRequestStarted(options.labels)
|
|
96
|
-
const measurement = yield* Ref.make<StreamMeasurement>({
|
|
97
|
-
success: true,
|
|
98
|
-
errorType: undefined,
|
|
99
|
-
usage: undefined
|
|
100
|
-
})
|
|
101
|
-
return stream.pipe(
|
|
102
|
-
Stream.tap((chunk) =>
|
|
103
|
-
chunk.usage === undefined
|
|
104
|
-
? Effect.void
|
|
105
|
-
: Ref.update(measurement, (current) => ({
|
|
106
|
-
...current,
|
|
107
|
-
usage: chunk.usage
|
|
108
|
-
}))
|
|
109
|
-
),
|
|
110
|
-
Stream.tapError((error) =>
|
|
111
|
-
Ref.update(measurement, (current) => ({
|
|
112
|
-
...current,
|
|
113
|
-
success: false,
|
|
114
|
-
errorType: error._tag
|
|
115
|
-
}))
|
|
116
|
-
),
|
|
117
|
-
Stream.ensuring(
|
|
118
|
-
Effect.gen(function* () {
|
|
119
|
-
const current = yield* Ref.get(measurement)
|
|
120
|
-
const endedAt = yield* Clock.currentTimeMillis
|
|
121
|
-
yield* complete(
|
|
122
|
-
collector,
|
|
123
|
-
options,
|
|
124
|
-
startedAt,
|
|
125
|
-
endedAt,
|
|
126
|
-
current.success,
|
|
127
|
-
current.errorType,
|
|
128
|
-
current.usage
|
|
129
|
-
)
|
|
130
|
-
})
|
|
131
|
-
)
|
|
132
|
-
)
|
|
133
|
-
})
|
|
134
|
-
)
|
|
135
|
-
|
|
136
|
-
export const meterLlmService = (
|
|
137
|
-
service: LlmServiceShape,
|
|
138
|
-
collector: MetricsCollectorShape,
|
|
139
|
-
options: MeteredLlmOptions
|
|
140
|
-
): LlmServiceShape => ({
|
|
141
|
-
executeStream: (prompt) => meterStream(service.executeStream(prompt), collector, options),
|
|
142
|
-
executeStreamWithHistory: (messages) =>
|
|
143
|
-
meterStream(service.executeStreamWithHistory(messages), collector, options),
|
|
144
|
-
executeWithTools: (prompt, tools) =>
|
|
145
|
-
meterEffect(service.executeWithTools(prompt, tools), collector, options, () => undefined),
|
|
146
|
-
executeStructured: (prompt, schema, jsonSchema) =>
|
|
147
|
-
meterEffect(
|
|
148
|
-
service.executeStructured(prompt, schema, jsonSchema),
|
|
149
|
-
collector,
|
|
150
|
-
options,
|
|
151
|
-
() => undefined
|
|
152
|
-
),
|
|
153
|
-
executeStructuredWithUsage: (prompt, schema, jsonSchema) =>
|
|
154
|
-
meterEffect(
|
|
155
|
-
service.executeStructuredWithUsage(prompt, schema, jsonSchema),
|
|
156
|
-
collector,
|
|
157
|
-
options,
|
|
158
|
-
<A>(result: StructuredResult<A>) => result[1]
|
|
159
|
-
),
|
|
160
|
-
scoreLabels: (prompt, labels) =>
|
|
161
|
-
meterEffect(
|
|
162
|
-
service.scoreLabels(prompt, labels),
|
|
163
|
-
collector,
|
|
164
|
-
options,
|
|
165
|
-
(distribution) => distribution.usage
|
|
166
|
-
),
|
|
167
|
-
isAvailable: service.isAvailable
|
|
168
|
-
})
|
|
@@ -1,200 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @deprecated Since 2.30.0 (ADR 0026): llm4ts exports OpenTelemetry spans and
|
|
3
|
-
* metrics over OTLP from `@llm4ts/runner/Otel`; this in-memory module is not
|
|
4
|
-
* used by flow or runner and will be removed in the next major.
|
|
5
|
-
*/
|
|
6
|
-
import * as Context from "effect/Context"
|
|
7
|
-
import * as Effect from "effect/Effect"
|
|
8
|
-
import * as Layer from "effect/Layer"
|
|
9
|
-
import * as Ref from "effect/Ref"
|
|
10
|
-
import * as Schema from "effect/Schema"
|
|
11
|
-
import { TokenUsage } from "../Models.ts"
|
|
12
|
-
|
|
13
|
-
export class RequestLabels extends Schema.Class<RequestLabels>("RequestLabels")({
|
|
14
|
-
provider: Schema.String,
|
|
15
|
-
model: Schema.String,
|
|
16
|
-
agent: Schema.optionalKey(Schema.String),
|
|
17
|
-
runId: Schema.optionalKey(Schema.String),
|
|
18
|
-
workflowStep: Schema.optionalKey(Schema.String)
|
|
19
|
-
}) {}
|
|
20
|
-
|
|
21
|
-
export class RequestMetrics extends Schema.Class<RequestMetrics>("RequestMetrics")({
|
|
22
|
-
labels: RequestLabels,
|
|
23
|
-
tokenUsage: Schema.optionalKey(TokenUsage),
|
|
24
|
-
latencyMs: Schema.Number,
|
|
25
|
-
success: Schema.Boolean,
|
|
26
|
-
errorType: Schema.optionalKey(Schema.String),
|
|
27
|
-
estimatedCostUsd: Schema.optionalKey(Schema.Number)
|
|
28
|
-
}) {}
|
|
29
|
-
|
|
30
|
-
export class ProviderHealth extends Schema.Class<ProviderHealth>("ProviderHealth")({
|
|
31
|
-
provider: Schema.String,
|
|
32
|
-
requestCount: Schema.Int,
|
|
33
|
-
successRate: Schema.Number,
|
|
34
|
-
avgLatencyMs: Schema.Number,
|
|
35
|
-
p95LatencyMs: Schema.Number,
|
|
36
|
-
estimatedCostUsd: Schema.Number
|
|
37
|
-
}) {}
|
|
38
|
-
|
|
39
|
-
export class ObservabilitySnapshot extends Schema.Class<ObservabilitySnapshot>(
|
|
40
|
-
"ObservabilitySnapshot"
|
|
41
|
-
)({
|
|
42
|
-
totalRequests: Schema.Int,
|
|
43
|
-
totalErrors: Schema.Int,
|
|
44
|
-
activeRequests: Schema.Int,
|
|
45
|
-
promptTokens: Schema.Int,
|
|
46
|
-
completionTokens: Schema.Int,
|
|
47
|
-
totalTokens: Schema.Int,
|
|
48
|
-
estimatedCostUsd: Schema.Number,
|
|
49
|
-
p50LatencyMs: Schema.Number,
|
|
50
|
-
p95LatencyMs: Schema.Number,
|
|
51
|
-
byProvider: Schema.Array(ProviderHealth),
|
|
52
|
-
byAgentRequests: Schema.Record(Schema.String, Schema.Int)
|
|
53
|
-
}) {}
|
|
54
|
-
|
|
55
|
-
export class DashboardMetricsSnapshot extends Schema.Class<DashboardMetricsSnapshot>(
|
|
56
|
-
"DashboardMetricsSnapshot"
|
|
57
|
-
)({
|
|
58
|
-
providerHealth: Schema.Array(ProviderHealth),
|
|
59
|
-
totalTokens: Schema.Int,
|
|
60
|
-
totalCostUsd: Schema.Number,
|
|
61
|
-
totalRequests: Schema.Int,
|
|
62
|
-
errorRate: Schema.Number
|
|
63
|
-
}) {}
|
|
64
|
-
|
|
65
|
-
export interface MetricsCollectorShape {
|
|
66
|
-
readonly markRequestStarted: (labels: RequestLabels) => Effect.Effect<void>
|
|
67
|
-
readonly recordCompleted: (metrics: RequestMetrics) => Effect.Effect<void>
|
|
68
|
-
readonly snapshot: Effect.Effect<ObservabilitySnapshot>
|
|
69
|
-
readonly dashboardSnapshot: Effect.Effect<DashboardMetricsSnapshot>
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export class MetricsCollector extends Context.Service<MetricsCollector, MetricsCollectorShape>()(
|
|
73
|
-
"@llm4ts/core/observability/MetricsCollector"
|
|
74
|
-
) {}
|
|
75
|
-
|
|
76
|
-
interface MetricsState {
|
|
77
|
-
readonly totalRequests: number
|
|
78
|
-
readonly activeRequests: number
|
|
79
|
-
readonly completed: ReadonlyArray<RequestMetrics>
|
|
80
|
-
readonly byAgentRequests: ReadonlyMap<string, number>
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const percentile = (values: ReadonlyArray<number>, quantile: number): number => {
|
|
84
|
-
if (values.length === 0) {
|
|
85
|
-
return 0
|
|
86
|
-
}
|
|
87
|
-
const sorted = [...values].sort((left, right) => left - right)
|
|
88
|
-
const index = Math.max(0, Math.ceil(quantile * sorted.length) - 1)
|
|
89
|
-
return sorted[index] ?? 0
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const providerHealth = (
|
|
93
|
-
completed: ReadonlyArray<RequestMetrics>
|
|
94
|
-
): ReadonlyArray<ProviderHealth> => {
|
|
95
|
-
const providers = new Map<string, Array<RequestMetrics>>()
|
|
96
|
-
for (const metric of completed) {
|
|
97
|
-
const current = providers.get(metric.labels.provider) ?? []
|
|
98
|
-
current.push(metric)
|
|
99
|
-
providers.set(metric.labels.provider, current)
|
|
100
|
-
}
|
|
101
|
-
return [...providers.entries()]
|
|
102
|
-
.sort(([left], [right]) => left.localeCompare(right))
|
|
103
|
-
.map(([provider, metrics]) => {
|
|
104
|
-
const latencies = metrics.map((metric) => metric.latencyMs)
|
|
105
|
-
const successes = metrics.filter((metric) => metric.success).length
|
|
106
|
-
return ProviderHealth.make({
|
|
107
|
-
provider,
|
|
108
|
-
requestCount: metrics.length,
|
|
109
|
-
successRate: metrics.length === 0 ? 1 : successes / metrics.length,
|
|
110
|
-
avgLatencyMs:
|
|
111
|
-
metrics.length === 0
|
|
112
|
-
? 0
|
|
113
|
-
: latencies.reduce((sum, latency) => sum + latency, 0) / metrics.length,
|
|
114
|
-
p95LatencyMs: percentile(latencies, 0.95),
|
|
115
|
-
estimatedCostUsd: metrics.reduce((sum, metric) => sum + (metric.estimatedCostUsd ?? 0), 0)
|
|
116
|
-
})
|
|
117
|
-
})
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const toSnapshot = (state: MetricsState): ObservabilitySnapshot => {
|
|
121
|
-
const completed = state.completed
|
|
122
|
-
const promptTokens = completed.reduce((sum, metric) => sum + (metric.tokenUsage?.prompt ?? 0), 0)
|
|
123
|
-
const completionTokens = completed.reduce(
|
|
124
|
-
(sum, metric) => sum + (metric.tokenUsage?.completion ?? 0),
|
|
125
|
-
0
|
|
126
|
-
)
|
|
127
|
-
return ObservabilitySnapshot.make({
|
|
128
|
-
totalRequests: state.totalRequests,
|
|
129
|
-
totalErrors: completed.filter((metric) => !metric.success).length,
|
|
130
|
-
activeRequests: state.activeRequests,
|
|
131
|
-
promptTokens,
|
|
132
|
-
completionTokens,
|
|
133
|
-
totalTokens: promptTokens + completionTokens,
|
|
134
|
-
estimatedCostUsd: completed.reduce((sum, metric) => sum + (metric.estimatedCostUsd ?? 0), 0),
|
|
135
|
-
p50LatencyMs: percentile(
|
|
136
|
-
completed.map((metric) => metric.latencyMs),
|
|
137
|
-
0.5
|
|
138
|
-
),
|
|
139
|
-
p95LatencyMs: percentile(
|
|
140
|
-
completed.map((metric) => metric.latencyMs),
|
|
141
|
-
0.95
|
|
142
|
-
),
|
|
143
|
-
byProvider: providerHealth(completed),
|
|
144
|
-
byAgentRequests: Object.fromEntries(state.byAgentRequests)
|
|
145
|
-
})
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
export const makeMetricsCollector = Effect.fn("@llm4ts/core/observability/MetricsCollector.make")(
|
|
149
|
-
function* (): Effect.fn.Return<MetricsCollectorShape> {
|
|
150
|
-
const state = yield* Ref.make<MetricsState>({
|
|
151
|
-
totalRequests: 0,
|
|
152
|
-
activeRequests: 0,
|
|
153
|
-
completed: [],
|
|
154
|
-
byAgentRequests: new Map()
|
|
155
|
-
})
|
|
156
|
-
|
|
157
|
-
const markRequestStarted = (labels: RequestLabels): Effect.Effect<void> =>
|
|
158
|
-
Ref.update(state, (current) => {
|
|
159
|
-
const agents = new Map(current.byAgentRequests)
|
|
160
|
-
if (labels.agent !== undefined) {
|
|
161
|
-
agents.set(labels.agent, (agents.get(labels.agent) ?? 0) + 1)
|
|
162
|
-
}
|
|
163
|
-
return {
|
|
164
|
-
totalRequests: current.totalRequests + 1,
|
|
165
|
-
activeRequests: current.activeRequests + 1,
|
|
166
|
-
completed: current.completed,
|
|
167
|
-
byAgentRequests: agents
|
|
168
|
-
}
|
|
169
|
-
})
|
|
170
|
-
|
|
171
|
-
const recordCompleted = (metrics: RequestMetrics): Effect.Effect<void> =>
|
|
172
|
-
Ref.update(state, (current) => ({
|
|
173
|
-
...current,
|
|
174
|
-
activeRequests: Math.max(0, current.activeRequests - 1),
|
|
175
|
-
completed: [...current.completed, metrics]
|
|
176
|
-
}))
|
|
177
|
-
|
|
178
|
-
const snapshot = Ref.get(state).pipe(Effect.map(toSnapshot))
|
|
179
|
-
const dashboardSnapshot = snapshot.pipe(
|
|
180
|
-
Effect.map((current) =>
|
|
181
|
-
DashboardMetricsSnapshot.make({
|
|
182
|
-
providerHealth: current.byProvider,
|
|
183
|
-
totalTokens: current.totalTokens,
|
|
184
|
-
totalCostUsd: current.estimatedCostUsd,
|
|
185
|
-
totalRequests: current.totalRequests,
|
|
186
|
-
errorRate: current.totalRequests === 0 ? 0 : current.totalErrors / current.totalRequests
|
|
187
|
-
})
|
|
188
|
-
)
|
|
189
|
-
)
|
|
190
|
-
|
|
191
|
-
return {
|
|
192
|
-
markRequestStarted,
|
|
193
|
-
recordCompleted,
|
|
194
|
-
snapshot,
|
|
195
|
-
dashboardSnapshot
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
)
|
|
199
|
-
|
|
200
|
-
export const MetricsCollectorLive = Layer.effect(MetricsCollector, makeMetricsCollector())
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @deprecated Since 2.30.0 (ADR 0026): llm4ts exports OpenTelemetry spans and
|
|
3
|
-
* metrics over OTLP from `@llm4ts/runner/Otel`; this in-memory module is not
|
|
4
|
-
* used by flow or runner and will be removed in the next major.
|
|
5
|
-
*/
|
|
6
|
-
import * as Clock from "effect/Clock"
|
|
7
|
-
import * as Context from "effect/Context"
|
|
8
|
-
import * as Effect from "effect/Effect"
|
|
9
|
-
import * as Layer from "effect/Layer"
|
|
10
|
-
import * as Schema from "effect/Schema"
|
|
11
|
-
import type { JsonValue } from "../providers/CliSupport.ts"
|
|
12
|
-
import { redactText, sanitizeFields, type RedactionOptions } from "./Redaction.ts"
|
|
13
|
-
import type { TracingServiceShape } from "./Tracing.ts"
|
|
14
|
-
|
|
15
|
-
export const LogLevel = Schema.Literals(["Debug", "Info", "Warning", "Error"])
|
|
16
|
-
export type LogLevel = typeof LogLevel.Type
|
|
17
|
-
|
|
18
|
-
export class StructuredLogEvent extends Schema.Class<StructuredLogEvent>("StructuredLogEvent")({
|
|
19
|
-
level: LogLevel,
|
|
20
|
-
message: Schema.String,
|
|
21
|
-
correlationId: Schema.String,
|
|
22
|
-
timestamp: Schema.Number,
|
|
23
|
-
fields: Schema.Record(Schema.String, Schema.Json)
|
|
24
|
-
}) {}
|
|
25
|
-
|
|
26
|
-
export interface StructuredLoggerShape {
|
|
27
|
-
readonly log: (
|
|
28
|
-
level: LogLevel,
|
|
29
|
-
message: string,
|
|
30
|
-
fields?: Readonly<Record<string, JsonValue>>
|
|
31
|
-
) => Effect.Effect<void>
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export class StructuredLogger extends Context.Service<StructuredLogger, StructuredLoggerShape>()(
|
|
35
|
-
"@llm4ts/core/observability/StructuredLogger"
|
|
36
|
-
) {}
|
|
37
|
-
|
|
38
|
-
export interface StructuredLoggerOptions extends RedactionOptions {
|
|
39
|
-
readonly sink: (event: StructuredLogEvent) => Effect.Effect<void>
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export const makeStructuredLogger = (
|
|
43
|
-
tracing: TracingServiceShape,
|
|
44
|
-
options: StructuredLoggerOptions
|
|
45
|
-
): StructuredLoggerShape => ({
|
|
46
|
-
log: Effect.fn("@llm4ts/core/observability/StructuredLogger.log")(function* (
|
|
47
|
-
level: LogLevel,
|
|
48
|
-
message: string,
|
|
49
|
-
fields: Readonly<Record<string, JsonValue>> = {}
|
|
50
|
-
) {
|
|
51
|
-
const correlationId = yield* tracing.correlationId
|
|
52
|
-
const timestamp = yield* Clock.currentTimeMillis
|
|
53
|
-
yield* options.sink(
|
|
54
|
-
StructuredLogEvent.make({
|
|
55
|
-
level,
|
|
56
|
-
message: redactText(message, options),
|
|
57
|
-
correlationId,
|
|
58
|
-
timestamp,
|
|
59
|
-
fields: sanitizeFields(fields, options)
|
|
60
|
-
})
|
|
61
|
-
)
|
|
62
|
-
})
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
export const StructuredLoggerNoop = Layer.succeed(StructuredLogger)({
|
|
66
|
-
log: (_level, _message, _fields) => Effect.void
|
|
67
|
-
})
|