Skip to main content

Overview

This guide walks through a complete RAG (Retrieval-Augmented Generation) pipeline using only HTTP requests to the Artanis REST API. You’ll see how to:
  1. Create a trace
  2. Record input observations
  3. Capture state for replay
  4. Record output observations
  5. Complete the trace
  6. Submit user feedback
This example is language-agnostic and works with any HTTP client.

Full Example: RAG Pipeline

#!/bin/bash
set -e

# Configuration
API_KEY="ak_..."
BASE_URL="https://app.artanis.ai"
AUTH_HEADER="Authorization: Bearer $API_KEY"

# Generate trace ID and timestamps
TRACE_ID="trace_$(openssl rand -hex 11)"
START_TIME=$(date +%s%3N)
START_TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%S.%3NZ")

echo "Starting trace: $TRACE_ID"

# 1. CREATE TRACE
curl -X POST "$BASE_URL/api/v1/traces" \
  -H "$AUTH_HEADER" \
  -H "Content-Type: application/json" \
  -d "{
    \"trace_id\": \"$TRACE_ID\",
    \"name\": \"rag-query\",
    \"timestamp\": \"$START_TIMESTAMP\",
    \"status\": \"running\",
    \"metadata\": {
      \"user_id\": \"user-123\",
      \"environment\": \"production\"
    }
  }"

# 2. RECORD INPUT (user question)
curl -X POST "$BASE_URL/api/v1/observations" \
  -H "$AUTH_HEADER" \
  -H "Content-Type: application/json" \
  -d "{
    \"trace_id\": \"$TRACE_ID\",
    \"type\": \"input\",
    \"data\": {
      \"question\": \"What is your refund policy?\",
      \"user_id\": \"user-123\"
    },
    \"timestamp\": \"$(date -u +"%Y-%m-%dT%H:%M:%S.%3NZ")\"
  }"

# 3. CAPTURE STATE (document corpus)
curl -X POST "$BASE_URL/api/v1/observations" \
  -H "$AUTH_HEADER" \
  -H "Content-Type: application/json" \
  -d "{
    \"trace_id\": \"$TRACE_ID\",
    \"type\": \"state\",
    \"key\": \"documents\",
    \"data\": [\"doc-123\", \"doc-456\", \"doc-789\"],
    \"timestamp\": \"$(date -u +"%Y-%m-%dT%H:%M:%S.%3NZ")\"
  }"

# 4. SIMULATE RETRIEVAL (sleep 100ms)
sleep 0.1

# 5. CAPTURE STATE (retrieved chunks)
curl -X POST "$BASE_URL/api/v1/observations" \
  -H "$AUTH_HEADER" \
  -H "Content-Type: application/json" \
  -d "{
    \"trace_id\": \"$TRACE_ID\",
    \"type\": \"state\",
    \"key\": \"retrieved_chunks\",
    \"data\": [
      {\"id\": \"doc-123\", \"score\": 0.95, \"title\": \"Refund Policy\"},
      {\"id\": \"doc-456\", \"score\": 0.87, \"title\": \"Returns FAQ\"}
    ],
    \"timestamp\": \"$(date -u +"%Y-%m-%dT%H:%M:%S.%3NZ")\"
  }"

# 6. RECORD INPUT (generation parameters)
curl -X POST "$BASE_URL/api/v1/observations" \
  -H "$AUTH_HEADER" \
  -H "Content-Type: application/json" \
  -d "{
    \"trace_id\": \"$TRACE_ID\",
    \"type\": \"input\",
    \"data\": {
      \"model\": \"gpt-4\",
      \"temperature\": 0.7,
      \"max_tokens\": 500
    },
    \"timestamp\": \"$(date -u +"%Y-%m-%dT%H:%M:%S.%3NZ")\"
  }"

# 7. CAPTURE STATE (generation config)
curl -X POST "$BASE_URL/api/v1/observations" \
  -H "$AUTH_HEADER" \
  -H "Content-Type: application/json" \
  -d "{
    \"trace_id\": \"$TRACE_ID\",
    \"type\": \"state\",
    \"key\": \"config\",
    \"data\": {
      \"model\": \"gpt-4\",
      \"temperature\": 0.7,
      \"prompt_template\": \"Answer based on context: {context}\"
    },
    \"timestamp\": \"$(date -u +"%Y-%m-%dT%H:%M:%S.%3NZ")\"
  }"

# 8. SIMULATE GENERATION (sleep 1s)
sleep 1

