Answer a question in English against a corpus - #980
Open
skearnes wants to merge 2 commits into
Open
Conversation
A {"compound": ...} value is resolved by whatever resolver the Corpus
was given, and the default asks PubChem, which returns pyridine as
C1=CC=NC=C1. The projection stores what RDKit canonicalizes, c1ccncc1.
Comparing those as strings matches nothing, and a query for pyridine as
a solvent answered with zero rows over a corpus holding 24,930 of them
-- wrong, and wrong in the way that looks like an answer.
The resolved SMILES is canonicalized before it is bound, once per name
per search. A name resolving to something RDKit cannot parse is left
alone rather than swallowed, since that is the resolver's fault to
report. Structure predicates never had this problem: they go through
RDKit rather than through string equality.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
ord_schema.search.nl turns a question into a Query, runs it, and says what came back. A model cannot be constrained to emit a valid Query -- the grammar is recursive, and structured outputs and strict tools share a validator that refuses circular references and then refuses what is left, once the recursion is removed, as too large -- so translation is generation checked afterwards. Three consequences, each measured rather than assumed. The predicate tree usually arrives JSON-encoded in a string, so it is coerced before validation rather than after a failure. The compiler's errors name the offending path and suggest a real one, so a query that does not compile is handed back exactly once. And the rules, the schema rendering, and the grammar are ~15k tokens that never change, so they are cached, which is most of what a query costs. The model is configuration rather than a decision: which one is worth its price is a question for the eval set, not for a constant here. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
ord_schema.search.nlturns a question into aQuery, runs it against aCorpus, and says what came back:Implements tasks 2–6 of the plan in ord-logbook#45. The measurements behind every design choice are in the entry beside it.
A model cannot be constrained to emit a valid
Query: structured outputs and strict tools share a validator that refuses circular references, then refuses what is left once the recursion is removed as too large. So translation is generation checked afterwards — a coercing parse, pydantic, the compiler, and one repair turn carrying the compiler's own "did you mean".A bug this turned up
The first real question I asked returned zero rows over a corpus holding 24,930 matches. The default resolver asks PubChem, which returns pyridine as
C1=CC=NC=C1; the projection stores what RDKit canonicalizes,c1ccncc1. Compared as strings they never match, and the query answered confidently with nothing.That is fixed in its own commit (75307eb) and is independent of the NL layer — any caller passing
{"compound": ...}to aneqon a smiles path hit it. Resolved SMILES are canonicalized once per name per search; a name resolving to something RDKit cannot parse is left alone rather than swallowed, since that is the resolver's fault to report.Changes
ord_schema/search/nl.py—ask,translate,answer,summarize, the client, and the error taxonomy (ModelRateLimitedError,ModelUnavailableError,MalformedQueryError, all underNLQueryError, which is what ord-interface maps onto status codes).ord_schema/search/nl_prompt.md— the system prompt as prose, including the rules that head off the failures measured on the cheap model (identifiers[*], quantifier scoping, reductions).pyproject.toml— annlextra;ord-schema[search]still installs withoutanthropic, anddependencies_test.pynow has a profile that proves it. The prompt ships as package data, and thetestsextra pullsnlso a fresh install can collect the new tests.ord_schema/search/README.md— a usage section saying plainly what is and is not guaranteed.Testing
uv run pytest -n auto— 1234 passed. Nothing in the suite reaches the network: a stub client returns realToolUseBlock/TextBlockobjects, which pins the coercion, the repair firing exactly once, the error mapping, the cacheable prefix, the forced tool call, and the summary staying bounded as the table grows.End to end against the full corpus (2,428,291 reactions):
The canonicalization test fails without its fix and passes with it — checked by reverting the one line.
Notes
Stacked on #979, which adds the reduction this layer needs for "the highest-yielding reactions"; retarget to main once that lands.
The eval harness (task 7) is not here. It scores on which reactions come back rather than on query shape, and its cases need real reaction IDs from the corpus — that is the next PR.
🤖 Generated with Claude Code
Greptile Summary
Adds an optional natural-language search layer that translates English questions into validated ORD queries, executes them, and summarizes their results.
Confidence Score: 5/5
The PR appears safe to merge because no concrete changed-code failure or reachable security issue remains.
The new natural-language flow validates and compiles generated queries before execution, limits repair to one attempt, bounds summarized table data, and preserves the executed query, while the resolver canonicalization aligns name-resolved SMILES with corpus storage.
Important Files Changed
nlextra, includes it in test installations, and packages the Markdown prompt.Sequence Diagram
sequenceDiagram participant U as Caller participant NL as search.nl participant M as Anthropic model participant C as Query compiler participant Corpus as Corpus U->>NL: ask(question, corpus) NL->>M: translate question with schema/tool M-->>NL: build_query tool input NL->>C: validate and compile alt invalid query C-->>NL: compiler error and suggestion NL->>M: one repair request M-->>NL: repaired tool input NL->>C: validate and compile again end NL->>Corpus: search(query, timeout) Corpus-->>NL: Arrow table NL->>M: question plus bounded summary M-->>NL: plain-text answer NL-->>U: Answer(query, table, text)Reviews (1): Last reviewed commit: "Answer a question in English against a c..." | Re-trigger Greptile