Files
passepartout/lisp/tokenizer.lisp
Amr Gharbeia c86d079418
Some checks failed
Deploy (Gitea) / deploy (push) Failing after 2s
passepartout: v0.5.0 — File Reorganization & Token Economics
File Reorganization:
- Extracted core-context → symbolic-awareness (skill)
- Extracted heartbeat → symbolic-events (skill)
- Relocated 6 utility fragments, renamed 23 files, deleted system-model.lisp
- Renamed gateway-* → channel-*, split gateway-messaging → 4 channel-* files
- Renamed defskill/defpackage names to match new file prefixes
- Deleted gateway-messaging.org/.lisp, removed core-context filter
- Documented self-repair criterion, added AGENTS.md core boundary rule

Token Economics (v0.5.0, skills not core):
- tokenizer.lisp: count-tokens, model-token-ratio, token-cost, provider-token-cost (11 tests)
- cost-tracker.lisp: cost-track-call, cost-session-total, cost-by-provider (6 tests)
- token-economics.lisp: prompt-prefix-cached, context-assemble-cached,
  enforce-token-budget with CONTEXT_MAX_TOKENS env var (9 tests)

Bug Fixes:
- Fixed DeepSeek 400 (removed malformed tools from cascade)
- Fixed UNDEFINED-FUNCTION crash (fboundp guards in think())
- Fixed gate-trace duplication (setf replaces list* in cognitive-verify)
- Tightened dexador connect-timeout 10s→5s

Test suite: 116/116 (100%)
2026-05-08 08:36:41 -04:00

