dsh-llm-verifier 0.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.
@@ -0,0 +1,324 @@
1
+ import { settingsNamespace } from "@deepseek-ai/dsh-settings";
2
+ import { BlockAssembler, ReasoningEffortId, createUserMessage, deepFreeze } from "@deepseek-ai/dsh-llm";
3
+ import { credentialRef } from "@deepseek-ai/dsh-credentials";
4
+ //#region src/top-logprobs.ts
5
+ var TopLogprobsUnsupportedError = class extends Error {
6
+ constructor(message) {
7
+ super(message);
8
+ this.name = "TopLogprobsUnsupportedError";
9
+ }
10
+ };
11
+ function object(value) {
12
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
13
+ }
14
+ function text(value) {
15
+ return typeof value === "string" && value.trim() ? value.trim() : void 0;
16
+ }
17
+ function endpoint(baseURL) {
18
+ return baseURL.replace(/\/+$/, "") + "/chat/completions";
19
+ }
20
+ function dataUrl(image) {
21
+ return "data:" + image.mediaType + ";base64," + Buffer.from(image.data.buffer, image.data.byteOffset, image.data.byteLength).toString("base64");
22
+ }
23
+ async function credential(ctx, name) {
24
+ if (!name) return void 0;
25
+ return (await ctx.get("credentials")?.resolve(credentialRef(name)))?.value;
26
+ }
27
+ async function resolveTopLogprobRoute(ctx, provider) {
28
+ const settings = ctx.get("settings");
29
+ if (provider === "deepseek-official") {
30
+ const value = settings ? object(settings.get(settingsNamespace("llm-deepseek"))) ?? {} : {};
31
+ const apiKey = await credential(ctx, text(value.apiKeyEnv) ?? "DEEPSEEK_API_KEY");
32
+ if (!apiKey) return void 0;
33
+ return {
34
+ baseURL: text(value.baseURL) ?? "https://api.deepseek.com",
35
+ apiKey,
36
+ deepSeekThinking: true
37
+ };
38
+ }
39
+ if (!settings) return void 0;
40
+ const profile = object(object(object(settings.get(settingsNamespace("llm-pi-ai")))?.providers)?.[provider]);
41
+ if (!profile || profile.api !== "openai-completions") return void 0;
42
+ const baseURL = text(profile.baseURL);
43
+ if (!baseURL || !/^https:\/\//i.test(baseURL)) return void 0;
44
+ const apiKey = await credential(ctx, text(profile.apiKeyEnv));
45
+ const rawHeaders = object(profile.headers);
46
+ const headers = rawHeaders === void 0 ? void 0 : Object.fromEntries(Object.entries(rawHeaders).filter((entry) => typeof entry[1] === "string"));
47
+ return {
48
+ baseURL,
49
+ ...apiKey ? { apiKey } : {},
50
+ ...headers ? { headers } : {},
51
+ deepSeekThinking: false
52
+ };
53
+ }
54
+ async function callTopLogprobs(route, model, prompt, maxTokens, reasoningEffort, signal, images) {
55
+ const content = images?.length ? [{
56
+ type: "text",
57
+ text: prompt
58
+ }, ...images.map((image) => ({
59
+ type: "image_url",
60
+ image_url: { url: dataUrl(image) }
61
+ }))] : prompt;
62
+ const thinking = route.deepSeekThinking && reasoningEffort ? reasoningEffort === "off" ? { thinking: { type: "disabled" } } : {
63
+ thinking: { type: "enabled" },
64
+ reasoning_effort: reasoningEffort
65
+ } : {};
66
+ const response = await fetch(endpoint(route.baseURL), {
67
+ method: "POST",
68
+ redirect: "error",
69
+ signal,
70
+ headers: {
71
+ "content-type": "application/json",
72
+ ...route.apiKey ? { authorization: "Bearer " + route.apiKey } : {},
73
+ ...route.headers
74
+ },
75
+ body: JSON.stringify({
76
+ model,
77
+ messages: [{
78
+ role: "user",
79
+ content
80
+ }],
81
+ max_tokens: maxTokens,
82
+ temperature: 1,
83
+ logprobs: true,
84
+ top_logprobs: 20,
85
+ ...thinking
86
+ })
87
+ });
88
+ const raw = await response.text();
89
+ if (!response.ok) {
90
+ const excerpt = raw.slice(0, 1e3);
91
+ if ([
92
+ 400,
93
+ 404,
94
+ 405,
95
+ 415,
96
+ 422
97
+ ].includes(response.status) && /logprob|top_logprobs|unsupported|unknown (?:field|parameter)|unrecognized (?:field|parameter)|not support/i.test(excerpt)) throw new TopLogprobsUnsupportedError("provider rejected top_logprobs: HTTP " + response.status + " " + excerpt);
98
+ throw new Error("llm-verifier: top_logprobs request failed with HTTP " + response.status + ": " + excerpt);
99
+ }
100
+ let body;
101
+ try {
102
+ body = object(JSON.parse(raw)) ?? {};
103
+ } catch {
104
+ throw new Error("llm-verifier: top_logprobs endpoint returned invalid JSON");
105
+ }
106
+ const choice = object((Array.isArray(body.choices) ? body.choices : [])[0]);
107
+ const message = object(choice?.message);
108
+ const answer = typeof message?.content === "string" ? message.content : "";
109
+ const logprobs = object(choice?.logprobs);
110
+ const rows = Array.isArray(logprobs?.content) ? logprobs.content : [];
111
+ if (!rows.length) throw new TopLogprobsUnsupportedError("provider returned no token logprobs");
112
+ const tokens = [];
113
+ const positions = [];
114
+ for (const rawRow of rows) {
115
+ const row = object(rawRow) ?? {};
116
+ const token = typeof row.token === "string" ? row.token : "";
117
+ tokens.push(token);
118
+ const alternatives = (Array.isArray(row.top_logprobs) ? row.top_logprobs : []).flatMap((value) => {
119
+ const item = object(value);
120
+ return item && typeof item.token === "string" && typeof item.logprob === "number" ? [{
121
+ token: item.token,
122
+ logprob: item.logprob
123
+ }] : [];
124
+ });
125
+ if (!alternatives.length && typeof row.logprob === "number") alternatives.push({
126
+ token,
127
+ logprob: row.logprob
128
+ });
129
+ positions.push(alternatives);
130
+ }
131
+ const rawUsage = object(body.usage) ?? {};
132
+ const promptDetails = object(rawUsage.prompt_tokens_details) ?? {};
133
+ const completionDetails = object(rawUsage.completion_tokens_details) ?? {};
134
+ const cached = Number(rawUsage.prompt_cache_hit_tokens ?? promptDetails.cached_tokens ?? 0) || 0;
135
+ const input = Number(rawUsage.prompt_tokens ?? 0) || 0;
136
+ return {
137
+ text: answer,
138
+ tokens,
139
+ positions,
140
+ scoringMode: "top-logprobs",
141
+ usage: {
142
+ calls: 1,
143
+ attempts: 1,
144
+ retries: 0,
145
+ inputTokens: Math.max(0, input - cached),
146
+ cachedInputTokens: cached,
147
+ outputTokens: Number(rawUsage.completion_tokens ?? 0) || 0,
148
+ reasoningTokens: Number(completionDetails.reasoning_tokens ?? 0) || 0
149
+ }
150
+ };
151
+ }
152
+ var TopLogprobCapabilityCache = class {
153
+ unsupported = /* @__PURE__ */ new Set();
154
+ isUnsupported(provider, model) {
155
+ return this.unsupported.has(provider + "\0" + model);
156
+ }
157
+ markUnsupported(provider, model) {
158
+ this.unsupported.add(provider + "\0" + model);
159
+ }
160
+ };
161
+ //#endregion
162
+ //#region src/caller.ts
163
+ function failureMessage(finish) {
164
+ if (finish.kind === "error" || finish.kind === "aborted") return finish.failure.message;
165
+ if (finish.kind === "max-tokens") return "verifier response reached max tokens before completing its answer";
166
+ }
167
+ async function delay(ms, signal) {
168
+ if (signal?.aborted) throw signal.reason;
169
+ await new Promise((resolve, reject) => {
170
+ const timer = setTimeout(resolve, ms);
171
+ const abort = () => {
172
+ clearTimeout(timer);
173
+ reject(signal?.reason);
174
+ };
175
+ signal?.addEventListener("abort", abort, { once: true });
176
+ });
177
+ }
178
+ function usage(attempts, value = {}) {
179
+ return {
180
+ calls: 1,
181
+ attempts,
182
+ retries: attempts - 1,
183
+ inputTokens: value.inputTokens ?? 0,
184
+ cachedInputTokens: (value.cacheReadTokens ?? 0) + (value.cacheWriteTokens ?? 0),
185
+ outputTokens: value.outputTokens ?? 0,
186
+ reasoningTokens: value.reasoningTokens ?? 0
187
+ };
188
+ }
189
+ async function callExplicitTag(config, prompt, signal, images) {
190
+ let attempt = 0;
191
+ while (true) {
192
+ attempt += 1;
193
+ const controller = new AbortController();
194
+ const timeout = setTimeout(() => controller.abort(/* @__PURE__ */ new Error("llm-verifier: request timed out")), config.timeoutMs);
195
+ const abort = () => controller.abort(signal?.reason);
196
+ signal?.addEventListener("abort", abort, { once: true });
197
+ try {
198
+ const content = [{
199
+ type: "text",
200
+ text: prompt
201
+ }];
202
+ for (const image of images ?? []) {
203
+ const ref = await config.attachments.saveImage({
204
+ data: image.data,
205
+ mediaType: image.mediaType
206
+ });
207
+ content.push({
208
+ type: "image",
209
+ attachment: ref
210
+ });
211
+ }
212
+ const messages = [createUserMessage({
213
+ content,
214
+ source: {
215
+ kind: "plugin",
216
+ plugin: "dsh-llm-verifier"
217
+ }
218
+ })];
219
+ const assembler = new BlockAssembler();
220
+ const options = deepFreeze({
221
+ provider: config.provider,
222
+ model: config.model,
223
+ ...config.reasoningEffort ? { reasoningEffort: ReasoningEffortId(config.reasoningEffort) } : {},
224
+ messages,
225
+ maxTokens: config.maxTokens,
226
+ temperature: 1,
227
+ signal: controller.signal
228
+ });
229
+ for await (const chunk of config.llm.stream(options)) assembler.push(chunk);
230
+ const failed = failureMessage(assembler.finish);
231
+ if (failed !== void 0) throw new Error("llm-verifier: model call failed: " + failed);
232
+ const text = assembler.blocks().filter((block) => block.type === "text").map((block) => block.text).join("");
233
+ if (!text.trim()) throw new Error("llm-verifier: selected DSH model produced no text");
234
+ return {
235
+ text,
236
+ tokens: [],
237
+ positions: [],
238
+ scoringMode: "explicit-tag",
239
+ usage: usage(attempt, assembler.usage)
240
+ };
241
+ } catch (error) {
242
+ if (signal?.aborted) throw signal.reason;
243
+ if (attempt > config.maxRetries || !(error instanceof Error) || !/rate|quota|timeout|timed out|temporar|network|fetch|socket|5dd/i.test(error.message)) throw error;
244
+ await delay(Math.min(3e4, config.retryBaseDelayMs * 2 ** (attempt - 1) * (.8 + Math.random() * .4)), signal);
245
+ } finally {
246
+ clearTimeout(timeout);
247
+ signal?.removeEventListener("abort", abort);
248
+ }
249
+ }
250
+ }
251
+ var RequestLimiter = class {
252
+ limit;
253
+ active = 0;
254
+ queue = [];
255
+ constructor(limit) {
256
+ this.limit = limit;
257
+ }
258
+ async run(operation, signal) {
259
+ if (this.active >= this.limit) await new Promise((resolve, reject) => {
260
+ const enter = () => {
261
+ signal?.removeEventListener("abort", abort);
262
+ resolve();
263
+ };
264
+ const abort = () => {
265
+ const index = this.queue.indexOf(enter);
266
+ if (index >= 0) this.queue.splice(index, 1);
267
+ reject(signal?.reason);
268
+ };
269
+ this.queue.push(enter);
270
+ signal?.addEventListener("abort", abort, { once: true });
271
+ });
272
+ if (signal?.aborted) throw signal.reason;
273
+ this.active += 1;
274
+ try {
275
+ return await operation();
276
+ } finally {
277
+ this.active -= 1;
278
+ this.queue.shift()?.();
279
+ }
280
+ }
281
+ };
282
+ async function callAutomatic(config, prompt, signal, images) {
283
+ if (!config.topLogprobCapabilities.isUnsupported(config.provider, config.model)) {
284
+ const route = await resolveTopLogprobRoute(config.ctx, config.provider);
285
+ if (route !== void 0) try {
286
+ return await callTopLogprobs(route, config.model, prompt, config.maxTokens, config.reasoningEffort, signal, images);
287
+ } catch (error) {
288
+ if (!(error instanceof TopLogprobsUnsupportedError)) throw error;
289
+ config.topLogprobCapabilities.markUnsupported(config.provider, config.model);
290
+ }
291
+ else config.topLogprobCapabilities.markUnsupported(config.provider, config.model);
292
+ }
293
+ return callExplicitTag(config, prompt, signal, images);
294
+ }
295
+ async function callVerifier(config, prompt, signal, images) {
296
+ const invoke = () => callAutomatic(config, prompt, signal, images);
297
+ return config.limiter === void 0 ? invoke() : config.limiter.run(invoke, signal);
298
+ }
299
+ function addUsage(target, source) {
300
+ for (const key of [
301
+ "calls",
302
+ "attempts",
303
+ "retries",
304
+ "inputTokens",
305
+ "cachedInputTokens",
306
+ "outputTokens",
307
+ "reasoningTokens"
308
+ ]) target[key] += source[key];
309
+ }
310
+ function emptyUsage() {
311
+ return {
312
+ calls: 0,
313
+ attempts: 0,
314
+ retries: 0,
315
+ inputTokens: 0,
316
+ cachedInputTokens: 0,
317
+ outputTokens: 0,
318
+ reasoningTokens: 0
319
+ };
320
+ }
321
+ //#endregion
322
+ export { TopLogprobCapabilityCache as a, emptyUsage as i, addUsage as n, callVerifier as r, RequestLimiter as t };
323
+
324
+ //# sourceMappingURL=caller-CGlgZ-Su.js.map
package/lib/caller.js ADDED
@@ -0,0 +1,2 @@
1
+ import { i as emptyUsage, n as addUsage, r as callVerifier, t as RequestLimiter } from "./caller-CGlgZ-Su.js";
2
+ export { RequestLimiter, addUsage, callVerifier, emptyUsage };