Skip to content

Instantly share code, notes, and snippets.

@simonw
Created November 2, 2024 18:24
Show Gist options
  • Save simonw/d6797005adf1688427470f9fcb8d287f to your computer and use it in GitHub Desktop.
Save simonw/d6797005adf1688427470f9fcb8d287f to your computer and use it in GitHub Desktop.

Token Counting API Interface

human (Nov 2, 2024, 11:05 AM)

curl https://api.anthropic.com/v1/messages/count_tokens \
    --header "x-api-key: $ANTHROPIC_API_KEY" \
    --header "content-type: application/json" \
    --header "anthropic-version: 2023-06-01" \
    --header "anthropic-beta: token-counting-2024-11-01" \
    --data '{
      "model": "claude-3-5-sonnet-20241022",
      "system": "You are a scientist",
      "messages": [{
        "role": "user",
        "content": "Hello, Claude"
      }]
    }'

Build an artifact that lets me pipe content to this API and see the JSON results - pull the API key from ANTHROPIC_API_KEY key in localStorage and prompt() for it if it doesn't exist

assistant (Nov 2, 2024, 11:05 AM)

I'll help you create a simple HTML interface to interact with the token counting API.

create Token Counter Interface

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Token Counter</title>
  <style>
  * {
    box-sizing: border-box;
  }

  body {
    font-family: Helvetica, Arial, sans-serif;
    line-height: 1.6;
    max-width: 800px;
    margin: 0 auto;
    padding: 20px;
  }

  textarea {
    width: 100%;
    min-height: 200px;
    margin: 10px 0;
    padding: 10px;
    font-size: 16px;
    font-family: monospace;
    border: 1px solid #ccc;
    border-radius: 4px;
  }

  button {
    background: #0066ff;
    color: white;
    border: none;
    padding: 10px 20px;
    border-radius: 4px;
    cursor: pointer;
    font-size: 16px;
  }

  button:disabled {
    background: #cccccc;
    cursor: not-allowed;
  }

  .output {
    background: #f5f5f5;
    padding: 20px;
    border-radius: 4px;
    margin-top: 20px;
    white-space: pre-wrap;
    font-family: monospace;
  }

  .error {
    color: #ff0000;
    margin: 10px 0;
  }

  .input-group {
    margin-bottom: 20px;
  }

  label {
    display: block;
    margin-bottom: 5px;
    font-weight: bold;
  }
  </style>
</head>
<body>
  <h1>Claude Token Counter</h1>
  
  <div class="input-group">
    <label for="system">System prompt:</label>
    <textarea id="system" placeholder="Enter system prompt (optional)"></textarea>
  </div>

  <div class="input-group">
    <label for="content">User message:</label>
    <textarea id="content" placeholder="Enter message content"></textarea>
  </div>

  <button id="count">Count Tokens</button>
  <div id="error" class="error"></div>
  <div id="output" class="output"></div>

<script type="module">
const API_URL = 'https://api.anthropic.com/v1/messages/count_tokens'
const MODEL = 'claude-3-5-sonnet-20241022'

function getApiKey() {
  let key = localStorage.getItem('ANTHROPIC_API_KEY')
  if (!key) {
    key = prompt('Please enter your Anthropic API key:')
    if (key) {
      localStorage.setItem('ANTHROPIC_API_KEY', key)
    }
  }
  return key
}

async function countTokens(system, content) {
  const apiKey = getApiKey()
  if (!apiKey) {
    throw new Error('API key is required')
  }

  const response = await fetch(API_URL, {
    method: 'POST',
    headers: {
      'x-api-key': apiKey,
      'content-type': 'application/json',
      'anthropic-version': '2023-06-01',
      'anthropic-beta': 'token-counting-2024-11-01'
    },
    body: JSON.stringify({
      model: MODEL,
      system: system || undefined,
      messages: [{
        role: 'user',
        content: content
      }]
    })
  })

  if (!response.ok) {
    const error = await response.text()
    throw new Error(`API error: ${error}`)
  }

  return response.json()
}

const systemInput = document.getElementById('system')
const contentInput = document.getElementById('content')
const countButton = document.getElementById('count')
const errorDiv = document.getElementById('error')
const outputDiv = document.getElementById('output')

countButton.addEventListener('click', async () => {
  errorDiv.textContent = ''
  outputDiv.textContent = 'Counting tokens...'
  countButton.disabled = true

  try {
    const result = await countTokens(
      systemInput.value.trim(),
      contentInput.value.trim()
    )
    outputDiv.textContent = JSON.stringify(result, null, 2)
  } catch (error) {
    errorDiv.textContent = error.message
    outputDiv.textContent = ''
  } finally {
    countButton.disabled = false
  }
})
</script>
</body>
</html>

