Transparent, cost-optimal tool routing via EU maximisation
Project description
credence-router
Transparent, cost-optimal tool routing via expected-utility maximisation.
Instead of asking an LLM to pick the right tool (and paying for that routing call), credence-router uses Bayesian decision theory to route questions to the cheapest reliable tool — with zero routing cost and sub-millisecond routing latency.
Benchmark (50 questions, simulated tools)
| Agent | Accuracy | Cost | Tools/Q |
|---|---|---|---|
| credence-router | 79.3% | $0.005 | 1.06 |
| langgraph-react | 60.9% | $0.312 | 1.96 |
| always-best | 74.0% | $0.250 | 1.00 |
| always-cheapest | 44.0% | $0.000 | 1.00 |
| random | 56.0% | $0.078 | 1.00 |
credence-router achieves higher accuracy than LangGraph at 1.6% of the cost, because it learns which tools are reliable for which question categories and only queries expensive tools when the expected value of information justifies it.
Installation
pip install credence-router
With optional API backends:
pip install credence-router[anthropic] # Claude (Haiku/Sonnet)
pip install credence-router[perplexity] # Perplexity web search
pip install credence-router[all] # everything
Quick start
from credence_router import Router
from credence_router.tools.calculator import CalculatorTool
from credence_router.tools.claude import ClaudeTool
# Define your tool palette — the router learns which to use
router = Router(tools=[
CalculatorTool(), # free, instant, numerical only
ClaudeTool("haiku"), # $0.0003/query, general-purpose
ClaudeTool("sonnet"), # $0.001/query, higher accuracy
])
# Route a question — returns Answer with choice, confidence, cost, trace
answer = router.solve(
question="What is 15% of 240?",
candidates=("32", "36", "40", "44"),
)
print(answer.choice_text) # "36"
print(answer.tools_used) # ("calculator",)
print(answer.monetary_cost) # 0.0
# Report ground truth so the router learns
router.report_outcome(correct=True)
# Save/load learned reliability across sessions
router.save_state("router_state.json")
How it works
Each question goes through a Value of Information (VOI) loop:
- Classify the question into a category (factual, numerical, reasoning, ...)
- Calculate the expected utility of submitting now vs. querying each tool
- Query the tool with highest net VOI (if any tool's VOI exceeds its cost)
- Update beliefs with the tool's response, repeat from step 2
- Submit when no tool's VOI justifies its cost
The router maintains a reliability table — P(correct | tool, category) — learned
from outcome feedback. It starts with prior coverage estimates and refines them
with every report_outcome() call.
CLI
Benchmark
# Run full benchmark with simulated tools
credence-router bench --run --simulate
# Explain specific questions (1-indexed)
credence-router bench --run --simulate --explain 1,5,10
# Show learned reliability table
credence-router bench --run --simulate --show-reliability
Route a single question
# With real tools (needs ANTHROPIC_API_KEY)
credence-router route "What is the capital of France?" -o "London" "Paris" "Berlin" "Madrid"
# Force simulated tools
credence-router route "What is 2+2?" -o "3" "4" "5" "6" --simulate
API
Router(tools, categories=..., scoring=..., latency_weight=...)
The main entry point. Takes a list of Tool instances and routes questions optimally.
tools: List of objects implementing theToolprotocolcategories: Tuple of category names (default: 5 built-in categories)scoring:ScoringRule(reward_correct, penalty_wrong, reward_abstain)latency_weight: Cost per second of latency (default: 0.0)
Tool protocol
class Tool(Protocol):
name: str
cost: float # $/query
latency: float # seconds
def query(self, question: str, candidates: tuple[str, ...]) -> int | None: ...
def coverage(self, categories: tuple[str, ...]) -> NDArray[np.float64]: ...
Answer
Returned by router.solve():
choice: candidate index (orNoneif abstained)choice_text: the answer stringconfidence: posterior probabilitytools_used: tuple of tool names queriedmonetary_cost/effective_cost: cost breakdownreasoning: human-readable VOI trace
Try it: LangGraph vs credence-router (Ollama)
Run a side-by-side comparison locally with zero API costs:
uv sync --extra demo
ollama pull llama3.1
uv run python examples/langgraph_comparison.py
This runs 8 questions through both LangGraph's ReAct agent (LLM-based routing)
and credence-router's drop-in replacement (VOI-based routing), using llama3.1
via Ollama. The output shows LLM calls saved and wall-clock speedup per question.
The only code change:
- from langgraph.prebuilt import create_react_agent
+ from credence_router.compat import create_react_agent
Why not LangGraph?
LangGraph uses an LLM to decide which tool to call. This means:
- You pay for routing. Every question costs an LLM call just to pick the tool.
- Routing is opaque. The LLM's tool selection can't be inspected or debugged.
- No learning. The router doesn't improve with feedback.
- Over-querying. ReAct agents call ~2 tools per question on average.
credence-router replaces the routing LLM with a closed-form VOI calculation:
- Zero routing cost. Tool selection is a matrix multiply, not an API call.
- Fully transparent. Every decision comes with a VOI trace you can inspect.
- Learns from feedback. Reliability estimates improve with every outcome.
- Minimal tool calls. Only queries tools when the expected value justifies the cost.
License
AGPL-3.0-only
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file credence_router-0.3.1.tar.gz.
File metadata
- Download URL: credence_router-0.3.1.tar.gz
- Upload date:
- Size: 155.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.10.9 {"installer":{"name":"uv","version":"0.10.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Arch Linux","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
af6eb460966ea6fd8dcaa15d485ef4cc0b35002f1698154b90da41dfeb37b21e
|
|
| MD5 |
c2c5e4b0603414951d3090ea86a3d627
|
|
| BLAKE2b-256 |
6448386b319617f157845eaabb7bf8a3297459748a10ea993a7e525b25fbb4aa
|
File details
Details for the file credence_router-0.3.1-py3-none-any.whl.
File metadata
- Download URL: credence_router-0.3.1-py3-none-any.whl
- Upload date:
- Size: 47.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.10.9 {"installer":{"name":"uv","version":"0.10.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Arch Linux","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
52dd5f5f914d1ed86b089e5a32354045196129f21a0db0671ba433e09c77a64a
|
|
| MD5 |
6aba0056b0a47f6f7a9f61fe1d7b3677
|
|
| BLAKE2b-256 |
b429972404f6306f2b7aa2ea68aefe9723a4b9ffbd8956723628923d6f91a569
|