Build your own RAG pipeline
Assemble, generate, cite·35 min

Assemble the context

0 / 1
# grade this stage (from your course directory)
npx theinference test --stage assemble-context --watch

Build the packer. This is graded as an exact string — the assembled context block must be byte-for-byte the reference, because it's what the generator (and the cassette key) receives.

The format (pinned, exact)
[doc 1] (eiffel-tower#p1) <chunk text>

[doc 2] (leaning-tower#p1) <chunk text>

[doc 3] (great-wall#p1) <chunk text>

# blocks joined by a blank line; i is 1-indexed by packed order

What to build

Your task

Implement assembleContext

Dedup by chunk id → sort by score desc (ties → ascending id) → greedily pack to tokenBudget, counting the formatted candidate string each time → format. Return { contextString, usedChunkIds, tokenCount }. The assemble <q> <k> <budget> command prints the contextString.

src/assemble.ts
export function assembleContext(
  chunks: { chunkId: string; sourceId: string; text: string; score: number }[],
  tokenBudget: number,
): { contextString: string; usedChunkIds: string[]; tokenCount: number } {
  // dedup -> sort -> for each: block = `[doc ${i}] (${sourceId}) ${text}`
  //   candidate = blocks.join("\n\n") + block; stop if countTokens(candidate) > budget
}
How the grader runs it
./your_program.sh assemble 0 5 200
# stdout must equal expected.txt byte-for-byte
How it's graded

process mode, stdout_exact. The printed context string must equal expected.txt exactly. Every space, every blank line between blocks, the [doc i] (sourceId) prefix — all of it.

Budget counts the formatted string

The budget counts the formatted block — markers and source ids included, not just the chunk text. [doc 1] (eiffel-tower#p1) is real tokens. Count what you actually emit, including the \n\n separators between blocks.