Skip to content

Commit 276bdcd

Browse files
committed
feat(AI Assistant): query rewriting for enhanced context retrieval
1 parent 921eef3 commit 276bdcd

File tree

3 files changed

+100
-26
lines changed

3 files changed

+100
-26
lines changed

admin/app/controllers/ollama_controller.ts

Lines changed: 70 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@ import { chatSchema, getAvailableModelsSchema } from '#validators/ollama'
55
import { inject } from '@adonisjs/core'
66
import type { HttpContext } from '@adonisjs/core/http'
77
import { SYSTEM_PROMPTS } from '../../constants/ollama.js'
8+
import logger from '@adonisjs/core/services/logger'
9+
import type { Message } from 'ollama'
810

911
@inject()
1012
export default class OllamaController {
1113
constructor(
1214
private ollamaService: OllamaService,
1315
private ragService: RagService
14-
) {}
16+
) { }
1517

1618
async availableModels({ request }: HttpContext) {
1719
const reqData = await request.validateUsing(getAvailableModelsSchema)
@@ -25,9 +27,8 @@ export default class OllamaController {
2527
async chat({ request }: HttpContext) {
2628
const reqData = await request.validateUsing(chatSchema)
2729

28-
/**If there are no system messages in the chat
29-
*(i.e. first message from the user)inject system prompts
30-
**/
30+
// If there are no system messages in the chat
31+
// (i.e. first message from the user) inject system prompts
3132
const hasSystemMessage = reqData.messages.some((msg) => msg.role === 'system')
3233
if (!hasSystemMessage) {
3334
const systemPrompt = {
@@ -37,18 +38,22 @@ export default class OllamaController {
3738
reqData.messages.unshift(systemPrompt)
3839
}
3940

40-
// Get the last user message to use for RAG context retrieval
41-
const lastUserMessage = [...reqData.messages].reverse().find((msg) => msg.role === 'user')
41+
// Query rewriting for better RAG retrieval with manageable context
42+
// Will return user's latest message if no rewriting is needed
43+
const rewrittenQuery = await this.rewriteQueryWithContext(
44+
reqData.messages,
45+
reqData.model
46+
)
4247

43-
if (lastUserMessage) {
44-
// Search for relevant context in the knowledge base
45-
// Using lower threshold (0.3) with improved hybrid search
48+
if (rewrittenQuery) {
4649
const relevantDocs = await this.ragService.searchSimilarDocuments(
47-
lastUserMessage.content,
48-
5, // Retrieve top 5 most relevant chunks
49-
0.3 // Minimum similarity score of 0.3 (lowered from 0.7 for better recall)
50+
rewrittenQuery,
51+
5, // Top 5 most relevant chunks
52+
0.3 // Minimum similarity score of 0.3
5053
)
5154

55+
logger.debug(`[RAG] Retrieved ${relevantDocs.length} relevant documents for query: "${rewrittenQuery}"`)
56+
5257
// If relevant context is found, inject as a system message
5358
if (relevantDocs.length > 0) {
5459
const contextText = relevantDocs
@@ -88,7 +93,59 @@ export default class OllamaController {
8893
}
8994
}
9095

91-
async installedModels({}: HttpContext) {
96+
async installedModels({ }: HttpContext) {
9297
return await this.ollamaService.getModels()
9398
}
99+
100+
private async rewriteQueryWithContext(
101+
messages: Message[],
102+
model: string
103+
): Promise<string | null> {
104+
try {
105+
// Get recent conversation history (last 6 messages for 3 turns)
106+
const recentMessages = messages.slice(-6)
107+
108+
// If there's only one user message, no rewriting needed
109+
const userMessages = recentMessages.filter(msg => msg.role === 'user')
110+
if (userMessages.length <= 1) {
111+
return userMessages[0]?.content || null
112+
}
113+
114+
const conversationContext = recentMessages
115+
.map(msg => {
116+
const role = msg.role === 'user' ? 'User' : 'Assistant'
117+
// Truncate assistant messages to first 200 chars to keep context manageable
118+
const content = msg.role === 'assistant'
119+
? msg.content.slice(0, 200) + (msg.content.length > 200 ? '...' : '')
120+
: msg.content
121+
return `${role}: "${content}"`
122+
})
123+
.join('\n')
124+
125+
const response = await this.ollamaService.chat({
126+
model,
127+
messages: [
128+
{
129+
role: 'system',
130+
content: SYSTEM_PROMPTS.query_rewrite,
131+
},
132+
{
133+
role: 'user',
134+
content: `Conversation:\n${conversationContext}\n\nRewritten Query:`,
135+
},
136+
],
137+
})
138+
139+
const rewrittenQuery = response.message.content.trim()
140+
logger.info(`[RAG] Query rewritten: "${rewrittenQuery}"`)
141+
return rewrittenQuery
142+
} catch (error) {
143+
logger.error(
144+
`[RAG] Query rewriting failed: ${error instanceof Error ? error.message : error}`
145+
)
146+
// Fallback to last user message if rewriting fails
147+
const lastUserMessage = [...messages].reverse().find(msg => msg.role === 'user')
148+
return lastUserMessage?.content || null
149+
}
150+
}
94151
}

admin/app/services/chat_service.ts

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,13 @@ import logger from '@adonisjs/core/services/logger'
44
import { DateTime } from 'luxon'
55
import { inject } from '@adonisjs/core'
66
import { OllamaService } from './ollama_service.js'
7-
import { ChatRequest } from 'ollama'
87
import { SYSTEM_PROMPTS } from '../../constants/ollama.js'
98
import { toTitleCase } from '../utils/misc.js'
109

1110
@inject()
1211
export class ChatService {
1312
constructor(private ollamaService: OllamaService) {}
1413

15-
async chat(chatRequest: ChatRequest & { stream?: false }) {
16-
try {
17-
return await this.ollamaService.chat(chatRequest)
18-
} catch (error) {
19-
logger.error(`[ChatService] Chat error: ${error instanceof Error ? error.message : error}`)
20-
throw new Error('Chat processing failed')
21-
}
22-
}
23-
2414
async getAllSessions() {
2515
try {
2616
const sessions = await ChatSession.query().orderBy('updated_at', 'desc')
@@ -230,9 +220,6 @@ export class ChatService {
230220
}
231221
}
232222

233-
/**
234-
* Delete all chat sessions and messages
235-
*/
236223
async deleteAllSessions() {
237224
try {
238225
await ChatSession.query().delete()

admin/constants/ollama.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,5 +107,35 @@ The suggestions should be in title case.
107107
Ensure that your suggestions are comma-seperated with no conjunctions like "and" or "or".
108108
Do not use line breaks, new lines, or extra spacing to separate the suggestions.
109109
Format: suggestion1, suggestion2, suggestion3
110+
`,
111+
query_rewrite: `
112+
You are a query rewriting assistant. Your task is to reformulate the user's latest question to include relevant context from the conversation history.
113+
114+
Given the conversation history, rewrite the user's latest question to be a standalone, context-aware search query that will retrieve the most relevant information.
115+
116+
Rules:
117+
1. Keep the rewritten query concise (under 150 words)
118+
2. Include key entities, topics, and context from previous messages
119+
3. Make it a clear, searchable query
120+
4. Do NOT answer the question - only rewrite the user's query to be more effective for retrieval
121+
5. Output ONLY the rewritten query, nothing else
122+
123+
Examples:
124+
125+
Conversation:
126+
User: "How do I install Gentoo?"
127+
Assistant: [detailed installation guide]
128+
User: "Is an internet connection required to install?"
129+
130+
Rewritten Query: "Is an internet connection required to install Gentoo Linux?"
131+
132+
---
133+
134+
Conversation:
135+
User: "What's the best way to preserve meat?"
136+
Assistant: [preservation methods]
137+
User: "How long does it last?"
138+
139+
Rewritten Query: "How long does preserved meat last using curing or smoking methods?"
110140
`,
111141
}

0 commit comments

Comments
 (0)