Skip to content

Python: add Milvus vector store (agent-framework-milvus) - #9346

Open
Nantha kumar (Nanduu24) wants to merge 12 commits into
microsoft:mainfrom
Nanduu24:python-milvus-vector-store
Open

Nantha kumar (Nanduu24) wants to merge 12 commits into
microsoft:mainfrom
Nanduu24:python-milvus-vector-store

Conversation

@Nanduu24

@Nanduu24 Nantha kumar (Nanduu24) commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Adds a new agent-framework-milvus package providing a Milvus vector store for Agent Framework, implementing the BaseVectorStore / BaseVectorCollection / BaseVectorSearch interfaces. 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-qdrant package (settings via load_settings, a store that mints collection clients, borrowed-vs-managed client ownership, operation_options validation, native filtering).

What's included

  • MilvusStore / MilvusCollection / MilvusSettings (+ py.typed).
  • Schema from the data model — the key field becomes the primary key (INT64 for int keys, VARCHAR otherwise), data fields become typed scalar fields, and vector fields become FLOAT_VECTOR fields, each with its own index.
  • Mappings — 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).
  • Full surface — ensure_collection_exists / collection_exists / ensure_collection_deleted, upsert, get (by key or portable filter), delete, and vector search.
  • Portable filters translated to Milvus boolean expressions: eq, ne, gt, gte, lt, lte, between, in, not_in, and and/or/not groups. Operators that Milvus can't implement with correct null/collection semantics raise NotImplementedError rather than silently returning wrong results.
  • Connector-side score_threshold that respects each metric's comparison direction (larger-is-closer for COSINE/IP, smaller-is-closer for L2), since core does not post-filter.
  • The synchronous MilvusClient is wrapped in asyncio.to_thread, which keeps the connector compatible across the supported pymilvus range while presenting the framework's async interface.
  • README, a runnable sample (samples/milvus_vectors.py), and the package registered in the workspace [tool.uv.sources].

Testing

  • Unit tests (tests/milvus/): filter translation, settings resolution/precedence, and collection behavior (upsert/get/delete/search, schema building, serialization, lifecycle, client ownership) — hermetic, no live service.
  • Integration tests gated behind MILVUS_TEST_URI for a real server.
  • Verified end-to-end against a live Milvus database (create → index → upsert → semantic search → portable-filter search → get with vectors → list → delete → lifecycle), all passing.
  • ruff check, ruff format --check, and Pyright (strict) are clean on the package; uv lock --locked succeeds.

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.

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.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Connector correctness, package registration, CI coverage, integration testing, and documentation issues remain unresolved.

9 open findings
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.

Comment thread python/packages/milvus/agent_framework_milvus/_vector_store.py Outdated
Comment thread python/packages/milvus/agent_framework_milvus/_vector_store.py Outdated
Comment thread python/packages/milvus/agent_framework_milvus/_vector_store.py
Comment thread python/packages/milvus/agent_framework_milvus/_vector_store.py
Comment thread python/packages/milvus/pyproject.toml Outdated
Comment thread python/packages/milvus/pyproject.toml
Comment thread python/packages/milvus/tests/milvus/conftest.py
Comment thread python/packages/milvus/README.md Outdated
Comment thread python/packages/milvus/pyproject.toml
- 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).
@allyson-tcesp

Copy link
Copy Markdown

Great! Thx

@eavanvalkenburg

Copy link
Copy Markdown
Member

/review

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread python/packages/milvus/agent_framework_milvus/_vector_store.py
Comment thread python/packages/milvus/agent_framework_milvus/_vector_store.py Outdated
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.
)
resolved_token = settings.get("token")
resolved_password = settings.get("password")
client_kwargs: dict[str, Any] = {"uri": settings.get("uri") or DEFAULT_URI}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
client_kwargs: dict[str, Any] = {"uri": settings.get("uri") or DEFAULT_URI}
client_kwargs: dict[str, Any] = {"uri": settings.get("uri", DEFAULT_URI)}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kept settings.get("uri") or DEFAULT_URI: load_settings includes optional keys as None when unset, so settings.get("uri", DEFAULT_URI) would return None rather than the default (verified). Happy to change if you'd prefer a different shape.

Comment thread python/packages/milvus/agent_framework_milvus/_vector_store.py Outdated
Comment thread python/packages/milvus/agent_framework_milvus/_vector_store.py Outdated
Comment on lines +251 to +254
if client is not None and any(
value is not None for value in (uri, token, user, password, db_name, env_file_path, env_file_encoding)
):
raise ValueError("Pass client or connection settings, not both.")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be handled after parsing the settings, some or all of this might come from the environment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This mirrors the Qdrant connector's guard and intentionally inspects only the explicit constructor args — env-sourced settings are ambient and aren't treated as 'passed'. Happy to drop the guard (and let an explicit client win) if you'd prefer; let me know.

Comment thread python/packages/milvus/agent_framework_milvus/_vector_store.py Outdated
Comment thread python/packages/milvus/agent_framework_milvus/_vector_store.py
Comment thread python/packages/milvus/tests/milvus/conftest.py Outdated
Comment thread python/packages/milvus/tests/milvus/test_integration.py
Comment thread python/packages/milvus/pyproject.toml
Addresses review feedback on microsoft#9346:

- Switch from the sync MilvusClient wrapped in asyncio.to_thread to the native
  AsyncMilvusClient, awaiting operations directly.
