import { CodeBlock } from '../../_components/code-block'

export const metadata = {
  alternates: { canonical: '/docs/features/prompts' },
  title: 'Prompts · Spanlens Docs',
  description:
    'Version-controlled prompt templates with real-data A/B comparison, latency, cost, and error rate per version.',
}

export default function PromptsDocs() {
  return (
    <div>
      <h1>Prompts</h1>
      <p className="lead">
        Store your prompt templates as named, versioned assets. Every time you tweak a prompt, Spanlens
        creates a new immutable version. Then compare versions side-by-side with real production
        metrics, average latency, error rate, and cost per call.
      </p>

      <h2>Why it matters</h2>
      <p>
        Prompts get edited constantly: a line added here, an example rewritten there, a tone shift
        on Friday afternoon. The unanswered question is always the same, <em>is this actually
        better, or does it just feel better?</em>
      </p>
      <p>
        Plain <code>.replace()</code> edits in your codebase give you no answers. Previous versions
        are lost, you can&apos;t roll back, and you never learn which version actually costs less or
        fails less. Spanlens Prompts fixes that without forcing you to adopt a new runtime or
        template engine.
      </p>

      <h2>How it works</h2>

      <h3>Versioning</h3>
      <p>
        Save a prompt under a name (e.g. <code>chatbot-system</code>) in the dashboard. Edit it
        later → a new version is auto-created with the next number. Old versions stay forever
        (immutable). No manual version bumps, no schema migrations.
      </p>
      <CodeBlock language="text">{`chatbot-system
  ├─ v1  (2 weeks ago)  "You are a helpful assistant..."
  ├─ v2  (1 week ago)   "You are a helpful Korean-speaking assistant..."
  └─ v3  (yesterday)    "You are a Korean assistant. Be concise..."`}</CodeBlock>

      <p>Each version stores:</p>
      <ul>
        <li><code>content</code>, the template body (up to 100K chars)</li>
        <li><code>variables</code>, typed placeholders like <code>{'{{userName}}'}</code> with description and <code>required</code> flag</li>
        <li><code>metadata</code>, free-form JSON for tags (team, task type, model target, etc.)</li>
        <li><code>project_id</code>, optional project scope</li>
      </ul>

      <h3>A/B comparison on real traffic</h3>
      <p>
        Click a prompt in <a href="/prompts">/prompts</a> and you&apos;ll see a comparison table of
        every version that has received production traffic in the last 30 days:
      </p>
      <div className="overflow-x-auto">
        <table className="[&_th:first-child]:text-left [&_td:first-child]:text-left [&_td:first-child]:whitespace-nowrap [&_td:first-child]:align-middle">
          <thead>
            <tr>
              <th>Version</th>
              <th className="text-right">Samples</th>
              <th className="text-right">Avg latency</th>
              <th className="text-right">Error %</th>
              <th className="text-right">Avg cost</th>
              <th className="text-right">Total cost</th>
            </tr>
          </thead>
          <tbody>
            <tr>
              <td>v3</td>
              <td className="text-right">1,245</td>
              <td className="text-right">820ms</td>
              <td className="text-right">0.4%</td>
              <td className="text-right">$0.0012</td>
              <td className="text-right">$1.49</td>
            </tr>
            <tr>
              <td>v2</td>
              <td className="text-right">3,102</td>
              <td className="text-right">1.2s</td>
              <td className="text-right">1.1%</td>
              <td className="text-right">$0.0018</td>
              <td className="text-right">$5.58</td>
            </tr>
            <tr>
              <td>v1</td>
              <td className="text-right">890</td>
              <td className="text-right">1.4s</td>
              <td className="text-right">2.3%</td>
              <td className="text-right">$0.0023</td>
              <td className="text-right">$2.04</td>
            </tr>
          </tbody>
        </table>
      </div>
      <p>
        In this example v3 is 32% faster, has 1/5 the error rate, and costs 33% less per call than
        v2. That&apos;s a clear keep-v3, retire-v2 decision with actual numbers behind it.
      </p>

      <h2>Using it</h2>

      <h3>Creating a prompt version via dashboard</h3>
      <ol>
        <li>Go to <a href="/prompts">/prompts</a> and click <strong>New prompt / version</strong>.</li>
        <li>Enter a name (e.g. <code>chatbot-system</code>). Reusing a name → new version.</li>
        <li>Paste the content. Save.</li>
      </ol>

      <h3>Creating via API</h3>
      <CodeBlock language="bash">{`curl https://server.spanlens.io/api/v1/prompts \\
  -H "Authorization: Bearer $SPANLENS_JWT" \\
  -H "Content-Type: application/json" \\
  -d '{
    "name": "chatbot-system",
    "content": "You are a Korean assistant. Be concise.",
    "metadata": { "team": "growth", "tested": true }
  }'`}</CodeBlock>
      <p>
        Response includes the auto-assigned <code>version</code>. See the full endpoint list below.
      </p>

      <h3>Fetching the comparison data</h3>
      <CodeBlock language="bash">{`GET /api/v1/prompts/:name/compare?sinceHours=720

# returns per-version metrics:
#   { version, sampleCount, avgLatencyMs, errorRate, avgCostUsd, totalCostUsd }`}</CodeBlock>

      <h3>API reference</h3>
      <table className="[&_th:first-child]:text-left [&_td:first-child]:text-left [&_td:first-child]:whitespace-nowrap [&_td:first-child]:align-middle">
        <thead>
          <tr>
            <th>Method + Path</th>
            <th>Description</th>
          </tr>
        </thead>
        <tbody>
          <tr>
            <td><code>GET /api/v1/prompts</code></td>
            <td>List all prompts (latest version per name)</td>
          </tr>
          <tr>
            <td><code>GET /api/v1/prompts/:name</code></td>
            <td>Full version history for a prompt name</td>
          </tr>
          <tr>
            <td><code>GET /api/v1/prompts/:name/compare</code></td>
            <td>Per-version metrics for A/B comparison</td>
          </tr>
          <tr>
            <td><code>GET /api/v1/prompts/:name/:version</code></td>
            <td>Fetch one specific version</td>
          </tr>
          <tr>
            <td><code>POST /api/v1/prompts</code></td>
            <td>Create a new version (auto-increments version number)</td>
          </tr>
          <tr>
            <td><code>POST /api/v1/prompts/:name/:version/rollback</code></td>
            <td>
              Copy an older version&apos;s content as a new (latest) version. The old version is
              not modified, the version counter always increases. Returns the newly created version.
            </td>
          </tr>
          <tr>
            <td><code>DELETE /api/v1/prompts/:name/:version</code></td>
            <td>Delete one version</td>
          </tr>
        </tbody>
      </table>

      <h2>Tagging requests with a prompt version</h2>
      <p>
        For the A/B table to fill up, each LLM request needs to declare which version it
        used. The SDK ships two ways to do that, pick whichever fits your call site.
      </p>

      <h3>Option 1 — <code>withPromptVersion()</code> per call</h3>
      <CodeBlock language="ts">{`import { createOpenAI, withPromptVersion } from '@spanlens/sdk/openai'

const openai = createOpenAI()

const res = await openai.chat.completions.create(
  {
    model: 'gpt-4o-mini',
    messages: [
      { role: 'system', content: promptV3Content },
      { role: 'user', content: userMessage },
    ],
  },
  withPromptVersion('chatbot-system@3'),
)`}</CodeBlock>
      <p>Same helper exists on <code>@spanlens/sdk/anthropic</code> for Claude calls.</p>

      <h3>Option 2 — <code>observeOpenAI()</code> with promptVersion option</h3>
      <p>If you&apos;re already using agent tracing, just add one option:</p>
      <CodeBlock language="ts">{`import { observeOpenAI } from '@spanlens/sdk'

const res = await observeOpenAI(
  trace,
  { name: 'answer', promptVersion: 'chatbot-system@3' },
  (headers) => openai.chat.completions.create({ /* ... */ }, { headers }),
)`}</CodeBlock>

      <h3>Accepted id formats</h3>
      <table className="[&_th:first-child]:text-left [&_th:nth-child(2)]:text-left [&_td:first-child]:text-left [&_td:nth-child(2)]:text-left [&_td:first-child]:whitespace-nowrap [&_td:nth-child(2)]:whitespace-nowrap [&_td:first-child]:align-middle [&_td:nth-child(2)]:align-middle">
        <thead>
          <tr><th>Format</th><th>Example</th><th>Notes</th></tr>
        </thead>
        <tbody>
          <tr>
            <td><code>name@version</code></td>
            <td><code>chatbot-system@3</code></td>
            <td>Most common; explicit version pin</td>
          </tr>
          <tr>
            <td><code>name@latest</code></td>
            <td><code>chatbot-system@latest</code></td>
            <td>Auto-resolves to the highest version server-side on every call</td>
          </tr>
          <tr>
            <td>Raw UUID</td>
            <td><code>ae1c3c1e-99eb-...</code></td>
            <td>Use the <code>id</code> returned from POST <code>/api/v1/prompts</code></td>
          </tr>
        </tbody>
      </table>
      <p>
        Server-side the header value is looked up in <code>prompt_versions</code> scoped to your
        organization. Invalid / unknown values silently resolve to null (the request still succeeds,
        it just isn&apos;t linked to a version).
      </p>

      <h2>Sub-tabs inside the Prompts page</h2>
      <p>
        Clicking a prompt in the dashboard reveals six sub-tabs:
      </p>
      <ul>
        <li>
          <strong>Versions</strong>, full version list with expandable content preview.{' '}
          Each version has a <strong>Roll back</strong> button that copies its content as a new
          latest version (the old version is never deleted, the version counter always increases).
        </li>
        <li><strong>Diff</strong>, select any two versions for an LCS-based line-level diff (+/− colors)</li>
        <li>
          <strong>Traffic</strong>, per-version traffic share with quality color coding
          (≥90 green / 70–89 yellow / &lt;70 red)
        </li>
        <li>
          <strong>Calls</strong>, per-version call count, latency, error rate,{' '}
          <strong>QUALITY</strong>, cost, and token totals. Clicking a row drills down to{' '}
          <code>/requests?promptVersionId=...</code>.{' '}
          The <strong>Quality column</strong> shows the average <code>eval_results</code> score
          from <a href="/docs/features/evals">Evals</a>.
        </li>
        <li>
          <strong>A/B</strong>, live production traffic A/B routing. Different from offline{' '}
          <a href="/docs/features/experiments">Experiments</a> (see table below). →{' '}
          <a href="/docs/features/prompt-ab">Full Prompt A/B docs</a>
        </li>
        <li>
          <strong>Playground</strong>, select a version, configure provider key, model,
          temperature, and variables, then run immediately. Results are not saved to the{' '}
          <code>requests</code> table. Rate limit: 20 req/min/user. →{' '}
          <a href="/docs/features/prompts-playground">Full Playground docs</a>
        </li>
      </ul>

      <h2>A/B routing vs Experiments</h2>
      <p>
        The word &quot;experiment&quot; appears in two places in Spanlens, here is how they differ:
      </p>
      <div className="overflow-x-auto">
        <table className="[&_th:first-child]:text-left [&_td:first-child]:text-left [&_td:first-child]:whitespace-nowrap [&_td:first-child]:align-middle [&_td:not(:first-child)]:align-middle">
          <thead>
            <tr>
              <th></th>
              <th>A/B (this tab)</th>
              <th><a href="/docs/features/experiments">Experiments</a></th>
            </tr>
          </thead>
          <tbody>
            <tr>
              <td>Data</td>
              <td>Live production traffic</td>
              <td>Offline dataset</td>
            </tr>
            <tr>
              <td>Timing</td>
              <td>Real-time, real users exposed</td>
              <td>Runs immediately, no user exposure</td>
            </tr>
            <tr>
              <td>Measurement</td>
              <td>Statistical significance (Welch&apos;s t-test)</td>
              <td>Direct output comparison + scores</td>
            </tr>
            <tr>
              <td>Risk</td>
              <td>A bad version reaches real users</td>
              <td>None</td>
            </tr>
          </tbody>
        </table>
      </div>
      <p>
        Complementary: <strong>pre-validate with Experiments → confirm in production with A/B</strong>.
      </p>

      <h2>Limitations</h2>
      <p>Honest view of what the feature does <em>not</em> do yet:</p>
      <ul>
        <li>
          <strong>No editor affordances.</strong> The create/edit form is a plain textarea ,
          no diff view, no syntax highlighting, no variable autocomplete. Good enough for now;
          polish deferred to post-launch.
        </li>
        <li>
          <strong>Comparison window is fixed at 30 days in the UI.</strong> The API accepts a{' '}
          <code>sinceHours</code> query parameter; we just haven&apos;t wired a UI picker yet.
        </li>
        <li>
          <strong>No statistical-significance hints.</strong> If v1 has 5 samples and v2 has 5,000,
          both show up the same way in the table. Significance flags are on the roadmap.
        </li>
      </ul>

      <hr />
      <p className="text-sm text-muted-foreground">
        Related: <a href="/docs/features/evals">Evals</a> (response quality scoring),{' '}
        <a href="/docs/features/experiments">Experiments</a> (offline comparison),{' '}
        <a href="/docs/features/savings">Savings</a> (model substitution),{' '}
        <a href="/docs/features/traces">Traces</a>, <a href="/prompts">/prompts</a> dashboard.
      </p>
    </div>
  )
}
