all repos — llm_aggregator @ 29a4710e981512a82abba77ec8d7b43e413453e7

A CLI tool to aggregate RSS feeds and summarise them with LLMs

internal/tokeniser/tokeniser.go (view raw)

  1package tokeniser
  2
  3import (
  4	"fmt"
  5	"strings"
  6	"sync"
  7
  8	"github.com/pkoukk/tiktoken-go"
  9)
 10
 11// Encoding names for different model families
 12const (
 13	EncodingCl100kBase = "cl100k_base" // GPT-4, GPT-3.5-turbo, embeddings
 14	EncodingO200kBase  = "o200k_base"  // GPT-4o, GPT-4.1, GPT-4.5
 15	EncodingP50kBase   = "p50k_base"   // Codex models, davinci-002, davinci-003
 16	EncodingR50kBase   = "r50k_base"   // GPT-3 models
 17)
 18
 19// tokenizerCache caches encodings to avoid repeated initialization overhead
 20type tokenizerCache struct {
 21	mu        sync.RWMutex
 22	encodings map[string]*tiktoken.Tiktoken
 23}
 24
 25var cache = &tokenizerCache{encodings: make(map[string]*tiktoken.Tiktoken)}
 26
 27// GetEncoding returns a Tiktoken encoding for the given encoding name.
 28func GetEncoding(encodingName string) (*tiktoken.Tiktoken, error) {
 29	// Check cache first
 30	cache.mu.RLock()
 31	if enc, ok := cache.encodings[encodingName]; ok {
 32		cache.mu.RUnlock()
 33		return enc, nil
 34	}
 35	cache.mu.RUnlock()
 36
 37	// Initialise encoding
 38	enc, err := tiktoken.GetEncoding(encodingName)
 39	if err != nil {
 40		return nil, fmt.Errorf("failed to get encoding %s: %w", encodingName, err)
 41	}
 42
 43	// Cache it
 44	cache.mu.Lock()
 45	cache.encodings[encodingName] = enc
 46	cache.mu.Unlock()
 47
 48	return enc, nil
 49}
 50
 51// EncodingForModel returns the appropriate encoding name for a given model.
 52func EncodingForModel(model string) (string, error) {
 53	model = strings.ToLower(model)
 54
 55	// GPT-4o and newer use o200k_base
 56	if strings.Contains(model, "gpt-4o") ||
 57		strings.Contains(model, "gpt-4.1") ||
 58		strings.Contains(model, "gpt-4.5") ||
 59		strings.HasPrefix(model, "gpt-4o") ||
 60		strings.HasPrefix(model, "gpt-4.1") ||
 61		strings.HasPrefix(model, "gpt-4.5") {
 62		return EncodingO200kBase, nil
 63	}
 64
 65	// GPT-4 family uses cl100k_base
 66	if strings.HasPrefix(model, "gpt-4") {
 67		return EncodingCl100kBase, nil
 68	}
 69
 70	// GPT-3.5-turbo uses cl100k_base
 71	if strings.Contains(model, "gpt-3.5-turbo") {
 72		return EncodingCl100kBase, nil
 73	}
 74
 75	// Embedding models use cl100k_base
 76	if strings.Contains(model, "text-embedding") {
 77		return EncodingCl100kBase, nil
 78	}
 79
 80	// Codex and davinci models use p50k_base
 81	if strings.Contains(model, "code-") ||
 82		strings.Contains(model, "davinci") {
 83		return EncodingP50kBase, nil
 84	}
 85
 86	// Default to cl100k_base (most common for modern models)
 87	return EncodingCl100kBase, nil
 88}
 89
 90// CountTokens counts the tokens in a text string using the appropriate encoding.
 91func CountTokens(text string, model string) (int, error) {
 92	encodingName, err := EncodingForModel(model)
 93	if err != nil {
 94		return 0, err
 95	}
 96
 97	enc, err := GetEncoding(encodingName)
 98	if err != nil {
 99		return 0, fmt.Errorf("failed to get encoding: %w", err)
100	}
101
102	tokens := enc.Encode(text, nil, nil)
103	return len(tokens), nil
104}