- Let the native Milvus client-creation error propagate instead of wrapping it.
- Replace the brittle already-exists message match: on a create failure, validate
  the existing collection and re-raise the original error if it does not match,
  so only a genuine concurrent-create with a complete, matching collection is
  accepted.
- Tests use MILVUS_URI (not a separate MILVUS_TEST_URI) to gate integration tests;
  the env-clearing fixture is skipped for integration tests.
- Require pymilvus>=2.5.3 (AsyncMilvusClient).
Verified pymilvus 2.5.3+ and its dependencies (grpcio, numpy, …) resolve, install
and import on Python 3.15, matching the framework's new 3.15 support.
@eavanvalkenburg

Copy link
Copy Markdown
Member

Nantha kumar (@Nanduu24) Thanks for the contribution. The package direction is already agreed, but the PR body does not link the agreed tracking issue. Please add that issue to the PR's Related Issue information so the recorded agreement and lifecycle decision are directly connected to this package addition.

@Nanduu24

Copy link
Copy Markdown
Contributor Author

Thanks Eduard van Valkenburg (@eavanvalkenburg)! Linked the tracking issue — added Closes #3488 to the PR description under Related issue.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a substantial and promising connector, but schema safety, vector validation, live integration coverage, and the PR-caused workspace CI failure need to be resolved before it is merge-ready.

Comment thread python/packages/milvus/agent_framework_milvus/_vector_store.py
Comment thread python/packages/milvus/agent_framework_milvus/_vector_store.py Outdated
python-version: ${{ env.UV_PYTHON }}
os: ${{ runner.os }}
- name: Test Milvus package
run: uv run poe test -P milvus

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This job runs only poe test, which excludes the newly added live integration tests. The workflow header requires parallel jobs to stay in sync with python-integration-tests.yml, but that workflow has no Milvus service/Lite job and Milvus is absent from its report/gate dependencies. Please schedule poe test-integration against a disposable Milvus server or Milvus Lite in the integration workflow and wire it into the aggregate report/check.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking into this, I hit two concrete constraints and want your steer before wiring it:

  1. Milvus Lite can't be driven via MILVUS_URI. pymilvus reads a global MILVUS_URI at import and validates it as http(s)://..., so pointing it at a local .db crashes import pymilvus. (I verified AsyncMilvusClient itself works fine against Lite when the path is passed as the uri= argument, not the env var — I've fixed the docs accordingly.)
  2. No single-container Milvus service. Milvus standalone needs etcd+MinIO (or embedded etcd via milvus run standalone), and GitHub services: can't override the image command, so it can't be a service container like qdrant/redis/postgres — it needs a docker run step.

Proposed: add python-tests-milvus to python-integration-tests.yml that docker runs a disposable Milvus standalone (embedded etcd) with MILVUS_URI=http://localhost:19530, waits on /healthz, runs poe test-integration, and wire it into the report + python-integration-tests-check gate. Happy to push that — just confirming you'd prefer the standalone-server route over Milvus Lite (which the reserved env var rules out for the MILVUS_URI-gated tests).

Comment thread python/packages/milvus/pyproject.toml
Addresses review feedback on microsoft#9346:

- Existing-collection validation now also checks the primary-key type, every
  data/vector field DataType, and the (non-AUTOINDEX) vector index kind, in
  addition to dimensions and metric, so a same-named but incompatible collection
  is rejected. Added key/data/vector type and index-kind mismatch tests.
- Added a shared dense-vector validator used for both stored and query vectors:
  rejects non-numeric sequences (incl. strings like "123"), booleans, and
  NaN/infinity or out-of-float32-range values, instead of coercing with float().
  Added regression tests.
- Removed the now-unused ty: ignore[unresolved-import] on the unrelated
  optimized_data_formats sample (pymilvus pulls pandas into the workspace, so ty
  resolves the import), fixing the PR-caused Samples/Typing CI failures.
- Clarified docs: Milvus Lite is selected via the uri= argument with a local .db
  path; the MILVUS_URI env var must be a server address (pymilvus reserves it).
@Nanduu24

Copy link
Copy Markdown
Contributor Author

Heads-up on the red Public API Compatibility check — it isn't from this connector. griffe raises AliasResolutionError on agent_framework._workflows._edge.EdgeGroup.builtin_type while loading the base API, because that builtins.type alias is imported inside the EdgeGroup class body (so griffe sees it as a class member). It fails on every PR against main. I opened #9614 with a one-line fix (move the alias to module scope, matching _request_info_mixin.py); once it lands and I re-sync, this check should go green here. The connector's own checks pass.

@Nanduu24

Copy link
Copy Markdown
Contributor Author

Hi Eduard van Valkenburg (@eavanvalkenburg) — thanks for merging #9614. With that fix on main and the branch re-synced, the Public API Compatibility check (and all of CI) is now green here: 40 passing / 16 skipped, 0 failing.

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 MILVUS_URI (pymilvus reserves that env var for an http(s) server address and crashes at import on a local .db path), so the cleanest option looks like a docker run standalone Milvus service in the integration job, mirroring the existing Qdrant/MongoDB jobs. Happy to wire that up — just let me know if that approach works for you or if you'd prefer something else.

This branch was successfully deployed

1 active deployment
github-app-auth — b1921c26 Deployed Oct 23, 2026 by eavanvalkenburg via add_label #26044
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Usage: [Issues, PRs], Target: documentation in the code base and learn docs python Usage: [Issues, PRs], Target: Python

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: MilvusDB Out-of-the-box VectorStore connector

4 participants