Guides
Get AI-powered answers from your documents
Get AI-powered answers to questions about your documents. EasyRAG finds relevant content and uses GPT-4 to generate accurate, grounded responses.
When you ask a question:
The AI is instructed to answer only from your documents and admit when it doesn't know.
bashcurl -X POST https://api.easyrag.com/v1/query \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "datasetId": "my-documents", "question": "What are the key features?" }'
javascriptconst response = await fetch('https://api.easyrag.com/v1/query', { method: 'POST', headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ datasetId: 'my-documents', question: 'What are the key features?', stream: false }) }); const { data } = await response.json(); console.log('Answer:', data.result);
json{ "success": true, "data": { "result": "Based on the documentation, the key features include:\n\n1. Automatic document processing for PDFs, Word docs, and spreadsheets\n2. Semantic search that understands meaning, not just keywords\n3. AI-powered question answering using GPT-4\n4. Multi-tenant architecture with complete data isolation\n\nThese features work together to provide a complete RAG solution.", "sources": [ { "pageContent": "The key features include automatic document processing...", "metadata": { "fileId": "f7a3b2c1", "originalName": "features.pdf" } } ] } }
Streaming provides a better user experience for chat interfaces - users see responses appear word-by-word.
javascriptasync function streamQuery(datasetId, question) { const response = await fetch('https://api.easyrag.com/v1/query', { method: 'POST', headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ datasetId, question, stream: true }) }); 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); const lines = chunk.split('\n'); for (const line of lines) { if (line.startsWith('data: ')) { const data = JSON.parse(line.slice(6)); if (data.delta) { // New text chunk console.log(data.delta); } else if (data.done) { // Stream complete console.log('Done!'); } else if (data.error) { // Error occurred console.error(data.error); } } } } }
The stream uses Server-Sent Events (SSE):
data: {"delta":"Based on"}
data: {"delta":" the documentation,"}
data: {"delta":" the key features"}
data: {"delta":" include:\n\n"}
data: {"delta":"1. Automatic"}
data: {"delta":" document processing\n"}
data: {"done":true}
Here's a complete streaming chat interface:
javascriptimport { useState, useRef, useEffect } from 'react'; function ChatInterface({ datasetId, token }) { const [messages, setMessages] = useState([]); const [input, setInput] = useState(''); const [loading, setLoading] = useState(false); const messagesEndRef = useRef(null); const scrollToBottom = () => { messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); }; useEffect(scrollToBottom, [messages]); const handleSubmit = async (e) => { e.preventDefault(); if (!input.trim() || loading) return; const userMessage = input; setInput(''); setMessages(prev => [...prev, { role: 'user', content: userMessage }]); setLoading(true); // Add placeholder for assistant const assistantIndex = messages.length + 1; setMessages(prev => [...prev, { role: 'assistant', content: '' }]); try { const response = await fetch('https://api.easyrag.com/v1/query', { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ datasetId, question: userMessage, stream: true }) }); const reader = response.body.getReader(); const decoder = new TextDecoder(); let buffer = ''; while (true) { const { done, value } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); const lines = buffer.split('\n'); buffer = lines.pop(); // Keep incomplete line for (const line of lines) { if (line.startsWith('data: ')) { const data = JSON.parse(line.slice(6)); if (data.delta) { setMessages(prev => { const updated = [...prev]; updated[assistantIndex].content += data.delta; return updated; }); } } } } } catch (error) { console.error('Query failed:', error); setMessages(prev => { const updated = [...prev]; updated[assistantIndex].content = 'Error: Failed to get response'; return updated; }); } finally { setLoading(false); } }; return ( <div style={{ display: 'flex', flexDirection: 'column', height: '600px' }}> {/* Messages */} <div style={{ flex: 1, overflow: 'auto', padding: '20px' }}> {messages.map((msg, i) => ( <div key={i} style={{ margin: '10px 0', padding: '12px', background: msg.role === 'user' ? '#e3f2fd' : '#f5f5f5', borderRadius: '8px', maxWidth: '80%', marginLeft: msg.role === 'user' ? 'auto' : '0' }} > <strong>{msg.role === 'user' ? 'You' : 'Assistant'}:</strong> <div style={{ whiteSpace: 'pre-wrap', marginTop: '5px' }}> {msg.content} </div> </div> ))} <div ref={messagesEndRef} /> </div> {/* Input */} <form onSubmit={handleSubmit} style={{ padding: '20px', borderTop: '1px solid #ccc' }}> <input type="text" value={input} onChange={(e) => setInput(e.target.value)} placeholder="Ask a question..." disabled={loading} style={{ width: '100%', padding: '12px', fontSize: '16px', border: '2px solid #ddd', borderRadius: '8px' }} /> </form> </div> ); } export default ChatInterface;
Apply metadata filters to search specific documents:
javascriptconst response = await fetch('https://api.easyrag.com/v1/query', { method: 'POST', headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ datasetId: 'company-docs', question: 'What is the vacation policy?', filters: [ { key: 'department', match: { value: 'HR' } }, { key: 'year', match: { value: 2024 } } ] }) });
See Filtering with Metadata for details.
The AI is instructed to answer only from your documents:
System Prompt:
You are a RAG assistant. Answer ONLY based on the provided context.
If the context is not enough, say you don't know and do NOT hallucinate.
If your documents don't contain the answer:
json{ "data": { "result": "I don't have enough information in the provided documents to answer that question." } }
The AI receives context like this:
### Document 1
Score: 0.892
Source: user-manual.pdf
[chunk content]
---
### Document 2
Score: 0.856
Source: faq.pdf
[chunk content]
---
User question:
What is the refund policy?
javascriptconst answer = await query( 'support-docs', 'How do I reset my password?' ); // Returns step-by-step instructions from your docs
javascriptconst answer = await query( 'company-policies', 'What is the work from home policy?', { filters: [ { key: 'department', match: { value: 'HR' } } ] } );
javascriptconst answer = await query( 'legal-docs', 'Summarize the key points of this contract', { filters: [ { key: 'fileId', match: { value: contractFileId } } ] } );
The embeddings work in 100+ languages:
javascript// Question in Spanish const answer = await query( 'multilingual-docs', '¿Cómo reinicio mi contraseña?' ); // Matches English docs about password reset
javascript// ✅ Good: Specific question "What are the steps to reset my password?" // ❌ Bad: Too vague "password"
javascriptconst { data } = await query(datasetId, question, { stream: false }); if (data.result.includes("don't have enough information")) { console.log("No relevant documents found"); } else { console.log("Answer:", data.result); }
javascriptconst [loading, setLoading] = useState(false); // Show loading indicator {loading && <div>Thinking...</div>}
javascript// Suggest related questions const followUps = [ "Can you elaborate on that?", "What are the alternatives?", "Are there any exceptions?" ];
| Feature | Streaming | Non-Streaming |
|---|---|---|
| UX | Better (real-time) | Slower (wait for full response) |
| Complexity | More code | Simpler |
| Use Case | Chat interfaces | API integrations, batch processing |
| Cost | Same (0.1 credit) | Same (0.1 credit) |
Use streaming when:
Use non-streaming when:
| Feature | /v1/query | /v1/search |
|---|---|---|
| Returns | AI-generated answer | Raw chunks |
| Processing | Uses GPT-4 | No LLM |
| Streaming | Yes | No |
| Use Case | Ready answers | Custom chat UI |
| Cost | 0.1 credit | 0.1 credit |
Use /v1/query when:
Use /v1/search when:
See Searching Documents for search details.
javascripttry { const response = await fetch('https://api.easyrag.com/v1/query', { method: 'POST', headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ datasetId, question }) }); if (response.status === 402) { const error = await response.json(); alert('Out of credits! Please top up.'); return; } const data = await response.json(); console.log(data); } catch (error) { console.error('Query failed:', error); }
javascript// In your streaming loop if (data.error) { console.error('Stream error:', data.error); // Show error message to user setError(data.error); break; }
Each query costs 0.1 credit, regardless of streaming mode.
| Operation | Cost |
|---|---|
| 1 query | 0.1 credit |
| 10 queries | 1 credit |
| 100 queries | 10 credits |
The cost includes both retrieval and GPT-4 generation.
Problem: AI gives vague responses
Solutions:
Problem: AI provides incorrect info
Solutions:
Problem: Query takes too long
Solutions:
For technical details, see Query API Reference.