Querying Documents Guide
Learn how to query your documents using natural language and retrieve relevant information from your enclaves.
Overview
Satori uses RAG (Retrieval-Augmented Generation) to answer questions about your documents:
- Query → Your natural language question
- Embed → Converted to vector embedding
- Search → Similar document chunks retrieved from vector store
- Context → Relevant chunks passed to LLM
- Generate → AI generates answer based on context
- Response → Answer with source citations
Basic Querying
Simple Query
curl -X POST "__API_HOST__/api/tenants/{tenant_id}/enclaves/{enclave_id}/query" \
-H "Authorization: Bearer <YOUR_JWT_TOKEN>" \
-H "Content-Type: application/json" \
-d '{"query": "What are the key points?"}'
Response:
{
"answer": "The key points in the document include...",
"sources": [
{
"node_id": "node_abc123",
"text": "Relevant document excerpt...",
"score": 0.95
}
]
}
Python Example
import requests
def query_enclave(enclave_id, query, instructions=None):
url = f"{BASE_URL}/api/tenants/{tenant_id}/enclaves/{enclave_id}/query"
headers = {
"Authorization": f"Bearer {JWT_TOKEN}",
"Content-Type": "application/json"
}
data = {"query": query}
if instructions:
data["instructions"] = instructions
response = requests.post(url, headers=headers, json=data)
return response.json()
# Usage
result = query_enclave(enclave_id, "What are the payment terms?")
print(f"Answer: {result['answer']}")
print(f"Sources: {len(result['sources'])} chunks found")
Query Instructions
Customize AI behavior with optional instructions:
AI Instructions
Guide how the AI responds:
result = query_enclave(
enclave_id,
"What are the key findings?",
instructions={
"ai_instruction": "Focus on statistical results and use formal language"
}
)
Document Context
Provide context about your documents:
result = query_enclave(
enclave_id,
"What are the termination clauses?",
instructions={
"document_context": "This is a software licensing agreement"
}
)
Full Example with Instructions
curl -X POST "__API_HOST__/api/tenants/{tenant_id}/enclaves/{enclave_id}/query" \
-H "Authorization: Bearer <YOUR_JWT_TOKEN>" \
-H "Content-Type: application/json" \
-d '{
"query": "What are the payment terms?",
"instructions": {
"ai_instruction": "Provide a concise summary with bullet points",
"document_context": "These are vendor contracts for software services"
}
}'
Query Best Practices
✅ Good Queries
- Specific and focused: "What are the payment terms in section 3?"
- Context-aware: "What does the contract say about termination?"
- Action-oriented: "List the key deliverables mentioned in the project plan"
❌ Poor Queries
- Too broad: "Tell me everything about this document"
- Vague: "What is this about?"
- Multiple questions: "What are the terms and who is responsible?"
Query Examples
# ✅ Good: Specific question
query = "What are the payment terms in the contract?"
# ✅ Good: Context-specific
query = "What does the research paper say about side effects?"
# ✅ Good: Comparative
query = "How do the results compare between group A and group B?"
# ❌ Bad: Too broad
query = "Summarize everything"
# ❌ Bad: Multiple questions
query = "What are the terms and when does it expire?"
Source Citations
Every query response includes source citations showing where the information came from.
Understanding Sources
{
"sources": [
{
"node_id": "node_abc123",
"text": "Payment terms are net 30 days...",
"score": 0.95
}
]
}
- node_id: Unique identifier for the document chunk
- text: The relevant excerpt from the document
- score: Relevance score (0-1, higher is better)
Getting Full References
Use node_ids to fetch complete reference information:
curl -X GET "__API_HOST__/api/tenants/{tenant_id}/enclaves/{enclave_id}/references?node_ids=node_abc123,node_def456" \
-H "Authorization: Bearer <YOUR_JWT_TOKEN>"
Response:
{
"references": [
{
"node_id": "node_abc123",
"text": "Complete document chunk with full context...",
"file_id": "850e8400-e29b-41d4-a716-446655440000",
"file_name": "contract.pdf",
"metadata": {
"page": 5,
"section": "Payment Terms",
"chunk_index": 12
},
"created_at": "2025-01-10T10:00:00Z"
}
]
}
Building Citation Systems
def query_with_citations(enclave_id, query):
# Get query response
result = query_enclave(enclave_id, query)
# Fetch full references
node_ids = [s["node_id"] for s in result["sources"]]
refs_response = requests.get(
f"{BASE_URL}/enclaves/{enclave_id}/references",
params={"node_ids": ",".join(node_ids)},
headers={"Authorization": f"Bearer {JWT_TOKEN}"}
)
references = refs_response.json()
# Combine answer with citations
return {
"answer": result["answer"],
"citations": [
{
"file": ref["file_name"],
"page": ref["metadata"].get("page", "N/A"),
"text": ref["text"]
}
for ref in references["references"]
]
}
Semantic Search
Use semantic search to find relevant documents without generating an answer:
curl -X POST "__API_HOST__/api/tenants/{tenant_id}/enclaves/{enclave_id}/search" \
-H "Authorization: Bearer <YOUR_JWT_TOKEN>" \
-d "query=payment terms"
Response:
{
"results": [
{
"node_id": "node_abc123",
"text": "Payment terms are net 30 days...",
"score": 0.95,
"file_id": "850e8400-e29b-41d4-a716-446655440000",
"file_name": "contract.pdf"
}
]
}
Chat Interface
Use the chat endpoint for conversational interactions:
curl -X POST "__API_HOST__/api/tenants/{tenant_id}/enclaves/{enclave_id}/chat/" \
-H "Authorization: Bearer <YOUR_JWT_TOKEN>" \
-H "Content-Type: application/json" \
-d '{
"query": "What are the key findings?",
"user_id": "user-123",
"session_id": "session-abc"
}'
Response: Streaming text (Server-Sent Events)
data: The key findings
data: from the research include
data: significant improvements in...
data: [DONE]
Handling Streaming Responses
import requests
def stream_chat(enclave_id, query, session_id=None):
url = f"{BASE_URL}/enclaves/{enclave_id}/chat/"
headers = {
"Authorization": f"Bearer {JWT_TOKEN}",
"Content-Type": "application/json"
}
data = {
"query": query,
"session_id": session_id or "default"
}
response = requests.post(url, headers=headers, json=data, stream=True)
for line in response.iter_lines():
if line:
text = line.decode('utf-8')
if text.startswith('data: '):
content = text[6:]
if content == '[DONE]':
break
yield content
JavaScript Streaming Example
async function* streamChat(
enclaveId: string,
query: string,
sessionId?: string
) {
const response = await fetch(
`/api/tenants/${tenantId}/enclaves/${enclaveId}/chat/`,
{
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
query,
session_id: sessionId || "default",
}),
}
);
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 text = line.slice(6);
if (text === "[DONE]") return;
yield text;
}
}
}
}
Session Management
Chat sessions maintain conversation history:
Creating a Session
session_id = f"session-{uuid.uuid4()}"
# First message
response1 = stream_chat(enclave_id, "What is this document about?", session_id)
# Follow-up (uses conversation history)
response2 = stream_chat(enclave_id, "Tell me more about that", session_id)
Retrieving Session History
curl -X GET "__API_HOST__/api/tenants/{tenant_id}/enclaves/{enclave_id}/chat/{session_id}/messages" \
-H "Authorization: Bearer <YOUR_JWT_TOKEN>"
Response:
{
"messages": [
{
"role": "user",
"content": "What are the key findings?",
"timestamp": "2025-01-15T10:30:00Z"
},
{
"role": "assistant",
"content": "The key findings include...",
"timestamp": "2025-01-15T10:30:05Z",
"sources": [
{
"file_name": "research_paper.pdf",
"chunk_id": "node_abc123"
}
]
}
],
"session_id": "session-abc",
"message_count": 2
}
Advanced Query Patterns
Multi-Step Queries
Break complex questions into multiple queries:
def complex_query(enclave_id, main_question):
# Step 1: Find relevant documents
search_results = search_enclave(enclave_id, main_question, limit=5)
# Step 2: Query specific aspects
queries = [
f"What does {main_question} say about topic A?",
f"What does {main_question} say about topic B?",
]
results = []
for query in queries:
result = query_enclave(enclave_id, query)
results.append(result)
return results
Comparative Queries
Compare information across documents:
def compare_documents(enclave_id, topic):
query = f"How do different documents address {topic}?"
result = query_enclave(enclave_id, query)
# Group sources by file
sources_by_file = {}
for source in result["sources"]:
file_id = source.get("file_id")
if file_id not in sources_by_file:
sources_by_file[file_id] = []
sources_by_file[file_id].append(source)
return sources_by_file
Error Handling
Common Errors
404 Not Found
- Enclave doesn't exist
- Enclave has no documents
- Solution: Verify enclave_id and upload files
500 Internal Server Error
- Query processing error
- Solution: Check query format, try simpler query
Handling Errors
def safe_query(enclave_id, query):
try:
result = query_enclave(enclave_id, query)
return result
except requests.HTTPError as e:
if e.response.status_code == 404:
raise ValueError("Enclave not found or has no documents")
elif e.response.status_code == 500:
raise ValueError("Query processing error - try a simpler query")
else:
raise
Performance Tips
✅ DO:
- Use specific, focused queries
- Break complex questions into multiple queries
- Use semantic search for finding documents
- Cache frequently asked questions
- Use streaming for better UX
❌ DON'T:
- Ask overly broad questions
- Expect answers about content not in documents
- Ignore source citations
- Make too many concurrent queries
Next Steps
- Chat & Agents Guide - Using AI agents
- Best Practices Guide - General usage best practices
- API Reference - Full API documentation