Skip to content

Chat & Agents Guide

Learn how to use Satori's AI agents for advanced interactions with your documents and knowledge bases.

Overview

Satori provides multiple ways to interact with your documents via AI. Choose the right approach based on your needs:

Feature Simple Chat Agent Chat Query
Endpoint /enclaves/{id}/chat/ /chat/agent /enclaves/{id}/query
Response Streaming text Streaming JSON events JSON
History Session-based Thread-based None
Use case Basic Q&A Advanced tools/conversation One-shot questions

When to Use Each

  • Simple Chat: Quick questions about documents with streaming responses
  • Agent Chat: Complex multi-turn conversations with tool use capabilities
  • Query: Programmatic one-shot questions returning structured JSON

Knowledge Base Chat

The simplest way to interact with your documents is through the enclave chat endpoint.

Basic Chat

Simple Query Format:

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"
  }'

Vercel AI SDK Message Format:

The endpoint accepts standard Vercel AI SDK messages with id, role, and parts[]:

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 '{
    "messages": [
      {
        "id": "msg-1",
        "role": "user",
        "parts": [{"type": "text", "text": "What are the key findings?"}]
      }
    ],
    "user_id": "user-123",
    "session_id": "session-abc"
  }'

Response: Streaming JSON events via Server-Sent Events (AI SDK 6 Data Stream Protocol)

Streaming Response Format

The chat endpoint returns streaming events in JSON format:

data: {"type":"text-delta","delta":"The study"}
data: {"type":"text-delta","delta":" shows"}
data: {"type":"text-delta","delta":" significant results"}
data: {"type":"finish","finishReason":"stop","usage":{"promptTokens":50,"completionTokens":25}}
data: [DONE]

Event Types: - text-delta: Text chunk from the response - tool-input-available: Tool call initiated - tool-output-available: Tool result available - finish: Response complete with usage stats - error: Error occurred

Python Example

import requests
import json

def chat_with_enclave(enclave_id, query, session_id=None, user_id=None):
    url = f"{BASE_URL}/api/tenants/{tenant_id}/enclaves/{enclave_id}/chat/"
    headers = {
        "Authorization": f"Bearer {JWT_TOKEN}",
        "Content-Type": "application/json"
    }
    data = {
        "query": query,
        "session_id": session_id or f"session-{uuid.uuid4()}",
        "user_id": user_id
    }

    response = requests.post(url, headers=headers, json=data, stream=True)

    full_response = ""
    for line in response.iter_lines():
        if line:
            text = line.decode('utf-8')
            if text.startswith('data: '):
                content = text[6:]
                if content == '[DONE]':
                    break

                # Parse JSON event
                try:
                    event = json.loads(content)
                    if event.get("type") == "text-delta":
                        chunk = event.get("delta", "")
                        full_response += chunk
                        print(chunk, end='', flush=True)
                    elif event.get("type") == "finish":
                        print(f"\n\nUsage: {event.get('usage')}")
                except json.JSONDecodeError:
                    pass

    return full_response

Session Management

Sessions maintain conversation history for context:

session_id = "my-session-123"

# First message
response1 = chat_with_enclave(enclave_id, "What is this document about?", session_id)

# Follow-up (uses previous context)
response2 = chat_with_enclave(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 primary endpoints?",
      "timestamp": "2025-01-15T10:30:00Z"
    },
    {
      "role": "assistant",
      "content": "The primary endpoints are...",
      "timestamp": "2025-01-15T10:30:05Z",
      "sources": [
        {
          "file_name": "protocol.pdf",
          "chunk_id": "node_abc123",
          "page": 12
        }
      ]
    }
  ],
  "session_id": "my-session-123",
  "message_count": 2
}

Advanced Agents

Satori supports advanced AI agents with tool calling capabilities.

Agent Chat Endpoint

curl -X POST "__API_HOST__/api/tenants/{tenant_id}/chat/agent" \
  -H "Authorization: Bearer <YOUR_JWT_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{
    "messages": [
      {
        "role": "user",
        "parts": [{"type": "text", "text": "What are the key findings?"}]
      }
    ],
    "thread_id": "user-123",
    "run_id": "session-456",
    "forwarded_props": {
      "enclave_id": "750e8400-e29b-41d4-a716-446655440000",
      "file_id": "850e8400-e29b-41d4-a716-446655440000"
    }
  }'

