Load a Support Chat Transcript by Pages, Then Trim to a Token Budget
SierraSoftware Engineer · Technical ScreenLast updated
Build the transcript-loading layer for a customer support chat agent: assemble a full conversation from a paginated backend into one ordered list, then trim it down whenever it grows past a token budget by dropping the oldest turns first.
You're building the part of a support chat agent that manages its view of the conversation before handing it to a language model. Two pieces of this are covered here: loading a transcript through a paginated API, and trimming it back down once it grows past a token budget. (The exercise continued into further parts not covered by this practice question.)
Assume this interface:
from dataclasses import dataclass
from typing import Callable, Optional
@dataclass(frozen=True)
class Turn:
id: str
speaker: str # e.g. "customer" or "agent"
text: str
token_count: int # precomputed token count for this turn
# fetch_page(cursor) returns one page of turns plus the cursor for the next
# page, or None once there are no more pages.
FetchPage = Callable[[Optional[str]], tuple[list[Turn], Optional[str]]]
Constraints and Clarifications
- Each turn's token count is already computed and given; you never tokenize text yourself.
- The token budget passed to the trimming step is always a positive whole number supplied by whoever calls it.
- Turns within a page, and pages themselves, are returned in chronological order.
Clarifying Questions
- Could
fetch_pageever return the same cursor twice, or otherwise misbehave, and if so how should the loader react? - Should turns be assembled into one list eagerly, or is a lazy, page-at-a-time generator preferred so a caller can stop early?
- When trimming, is a total exactly equal to the budget acceptable, or must it be strictly under?
- If a single turn's token count alone exceeds the budget, should it be kept anyway (as the only context available) or dropped?
Part 1 — Pagination
Implement:
def load_transcript(fetch_page: FetchPage) -> list[Turn]:
"""Call fetch_page repeatedly, starting with cursor=None, until it
reports no further pages, and return every turn seen, in order."""
Your stopping condition is a None cursor, not an empty page — a page can legitimately come back empty while more pages still follow. Also decide what your loop does if a cursor it already used comes back again.
What This Part Should Cover
- A loop with a termination condition based on the cursor, not page emptiness
- Correct ordering of turns across pages and within a page
- A guard against a cursor repeating, so a misbehaving source can't loop forever
Part 2 — Trimming to a token budget
Implement:
def trim_to_budget(turns: list[Turn], token_budget: int) -> list[Turn]:
"""Return the longest chronological suffix of turns whose token counts
sum to at most token_budget, dropping the oldest turns first."""
Recomputing the sum after every removal makes this quadratic for no reason. Think about how a running total should change as you drop turns from the front one at a time.
What This Part Should Cover
- Correctly dropping from the oldest end first, keeping the most recent context
- A running total maintained incrementally rather than resummed after each drop
- What happens when even the single most recent turn exceeds the budget on its own
Hints
Hint 1
Loop on the cursor being non-None, not on the page being non-empty — those are different conditions and only one of them is the real stopping signal.
Hint 2
Keep a set of cursors you've already consumed so a source that repeats a cursor can be caught instead of looping forever.
Hint 3
For trimming, maintain a running token total and pop from the front of a deque while the total exceeds budget, updating the total by subtraction rather than re-summing.
Hint 4
Check what trim_to_budget should return when the budget is smaller than even the newest single turn's token count.
Follow-up questions
- How would you turn load_transcript into a lazy generator that a caller could stop consuming early, and what would that change about the pagination guard?
- If turns could be edited or deleted after being fetched, how would you keep a cached transcript consistent across repeated calls?
- How would you change trim_to_budget if certain turns (like a leading system prompt) must never be dropped regardless of budget?
- How would you support fetching pages concurrently to speed up loading a very long transcript, while still assembling turns in the correct order?
Solution walkthrough is for members
Get the full walkthrough with reference code and complexity analysis, plus Kitty Coach at 300 messages a day. Hints, the code runner and community answers stay free.