Skip to content

Commit 70a5100

Browse files
committed
Better formatting
1 parent 17513d7 commit 70a5100

File tree

5 files changed

+370
-302
lines changed

5 files changed

+370
-302
lines changed

apps/sim/app/api/docs/ask/route.ts

Lines changed: 83 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
1-
import { NextRequest, NextResponse } from 'next/server'
1+
import { sql } from 'drizzle-orm'
2+
import { type NextRequest, NextResponse } from 'next/server'
23
import { z } from 'zod'
4+
import { env } from '@/lib/env'
5+
import { createLogger } from '@/lib/logs/console-logger'
6+
import { getRotatingApiKey } from '@/lib/utils'
7+
import { generateEmbeddings } from '@/app/api/knowledge/utils'
38
import { db } from '@/db'
49
import { docsEmbeddings } from '@/db/schema'
5-
import { generateEmbeddings } from '@/app/api/knowledge/utils'
6-
import { createLogger } from '@/lib/logs/console-logger'
7-
import { sql } from 'drizzle-orm'
8-
import { env } from '@/lib/env'
910
import { executeProviderRequest } from '@/providers'
1011
import { getProviderDefaultModel } from '@/providers/models'
11-
import { getRotatingApiKey } from '@/lib/utils'
1212

1313
const logger = createLogger('DocsRAG')
1414

