Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-hover-card": "^1.1.15",
"@radix-ui/react-slot": "^1.2.4",
"@radix-ui/react-tabs": "^1.1.21",
"@radix-ui/react-tooltip": "^1.2.8",
"@shikijs/rehype": "^4.0.0",
"@shikijs/transformers": "^4.0.0",
Expand Down
12 changes: 11 additions & 1 deletion apps/docs/src/app/api/chat/route.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { type CoreMessage } from 'ai'
import { toUIMessageStream } from '@ai-sdk/langchain'
import { chatModel } from '@/lib/ai/providers'
import { MissingApiKeyError, getChatModel } from '@/lib/ai/providers'
import { provideLinks } from '@/lib/ai/tools/provide-links'
import { searchDocs } from '@/lib/ai/tools/search-docs'
import { getPageContent } from '@/lib/ai/tools/get-page-content'
Expand Down Expand Up @@ -49,6 +49,16 @@ function stringifyContent(content: CoreMessage['content']): string {
}

export async function POST(request: Request) {
let chatModel: ReturnType<typeof getChatModel>
try {
chatModel = getChatModel()
} catch (error) {
if (error instanceof MissingApiKeyError) {
return new Response(error.message, { status: 503 })
}
throw error
}

const { messages }: { messages: CoreMessage[] } = await request.json()

const tools = [provideLinks, searchDocs, getPageContent]
Expand Down
48 changes: 40 additions & 8 deletions apps/docs/src/lib/ai/providers.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,43 @@
import { ChatGroq } from '@langchain/groq'
import { OpenAIEmbeddings } from '@langchain/openai'

export const chatModel = new ChatGroq({
model: 'llama-3.3-70b-versatile',
temperature: 0,
})

export const embeddingModel = new OpenAIEmbeddings({
model: 'text-embedding-3-small',
})
/**
* Providers are created lazily, on first use.
*
* `new ChatGroq()` / `new OpenAIEmbeddings()` throw when their API key is
* missing, and Next.js evaluates every route module while collecting page data
* during `next build`. Instantiating at module scope therefore fails the build
* on any machine without the keys (CI, Vercel preview builds). Constructing on
* demand keeps the build key-free and moves the failure to the request that
* actually needs the model.
*/

let cachedChatModel: ChatGroq | undefined
let cachedEmbeddingModel: OpenAIEmbeddings | undefined

export class MissingApiKeyError extends Error {
constructor(envVar: string) {
super(
`${envVar} is not set. Add it to your environment to enable the docs assistant.`
)
this.name = 'MissingApiKeyError'
}
}

export function getChatModel(): ChatGroq {
if (!process.env.GROQ_API_KEY) throw new MissingApiKeyError('GROQ_API_KEY')
cachedChatModel ??= new ChatGroq({
model: 'llama-3.3-70b-versatile',
temperature: 0,
})
return cachedChatModel
}

export function getEmbeddingModel(): OpenAIEmbeddings {
if (!process.env.OPENAI_API_KEY)
throw new MissingApiKeyError('OPENAI_API_KEY')
cachedEmbeddingModel ??= new OpenAIEmbeddings({
model: 'text-embedding-3-small',
})
return cachedEmbeddingModel
}
1 change: 1 addition & 0 deletions turbo.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"tasks": {
"build": {
"dependsOn": ["^build"],
"env": ["GROQ_API_KEY", "OPENAI_API_KEY"],
"outputs": ["dist/**", ".next/**", "!.next/cache/**", "build/**"]
},
"dev": {
Expand Down
Loading