Skip to content

Commit 2b0ad1f

Browse files
committed
try to improve ai chat search
1 parent 7b0715e commit 2b0ad1f

2 files changed

Lines changed: 39 additions & 28 deletions

File tree

app/api/chat/route.ts

Lines changed: 38 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { createOpenRouter } from '@openrouter/ai-sdk-provider';
2-
import { convertToModelMessages, stepCountIs, streamText, tool, type UIMessage } from 'ai';
2+
import { convertToModelMessages, streamText, tool, type UIMessage } from 'ai';
33
import { z } from 'zod';
44
import { source } from '@/lib/source';
55
import { Document, type DocumentData } from 'flexsearch';
@@ -56,61 +56,72 @@ async function chunkedAll<O>(promises: Promise<O>[]): Promise<O[]> {
5656
return out;
5757
}
5858

59+
async function runSearch(query: string, limit = 8): Promise<CustomDocument[]> {
60+
const search = await searchServer;
61+
const results = await search.searchAsync(query, { limit, merge: true, enrich: true });
62+
return (results as any[])
63+
.flatMap((r) => r.result ?? [])
64+
.map((d) => ({
65+
...d.doc,
66+
content: d.doc?.content?.slice(0, 1200) ?? '',
67+
}))
68+
.filter((d) => d.url);
69+
}
70+
5971
const openrouter = createOpenRouter({
6072
apiKey: process.env.OPENROUTER_API_KEY,
6173
});
6274

6375
const systemPrompt = [
6476
'You are a helpful technical assistant for Next Commerce developer documentation.',
65-
'Your job is to answer developer questions clearly and concisely based on the documentation.',
66-
'Always use the `search` tool first to find relevant documentation before answering.',
67-
'Base your answers on the search results. Cite sources as markdown links using the document `url` field.',
77+
'Your job is to answer developer questions clearly and concisely based on the documentation provided in context.',
78+
'Base your answers on the documentation context below. Cite sources as markdown links using the document URL.',
6879
'When writing code examples, use the language shown in the documentation.',
69-
'If the search results do not contain enough information to answer the question, say so honestly.',
80+
'If the documentation context does not contain enough information to answer the question, say so honestly.',
7081
'Do not make up API endpoints, parameters, or behaviours that are not in the documentation.',
7182
'Keep answers focused and practical. Avoid lengthy preambles — get straight to the answer.',
7283
].join('\n');
7384

7485
export async function POST(req: Request) {
7586
const reqJson: { messages?: UIMessage[] } = await req.json();
87+
const messages = reqJson.messages ?? [];
88+
89+
// Extract latest user question and search server-side — don't rely on model to call tools
90+
const lastUserText = messages
91+
.filter((m) => m.role === 'user')
92+
.at(-1)
93+
?.parts?.filter((p: any) => p.type === 'text')
94+
.map((p: any) => p.text as string)
95+
.join(' ') ?? '';
96+
97+
const docs = lastUserText ? await runSearch(lastUserText) : [];
98+
99+
const contextBlock =
100+
docs.length > 0
101+
? 'Relevant documentation:\n\n' +
102+
docs.map((d) => `### [${d.title}](${d.url})\n${d.description ? d.description + '\n' : ''}${d.content}`).join('\n\n---\n\n')
103+
: 'No relevant documentation found.';
76104

77105
const result = streamText({
78106
model: openrouter.chat(process.env.OPENROUTER_MODEL ?? 'anthropic/claude-3.5-sonnet'),
79-
stopWhen: stepCountIs(5),
80-
tools: {
81-
search: searchTool,
82-
},
83107
messages: [
84-
{ role: 'system', content: systemPrompt },
85-
...(await convertToModelMessages(reqJson.messages ?? [])),
108+
{ role: 'system', content: `${systemPrompt}\n\n${contextBlock}` },
109+
...(await convertToModelMessages(messages)),
86110
],
87-
prepareStep: ({ stepNumber }) => ({
88-
toolChoice: stepNumber === 0 ? 'required' : 'auto',
89-
}),
90111
});
91112

92113
return result.toUIMessageStreamResponse();
93114
}
94115

116+
// Keep tool definition so the UI type import still works
95117
const searchTool = tool({
96118
description: 'Search the docs content and return raw JSON results.',
97119
inputSchema: z.object({
98120
query: z.string(),
99-
limit: z.number().int().min(1).max(20).default(5),
121+
limit: z.number().int().min(1).max(20).default(8),
100122
}),
101123
async execute({ query, limit }) {
102-
const search = await searchServer;
103-
const results = await search.searchAsync(query, { limit, merge: true, enrich: true });
104-
// Truncate content to avoid flooding the context window
105-
return results.map((r: any) => ({
106-
...r,
107-
result: r.result?.map((doc: any) => ({
108-
...doc,
109-
doc: doc.doc
110-
? { ...doc.doc, content: doc.doc.content?.slice(0, 1500) }
111-
: doc.doc,
112-
})),
113-
}));
124+
return runSearch(query, limit);
114125
},
115126
});
116127

tsconfig.tsbuildinfo

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)