Collection selection, and the config route the data model got wrong - #255
Conversation
resolve_collections with the spec's asymmetry: empty or absent searches everything, a valid selection narrows, and any unknown name widens back to everything with a WARNING naming it. Narrowing on a selection we do not understand costs the answer; widening costs latency, so every failure widens. **The data model's route does not exist.** It sends the selection through `RunnableConfig["configurable"]["collections"]` into the retriever. Measured against langchain-core 0.2.14: config is not passed to `_get_relevant_documents` as a kwarg, and `var_child_runnable_config` is unset inside a retriever run. A per-request attribute would race, because the retriever is built once and shared. Rebuilding per request discards the BM25 indexes. So the selection travels in a ContextVar, which asyncio copies per task. A test runs a narrow request and a wide one concurrently and asserts neither sees the other's selection -- that isolation is the whole reason for the choice, so it is pinned rather than assumed. A characterization test records today's behaviour, every collection searched, so that narrowing becomes a visible change rather than a silent one. Not yet wired: QueryIntent has no `collections` field and nothing sets the ContextVar, so behaviour is unchanged. That is the next task. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adversarial reviewThree things checked, all on the real code rather than the fakes the unit tests use. Does the ContextVar actually reach the retriever? This is the risk that would make the feature silently not work. LangChain may run a sync retriever in an executor, and
Are the two edits correct? They were made programmatically on two functions and only exercised by a fake class. Read both: indentation correct, Does the real
The caveat that goes with those numbersNarrowing drops the context from 50 documents to 10. The time saving is real and large, but fewer documents in context can cost answer quality, and that is precisely what this spec's before/after comparison exists to measure. I am not claiming routing is a win on the strength of a latency number. The timings are one sample each. The collection sets and document counts are deterministic and are the part worth trusting; the seconds are directional only. So the next task is not more plumbing — it is re-capturing against |
The machinery for spec 009 collection routing. Behaviour is unchanged — nothing sets a selection yet, so every question still searches every collection. Wiring the classifier is the next task.
resolve_collections, with the asymmetry that mattersEvery failure widens; none narrows. An unrecognised name means the classifier's prompt and the installed bundle disagree about what exists — narrowing on that disagreement drops a collection the question needed, while widening only costs latency. That is Principle IV, and it is why an empty list is the safe default for an omitted field, an older prompt and a parse failure alike.
The data model describes a route that does not exist
It sends the selection through
RunnableConfig["configurable"]["collections"]intoHybridRetriever.retrieve_documents. Measured against langchain-core 0.2.14, the pinned version:_get_relevant_documentsas a kwarg — a retriever declared with**kwargsreceives an empty dictvar_child_runnable_configis unset inside a retriever run, so the ambient config cannot be read eitherTwo alternatives rejected: a per-request attribute on the retriever races, because it is built once at startup and shared by every request; rebuilding a filtered retriever per request throws away the constructed BM25 indexes.
So the selection travels in a
ContextVar, which asyncio copies per task. That isolation is the entire justification, so it is pinned rather than assumed — a test runs a narrow request and a wide one concurrently and asserts neither sees the other's selection.Recorded in
research.md, with the data model's diagram left as the intent.Characterization first
A test records today's behaviour — every collection searched, always — so that narrowing becomes a visible change rather than a silent one. If it starts failing without a selection being set, something narrowed retrieval by accident, which is the failure that costs answers rather than time.
12 tests. ruff, mypy (134 files), full suite with no API keys set.
🤖 Generated with Claude Code