codex-agent-3 / tests /test_context.py
m5ike's picture
upd
0f9caed
Raw
History Blame Contribute Delete
4.99 kB
"""Testy context.py — odhad tokenů, TokenStats, kompakce kontextu."""
from types import SimpleNamespace
from context import (TRUNCATION_MARK, TokenStats, compact_messages,
estimate_tokens)
def _msgs(*parts):
return list(parts)
def sys_msg(text="system prompt"):
return {"role": "system", "content": text}
def user_msg(text):
return {"role": "user", "content": text}
def asst_tool_call(name, call_id):
return {"role": "assistant", "content": "",
"tool_calls": [{"id": call_id, "type": "function",
"function": {"name": name, "arguments": "{}"}}]}
def tool_msg(call_id, content):
return {"role": "tool", "tool_call_id": call_id, "content": content}
# ---------------------------------------------------------------- odhad
def test_estimate_tokens_scales_with_length():
short = estimate_tokens([user_msg("abcd" * 10)])
long = estimate_tokens([user_msg("abcd" * 1000)])
assert long > short * 10
# ~4 znaky/token + režie
assert abs(estimate_tokens([user_msg("x" * 4000)]) - 1004) < 50
def test_estimate_counts_tool_calls():
plain = estimate_tokens([{"role": "assistant", "content": "hi"}])
with_calls = estimate_tokens([asst_tool_call("run_command", "c1")])
assert with_calls > plain
# ---------------------------------------------------------------- TokenStats
def test_token_stats_object_and_dict_usage():
stats = TokenStats()
stats.add("local", SimpleNamespace(prompt_tokens=100, completion_tokens=20))
stats.add("api", {"prompt_tokens": 50, "completion_tokens": 5})
stats.add("local", None) # cache hit / bez usage — nepočítá se
snap = stats.snapshot()
assert snap["calls"] == 2
assert snap["prompt_tokens"] == 150
assert snap["completion_tokens"] == 25
assert snap["total_tokens"] == 175
assert snap["last_context_tokens"] == 50
assert snap["by_source"]["local"]["calls"] == 1
assert snap["by_source"]["api"]["prompt_tokens"] == 50
def test_token_stats_saved():
stats = TokenStats()
stats.add_saved(500)
stats.add_saved(-10) # záporné se ignoruje
assert stats.snapshot()["saved_by_compaction_tokens"] == 500
# ---------------------------------------------------------------- kompakce
def _history(steps, result_chars=4000):
msgs = [sys_msg(), user_msg("Oprav bug v projektu")]
for i in range(steps):
msgs.append(asst_tool_call(f"tool_{i}", f"c{i}"))
msgs.append(tool_msg(f"c{i}", "x" * result_chars))
return msgs
def test_under_budget_untouched():
msgs = _history(2)
out, saved = compact_messages(msgs, budget_tokens=10**6)
assert out == msgs and saved == 0
def test_phase1_ages_old_tool_results():
msgs = _history(10)
out, saved = compact_messages(msgs, budget_tokens=8000,
keep_last_steps=2, aged_chars=500)
assert saved > 0
# staré tool výsledky zkráceny, poslední 2 bloky netknuté
aged = [m for m in out if m.get("role") == "tool" and TRUNCATION_MARK in m["content"]]
full = [m for m in out if m.get("role") == "tool" and TRUNCATION_MARK not in m["content"]]
assert len(aged) >= 1
assert len(full) >= 2
assert out[-1]["content"].endswith("x" * 100) # poslední výsledek celý
# vstup nemodifikován
assert TRUNCATION_MARK not in msgs[3]["content"]
def test_phase2_drops_oldest_blocks_with_summary():
msgs = _history(20)
out, saved = compact_messages(msgs, budget_tokens=4000,
keep_last_steps=3, aged_chars=300)
assert saved > 0
assert estimate_tokens(out) <= 4000
# kotva zachována
assert out[0]["role"] == "system"
assert out[1]["role"] == "user" and "Oprav bug" in out[1]["content"]
# souhrnná poznámka o vypuštění
summary = [m for m in out if "Kompakce kontextu" in str(m.get("content"))]
assert len(summary) == 1
assert "tool_0" in summary[0]["content"]
# struktura validní: každá tool zpráva má před sebou assistant s tool_calls
for i, m in enumerate(out):
if m.get("role") == "tool":
prev = next(p for p in reversed(out[:i])
if p.get("role") in ("assistant", "user", "system"))
assert prev.get("tool_calls"), "tool zpráva bez tool_calls předchůdce"
# poslední 3 bloky přežily
tool_names = [tc["function"]["name"] for m in out
for tc in (m.get("tool_calls") or [])]
assert tool_names[-3:] == ["tool_17", "tool_18", "tool_19"]
def test_best_effort_when_budget_impossible():
msgs = _history(20)
out, saved = compact_messages(msgs, budget_tokens=10,
keep_last_steps=3, aged_chars=300)
# nevejde se ani po maximální kompakci — vrací best effort, nikoli prázdno
assert saved > 0
assert any(m.get("role") == "user" for m in out)
assert estimate_tokens(out) < estimate_tokens(msgs)