@@ -74,10 +74,17 @@ async function searchDocs(queryEmbedding: number[], topK: number) {
7474
/**
7575
* Generate response using LLM with retrieved context
7676
*/
77-
async function generateResponse(query: string, chunks: any[], provider?: string, model?: string, stream: boolean = false): Promise<string | ReadableStream> {
77+
async function generateResponse(
78+
query: string,
79+
chunks: any[],
80+
provider?: string,
81+
model?: string,
82+
stream = false
83+
): Promise<string | ReadableStream> {
7884
// Determine which provider and model to use
7985
const selectedProvider = provider || DOCS_RAG_CONFIG.defaultProvider
80-
const selectedModel = model || DOCS_RAG_CONFIG.defaultModel || getProviderDefaultModel(selectedProvider)
86+
const selectedModel =
87+
model || DOCS_RAG_CONFIG.defaultModel || getProviderDefaultModel(selectedProvider)
8188

8289
// Get API key for the selected provider
8390
let apiKey: string
@@ -103,11 +110,19 @@ async function generateResponse(query: string, chunks: any[], provider?: string,
103110
const context = chunks
104111
.map((chunk, index) => {
105112
// Ensure all chunk properties are strings to avoid object serialization
106-
const headerText = typeof chunk.headerText === 'string' ? chunk.headerText : String(chunk.headerText || 'Untitled Section')
107-
const sourceDocument = typeof chunk.sourceDocument === 'string' ? chunk.sourceDocument : String(chunk.sourceDocument || 'Unknown Document')
108-
const sourceLink = typeof chunk.sourceLink === 'string' ? chunk.sourceLink : String(chunk.sourceLink || '#')
109-
const chunkText = typeof chunk.chunkText === 'string' ? chunk.chunkText : String(chunk.chunkText || '')
110-
113+
const headerText =
114+
typeof chunk.headerText === 'string'
115+
? chunk.headerText
116+
: String(chunk.headerText || 'Untitled Section')
117+
const sourceDocument =
118+
typeof chunk.sourceDocument === 'string'
119+
? chunk.sourceDocument
120+
: String(chunk.sourceDocument || 'Unknown Document')
121+
const sourceLink =
122+
typeof chunk.sourceLink === 'string' ? chunk.sourceLink : String(chunk.sourceLink || '#')
123+
const chunkText =
124+
typeof chunk.chunkText === 'string' ? chunk.chunkText : String(chunk.chunkText || '')
125+
111126
return `[${index + 1}] ${headerText}
112127
Document: ${sourceDocument}
113128
URL: ${sourceLink}
@@ -117,15 +132,20 @@ Content: ${chunkText}`
117132

118133
const systemPrompt = `You are a helpful assistant that answers questions about Sim Studio documentation.
119134
120-
IMPORTANT: Use inline citations throughout your response. When referencing information from the sources, include the citation number in square brackets like [1], [2], etc.
135+
IMPORTANT: Use inline citations strategically and sparingly. When referencing information from the sources, include the citation number in curly braces like {cite:1}, {cite:2}, etc.
121136
122-
Guidelines:
137+
Citation Guidelines:
138+
- Cite each source only ONCE at the specific header or topic that relates to that source
139+
- Do NOT repeatedly cite the same source throughout your response
140+
- Place citations directly after the header or concept that the source specifically addresses
141+
- If multiple sources support the same specific topic, cite them together like {cite:1}{cite:2}{cite:3}
142+
- Each citation should be placed at the relevant header/topic it supports, not grouped at the beginning
143+
- Avoid cluttering the text with excessive citations
144+
145+
Content Guidelines:
123146
- Answer the user's question accurately using the provided documentation
124-
- Include inline citations [1], [2], etc. when referencing specific information
125-
- Use multiple citations for comprehensive answers
126147
- Format your response in clean, readable markdown
127148
- Use bullet points, code blocks, and headers where appropriate
128-
- If information spans multiple sources, cite all relevant ones
129149
- If the question cannot be answered from the context, say so clearly
130150
- Be conversational but precise
131151
- NEVER include object representations like "[object Object]" - always use proper text
@@ -162,33 +182,33 @@ ${context}`
162182
if (response instanceof ReadableStream) {
163183
if (stream) {
164184
return response // Return the stream directly for streaming requests
165-
} else {
166-
throw new Error('Unexpected streaming response when non-streaming was requested')
167185
}
186+
throw new Error('Unexpected streaming response when non-streaming was requested')
168187
}
169188

170189
if ('stream' in response && 'execution' in response) {
171190
// Handle StreamingExecution for providers like Anthropic
172191
if (stream) {
173192
return response.stream // Return the stream from StreamingExecution
174-
} else {
175-
throw new Error('Unexpected streaming execution response when non-streaming was requested')
176193
}
194+
throw new Error('Unexpected streaming execution response when non-streaming was requested')
177195
}
178196

179197
// At this point, we have a ProviderResponse
180198
const content = response.content || 'Sorry, I could not generate a response.'
181-
199+
182200
// Clean up any object serialization artifacts
183201
const cleanedContent = content
184202
.replace(/\[object Object\],?/g, '') // Remove [object Object] artifacts
185203
.replace(/\s+/g, ' ') // Normalize whitespace
186204
.trim()
187-
205+
188206
return cleanedContent
189207
} catch (error) {
190208
logger.error('Failed to generate LLM response:', error)
191-
throw new Error(`Failed to generate response using ${selectedProvider}: ${error instanceof Error ? error.message : 'Unknown error'}`)
209+
throw new Error(
210+
`Failed to generate response using ${selectedProvider}: ${error instanceof Error ? error.message : 'Unknown error'}`
211+
)
192212
}
193213
}
194214

@@ -198,14 +218,17 @@ ${context}`
198218
*/
199219
export async function POST(req: NextRequest) {
200220
const requestId = crypto.randomUUID()
201-
221+
202222
try {
203223
const body = await req.json()
204224
const { query, topK, provider, model, stream } = DocsQuerySchema.parse(body)
205225

206226
logger.info(`[${requestId}] Docs RAG query: "${query}"`, {
207227
provider: provider || DOCS_RAG_CONFIG.defaultProvider,
208-
model: model || DOCS_RAG_CONFIG.defaultModel || getProviderDefaultModel(provider || DOCS_RAG_CONFIG.defaultProvider),
228+
model:
229+
model ||
230+
DOCS_RAG_CONFIG.defaultModel ||
231+
getProviderDefaultModel(provider || DOCS_RAG_CONFIG.defaultProvider),
209232
topK,
210233
})
211234

@@ -214,10 +237,7 @@ export async function POST(req: NextRequest) {
214237
const queryEmbedding = await generateSearchEmbedding(query)
215238

216239
if (queryEmbedding.length === 0) {
217-
return NextResponse.json(
218-
{ error: 'Failed to generate query embedding' },
219-
{ status: 500 }
220-
)
240+
return NextResponse.json({ error: 'Failed to generate query embedding' }, { status: 500 })
221241
}
222242

223243
// Step 2: Search for relevant docs chunks
@@ -227,14 +247,18 @@ export async function POST(req: NextRequest) {
227247
if (chunks.length === 0) {
228248
return NextResponse.json({
229249
success: true,
230-
response: "I couldn't find any relevant documentation for your question. Please try rephrasing your query or check if you're asking about a feature that exists in Sim Studio.",
250+
response:
251+
"I couldn't find any relevant documentation for your question. Please try rephrasing your query or check if you're asking about a feature that exists in Sim Studio.",
231252
sources: [],
232253
metadata: {
233254
requestId,
234255
chunksFound: 0,
235256
query,
236257
provider: provider || DOCS_RAG_CONFIG.defaultProvider,
237-
model: model || DOCS_RAG_CONFIG.defaultModel || getProviderDefaultModel(provider || DOCS_RAG_CONFIG.defaultProvider),
258+
model:
259+
model ||
260+
DOCS_RAG_CONFIG.defaultModel ||
261+
getProviderDefaultModel(provider || DOCS_RAG_CONFIG.defaultProvider),
238262
},
239263
})
240264
}
@@ -254,16 +278,16 @@ export async function POST(req: NextRequest) {
254278
// Handle streaming response
255279
if (response instanceof ReadableStream) {
256280
logger.info(`[${requestId}] Returning streaming response`)
257-
281+
258282
// Create a new stream that includes metadata
259283
const encoder = new TextEncoder()
260284
const decoder = new TextDecoder()
261-
285+
262286
return new Response(
263287
new ReadableStream({
264288
async start(controller) {
265289
const reader = response.getReader()
266-
290+
267291
// Send initial metadata
268292
const metadata = {
269293
type: 'metadata',
@@ -274,27 +298,30 @@ export async function POST(req: NextRequest) {
274298
query,
275299
topSimilarity: sources[0]?.similarity,
276300
provider: provider || DOCS_RAG_CONFIG.defaultProvider,
277-
model: model || DOCS_RAG_CONFIG.defaultModel || getProviderDefaultModel(provider || DOCS_RAG_CONFIG.defaultProvider),
301+
model:
302+
model ||
303+
DOCS_RAG_CONFIG.defaultModel ||
304+
getProviderDefaultModel(provider || DOCS_RAG_CONFIG.defaultProvider),
278305
},
279306
}
280307
controller.enqueue(encoder.encode(`data: ${JSON.stringify(metadata)}\n\n`))
281-
308+
282309
try {
283310
while (true) {
284311
const { done, value } = await reader.read()
285312
if (done) break
286-
287-
// Forward the chunk with content type
288-
const chunkText = decoder.decode(value)
289-
// Clean up any object serialization artifacts in streaming content
290-
const cleanedChunk = chunkText.replace(/\[object Object\],?/g, '')
291-
const contentChunk = {
292-
type: 'content',
293-
content: cleanedChunk,
294-
}
295-
controller.enqueue(encoder.encode(`data: ${JSON.stringify(contentChunk)}\n\n`))
313+
314+
// Forward the chunk with content type
315+
const chunkText = decoder.decode(value)
316+
// Clean up any object serialization artifacts in streaming content
317+
const cleanedChunk = chunkText.replace(/\[object Object\],?/g, '')
318+
const contentChunk = {
319+
type: 'content',
320+
content: cleanedChunk,
321+
}
322+
controller.enqueue(encoder.encode(`data: ${JSON.stringify(contentChunk)}\n\n`))
296323
}
297-
324+
298325
// Send end marker
299326
controller.enqueue(encoder.encode(`data: {"type":"done"}\n\n`))
300327
} catch (error) {
@@ -313,7 +340,7 @@ export async function POST(req: NextRequest) {
313340
headers: {
314341
'Content-Type': 'text/event-stream',
315342
'Cache-Control': 'no-cache',
316-
'Connection': 'keep-alive',
343+
Connection: 'keep-alive',
317344
},
318345
}
319346
)
@@ -331,10 +358,12 @@ export async function POST(req: NextRequest) {
331358
query,
332359
topSimilarity: sources[0]?.similarity,
333360
provider: provider || DOCS_RAG_CONFIG.defaultProvider,
334-
model: model || DOCS_RAG_CONFIG.defaultModel || getProviderDefaultModel(provider || DOCS_RAG_CONFIG.defaultProvider),
361+
model:
362+
model ||
363+
DOCS_RAG_CONFIG.defaultModel ||
364+
getProviderDefaultModel(provider || DOCS_RAG_CONFIG.defaultProvider),
335365
},
336366
})
337-
338367
} catch (error) {
339368
if (error instanceof z.ZodError) {
340369
return NextResponse.json(
@@ -344,9 +373,6 @@ export async function POST(req: NextRequest) {
344373
}
345374

346375
logger.error(`[${requestId}] RAG error:`, error)
347-
return NextResponse.json(
348-
{ error: 'Internal server error' },
349-
{ status: 500 }
350-
)
376+
return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
351377
}
352-
}
378+
}

0 commit comments

Comments
 (0)