acb / tests /test_token_optimizer.py
Kagan Tek
merge
79d4fd5
Raw
History Blame Contribute Delete
18.5 kB
import pytest
from unittest.mock import patch, MagicMock
from src.utils.token_optimizer import (
TokenOptimizer,
_ApproximateEncoder,
get_token_optimizer,
reset_token_optimizer,
)
class TestTokenOptimizerInit:
def test_default_max_tokens(self):
optimizer = TokenOptimizer()
assert optimizer._max_tokens == 4000
def test_custom_max_tokens(self):
optimizer = TokenOptimizer(max_tokens=2000)
assert optimizer._max_tokens == 2000
def test_encoder_lazy_loaded(self):
optimizer = TokenOptimizer()
assert optimizer._encoder is None
def test_encoder_loaded_on_access(self):
optimizer = TokenOptimizer()
enc = optimizer.encoder
assert enc is not None
class TestCountTokens:
def test_empty_string_returns_zero(self):
optimizer = TokenOptimizer()
assert optimizer.count_tokens("") == 0
def test_none_returns_zero(self):
optimizer = TokenOptimizer()
assert optimizer.count_tokens(None) == 0
def test_simple_text_returns_positive(self):
optimizer = TokenOptimizer()
count = optimizer.count_tokens("Hello world this is a test")
assert count > 0
def test_longer_text_more_tokens(self):
optimizer = TokenOptimizer()
short = optimizer.count_tokens("hello")
long = optimizer.count_tokens("hello world this is a much longer text with many words")
assert long > short
def test_consistent_results(self):
optimizer = TokenOptimizer()
text = "consistent token counting"
assert optimizer.count_tokens(text) == optimizer.count_tokens(text)
def test_turkish_text(self):
optimizer = TokenOptimizer()
count = optimizer.count_tokens("Satışları göster ve toplam tutarı hesapla")
assert count > 0
class TestTruncateChatHistory:
def test_empty_messages_returns_empty(self):
optimizer = TokenOptimizer()
result, tokens = optimizer.truncate_chat_history([])
assert result == []
assert tokens == 0
def test_short_history_unchanged(self):
optimizer = TokenOptimizer()
messages = [
{"role": "user", "content": "hello"},
{"role": "assistant", "content": "hi there"},
]
result, tokens = optimizer.truncate_chat_history(messages, max_tokens=500)
assert len(result) == 2
assert tokens > 0
def test_truncation_keeps_recent_messages(self):
optimizer = TokenOptimizer()
messages = [
{"role": "user", "content": "old message " * 200},
{"role": "assistant", "content": "old reply " * 200},
{"role": "user", "content": "recent question"},
{"role": "assistant", "content": "recent answer"},
]
result, tokens = optimizer.truncate_chat_history(messages, max_tokens=50)
assert any("recent" in m.get("content", "") for m in result)
def test_never_drops_last_message(self):
optimizer = TokenOptimizer()
messages = [
{"role": "user", "content": "very " * 500},
]
result, tokens = optimizer.truncate_chat_history(messages, max_tokens=10)
assert len(result) >= 1
def test_preserves_message_pairs(self):
optimizer = TokenOptimizer()
messages = [
{"role": "user", "content": "q1"},
{"role": "assistant", "content": "a1"},
{"role": "user", "content": "q2"},
{"role": "assistant", "content": "a2"},
]
result, _ = optimizer.truncate_chat_history(messages, max_tokens=500)
roles = [m["role"] for m in result]
if len(roles) >= 2:
assert roles[0] == "user" or (roles[0] == "assistant" and len(result) == len(messages))
def test_respects_token_budget(self):
optimizer = TokenOptimizer()
messages = [{"role": "user", "content": f"message number {i} " * 20} for i in range(20)]
result, tokens = optimizer.truncate_chat_history(messages, max_tokens=100)
assert tokens <= 200
class TestTruncateContext:
def test_empty_context(self):
optimizer = TokenOptimizer()
result, tokens = optimizer.truncate_context("")
assert result == ""
assert tokens == 0
def test_short_context_unchanged(self):
optimizer = TokenOptimizer()
text = "Short context text."
result, tokens = optimizer.truncate_context(text, max_tokens=500)
assert result == text
def test_long_context_truncated(self):
optimizer = TokenOptimizer()
text = "This is a sentence. " * 500
result, tokens = optimizer.truncate_context(text, max_tokens=50)
assert tokens <= 60
assert result.endswith("...")
def test_preserve_start_true(self):
optimizer = TokenOptimizer()
text = "First sentence. Middle sentence. Last sentence."
result, _ = optimizer.truncate_context(text, max_tokens=10, preserve_start=True)
assert "First" in result
def test_preserve_start_false(self):
optimizer = TokenOptimizer()
text = "First sentence here. " * 10 + "Last sentence here."
result, _ = optimizer.truncate_context(text, max_tokens=15, preserve_start=False)
assert result.startswith("...")
def test_single_long_sentence_hard_truncated(self):
optimizer = TokenOptimizer()
text = "word " * 1000
result, tokens = optimizer.truncate_context(text, max_tokens=20)
assert tokens <= 30
class TestOptimizeSchemaContext:
def test_empty_schema(self):
optimizer = TokenOptimizer()
assert optimizer.optimize_schema_context("") == ""
def test_short_schema_unchanged(self):
optimizer = TokenOptimizer()
schema = "Table: users\nColumns: id, name"
result = optimizer.optimize_schema_context(schema, max_tokens=500)
assert result == schema
def test_prioritizes_relevant_tables(self):
optimizer = TokenOptimizer()
schema = """Table: customers
Columns: id, name, city
Table: products
Columns: id, name, price
Table: sales
Columns: id, product_id, amount"""
result = optimizer.optimize_schema_context(schema, relevant_tables=["sales"], max_tokens=50)
assert "sales" in result
def test_truncates_when_over_budget(self):
optimizer = TokenOptimizer()
schema = "\n\n".join([f"Table: table_{i}\nColumns: " + ", ".join([f"col_{j}" for j in range(20)]) for i in range(50)])
result = optimizer.optimize_schema_context(schema, max_tokens=100)
assert optimizer.count_tokens(result) <= 120
def test_no_relevant_tables_keeps_order(self):
optimizer = TokenOptimizer()
schema = "Table: alpha\nColumns: a\n\nTable: beta\nColumns: b"
result = optimizer.optimize_schema_context(schema, max_tokens=500)
assert "alpha" in result
assert "beta" in result
class TestParseTableBlocks:
def test_parses_multiple_tables(self):
optimizer = TokenOptimizer()
schema = "Table: users\nColumns: id, name\n\nTable: orders\nColumns: id, user_id"
blocks = optimizer._parse_table_blocks(schema)
assert len(blocks) == 2
assert blocks[0][0] == "users"
assert blocks[1][0] == "orders"
def test_empty_schema_returns_empty(self):
optimizer = TokenOptimizer()
assert optimizer._parse_table_blocks("") == []
def test_single_table(self):
optimizer = TokenOptimizer()
schema = "Table: users\nColumns: id, name"
blocks = optimizer._parse_table_blocks(schema)
assert len(blocks) == 1
def test_no_table_prefix_returns_empty(self):
optimizer = TokenOptimizer()
assert optimizer._parse_table_blocks("just some text") == []
class TestSelectFewShotExamples:
def test_empty_examples(self):
optimizer = TokenOptimizer()
assert optimizer.select_few_shot_examples([], "query") == []
def test_selects_relevant_examples(self):
optimizer = TokenOptimizer()
examples = [
{"query": "show total sales amount", "sql": "SELECT SUM(amount) FROM sales"},
{"query": "list all customers", "sql": "SELECT * FROM customers"},
{"query": "calculate average sales price", "sql": "SELECT AVG(price) FROM sales"},
]
result = optimizer.select_few_shot_examples(examples, "total sales", max_tokens=500)
assert len(result) > 0
assert any("sales" in ex.get("query", "") and "total" in ex.get("query", "") for ex in result)
def test_respects_token_budget(self):
optimizer = TokenOptimizer()
examples = [{"query": f"example {i} " * 50, "sql": f"SELECT {i}"} for i in range(20)]
result = optimizer.select_few_shot_examples(examples, "example 1", max_tokens=100)
total = sum(optimizer.count_tokens(optimizer._example_text(ex)) for ex in result)
assert total <= 200
def test_ranks_by_keyword_similarity(self):
optimizer = TokenOptimizer()
examples = [
{"query": "customer list", "sql": "SELECT * FROM customers"},
{"query": "sales report total", "sql": "SELECT SUM(total) FROM sales"},
]
result = optimizer.select_few_shot_examples(examples, "sales total report", max_tokens=500)
assert result[0]["query"] == "sales report total"
class TestKeywordSimilarity:
def test_identical_text(self):
optimizer = TokenOptimizer()
assert optimizer._keyword_similarity("hello world", "hello world") == 1.0
def test_no_overlap(self):
optimizer = TokenOptimizer()
assert optimizer._keyword_similarity("hello", "goodbye") == 0.0
def test_partial_overlap(self):
optimizer = TokenOptimizer()
score = optimizer._keyword_similarity("hello world", "hello there")
assert 0 < score < 1
def test_empty_query(self):
optimizer = TokenOptimizer()
assert optimizer._keyword_similarity("", "hello") == 0.0
def test_case_insensitive(self):
optimizer = TokenOptimizer()
assert optimizer._keyword_similarity("Hello", "hello") == 1.0
class TestOptimizeForLlmCall:
def test_all_components_present(self):
optimizer = TokenOptimizer()
components = {
"query": "what are total sales?",
"system_prompt": "you are a sql assistant",
"schema": "Table: sales\nColumns: id, amount",
"few_shots": "Example: SELECT COUNT(*) FROM sales",
"chat_history": "user: show sales\nassistant: here are sales",
}
result = optimizer.optimize_for_llm_call(components, total_budget=3000)
assert "query" in result
assert "system_prompt" in result
assert "schema" in result
assert "few_shots" in result
assert "chat_history" in result
def test_query_preserved(self):
optimizer = TokenOptimizer()
components = {"query": "full query text", "system_prompt": "sp", "schema": "s" * 5000}
result = optimizer.optimize_for_llm_call(components, total_budget=100)
assert result["query"] == "full query text"
def test_system_prompt_preserved(self):
optimizer = TokenOptimizer()
components = {"query": "q", "system_prompt": "system prompt text", "schema": "s" * 5000}
result = optimizer.optimize_for_llm_call(components, total_budget=100)
assert result["system_prompt"] == "system prompt text"
def test_empty_components(self):
optimizer = TokenOptimizer()
result = optimizer.optimize_for_llm_call({}, total_budget=1000)
assert result["query"] == ""
assert result["system_prompt"] == ""
def test_large_schema_truncated(self):
optimizer = TokenOptimizer()
components = {
"query": "q",
"system_prompt": "sp",
"schema": "Table: big\nColumns: " + ", ".join([f"col_{i}" for i in range(500)]),
}
result = optimizer.optimize_for_llm_call(components, total_budget=100)
assert optimizer.count_tokens(result["schema"]) < optimizer.count_tokens(components["schema"])
class TestGetOptimizationStats:
def test_stats_structure(self):
optimizer = TokenOptimizer()
original = {"query": "hello world", "schema": "big " * 100}
optimized = {"query": "hello world", "schema": "big"}
stats = optimizer.get_optimization_stats(original, optimized)
assert "original_tokens" in stats
assert "optimized_tokens" in stats
assert "tokens_saved" in stats
assert "reduction_ratio" in stats
def test_reduction_calculated(self):
optimizer = TokenOptimizer()
original = {"text": "word " * 100}
optimized = {"text": "word " * 10}
stats = optimizer.get_optimization_stats(original, optimized)
assert stats["tokens_saved"] > 0
assert stats["reduction_ratio"] > 0
def test_no_reduction(self):
optimizer = TokenOptimizer()
data = {"text": "same"}
stats = optimizer.get_optimization_stats(data, data)
assert stats["tokens_saved"] == 0
assert stats["reduction_ratio"] == 0.0
def test_empty_input(self):
optimizer = TokenOptimizer()
stats = optimizer.get_optimization_stats({}, {})
assert stats["original_tokens"] == 0
class TestApproximateEncoder:
def test_encode_returns_list(self):
enc = _ApproximateEncoder()
result = enc.encode("hello world")
assert isinstance(result, list)
def test_decode_returns_string(self):
enc = _ApproximateEncoder()
result = enc.decode([1, 2, 3])
assert isinstance(result, str)
def test_approximate_proportional(self):
enc = _ApproximateEncoder()
short = len(enc.encode("hi"))
long = len(enc.encode("hello world this is longer"))
assert long > short
class TestSingleton:
def setup_method(self):
reset_token_optimizer()
def teardown_method(self):
reset_token_optimizer()
def test_get_returns_instance(self):
optimizer = get_token_optimizer()
assert isinstance(optimizer, TokenOptimizer)
def test_get_returns_same_instance(self):
first = get_token_optimizer()
second = get_token_optimizer()
assert first is second
def test_reset_clears_instance(self):
first = get_token_optimizer()
reset_token_optimizer()
second = get_token_optimizer()
assert first is not second
def test_get_with_custom_max_tokens(self):
optimizer = get_token_optimizer(max_tokens=2000)
assert optimizer._max_tokens == 2000
class TestExampleText:
def test_extracts_query_and_sql(self):
optimizer = TokenOptimizer()
ex = {"query": "show sales", "sql": "SELECT * FROM sales"}
text = optimizer._example_text(ex)
assert "show sales" in text
assert "SELECT * FROM sales" in text
def test_handles_missing_keys(self):
optimizer = TokenOptimizer()
ex = {"other": "data"}
text = optimizer._example_text(ex)
assert len(text) > 0
def test_empty_example(self):
optimizer = TokenOptimizer()
ex = {}
text = optimizer._example_text(ex)
assert isinstance(text, str)
class TestEdgeCases:
def test_count_tokens_with_special_characters(self):
optimizer = TokenOptimizer()
count = optimizer.count_tokens("!@#$%^&*()_+{}|:<>?")
assert count > 0
def test_truncate_context_single_word(self):
optimizer = TokenOptimizer()
result, _ = optimizer.truncate_context("word", max_tokens=500)
assert result == "word"
def test_schema_with_no_table_prefix(self):
optimizer = TokenOptimizer()
result = optimizer.optimize_schema_context("just plain text", max_tokens=500)
assert result == "just plain text"
def test_few_shot_with_single_example(self):
optimizer = TokenOptimizer()
examples = [{"query": "test", "sql": "SELECT 1"}]
result = optimizer.select_few_shot_examples(examples, "test", max_tokens=500)
assert len(result) == 1
def test_optimize_llm_call_missing_optional_components(self):
optimizer = TokenOptimizer()
components = {"query": "hello"}
result = optimizer.optimize_for_llm_call(components, total_budget=1000)
assert result["schema"] == ""
assert result["few_shots"] == ""
assert result["chat_history"] == ""
def test_truncate_history_with_empty_content(self):
optimizer = TokenOptimizer()
messages = [{"role": "user", "content": ""}, {"role": "assistant", "content": "reply"}]
result, _ = optimizer.truncate_chat_history(messages, max_tokens=500)
assert len(result) == 2
def test_preserve_message_pairs_no_orphan_assistant(self):
optimizer = TokenOptimizer()
messages = [
{"role": "user", "content": "q1 " * 200},
{"role": "assistant", "content": "a1 " * 200},
{"role": "user", "content": "q2"},
{"role": "assistant", "content": "a2"},
]
result, _ = optimizer.truncate_chat_history(messages, max_tokens=20)
if result and result[0].get("role") == "assistant":
idx = messages.index(result[0])
assert idx > 0
@patch("src.utils.token_optimizer.TokenOptimizer.encoder", new_callable=lambda: property(lambda self: _ApproximateEncoder()))
def test_fallback_encoder_works(self, _):
optimizer = TokenOptimizer()
optimizer._encoder = _ApproximateEncoder()
count = optimizer.count_tokens("hello world test")
assert count >= 0
class TestConfigIntegration:
def test_config_constants_available(self):
from utils.token_optimizer import NL2SQL_MAX_CONTEXT_TOKENS, CHAT_HISTORY_MAX_TOKENS, SCHEMA_CONTEXT_MAX_TOKENS, FEW_SHOT_MAX_TOKENS
assert NL2SQL_MAX_CONTEXT_TOKENS > 0
assert CHAT_HISTORY_MAX_TOKENS > 0
assert SCHEMA_CONTEXT_MAX_TOKENS > 0
assert FEW_SHOT_MAX_TOKENS > 0
def test_optimizer_uses_config_defaults(self):
optimizer = TokenOptimizer()
messages = [{"role": "user", "content": "test"}]
result, _ = optimizer.truncate_chat_history(messages)
assert len(result) == 1