DeepSearcher: a string-keyed LLM config in front of Milvus
Open Source Deep Research Alternative to Reason and Search on Private Data. Written in Python.
At a glance
- What is it?
- DeepSearcher is an Apache-2.0 Python library from Zilliz that runs search, evaluation and reasoning over private data, with Milvus as a hard dependency and a dozen LLM providers reached by name string. Its README is strong on provider examples and weak on the parts that decide whether the thing works on your data.
- Who is it for?
- Adopt DeepSearcher if your data already lives in Milvus and you want one library that wires an LLM to it through a single configuration object. Do not adopt it if you need a vector store other than Milvus without installing extras first, or if you need web crawling that the README calls unfinished.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 53 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on November 14, 2026, and from our analysis. They are not legal advice.
Star history
- zilliztech/deep-searcher8,395 ★ · +104
| Repository | 2026-09-30 | 2026-11-14 |
|---|---|---|
| zilliztech/deep-searcher | 8.3k | 8.4k |
104 Stars in the last 45 days, measured from the daily GitHub snapshot. Compare with other projects
Editorial analysis
Configuration is one object, a provider name and an arguments dict
The whole runtime surface is a `Configuration` object you mutate before anything runs. `config.set_provider_config("llm", "OpenAI", {"model": "o1-mini"})` takes three things: a slot name, a provider name, and a dictionary of arguments for that provider's class. A second call fills the embedding slot with `config.set_provider_config("embedding", "OpenAIEmbedding", {"model": "text-embedding-ada-002"})`, and `init_config(config = config)` publishes the result to the rest of the library.
That design has one consequence worth internalizing: the provider is a string, not a class, so the set of things that can go wrong at configuration time is not bounded by what you imported. A typo in the provider name is not caught by your editor. The README spells the shape out as `config.set_provider_config("llm", "(LLMName)", "(Arguments dict)")` and then lists the accepted names, which is the closest thing to a contract the project offers.
The provider list does not contain the name the Claude example uses
The README lists eleven names for the "llm" slot: DeepSeek, OpenAI, XAI, SiliconFlow, Aliyun, PPIO, TogetherAI, Gemini, Ollama, Novita and Jiekou.AI. Anthropic is not among them. The Claude example further down nonetheless calls:
config.set_provider_config("llm", "Anthropic", {"model": "claude-sonnet-4-0"})So the list cannot be used to resolve a provider name, and anyone configuring from it alone will not find Claude. The dependency has the same split: `anthropic>=0.51.0` lives in `[project.optional-dependencies]`, so a plain `pip install deepsearcher` does not install the package the Claude example needs.
base_url turns the OpenAI slot into any OpenAI-compatible gateway
One example configures a third-party gateway while keeping the provider name OpenAI:
config.set_provider_config("llm", "OpenAI", {"model": "qwen/qwen3-235b-a22b:free", "base_url": "https://openrouter.ai/api/v1", "api_key": "OPENROUTER_API_KEY"})Read that as the mechanism it is: the provider string selects a client class, and `base_url` redirects it. Any OpenAI-compatible endpoint is reachable the same way, which is how the README routes Qwen3 through OpenRouter without adding a provider entry. One detail does not add up, though. Every other example relies on an environment variable and passes no key at all, while this one puts the literal string OPENROUTER_API_KEY into the `api_key` field, so that name is what reaches the client. It is worth checking before you copy the line into a config.
Milvus is a hard dependency and every other backend is an extra
`pyproject.toml` sets `version = "0.0.2"` and `requires-python = ">=3.10"`. The core dependency list is Milvus-shaped: `pymilvus>=2.5.8` sits beside `langchain-text-splitters>=0.3.8`, `pdfplumber>=0.11.6`, `firecrawl-py>=2.5.3`, `openai>=1.77.0`, `fastapi>=0.115.12` and `uvicorn>=0.34.2`. Every other vector store is optional: `qdrant-client`, `azure-search-documents`, `oracledb` and `boto3` all live in `[project.optional-dependencies]`, and the examples directory carries `basic_example_azuresearch.py`, `basic_example_oracle.py` and `basic_watsonx_example.py` beside the plain `basic_example.py`.
So the two install routes look symmetric but are not. The pip route installs a Milvus-first package, and the cost of Azure or Oracle is a second install afterwards:
pip install deepsearcheror, for an optional provider:
pip install "deepsearcher[ollama]"`pyproject.toml` also declares `[project.scripts] deepsearcher = "deepsearcher.cli:main"`, a console entry point.
The image health check is /docs, and CORS is on in the shipped command
The Dockerfile is short and says a lot. It builds on `ghcr.io/astral-sh/uv:python3.10-bookworm-slim`, copies `pyproject.toml`, `uv.lock`, `LICENSE`, `README.md` and the `deepsearcher/` package before the rest of the tree, runs `uv sync`, and ends like this:
EXPOSE 8000
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8000/docs || exit 1
CMD ["uv", "run", "python", "main.py", "--enable-cors", "true"]Two things follow. The container reports itself healthy when its documentation endpoint answers, which says nothing about whether queries work. And the command that ships enables CORS, on a service whose Dockerfile contains no username, token or other access control. Anything that can reach port 8000 can drive an app holding your LLM key and your data path, so treat that port as local unless you put an authenticating proxy in front of it.
Web crawling is called unfinished while a crawler dependency ships
The feature list describes the Document Loader as supporting local file loading, with web crawling capabilities under development. The package contents tell a different story: `firecrawl-py>=2.5.3` is a core dependency, not an extra, and the examples directory contains `load_website_using_firecrawl.py` next to `load_local_file_using_unstructured.py` and `load_and_crawl_using_docling.py`.
Treat the feature list as a status note and the examples as the specification. The two optional loader stacks, `unstructured-ingest` with `unstructured[all-docs]` and `docling` with `docling-core`, are installed explicitly, and `crawl4ai>=0.6.2` is in the `all` group, so the loader you can actually run depends on which extras you installed. If your data is on the web rather than on disk, the README's unfinished label is the least useful sentence on the page and the examples directory is where the working combination is.
Version 0.0.2 and a single tag named after an embedding model
The version field in `pyproject.toml` reads 0.0.2, and the release record holds one entry dated 2025-02-20 named `embedding_model`, carrying a Milvus default embedding model and a GPTCache model rather than a version of the package. There is no tag series to pin a deployment against, and the README installs from PyPI while contributing means cloning `master` and running `uv sync`:
git clone https://github.com/zilliztech/deep-searcher.git && cd deep-searcherThe two routes are not pinned to each other by anything in the repository, so `pip install deepsearcher` and the branch you develop against can differ. The last push was on 2026-09-22 and the repository is not archived, which is the maintenance signal available. Licence-wise the position is simple: Apache-2.0, with `license = { file = "LICENSE"}` in `pyproject.toml` and the text at the repository root. Ownership records are thin too, just MAINTAINERS, OWNERS and OWNERS_ALIASES files at the top level, and the docs are built with mkdocs from `mkdocs.yml`.
Editorial conclusion
Adopt DeepSearcher if your data already lives in Milvus and you want one library that wires an LLM to it through a single configuration object. Do not adopt it if you need a vector store other than Milvus without installing extras first, or if you need web crawling that the README calls unfinished. Before anything else, read the LLM provider list in the README and check your provider name against it, because the list and the Claude example do not agree.
Frequently asked questions
How do I install DeepSearcher?
The pip route creates a virtual environment, the README recommending Python 3.10, then runs `pip install deepsearcher`, with extras such as `pip install "deepsearcher[ollama]"` for optional providers. The development route is `git clone https://github.com/zilliztech/deep-searcher.git && cd deep-searcher` followed by `uv sync`, which the README recommends using uv for.
Which vector databases can DeepSearcher use?
The README names Milvus and other vector databases with data partitioning. In pyproject.toml, pymilvus>=2.5.8 is a core dependency while qdrant-client, azure-search-documents, oracledb and boto3 are optional, and the examples directory ships basic_example_azuresearch.py, basic_example_oracle.py and basic_watsonx_example.py.
How do I point DeepSearcher at a different model provider?
Call `config.set_provider_config("llm", "(LLMName)", "(Arguments dict)")` before `init_config(config = config)`, where the name is one of DeepSeek, OpenAI, XAI, SiliconFlow, Aliyun, PPIO, TogetherAI, Gemini, Ollama, Novita or Jiekou.AI. An OpenAI-compatible gateway is reached by keeping the name OpenAI and setting model, base_url and api_key in the same dict.
Which API keys does DeepSearcher expect?
The quick start asks for OPENAI_API_KEY, and each provider example names its own variable: DASHSCOPE_API_KEY for Aliyun Bailian, DEEPSEEK_API_KEY, SILICONFLOW_API_KEY, TOGETHER_API_KEY, XAI_API_KEY, ANTHROPIC_API_KEY for Claude and GEMINI_API_KEY for Google Gemini. Change the model in the configuration and the matching key has to be in the environment.
Can DeepSearcher read web pages, or only local files?
The feature list says local file loading with web crawling capabilities under development. The package still ships firecrawl-py>=2.5.3 as a core dependency and examples/load_website_using_firecrawl.py, and the docling, unstructured and crawl4ai loaders are optional extras.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/zilliztech-deep-searcher)Star history chart for your README
[](https://hysenlabs.com/projects/zilliztech-deep-searcher)Was this useful?