tiktoken-go

repository·main·Indexed 21 days ago

https://github.com/pkoukk/tiktoken-go

A fast Byte Pair Encoding (BPE) tokenizer for Go, ported from OpenAI's original tiktoken library. It is used to count and encode tokens for OpenAI models such as GPT-4o, GPT-4, and GPT-3.5-turbo. The library provides parity-compatibility with the official Python tiktoken implementation and supports custom BpeLoaders and dictionary caching via the TIKTOKEN_CACHE_DIR environment variable.

Tokens
8.1K
Snippets
23
Records
30
Agent score
74%

What's inside tiktoken-go

  1. Use alternative BPE loaders

    main

    If you want to avoid downloading dictionaries at runtime or using the default cache, you can provide a custom BpeLoader.

    To use a custom loader, call tiktoken.SetBpeLoader before calling tiktoken.GetEncoding or tiktoken.EncodingForModel. BpeLoader is an interface that you can implement yourself.

    For an offline BPE loader (which loads dictionaries from embedded files), you can use the separate tiktoken_loader project: github.com/pkoukk/tiktoken-go-loader.

    // Example of setting an offline loader
    // tiktoken.SetBpeLoader(tiktoken_loader.NewOfflineLoader())
  2. Use an alternative BPE loader

    main

    By default, tiktoken-go downloads dictionaries at runtime. To avoid runtime downloads or to use a local cache, you can implement the BpeLoader interface and set it before calling tiktoken.GetEncoding or tiktoken.EncodingForModel using tiktoken.SetBpeLoader.

    For an offline BPE loader that loads dictionaries from embedded files, you can use the tiktoken_loader package: github.com/pkoukk/tiktoken-go-loader.

    // Example of setting an offline loader
    // tiktoken.SetBpeLoader(tiktoken_loader.NewOfflineLoader())
  3. Map OpenAI models to their corresponding encodings

    main

    The library provides internal mapping between OpenAI model names and their required BPE encodings. This allows you to resolve which encoding to use based on the model you are targeting.

    Model to Encoding Mappings:

    • gpt-4.5, gpt-4.1, gpt-4o $\rightarrow$ o200k_base
    • gpt-4, gpt-3.5-turbo, text-embedding-3-large $\rightarrow$ cl100k_base
    • text-davinci-003, code-davinci-002 $\rightarrow$ p50k_base
    • text-davinci-001, davinci, curie $\rightarrow$ r50k_base
    • text-davinci-edit-001 $\rightarrow$ p50k_edit
    • gpt2 $\rightarrow$ gpt2

    Prefix-based Mappings: If you have a specific versioned model name (e.g., gpt-4o-2024-05-13), the library uses MODEL_PREFIX_TO_ENCODING to match the prefix (e.g., gpt-4o-) to the correct base encoding.

  4. Configure the BPE dictionary cache

    main

    tiktoken-go uses a cache mechanism for token dictionaries. You can control where these are stored using the TIKTOKEN_CACHE_DIR environment variable.

    • If set: The library will use the specified directory to cache the dictionary.
    • If not set: The library will download the dictionary from the internet every time you initialize an encoding for the first time.
  5. Configure the token dictionary cache

    main

    Like the original OpenAI tiktoken library, tiktoken-go supports a caching mechanism for token dictionaries.

    You can set the cache directory using the TIKTOKEN_CACHE_DIR environment variable. If this variable is set, the library will use that directory to cache dictionaries. If not set, tiktoken-go will download the dictionaries every time an encoding is initialized for the first time.

  6. Calculate token consumption for Chat API messages

    main

    To estimate the number of tokens used in a Chat Completion request (similar to the OpenAI cookbook), you must account for the specific overhead added by the model's message format (roles, names, and content).

    Note: The exact calculation method may change as OpenAI updates their models. This implementation is based on logic for gpt-3.5-turbo and gpt-4 variants as of mid-2023.

    package main
    
    import (
    	"fmt"
    
    	"github.com/pkoukk/tiktoken-go"
    	"github.com/sashabaranov/go-openai"
    )
    
    func NumTokensFromMessages(messages []openai.ChatCompletionMessage, model string) (numTokens int) {
    	tkm, err := tiktoken.EncodingForModel(model)
    	if err != nil {
    		err = fmt.Errorf("encoding for model: %v", err)
    		log.Println(err)
    		return
    	}
    
    	var tokensPerMessage, tokensPerName int
    	switch model {
    	case "gpt-3.5-turbo-0613",
    		"gpt-3.5-turbo-16k-0613",
    		"gpt-4-0314",
    		"gpt-4-32k-0314",
    		"gpt-4-0613",
    		"gpt-4-32k-0613":
    		tokensPerMessage = 3
    		tokensPerName = 1
    	case "gpt-3.5-turbo-0301":
    		tokensPerMessage = 4 // every message follows <|start|>{role/name}\n{content}<|end|>
    		tokensPerName = -1   // if there's a name, the role is omitted
    	default:
    		if strings.Contains(model, "gpt-3.5-turbo") {
    			log.Println("warning: gpt-3.5-turbo may update over time. Returning num tokens assuming gpt-3.5-turbo-0613.")
    			return NumTokensFromMessages(messages, "gpt-3.5-turbo-0613")
    		} else if strings.Contains(model, "gpt-4") {
    			log.Println("warning: gpt-4 may update over time. Returning num tokens assuming gpt-4-0613.")
    			return NumTokensFromMessages(messages, "gpt-4-0613")
    		} else {
    			err = fmt.Errorf("num_tokens_from_messages() is not implemented for model %s. See https://github.com/openai/openai-python/blob/main/chatml.md for information on how messages are converted to tokens.", model)
    			log.Println(err)
    			return
    		}
    	}
    
    	for _, message := range messages {
    		numTokens += tokensPerMessage
    		numTokens += len(tkm.Encode(message.Content, nil, nil))
    		numTokens += len(tkm.Encode(message.Role, nil, nil))
    		numTokens += len(tkm.Encode(message.Name, nil, nil))
    		if message.Name != "" {
    			numTokens += tokensPerName
    		}
    	}
    	numTokens += 3 // every reply is primed with <|start|>assistant<|message|>
    	return numTokens
    }
  7. Count tokens for Chat API messages

    main

    When working with Chat Completion APIs (like gpt-3.5-turbo or gpt-4), the token count includes metadata for roles, names, and message delimiters.

    Warning: The exact calculation method for messages may change as OpenAI updates their models. The following implementation is based on the OpenAI Cookbook (June 2023) and should be verified against official documentation for production use.

    package main
    
    import (
    	"fmt"
    	"log"
    	"strings"
    
    	"github.com/pkoukk/tiktoken-go"
    	"github.com/sashabaranov/go-openai"
    )
    
    // NumTokensFromMessages calculates the number of tokens used by a list of chat messages.
    func NumTokensFromMessages(messages []openai.ChatCompletionMessage, model string) (numTokens int) {
    	tkm, err := tiktoken.EncodingForModel(model)
    	if err != nil {
    		err = fmt.Errorf("encoding for model: %v", err)
    		log.Println(err)
    		return
    	}
    
    	var tokensPerMessage, tokensPerName int
    	switch model {
    	case "gpt-3.5-turbo-0613",
    		"gpt-3.5-turbo-16k-0613",
    		"gpt-4-0314",
    		"gpt-4-32k-0314",
    		"gpt-4-0613",
    		"gpt-4-32k-0613":
    		tokensPerMessage = 3
    		tokensPerName = 1
    	case "gpt-3.5-turbo-0301":
    		tokensPerMessage = 4
    		tokensPerName = -1
    	default:
    		if strings.Contains(model, "gpt-3.5-turbo") {
    			log.Println("warning: gpt-3.5-turbo may update over time. Returning num tokens assuming gpt-3.5-turbo-0613.")
    			return NumTokensFromMessages(messages, "gpt-3.5-turbo-0613")
    		} else if strings.Contains(model, "gpt-4") {
    			log.Println("warning: gpt-4 may update over time. Returning num tokens assuming gpt-4-0613.")
    			return NumTokensFromMessages(messages, "gpt-4-0613")
    		} else {
    			err = fmt.Errorf("num_tokens_from_messages() is not implemented for model %s. See https://github.com/openai/openai-python/blob/main/chatml.md for information on how messages are converted to tokens.", model)
    			log.Println(err)
    			return
    		}
    	}
    
    	for _, message := range messages {
    		numTokens += tokensPerMessage
    		numTokens += len(tkm.Encode(message.Content, nil, nil))
    		numTokens += len(tkm.Encode(message.Role, nil, nil))
    		numTokens += len(tkm.Encode(message.Name, nil, nil))
    		if message.Name != "" {
    			numTokens += tokensPerName
    		}
    	}
    	numTokens += 3 // every reply is primed with <|start|>assistant<|message|>
    	return numTokens
    }
  8. Get tokens by encoding name

    main

    Use tiktoken.GetEncoding(encoding) to retrieve an encoding by its specific name (e.g., cl100k_base). Once you have the encoding object, use .Encode(text, nil, nil) to convert text into tokens.

    package main
    
    import (
        "fmt"
        "github.com/pkoukk/tiktoken-go"
    )
    
    func main()  {
    	text := "Hello, world!"
    	encoding := "cl100k_base"
    
    	ke, err := tiktoken.GetEncoding(encoding)
    	if err != nil {
    		err = fmt.Errorf("getEncoding: %v", err)
    		return
    	}
    
    	// encode
    	token := tke.Encode(text, nil, nil)
    
    	// tokens
    	fmt.Println((token))
    	// num_tokens
    	fmt.Println(len(token))
    }
  9. Get tokens by model name

    main

    Use tiktoken.EncodingForModel(model) to retrieve an encoding instance associated with a specific OpenAI model name (e.g., gpt-3.5-turbo). This is often more convenient than manually looking up the encoding type.

    package main
    
    import (
    	"fmt"
    	"github.com/pkoukk/tiktoken-go"
    )
    
    func main() {
    	text := "Hello, world!"
    	encoding := "gpt-3.5-turbo"
    
    	tkm, err := tiktoken.EncodingForModel(encoding)
    	if err != nil {
    		fmt.Printf("getEncoding: %v\n", err)
    		return
    	}
    
    	// encode
    	token := tkm.Encode(text, nil, nil)
    
    	// tokens
    	fmt.Println(token)
    	// num_tokens
    	fmt.Println(len(token))
    }
  10. Configure the BPE cache directory

    main

    The defaultBpeLoader uses a cache to store downloaded BPE files. You can control where these files are stored by setting one of the following environment variables in your system:

    1. TIKTOKEN_CACHE_DIR: The primary directory used for caching.
    2. DATA_GYM_CACHE_DIR: A fallback directory if TIKTOKEN_CACHE_DIR is not set.

    If neither environment variable is provided, the loader defaults to using a directory named data-gym-cache within your system's temporary directory (os.TempDir()).

  11. Reference: Available Encodings

    main

    The following encoding names are supported and map to specific OpenAI models:

    | Encoding name           | OpenAI models                                                                                          |
    |-------------------------|--------------------------------------------------------------------------------------------------------|
    | `o200k_base`            | `gpt-4o`, `gpt-4.1`, `gpt-4.5`                                                                         |
    | `cl100k_base`           | `gpt-4`, `gpt-3.5-turbo`, `text-embedding-ada-002`, `text-embedding-3-small`, `text-embedding-3-large` |
    | `p50k_base`             | Codex models, `text-davinci-002`, `text-davinci-003`                                                    |
    | `r50k_base` (or `gpt2`) | GPT-3 models like `davinci`                                                                             |