I've created a simple token counting interface that:

  1. Provides separate inputs for system prompt and user message
  2. Stores the API key in localStorage and prompts for it if not found
  3. Makes API calls to the token counting endpoint
  4. Displays the full JSON response or any errors
  5. Uses vanilla JavaScript and CSS with no external dependencies
  6. Follows the requested style guidelines for CSS and JavaScript

To use it:

  1. Enter your text in either/both fields
  2. Click "Count Tokens"
  3. The first time, you'll be prompted for your API key
  4. Results will appear below in a formatted JSON display

Let me know if you'd like any adjustments to the styling or functionality!

human (Nov 2, 2024, 11:11 AM)

I fixed it to be like this:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Token Counter</title>
  <style>
  * {
    box-sizing: border-box;
  }

  body {
    font-family: Helvetica, Arial, sans-serif;
    line-height: 1.6;
    max-width: 800px;
    margin: 0 auto;
    padding: 20px;
  }

  textarea {
    width: 100%;
    min-height: 200px;
    margin: 10px 0;
    padding: 10px;
    font-size: 16px;
    font-family: monospace;
    border: 1px solid #ccc;
    border-radius: 4px;
  }

  button {
    background: #0066ff;
    color: white;
    border: none;
    padding: 10px 20px;
    border-radius: 4px;
    cursor: pointer;
    font-size: 16px;
  }

  button:disabled {
    background: #cccccc;
    cursor: not-allowed;
  }

  .output {
    background: #f5f5f5;
    padding: 20px;
    border-radius: 4px;
    margin-top: 20px;
    white-space: pre-wrap;
    font-family: monospace;
  }

  .error {
    color: #ff0000;
    margin: 10px 0;
  }

  .input-group {
    margin-bottom: 20px;
  }

  label {
    display: block;
    margin-bottom: 5px;
    font-weight: bold;
  }
  </style>
</head>
<body>
  <h1>Claude Token Counter</h1>
  
  <div class="input-group">
    <label for="system">System prompt:</label>
    <textarea id="system" placeholder="Enter system prompt (optional)"></textarea>
  </div>

  <div class="input-group">
    <label for="content">User message:</label>
    <textarea id="content" placeholder="Enter message content"></textarea>
  </div>

  <button id="count">Count Tokens</button>
  <div id="error" class="error"></div>
  <div id="output" class="output"></div>

<script type="module">
const API_URL = 'https://api.anthropic.com/v1/messages/count_tokens'
const MODEL = 'claude-3-5-sonnet-20241022'

function getApiKey() {
  let key = localStorage.getItem('ANTHROPIC_API_KEY')
  if (!key) {
    key = prompt('Please enter your Anthropic API key:')
    if (key) {
      localStorage.setItem('ANTHROPIC_API_KEY', key)
    }
  }
  return key
}

async function countTokens(system, content) {
  const apiKey = getApiKey()
  if (!apiKey) {
    throw new Error('API key is required')
  }

  const response = await fetch(API_URL, {
    method: 'POST',
    headers: {
      'x-api-key': apiKey,
      'content-type': 'application/json',
      'anthropic-version': '2023-06-01',
      'anthropic-beta': 'token-counting-2024-11-01',
      'anthropic-dangerous-direct-browser-access': 'true'
    },
    body: JSON.stringify({
      model: MODEL,
      system: system || undefined,
      messages: [{
        role: 'user',
        content: content
      }]
    })
  })

  if (!response.ok) {
    const error = await response.text()
    throw new Error(`API error: ${error}`)
  }

  return response.json()
}

const systemInput = document.getElementById('system')
const contentInput = document.getElementById('content')
const countButton = document.getElementById('count')
const errorDiv = document.getElementById('error')
const outputDiv = document.getElementById('output')

countButton.addEventListener('click', async () => {
  errorDiv.textContent = ''
  outputDiv.textContent = 'Counting tokens...'
  countButton.disabled = true

  try {
    const result = await countTokens(
      systemInput.value.trim(),
      contentInput.value.trim()
    )
    outputDiv.textContent = JSON.stringify(result, null, 2)
  } catch (error) {
    errorDiv.textContent = error.message
    outputDiv.textContent = ''
  } finally {
    countButton.disabled = false
  }
})
</script>
</body>
</html>

Now update that to add a select box for picking the model from this list:

claude-3-opus-20240229 claude-3-sonnet-20240229 claude-3-haiku-20240307 claude-3-5-sonnet-20240620 claude-3-5-sonnet-20241022

Make the claude-3-5-sonnet-20241022 one the default

Also make the system prompt box smaller in height

assistant (Nov 2, 2024, 11:11 AM)

I'll update the HTML to add the model selector and adjust the system prompt height.

