Skip to content

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:

  1. Query → Your natural language question
  2. Embed → Converted to vector embedding
  3. Search → Similar document chunks retrieved from vector store
  4. Context → Relevant chunks passed to LLM
  5. Generate → AI generates answer based on context
  6. 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"]
        ]
    }

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