-
Notifications
You must be signed in to change notification settings - Fork 21
Cross-collection agentic retrieval + live schema discovery #150
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
970dd29
53db022
9db5640
905acc0
43eb203
03aa2e0
dc0d8fc
a7d8b2f
2bb468a
4e2499d
6f4a799
ab601aa
0be3253
a45e09d
cda73a5
d25a17b
3f920b5
d1145f2
438c296
b53242f
36b8553
a4d71f3
0351bdd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,84 @@ | ||
| # ============================================================================= | ||
| # Cosmos Retriever configuration (Python service) | ||
| # ============================================================================= | ||
| # Every setting read by `RetrieverSettings` (config.py) is listed here with its | ||
| # default. Values load from environment variables or a `.env` / `.env.local` file | ||
| # at the repo root. Required keys are uncommented with placeholders; optional keys | ||
| # are commented out showing their default. Variable names are case-insensitive. | ||
| # | ||
| # NOTE: this file configures the *Python retriever service*. The .NET MCP server | ||
| # uses the separate top-level `../.env.example`. | ||
|
|
||
| # ----- Inference backend ----- | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Have you done end-to-end testing with all three APIs?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we have a repro/test files for these? |
||
| # "openai_responses" (default): OpenAI-compatible /responses model (reasoning | ||
| # models such as gpt-5.x). | ||
| # "openai_chat": OpenAI-compatible /chat/completions model (Azure AI Foundry | ||
| # deployment, OpenAI, local server, ...). | ||
| # "anthropic_messages": Anthropic Messages API (e.g. Claude on Azure AI Foundry). | ||
| INFERENCE_BACKEND=openai_responses | ||
|
|
||
| # ----- LLM endpoint (drives the retrieval agent) ----- | ||
| # For Azure AI Foundry: CHAT_BASE_URL is the endpoint URL, CHAT_MODEL the | ||
| # deployment name. Set CHAT_API_VERSION to use the Azure OpenAI client. | ||
| CHAT_BASE_URL=https://your-resource.services.ai.azure.com/openai/v1 | ||
| CHAT_API_KEY= | ||
| CHAT_MODEL=gpt-5.2 | ||
| # CHAT_API_VERSION= | ||
| # CHAT_TEMPERATURE=0.7 # sampling temperature (chat backend) | ||
| # CHAT_MAX_TOKENS=4096 # max output tokens per model turn | ||
| # CHAT_MAX_TURNS=20 # max model<->tool round-trips per search | ||
| # CHAT_REASONING_EFFORT= # low|medium|high (openai_responses reasoning models only) | ||
| # anthropic_messages only: | ||
| # ANTHROPIC_VERSION=2023-06-01 | ||
| # ANTHROPIC_AUTH_HEADER=x-api-key | ||
|
|
||
| # ----- Cosmos DB target (required) ----- | ||
| ACCOUNT_URI=https://your-cosmos-account.documents.azure.com:443/ | ||
| COSMOS_DATABASE=your-database-name | ||
| COSMOS_CORPUS_CONTAINER=your-corpus-container | ||
| # COSMOS_KEY= # unset -> AzureCliCredential (default) | ||
| # COSMOS_USE_DEFAULT_CREDENTIAL=false # true -> use the DefaultAzureCredential chain | ||
|
|
||
| # ----- Embeddings for SearchCorpusTool (required) ----- | ||
| # Default embedding endpoint/key/model, used when a corpus is NOT in the registry. | ||
| OPENAI_API_KEY=sk-... | ||
| OPENAI_EMBEDDING_MODEL=text-embedding-3-small | ||
| # EMBED_ENDPOINT= # OpenAI (api.openai.com) if unset. For Azure pass | ||
| # # https://<resource>.services.ai.azure.com/openai/v1; | ||
| # # for a local server pass http://host:port/v1 | ||
| # OPENAI_EMBEDDING_DIMENSIONS= # request truncated (MRL) output dims, e.g. 2560 to | ||
| # # match a Qwen3-Embedding corpus. Unset = model native. | ||
| # EMBED_QUERY_INSTRUCTION= # optional "Instruct:" prefix (some Qwen embedders) | ||
|
|
||
| # ----- Per-corpus embedding registry (optional) ----- | ||
| # Map a container to its own account / database / embedding endpoint+model+dims. | ||
| # Provide ONE of these. A registry entry references its key via `embed_api_key_env` | ||
| # (any env var name you choose, e.g. AZURE_OPENAI_EMBED_API_KEY below). | ||
| # CORPUS_REGISTRY_FILE=corpus_registry.json | ||
| # CORPUS_REGISTRY={"db/container": {"account_uri": "...", "embed_model": "..."}} | ||
| # AZURE_OPENAI_EMBED_API_KEY= # example key referenced by a registry entry | ||
|
|
||
| # ----- Reranker (optional; pick at most one) ----- | ||
| # BASETEN_API_KEY= # Baseten Qwen3-Reranker-8B classify | ||
| # BASETEN_MODEL_URL=https://model-xyz.api.baseten.co/environments/production/sync | ||
| # VLLM_RERANKER_URL=http://127.0.0.1:8011 # local vLLM Qwen3-Reranker /score | ||
|
|
||
| # ----- Retriever budgets & limits (optional) ----- | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a great config file btw. Defaults mentioned, non-necessary commented out, scoped by section etc etc. Good job. |
||
| # COSMOS_RETRIEVER_MAX_TURNS=35 # hard cap on agent turns | ||
| # COSMOS_RETRIEVER_THRESHOLD_BUDGET=16384 # soft cap: prune-or-conclude kicks in | ||
| # COSMOS_RETRIEVER_TOKEN_BUDGET=32268 # hard cap on transcript tokens | ||
| # COSMOS_RETRIEVER_SEARCH_DISPLAY_LIMIT=15 # rows shown per search result | ||
| # COSMOS_RETRIEVER_RAW_QUERY_ENABLED=true # expose the read-only execute_query tool | ||
| # COSMOS_RETRIEVER_SCHEMA_OVERRIDE= # JSON: document_id_path, chunk_order_path, ... | ||
| # Note: the per-tool output clamp (~4096) and spillage fraction (0.5) are code-level | ||
| # constants in agent_loop.py (_DEFAULT_TOOL_OUTPUT_BUDGET / _DEFAULT_SPILLAGE_FRACTION), | ||
| # not env-configurable. | ||
|
|
||
| # ----- Retriever pool cache (optional) ----- | ||
| # COSMOS_RETRIEVER_CACHE_MAX_ENTRIES=32 # max pooled retriever engines (LRU) | ||
| # COSMOS_RETRIEVER_CACHE_TTL_SECONDS=900.0 # engine TTL (seconds) before rebuild | ||
|
|
||
| # ----- HTTP server ----- | ||
| HOST=0.0.0.0 | ||
| PORT=9000 | ||
| LOG_LEVEL=info | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sajeetharan (@sajeetharan) not sure if this github workflows file needs to be present in the MCP repo. Usually, if this was a monolithic repo I would include it as it is needed for replication of venvs but if it is being merged to this MCP Toolkit I am not sure if it should be included. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| name: ci | ||
|
|
||
| on: | ||
| push: | ||
| branches: [main] | ||
| pull_request: | ||
|
|
||
| concurrency: | ||
| group: ci-${{ github.ref }} | ||
| cancel-in-progress: true | ||
|
|
||
| jobs: | ||
| lint-and-test: | ||
| runs-on: ubuntu-latest | ||
| strategy: | ||
| matrix: | ||
| python-version: ["3.11", "3.12"] | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
|
|
||
| - uses: actions/setup-python@v5 | ||
| with: | ||
| python-version: ${{ matrix.python-version }} | ||
|
|
||
| - name: Install uv | ||
| uses: astral-sh/setup-uv@v3 | ||
| with: | ||
| enable-cache: true | ||
|
|
||
| - name: Install package with dev extras | ||
| run: uv pip install --system -e ".[dev]" | ||
|
|
||
| - name: Ruff lint | ||
| run: ruff check src tests | ||
|
|
||
| - name: Pytest | ||
| run: pytest -q |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| # --- Python --- | ||
| __pycache__/ | ||
| *.py[cod] | ||
| *$py.class | ||
| *.egg-info/ | ||
| .eggs/ | ||
| build/ | ||
| dist/ | ||
| .coverage | ||
| .coverage.* | ||
| htmlcov/ | ||
| .pytest_cache/ | ||
| .mypy_cache/ | ||
| .ruff_cache/ | ||
|
|
||
| # --- Virtual envs --- | ||
| .venv/ | ||
| venv/ | ||
| env/ | ||
|
|
||
| # --- IDE --- | ||
| .vscode/ | ||
| .idea/ | ||
| *.swp | ||
|
|
||
| # --- Secrets / local config --- | ||
| .env | ||
| .env.local | ||
| .env.*.local | ||
| .env.* | ||
| !.env.example | ||
|
|
||
| # --- Logs / scratch --- | ||
| *.log | ||
| tmp/ | ||
| runs/ | ||
|
|
||
| # --- Build artefacts --- | ||
| src/*.egg-info/ |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,158 @@ | ||
| # Cosmos Retriever (Python helper) | ||
|
|
||
| This package runs a multi-turn search agent against an Azure Cosmos DB corpus and | ||
| returns the curated documents as JSON. The agent is model-agnostic: it drives any | ||
| OpenAI-compatible endpoint (the `/responses` or `/chat/completions` APIs) or an | ||
| Anthropic Messages endpoint. The same code is available three ways, an importable | ||
| Python package (`CosmosRetriever`), a FastAPI service | ||
| (`python -m cosmos_retriever serve`), and a one-shot CLI | ||
| (`python -m cosmos_retriever search`). | ||
|
|
||
| The [Azure Cosmos DB MCP Toolkit](../MCPToolKit/)'s `agentic_search` tool calls | ||
| this service's `POST /search` endpoint over HTTP. | ||
|
|
||
| ```text | ||
| Claude Desktop / AI Foundry / VS Code | ||
| │ | ||
| │ MCP streamable-HTTP | ||
| ▼ | ||
| Azure Cosmos DB MCP Toolkit (.NET) | ||
| ├─ list_databases / list_collections / ... (8 native tools) | ||
| └─ agentic_search ◀─── 9th tool | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is great! |
||
| │ | ||
| │ HTTP: POST http://127.0.0.1:9000/search | ||
| ▼ | ||
| cosmos_retriever (this package, FastAPI + uvicorn) | ||
| ├─ TokenBudgetRetrievalSubagent | ||
| ├─ SearchCorpus / Grep / ReadDocument / PruneChunks tools | ||
| └─ VLLMHarmonyInferenceModel ──► vLLM /v1/completions (token-IDs) | ||
| Cosmos DB hybrid RRF | ||
| Azure OpenAI embeddings | ||
| Qwen3-Reranker (Baseten or local vLLM) | ||
| ``` | ||
|
|
||
| ## Install | ||
|
|
||
| ```bash | ||
| cd cosmos-retriever | ||
| uv venv --python 3.11 .venv | ||
| uv pip install --python .venv/bin/python -e ".[dev]" | ||
| ``` | ||
|
|
||
| ## HTTP service | ||
|
|
||
| The MCP Toolkit talks to a long-lived FastAPI service. Start it with: | ||
|
|
||
| ```bash | ||
| python -m cosmos_retriever serve # binds HOST:PORT (default 0.0.0.0:9000) | ||
| ``` | ||
|
|
||
| Endpoints: | ||
|
|
||
| | Method & path | Body / response | | ||
| |---|---| | ||
| | `GET /health` | `{"status": "ok"}` | | ||
| | `POST /search` | request `{"query": str, "maxDocuments": int, "database": str?, "container": str?}` → the JSON result below | | ||
|
|
||
| Example request to test a running service (the query and its answer depend on the corpus you configured): | ||
|
|
||
| ```bash | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is to test? Please say so? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "Who discovered radium?" depends on the configured readme? |
||
| curl -s http://127.0.0.1:9000/search \ | ||
| -H 'content-type: application/json' \ | ||
| -d '{"query": "Who discovered radium?", "maxDocuments": 5}' | ||
| ``` | ||
|
|
||
| ## CLI | ||
|
|
||
| To smoke-test locally, use the command below to query the service with a single | ||
| question and print the answer documents. JSON goes to **stdout**, logs go to | ||
| **stderr**. | ||
|
|
||
| ```bash | ||
| python -m cosmos_retriever search \ | ||
| --query "Who discovered radium?" \ | ||
| --max-documents 5 | ||
| ``` | ||
|
|
||
| Expected output (same schema returned by `POST /search`): | ||
| ```json | ||
| { | ||
| "query": "Who discovered radium?", | ||
| "num_turns": 5, | ||
| "elapsed_s": 32.3, | ||
| "documents": [ | ||
| { "id": "96308__3", "rank": 0, "justification": "...", "text": "..." } | ||
| ] | ||
| } | ||
| ``` | ||
|
|
||
| ## Configuration | ||
|
|
||
| All settings come from environment variables, or from a `.env` / `.env.local` | ||
| file in the `cosmos-retriever/` directory. Precedence is real environment | ||
| variables first, then `.env.local`, then `.env`. Use `.env.local` for local | ||
| secrets and overrides, it is gitignored. Required settings: | ||
|
|
||
| | Variables | Purpose | | ||
| |---|---| | ||
| | `INFERENCE_BACKEND`, `CHAT_BASE_URL`, `CHAT_API_KEY`, `CHAT_MODEL` | The backend, endpoint, key, and model for the LLM that drives the agent (see Inference backend below) | | ||
| | `ACCOUNT_URI`, `COSMOS_DATABASE`, `COSMOS_CORPUS_CONTAINER` | The Cosmos account, database, and container to search | | ||
| | `OPENAI_API_KEY`, `OPENAI_EMBEDDING_MODEL` | The embeddings key and model (set `EMBED_ENDPOINT` for Azure or a local server) | | ||
|
|
||
| Each row is a group of related settings, not alternatives. See | ||
| [`.env.example`](.env.example) for the complete list and defaults. | ||
|
|
||
| ### Inference backend | ||
|
|
||
| `INFERENCE_BACKEND` selects what drives the retrieval agent: | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is this not part of the first row of variables from the previous section? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
|
||
| | Value | Model | Endpoint vars | | ||
| |---|---|---| | ||
| | `openai_responses` *(default)* | Any OpenAI-compatible `/responses` model (reasoning models such as gpt-5.x). | `CHAT_BASE_URL`, `CHAT_API_KEY`, `CHAT_MODEL`, optional `CHAT_API_VERSION` | | ||
| | `openai_chat` | Any OpenAI-compatible `/chat/completions` model (Azure AI Foundry deployment, OpenAI, local server, ...). | `CHAT_BASE_URL`, `CHAT_API_KEY`, `CHAT_MODEL`, optional `CHAT_API_VERSION` | | ||
| | `anthropic_messages` | Any Anthropic Messages API endpoint — e.g. Claude on Azure AI Foundry (served over the Messages API, not OpenAI-shaped). | `CHAT_BASE_URL`, `CHAT_API_KEY`, `CHAT_MODEL`, optional `ANTHROPIC_VERSION`, `ANTHROPIC_AUTH_HEADER` | | ||
|
|
||
| All backends drive the same Cosmos tools, so retrieval quality depends on the | ||
| chosen model's tool-use ability. Example (Azure AI Foundry): | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. great clarification |
||
|
|
||
| ```bash | ||
| INFERENCE_BACKEND=openai_chat \ | ||
| CHAT_BASE_URL=https://your-resource.services.ai.azure.com/openai/v1 \ | ||
| CHAT_API_KEY=... \ | ||
| CHAT_MODEL=gpt-4o \ | ||
| python -m cosmos_retriever serve | ||
| ``` | ||
|
|
||
| ### Optional reranker | ||
|
|
||
| An independent reranker model can be configured to reorder the retrieved | ||
| documents by relevance before they are returned, which improves the quality of | ||
| the final ranking. It is optional. Without it, the agent keeps the raw retrieval | ||
| order. Configure at most one of: | ||
|
|
||
| - `VLLM_RERANKER_URL`, a local vLLM `/score` endpoint serving Qwen3-Reranker-8B. | ||
| - `BASETEN_API_KEY` and `BASETEN_MODEL_URL`, a Baseten Qwen3-Reranker-8B deployment. | ||
|
|
||
| ## Layout | ||
|
|
||
| ```text | ||
| src/cosmos_retriever/ | ||
| __init__.py # CosmosRetriever, RetrievalResult, RetrievedDocument | ||
| __main__.py # `python -m cosmos_retriever {search,serve}` | ||
| server.py # FastAPI app: GET /health + POST /search | ||
| retriever.py # CosmosRetriever facade | ||
| agent.py # 3 agent classes + prune_chunks_from_trajectory | ||
| tools.py # SearchCorpus / Grep / ReadDocument / PruneChunks | ||
| trajectory.py # Action / Observation / Trajectory + Harmony rendering | ||
| rerank.py # Reranker ABC + Baseten + local-vLLM | ||
| inference/ | ||
| base.py # AgentInferenceModel ABC | ||
| vllm.py # VLLMHarmonyInferenceModel (httpx → /v1/completions) | ||
| prompts.py # retrieval subagent system prompt | ||
| config.py # RetrieverSettings (pydantic-settings) | ||
| utils.py | ||
| ``` | ||
|
|
||
| ## License | ||
|
|
||
| MIT — this package is covered by the repository's top-level [LICENSE](../LICENSE). | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is an
.env.examplein cosmos-retriever and at the top level?Also expose defaults like prune budget here -- up to you
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, there are two separate .env.example files because they configure two separate services. the top-level file is for the .net mcp server, while cosmos-retriever/.env.example is for the python retriever service. there is also a separate one for the sample client.
i updated the python .env.example to make it a complete reference for the service rather than only showing the minimum required variables. it now includes every configurable setting, grouped by llm, cosmos, embeddings, corpus registry, reranking, budgets, cache, and server settings, with the defaults documented alongside them.
this includes the threshold and token budgets, search limits, max turns, cache sizing, schema overrides, and the other settings that were previously missing. i also corrected a few stale examples that referenced variables the python service does not actually read.
the lower-level prune settings, such as the per-tool output budget and spillage fraction, were previously code-only constants. i have exposed those as config settings as well so the .env.example can serve as the single place people refer to for all defaults instead of having to inspect the implementation.