1
0
Fork 0
docsearch/packages/website/docs/v4/askai-api.mdx
Vasco Bettencourt d8ad7f6d5f
feat: curate AskAI search parameters (#2768)
* feat: curate AskAI search parameters

* fix

* fix docs

* fix

* fix: remove attributesToSnippet

* fix

---------

Co-authored-by: Dylan Tientcheu <dylan.tientcheu@algolia.com>
2025-09-24 19:40:08 +01:00

786 lines
20 KiB
Text

---
title: Ask AI API Reference
---
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
The Ask AI API enables developers to build custom chat interfaces powered by Algolia's AI assistant. Use these endpoints to create tailored conversational experiences that search your Algolia index and generate contextual responses using your own LLM provider.
**Key capabilities:**
- Real-time streaming responses for better user experience
- Advanced facet filtering to control AI context
- Authorization token authentication for secure API access
- Full compatibility with popular frameworks like Next.js and Vercel AI SDK
:::info This API documentation is primarily for developers building custom Ask AI integrations. If you're using the DocSearch package, you typically won't need this information since DocSearch handles the Ask AI API integration automatically. For standard DocSearch usage, see the [DocSearch documentation](/docs/docsearch) instead. :::
## Overview
The Algolia Ask AI API provides endpoints for integrating with an Algolia Ask AI assistant. You can use this API to build custom chat interfaces and integrate Algolia with your LLM.
**Base URL:** `https://askai.algolia.com`
All endpoints allow cross-origin requests (CORS) from browser-based apps.
---
## Authentication
Ask AI uses authorization tokens for authentication. Tokens expire after 5 minutes, so you'll need to request a new one before each chat request.
### Get a Token
**POST** `/chat/token`
**Headers:**
- `X-Algolia-Assistant-Id`: Your Ask AI assistant configuration ID
- `Origin` (optional): Request origin for CORS validation
- `Referer` (optional): Full URL of the requesting page
**Response:**
```json
{
"success": true,
"token": "TOKEN"
}
```
---
## Endpoints
### Chat with Ask AI
**POST** `/chat`
Start or continue a chat with the AI assistant. **The response is streamed in real-time** using [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events), allowing you to display the AI's response as it's being generated.
**Headers:**
- `X-Algolia-Application-Id`: Your Algolia application ID
- `X-Algolia-API-Key`: Your Algolia API key
- `X-Algolia-Index-Name`: Algolia index to use
- `X-Algolia-Assistant-Id`: Ask AI assistant configuration ID
- `Authorization`: TOKEN `token_here` (get from `/chat/token`)
**Request Body:**
```json
{
"id": "your-conversation-id",
"messages": [
{
"role": "user",
"content": "What is Algolia?",
"id": "msg-123",
"createdAt": "2025-01-01T12:00:00.000Z",
"parts": [
{
"type": "text",
"text": "What is Algolia?"
}
]
}
],
"searchParameters": {
"facetFilters": ["language:en", "version:latest"]
}
}
```
**Request Body Parameters:**
- `id` (string, required): Unique conversation identifier
- `messages` (array, required): Array of conversation messages
- `role` (string): "user" or "assistant"
- `content` (string): Message content
- `id` (string): Unique message ID
- `createdAt` (string, optional): ISO timestamp
- `parts` (array, optional): Message parts (used by Vercel AI SDK)
- `searchParameters` (object, optional): Search configuration
- `facetFilters` (array, optional): Filter by language, version, type
- `filters` (string, optional): Apply complex filtering rules
- `attributesToRetrieve` (array, optional): Control which attributes are retrieved
- `restrictSearchableAttributes` (array, optional): Limit search to specific fields
- `distinct` (boolean, optional): Remove duplicate results
**Using Search Parameters:**
Search parameters allow you to control how Ask AI searches your index:
```json
{
"id": "conversation-1",
"messages": [
{
"role": "user",
"content": "How do I configure the API?",
"id": "msg-1"
}
],
"searchParameters": {
"facetFilters": ["language:en", "version:latest"],
"filters": "type:content AND language:en",
"attributesToRetrieve": ["title", "content", "url"],
"restrictSearchableAttributes": ["title", "content"],
"distinct": true
}
}
```
**Advanced Facet Filtering with OR Logic:**
You can use nested arrays for OR logic within facet filters:
```json
{
"searchParameters": {
"facetFilters": [
"language:en",
["docusaurus_tag:default", "docusaurus_tag:docs-default-current"]
]
}
}
```
This example filters to:
- `language:en` **AND**
- (`docusaurus_tag:default` **OR** `docusaurus_tag:docs-default-current`)
**Common Use Cases:**
- **Multi-language sites**: `["language:en"]`
- **Versioned documentation**: `["version:latest"]` or `["version:v2.0"]`
- **Content types**: `["type:content"]` to exclude navigation/metadata
- **Multiple tags**: `[["tag:api", "tag:tutorial"]]` for OR logic
- **Categories with fallbacks**: `[["category:advanced", "category:intermediate"]]`
**Response:**
- **Content-Type:** `text/event-stream`
- **Format:** Server-sent events with incremental AI response chunks
- **Benefits:** Real-time response display, better user experience, lower perceived latency
**Handling Streaming Responses:**
```js
const response = await fetch('/chat', {
/* ... */
});
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value);
// Display chunk immediately in your UI
console.log('Received chunk:', chunk);
}
```
### Submit Feedback
**POST** `/chat/feedback`
Submit thumbs up/down feedback for a chat message.
**Headers:**
- `X-Algolia-Assistant-Id`: Your Ask AI assistant configuration ID
- `Authorization`: TOKEN `token_here`
**Request Body:**
```json
{
"appId": "YOUR_APP_ID",
"messageId": "msg-123",
"thumbs": 1
}
```
- `thumbs`: 1 for positive feedback, 0 for negative
**Response:**
```json
{
"success": true,
"message": "Feedback was successfully submitted."
}
```
### Health Check
**GET** `/chat/health`
Check the operational status of the Ask AI service.
**Response:** `OK` (text/plain)
---
## Custom Integration Examples
### Basic Chat Implementation
<Tabs groupId="language" defaultValue="js" values={[{ label: 'JavaScript', value: 'js' }, { label: 'React', value: 'react' }]}>
<TabItem value="js">
```js
class AskAIChat {
constructor({ appId, apiKey, indexName, assistantId }) {
this.appId = appId;
this.apiKey = apiKey;
this.indexName = indexName;
this.assistantId = assistantId;
this.baseUrl = 'https://askai.algolia.com';
}
async getToken() {
const response = await fetch(`${this.baseUrl}/chat/token`, {
method: 'POST',
headers: {
'X-Algolia-Assistant-Id': this.assistantId,
},
});
const data = await response.json();
return data.token;
}
async sendMessage(conversationId, messages, searchParameters = {}) {
const token = await this.getToken();
const response = await fetch(`${this.baseUrl}/chat`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Algolia-Application-Id': this.appId,
'X-Algolia-API-Key': this.apiKey,
'X-Algolia-Index-Name': this.indexName,
'X-Algolia-Assistant-Id': this.assistantId,
Authorization: `TOKEN ${token}`,
},
body: JSON.stringify({
id: conversationId,
messages,
...(Object.keys(searchParameters).length > 0 && { searchParameters }),
}),
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
// Return a streaming iterator for real-time response handling
return {
async *[Symbol.asyncIterator]() {
const reader = response.body.getReader();
const decoder = new TextDecoder();
try {
while (true) {
const { done, value } = await reader.read();
if (done) break;
// Decode and yield each chunk as it arrives
const chunk = decoder.decode(value, { stream: true });
if (chunk.trim()) {
yield chunk;
}
}
} finally {
reader.releaseLock();
}
},
};
}
async submitFeedback(messageId, thumbs) {
const token = await this.getToken();
const response = await fetch(`${this.baseUrl}/chat/feedback`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Algolia-Assistant-Id': this.assistantId,
Authorization: `TOKEN ${token}`,
},
body: JSON.stringify({
appId: this.appId,
messageId,
thumbs,
}),
});
return response.json();
}
}
// Usage with streaming
const chat = new AskAIChat({
appId: 'YOUR_APP_ID',
apiKey: 'YOUR_API_KEY',
indexName: 'YOUR_INDEX_NAME',
assistantId: 'YOUR_ASSISTANT_ID',
});
// Send message and handle streaming response
const stream = await chat.sendMessage(
'conversation-1',
[
{
role: 'user',
content: 'What is Algolia?',
id: 'msg-1',
},
],
{
facetFilters: ['language:en', 'type:content'],
}
); // Add search parameters
// Display response as it streams in real-time
let fullResponse = '';
for await (const chunk of stream) {
fullResponse += chunk;
console.log('Received chunk:', chunk);
// Update your UI immediately with each chunk
// e.g., appendToMessageUI(chunk);
}
console.log('Complete response:', fullResponse);
```
</TabItem>
<TabItem value="react">
```jsx
import { useState, useCallback, useRef } from 'react';
function AskAIChat({ appId, apiKey, indexName, assistantId }) {
const [messages, setMessages] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const [isStreaming, setIsStreaming] = useState(false);
const abortControllerRef = useRef(null);
const baseUrl = 'https://askai.algolia.com';
const getToken = useCallback(async () => {
const response = await fetch(`${baseUrl}/chat/token`, {
method: 'POST',
headers: {
'X-Algolia-Assistant-Id': assistantId,
},
});
const data = await response.json();
return data.token;
}, [assistantId]);
const sendMessage = useCallback(
async (content) => {
const newMessage = {
role: 'user',
content,
id: `msg-${Date.now()}`,
};
setMessages((prev) => [...prev, newMessage]);
setIsLoading(true);
setIsStreaming(true);
// Create abort controller for cancellation
abortControllerRef.current = new AbortController();
try {
const token = await getToken();
const response = await fetch(`${baseUrl}/chat`, {
method: 'POST',
signal: abortControllerRef.current.signal,
headers: {
'Content-Type': 'application/json',
'X-Algolia-Application-Id': appId,
'X-Algolia-API-Key': apiKey,
'X-Algolia-Index-Name': indexName,
'X-Algolia-Assistant-Id': assistantId,
Authorization: `TOKEN ${token}`,
},
body: JSON.stringify({
id: 'conversation-1',
messages: [...messages, newMessage],
searchParameters: {
facetFilters: ['language:en', 'type:content'],
},
}),
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
// Handle streaming response in real-time
const reader = response.body.getReader();
const decoder = new TextDecoder();
let assistantMessage = '';
let assistantMessageId = `assistant-${Date.now()}`;
// Add initial empty assistant message
setMessages((prev) => [
...prev,
{
role: 'assistant',
content: '',
id: assistantMessageId,
},
]);
while (true) {
const { done, value } = await reader.read();
if (done) break;
// Decode chunk and add to message
const chunk = decoder.decode(value, { stream: true });
if (chunk.trim()) {
assistantMessage += chunk;
// Update UI with streaming content immediately
setMessages((prev) =>
prev.map((msg) =>
msg.id === assistantMessageId
? { ...msg, content: assistantMessage }
: msg
)
);
}
}
} catch (error) {
if (error.name !== 'AbortError') {
console.error('Streaming error:', error);
// Add error message to chat
setMessages((prev) => [
...prev,
{
role: 'assistant',
content: 'Sorry, there was an error processing your request.',
id: `error-${Date.now()}`,
},
]);
}
} finally {
setIsLoading(false);
setIsStreaming(false);
abortControllerRef.current = null;
}
},
[messages, appId, apiKey, indexName, assistantId, getToken]
);
const cancelStream = useCallback(() => {
if (abortControllerRef.current) {
abortControllerRef.current.abort();
}
}, []);
return (
<div className="chat-container">
<div className="messages">
{messages.map((message) => (
<div key={message.id} className={`message ${message.role}`}>
<strong>{message.role}:</strong>
<span
className={
isStreaming &&
message.role === 'assistant' &&
message === messages[messages.length - 1]
? 'streaming'
: ''
}
>
{message.content}
{isStreaming &&
message.role === 'assistant' &&
message === messages[messages.length - 1] && (
<span className="cursor">▊</span>
)}
</span>
</div>
))}
</div>
<form
onSubmit={(e) => {
e.preventDefault();
if (isLoading) return;
const input = e.target.elements.message;
if (input.value.trim()) {
sendMessage(input.value);
input.value = '';
}
}}
>
<input
name="message"
type="text"
placeholder="Ask a question..."
disabled={isLoading}
/>
<button type="submit" disabled={isLoading}>
{isLoading ? 'Sending...' : 'Send'}
</button>
{isStreaming && (
<button type="button" onClick={cancelStream}>
Cancel
</button>
)}
</form>
<style jsx>{`
.streaming {
animation: pulse 1.5s ease-in-out infinite;
}
.cursor {
animation: blink 1s infinite;
}
@keyframes pulse {
0%,
100% {
opacity: 1;
}
50% {
opacity: 0.7;
}
}
@keyframes blink {
0%,
50% {
opacity: 1;
}
51%,
100% {
opacity: 0;
}
}
`}</style>
</div>
);
}
export default AskAIChat;
```
</TabItem>
</Tabs>
### With Vercel AI SDK
The Vercel AI SDK (v4) provides automatic handling of the request format and streaming:
#### Option 1: Direct Integration
```js
import { useChat } from 'ai/react';
function ChatComponent() {
const { messages, input, handleInputChange, handleSubmit } = useChat({
api: 'https://askai.algolia.com/chat',
headers: {
'X-Algolia-Application-Id': 'YOUR_APP_ID',
'X-Algolia-API-Key': 'YOUR_API_KEY',
'X-Algolia-Index-Name': 'YOUR_INDEX_NAME',
'X-Algolia-Assistant-Id': 'YOUR_ASSISTANT_ID',
},
});
return (
<div>
{messages.map((m) => (
<div key={m.id}>
{m.role === 'user' ? 'User: ' : 'AI: '}
{m.content}
</div>
))}
<form onSubmit={handleSubmit}>
<input
value={input}
placeholder="Say something..."
onChange={handleInputChange}
/>
</form>
</div>
);
}
```
#### Option 2: Using a Next.js API Proxy (Recommended)
For better security and token management, create a Next.js API route as a proxy:
**pages/api/chat.ts** (or **app/api/chat/route.ts** for App Router):
```ts
import { StreamingTextResponse } from 'ai';
export const runtime = 'edge';
async function getToken(assistantId: string, origin: string) {
const tokenRes = await fetch('https://askai.algolia.com/chat/token', {
method: 'POST',
headers: {
'X-Algolia-Assistant-Id': assistantId,
Origin: origin,
},
});
const tokenData = await tokenRes.json();
if (!tokenData.success) {
throw new Error(tokenData.message || 'Failed to get token');
}
return tokenData.token;
}
export default async function handler(req: Request) {
try {
const body = await req.json();
const assistantId = process.env.ALGOLIA_ASSISTANT_ID!;
const origin = req.headers.get('origin') || '';
// Fetch a new token before each chat call
const token = await getToken(assistantId, origin);
// Prepare headers for Algolia Ask AI
const headers = {
'X-Algolia-Application-Id': process.env.ALGOLIA_APP_ID!,
'X-Algolia-API-Key': process.env.ALGOLIA_API_KEY!,
'X-Algolia-Index-Name': process.env.ALGOLIA_INDEX_NAME!,
'X-Algolia-Assistant-Id': assistantId,
Authorization: `TOKEN ${token}`,
'Content-Type': 'application/json',
};
// Forward the request to Algolia Ask AI
const response = await fetch('https://askai.algolia.com/chat', {
method: 'POST',
headers,
body: JSON.stringify(body),
});
if (!response.ok) {
throw new Error(`Ask AI API error: ${response.status}`);
}
// Stream the response back to the client
return new StreamingTextResponse(response.body);
} catch (error) {
console.error('Chat API error:', error);
return new Response(JSON.stringify({ error: 'Internal server error' }), {
status: 500,
headers: { 'Content-Type': 'application/json' },
});
}
}
```
**Environment Variables (.env.local):**
```env
ALGOLIA_APP_ID=your_app_id
ALGOLIA_API_KEY=your_api_key
ALGOLIA_INDEX_NAME=your_index_name
ALGOLIA_ASSISTANT_ID=your_assistant_id
```
**Frontend with useChat:**
```jsx
import { useChat } from 'ai/react';
function ChatComponent() {
const { messages, input, handleInputChange, handleSubmit, isLoading } =
useChat({
api: '/api/chat', // Use your Next.js API route
body: {
searchParameters: {
facetFilters: ['language:en', 'type:content'],
},
},
});
return (
<div className="chat-container">
<div className="messages">
{messages.map((m) => (
<div key={m.id} className={`message ${m.role}`}>
<strong>{m.role === 'user' ? 'You' : 'AI'}:</strong>
<div>{m.content}</div>
</div>
))}
{isLoading && <div className="loading">AI is thinking...</div>}
</div>
<form onSubmit={handleSubmit}>
<input
value={input}
placeholder="Ask a question..."
onChange={handleInputChange}
disabled={isLoading}
/>
<button type="submit" disabled={isLoading}>
{isLoading ? 'Sending...' : 'Send'}
</button>
</form>
</div>
);
}
```
**Benefits of the proxy approach:**
- **Security**: API keys stay on the server
- **Token management**: Automatic token refresh
- **Error handling**: Centralized error management
- **CORS**: No cross-origin issues
- **Caching**: Can add caching logic if needed
---
## Error Handling
All error responses follow this format:
```json
{
"success": false,
"message": "Error description"
}
```
Common error scenarios:
- **Invalid assistant ID**: Configuration doesn't exist
- **Expired token**: Request a new authorization token
- **Rate limiting**: Too many requests
- **Invalid index**: Index name doesn't exist or isn't accessible
> Please view our full [Error Reference](/docs/v4/askai-errors.mdx) for more detailed information
---
## Best Practices
1. **Token Management**: Always request a fresh token before chat requests
2. **Error Handling**: Implement retry logic for network failures
3. **Streaming**: Handle server-sent events properly for real-time responses
4. **Feedback**: Implement thumbs up/down for continuous improvement
5. **CORS**: Ensure your domain is whitelisted in your Ask AI configuration
For more information, see the [Ask AI documentation](/docs/v4/askai.mdx).