147 lines
5.5 KiB
Common Lisp
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
(in-package :passepartout)
(defparameter *model-token-ratios*
'((:gpt-4o-mini . 4.0)
(:gpt-4o . 4.0)
(:gpt-3.5-turbo . 4.0)
(:claude-3-5-sonnet . 4.5)
(:claude-3-opus . 4.5)
(:claude-3-haiku . 4.5)
(:deepseek-chat . 4.0)
(:deepseek-reasoner . 4.0)
(:llama-3.1-70b . 3.5)
(:llama-3.1-405b . 3.5)
(:gemini-2.0-flash . 4.0)
(:gemini-1.5-pro . 4.0)
(:openrouter/auto . 4.0))
"Estimated characters per token for each model family.")
(defparameter *default-token-ratio* 4.0
"Fallback characters-per-token ratio when model is unknown.")
(defun model-token-ratio (model-keyword)
"Returns the estimated characters-per-token for MODEL-KEYWORD.
Falls back to *DEFAULT-TOKEN-RATIO* for unknown models."
(or (cdr (assoc model-keyword *model-token-ratios*))
*default-token-ratio*))
(defun count-tokens (text &key model)
"Returns the estimated token count for TEXT.
Uses character-count / ratio heuristic calibrated per model family.
MODEL is a keyword identifying the model (e.g. :gpt-4o-mini)."
(let ((clean (if (stringp text) text (format nil "~a" text))))
(ceiling (length clean) (model-token-ratio model))))
(defparameter *token-prices*
'((:gpt-4o-mini . 0.15) ; $0.15/1M input tokens
(:gpt-4o . 2.50) ; $2.50/1M input tokens
(:gpt-3.5-turbo . 0.50) ; $0.50/1M input tokens
(:claude-3-5-sonnet . 3.00) ; $3.00/1M input tokens
(:claude-3-opus . 15.00) ; $15.00/1M input tokens
(:claude-3-haiku . 0.25) ; $0.25/1M input tokens
(:deepseek-chat . 0.27) ; $0.27/1M input tokens
(:deepseek-reasoner . 0.55) ; $0.55/1M input tokens
(:llama-3.1-70b . 0.59) ; Groq: $0.59/1M
(:llama-3.1-405b . 1.30) ; NVIDIA NIM: ~$1.30/1M
(:gemini-2.0-flash . 0.10) ; $0.10/1M input
(:gemini-1.5-pro . 1.25)) ; $1.25/1M input
"Provider pricing in USD per 1M input tokens.
Prices sourced as of 2026-05. Output tokens cost 2-5× more;
we bill at input rates as a conservative estimate.")
(defun token-cost (model token-count)
"Returns the estimated cost in USD for TOKEN-COUNT tokens at MODEL's price.
Returns 0.0 for unknown models."
(let ((price-per-1m (or (cdr (assoc model *token-prices*)) 0.0)))
(* (/ price-per-1m 1000000.0) token-count)))
(defparameter *provider-default-models*
'((:deepseek . :deepseek-chat)
(:openai . :gpt-4o-mini)
(:anthropic . :claude-3-5-sonnet)
(:groq . :llama-3.1-70b)
(:gemini . :gemini-2.0-flash)
(:nvidia . :llama-3.1-405b)
(:openrouter . :openrouter/auto))
"Maps provider keywords to their default model families for cost tracking.")
(defun provider-token-cost (provider token-count)
"Returns the estimated cost in USD for a given PROVIDER and TOKEN-COUNT.
Uses the provider's default model for pricing."
(let ((model (cdr (assoc provider *provider-default-models*))))
(if model
(token-cost model token-count)
0.0)))
(eval-when (:compile-toplevel :load-toplevel :execute)
(ql:quickload :fiveam :silent t))
(defpackage :passepartout-tokenizer-tests
(:use :cl :fiveam :passepartout)
(:export #:tokenizer-suite))
(in-package :passepartout-tokenizer-tests)
(def-suite tokenizer-suite :description "Token counting and cost estimation")
(in-suite tokenizer-suite)
(test test-count-tokens-default
"Contract 1: count-tokens returns non-zero for a non-empty string."
(let ((count (count-tokens "hello world")))
(is (> count 0))
(is (integerp count))))
(test test-count-tokens-known-model
"Contract 1: count-tokens with a known model returns a count."
(let ((count (count-tokens "hello world" :model :gpt-4o-mini)))
(is (> count 0))
(is (integerp count))))
(test test-count-tokens-unknown-model
"Contract 1: count-tokens with an unknown model falls back to default."
(let ((count (count-tokens "hello world" :model :unknown-model-xyz)))
(is (> count 0))
(is (integerp count))))
(test test-count-tokens-empty
"Contract 1: count-tokens on empty string returns 0."
(let ((count (count-tokens "")))
(is (= 0 count))))
(test test-model-token-ratio-known
"Contract 2: known model returns correct ratio."
(is (= 4.0 (model-token-ratio :gpt-4o-mini)))
(is (= 4.5 (model-token-ratio :claude-3-5-sonnet)))
(is (= 3.5 (model-token-ratio :llama-3.1-70b))))
(test test-model-token-ratio-unknown
"Contract 2: unknown model returns default ratio."
(is (= 4.0 (model-token-ratio :unknown-model-abc))))
(test test-token-cost-known
"Contract 3: token-cost returns a number for known model."
(let ((cost (token-cost :gpt-4o-mini 1000)))
(is (numberp cost))
(is (> cost 0.0))))
(test test-token-cost-unknown
"Contract 3: token-cost returns 0.0 for unknown model."
(is (= 0.0 (token-cost :no-such-model 1000))))
(test test-provider-token-cost
"Contract: provider-token-cost maps provider to model price."
(let ((cost (provider-token-cost :deepseek 1000)))
(is (numberp cost))
(is (> cost 0.0))))
(test test-count-tokens-ratio-sensitivity
"Contract 1: longer text produces proportionally more tokens."
(let ((short (count-tokens "hi" :model :gpt-4o-mini))
(long (count-tokens "this is a much longer piece of text with many words in it" :model :gpt-4o-mini)))
(is (> long short))))
(test test-count-tokens-non-string
"Contract 1: non-string values are coerced and counted."
(let ((count (count-tokens 12345)))
(is (> count 0))))