Repository navigation
Python: add Milvus vector store (agent-framework-milvus) - #9346
Nantha kumar (Nanduu24) wants to merge 20 commits into
Conversation
Adds a new agent-framework-milvus package providing MilvusStore, MilvusCollection and MilvusSettings on the BaseVectorStore / BaseVectorCollection interfaces, so Milvus is available as a vector store alongside the Postgres, Qdrant, Redis, MongoDB and other connectors. - The data model definition drives the collection schema: the key field becomes the primary key (INT64/VARCHAR), data fields become typed scalar fields, and vector fields become FLOAT_VECTOR fields, each with an index. - Distance functions map to Milvus metrics (COSINE/IP/L2) and index kinds to Milvus index types (HNSW/FLAT/IVF_FLAT/DISKANN/AUTOINDEX). - Portable filters (eq, ne, gt, gte, lt, lte, between, in, not_in and and/or/not groups) are translated to Milvus boolean expressions; connector-side score thresholds respect each metric's comparison direction. - Upsert, get (by key or filter), delete, vector search and collection lifecycle, with the sync MilvusClient wrapped in asyncio.to_thread. - README, a runnable sample, and unit tests (filters, settings, collection) plus integration tests gated behind MILVUS_TEST_URI. Registered in the workspace.
There was a problem hiding this comment.
🟡 Changes recommended
Connector correctness, package registration, CI coverage, integration testing, and documentation issues remain unresolved.
9 open findings
Concurrent collection creation is not handled atomically · New String escaping leaves control characters in expressions · New Data-field indexing options are silently ignored · New Score threshold lacks finite non-boolean validation · New Milvus Lite dependency is missing for documented local paths · New Milvus tests are not scheduled for pull requests · New Integration tests select no tests · New Usage snippet passes invalid upsert and result handling · New Alpha package is missing from package status metadata · New
What changed in this PR
Adds a first-party Milvus vector-store connector implementing Agent Framework’s collection, store, filtering, lifecycle, and vector-search abstractions.
Changes:
- Adds Milvus schema, CRUD, filtering, search, settings, and client ownership support.
- Adds package documentation, samples, and unit-test scaffolding.
- Registers and locks the new workspace package and dependencies.
| File | Description |
|---|---|
python/uv.lock |
Locks Milvus and transitive dependencies. |
python/pyproject.toml |
Registers the workspace source. |
python/packages/milvus/tests/milvus/test_settings.py |
Tests settings resolution. |
python/packages/milvus/tests/milvus/test_filters.py |
Tests filter translation. |
python/packages/milvus/tests/milvus/test_collection.py |
Tests collection behavior. |
python/packages/milvus/tests/milvus/conftest.py |
Defines test fixtures. |
python/packages/milvus/samples/README.md |
Documents the sample. |
python/packages/milvus/samples/milvus_vectors.py |
Demonstrates Milvus usage. |
python/packages/milvus/README.md |
Documents installation and APIs. |
python/packages/milvus/pyproject.toml |
Defines package metadata and tasks. |
python/packages/milvus/LICENSE |
Adds the MIT license. |
python/packages/milvus/agent_framework_milvus/py.typed |
Marks typed-package support. |
python/packages/milvus/agent_framework_milvus/_vector_store.py |
Implements the connector. |
python/packages/milvus/agent_framework_milvus/__init__.py |
Exports public APIs. |
🧠 Review effort: Balanced
💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.
- ensure_collection_exists: handle concurrent creation (treat an already-created collection as success instead of failing the ensure operation). - Filter string literals are serialized with json.dumps so quotes, backslashes and control characters cannot break out of the Milvus expression. - Reject is_indexed / is_full_text_indexed on data fields until scalar/full-text index creation is implemented, rather than silently ignoring them. - Validate score_threshold as a finite, non-boolean number before searching. - Qualify the Milvus Lite (local .db) docs: it requires the pymilvus[milvus_lite] extra; fix the README usage snippet (sequence upsert, generate_vectors, subscript SearchResponse fields). - Add live integration tests exercising server_collection (CRUD, search, filters), selected by -m integration. - Add a Milvus PR unit-test job and path filter so regressions are caught on PRs. - Register the package in PACKAGE_STATUS.md (alpha / no).
|
Great! Thx |
|
/review |
There was a problem hiding this comment.
MAF Automated Review — Iteration 1
Result: Findings reported
Scope: full PR (2 commit(s)): d121cfe0e10a, 73796abec852
Model: gpt-5.6-sol
Overview
The review found 2 verified inline finding(s).
Reviewed the supplied pull-request change set across correctness, security/reliability, architecture, and failure behavior.
2 verified findings remained after source verification (2 medium) across 1 file. Details are attached to the affected lines below.
Affected areas: python/packages/milvus/agent_framework_milvus/_vector_store.py
Addresses review feedback on microsoft#9346: - ensure_collection_exists now validates an existing collection's fields, vector dimensions and metric types against the data model definition (and does the same after an already-exists race) instead of accepting a stale or unrelated schema. - Portable filter operands are validated against the declared field type: a boolean operand on a numeric field (or any type mismatch) is rejected with a clear error rather than emitting an invalid comparison such as 'price == true'; ordered comparisons require a numeric field. - Added unit tests for schema mismatch (dimension, metric) and operand-type rejection.
…on_exists Per review on microsoft#9346: create_collection builds the collection and then separately creates indexes and loads it. Treating any exception as success when the collection exists afterwards could mask an index or load failure, leaving an unindexed/unloaded collection. Now only a genuine already-exists/concurrent-create MilvusException is swallowed (then the existing collection is validated); all other create/index/load failures are re-raised. Added tests for the index/load-failure and non-Milvus cases.
|
Heads-up on the red |
|
Hi Eduard van Valkenburg (@eavanvalkenburg) — thanks for merging #9614. With that fix on The code-review items from your last pass are all addressed and pushed — schema safety (strict validation of an existing collection's key/primary/data/vector field types, dims, and metric), dense-vector validation (rejecting NaN/inf/overflow/non-numeric), and the concurrent-create handling. The one item still awaiting your steer is live integration coverage. Milvus Lite can't be driven through |
Eduard van Valkenburg (eavanvalkenburg)
left a comment
There was a problem hiding this comment.
Nantha kumar (@Nanduu24) Please address the inline dependency-floor finding, implement the integration-test direction in the existing thread, and add the missing reviewer-focus answer to the PR description.
- Raise the pymilvus dependency floor to >=2.6.0,<3 and update uv.lock metadata. 2.5.x AsyncMilvusClient lacks has_collection, list_collections, describe_collection, describe_index, and (in 2.5.3) prepare_index_params, so valid installs at the old 2.5.3 floor failed basic collection operations with AttributeError. - Add a dependency-bounds workflow that installs the pymilvus==2.6.0 floor and runs the unit tests + Pyright against it, keeping the minimum supported version covered. - Add a Milvus integration job to python-merge-tests.yml: a disposable single-node Milvus (embedded etcd + local storage, no credentials) started with docker, mirroring the Qdrant and MongoDB jobs, running poe test-integration and feeding the aggregate report and the gate. - Gate integration tests on MILVUS_TEST_URI instead of MILVUS_URI so the suite does not collide with the env var pymilvus reserves for its default client.
Milvus 2.6 standalone panics with "embedded etcd can not be used under distributed mode" when started with ETCD_USE_EMBED=true, because a package-level init reads the config before `milvus run standalone` sets the role, so the server never became healthy and the integration job timed out waiting on :9091/healthz. Replace the single embedded-etcd container with the official Milvus standalone stack (external etcd + MinIO + Milvus) brought up via docker compose, pinned to the image versions from the v2.6.20 release compose (etcd v3.5.25, MinIO RELEASE.2024-12-18, milvus v2.6.20, MQ_TYPE woodpecker). Still disposable and credential-free; the readiness loop and test steps are unchanged.
The standalone stack failed to start because MinIO removed its public Docker Hub image (`minio/minio` now 404s: "pull access denied ... repository does not exist"), so `docker compose up` aborted before Milvus started. Milvus standalone does not need object storage over the network for this test: set COMMON_STORAGETYPE=local on the standalone service and remove the MinIO service entirely. The stack is now just external etcd + Milvus (local-disk object storage, woodpecker WAL), which keeps the fix for the embedded-etcd panic while removing the fragile image dependency.
…rage) The standalone container started and reported healthy, but every integration test hung at fixture setup and was killed by pytest-timeout (>120s), with no Milvus error — a silent data-plane stall. Cause: the job paired COMMON_STORAGETYPE=local with MQ_TYPE=woodpecker. Woodpecker (Milvus 2.6's WAL) persists its segments to remote object storage, so with local-disk storage and no MinIO/S3 it never acknowledges writes and collection/insert operations block indefinitely. Milvus's own standalone_embed.sh uses local storage without setting MQ_TYPE, letting standalone fall back to the local-disk message queue. Remove MQ_TYPE so local storage uses its matching default MQ.
|
Nantha kumar (@Nanduu24) I found two additional current-head blockers:
Please address both before approval. |
…elds; strong read-after-write Addresses review findings and the integration read-after-write failures: - Non-BMP string literals: _format_scalar now uses json.dumps(ensure_ascii=False). The default ensure_ascii=True emits 😀-style surrogate escapes for emoji / supplementary-plane characters, which Milvus's Go strconv.Unquote rejects, so eq/ne/in/not_in on such strings failed. Quote/backslash/control escaping is unchanged. - Integer-field float operands: operands on INT64 fields are now normalized. Integral floats (1.0) emit integer literals so eq/ne/in match the key; fractional ordered comparisons are translated exactly (id > 1.5 -> id >= 2, id < 1.5 -> id <= 1, likewise for between bounds); fractional equality and membership are rejected since no integer key can equal them. Float fields are unaffected. - Read-after-write: the collection is created with consistency_level=Strong and get/query/search default to Strong (both overridable via operation_options), so a record is visible immediately after upsert on a real Milvus server. Milvus Lite was already strongly consistent; the server default (bounded) made the integration tests' immediate reads intermittently miss fresh data. Adds filter tests for non-BMP strings and integral/fractional float operands on integer fields.
|
Thanks Eduard van Valkenburg (@eavanvalkenburg) — both addressed in d04a8b9:
Separately, now that the integration job runs against a real server it surfaced a third issue: immediate read-after-write missed fresh data under Milvus's default bounded consistency (Milvus Lite is strongly consistent, so it only showed on the server). The collection is now created with 65 unit tests pass; |
…tory The release-manifest validation (test_inventory_covers_every_workspace_distribution_ with_a_public_export) requires every workspace distribution to appear in prepare_release.SMOKE_IMPORTS with a reviewed public module:export entry point. Add agent-framework-milvus -> agent_framework_milvus:MilvusStore so the new package passes release validation, matching the other vector-store connectors.
|
Status after syncing
One red check, |
|
Nantha kumar (@Nanduu24) Independently created connector-owned |
…lias At the pymilvus==2.6.0 floor, AsyncMilvusClient derives its connection alias from the uri/auth/db_name and reuses any global connection already registered under that alias (pymilvus.milvus_client._utils.create_connection). Two connector-created stores pointing at the same server therefore shared one connection, so closing one store (connections.async_remove_connection) tore down the connection the other store still depended on. Pass a unique alias for every connector-created client so each owns an independent connection; caller-supplied clients are untouched. Add regression tests proving two independently created stores get distinct aliases and that closing one leaves the other connected.
|
Good catch — fixed in 45f2619. Confirmed against the 2.6.0 source: I took the unique-alias route rather than raising the floor: each connector-created client now gets Regression added in
67 unit tests pass, ruff + pyright-strict clean. |



Summary
Adds a new
agent-framework-milvuspackage providing a Milvus vector store for Agent Framework, implementing theBaseVectorStore/BaseVectorCollection/BaseVectorSearchinterfaces. This makes Milvus — one of the most widely used open-source vector databases — available out of the box alongside the existing Postgres, Qdrant, Redis, MongoDB, SQL Server and other vector-store packages.It follows the structure of the existing
agent-framework-qdrantpackage (settings viaload_settings, a store that mints collection clients, borrowed-vs-managed client ownership,operation_optionsvalidation, native filtering).What's included
MilvusStore/MilvusCollection/MilvusSettings(+py.typed).INT64forintkeys,VARCHARotherwise), data fields become typed scalar fields, and vector fields becomeFLOAT_VECTORfields, each with its own index.DistanceFunction→ Milvus metric types (cosine_similarity/cosine_distance→COSINE,dot_prod→IP,euclidean_distance/euclidean_squared_distance→L2);IndexKind→ Milvus index types (hnsw,flat,ivf_flat,disk_ann,default→AUTOINDEX).ensure_collection_exists/collection_exists/ensure_collection_deleted,upsert,get(by key or portable filter),delete, and vectorsearch.eq,ne,gt,gte,lt,lte,between,in,not_in, andand/or/notgroups. Operators that Milvus can't implement with correct null/collection semantics raiseNotImplementedErrorrather than silently returning wrong results.score_thresholdthat respects each metric's comparison direction (larger-is-closer forCOSINE/IP, smaller-is-closer forL2), since core does not post-filter.AsyncMilvusClientthroughout, presenting the framework's async interface without wrapping a synchronous client.samples/milvus_vectors.py), and the package registered in the workspace[tool.uv.sources].Review guide — what to focus on
The areas most worth a careful read, since they are where a vector-store connector most easily goes subtly wrong:
_validate_existing_collection): when a collection already exists, the connector verifies it matches the data model — key presence and primary flag,INT64-vs-VARCHARkey type, each scalar field'sDataType, and each vector field'sFLOAT_VECTORtype, dimension, metric (viadescribe_index), and that it is not left onAUTOINDEX— rather than trusting it blindly.eq/ne/gt/gte/lt/lte/between/in/not_in+and/or/not), and the deliberateNotImplementedErrorfor operators Milvus can't express with correct null/collection semantics — the design choice is to fail loudly instead of returning wrong results._validate_dense_vector): rejects non-numeric, bool,NaN/inf, and float32-overflow values before they reach the server.score_thresholddirection handling per metric, given core does not post-filter results.pymilvus>=2.6.0floor and the new disposable-Milvus integration job.Testing
tests/milvus/): filter translation, settings resolution/precedence, and collection behavior (upsert/get/delete/search, schema building, serialization, lifecycle, client ownership) — hermetic, no live service.MILVUS_TEST_URIfor a real server, now run in CI against a disposable single-node Milvus (embedded etcd + local storage, no cloud credentials) via a newPython Integration Tests - Milvusjob that mirrors the existing Qdrant and MongoDB jobs and feeds the aggregate report and the integration gate.pymilvus==2.6.0) and runs the unit tests + Pyright against it, so the minimum supported version stays covered.ruff check,ruff format --check, and Pyright (strict) are clean on the package;uv lock --lockedsucceeds.Motivation
Milvus is a top-tier open-source vector database with no first-party Agent Framework integration. This PR implements the accepted feature request in #3488, with a connector that matches the conventions of the other vector-store packages.
Related issue
Closes #3488 — the agreed Milvus VectorStore connector feature request.