feat(docs): api documentation and better examples (#2686)
* feat(docs): api documentation and better examples * feat(docs): api documentation and better examples
This commit is contained in:
parent
942c1ca353
commit
920e3926b7
4 changed files with 933 additions and 7 deletions
734
packages/website/docs/v4/askai-api.mdx
Normal file
734
packages/website/docs/v4/askai-api.mdx
Normal file
|
|
@ -0,0 +1,734 @@
|
|||
---
|
||||
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
|
||||
- HMAC token authentication for secure API access
|
||||
- Full compatibility with popular frameworks like Next.js and Vercel AI SDK
|
||||
|
||||
:::warning
|
||||
Ask AI is a **private beta** feature under the [Algolia Terms of Service ("Beta Services")](https://www.algolia.com/policies/terms/).
|
||||
Use of this feature is subject to [Algolia's GenAI Addendum](https://www.algolia.com/policies/algolia-genai-addendum).
|
||||
:::
|
||||
|
||||
:::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 HMAC tokens for authentication. Tokens expire after 5 minutes, so you'll need to request a new one before each chat request.
|
||||
|
||||
### Get an HMAC 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": "HMAC_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`: HMAC token (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 the context used by Ask AI
|
||||
|
||||
**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",
|
||||
"type:content"
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**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`: HMAC token
|
||||
|
||||
**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,
|
||||
},
|
||||
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,
|
||||
},
|
||||
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,
|
||||
},
|
||||
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,
|
||||
'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 HMAC token
|
||||
- **Rate limiting**: Too many requests
|
||||
- **Invalid index**: Index name doesn't exist or isn't accessible
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Token Management**: Always request a fresh HMAC 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).
|
||||
|
|
@ -81,7 +81,19 @@ The Crawler will process your content using the markdown extraction helper and p
|
|||
|
||||
## Step 3: Integrate your new index with Ask AI
|
||||
|
||||
Once your Crawler and index are configured, set up your frontend to use both your main keyword index and your markdown index for AskAI. Here’s how you might configure DocSearch to use your main keyword index for search and your markdown index for AskAI:
|
||||
Once your Crawler has created your optimized index, you can integrate it with Ask AI in two ways: using DocSearch (recommended for most users) or building a custom integration using the Ask AI API.
|
||||
|
||||
<Tabs groupId="integration-type" defaultValue="docsearch" values={[{ label: 'DocSearch Integration', value: 'docsearch' }, { label: 'Custom API Integration', value: 'custom' }]}>
|
||||
|
||||
<TabItem value="docsearch">
|
||||
|
||||
### Using DocSearch
|
||||
|
||||
Configure DocSearch to use both your main keyword index and your markdown index for Ask AI:
|
||||
|
||||
<Tabs groupId="language" defaultValue="js" values={[{ label: 'JavaScript', value: 'js' }, { label: 'React', value: 'react' }]}>
|
||||
|
||||
<TabItem value="js">
|
||||
|
||||
```js
|
||||
docsearch({
|
||||
|
|
@ -89,17 +101,192 @@ docsearch({
|
|||
apiKey: 'YOUR_SEARCH_API_KEY',
|
||||
appId: 'YOUR_APP_ID',
|
||||
askAi: {
|
||||
indexName: 'YOUR_INDEX_NAME-markdown', // Markdown index for AskAI
|
||||
indexName: 'YOUR_INDEX_NAME-markdown', // Markdown index for Ask AI
|
||||
apiKey: 'YOUR_SEARCH_API_KEY', // (or a different key if needed)
|
||||
appId: 'YOUR_APP_ID',
|
||||
assistantId: 'YOUR_ALGOLIA_ASSISTANT_ID',
|
||||
searchParameters: {
|
||||
facetFilters: ['language:en'], // Optional: filter to specific language/version
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="react">
|
||||
|
||||
```jsx
|
||||
<DocSearch
|
||||
indexName="YOUR_INDEX_NAME" // Main DocSearch keyword index
|
||||
apiKey="YOUR_SEARCH_API_KEY"
|
||||
appId="YOUR_APP_ID"
|
||||
askAi={{
|
||||
indexName: 'YOUR_INDEX_NAME-markdown', // Markdown index for Ask AI
|
||||
apiKey: 'YOUR_SEARCH_API_KEY',
|
||||
appId: 'YOUR_APP_ID',
|
||||
assistantId: 'YOUR_ALGOLIA_ASSISTANT_ID',
|
||||
searchParameters: {
|
||||
facetFilters: ['language:en'], // Optional: filter to specific language/version
|
||||
},
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
</Tabs>
|
||||
|
||||
- `indexName`: Your main DocSearch index for keyword search.
|
||||
- `askAi.indexName`: The markdown index you created for AskAI context.
|
||||
- `assistantId`: The ID of your configured AskAI assistant.
|
||||
- `askAi.indexName`: The markdown index you created for Ask AI context.
|
||||
- `assistantId`: The ID of your configured Ask AI assistant.
|
||||
- `searchParameters.facetFilters`: Optional filters to limit Ask AI context (useful for multi-language sites).
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="custom">
|
||||
|
||||
### Custom API Integration
|
||||
|
||||
:::info
|
||||
We highly recommend using the DocSearch package for most use cases. Custom implementations using the Ask AI API directly are not fully supported to the same extent as the DocSearch package, and may require additional development effort for features like error handling, authentication, and UI components.
|
||||
:::
|
||||
|
||||
Build your own chat interface using the Ask AI API. This gives you full control over the user experience and allows for advanced customizations.
|
||||
|
||||
```js
|
||||
class CustomAskAI {
|
||||
constructor({ appId, apiKey, indexName, assistantId }) {
|
||||
this.appId = appId;
|
||||
this.apiKey = apiKey;
|
||||
this.indexName = indexName; // Your markdown index
|
||||
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, // Use your markdown index
|
||||
'X-Algolia-Assistant-Id': this.assistantId,
|
||||
'Authorization': token,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
id: conversationId,
|
||||
messages,
|
||||
...(Object.keys(searchParameters).length > 0 && { searchParameters }),
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
// Handle streaming response
|
||||
const reader = response.body.getReader();
|
||||
const decoder = new TextDecoder();
|
||||
|
||||
return {
|
||||
async *[Symbol.asyncIterator]() {
|
||||
try {
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
|
||||
const chunk = decoder.decode(value, { stream: true });
|
||||
if (chunk.trim()) {
|
||||
yield chunk;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
reader.releaseLock();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
const askAI = new CustomAskAI({
|
||||
appId: 'YOUR_APP_ID',
|
||||
apiKey: 'YOUR_API_KEY',
|
||||
indexName: 'YOUR_INDEX_NAME-markdown', // Your markdown index
|
||||
assistantId: 'YOUR_ASSISTANT_ID',
|
||||
});
|
||||
|
||||
// Send a message with facet filters for your markdown index
|
||||
const stream = await askAI.sendMessage('conversation-1', [
|
||||
{
|
||||
role: 'user',
|
||||
content: 'How do I configure my API?',
|
||||
id: 'msg-1',
|
||||
},
|
||||
], {
|
||||
facetFilters: ['language:en', 'type:content'] // Filter to relevant content
|
||||
});
|
||||
|
||||
// Handle streaming response
|
||||
for await (const chunk of stream) {
|
||||
console.log(chunk); // Handle each chunk of the response
|
||||
}
|
||||
```
|
||||
|
||||
**Benefits of custom integration:**
|
||||
- Full control over UI/UX
|
||||
- Custom authentication and session management
|
||||
- Advanced filtering and search parameters for your markdown index
|
||||
- Integration with existing chat systems
|
||||
- Custom analytics and monitoring
|
||||
|
||||
> **📚 Learn More:** For complete API documentation, authentication details, advanced examples, and more integration patterns, see the [Ask AI API Reference](/docs/v4/askai-api).
|
||||
|
||||
**Using Facet Filters with Your Markdown Index:**
|
||||
|
||||
Since your markdown index includes attributes like `lang`, `version`, and `docusaurus_tag`, you can filter Ask AI's context precisely:
|
||||
|
||||
```js
|
||||
// Example: Filter to English documentation only
|
||||
const searchParameters = {
|
||||
facetFilters: ['lang:en']
|
||||
};
|
||||
|
||||
// Example: Filter to specific version and content type
|
||||
const searchParameters = {
|
||||
facetFilters: ['lang:en', 'version:latest', 'type:content']
|
||||
};
|
||||
|
||||
// Example: Use OR logic for multiple tags (from your integration examples)
|
||||
const searchParameters = {
|
||||
facetFilters: [
|
||||
'lang:en',
|
||||
[
|
||||
'docusaurus_tag:default',
|
||||
'docusaurus_tag:docs-default-current'
|
||||
]
|
||||
]
|
||||
};
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
</Tabs>
|
||||
|
||||
> **Tip:** Keep both indexes updated as your documentation evolves to ensure the best search and AI answer quality.
|
||||
|
||||
|
|
|
|||
|
|
@ -2,9 +2,13 @@
|
|||
title: Prompting
|
||||
---
|
||||
|
||||
> **Why this page?**
|
||||
> A well-crafted prompt turns raw language-model horsepower into accurate, on-brand answers.
|
||||
> This guide distills the best practices from OpenAI, Anthropic, and our own experience running **Ask AI** at scale. It explains *how* prompts are used in DocSearch v4, then shows you *how* to write your own.
|
||||
Master the art of prompting to get the best results from Ask AI. This guide covers how prompts work in DocSearch v4, shares proven techniques from leading AI providers, and provides practical examples to help you create effective, on-brand AI responses for your documentation.
|
||||
|
||||
**What you'll learn:**
|
||||
- How Ask AI uses prompts alongside your content
|
||||
- Proven prompting techniques and patterns
|
||||
- Common pitfalls and how to avoid them
|
||||
- Security and compliance best practices
|
||||
|
||||
## 1. How prompting works in Ask AI
|
||||
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ export default {
|
|||
label: 'Algolia AskAI - Beta',
|
||||
items: [
|
||||
'v4/askai',
|
||||
'v4/askai-api',
|
||||
'v4/askai-prompts',
|
||||
'v4/askai-whitelisted-domains',
|
||||
'v4/askai-models',
|
||||
|
|
|
|||
Loading…
Reference in a new issue