Key Fields:

  • messages: Conversation history in Vercel AI SDK format (required)
  • thread_id: User identifier for maintaining conversation state
  • run_id: Session identifier for grouping related queries
  • forwarded_props: Metadata passed to agent
  • enclave_id: Target enclave (required for document queries)
  • file_id: Optional specific file to query

Message Format

The endpoint accepts the standard Vercel AI SDK 6 message format:

interface Message {
  id?: string;           // Optional message ID
  role: "user" | "assistant" | "system" | "tool";
  parts: Part[];         // Required parts array (AI SDK 6)
  createdAt?: string;    // ISO 8601 timestamp
}

interface Part {
  type: "text" | string;  // "text" or "tool-{toolName}"
  text?: string;          // For text parts
  toolCallId?: string;    // For tool parts
  toolName?: string;      // For tool parts
  input?: any;            // Tool input parameters
  output?: any;           // Tool output result
  state?: "input-available" | "output-available" | "output-error";
}

Example:

{
  "messages": [
    {
      "id": "msg-1",
      "role": "user",
      "parts": [
        {"type": "text", "text": "What are the key findings?"}
      ]
    }
  ],
  "thread_id": "user-123",
  "run_id": "session-456"
}

Agent Architecture

Agents use a message-based system:

  • User Messages: Questions and instructions
  • System Messages: Configuration and context
  • Assistant Messages: Agent responses
  • Tool Messages: Results from tool executions
  • Developer Messages: System instructions

Example Agent Interaction

import requests
from typing import List, Dict, Any

def create_agent_request(
    query: str,
    thread_id: str,
    run_id: str,
    enclave_id: str = None,
    file_id: str = None
) -> Dict[str, Any]:
    request = {
        "thread_id": thread_id,
        "run_id": run_id,
        "messages": [
            {
                "role": "user",
                "parts": [{"type": "text", "text": query}]
            }
        ]
    }

    if enclave_id:
        request["forwarded_props"] = {"enclave_id": enclave_id}
        if file_id:
            request["forwarded_props"]["file_id"] = file_id

    return request

# Query documents in an enclave
request = create_agent_request(
    query="What are the key findings in the research papers?",
    thread_id="user-123",
    run_id="session-456",
    enclave_id="750e8400-e29b-41d4-a716-446655440000"
)

response = requests.post(
    f"{BASE_URL}/api/tenants/{tenant_id}/chat/agent",
    headers={"Authorization": f"Bearer {JWT_TOKEN}"},
    json=request,
    stream=True
)

Clinical Research Agents

Clinical research agents have access to medical literature and research databases.

Clinical Agent Features

  • Access to medical research databases
  • Clinical trial information
  • Medical literature search
  • Evidence-based answers

Using Clinical Agents

import uuid

def query_clinical_agent(query: str, session_id: str = None):
    """Query the clinical research agent (without specifying an enclave)."""
    url = f"{BASE_URL}/api/tenants/{tenant_id}/chat/agent"

    request = {
        "thread_id": session_id or f"clinical-{uuid.uuid4()}",
        "run_id": f"run-{uuid.uuid4()}",
        "messages": [
            {
                "role": "user",
                "parts": [{"type": "text", "text": query}]
            }
        ]
        # No forwarded_props means agent uses clinical research capabilities
    }

    response = requests.post(
        url,
        headers={"Authorization": f"Bearer {JWT_TOKEN}"},
        json=request,
        stream=True
    )

    return response

Tool Calling

Agents can use tools to perform actions:

Available Tools

  • Document Search: Search within enclave documents
  • Web Search: Search the web for information
  • Database Queries: Query structured data
  • Custom Tools: Define your own tools

Tool Definition

tool = {
    "name": "search_documents",
    "description": "Search for information in uploaded documents",
    "parameters": {
        "type": "object",
        "properties": {
            "query": {
                "type": "string",
                "description": "Search query"
            },
            "limit": {
                "type": "integer",
                "description": "Maximum number of results"
            }
        },
        "required": ["query"]
    }
}

