@plurnk/plurnk-providers 1.3.4 → 1.3.6
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/.env.defaults +41 -52
- package/README.md +44 -53
- package/SPEC.md +215 -354
- package/dist/AiSdkProvider.d.ts +78 -0
- package/dist/AiSdkProvider.d.ts.map +1 -0
- package/dist/AiSdkProvider.js +591 -0
- package/dist/AiSdkProvider.js.map +1 -0
- package/dist/Mock.d.ts +1 -1
- package/dist/Mock.d.ts.map +1 -1
- package/dist/Mock.js +1 -1
- package/dist/Mock.js.map +1 -1
- package/dist/OpenAICompat.d.ts +3 -5
- package/dist/OpenAICompat.d.ts.map +1 -1
- package/dist/OpenAICompat.js +44 -133
- package/dist/OpenAICompat.js.map +1 -1
- package/dist/Pool.d.ts +1 -1
- package/dist/Pool.d.ts.map +1 -1
- package/dist/Pool.js +1 -1
- package/dist/Pool.js.map +1 -1
- package/dist/ProviderRegistry.d.ts.map +1 -1
- package/dist/ProviderRegistry.js +37 -24
- package/dist/ProviderRegistry.js.map +1 -1
- package/dist/aiSdkTransport.d.ts +52 -0
- package/dist/aiSdkTransport.d.ts.map +1 -0
- package/dist/aiSdkTransport.js +294 -0
- package/dist/aiSdkTransport.js.map +1 -0
- package/dist/catalogProvider.d.ts +15 -0
- package/dist/catalogProvider.d.ts.map +1 -0
- package/dist/catalogProvider.js +103 -0
- package/dist/catalogProvider.js.map +1 -0
- package/dist/compatibleProvider.d.ts +3 -0
- package/dist/compatibleProvider.d.ts.map +1 -0
- package/dist/compatibleProvider.js +146 -0
- package/dist/compatibleProvider.js.map +1 -0
- package/dist/discover.d.ts.map +1 -1
- package/dist/discover.js.map +1 -1
- package/dist/env.d.ts +1 -0
- package/dist/env.d.ts.map +1 -1
- package/dist/env.js +13 -6
- package/dist/env.js.map +1 -1
- package/dist/index.d.ts +5 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -8
- package/dist/index.js.map +1 -1
- package/dist/ollama.d.ts +3 -0
- package/dist/ollama.d.ts.map +1 -0
- package/dist/ollama.js +39 -0
- package/dist/ollama.js.map +1 -0
- package/dist/openai.d.ts +2 -4
- package/dist/openai.d.ts.map +1 -1
- package/dist/openai.js +1 -2
- package/dist/openai.js.map +1 -1
- package/dist/sdkModels.d.ts +13 -0
- package/dist/sdkModels.d.ts.map +1 -0
- package/dist/sdkModels.js +153 -0
- package/dist/sdkModels.js.map +1 -0
- package/dist/standardProviders.d.ts +0 -1
- package/dist/standardProviders.d.ts.map +1 -1
- package/dist/standardProviders.js +9 -11
- package/dist/standardProviders.js.map +1 -1
- package/dist/telemetry.d.ts.map +1 -1
- package/dist/telemetry.js +20 -9
- package/dist/telemetry.js.map +1 -1
- package/dist/types.d.ts +4 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/usage.d.ts +1 -1
- package/dist/usage.d.ts.map +1 -1
- package/dist/usage.js +4 -2
- package/dist/usage.js.map +1 -1
- package/package.json +19 -8
- package/src/{OpenAICompat.test.ts → AiSdkProvider.test.ts} +202 -177
- package/src/{OpenAICompat.ts → AiSdkProvider.ts} +89 -144
- package/src/Mock.test.ts +3 -3
- package/src/Mock.ts +1 -1
- package/src/Pool.test.ts +3 -3
- package/src/Pool.ts +1 -1
- package/src/ProviderRegistry.test.ts +40 -27
- package/src/ProviderRegistry.ts +35 -24
- package/src/aiSdkTransport.test.ts +253 -0
- package/src/aiSdkTransport.ts +369 -0
- package/src/boundaries.test.ts +2 -2
- package/src/catalogProvider.test.ts +100 -0
- package/src/catalogProvider.ts +151 -0
- package/src/compatibleProvider.test.ts +44 -0
- package/src/compatibleProvider.ts +205 -0
- package/src/discover.test.ts +12 -12
- package/src/discover.ts +3 -6
- package/src/env.ts +14 -6
- package/src/index.ts +7 -11
- package/src/ollama.ts +63 -0
- package/src/openai.ts +2 -8
- package/src/sdkModels.test.ts +47 -0
- package/src/sdkModels.ts +194 -0
- package/src/telemetry.test.ts +17 -10
- package/src/telemetry.ts +22 -14
- package/src/types.ts +10 -13
- package/src/usage.test.ts +8 -10
- package/src/usage.ts +7 -3
- package/src/openaiStream.ts +0 -310
- package/src/standardProviders.test.ts +0 -949
- package/src/standardProviders.ts +0 -635
package/src/ProviderRegistry.ts
CHANGED
|
@@ -3,18 +3,19 @@
|
|
|
3
3
|
// overrides) lives in @plurnk/plurnk-aliases — the zero-dep parser shared with
|
|
4
4
|
// thin clients (#27); this module resolves the active alias to a Provider.
|
|
5
5
|
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
// The framework is contract-only — it does NOT depend on its plugins; the
|
|
11
|
-
// scan is what surfaces them (#12/#14).
|
|
6
|
+
// Resolution order (SPEC §5): models.dev catalog → PLURNK provider declaration
|
|
7
|
+
// → local protocol adapter → scope-agnostic AI SDK plugin discovery. Generic
|
|
8
|
+
// provider facts belong to models.dev or operator config; PLURNK owns only the
|
|
9
|
+
// stable Provider contract and product-specific local behavior.
|
|
12
10
|
|
|
13
|
-
import type {
|
|
14
|
-
import {
|
|
11
|
+
import type { AiSdkProviderPlugin, Provider } from "./types.ts";
|
|
12
|
+
import { catalogProviderFromEnv, providerFromSdkModel } from "./catalogProvider.ts";
|
|
15
13
|
import { discover, type DiscoverOptions, type Discovery } from "./discover.ts";
|
|
16
14
|
import { resolveActiveAlias } from "@plurnk/plurnk-aliases";
|
|
17
15
|
import { scopeEnvToAlias } from "./env.ts";
|
|
16
|
+
import { ollamaProviderFromEnv } from "./ollama.ts";
|
|
17
|
+
import { compatibleProviderFromEnv } from "./compatibleProvider.ts";
|
|
18
|
+
import { contextWindowFromEnv } from "./env.ts";
|
|
18
19
|
|
|
19
20
|
// Two injectable seams, both defaulting to production behavior and never passed
|
|
20
21
|
// by real callers: the module importer (tests exercise the bespoke path without
|
|
@@ -33,10 +34,8 @@ const providerPackages = async (discoverFn: DiscoverFn, env: NodeJS.ProcessEnv):
|
|
|
33
34
|
return discoveredCache;
|
|
34
35
|
};
|
|
35
36
|
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
// authoritative — a scanned package whose name duplicates a standard one is
|
|
39
|
-
// shadowed here (never reached), since tier 1 returns first.
|
|
37
|
+
// Catalog and explicit declarations are authoritative. Discovery is the
|
|
38
|
+
// extensibility seam for an AI SDK provider that neither source describes.
|
|
40
39
|
export const instantiateProvider = async (
|
|
41
40
|
name: string,
|
|
42
41
|
env: NodeJS.ProcessEnv,
|
|
@@ -46,14 +45,13 @@ export const instantiateProvider = async (
|
|
|
46
45
|
baseUrl?: string, // per-alias endpoint override (PLURNK_BASEURL_<alias>); threaded to both tiers
|
|
47
46
|
alias?: string, // the alias this instantiation serves — scopes PLURNK_PROVIDERS_<KNOB>_<alias> overrides
|
|
48
47
|
): Promise<Provider> => {
|
|
49
|
-
// Per-alias knob scoping
|
|
50
|
-
//
|
|
48
|
+
// Per-alias knob scoping overlays _<alias>-suffixed knobs onto their bare
|
|
49
|
+
// names before any resolver reads them.
|
|
51
50
|
if (alias !== undefined) env = scopeEnvToAlias(env, alias);
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
51
|
+
const catalog = catalogProviderFromEnv(name, env, model, baseUrl);
|
|
52
|
+
if (catalog !== null) return catalog;
|
|
53
|
+
if (name === "ollama") return ollamaProviderFromEnv(env, model, baseUrl === undefined ? undefined : { baseUrl });
|
|
54
|
+
if (name === "openai" || name === "plurnk") return compatibleProviderFromEnv(name, env, model, baseUrl);
|
|
57
55
|
const { registry, skipped } = await providerPackages(discoverFn, env);
|
|
58
56
|
const specifier = registry.get(name);
|
|
59
57
|
if (specifier === undefined) {
|
|
@@ -61,7 +59,7 @@ export const instantiateProvider = async (
|
|
|
61
59
|
if (declined !== undefined) {
|
|
62
60
|
throw new Error(`provider "${name}" resolves to ${declined}, but it is untrusted under PLURNK_PLUGINS_TRUSTED_ONLY — add it to the allowlist (or publish under @plurnk/)`);
|
|
63
61
|
}
|
|
64
|
-
throw new Error(`unknown provider "${name}":
|
|
62
|
+
throw new Error(`unknown provider "${name}": absent from models.dev, operator declarations, local adapters, and installed AI SDK provider plugins`);
|
|
65
63
|
}
|
|
66
64
|
let mod: unknown;
|
|
67
65
|
try {
|
|
@@ -69,11 +67,24 @@ export const instantiateProvider = async (
|
|
|
69
67
|
} catch (cause) {
|
|
70
68
|
throw new Error(`provider "${name}" resolves to ${specifier}, but importing it failed`, { cause });
|
|
71
69
|
}
|
|
72
|
-
const
|
|
73
|
-
if (
|
|
74
|
-
throw new Error(`${specifier} default export is not
|
|
70
|
+
const sdkProvider = (mod as { default?: AiSdkProviderPlugin }).default;
|
|
71
|
+
if (sdkProvider === undefined || typeof sdkProvider.languageModel !== "function") {
|
|
72
|
+
throw new Error(`${specifier} default export is not an AI SDK provider (missing languageModel)`);
|
|
73
|
+
}
|
|
74
|
+
if (baseUrl !== undefined) {
|
|
75
|
+
throw new Error(`${specifier}: PLURNK_BASEURL_${alias ?? "<alias>"} cannot reconfigure an installed AI SDK provider; declare the provider through PLURNK_PROVIDERS_PROVIDER_* instead`);
|
|
76
|
+
}
|
|
77
|
+
const contextWindow = contextWindowFromEnv(env, name);
|
|
78
|
+
if (contextWindow === null) {
|
|
79
|
+
throw new Error(`${specifier}: PLURNK_PROVIDERS_CONTEXT_WINDOW must be set because Models.dev has no metadata for provider "${name}"`);
|
|
75
80
|
}
|
|
76
|
-
return
|
|
81
|
+
return providerFromSdkModel({
|
|
82
|
+
name,
|
|
83
|
+
env,
|
|
84
|
+
model,
|
|
85
|
+
languageModel: sdkProvider.languageModel(model),
|
|
86
|
+
contextWindow,
|
|
87
|
+
});
|
|
77
88
|
};
|
|
78
89
|
|
|
79
90
|
// Test-only: drop the memoized discovery so a fresh scan/injection runs next.
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import { executeOpenAICompatible } from "./aiSdkTransport.ts";
|
|
4
|
+
|
|
5
|
+
const request = {
|
|
6
|
+
url: "https://example.test/v1/chat/completions",
|
|
7
|
+
model: "test-model",
|
|
8
|
+
headers: {},
|
|
9
|
+
body: {},
|
|
10
|
+
messages: [{ role: "user" as const, content: "question" }],
|
|
11
|
+
fetchTimeoutMs: 1_000,
|
|
12
|
+
retryAttempts: 2,
|
|
13
|
+
streaming: false,
|
|
14
|
+
captureRawBody: false,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
test("X-Should-Retry:false prevents nested retries for a normally retryable status", async () => {
|
|
18
|
+
let calls = 0;
|
|
19
|
+
await assert.rejects(executeOpenAICompatible({
|
|
20
|
+
...request,
|
|
21
|
+
fetch: async () => {
|
|
22
|
+
calls += 1;
|
|
23
|
+
return new Response(
|
|
24
|
+
JSON.stringify({ error: { message: "upstream attempts exhausted" } }),
|
|
25
|
+
{
|
|
26
|
+
status: 503,
|
|
27
|
+
headers: {
|
|
28
|
+
"content-type": "application/json",
|
|
29
|
+
"x-should-retry": "false",
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
);
|
|
33
|
+
},
|
|
34
|
+
}));
|
|
35
|
+
assert.equal(calls, 1);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("the adapter preserves PLURNK request extensions and response evidence", async () => {
|
|
39
|
+
let body: Record<string, unknown> | undefined;
|
|
40
|
+
const responseBody = {
|
|
41
|
+
id: "response-1",
|
|
42
|
+
object: "chat.completion",
|
|
43
|
+
created: 1,
|
|
44
|
+
model: "served-model",
|
|
45
|
+
choices: [{
|
|
46
|
+
index: 0,
|
|
47
|
+
message: {
|
|
48
|
+
role: "assistant",
|
|
49
|
+
content: "answer",
|
|
50
|
+
reasoning_content: "because",
|
|
51
|
+
},
|
|
52
|
+
finish_reason: "stop",
|
|
53
|
+
logprobs: {
|
|
54
|
+
content: [{ token: "answer", logprob: -0.1, top_logprobs: [] }],
|
|
55
|
+
},
|
|
56
|
+
}],
|
|
57
|
+
usage: {
|
|
58
|
+
prompt_tokens: 3,
|
|
59
|
+
completion_tokens: 5,
|
|
60
|
+
total_tokens: 8,
|
|
61
|
+
completion_tokens_details: { reasoning_tokens: 2 },
|
|
62
|
+
},
|
|
63
|
+
balance: { amount: 1.25, currency: "USD" },
|
|
64
|
+
};
|
|
65
|
+
const result = await executeOpenAICompatible({
|
|
66
|
+
...request,
|
|
67
|
+
retryAttempts: 0,
|
|
68
|
+
captureRawBody: true,
|
|
69
|
+
body: {
|
|
70
|
+
grammar: "root ::= \"answer\"",
|
|
71
|
+
id_slot: 2,
|
|
72
|
+
},
|
|
73
|
+
fetch: async (_input, init) => {
|
|
74
|
+
body = JSON.parse(String(init?.body)) as Record<string, unknown>;
|
|
75
|
+
return new Response(JSON.stringify(responseBody), {
|
|
76
|
+
headers: { "content-type": "application/json" },
|
|
77
|
+
});
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
assert.equal(body?.grammar, "root ::= \"answer\"");
|
|
82
|
+
assert.equal(body?.id_slot, 2);
|
|
83
|
+
assert.equal(result.model, "served-model");
|
|
84
|
+
assert.equal(result.content, "answer");
|
|
85
|
+
assert.equal(result.reasoning, "because");
|
|
86
|
+
assert.equal(result.finishReason, "stop");
|
|
87
|
+
assert.deepEqual(result.usage, {
|
|
88
|
+
prompt: 3,
|
|
89
|
+
completion: 3,
|
|
90
|
+
reasoning: 2,
|
|
91
|
+
cached: 0,
|
|
92
|
+
total: 8,
|
|
93
|
+
});
|
|
94
|
+
assert.equal(result.logprobs[0]?.token, "answer");
|
|
95
|
+
assert.deepEqual(result.metadata.balance, { amount: 1.25, currency: "USD" });
|
|
96
|
+
assert.deepEqual(result.rawBody, responseBody);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test("the adapter maps leading system messages to AI SDK instructions", async () => {
|
|
100
|
+
const calls: Record<string, unknown>[] = [];
|
|
101
|
+
const fetch: typeof globalThis.fetch = async (_url, init) => {
|
|
102
|
+
calls.push(JSON.parse(String(init?.body)) as Record<string, unknown>);
|
|
103
|
+
return new Response(JSON.stringify({
|
|
104
|
+
model: "m",
|
|
105
|
+
choices: [{ message: { content: "ok" }, finish_reason: "stop" }],
|
|
106
|
+
usage: { prompt_tokens: 2, completion_tokens: 1, total_tokens: 3 },
|
|
107
|
+
}), { status: 200, headers: { "content-type": "application/json" } });
|
|
108
|
+
};
|
|
109
|
+
await executeOpenAICompatible({
|
|
110
|
+
url: "https://example.test/v1/chat/completions",
|
|
111
|
+
model: "m",
|
|
112
|
+
headers: {},
|
|
113
|
+
body: {},
|
|
114
|
+
messages: [
|
|
115
|
+
{ role: "system", content: "system contract" },
|
|
116
|
+
{ role: "user", content: "hello" },
|
|
117
|
+
],
|
|
118
|
+
fetchTimeoutMs: 1000,
|
|
119
|
+
retryAttempts: 0,
|
|
120
|
+
streaming: false,
|
|
121
|
+
captureRawBody: false,
|
|
122
|
+
fetch,
|
|
123
|
+
});
|
|
124
|
+
assert.deepEqual(calls[0]?.messages, [
|
|
125
|
+
{ role: "system", content: "system contract" },
|
|
126
|
+
{ role: "user", content: "hello" },
|
|
127
|
+
]);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test("the adapter preserves nonstandard reasoning accounting after SDK parsing", async (t) => {
|
|
131
|
+
const execute = (responseBody: object) => executeOpenAICompatible({
|
|
132
|
+
...request,
|
|
133
|
+
retryAttempts: 0,
|
|
134
|
+
fetch: async () => new Response(JSON.stringify(responseBody), {
|
|
135
|
+
headers: { "content-type": "application/json" },
|
|
136
|
+
}),
|
|
137
|
+
});
|
|
138
|
+
const response = (
|
|
139
|
+
message: Record<string, unknown>,
|
|
140
|
+
usage: Record<string, number>,
|
|
141
|
+
) => ({
|
|
142
|
+
id: "response-1",
|
|
143
|
+
object: "chat.completion",
|
|
144
|
+
created: 1,
|
|
145
|
+
model: "served-model",
|
|
146
|
+
choices: [{ index: 0, message: { role: "assistant", ...message }, finish_reason: "stop" }],
|
|
147
|
+
usage,
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
await t.test("Gemini-style total gap becomes reasoning", async () => {
|
|
151
|
+
const result = await execute(response(
|
|
152
|
+
{ content: "answer" },
|
|
153
|
+
{ prompt_tokens: 2, completion_tokens: 3, total_tokens: 9 },
|
|
154
|
+
));
|
|
155
|
+
assert.deepEqual(result.usage, {
|
|
156
|
+
prompt: 2,
|
|
157
|
+
completion: 3,
|
|
158
|
+
reasoning: 4,
|
|
159
|
+
cached: 0,
|
|
160
|
+
total: 9,
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
await t.test("Fireworks-style unitemized output is split by returned channels", async () => {
|
|
165
|
+
const result = await execute(response(
|
|
166
|
+
{ content: "aa", reasoning_content: "bbbbbb" },
|
|
167
|
+
{ prompt_tokens: 2, completion_tokens: 10, total_tokens: 12 },
|
|
168
|
+
));
|
|
169
|
+
assert.deepEqual(result.usage, {
|
|
170
|
+
prompt: 2,
|
|
171
|
+
completion: 2,
|
|
172
|
+
reasoning: 8,
|
|
173
|
+
cached: 0,
|
|
174
|
+
total: 12,
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
await t.test("streamed Gemini-style total gap is preserved", async () => {
|
|
179
|
+
const chunks = [
|
|
180
|
+
{
|
|
181
|
+
id: "response-1",
|
|
182
|
+
object: "chat.completion.chunk",
|
|
183
|
+
created: 1,
|
|
184
|
+
model: "served-model",
|
|
185
|
+
choices: [{ index: 0, delta: { content: "answer" }, finish_reason: null }],
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
id: "response-1",
|
|
189
|
+
object: "chat.completion.chunk",
|
|
190
|
+
created: 1,
|
|
191
|
+
model: "served-model",
|
|
192
|
+
choices: [{ index: 0, delta: {}, finish_reason: "stop" }],
|
|
193
|
+
usage: { prompt_tokens: 2, completion_tokens: 3, total_tokens: 9 },
|
|
194
|
+
},
|
|
195
|
+
];
|
|
196
|
+
const result = await executeOpenAICompatible({
|
|
197
|
+
...request,
|
|
198
|
+
retryAttempts: 0,
|
|
199
|
+
streaming: true,
|
|
200
|
+
fetch: async () => new Response(
|
|
201
|
+
`${chunks.map((chunk) => `data: ${JSON.stringify(chunk)}\n\n`).join("")}data: [DONE]\n\n`,
|
|
202
|
+
{ headers: { "content-type": "text/event-stream" } },
|
|
203
|
+
),
|
|
204
|
+
});
|
|
205
|
+
assert.deepEqual(result.usage, {
|
|
206
|
+
prompt: 2,
|
|
207
|
+
completion: 3,
|
|
208
|
+
reasoning: 4,
|
|
209
|
+
cached: 0,
|
|
210
|
+
total: 9,
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
await t.test("streamed Fireworks-style channels preserve the output split", async () => {
|
|
215
|
+
const chunks = [
|
|
216
|
+
{
|
|
217
|
+
id: "response-1",
|
|
218
|
+
object: "chat.completion.chunk",
|
|
219
|
+
created: 1,
|
|
220
|
+
model: "served-model",
|
|
221
|
+
choices: [{
|
|
222
|
+
index: 0,
|
|
223
|
+
delta: { reasoning_content: "bbbbbb", content: "aa" },
|
|
224
|
+
finish_reason: null,
|
|
225
|
+
}],
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
id: "response-1",
|
|
229
|
+
object: "chat.completion.chunk",
|
|
230
|
+
created: 1,
|
|
231
|
+
model: "served-model",
|
|
232
|
+
choices: [{ index: 0, delta: {}, finish_reason: "stop" }],
|
|
233
|
+
usage: { prompt_tokens: 2, completion_tokens: 10, total_tokens: 12 },
|
|
234
|
+
},
|
|
235
|
+
];
|
|
236
|
+
const result = await executeOpenAICompatible({
|
|
237
|
+
...request,
|
|
238
|
+
retryAttempts: 0,
|
|
239
|
+
streaming: true,
|
|
240
|
+
fetch: async () => new Response(
|
|
241
|
+
`${chunks.map((chunk) => `data: ${JSON.stringify(chunk)}\n\n`).join("")}data: [DONE]\n\n`,
|
|
242
|
+
{ headers: { "content-type": "text/event-stream" } },
|
|
243
|
+
),
|
|
244
|
+
});
|
|
245
|
+
assert.deepEqual(result.usage, {
|
|
246
|
+
prompt: 2,
|
|
247
|
+
completion: 2,
|
|
248
|
+
reasoning: 8,
|
|
249
|
+
cached: 0,
|
|
250
|
+
total: 12,
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
});
|