# 9. RECORD OUTPUT
curl -X POST "$BASE_URL/api/v1/observations" \
  -H "$AUTH_HEADER" \
  -H "Content-Type: application/json" \
  -d "{
    \"trace_id\": \"$TRACE_ID\",
    \"type\": \"output\",
    \"data\": \"We offer a 30-day money-back guarantee on all purchases. Simply contact our support team to initiate a refund.\",
    \"timestamp\": \"$(date -u +"%Y-%m-%dT%H:%M:%S.%3NZ")\"
  }"

# 10. CALCULATE DURATION
END_TIME=$(date +%s%3N)
DURATION_MS=$((END_TIME - START_TIME))

# 11. COMPLETE TRACE
curl -X POST "$BASE_URL/api/v1/traces" \
  -H "$AUTH_HEADER" \
  -H "Content-Type: application/json" \
  -d "{
    \"trace_id\": \"$TRACE_ID\",
    \"name\": \"rag-query\",
    \"timestamp\": \"$START_TIMESTAMP\",
    \"status\": \"completed\",
    \"duration_ms\": $DURATION_MS,
    \"metadata\": {
      \"user_id\": \"user-123\",
      \"environment\": \"production\"
    }
  }"

echo "Trace completed in ${DURATION_MS}ms"
echo "Trace ID: $TRACE_ID"

# 12. SUBMIT FEEDBACK (positive)
curl -X POST "$BASE_URL/api/v1/feedback" \
  -H "$AUTH_HEADER" \
  -H "Content-Type: application/json" \
  -d "{
    \"trace_id\": \"$TRACE_ID\",
    \"rating\": \"positive\",
    \"timestamp\": \"$(date -u +"%Y-%m-%dT%H:%M:%S.%3NZ")\"
  }"

echo "Feedback submitted"

Flow Diagram

Here’s the sequence of API calls in this example:

Key Concepts Demonstrated

1. Client-Side Trace ID Generation

The trace ID is generated client-side before any API calls. This enables the fire-and-forget pattern with no server round-trips.
trace_id = f"trace_{secrets.token_hex(11)}"

2. Immediate Observation Sending

Observations are sent immediately as they occur, not batched. This ensures data is captured even if the application crashes.

3. State Capture for Replay

The example captures three types of state:
  • documents: The document corpus at query time
  • retrieved_chunks: Which documents were retrieved and their scores
  • config: Generation parameters and prompt template
This enables exact replay of the scenario later.

4. Progressive Input Recording

Inputs are recorded at multiple stages:
  • Initial user query
  • Generation parameters added later
You can call the observations endpoint multiple times to progressively build up the input context.

5. Consistent Metadata

The same metadata is used in both the “create” and “complete” trace requests, ensuring consistency.

Error Handling

Add error handling to production code:
import requests
from requests.exceptions import RequestException
import time

def make_request_with_retry(url: str, headers: dict, json: dict, max_retries: int = 3):
    """Make request with exponential backoff retry"""
    for attempt in range(max_retries):
        try:
            response = requests.post(url, headers=headers, json=json, timeout=5)

            if response.status_code == 202:
                return response
            elif response.status_code == 429:
                # Rate limited - exponential backoff
                wait_time = 2 ** attempt
                time.sleep(wait_time)
                continue
            elif response.status_code >= 500:
                # Server error - retry
                wait_time = 2 ** attempt
                time.sleep(wait_time)
                continue
            else:
                # Client error - don't retry
                print(f"Error: {response.status_code} - {response.text}")
                return None

        except RequestException as e:
            print(f"Request failed: {e}")
            if attempt < max_retries - 1:
                wait_time = 2 ** attempt
                time.sleep(wait_time)
            else:
                return None

    return None

# Usage
make_request_with_retry(
    url=f"{BASE_URL}/api/v1/traces",
    headers=headers,
    json={
        "trace_id": trace_id,
        "name": "rag-query",
        "timestamp": start_timestamp,
        "status": "running"
    }
)
In production, implement exponential backoff for rate limits (429) and server errors (5xx). Don’t retry client errors (4xx).

Next Steps

Comparison: REST API vs SDK

FeatureREST APIOfficial SDK
SetupNo installation, any HTTP clientnpm/pip install
CodeManual HTTP requestsSimple method calls
Trace IDManual generation requiredAutomatic generation
Error HandlingManual retry logicBuilt-in silent failures
OverheadNetwork latency per request<0.1ms per operation
Type SafetyJSON validation at runtimeCompile-time type checking
Best ForLanguages without SDKsPython & TypeScript projects
The REST API is perfect for Haskell, Go, Rust, and other languages without official SDKs. The official SDKs provide a better developer experience when available.