Api

Chat API Reference

Last updated by
Harlan Wilton
in chore: sync.

Endpoint

POST /api/chat

AI SDK compatible chat endpoint with RAG (retrieval-augmented generation) and streaming responses.

Request

Request Body

interface ChatRequest {
  messages: Array<{
    role: 'user' | 'assistant' | 'system'
    content: string
  }>
}

Example

curl -X POST "https://example.com/api/chat" \
  -H "Content-Type: application/json" \
  -d '{
    "messages": [
      {"role": "user", "content": "How do I build a Nuxt module?"}
    ]
  }'

Response

Success Response (200)

Streaming AI SDK UI message stream format.

Headers:

  • Content-Type: text/event-stream; charset=utf-8
  • Cache-Control: no-cache
  • Connection: keep-alive

Stream Format:

0:"Hello"
0:" there"
0:"!"

The response is compatible with AI SDK's useChat() composable.

Hook System

ai-search:chat:messages

Transform messages before processing.

export default defineNitroPlugin((nitro) => {
  nitro.hooks.hook('ai-search:chat:messages', async (ctx, result) => {
    // Filter or transform messages
    result.messages = result.messages.filter(m => m.role !== 'system')
  })
})

Context:

interface ChatMessagesContext {
  messages: Message[]
  event: H3Event
  model: LanguageModel
  config: RuntimeConfig
}

ai-search:chat:context

Modify search results used as RAG context.

export default defineNitroPlugin((nitro) => {
  nitro.hooks.hook('ai-search:chat:context', async (ctx, result) => {
    // Modify query or results for context
    result.results = result.results.filter(r => r.score > 0.3)
  })
})

Context:

interface ChatContextContext {
  query: string
  results: SearchResult[]
  history: Message[]
  event: H3Event
  config: RuntimeConfig
}

RAG Behavior

High Relevance (score >= 0.4)

Uses filtered search results (score > 0.2) as context:

Prompt: "Answer from context only, cite sources"
Context: URLs, scores, and markdown content

Low Relevance (score < 0.4)

Falls back to /llms.txt full context:

Prompt: "Provide overview of available topics"
Context: Complete llms.txt content

Configuration

nuxt.config.ts
export default defineNuxtConfig({
  aiSearch: {
    chat: {
      enabled: true,
      route: '/api/chat'
    },
    llm: {
      provider: 'openai',
      model: 'gpt-4o-mini',
      apiKey: process.env.OPENAI_API_KEY
    }
  }
})

Examples

Basic Chat

curl -X POST "https://example.com/api/chat" \
  -H "Content-Type: application/json" \
  -d '{
    "messages": [
      {"role": "user", "content": "What is Nuxt?"}
    ]
  }'

Conversational Context

curl -X POST "https://example.com/api/chat" \
  -H "Content-Type": "application/json" \
  -d '{
    "messages": [
      {"role": "user", "content": "What is Nuxt?"},
      {"role": "assistant", "content": "Nuxt is a Vue.js framework..."},
      {"role": "user", "content": "How do I create a module?"}
    ]
  }'

JavaScript/TypeScript

// Fetch API with streaming
const response = await fetch('/api/chat', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    messages: [
      { role: 'user', content: 'How do I build a Nuxt module?' }
    ]
  })
})

const reader = response.body.getReader()
const decoder = new TextDecoder()

while (true) {
  const { done, value } = await reader.read()
  if (done) break
  console.log(decoder.decode(value))
}

AI SDK useChat

<script setup lang="ts">
import { useChat } from 'ai/vue'

const { messages, input, handleSubmit } = useChat({
  api: '/api/chat'
})
</script>

<template>
  <div>
    <div v-for="m in messages" :key="m.id">
      <strong>{{ m.role }}:</strong> {{ m.content }}
    </div>
    <form @submit="handleSubmit">
      <input v-model="input" placeholder="Ask a question..." />
      <button type="submit">Send</button>
    </form>
  </div>
</template>

Performance

Typical Response Times

  • Query extraction: <10ms
  • Vector search: <200ms
  • LLM first token: ~1-3 seconds
  • Total streaming: ~2-5 seconds

Optimization

  • Search results cached on vector DB layer
  • Streaming reduces time-to-first-byte
  • Low relevance fallback avoids irrelevant context

Error Responses

400 Bad Request

Invalid Messages:

{
  "statusCode": 400,
  "message": "Messages array is required"
}

No User Messages:

{
  "statusCode": 400,
  "message": "At least one user message is required"
}

500 Internal Server Error

LLM Not Configured:

{
  "statusCode": 500,
  "message": "LLM not configured for chat endpoint"
}

Index Not Found:

{
  "statusCode": 500,
  "message": "Vector database not found"
}

CORS

To allow cross-origin requests:

nuxt.config.ts
export default defineNuxtConfig({
  routeRules: {
    '/api/chat': {
      cors: true,
      headers: {
        'Access-Control-Allow-Origin': '*',
        'Access-Control-Allow-Methods': 'POST, OPTIONS',
        'Access-Control-Allow-Headers': 'Content-Type'
      }
    }
  }
})

TypeScript Types

import type { Message } from 'ai'

interface ChatRequest {
  messages: Message[]
}

const request: ChatRequest = {
  messages: [
    { role: 'user', content: 'How do I build a Nuxt module?' }
  ]
}

const response = await $fetch('/api/chat', {
  method: 'POST',
  body: request
})

Next Steps

Did this page help you?