Skip to content

fix(graphile-llm): treat blank VectorNearbyInput text as not given - #1970

Open
theothersideofgod wants to merge 2 commits into
mainfrom
fix/vector-nearby-empty-text
Open

theothersideofgod wants to merge 2 commits into
mainfrom
fix/vector-nearby-empty-text

Conversation

@theothersideofgod

Copy link
Copy Markdown
Contributor

Why

VectorNearbyInput takes exactly one of vector or text (#1955). The nearby rule in embed-inputs.ts counted any string as "text given", so text: "" or text: " " on its own was sent to the embedder instead of being rejected. What the client got then depended on the provider:

  • Ollama nomic-embed-text answers "" with no vector, the default onQuotaExceeded: degrade returns without setting vector, and the filter disappears: the whole table comes back with null distances and no error.
  • Whitespace is embedded into a real vector, so rows are ranked by a vector that has nothing to do with any query.

The unified rule in the same file already treats blank text as not given (text.trim().length === 0).

Change

In the nearby rule, text counts as given only when it is a string with non-whitespace content. Blank text on its own now raises the existing error, before any embedding call:

VectorNearbyInput: provide exactly one of `vector` or `text`; neither was given.

The "both" check still uses "a string was sent", so vector plus blank text is still rejected as both. The text field description and the plugin header say that blank text counts as not given.

Not changed: the {column}Text companion rule (an empty embed already fails there with EMBED_FAILED) and the degrade fallback when the provider fails on real text.

Behavior changes

  • text: "" or whitespace alone, embedder configured. Was: embedded; with Ollama, "" dropped the filter (whole table, null distances) and whitespace ranked by an unrelated vector. Now: neither was given error; the embedder is not called. Why: blank text is not a query, and returning the unfiltered table without an error is a wrong result.
  • Blank text alone, no embedder configured. Was: VectorNearbyInput: `text` needs an embedding provider and none is configured; send `vector` instead. Now: neither was given. Why: the input is checked before the provider (read from the code; no test covers this case).
  • vector plus blank text: unchanged, not both (covered by a new test).

Question for the owner

Should vector plus blank text be accepted as a vector query? This PR keeps it an error, as #1955 defined.

Before / after

Base: origin/main f1f197863. cd graphile/graphile-llm && PGPORT=15432 pnpm exec jest src/__tests__/embed-inputs.test.ts

  • Before (test commit only): Tests: 2 failed, 26 passed, 28 total. Both rejects a VectorNearbyInput whose only input is blank text "" and ... " " fail at expect(result.errors?.[0]?.message).toMatch(...) (embed-inputs.test.ts:265) with Received has value: undefined: no error, the query ran.
  • After: Tests: 28 passed, 28 total. Whole package: 98 passed on the base, 101 passed with this PR (3 new tests). tsc --noEmit and eslint . exit 0 (2 existing warnings, in files this PR does not touch).

Cluster end-to-end

A GraphQL image built from the deployed pin (ca40d826b) plus this fix (same patch-id) and the #1955 commits it depends on, swapped into a local cluster and swapped back afterwards. Query: dataRoomFiles with embeddingVectorDistance and VectorNearbyInput { text }, as a tenant user.

input before (deployed image) after (fix) restored image
text: "" no error, all 6 rows, every distance null provide exactly one of `vector` or `text`; neither was given., dataRoomFiles: null no error, all 10 rows, distance null
text: " " no "not given" error, rows ranked by distances 0.4067 / 0.4154 / 0.4855 from a vector unrelated to any query neither was given same as before
text: "when did the lava…" (sanity) volcano file first, 0.29332 same row first, same distance same row first

Row counts grow 6 → 8 → 10 because each run added two files. The deployed image also returned Float cannot represent non numeric value: Infinity errors on rows without embeddings in the " " and sanity queries; that is a separate chunk-distance bug (not part of this PR), and those errors are gone in the "after" column only because that test image also carried a fix for it. The cluster image runs an older pin than main, and the gateway route from #1961 was not part of it; what the gateway returns for "" was not tested. The fix rejects blank text before any embedding call, so it does not depend on the provider's answer.

Rules touched: none (graphile-llm input validation only).

🤖 Generated with Claude Code

theothersideofgods and others added 2 commits November 6, 2026 09:15
A blank `text` is embedded today. A provider that returns no vector for it
drops the vector filter under `degrade`, so the query returns the whole
table without an error. These tests pin the intended behavior: blank text
alone fails like neither field, and blank text beside a vector is ignored.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The `nearby` rule counted any string as text and embedded it. Ollama's
nomic-embed-text answers `""` with no vector, and under the default
`onQuotaExceeded: 'degrade'` the vector filter was then dropped, so
`text: ""` returned every row with a null distance and no error.

Blank text now counts as absent, the way the `unified` rule already treats
a blank `unifiedSearch`. Blank text alone raises the "neither was given"
error. Blank text beside a vector is ignored, the same way an empty vector
beside text is ignored. The field description says so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@tenki-reviewer

tenki-reviewer Bot commented Nov 6, 2026 •

Copy link
Copy Markdown

Review complete. No issues found — approved ✅.


The change aligns embed-input validation semantics for whitespace-only text values across the LLM search input paths, so blank text no longer counts as provided and does not trigger a plan-time embedding call. Verified scans (correctness, security, api-data, two domain specialists, holistic, and sweep) surfaced no defects above the confidence threshold in the changed lines; companion blank-text claims were pre-existing or untouched by this diff, and the doc-vs-code concern was refuted by the new tests.

Reviewed commit: e7239cf

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants