add vision analysis service with prompt versioning, response schema, and structured JSON output

This commit is contained in:
2026-09-23 20:34:33 +02:00
parent 85e6d0c957
commit 80a30e2ce0
7 changed files with 142 additions and 23 deletions
+16 -2
View File
@@ -104,7 +104,7 @@ export function createModelService(config, overrides = {}) {
}
};
const readStreamingReply = async (response, onChunk = null, onThinking = null, touch = () => { }, signal = null) => {
const readStreamingReply = async (response, onChunk = null, onThinking = null, touch = () => { }, signal = null, onRawReply = null) => {
if (!response.body) {
throw new ModelServiceError("MODEL_BAD_RESPONSE", "Model backend returned no body.", 502);
}
@@ -161,6 +161,7 @@ export function createModelService(config, overrides = {}) {
consume(buffer.trim());
}
if (!completed) throw new ModelServiceError("MODEL_INTERRUPTED", "Model response was interrupted.", 502);
onRawReply?.(fullReply);
return fullReply.trim();
} finally {
signal?.removeEventListener("abort", cancel);
@@ -187,6 +188,18 @@ export function createModelService(config, overrides = {}) {
};
return {
getRequestMetadata(model, generation = {}, reasoning = false) {
const body = buildRequestBody(model, [], generation, reasoning);
return {
model: body.model,
think: body.think,
stream: body.stream,
temperature: body.options?.temperature ?? null,
numCtx: body.options?.num_ctx ?? null,
numPredict: body.options?.num_predict ?? null,
keepAlive: body.keep_alive
};
},
async generateReply(model, messagesPayload, options = {}) {
const state = ensureModelState(model);
state.activeRequests += 1;
@@ -206,7 +219,8 @@ export function createModelService(config, overrides = {}) {
}
touch();
return readStreamingReply(response, null, options.reasoning === true && supportsReasoning(model) ? options.onThinking : null, touch, signal);
return readStreamingReply(response, null, options.reasoning === true && supportsReasoning(model) ? options.onThinking : null,
touch, signal, options.onRawReply);
}, options.signal);
lastHealth = { ok: true, checkedAt: new Date().toISOString(), error: null, latencyMs: Date.now() - startedAt };
return fullReply;