create Token Counter Interface with Model Selection

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Token Counter</title>
  <style>
  * {
    box-sizing: border-box;
  }

  body {
    font-family: Helvetica, Arial, sans-serif;
    line-height: 1.6;
    max-width: 800px;
    margin: 0 auto;
    padding: 20px;
  }

  textarea {
    width: 100%;
    min-height: 200px;
    margin: 10px 0;
    padding: 10px;
    font-size: 16px;
    font-family: monospace;
    border: 1px solid #ccc;
    border-radius: 4px;
  }

  textarea#system {
    min-height: 100px;
  }

  select {
    width: 100%;
    padding: 8px;
    font-size: 16px;
    border: 1px solid #ccc;
    border-radius: 4px;
    margin: 10px 0;
  }

  button {
    background: #0066ff;
    color: white;
    border: none;
    padding: 10px 20px;
    border-radius: 4px;
    cursor: pointer;
    font-size: 16px;
  }

  button:disabled {
    background: #cccccc;
    cursor: not-allowed;
  }

  .output {
    background: #f5f5f5;
    padding: 20px;
    border-radius: 4px;
    margin-top: 20px;
    white-space: pre-wrap;
    font-family: monospace;
  }

  .error {
    color: #ff0000;
    margin: 10px 0;
  }

  .input-group {
    margin-bottom: 20px;
  }

  label {
    display: block;
    margin-bottom: 5px;
    font-weight: bold;
  }
  </style>
</head>
<body>
  <h1>Claude Token Counter</h1>
  
  <div class="input-group">
    <label for="model">Model:</label>
    <select id="model">
      <option value="claude-3-opus-20240229">Claude 3 Opus</option>
      <option value="claude-3-sonnet-20240229">Claude 3 Sonnet</option>
      <option value="claude-3-haiku-20240307">Claude 3 Haiku</option>
      <option value="claude-3-5-sonnet-20240620">Claude 3.5 Sonnet (June)</option>
      <option value="claude-3-5-sonnet-20241022" selected>Claude 3.5 Sonnet (Current)</option>
    </select>
  </div>

  <div class="input-group">
    <label for="system">System prompt:</label>
    <textarea id="system" placeholder="Enter system prompt (optional)"></textarea>
  </div>

  <div class="input-group">
    <label for="content">User message:</label>
    <textarea id="content" placeholder="Enter message content"></textarea>
  </div>

  <button id="count">Count Tokens</button>
  <div id="error" class="error"></div>
  <div id="output" class="output"></div>

<script type="module">
const API_URL = 'https://api.anthropic.com/v1/messages/count_tokens'

function getApiKey() {
  let key = localStorage.getItem('ANTHROPIC_API_KEY')
  if (!key) {
    key = prompt('Please enter your Anthropic API key:')
    if (key) {
      localStorage.setItem('ANTHROPIC_API_KEY', key)
    }
  }
  return key
}

async function countTokens(system, content, model) {
  const apiKey = getApiKey()
  if (!apiKey) {
    throw new Error('API key is required')
  }

  const response = await fetch(API_URL, {
    method: 'POST',
    headers: {
      'x-api-key': apiKey,
      'content-type': 'application/json',
      'anthropic-version': '2023-06-01',
      'anthropic-beta': 'token-counting-2024-11-01',
      'anthropic-dangerous-direct-browser-access': 'true'
    },
    body: JSON.stringify({
      model: model,
      system: system || undefined,
      messages: [{
        role: 'user',
        content: content
      }]
    })
  })

  if (!response.ok) {
    const error = await response.text()
    throw new Error(`API error: ${error}`)
  }

  return response.json()
}

const modelSelect = document.getElementById('model')
const systemInput = document.getElementById('system')
const contentInput = document.getElementById('content')
const countButton = document.getElementById('count')
const errorDiv = document.getElementById('error')
const outputDiv = document.getElementById('output')

countButton.addEventListener('click', async () => {
  errorDiv.textContent = ''
  outputDiv.textContent = 'Counting tokens...'
  countButton.disabled = true

  try {
    const result = await countTokens(
      systemInput.value.trim(),
      contentInput.value.trim(),
      modelSelect.value
    )
    outputDiv.textContent = JSON.stringify(result, null, 2)
  } catch (error) {
    errorDiv.textContent = error.message
    outputDiv.textContent = ''
  } finally {
    countButton.disabled = false
  }
})
</script>
</body>
</html>

I've made the following changes:

  1. Added a model selector dropdown with all the specified models
  2. Set claude-3-5-sonnet-20241022 as the default selected option
  3. Reduced the system prompt textarea height to 100px using a specific CSS rule
  4. Updated the countTokens function to accept the model parameter
  5. Passed the selected model value to the API call

The interface now allows users to select different Claude models while maintaining all the previous functionality. Let me know if you'd like any adjustments to the styling or behavior!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment