Skip to content

Dynamic Prompts

Dynamic Prompts is an advanced integration pattern. Tuor can store and version the system prompts your models use. Pulling the active prompt at runtime — instead of hard-coding it — means every prompt change automatically applies to new traces, and every trace is permanently linked to the exact prompt version that produced it.


How it works

  1. You store your prompt in Tuor and fetch the active version at runtime.
  2. Your model runs with that prompt content.
  3. You ingest the trace with the prompt version ID stamped in trace_config.
  4. Tuor accumulates enough reviewed traces to suggest a refinement.
  5. You review and accept the suggestion — it becomes the new active version.
  6. Your service picks it up on the next fetch.
sequenceDiagram
    participant App as Your App
    participant Tuor as Tuor API
    participant Model as Your Model

    App->>Tuor: GET /v1/projects/{id}/prompts
    Tuor-->>App: { active: { id, content, version } }
    App->>Model: Run with active.content
    Model-->>App: model_output
    App->>Tuor: POST /v1/traces/ (trace_config.prompt_version_id = active.id)

Fetch the active prompt

curl "https://api.tuor.dev/v1/projects/proj_abc123/prompts" \
  -H "X-API-Key: $TUOR_API_KEY"

Response

{
  "active": {
    "id": "prm_abc123",
    "project_id": "proj_abc123",
    "version": 3,
    "content": "You are a financial assistant. Extract the following fields...",
    "source": "manual",
    "is_active": true,
    "created_at": "2026-05-20T10:00:00Z",
    "created_by": "user_1abc"
  },
  "history": [...]
}

active is null if no prompt has been saved yet for the project. The objects above are abbreviated — each prompt also carries org_id, parent_id, rationale, and lineage_event_ids.


Ingest traces linked to the prompt

Stamp prompt_version_id in trace_config on every ingest call. Tuor echoes it back in webhook payloads and exports so you can trace any output back to the exact prompt that produced it.

curl -X POST "https://api.tuor.dev/v1/traces/" \
  -H "X-API-Key: $TUOR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "project_id": "proj_abc123",
    "model_input": { "content": "Extract net income from the attached statement." },
    "model_output": { "net_income": 250000 },
    "trace_config": {
      "model": "gpt-4o",
      "prompt_version_id": "prm_abc123",
      "internal_run_id": "run_01j2abcde"
    }
  }'

Cache the active prompt

The active prompt ID only changes when a new version is saved. Fetching it once at startup — or on a short TTL — is sufficient. There is no need to fetch it per-request.

Python

import os
import httpx

TUOR_API_KEY = os.environ["TUOR_API_KEY"]
PROJECT_ID = "proj_abc123"

def fetch_active_prompt() -> dict:
    resp = httpx.get(
        f"https://api.tuor.dev/v1/projects/{PROJECT_ID}/prompts",
        headers={"X-API-Key": TUOR_API_KEY},
        timeout=5,
    )
    resp.raise_for_status()
    return resp.json()["active"]

# Cache at startup
prompt = fetch_active_prompt()

def run_and_trace(user_input: str) -> dict:
    model_output = call_your_model(prompt["content"], user_input)

    httpx.post(
        "https://api.tuor.dev/v1/traces/",
        headers={"X-API-Key": TUOR_API_KEY, "Content-Type": "application/json"},
        json={
            "project_id": PROJECT_ID,
            "model_input": {"content": user_input},
            "model_output": model_output,
            "trace_config": {
                "model": "gpt-4o",
                "prompt_version_id": prompt["id"],
            },
        },
    )
    return model_output

TypeScript

const TUOR_API_KEY = process.env.TUOR_API_KEY!;
const PROJECT_ID = "proj_abc123";

async function fetchActivePrompt() {
  const res = await fetch(
    `https://api.tuor.dev/v1/projects/${PROJECT_ID}/prompts`,
    { headers: { "X-API-Key": TUOR_API_KEY } }
  );
  const data = await res.json();
  return data.active;
}

// Cache at startup
const prompt = await fetchActivePrompt();

async function runAndTrace(userInput: string) {
  const modelOutput = await callYourModel(prompt.content, userInput);

  await fetch("https://api.tuor.dev/v1/traces/", {
    method: "POST",
    headers: {
      "X-API-Key": TUOR_API_KEY,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      project_id: PROJECT_ID,
      model_input: { content: userInput },
      model_output: modelOutput,
      trace_config: {
        model: "gpt-4o",
        prompt_version_id: prompt.id,
      },
    }),
  });

  return modelOutput;
}

Save a new prompt version

When you want to update the prompt manually, POST a new version. It becomes active immediately and all subsequent fetches will return it.

curl -X POST "https://api.tuor.dev/v1/projects/proj_abc123/prompts" \
  -H "X-API-Key: $TUOR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "content": "You are a financial assistant. Extract the following fields with strict ISO formatting..." }'

Prompt content is capped at 16 KB. The previous active version is retained in history and linked traces are unaffected.


Prompt refinement

Once a project has accumulated at least 10 reviewed traces since the current active version, Tuor can suggest a revised prompt based on what reviewers actually corrected. See the Integration API for the /refine and /accept-refinement endpoints.