fix(dspy): scope WeaviateRM tenant retrieval to the collection not the query namespace - #98
Conversation
…e query namespace
Greptile SummaryThis PR corrects Weaviate v4 tenant retrieval by scoping the collection before invoking hybrid search and adds regression coverage for tenant, non-tenant, keyword-forwarding, and legacy-client paths.
Confidence Score: 4/5The production fix appears safe to merge, with non-blocking test-isolation and test-fidelity concerns remaining. The v4 tenant query now follows the supported Weaviate collection-scoping API without changing non-tenant behavior; the identified issues affect test reliability and the credibility of legacy-client coverage rather than production retrieval. Files Needing Attention: tests/retrievers/test_weaviate_rm.py Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart LR
Q[Query and optional tenant_id] --> T{Tenant supplied?}
T -->|Yes| S[Collection.with_tenant]
T -->|No| C[Base collection]
S --> H[Scoped collection.query.hybrid]
C --> H
H --> R[Weaviate results]
R --> P[DSPy passage predictions]
Reviews (1): Last reviewed commit: "fix(dspy): scope WeaviateRM tenant retri..." | Re-trigger Greptile |
| sys.modules["weaviate"] = _weaviate_stub | ||
| sys.modules["weaviate.util"] = _weaviate_util_stub |
There was a problem hiding this comment.
The module-level sys.modules assignments permanently shadow the real weaviate package whenever it is installed but has not yet been imported. Because pytest imports this file during collection, later integration tests receive this incomplete stub instead of the installed client, which can cause order-dependent failures or invalid test behavior. Use a scoped patch that restores sys.modules after importing the module under test.
| return rm, client | ||
|
|
||
|
|
||
| def test_v4_query_namespace_has_no_with_tenant(): | ||
| """The query object returned by `collection.query` must NOT expose `with_tenant`. |
There was a problem hiding this comment.
Test Bypasses Public Constructor
This v3 regression test bypasses the public constructor and manually forces the internal client type. It therefore passes even though a real v3 client cannot construct WeaviateRM, because __init__ accesses client.collections before checking for the v3 query interface. This gives misleading coverage of behavior users cannot reach; exercise construction through the public API or avoid presenting this as a v3 behavior regression test.
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Warning
GitHub issue creation failed
Detail attempted to publish this bug to GitHub, but the issue could not be created. This fix PR was created without that issue, and missing tracker references are shown as
Unknown issue.You can review and merge this PR normally. Please review your tracker integration settings before the next publish run.
Detail bug report: View on Detail
📝 Changes Description
Closes Unknown issue
Bug: On the Weaviate v4 (
WeaviateClient) tenant path,WeaviateRM.forward()calledself._weaviate_collection.query.with_tenant(tenant).collection.queryreturns a_QueryCollection, which has nowith_tenantin any pinned version ofweaviate-client(>=4.5.4,<4.22.0);with_tenantlives on theCollectionitself. Every v4 retrieval with atenant_id(constructor or per-call) raisedAttributeError: '_QueryCollection' object has no attribute 'with_tenant', leaving the entire multi-tenancy feature (added in10fe155a) unreachable on the v4 client — the only client for whichinsert/get_objectsare supported.Fix: Scope the collection to the tenant before querying — the v4 API's prescribed pattern, where
Collection.with_tenant(t)returns a new tenant-scoped collection whose.query.hybrid(...)threads the tenant into the gRPC request:This collapses the prior
if tenant/elseinto one query call. The v3Clientbranch is unchanged —with_tenantlegitimately exists on its GraphQLGetbuilder.Testing:
tests/retrievers/test_weaviate_rm.py): run without theweaviateextra by stubbing the optional import insys.modules(thetest_gepa.pypattern). They lock in the v4 API invariant (with_tenantonCollection, not onquery), the fix's tenant-scoping behavior, the no-tenant path, thattenant_idis consumed and not forwarded tohybrid, and the v3 GraphQL-builder path. Reverting the fix makes the v4 tenant tests fail with the exact productionAttributeError. Reverting/restore was confirmed during verification.ruff check,ruff format --check, the retriever unit tests, and the broader default test suite all pass with no regressions.weaviate-client==4.21.3:Collectionhaswith_tenant;_QueryCollectionhashybridbut notwith_tenant; the fixed call chain reaches the gRPC send layer with the tenant threaded through (scoped._query._tenant == "T1").weaviate:1.30.0): created a multi-tenant collection with two tenants and confirmedWeaviateRMreturns tenant-scoped results. QueryingTenant1for content present only inTenant2returns onlyTenant1docs (no boundary leak), proving the tenant actually reaches the request rather than just chaining a method call. Per-calltenant_idoverride,khonoring, and__call__/forwardparity also verified.weaviate-client==4.5.4against the live server: thewith_tenantGraphQL idiom returns tenant-scoped results. The full v3forward()call could not be exercised because the base Docker image ships no vectorizer module and the test collection usesvectorizer=none; the v3 branch'swith_hybrid(query=query)(no vector) is rejected by the server (VectorFromInput was called without vectorizer), which is independent of this fix.✅ Contributor Checklist
ruff formatwas applied todspy/retrievers/weaviate_rm.py; the formatter also normalized a few lines outside the v4 tenant branch (the v3query.get(...)continuation,# TODOcomment spacing, andinsert/get_objectsindentation). Those are formatter-only changes, not logic changes — the project's pre-commit hook would produce the same edits.Automatic Fixes PRs can be configured here.