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

This commit is contained in:
2026-09-22 15:14:28 +02:00
parent 72e429b1b3
commit 99a3277e64
10 changed files with 380 additions and 71 deletions
+10 -8
View File
@@ -294,8 +294,8 @@ export function createChatService(repositories, modelService, config, webSearchP
content: entry.content
}));
messagesPayload.push(...conversationMessages.slice(0, -1));
const visionContext = attachmentService?.context(history);
if (visionContext) messagesPayload.push(visionContext);
const visionContext = attachmentService?.context(history, conversation.at(-1)?.content || "");
if (visionContext) messagesPayload.push(...visionContext);
if (webSearchContext) messagesPayload.push(webSearchContext[0]);
if (webSearchContext?.[1]) messagesPayload.push(webSearchContext[1]);
if (conversationMessages.length) messagesPayload.push(conversationMessages.at(-1));
@@ -337,14 +337,15 @@ export function createChatService(repositories, modelService, config, webSearchP
return {fullReply: opener, thinking, promptMessageId: null};
}
if (attachmentService?.list(promptMessage.id).length) onStatus?.({status: "analyzing"});
await attachmentService?.analyze(promptMessage.id, promptMessage.content, signal);
const imageHistory = repositories.getRecentChatMessagesUpToId(chatId, promptMessage.id).reverse();
if (attachmentService?.hasImages(imageHistory)) onStatus?.({status: "analyzing"});
await attachmentService?.analyzeContext(imageHistory, promptMessage.content, signal);
let sources = null;
let searchState = "skipped";
if (webSearch) {
const search = await retrieveWebSearch(promptMessage.content, webSearchProvider, {
signal,
referenceText: attachmentService?.searchContext(promptMessage.id) || "",
referenceText: attachmentService?.searchContext(imageHistory, promptMessage.content) || "",
onSearching: () => onStatus?.({status: "searching"})
});
sources = search.sources;
@@ -406,13 +407,14 @@ export function createChatService(repositories, modelService, config, webSearchP
const fullReply = await withActiveGeneration(chatId, signal, async (activeSignal) => {
activeSignal.throwIfAborted();
onStatus?.({status: "accepted"});
if (attachments.length) {
const imageHistory = repositories.getRecentChatMessagesUpToId(chatId, userMessageId).reverse();
if (attachmentService?.hasImages(imageHistory)) {
onStatus?.({status: "analyzing"});
await attachmentService.analyze(userMessageId, message, activeSignal);
await attachmentService.analyzeContext(imageHistory, message, activeSignal);
}
if (webSearch) {
const search = await retrieveWebSearch(message, webSearchProvider, {
referenceText: attachments.length ? attachmentService.searchContext(userMessageId) : "",
referenceText: attachmentService?.searchContext(imageHistory, message) || "",
signal: activeSignal,
onSearching: () => onStatus?.({status: "searching"})
});