Quickstart: pack a chat history under a token budget¶
This notebook walks through the smallest useful convopack example. It uses the zero-dependency approx tokenizer, so you can run every cell without any API keys.
Install if you haven't already:
pip install convopack
# or
uv add convopack
In [ ]:
Copied!
from convopack import Message, Packer, Recency, TextBlock
from convopack import Message, Packer, Recency, TextBlock
1. Build a conversation¶
Message is the internal, provider-agnostic shape. Provider adapters convert OpenAI/Anthropic/Gemini dicts to and from this shape.
In [ ]:
Copied!
history = [
Message(role="system", content=[TextBlock(text="You are a concise assistant.")]),
Message(role="user", content=[TextBlock(text="Tell me about Norway.")]),
Message(role="assistant", content=[TextBlock(text="Norway is a Nordic country.")]),
Message(role="user", content=[TextBlock(text="And Sweden?")]),
Message(role="assistant", content=[TextBlock(text="Sweden is a Scandinavian country.")]),
Message(role="user", content=[TextBlock(text="Denmark?")]),
]
len(history)
history = [
Message(role="system", content=[TextBlock(text="You are a concise assistant.")]),
Message(role="user", content=[TextBlock(text="Tell me about Norway.")]),
Message(role="assistant", content=[TextBlock(text="Norway is a Nordic country.")]),
Message(role="user", content=[TextBlock(text="And Sweden?")]),
Message(role="assistant", content=[TextBlock(text="Sweden is a Scandinavian country.")]),
Message(role="user", content=[TextBlock(text="Denmark?")]),
]
len(history)
2. Build a packer¶
The four knobs:
| Argument | Purpose |
|---|---|
budget |
Max tokens for the packed conversation. |
tokenizer |
"approx", "tiktoken:gpt-4o", "anthropic:...", or "huggingface:...". |
strategy |
How to decide which messages survive. |
pin |
Messages that must always be kept. |
In [ ]:
Copied!
packer = Packer(
budget=50,
tokenizer="approx",
strategy=Recency(),
pin=("system", "last_user"),
)
packer = Packer(
budget=50,
tokenizer="approx",
strategy=Recency(),
pin=("system", "last_user"),
)
3. Pack and inspect the result¶
pack() returns a PackResult carrying the kept messages, dropped messages, total token count, and the original budget.
In [ ]:
Copied!
result = packer.pack(history)
print(f"kept {len(result.kept)} of {len(history)} messages")
print(f"tokens: {result.token_count}/{result.budget}")
print(f"fits: {result.fits}")
for m in result.kept:
print(f" [{m.role}] {m.text()[:60]}")
result = packer.pack(history)
print(f"kept {len(result.kept)} of {len(history)} messages")
print(f"tokens: {result.token_count}/{result.budget}")
print(f"fits: {result.fits}")
for m in result.kept:
print(f" [{m.role}] {m.text()[:60]}")
Notice:
- The system prompt is kept (it was pinned).
- The most recent user turn (
"Denmark?") is kept (last_userwas pinned). - Older middle turns were dropped silently to fit the budget.
4. Round-trip through OpenAI shape¶
Most apps already store conversations as OpenAI-style dicts. Use pack_openai to skip the manual conversion:
In [ ]:
Copied!
raw_history = [
{"role": "system", "content": "You are concise."},
{"role": "user", "content": "What is rain?"},
{"role": "assistant", "content": "Water falling from clouds."},
{"role": "user", "content": "And snow?"},
]
packed_raw = packer.pack_openai(raw_history)
for m in packed_raw:
print(m)
raw_history = [
{"role": "system", "content": "You are concise."},
{"role": "user", "content": "What is rain?"},
{"role": "assistant", "content": "Water falling from clouds."},
{"role": "user", "content": "And snow?"},
]
packed_raw = packer.pack_openai(raw_history)
for m in packed_raw:
print(m)
5. Inspect what would happen for any budget¶
Useful for budget tuning or before-call sanity checks:
In [ ]:
Copied!
for budget in (20, 40, 80, 200):
p = Packer(budget=budget, tokenizer="approx", strategy=Recency(), pin=("system",))
r = p.pack(history)
print(f"budget={budget:>3} kept={len(r.kept):>2} tokens={r.token_count:>3}/{r.budget}")
for budget in (20, 40, 80, 200):
p = Packer(budget=budget, tokenizer="approx", strategy=Recency(), pin=("system",))
r = p.pack(history)
print(f"budget={budget:>3} kept={len(r.kept):>2} tokens={r.token_count:>3}/{r.budget}")
Next¶
02_tool_pair_atomicity.ipynb— the killer feature: keeptool_use/tool_resultpairs together.03_strategies.ipynb— compareRecency,FirstFit,SummaryEvict,Importance, andSemanticDedupside-by-side.04_prompt_caching.ipynb— get 90% cost reduction on Anthropic and OpenAI prefix caching.