Metadata-Version: 2.5
Name: schemarouter
Version: 0.10.0
Summary: Typed capability retrieval, planning, and execution layer for LLM and RAG tool ecosystems
Project-URL: Homepage, https://github.com/JDeun/SchemaRouter
Project-URL: Repository, https://github.com/JDeun/SchemaRouter
Project-URL: Documentation, https://jdeun.github.io/SchemaRouter/
Project-URL: Issues, https://github.com/JDeun/SchemaRouter/issues
Project-URL: Research, https://github.com/JDeun/paper_SchemaRouter
Project-URL: Brand, https://jdeun.github.io/SchemaRouter/project/brand/
Author: Yong-eun Cho
License-Expression: MIT
License-File: LICENSE
Keywords: agents,llm,mcp,openapi,optimade,schema,tool-routing
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: httpx<1,>=0.27
Requires-Dist: jsonschema<5,>=4.23
Requires-Dist: pydantic<3,>=2.8
Requires-Dist: pyyaml<7,>=6.0
Provides-Extra: dev
Requires-Dist: build>=1.2; extra == 'dev'
Requires-Dist: hypothesis<7,>=6.130; extra == 'dev'
Requires-Dist: pyright<2,>=1.1; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest-cov<8,>=5; extra == 'dev'
Requires-Dist: pytest>=8.2; extra == 'dev'
Requires-Dist: ruff>=0.6; extra == 'dev'
Requires-Dist: twine>=5; extra == 'dev'
Provides-Extra: docs
Requires-Dist: mkdocs-material<10,>=9.7; extra == 'docs'
Requires-Dist: mkdocstrings-python<3,>=2; extra == 'docs'
Requires-Dist: mkdocstrings<2,>=1; extra == 'docs'
Provides-Extra: jev
Requires-Dist: typesafe-sdk<1,>=0.7; extra == 'jev'
Provides-Extra: langchain
Requires-Dist: langchain-core<2,>=1.6; extra == 'langchain'
Provides-Extra: langgraph
Requires-Dist: langgraph<2,>=1.2; extra == 'langgraph'
Provides-Extra: laya
Requires-Dist: laya<1,>=0.3.6; extra == 'laya'
Provides-Extra: llamaindex
Requires-Dist: llama-index-core<1,>=0.14; extra == 'llamaindex'
Provides-Extra: mcp
Requires-Dist: mcp<3,>=2; extra == 'mcp'
Provides-Extra: otel
Requires-Dist: opentelemetry-api<2,>=1.44; extra == 'otel'
Requires-Dist: opentelemetry-sdk<2,>=1.44; extra == 'otel'
Provides-Extra: systemone
Requires-Dist: typesafe-sdk<1,>=0.7; extra == 'systemone'
Description-Content-Type: text/markdown

<p align="center">
  <picture>
    <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/JDeun/SchemaRouter/main/docs/assets/brand/schemarouter-lockup-dark.svg">
    <img alt="SchemaRouter" src="https://raw.githubusercontent.com/JDeun/SchemaRouter/main/docs/assets/brand/schemarouter-lockup-light.svg" width="680">
  </picture>
</p>

<p align="center"><strong>Schema-aware planning and execution for LLM tool ecosystems.</strong></p>

<p align="center">
  <a href="README.md">English</a> ·
  <a href="README.ko.md">한국어</a> ·
  <a href="https://jdeun.github.io/SchemaRouter/">Docs</a> ·
  <a href="https://github.com/JDeun/SchemaRouter/releases/latest">Latest release</a>
</p>

<p align="center">
  <a href="https://github.com/JDeun/SchemaRouter/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/JDeun/SchemaRouter/actions/workflows/ci.yml/badge.svg"></a>
  <a href="https://github.com/JDeun/SchemaRouter/actions/workflows/docs.yml"><img alt="Docs" src="https://github.com/JDeun/SchemaRouter/actions/workflows/docs.yml/badge.svg"></a>
  <a href="https://pypi.org/project/schemarouter/"><img alt="PyPI" src="https://img.shields.io/pypi/v/schemarouter"></a>
  <a href="https://github.com/JDeun/SchemaRouter/blob/main/LICENSE"><img alt="MIT" src="https://img.shields.io/badge/License-MIT-yellow.svg"></a>
</p>

SchemaRouter sits between an agent/RAG application and its data-capable tools. It parses registered
OpenAPI, MCP, OPTIMADE, Python, or plugin capabilities into a typed catalog, retrieves the smallest
declared executable data surface that can satisfy a request, and validates the resulting plan again
at runtime before anything executes.