Agent with Tools

def agent_with_tools(query: str, enclave_id: str):
    tools = [
        {
            "name": "search_enclave",
            "description": f"Search documents in enclave {enclave_id}",
            "parameters": {
                "type": "object",
                "properties": {
                    "query": {"type": "string"},
                    "limit": {"type": "integer", "default": 5}
                }
            }
        }
    ]

    request = create_agent_request(
        query=query,
        thread_id=f"thread-{uuid.uuid4()}",
        run_id=f"run-{uuid.uuid4()}",
        tools=tools
    )

    response = requests.post(
        f"{BASE_URL}/api/tenants/{tenant_id}/chat/agent",
        headers={"Authorization": f"Bearer {JWT_TOKEN}"},
        json=request,
        stream=True
    )

    return response

Streaming Responses

All chat endpoints support streaming for real-time responses using AI SDK 6 Data Stream Protocol.

Stream Event Format

Each event is a JSON object with a type field:

interface StreamEvent {
  type: "text-delta" | "tool-input-available" | "tool-output-available" | "finish" | "error";
  // Event-specific fields
}

interface TextDeltaEvent {
  type: "text-delta";
  delta: string;  // Text chunk
}

interface FinishEvent {
  type: "finish";
  finishReason: "stop" | "length" | "tool-calls" | "error";
  usage?: {
    promptTokens: number;
    completionTokens: number;
  };
}

interface ToolEvent {
  type: "tool-input-available" | "tool-output-available";
  toolCallId: string;
  toolName: string;
  input?: any;
  output?: any;
}

Handling Streams

import json

def process_stream(response):
    """Process streaming response from agent."""
    full_text = ""

    for line in response.iter_lines():
        if line:
            text = line.decode('utf-8')

            # Handle Server-Sent Events format
            if text.startswith('data: '):
                content = text[6:]
                if content == '[DONE]':
                    break

                try:
                    event = json.loads(content)
                    event_type = event.get("type")

                    if event_type == "text-delta":
                        chunk = event.get("delta", "")
                        full_text += chunk
                        yield chunk
                    elif event_type == "finish":
                        print(f"\nFinish reason: {event.get('finishReason')}")
                        print(f"Usage: {event.get('usage')}")
                    elif event_type == "tool-input-available":
                        print(f"\nTool called: {event.get('toolName')}")
                    elif event_type == "error":
                        print(f"\nError: {event.get('error')}")
                except json.JSONDecodeError:
                    pass

    return full_text

JavaScript/TypeScript Streaming

async function* streamAgentResponse(response: Response) {
  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 content = line.slice(6);
        if (content === "[DONE]") return;

        try {
          const event = JSON.parse(content);

          switch (event.type) {
            case "text-delta":
              yield event.delta;
              break;
            case "finish":
              console.log("Finish:", event.finishReason, event.usage);
              break;
            case "tool-input-available":
              console.log("Tool called:", event.toolName);
              break;
            case "error":
              console.error("Error:", event.error);
              break;
          }
        } catch (e) {
          // Skip invalid JSON
        }
      }
    }
  }
}

Best Practices

✅ DO:

  • Use sessions for conversational context
  • Provide clear, specific queries
  • Handle streaming responses properly
  • Review source citations
  • Use appropriate agent types for your use case

❌ DON'T:

  • Mix different conversation topics in the same session
  • Ignore source citations
  • Make too many concurrent agent requests
  • Use agents for simple queries (use direct query endpoint instead)

Error Handling

Common Errors

404 Not Found

  • Enclave doesn't exist
  • Session not found
  • Solution: Verify IDs and create resources first

500 Internal Server Error

  • Agent execution error
  • Tool execution failed
  • Solution: Check query format and tool definitions

Error Handling Example

def safe_agent_query(query: str, enclave_id: str):
    try:
        response = agent_with_tools(query, enclave_id)
        return process_stream(response)
    except requests.HTTPError as e:
        if e.response.status_code == 404:
            raise ValueError("Enclave or session not found")
        elif e.response.status_code == 500:
            raise ValueError("Agent execution error")
        else:
            raise

Next Steps