@oscharko-dev/keiko-server 0.2.7 → 0.2.8
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/.tsbuildinfo +1 -1
- package/dist/deps.d.ts +1 -0
- package/dist/deps.d.ts.map +1 -1
- package/dist/files.d.ts +18 -0
- package/dist/files.d.ts.map +1 -1
- package/dist/files.js +174 -0
- package/dist/gateway-readiness.d.ts +6 -0
- package/dist/gateway-readiness.d.ts.map +1 -0
- package/dist/gateway-readiness.js +624 -0
- package/dist/grounded-qa-hybrid.d.ts.map +1 -1
- package/dist/grounded-qa-hybrid.js +2 -0
- package/dist/grounded-qa-multi-source.d.ts.map +1 -1
- package/dist/grounded-qa-multi-source.js +1 -0
- package/dist/grounded-qa.d.ts.map +1 -1
- package/dist/grounded-qa.js +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/local-knowledge-grounded-qa.d.ts.map +1 -1
- package/dist/local-knowledge-grounded-qa.js +11 -2
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +5 -10
- package/dist/run-handlers.d.ts +0 -1
- package/dist/run-handlers.d.ts.map +1 -1
- package/dist/run-handlers.js +0 -217
- package/dist/store/db.d.ts.map +1 -1
- package/dist/store/db.js +2 -1
- package/dist/store/index.d.ts +1 -1
- package/dist/store/index.d.ts.map +1 -1
- package/dist/store/messages.d.ts +2 -1
- package/dist/store/messages.d.ts.map +1 -1
- package/dist/store/messages.js +46 -4
- package/dist/store/schema.d.ts +1 -1
- package/dist/store/schema.d.ts.map +1 -1
- package/dist/store/schema.js +7 -1
- package/dist/store/types.d.ts +3 -2
- package/dist/store/types.d.ts.map +1 -1
- package/package.json +19 -19
- package/dist/grounded-handoff.d.ts +0 -4
- package/dist/grounded-handoff.d.ts.map +0 -1
- package/dist/grounded-handoff.js +0 -445
|
@@ -0,0 +1,624 @@
|
|
|
1
|
+
import { apiKeyHeaderValue, DEFAULT_API_KEY_HEADER_NAME, isConversationEligibleModel, listConfiguredCapabilities, } from "@oscharko-dev/keiko-model-gateway";
|
|
2
|
+
import { gatewayFetch, readJsonCapped, readSseStream, } from "@oscharko-dev/keiko-model-gateway/internal/http";
|
|
3
|
+
import { currentGatewayConfig, currentGatewayEgressConfig } from "./deps.js";
|
|
4
|
+
const DEFAULT_PROBES = [
|
|
5
|
+
"chat",
|
|
6
|
+
"streaming",
|
|
7
|
+
"tool_calling",
|
|
8
|
+
"json_schema",
|
|
9
|
+
];
|
|
10
|
+
const DEEP_PROBES = [
|
|
11
|
+
"reasoning",
|
|
12
|
+
"image_input",
|
|
13
|
+
"document_input",
|
|
14
|
+
"long_context",
|
|
15
|
+
];
|
|
16
|
+
const ALL_PROBES = new Set([...DEFAULT_PROBES, ...DEEP_PROBES]);
|
|
17
|
+
const VERIFIED_CAPABILITY_PROBES = [
|
|
18
|
+
["streaming", "streaming"],
|
|
19
|
+
["toolCalling", "tool_calling"],
|
|
20
|
+
["structuredOutput", "json_schema"],
|
|
21
|
+
["reasoningOutput", "reasoning"],
|
|
22
|
+
["imageInput", "image_input"],
|
|
23
|
+
["documentInput", "document_input"],
|
|
24
|
+
];
|
|
25
|
+
const MAX_MODEL_ID_CHARS = 240;
|
|
26
|
+
const MAX_BODY_BYTES = 64_000;
|
|
27
|
+
const MAX_CONTEXT_TOKENS = 128_000;
|
|
28
|
+
const DEFAULT_LONG_CONTEXT_TOKENS = 32_000;
|
|
29
|
+
const EXTENDED_LONG_CONTEXT_TOKENS = 64_000;
|
|
30
|
+
const MAX_PROVIDER_RESPONSE_BYTES = 500_000;
|
|
31
|
+
const RED_PIXEL_PNG_DATA_URL = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVR42mP8z8AAAAMBAQDJ/pLvAAAAAElFTkSuQmCC";
|
|
32
|
+
const MINI_PDF_DATA_URL = "data:application/pdf;base64,JVBERi0xLjQKMSAwIG9iago8PCAvVHlwZSAvQ2F0YWxvZyAvUGFnZXMgMiAwIFIgPj4KZW5kb2JqCjIgMCBvYmoKPDwgL1R5cGUgL1BhZ2VzIC9LaWRzIFszIDAgUl0gL0NvdW50IDEgPj4KZW5kb2JqCjMgMCBvYmoKPDwgL1R5cGUgL1BhZ2UgL1BhcmVudCAyIDAgUiAvTWVkaWFCb3ggWzAgMCAzMDAgMTQ0XSAvQ29udGVudHMgNCAwIFIgL1Jlc291cmNlcyA8PCAvRm9udCA8PCAvRjEgNSAwIFIgPj4gPj4gPj4KZW5kb2JqCjQgMCBvYmoKPDwgL0xlbmd0aCA2MSA+PgpzdHJlYW0KQlQKL0YxIDE4IFRmCjUwIDgwIFRkCihLRUlLTyBQREYgUkVBRElORVNTIFBST0JFKSBUagpFVApzdHJlYW0KZW5kb2JqCjUgMCBvYmoKPDwgL1R5cGUgL0ZvbnQgL1N1YnR5cGUgL1R5cGUxIC9CYXNlRm9udCAvSGVsdmV0aWNhID4+CmVuZG9iagp4cmVmCjAgNgowMDAwMDAwMDAwIDY1NTM1IGYgCjAwMDAwMDAwMDkgMDAwMDAgbiAKMDAwMDAwMDA1OCAwMDAwMCBuIAowMDAwMDAwMTE1IDAwMDAwIG4gCjAwMDAwMDAyNjIgMDAwMDAgbiAKMDAwMDAwMDM3MyAwMDAwMCBuIAp0cmFpbGVyCjw8IC9Sb290IDEgMCBSIC9TaXplIDYgPj4Kc3RhcnR4cmVmCjQ0MgolJUVPRgo=";
|
|
33
|
+
class ReadinessBodyTooLargeError extends Error {
|
|
34
|
+
constructor() {
|
|
35
|
+
super("Readiness request body exceeds the size limit.");
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function readBody(req) {
|
|
39
|
+
return new Promise((resolve, reject) => {
|
|
40
|
+
const chunks = [];
|
|
41
|
+
let bytes = 0;
|
|
42
|
+
req.on("data", (chunk) => {
|
|
43
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk, "utf8");
|
|
44
|
+
bytes += buffer.byteLength;
|
|
45
|
+
if (bytes > MAX_BODY_BYTES) {
|
|
46
|
+
reject(new ReadinessBodyTooLargeError());
|
|
47
|
+
req.destroy();
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
chunks.push(buffer);
|
|
51
|
+
});
|
|
52
|
+
req.on("end", () => {
|
|
53
|
+
resolve(Buffer.concat(chunks).toString("utf8"));
|
|
54
|
+
});
|
|
55
|
+
req.on("error", reject);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
function error(code, message, status = 400) {
|
|
59
|
+
return { status, body: { error: { code, message } } };
|
|
60
|
+
}
|
|
61
|
+
function isRecord(value) {
|
|
62
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
63
|
+
}
|
|
64
|
+
function isProbeName(value) {
|
|
65
|
+
return typeof value === "string" && ALL_PROBES.has(value);
|
|
66
|
+
}
|
|
67
|
+
async function readJsonBody(req) {
|
|
68
|
+
let raw;
|
|
69
|
+
try {
|
|
70
|
+
raw = await readBody(req);
|
|
71
|
+
}
|
|
72
|
+
catch (bodyError) {
|
|
73
|
+
if (bodyError instanceof ReadinessBodyTooLargeError) {
|
|
74
|
+
return error("PAYLOAD_TOO_LARGE", "Readiness request body exceeds the size limit.", 413);
|
|
75
|
+
}
|
|
76
|
+
return error("BAD_REQUEST", "The readiness request body could not be read.");
|
|
77
|
+
}
|
|
78
|
+
let parsed;
|
|
79
|
+
try {
|
|
80
|
+
parsed = raw.trim().length === 0 ? {} : JSON.parse(raw);
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return error("BAD_REQUEST", "The readiness request body must be valid JSON.");
|
|
84
|
+
}
|
|
85
|
+
if (!isRecord(parsed)) {
|
|
86
|
+
return error("BAD_REQUEST", "The readiness request body must be a JSON object.");
|
|
87
|
+
}
|
|
88
|
+
return { parsed: parseReadinessRequest(parsed) };
|
|
89
|
+
}
|
|
90
|
+
function parseReadinessRequest(raw) {
|
|
91
|
+
const modelId = typeof raw.modelId === "string" && raw.modelId.trim().length > 0
|
|
92
|
+
? raw.modelId.trim().slice(0, MAX_MODEL_ID_CHARS)
|
|
93
|
+
: undefined;
|
|
94
|
+
return {
|
|
95
|
+
...(modelId !== undefined ? { modelId } : {}),
|
|
96
|
+
...(isRecord(raw.options) ? { options: parseReadinessOptions(raw.options) } : {}),
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
function parseReadinessOptions(raw) {
|
|
100
|
+
const probes = Array.isArray(raw.probes) ? uniqueProbeNames(raw.probes) : undefined;
|
|
101
|
+
const includeDeepProbes = typeof raw.includeDeepProbes === "boolean" ? raw.includeDeepProbes : undefined;
|
|
102
|
+
const maxContextTokens = typeof raw.maxContextTokens === "number" && Number.isFinite(raw.maxContextTokens)
|
|
103
|
+
? Math.max(1, Math.min(MAX_CONTEXT_TOKENS, Math.trunc(raw.maxContextTokens)))
|
|
104
|
+
: undefined;
|
|
105
|
+
return {
|
|
106
|
+
...(probes !== undefined ? { probes } : {}),
|
|
107
|
+
...(includeDeepProbes !== undefined ? { includeDeepProbes } : {}),
|
|
108
|
+
...(maxContextTokens !== undefined ? { maxContextTokens } : {}),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
function uniqueProbeNames(values) {
|
|
112
|
+
const out = [];
|
|
113
|
+
for (const value of values) {
|
|
114
|
+
if (isProbeName(value) && !out.includes(value)) {
|
|
115
|
+
out.push(value);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return out.length > 0 ? out : [...DEFAULT_PROBES];
|
|
119
|
+
}
|
|
120
|
+
function requestedProbeNames(options) {
|
|
121
|
+
const base = options?.probes ?? DEFAULT_PROBES;
|
|
122
|
+
const names = new Set(["chat", ...base]);
|
|
123
|
+
if (options?.includeDeepProbes === true) {
|
|
124
|
+
for (const probe of DEEP_PROBES)
|
|
125
|
+
names.add(probe);
|
|
126
|
+
}
|
|
127
|
+
return Array.from(names);
|
|
128
|
+
}
|
|
129
|
+
function chooseProvider(config, requestedModelId) {
|
|
130
|
+
if (config === undefined || config.providers.length === 0) {
|
|
131
|
+
return error("NO_MODEL", "Configure a gateway before running readiness checks.");
|
|
132
|
+
}
|
|
133
|
+
const capabilities = listConfiguredCapabilities(config);
|
|
134
|
+
const modelId = requestedModelId ??
|
|
135
|
+
capabilities.find((capability) => isConversationEligibleModel(capability))?.id ??
|
|
136
|
+
config.providers[0]?.modelId;
|
|
137
|
+
const provider = config.providers.find((candidate) => candidate.modelId === modelId);
|
|
138
|
+
if (provider === undefined) {
|
|
139
|
+
return error("NO_MODEL", "Select a configured chat model before running readiness checks.");
|
|
140
|
+
}
|
|
141
|
+
const capability = capabilities.find((candidate) => candidate.id === provider.modelId);
|
|
142
|
+
if (capability !== undefined && !isConversationEligibleModel(capability)) {
|
|
143
|
+
return error("NO_MODEL", "Select a conversation-capable model before running readiness checks.");
|
|
144
|
+
}
|
|
145
|
+
return { config, provider, capability };
|
|
146
|
+
}
|
|
147
|
+
function providerHeaders(provider) {
|
|
148
|
+
const headerName = provider.apiKeyHeaderName ?? DEFAULT_API_KEY_HEADER_NAME;
|
|
149
|
+
return {
|
|
150
|
+
"content-type": "application/json",
|
|
151
|
+
[headerName]: apiKeyHeaderValue(headerName, provider.apiKey),
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
async function providerRequest(deps, provider, body, options = {}) {
|
|
155
|
+
const requestBody = JSON.stringify({
|
|
156
|
+
model: provider.modelId,
|
|
157
|
+
...body,
|
|
158
|
+
...(options.stream === true ? { stream: true, stream_options: { include_usage: true } } : {}),
|
|
159
|
+
});
|
|
160
|
+
return gatewayFetch(`${provider.baseUrl}/chat/completions`, {
|
|
161
|
+
method: "POST",
|
|
162
|
+
headers: providerHeaders(provider),
|
|
163
|
+
body: requestBody,
|
|
164
|
+
fetchImpl: deps.gatewayReadinessFetch,
|
|
165
|
+
timeoutMs: provider.timeoutMs,
|
|
166
|
+
maxResponseBytes: MAX_PROVIDER_RESPONSE_BYTES,
|
|
167
|
+
egress: provider.egress ?? currentGatewayEgressConfig(deps),
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
function result(name, status, start, evidence, warning) {
|
|
171
|
+
return {
|
|
172
|
+
name,
|
|
173
|
+
status,
|
|
174
|
+
latencyMs: Math.max(0, Date.now() - start),
|
|
175
|
+
evidence,
|
|
176
|
+
...(warning !== undefined ? { warning } : {}),
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
function skipped(name, evidence) {
|
|
180
|
+
return { name, status: "skipped", latencyMs: 0, evidence };
|
|
181
|
+
}
|
|
182
|
+
function providerWarning(errorValue) {
|
|
183
|
+
if (errorValue instanceof DOMException && errorValue.name === "TimeoutError") {
|
|
184
|
+
return "The probe timed out before the provider answered.";
|
|
185
|
+
}
|
|
186
|
+
return "The provider could not complete this probe. Chat configuration was not changed.";
|
|
187
|
+
}
|
|
188
|
+
function unsupportedStatus(response) {
|
|
189
|
+
return (response.status === 400 ||
|
|
190
|
+
response.status === 404 ||
|
|
191
|
+
response.status === 422 ||
|
|
192
|
+
response.status === 501);
|
|
193
|
+
}
|
|
194
|
+
function unsuccessfulEvidence(label, response) {
|
|
195
|
+
return `${label} endpoint returned HTTP ${response.status.toString()}.`;
|
|
196
|
+
}
|
|
197
|
+
function textFromContent(value) {
|
|
198
|
+
if (typeof value === "string")
|
|
199
|
+
return value;
|
|
200
|
+
if (!Array.isArray(value))
|
|
201
|
+
return "";
|
|
202
|
+
return value
|
|
203
|
+
.map((part) => (isRecord(part) && typeof part.text === "string" ? part.text : ""))
|
|
204
|
+
.join("");
|
|
205
|
+
}
|
|
206
|
+
function firstMessage(payload) {
|
|
207
|
+
if (!isRecord(payload) || !Array.isArray(payload.choices))
|
|
208
|
+
return undefined;
|
|
209
|
+
const choices = payload.choices;
|
|
210
|
+
const choice = choices[0];
|
|
211
|
+
if (!isRecord(choice) || !isRecord(choice.message))
|
|
212
|
+
return undefined;
|
|
213
|
+
return choice.message;
|
|
214
|
+
}
|
|
215
|
+
function assistantText(payload) {
|
|
216
|
+
return textFromContent(firstMessage(payload)?.content);
|
|
217
|
+
}
|
|
218
|
+
function hasToolCall(payload, toolName) {
|
|
219
|
+
const message = firstMessage(payload);
|
|
220
|
+
if (message === undefined || !Array.isArray(message.tool_calls))
|
|
221
|
+
return false;
|
|
222
|
+
return message.tool_calls.some((call) => {
|
|
223
|
+
if (!isRecord(call) || !isRecord(call.function))
|
|
224
|
+
return false;
|
|
225
|
+
return call.function.name === toolName;
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
function parseJsonObjectFromAssistant(payload) {
|
|
229
|
+
const text = assistantText(payload).trim();
|
|
230
|
+
if (text.length === 0)
|
|
231
|
+
return undefined;
|
|
232
|
+
try {
|
|
233
|
+
const parsed = JSON.parse(text);
|
|
234
|
+
return isRecord(parsed) ? parsed : undefined;
|
|
235
|
+
}
|
|
236
|
+
catch {
|
|
237
|
+
return undefined;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
function reasoningDetectedInMessage(message) {
|
|
241
|
+
if (message === undefined)
|
|
242
|
+
return false;
|
|
243
|
+
const fields = ["reasoning", "reasoning_content", "reasoning_details"];
|
|
244
|
+
return (fields.some((field) => message[field] !== undefined) ||
|
|
245
|
+
/<think>[\s\S]*<\/think>/iu.test(textFromContent(message.content)));
|
|
246
|
+
}
|
|
247
|
+
function firstDelta(chunk) {
|
|
248
|
+
if (!isRecord(chunk) || !Array.isArray(chunk.choices))
|
|
249
|
+
return undefined;
|
|
250
|
+
const choices = chunk.choices;
|
|
251
|
+
const choice = choices[0];
|
|
252
|
+
return isRecord(choice) && isRecord(choice.delta) ? choice.delta : undefined;
|
|
253
|
+
}
|
|
254
|
+
function deltaContent(chunk) {
|
|
255
|
+
return textFromContent(firstDelta(chunk)?.content);
|
|
256
|
+
}
|
|
257
|
+
async function readProviderJson(response) {
|
|
258
|
+
return readJsonCapped(response, MAX_PROVIDER_RESPONSE_BYTES);
|
|
259
|
+
}
|
|
260
|
+
async function probeChat(deps, provider) {
|
|
261
|
+
const start = Date.now();
|
|
262
|
+
try {
|
|
263
|
+
const response = await providerRequest(deps, provider, {
|
|
264
|
+
messages: [
|
|
265
|
+
{ role: "system", content: "You are a minimal readiness probe." },
|
|
266
|
+
{ role: "user", content: "Reply with exactly: keiko-ready" },
|
|
267
|
+
],
|
|
268
|
+
});
|
|
269
|
+
if (!response.ok) {
|
|
270
|
+
return result("chat", "failed", start, unsuccessfulEvidence("Basic chat", response));
|
|
271
|
+
}
|
|
272
|
+
const text = assistantText(await readProviderJson(response)).toLowerCase();
|
|
273
|
+
const passed = text.includes("keiko-ready");
|
|
274
|
+
return result("chat", passed ? "passed" : "failed", start, passed
|
|
275
|
+
? "Working today: basic chat returned the expected readiness token."
|
|
276
|
+
: "Basic chat answered, but did not return the expected readiness token.");
|
|
277
|
+
}
|
|
278
|
+
catch (probeError) {
|
|
279
|
+
return result("chat", "failed", start, "Basic chat could not be verified.", providerWarning(probeError));
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
async function probeStreaming(deps, provider) {
|
|
283
|
+
const start = Date.now();
|
|
284
|
+
try {
|
|
285
|
+
const response = await providerRequest(deps, provider, {
|
|
286
|
+
messages: [
|
|
287
|
+
{ role: "system", content: "You are a minimal streaming readiness probe." },
|
|
288
|
+
{ role: "user", content: "Reply with exactly: stream-ok" },
|
|
289
|
+
],
|
|
290
|
+
}, { stream: true });
|
|
291
|
+
if (!response.ok) {
|
|
292
|
+
const status = unsupportedStatus(response) ? "unsupported" : "failed";
|
|
293
|
+
return result("streaming", status, start, "Streaming was not accepted by the endpoint.");
|
|
294
|
+
}
|
|
295
|
+
let text = "";
|
|
296
|
+
for await (const chunk of readSseStream(response, MAX_PROVIDER_RESPONSE_BYTES)) {
|
|
297
|
+
text += deltaContent(chunk);
|
|
298
|
+
}
|
|
299
|
+
const passed = text.toLowerCase().includes("stream-ok");
|
|
300
|
+
return result("streaming", passed ? "passed" : "unsupported", start, passed
|
|
301
|
+
? "Streaming produced content deltas."
|
|
302
|
+
: "Streaming completed without the expected text delta.");
|
|
303
|
+
}
|
|
304
|
+
catch (probeError) {
|
|
305
|
+
return result("streaming", "failed", start, "Streaming could not be verified.", providerWarning(probeError));
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
async function probeToolCalling(deps, provider) {
|
|
309
|
+
const start = Date.now();
|
|
310
|
+
try {
|
|
311
|
+
const response = await providerRequest(deps, provider, toolCallingBody());
|
|
312
|
+
if (!response.ok) {
|
|
313
|
+
const status = unsupportedStatus(response) ? "unsupported" : "failed";
|
|
314
|
+
return toolCallingResult(provider, status, start);
|
|
315
|
+
}
|
|
316
|
+
return toolCallingPayloadResult(provider, start, await readProviderJson(response));
|
|
317
|
+
}
|
|
318
|
+
catch (probeError) {
|
|
319
|
+
return result("tool_calling", "failed", start, "Tool calling could not be verified.", providerWarning(probeError));
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
function toolCallingBody() {
|
|
323
|
+
return {
|
|
324
|
+
messages: [
|
|
325
|
+
{ role: "system", content: "Use the provided tool for readiness checks." },
|
|
326
|
+
{ role: "user", content: "Call the report_readiness tool with status ok." },
|
|
327
|
+
],
|
|
328
|
+
tools: [
|
|
329
|
+
{
|
|
330
|
+
type: "function",
|
|
331
|
+
function: {
|
|
332
|
+
name: "report_readiness",
|
|
333
|
+
description: "Report gateway readiness.",
|
|
334
|
+
parameters: {
|
|
335
|
+
type: "object",
|
|
336
|
+
additionalProperties: false,
|
|
337
|
+
properties: { status: { type: "string", enum: ["ok"] } },
|
|
338
|
+
required: ["status"],
|
|
339
|
+
},
|
|
340
|
+
},
|
|
341
|
+
},
|
|
342
|
+
],
|
|
343
|
+
tool_choice: { type: "function", function: { name: "report_readiness" } },
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
function toolCallingResult(provider, status, start) {
|
|
347
|
+
return result("tool_calling", status, start, "Tool calling was not accepted by the endpoint.", qwenToolWarning(provider, status));
|
|
348
|
+
}
|
|
349
|
+
function toolCallingPayloadResult(provider, start, payload) {
|
|
350
|
+
const passed = hasToolCall(payload, "report_readiness");
|
|
351
|
+
return result("tool_calling", passed ? "passed" : "unsupported", start, passed
|
|
352
|
+
? "OpenAI-compatible tool call returned the expected function name."
|
|
353
|
+
: "The endpoint answered without a valid tool call.", passed ? undefined : qwenToolWarning(provider, "unsupported"));
|
|
354
|
+
}
|
|
355
|
+
function qwenToolWarning(provider, status) {
|
|
356
|
+
if (status === "passed" || !provider.modelId.toLowerCase().includes("qwen3-coder"))
|
|
357
|
+
return undefined;
|
|
358
|
+
return "For Qwen3-Coder on vLLM, ask the provider to enable auto tool choice and the qwen3_coder tool parser.";
|
|
359
|
+
}
|
|
360
|
+
async function probeJsonSchema(deps, provider) {
|
|
361
|
+
const start = Date.now();
|
|
362
|
+
try {
|
|
363
|
+
const response = await providerRequest(deps, provider, jsonSchemaBody());
|
|
364
|
+
if (!response.ok) {
|
|
365
|
+
const status = unsupportedStatus(response) ? "unsupported" : "failed";
|
|
366
|
+
return jsonSchemaResult(status, start);
|
|
367
|
+
}
|
|
368
|
+
return jsonSchemaPayloadResult(start, await readProviderJson(response));
|
|
369
|
+
}
|
|
370
|
+
catch (probeError) {
|
|
371
|
+
return result("json_schema", "failed", start, "Structured JSON output could not be verified.", providerWarning(probeError));
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
function jsonSchemaBody() {
|
|
375
|
+
return {
|
|
376
|
+
messages: [
|
|
377
|
+
{ role: "system", content: "Return only JSON matching the schema." },
|
|
378
|
+
{ role: "user", content: 'Return {"status":"json-ok"}.' },
|
|
379
|
+
],
|
|
380
|
+
response_format: {
|
|
381
|
+
type: "json_schema",
|
|
382
|
+
json_schema: {
|
|
383
|
+
name: "keiko_readiness_probe",
|
|
384
|
+
strict: true,
|
|
385
|
+
schema: {
|
|
386
|
+
type: "object",
|
|
387
|
+
additionalProperties: false,
|
|
388
|
+
properties: { status: { const: "json-ok" } },
|
|
389
|
+
required: ["status"],
|
|
390
|
+
},
|
|
391
|
+
},
|
|
392
|
+
},
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
function jsonSchemaResult(status, start) {
|
|
396
|
+
return result("json_schema", status, start, "JSON schema response_format was not accepted by the endpoint.");
|
|
397
|
+
}
|
|
398
|
+
function jsonSchemaPayloadResult(start, payload) {
|
|
399
|
+
const json = parseJsonObjectFromAssistant(payload);
|
|
400
|
+
const passed = json?.status === "json-ok";
|
|
401
|
+
return result("json_schema", passed ? "passed" : "unsupported", start, passed
|
|
402
|
+
? "Structured JSON output matched the expected schema."
|
|
403
|
+
: "The endpoint answered, but did not produce schema-valid JSON.");
|
|
404
|
+
}
|
|
405
|
+
async function probeReasoning(deps, provider) {
|
|
406
|
+
const start = Date.now();
|
|
407
|
+
try {
|
|
408
|
+
const response = await providerRequest(deps, provider, {
|
|
409
|
+
messages: [
|
|
410
|
+
{ role: "system", content: "Run a reasoning readiness probe. Do not reveal private data." },
|
|
411
|
+
{ role: "user", content: "/think\nWhat is 1 + 1? End with FINAL: 2." },
|
|
412
|
+
],
|
|
413
|
+
reasoning_effort: "low",
|
|
414
|
+
});
|
|
415
|
+
if (!response.ok) {
|
|
416
|
+
const status = unsupportedStatus(response) ? "unsupported" : "failed";
|
|
417
|
+
return result("reasoning", status, start, "Reasoning parameters were not accepted by the endpoint.", qwenReasoningWarning(provider));
|
|
418
|
+
}
|
|
419
|
+
const payload = await readProviderJson(response);
|
|
420
|
+
const detected = reasoningDetectedInMessage(firstMessage(payload));
|
|
421
|
+
return result("reasoning", detected ? "passed" : "unsupported", start, detected
|
|
422
|
+
? "Reasoning output was detected in provider fields or think tags."
|
|
423
|
+
: "Reasoning output not verified on this deployment.", detected ? undefined : qwenReasoningWarning(provider));
|
|
424
|
+
}
|
|
425
|
+
catch (probeError) {
|
|
426
|
+
return result("reasoning", "failed", start, "Reasoning output could not be verified.", providerWarning(probeError));
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
function qwenReasoningWarning(provider) {
|
|
430
|
+
return provider.modelId.toLowerCase().includes("qwen3-coder")
|
|
431
|
+
? "Reasoning output is not assumed from the model name; it must be confirmed by this deployment."
|
|
432
|
+
: undefined;
|
|
433
|
+
}
|
|
434
|
+
async function probeImageInput(deps, provider) {
|
|
435
|
+
const start = Date.now();
|
|
436
|
+
try {
|
|
437
|
+
const response = await providerRequest(deps, provider, {
|
|
438
|
+
messages: [
|
|
439
|
+
{
|
|
440
|
+
role: "user",
|
|
441
|
+
content: [
|
|
442
|
+
{ type: "text", text: "What color is this one-pixel image? Answer one word." },
|
|
443
|
+
{ type: "image_url", image_url: { url: RED_PIXEL_PNG_DATA_URL } },
|
|
444
|
+
],
|
|
445
|
+
},
|
|
446
|
+
],
|
|
447
|
+
});
|
|
448
|
+
if (!response.ok) {
|
|
449
|
+
const status = unsupportedStatus(response) ? "unsupported" : "failed";
|
|
450
|
+
return result("image_input", status, start, "Image input was not accepted by the endpoint.");
|
|
451
|
+
}
|
|
452
|
+
const passed = /\bred\b/iu.test(assistantText(await readProviderJson(response)));
|
|
453
|
+
return result("image_input", passed ? "passed" : "unsupported", start, passed
|
|
454
|
+
? "Mini image content was interpreted correctly."
|
|
455
|
+
: "The endpoint accepted image input but did not identify the test image content.");
|
|
456
|
+
}
|
|
457
|
+
catch (probeError) {
|
|
458
|
+
return result("image_input", "failed", start, "Image input could not be verified.", providerWarning(probeError));
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
async function probeDocumentInput(deps, provider) {
|
|
462
|
+
const start = Date.now();
|
|
463
|
+
try {
|
|
464
|
+
const response = await providerRequest(deps, provider, documentInputBody());
|
|
465
|
+
if (!response.ok) {
|
|
466
|
+
const status = unsupportedStatus(response) ? "unsupported" : "failed";
|
|
467
|
+
return documentInputResult(status, start);
|
|
468
|
+
}
|
|
469
|
+
return documentInputPayloadResult(start, await readProviderJson(response));
|
|
470
|
+
}
|
|
471
|
+
catch (probeError) {
|
|
472
|
+
return result("document_input", "failed", start, "Document input could not be verified.", providerWarning(probeError));
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
function documentInputBody() {
|
|
476
|
+
return {
|
|
477
|
+
messages: [
|
|
478
|
+
{
|
|
479
|
+
role: "user",
|
|
480
|
+
content: [
|
|
481
|
+
{ type: "text", text: "Read the attached PDF. Reply with the exact probe phrase." },
|
|
482
|
+
{
|
|
483
|
+
type: "file",
|
|
484
|
+
file: {
|
|
485
|
+
filename: "keiko-readiness.pdf",
|
|
486
|
+
file_data: MINI_PDF_DATA_URL,
|
|
487
|
+
},
|
|
488
|
+
},
|
|
489
|
+
],
|
|
490
|
+
},
|
|
491
|
+
],
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
function documentInputResult(status, start) {
|
|
495
|
+
return result("document_input", status, start, "PDF document input was not accepted by the endpoint.");
|
|
496
|
+
}
|
|
497
|
+
function documentInputPayloadResult(start, payload) {
|
|
498
|
+
const passed = /keiko pdf readiness probe/iu.test(assistantText(payload));
|
|
499
|
+
return result("document_input", passed ? "passed" : "unsupported", start, passed
|
|
500
|
+
? "Mini PDF content was interpreted correctly."
|
|
501
|
+
: "The endpoint accepted a document part but did not extract the test PDF content.");
|
|
502
|
+
}
|
|
503
|
+
function longContextTokens(options, capability) {
|
|
504
|
+
const contextWindow = capability?.contextWindow ?? 0;
|
|
505
|
+
if (options?.maxContextTokens !== undefined) {
|
|
506
|
+
const deploymentCeiling = contextWindow > 0 ? contextWindow : MAX_CONTEXT_TOKENS;
|
|
507
|
+
return Math.min(options.maxContextTokens, deploymentCeiling, MAX_CONTEXT_TOKENS);
|
|
508
|
+
}
|
|
509
|
+
if (contextWindow >= EXTENDED_LONG_CONTEXT_TOKENS)
|
|
510
|
+
return EXTENDED_LONG_CONTEXT_TOKENS;
|
|
511
|
+
return DEFAULT_LONG_CONTEXT_TOKENS;
|
|
512
|
+
}
|
|
513
|
+
function longContextBody(tokens) {
|
|
514
|
+
const approximateChars = tokens * 4;
|
|
515
|
+
const filler = "alpha beta gamma delta epsilon zeta eta theta\n".repeat(Math.max(1, Math.ceil(approximateChars / 46)));
|
|
516
|
+
const sentinel = "KEIKO_LONG_CONTEXT_SENTINEL";
|
|
517
|
+
return {
|
|
518
|
+
sentinel,
|
|
519
|
+
body: {
|
|
520
|
+
messages: [
|
|
521
|
+
{ role: "system", content: "Find the sentinel at the end of the user message." },
|
|
522
|
+
{
|
|
523
|
+
role: "user",
|
|
524
|
+
content: `${filler.slice(0, approximateChars)}\n${sentinel}\nReply with exactly the sentinel.`,
|
|
525
|
+
},
|
|
526
|
+
],
|
|
527
|
+
},
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
function longContextPayloadResult(start, tokens, sentinel, payload) {
|
|
531
|
+
const passed = assistantText(payload).includes(sentinel);
|
|
532
|
+
return result("long_context", passed ? "passed" : "unsupported", start, passed
|
|
533
|
+
? `${tokens.toString()} approximate tokens were accepted and the sentinel was recovered.`
|
|
534
|
+
: "The endpoint answered, but did not recover the long-context sentinel.");
|
|
535
|
+
}
|
|
536
|
+
async function probeLongContext(deps, provider, capability, options) {
|
|
537
|
+
const start = Date.now();
|
|
538
|
+
const tokens = longContextTokens(options, capability);
|
|
539
|
+
const { body, sentinel } = longContextBody(tokens);
|
|
540
|
+
try {
|
|
541
|
+
const response = await providerRequest(deps, provider, body);
|
|
542
|
+
if (!response.ok) {
|
|
543
|
+
const status = unsupportedStatus(response) ? "unsupported" : "failed";
|
|
544
|
+
return result("long_context", status, start, `${tokens.toString()} approximate tokens were not accepted.`);
|
|
545
|
+
}
|
|
546
|
+
return longContextPayloadResult(start, tokens, sentinel, await readProviderJson(response));
|
|
547
|
+
}
|
|
548
|
+
catch (probeError) {
|
|
549
|
+
return result("long_context", "failed", start, "Long-context input could not be verified.", providerWarning(probeError));
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
async function runProbe(name, deps, selection, options) {
|
|
553
|
+
if (name === "chat")
|
|
554
|
+
return probeChat(deps, selection.provider);
|
|
555
|
+
if (name === "streaming")
|
|
556
|
+
return probeStreaming(deps, selection.provider);
|
|
557
|
+
if (name === "tool_calling")
|
|
558
|
+
return probeToolCalling(deps, selection.provider);
|
|
559
|
+
if (name === "json_schema")
|
|
560
|
+
return probeJsonSchema(deps, selection.provider);
|
|
561
|
+
if (name === "reasoning")
|
|
562
|
+
return probeReasoning(deps, selection.provider);
|
|
563
|
+
if (name === "image_input")
|
|
564
|
+
return probeImageInput(deps, selection.provider);
|
|
565
|
+
if (name === "document_input")
|
|
566
|
+
return probeDocumentInput(deps, selection.provider);
|
|
567
|
+
return probeLongContext(deps, selection.provider, selection.capability, options);
|
|
568
|
+
}
|
|
569
|
+
function reportStatus(probes) {
|
|
570
|
+
const chat = probes.find((probe) => probe.name === "chat");
|
|
571
|
+
if (chat?.status !== "passed")
|
|
572
|
+
return "failed";
|
|
573
|
+
return probes.every((probe) => probe.status === "passed") ? "ready" : "partial";
|
|
574
|
+
}
|
|
575
|
+
function verifiedCapabilities(probes) {
|
|
576
|
+
const passed = new Set(probes
|
|
577
|
+
.filter((probe) => probe.status === "passed")
|
|
578
|
+
.map((probe) => probe.name));
|
|
579
|
+
const longContext = probes.find((probe) => probe.name === "long_context" && probe.status === "passed");
|
|
580
|
+
const tokenMatch = longContext?.evidence.match(/(\d+) approximate tokens/u);
|
|
581
|
+
const testedContextTokens = tokenMatch === undefined || tokenMatch === null
|
|
582
|
+
? undefined
|
|
583
|
+
: Number.parseInt(tokenMatch[1] ?? "0", 10);
|
|
584
|
+
const verified = Object.fromEntries(VERIFIED_CAPABILITY_PROBES.map(([key, probe]) => [key, passed.has(probe) || undefined]));
|
|
585
|
+
return {
|
|
586
|
+
...verified,
|
|
587
|
+
testedContextTokens,
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
export async function runGatewayReadiness(request, deps) {
|
|
591
|
+
const selection = chooseProvider(currentGatewayConfig(deps), request.modelId);
|
|
592
|
+
if ("status" in selection)
|
|
593
|
+
return selection;
|
|
594
|
+
const names = requestedProbeNames(request.options);
|
|
595
|
+
const probes = [];
|
|
596
|
+
const chat = await runProbe("chat", deps, selection, request.options);
|
|
597
|
+
probes.push(chat);
|
|
598
|
+
if (chat.status !== "passed") {
|
|
599
|
+
for (const name of names.filter((candidate) => candidate !== "chat")) {
|
|
600
|
+
probes.push(skipped(name, "Skipped because basic chat was not verified."));
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
else {
|
|
604
|
+
for (const name of names.filter((candidate) => candidate !== "chat")) {
|
|
605
|
+
probes.push(await runProbe(name, deps, selection, request.options));
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
return {
|
|
609
|
+
modelId: selection.provider.modelId,
|
|
610
|
+
checkedAt: new Date().toISOString(),
|
|
611
|
+
overallStatus: reportStatus(probes),
|
|
612
|
+
probes,
|
|
613
|
+
verifiedCapabilities: verifiedCapabilities(probes),
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
export async function handleGatewayReadiness(ctx, deps) {
|
|
617
|
+
const body = await readJsonBody(ctx.req);
|
|
618
|
+
if ("status" in body)
|
|
619
|
+
return body;
|
|
620
|
+
const report = await runGatewayReadiness(body.parsed, deps);
|
|
621
|
+
if ("status" in report)
|
|
622
|
+
return report;
|
|
623
|
+
return { status: 200, body: report };
|
|
624
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"grounded-qa-hybrid.d.ts","sourceRoot":"","sources":["../src/grounded-qa-hybrid.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAE7D,OAAO,EAIL,KAAK,cAAc,EACnB,KAAK,eAAe,EACrB,MAAM,qCAAqC,CAAC;AAiB7C,OAAO,EAGL,KAAK,uBAAuB,EAO7B,MAAM,wCAAwC,CAAC;AAIhD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C,OAAO,KAAK,EAAY,aAAa,EAAE,MAAM,WAAW,CAAC;AAEzD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAM7C,OAAO,EAML,KAAK,iBAAiB,EACvB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAQL,KAAK,2BAA2B,EACjC,MAAM,kCAAkC,CAAC;AAE1C,OAAO,EAEL,KAAK,qBAAqB,EAE3B,MAAM,sBAAsB,CAAC;AAwB9B,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,IAAI,GAAG,SAAS,uBAAuB,EAAE,CAExF;AAID,wBAAgB,eAAe,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,MAAM,EAAE,CAU/E;AAID,MAAM,MAAM,eAAe,GAAG,iBAAiB,CAAC;AAChD,MAAM,MAAM,iBAAiB,GAAG,CAC9B,KAAK,EAAE,cAAc,EACrB,KAAK,EAAE,uBAAuB,EAC9B,QAAQ,EAAE,2BAA2B,KAClC,OAAO,CAAC,eAAe,CAAC,CAAC;AAC9B,MAAM,MAAM,cAAc,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,qBAAqB,CAAC,CAAC;AAE9F,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;IACpB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,QAAQ,CAAC,eAAe,CAAC,EAAE,eAAe,CAAC;IAC3C,QAAQ,CAAC,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;IAC/C,QAAQ,CAAC,MAAM,CAAC,EAAE,cAAc,CAAC;IAGjC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,SAAS;QACpC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;QACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;KAC1B,EAAE,CAAC;CACL;AAwND,qBAAa,qBAAsB,SAAQ,KAAK;aACX,MAAM,EAAE,WAAW;gBAAnB,MAAM,EAAE,WAAW;CAIvD;AA2FD,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,SAAS,EAChB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,WAAW,GAClB,cAAc,CAwBhB;
|
|
1
|
+
{"version":3,"file":"grounded-qa-hybrid.d.ts","sourceRoot":"","sources":["../src/grounded-qa-hybrid.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAE7D,OAAO,EAIL,KAAK,cAAc,EACnB,KAAK,eAAe,EACrB,MAAM,qCAAqC,CAAC;AAiB7C,OAAO,EAGL,KAAK,uBAAuB,EAO7B,MAAM,wCAAwC,CAAC;AAIhD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C,OAAO,KAAK,EAAY,aAAa,EAAE,MAAM,WAAW,CAAC;AAEzD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAM7C,OAAO,EAML,KAAK,iBAAiB,EACvB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAQL,KAAK,2BAA2B,EACjC,MAAM,kCAAkC,CAAC;AAE1C,OAAO,EAEL,KAAK,qBAAqB,EAE3B,MAAM,sBAAsB,CAAC;AAwB9B,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,IAAI,GAAG,SAAS,uBAAuB,EAAE,CAExF;AAID,wBAAgB,eAAe,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,MAAM,EAAE,CAU/E;AAID,MAAM,MAAM,eAAe,GAAG,iBAAiB,CAAC;AAChD,MAAM,MAAM,iBAAiB,GAAG,CAC9B,KAAK,EAAE,cAAc,EACrB,KAAK,EAAE,uBAAuB,EAC9B,QAAQ,EAAE,2BAA2B,KAClC,OAAO,CAAC,eAAe,CAAC,CAAC;AAC9B,MAAM,MAAM,cAAc,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,qBAAqB,CAAC,CAAC;AAE9F,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;IACpB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,QAAQ,CAAC,eAAe,CAAC,EAAE,eAAe,CAAC;IAC3C,QAAQ,CAAC,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;IAC/C,QAAQ,CAAC,MAAM,CAAC,EAAE,cAAc,CAAC;IAGjC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,SAAS;QACpC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;QACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;KAC1B,EAAE,CAAC;CACL;AAwND,qBAAa,qBAAsB,SAAQ,KAAK;aACX,MAAM,EAAE,WAAW;gBAAnB,MAAM,EAAE,WAAW;CAIvD;AA2FD,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,SAAS,EAChB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,WAAW,GAClB,cAAc,CAwBhB;AA6eD,wBAAsB,oBAAoB,CAAC,GAAG,EAAE,oBAAoB,GAAG,OAAO,CAAC,WAAW,CAAC,CAS1F"}
|
|
@@ -576,6 +576,7 @@ function assembleHybridNoEvidenceRoute(ctx, store, meta, selected, limits) {
|
|
|
576
576
|
folderSourceCount: meta.folderScopeCount,
|
|
577
577
|
connectorSourceCount: meta.connectorScopeCount,
|
|
578
578
|
}, store, selected, limits, { content, usage: { promptTokens: 0, completionTokens: 0 } }, { userMessageId: userMessage.id, assistantMessageId: assistantMessage.id });
|
|
579
|
+
ctx.deps.store.attachGroundedAnswer(assistantMessage.id, answer);
|
|
579
580
|
return { status: 200, body: answer };
|
|
580
581
|
}
|
|
581
582
|
export async function runHybridGroundedAsk(ctx) {
|
|
@@ -737,6 +738,7 @@ async function answerAndAssemble(ctx, store, meta) {
|
|
|
737
738
|
folderSourceCount: meta.folderScopeCount,
|
|
738
739
|
connectorSourceCount: meta.connectorScopeCount,
|
|
739
740
|
}, store, selected, limits, assistant, { userMessageId: userMessage.id, assistantMessageId: assistantMessage.id });
|
|
741
|
+
ctx.deps.store.attachGroundedAnswer(assistantMessage.id, answer);
|
|
740
742
|
return { status: 200, body: answer };
|
|
741
743
|
}
|
|
742
744
|
// Issue #154 (GAP-B) — a GatewayError is redacted inside mappedGatewayError (shared with the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"grounded-qa-multi-source.d.ts","sourceRoot":"","sources":["../src/grounded-qa-multi-source.ts"],"names":[],"mappings":"AAWA,OAAO,EAGL,KAAK,WAAW,IAAI,kBAAkB,EACvC,MAAM,mCAAmC,CAAC;AAC3C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAQ7D,OAAO,EAIL,KAAK,oBAAoB,EACzB,KAAK,iBAAiB,EAGvB,MAAM,iDAAiD,CAAC;AACzD,OAAO,EAEL,KAAK,kBAAkB,EAGvB,KAAK,gCAAgC,EAGtC,MAAM,wCAAwC,CAAC;AAEhD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,KAAK,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAEzD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAIL,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,EACzB,MAAM,4BAA4B,CAAC;AAKpC,OAAO,EAEL,KAAK,qBAAqB,EAE3B,MAAM,sBAAsB,CAAC;AA4B9B,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,IAAI,GAAG,SAAS,kBAAkB,EAAE,CAE9E;AAOD,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,iBAAiB,EAAE,CAAC,EAAE,MAAM,GAAG,iBAAiB,CAY5F;AAgBD,wBAAgB,YAAY,CAAC,MAAM,EAAE,SAAS,kBAAkB,EAAE,GAAG,SAAS,MAAM,EAAE,CAUrF;AAoID,wBAAgB,yBAAyB,CACvC,SAAS,EAAE,SAAS,gCAAgC,EAAE,GACrD,gCAAgC,CA0BlC;AAID,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,oBAAoB,CAAC;CACrC;AAoBD,wBAAgB,+BAA+B,CAC7C,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,SAAS,WAAW,EAAE,EACpC,QAAQ,EAAE,QAAQ,GACjB,SAAS,kBAAkB,EAAE,CAE/B;AAmFD,MAAM,MAAM,iBAAiB,GAAG,CAAC,KAAK,EAAE,iBAAiB,KAAK,OAAO,CAAC,mBAAmB,CAAC,CAAC;AAI3F,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,WAAW,GAAG,iBAAiB,CAUvE;AAID,MAAM,MAAM,mBAAmB,GAAG,CAChC,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,SAAS,WAAW,EAAE,KACjC,OAAO,CAAC,qBAAqB,CAAC,CAAC;AAEpC,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,SAAS,EAChB,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,QAAQ,EAClB,MAAM,EAAE,WAAW,GAClB,mBAAmB,CAqBrB;AAyBD,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;IACpB,QAAQ,CAAC,MAAM,EAAE,SAAS,kBAAkB,EAAE,CAAC;IAC/C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAC7B,QAAQ,CAAC,SAAS,EAAE,iBAAiB,CAAC;IACtC,QAAQ,CAAC,QAAQ,EAAE,mBAAmB,CAAC;IACvC,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAG7B,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS;QAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CACvF;AAkND,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,mBAAmB,GAAG,OAAO,CAAC,WAAW,CAAC,
|
|
1
|
+
{"version":3,"file":"grounded-qa-multi-source.d.ts","sourceRoot":"","sources":["../src/grounded-qa-multi-source.ts"],"names":[],"mappings":"AAWA,OAAO,EAGL,KAAK,WAAW,IAAI,kBAAkB,EACvC,MAAM,mCAAmC,CAAC;AAC3C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAQ7D,OAAO,EAIL,KAAK,oBAAoB,EACzB,KAAK,iBAAiB,EAGvB,MAAM,iDAAiD,CAAC;AACzD,OAAO,EAEL,KAAK,kBAAkB,EAGvB,KAAK,gCAAgC,EAGtC,MAAM,wCAAwC,CAAC;AAEhD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,KAAK,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAEzD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAIL,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,EACzB,MAAM,4BAA4B,CAAC;AAKpC,OAAO,EAEL,KAAK,qBAAqB,EAE3B,MAAM,sBAAsB,CAAC;AA4B9B,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,IAAI,GAAG,SAAS,kBAAkB,EAAE,CAE9E;AAOD,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,iBAAiB,EAAE,CAAC,EAAE,MAAM,GAAG,iBAAiB,CAY5F;AAgBD,wBAAgB,YAAY,CAAC,MAAM,EAAE,SAAS,kBAAkB,EAAE,GAAG,SAAS,MAAM,EAAE,CAUrF;AAoID,wBAAgB,yBAAyB,CACvC,SAAS,EAAE,SAAS,gCAAgC,EAAE,GACrD,gCAAgC,CA0BlC;AAID,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,oBAAoB,CAAC;CACrC;AAoBD,wBAAgB,+BAA+B,CAC7C,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,SAAS,WAAW,EAAE,EACpC,QAAQ,EAAE,QAAQ,GACjB,SAAS,kBAAkB,EAAE,CAE/B;AAmFD,MAAM,MAAM,iBAAiB,GAAG,CAAC,KAAK,EAAE,iBAAiB,KAAK,OAAO,CAAC,mBAAmB,CAAC,CAAC;AAI3F,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,WAAW,GAAG,iBAAiB,CAUvE;AAID,MAAM,MAAM,mBAAmB,GAAG,CAChC,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,SAAS,WAAW,EAAE,KACjC,OAAO,CAAC,qBAAqB,CAAC,CAAC;AAEpC,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,SAAS,EAChB,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,QAAQ,EAClB,MAAM,EAAE,WAAW,GAClB,mBAAmB,CAqBrB;AAyBD,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;IACpB,QAAQ,CAAC,MAAM,EAAE,SAAS,kBAAkB,EAAE,CAAC;IAC/C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAC7B,QAAQ,CAAC,SAAS,EAAE,iBAAiB,CAAC;IACtC,QAAQ,CAAC,QAAQ,EAAE,mBAAmB,CAAC;IACvC,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAG7B,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS;QAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CACvF;AAkND,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,mBAAmB,GAAG,OAAO,CAAC,WAAW,CAAC,CA6CtF"}
|
|
@@ -481,6 +481,7 @@ export async function runMultiSourceAsk(ctx) {
|
|
|
481
481
|
userMessageId: userMessage.id,
|
|
482
482
|
assistantMessageId: assistantMessage.id,
|
|
483
483
|
});
|
|
484
|
+
ctx.deps.store.attachGroundedAnswer(assistantMessage.id, answer);
|
|
484
485
|
rememberGroundedTurn({
|
|
485
486
|
assistantMessageId: assistantMessage.id,
|
|
486
487
|
chatId: ctx.chat.id,
|