```text
Query
  -> Tool
  -> Endpoint
  -> Parameters
  -> Response fields
  -> Policy / evidence
  -> Schema validation
  -> Execute
```

It is **not** another general agent framework. LangChain, LangGraph, LlamaIndex, or your own
orchestrator can stay above it; OpenAPI, MCP, OPTIMADE, Python callables, and adapter plugins stay
below it.

### Mental model: RAG for executable capabilities

A conventional RAG stack parses documents into chunks + metadata, indexes them, and retrieves the
smallest relevant context. SchemaRouter applies the same separation to APIs and tools:

```text
documents -> parser -> chunks/metadata -> index -> retriever -> RAG
APIs/tools -> adapter -> endpoint/field contracts -> registry/index -> SchemaRouter -> RAG/agent
```

The retrieved unit is executable: endpoint operation, inputs, output fields, datatype/unit contracts,
policy/evidence and availability. The registry is a logical capability graph; it does not require a
graph database. Registered schema remains authority even when embeddings or optional decision
backends help search it.

[Capability catalog and RAG analogy](https://jdeun.github.io/SchemaRouter/concepts/capability-catalog/)

### Field-first, route-second

SchemaRouter first asks **which declared data fields are actually needed to answer the request**,
then chooses a provider/access path that can supply those fields. When an endpoint explicitly
supports server-side projection, only the planned fields are requested upstream; after raw schema
validation, final local projection keeps the downstream LLM context narrow even if a provider sends
extra data.

Availability may change the route, but it does not broaden the data need. Precompiled read-only
fallbacks can move from one access mode to another—and, when explicitly enabled, to another
provider—without turning runtime into an autonomous agent loop.

```text
Agent / graph / application orchestrator
                 |
           SchemaRouter
     typed planning + validation
                 |
        capability sources
 OpenAPI / MCP / OPTIMADE / Python

Optional decision backends (Laya / Ollama / Jev / System One-compatible providers) plug into
SchemaRouter's bounded selection step. Jev-compatible models can be swapped by configuration through
`SystemOneDecisionBackend`; other trusted bounded runtimes can enter through
`CallableDecisionBackend`. They do not become agents, do not run tool loops, and do not receive
execution authority.
```

> **Current stable release: 0.10.0** · `pip install schemarouter` · pre-1.0

## Why SchemaRouter

Tool selection alone is not enough once an agent has many capabilities. SchemaRouter makes the
execution boundary explicit:

- choose a declared tool and endpoint;
- accept only declared parameters and output fields;
- validate inputs and raw outputs with JSON Schema;
- reject stale schema fingerprints and stale invoker bindings;
- keep mutation/destructive authority local and fail closed;
- separate credentials from model-visible arguments;
- bound retries, elapsed time, remote calls, and response size;
- surface OpenAPI compatibility gaps instead of silently guessing.

## Real-world scenario

<p align="center">
  <img src="docs/assets/real-world-scenario.svg" alt="SchemaRouter real-world scenario: field-first, route-second" width="100%">
</p>

A single request can require fields from different providers. For example, a materials question may
need a numeric `band_gap` from Materials Project and a text `abstract` from arXiv.

SchemaRouter resolves the **semantic field need first**, then spends the bounded `max_calls` budget
on complementary provider/access paths that can satisfy those fields. A route can change because of
health, policy, or availability; the required data contract does not.

```text
band_gap  -> Materials Project / OpenAPI
             ↳ OPTIMADE fallback
abstract  -> arXiv API
```

The final execution plan remains schema-validated, policy-bounded, and fail-closed. When one route
already covers every matched field, SchemaRouter can stop before reaching the `max_calls` bound.

## Quickstart

```python
from pydantic import BaseModel
from schemarouter import PlanRequest, SchemaRouter, schema_tool


class Weather(BaseModel):
    city: str
    temperature: float


@schema_tool(read_only=True)
def current_weather(city: str) -> Weather:
    return Weather(city=city, temperature=20.5)


router = SchemaRouter()
router.add_callable(current_weather)

result = router.invoke(
    PlanRequest(query="city temperature", arguments={"city": "Seoul"})
)

print(result[0].data)
```

## Connect capabilities

| Source | Use when | Entry point |
| --- | --- | --- |
| **Python** | capability is local and typed | `router.add_callable(...)` |
| **OpenAPI** | HTTP API publishes a machine-readable contract | `SchemaRouter.from_url(..., kind="openapi")` |
| **MCP** | tools are exposed through MCP | `SchemaRouter.from_url(..., kind="mcp")` |
| **OPTIMADE** | materials data is exposed through OPTIMADE | `SchemaRouter.from_url(..., kind="optimade")` |
| **Human-readable docs** | no machine-readable schema exists | inspect → proposal → explicit approval |

Framework bridges are available for **LangChain, LangGraph, and LlamaIndex**. **OpenTelemetry**
provides optional telemetry export. **Jev / TypeSafe, System One-compatible providers, Laya, and
Ollama are optional decision backends**, not agent frameworks. Jev-wire-compatible models can reuse
`SystemOneDecisionBackend` by changing provider/model configuration rather than adding a planner
class. Existing **GPT, Gemini, Claude, or other hosted/local decision clients** can also be injected
through the provider-neutral `ModelQueryAnalyzer` or `CallableDecisionBackend` contracts. None of
these paths bypass SchemaRouter's policy, schema validation, or execution boundary.

Decision models are replaceable without moving execution authority into the model:

- Jev/System-One-compatible servers use `SystemOneDecisionBackend` and can swap
  `base_url/model/provider_name` by configuration;
- one-off local or hosted experiments can use `CallableDecisionBackend`;
- reusable third-party runtimes can publish a `schemarouter.decision_backends` entry-point plugin.

SchemaRouter still validates the returned finite option ID locally. See the
[decision backend plugin guide](https://jdeun.github.io/SchemaRouter/integrations/decision-backend-plugins/)
and [decision-model ecosystem intake](https://jdeun.github.io/SchemaRouter/integrations/decision-model-ecosystem/).

## What 0.6 adds

0.6 adds optional local/model-assisted decision backends, operational inspection/dashboard
surfaces, and a broader fail-closed OpenAPI subset:

- local Laya decisions with CPU/CUDA/MPS device controls and benchmark metadata;
- provider-neutral reuse of existing GPT, Gemini, Claude, or other hosted clients;
- live/persistent inspection plus a self-contained read-only HTML dashboard;
- response `oneOf`/`anyOf` field discovery and static same-origin `$id`/`$anchor` resolution;
- typed JSON root request bodies, OpenAPI 3.0 nullable normalization, and default parameter-style
  serialization.

See the [0.6.0 release notes](https://jdeun.github.io/SchemaRouter/releases/0.6.0/) for details.

## What 0.7 adds

Version `0.7.0` strengthens the same narrow execution boundary rather than adding agent orchestration:

- conservative schema-drift explanations while exact fingerprints still fail closed;
- operation-scoped local allow/deny/approval policy rules;
- structured, auditable plan explanations based on SchemaRouter-visible signals;
- explicit flat parallel fan-out only when every planned call is currently trusted read-only;
- provider/access identity, bounded read-only fallback, server-side field projection contracts,
  and recoverable access-path health state;
- typed scientific result contracts with explicit JSON datatypes, optional unit metadata, canonical
  affine unit normalization, and exact measurement/material qualifiers such as temperature, phase,
  orientation, or method;
- qualifier-aware routing that can distinguish otherwise equivalent scientific fields when the
  condition is explicitly present in the query, without unit conversion or scientific inference;
- trusted parameter aliases so the same logical argument can bind safely to provider-specific local
  parameter names without model-authored remapping.

Workflow/DAG semantics, memory, prompt systems, and autonomous tool loops remain out of scope.

See the [0.7.0 release notes](https://jdeun.github.io/SchemaRouter/releases/0.7.0/) for details.

## What 0.10 consolidates

Version `0.10.0` is a consolidation release rather than a claim that open-set routing is solved.

- clarifies SchemaRouter as a typed capability retrieval/execution layer for agent and RAG systems;
- keeps **field-first, route-second** planning as the stable product architecture;
- treats JSON datatype/shape, semantic IDs, optional units, explicit unit normalization, and exact
  scientific qualifiers as part of the registered data contract;
- keeps OpenAPI/MCP/OPTIMADE/Python registration provider-neutral rather than benchmark-specific;
- keeps learned decision backends bounded and optional;
- publishes the current routing benchmark outcome honestly: the strongest DEV candidate passed the
  standing target, but its frozen zero-overlap fresh confirmation failed, so no production-target
  candidate was promoted.

The successor registry-compiled capability verifier remains a research prototype and is **not** the
0.10.0 default.

[0.10.0 release notes](https://jdeun.github.io/SchemaRouter/releases/0.10.0/) ·
[Routing research status](https://jdeun.github.io/SchemaRouter/research/routing-status/)

## What 0.9 adds

Version `0.9.0` adds a provider-neutral bounded pairwise scoring backend for applications that want
cross-encoder or reranker-style decisions without moving execution authority into the model.

- `PairwiseDecisionBackend` scores only the locally authorized `(query, option)` pairs;
- opaque option IDs remain local and model/scorer output cannot invent a route;
- malformed, non-finite, out-of-range, or wrong-length score batches fail closed;
- sync and async scorers are supported without adding Torch, Transformers, or a specific model to
  SchemaRouter core;
- benchmark metadata now records pairwise scoring configuration for reproducibility.

The frozen BGE pairwise candidate was evaluated exactly once on the fresh 600-case v11 holdout. It
reached **51.667% overall accuracy**, **25.781% supported-operation routed accuracy**, **97.396%
near-domain unsupported-operation rejection**, and **100% ordinary OOD rejection**. A post-consumption
MiniLM 0.40 diagnostic on the same corpus reached **54.833% overall**, **40.625% supported**, and
**77.604% near-domain rejection**. BGE improved the equal-weight supported/rejection balanced score,
but materially reduced supported recall and more than doubled CPU latency, so it is **not** the
library default.

v11 is consumed evidence and is not eligible for further threshold/model selection.

See the [0.9.0 release notes](https://jdeun.github.io/SchemaRouter/releases/0.9.0/) and the
[decision routing benchmark guide](https://jdeun.github.io/SchemaRouter/guides/decision-benchmark/).

## Inspect what SchemaRouter built

Persisted registries and run traces can be inspected without executing tools:

```bash
schemarouter inspect registry --db ./registry.sqlite3
schemarouter inspect tool materials --db ./registry.sqlite3
schemarouter inspect diff materials \
  --old-db ./registry-before.sqlite3 \
  --new-db ./registry-current.sqlite3
schemarouter inspect traces --db ./traces.sqlite3
schemarouter inspect trace <RUN_ID> --db ./traces.sqlite3
schemarouter dashboard \
  --registry ./registry.sqlite3 \
  --traces ./traces.sqlite3 \
  --output ./artifacts/schemarouter-dashboard.html
```

Add `--json` to inspection commands for automation. The dashboard is a self-contained read-only
HTML export built from the same inspection contracts. The registry view exposes tool/endpoint
topology, method/path, mutation classification, parameter/output-field counts, and schema
fingerprints; trace views expose persisted run/event history.

[Operational inspection guide](https://jdeun.github.io/SchemaRouter/guides/inspection/)

## Documentation

Start with the manual rather than this README:

- [Install and quickstart](https://jdeun.github.io/SchemaRouter/getting-started/installation/)
- [Understand the execution model](https://jdeun.github.io/SchemaRouter/concepts/schema-router/)
- [Read the design principles](https://jdeun.github.io/SchemaRouter/concepts/design-principles/)
- [OpenAPI guide](https://jdeun.github.io/SchemaRouter/guides/openapi/)
- [Runtime policy and retry](https://jdeun.github.io/SchemaRouter/guides/execution-policy/)
- [Framework integrations](https://jdeun.github.io/SchemaRouter/integrations/langchain/)
- [API reference](https://jdeun.github.io/SchemaRouter/reference/api/)
- [Architecture and maturity](https://jdeun.github.io/SchemaRouter/architecture/)
- [Security model](https://jdeun.github.io/SchemaRouter/security/threat-model/)

## Development

```bash
python -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"

ruff check .
pytest -q -m "not mcp_integration"
```

The protected CI surface also covers Python 3.10–3.14, Windows, minimum dependencies, package
artifacts, Pyright, coverage, documentation, and optional integration suites.

## Scope

SchemaRouter intentionally does **not** implement another chat abstraction, model-provider layer,
memory system, checkpoint store, or graph runtime.

> **Natural-language request → typed tool execution plan → validated execution.**

## Research and license

SchemaRouter originated from
[SchemaRouter: Field-Aware Tool Routing for Efficient Heterogeneous Agentic RAG](https://github.com/JDeun/paper_SchemaRouter).

[MIT](LICENSE) © 2026 Yong-eun Cho
