project-nomad/admin/app/controllers/ollama_controller.ts
2026-02-04 16:45:12 -08:00

95 lines
3.3 KiB
TypeScript

import { OllamaService } from '#services/ollama_service'
import { RagService } from '#services/rag_service'
import { modelNameSchema } from '#validators/download'
import { chatSchema, getAvailableModelsSchema } from '#validators/ollama'
import { inject } from '@adonisjs/core'
import type { HttpContext } from '@adonisjs/core/http'
import { SYSTEM_PROMPTS } from '../../constants/ollama.js'
@inject()
export default class OllamaController {
constructor(
private ollamaService: OllamaService,
private ragService: RagService
) {}
async availableModels({ request }: HttpContext) {
const reqData = await request.validateUsing(getAvailableModelsSchema)
return await this.ollamaService.getAvailableModels({
sort: reqData.sort,
recommendedOnly: reqData.recommendedOnly,
query: reqData.query || null,
})
}
async chat({ request }: HttpContext) {
const reqData = await request.validateUsing(chatSchema)
/**If there are no system messages in the chat
*(i.e. first message from the user)inject system prompts
**/
const hasSystemMessage = reqData.messages.some((msg) => msg.role === 'system')
if (!hasSystemMessage) {
const systemPrompt = {
role: 'system' as const,
content: SYSTEM_PROMPTS.default,
}
reqData.messages.unshift(systemPrompt)
}
// Get the last user message to use for RAG context retrieval
const lastUserMessage = [...reqData.messages].reverse().find((msg) => msg.role === 'user')
if (lastUserMessage) {
// Search for relevant context in the knowledge base
// Using lower threshold (0.3) with improved hybrid search
const relevantDocs = await this.ragService.searchSimilarDocuments(
lastUserMessage.content,
5, // Retrieve top 5 most relevant chunks
0.3 // Minimum similarity score of 0.3 (lowered from 0.7 for better recall)
)
// If relevant context is found, inject as a system message
if (relevantDocs.length > 0) {
const contextText = relevantDocs
.map((doc, idx) => `[Context ${idx + 1}] (Relevance: ${(doc.score * 100).toFixed(1)}%)\n${doc.text}`)
.join('\n\n')
const systemMessage = {
role: 'system' as const,
content: SYSTEM_PROMPTS.rag_context(contextText),
}
// Insert system message at the beginning (after any existing system messages)
const firstNonSystemIndex = reqData.messages.findIndex((msg) => msg.role !== 'system')
const insertIndex = firstNonSystemIndex === -1 ? 0 : firstNonSystemIndex
reqData.messages.splice(insertIndex, 0, systemMessage)
}
}
return await this.ollamaService.chat(reqData)
}
async deleteModel({ request }: HttpContext) {
const reqData = await request.validateUsing(modelNameSchema)
await this.ollamaService.deleteModel(reqData.model)
return {
success: true,
message: `Model deleted: ${reqData.model}`,
}
}
async dispatchModelDownload({ request }: HttpContext) {
const reqData = await request.validateUsing(modelNameSchema)
await this.ollamaService.dispatchModelDownload(reqData.model)
return {
success: true,
message: `Download job dispatched for model: ${reqData.model}`,
}
}
async installedModels({}: HttpContext) {
return await this.ollamaService.getModels()
}
}