Metadata-Version: 2.3
Name: cambium-ai
Version: 0.2.6
Summary: Cambium — a bounded-agent behavioral coherence engine. The deterministic spine decides what to ask. The model only answers. The eval framework decides whether the answer counted.
License: Apache-2.0
Keywords: ai,agents,agentic-ai,ai-governance,behavioral-finance,evaluation,anthropic,claude,mcp,oakquant
Author: Pumulo Sikaneta
Author-email: pumulo@oakquant.ai
Maintainer: Pumulo Sikaneta
Maintainer-email: pumulo@oakquant.ai
Requires-Python: >=3.13,<4.0
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Financial and Insurance Industry
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Typing :: Typed
Provides-Extra: claude
Provides-Extra: gemini
Provides-Extra: generative
Requires-Dist: PyYAML (>=6.0,<7.0)
Requires-Dist: anthropic (>=0.40,<2.0) ; extra == "claude" or extra == "generative"
Requires-Dist: cryptography (>=46.0.0,<47.0.0)
Requires-Dist: google-genai (>=1.0,<2.0) ; extra == "gemini" or extra == "generative"
Requires-Dist: pydantic (>=2.5.0,<3.0.0)
Project-URL: Bug Tracker, https://github.com/oakquant-ai/cambium/issues
Project-URL: Changelog, https://github.com/oakquant-ai/cambium/blob/main/CHANGELOG.md
Project-URL: Documentation, https://github.com/oakquant-ai/cambium/tree/main/docs
Project-URL: Homepage, https://github.com/oakquant-ai/cambium
Project-URL: Repository, https://github.com/oakquant-ai/cambium
Description-Content-Type: text/markdown

# Cambium

> Cost discipline and bounded agency for LLM call sites — the deterministic spine decides what to ask, the model only answers.

[![PyPI](https://img.shields.io/pypi/v/cambium-ai.svg)](https://pypi.org/project/cambium-ai/)
[![Python](https://img.shields.io/pypi/pyversions/cambium-ai.svg)](https://pypi.org/project/cambium-ai/)
[![License: Apache-2.0](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)

## What it is

Cambium is the cost-discipline / bounded-agent layer that sits in front of your
model calls. Instead of handing an LLM every decision, it routes work through
the cheapest path that can answer it and only pays for the model when nothing
cheaper will do. Three deterministic, pure-Python capabilities anchor the
public surface:

- **`cambium.distill`** — pre-call payload minimization. Decide whether a
  payload even needs the model (`assemble` / `pass` / `condense` / `summarize`),
  shrink oversized payloads deterministically, and record what every call cost
  via a `CostLedger`.
- **`cambium.resolve`** — a cost-tiered resolution ladder. Try resolvers in cost
  order (deterministic → statistical → generative) and stop at the first one
  confident enough; the expensive generative tier is a budget-gated last resort.
- **`cambium.adapt`** — the feedback substrate for outcome-driven learning:
  record predictions, observe real outcomes, score them, and attribute
  credit/blame across the features and strategies that produced them.

The package import name is `cambium`; the PyPI distribution is `cambium-ai`.

## Install

```bash
pip install cambium-ai
```

The core (`distill` / `resolve` / `adapt`) has no LLM dependency. Live
generative clients are optional extras:

```bash
pip install "cambium-ai[claude]"   # live Anthropic client
pip install "cambium-ai[gemini]"   # live Gemini generator
```

## `cambium.distill` — pre-call payload minimization + cost ledger

`prepare` runs the full routing pipeline and hands back a ready-to-send payload
plus the accounting the ledger needs. `Route.ASSEMBLE` means no model call at
all.

```python
from cambium.distill import prepare, Route, CostLedger

prepared = prepare(task_output, budget_tokens=2000)
ledger = CostLedger(job_id="research-run")

if prepared.route is Route.ASSEMBLE:
    answer = format_table(task_output)                 # deterministic, no model
    ledger.record_avoided(label="summary", route="assemble",
                          tokens_saved=prepared.tokens_saved)
else:
    response = claude.call(prompt_with(prepared.content))   # your model call
    ledger.record(label="summary", model="claude-3-5-haiku",
                  input_tokens=response.input_tokens,
                  output_tokens=response.output_tokens,
                  tier="generative", tokens_saved=prepared.tokens_saved)

report = ledger.report
report.within_budget(max_cost_usd=0.05)   # cost-regression gate
```

`estimate_tokens(content)` and `condense(content, max_tokens=...)` are also
public if you want the pieces directly.

## `cambium.resolve` — cost-tiered resolution ladder

Implement a `Resolver` per tier and let `resolve` climb only as far as it must.
The generative tier is skipped once the ledger is over budget.

```python
from cambium.resolve import Tier, Resolution, resolve
from cambium.distill import CostLedger

class ExactMatch:
    tier = Tier.DETERMINISTIC
    name = "exact_match"
    def resolve(self, request):
        hit = lookup(request)
        if hit is None:
            return None
        return Resolution(value=hit, confidence=1.0, tier=self.tier)

ledger = CostLedger()
result = resolve(
    request,
    [ExactMatch(), embedding_resolver, llm_resolver],
    ledger=ledger,
    threshold=0.8,
    max_cost_usd=0.05,   # gates the generative tier
)
```

`resolve` returns the first resolution at/above `threshold`, else the best
below-threshold one, else `None`. `ResolverRegistry` is available for grouping
resolvers by decision type.

## `cambium.adapt` — outcome-driven feedback substrate

Record predictions, feed back real outcomes, and mature them into scored
attributions that a reweighting layer can consume.

```python
from cambium.adapt import (
    PredictionRecord, OutcomeRecord, InMemoryPredictionLedger, mature,
)

led = InMemoryPredictionLedger()
led.record_prediction(PredictionRecord(
    id="p1", subject="AAPL", strategy="momentum",
    features={"rsi": 0.7, "trend": 0.3}, predicted=1.0, baseline=0.0,
))
led.record_outcome(OutcomeRecord(prediction_id="p1", actual=1.0))

scores = mature(led)   # [PredictionScore(accuracy=1.0, attributions={...})]
```

`Weights` / `update` / `aggregate` and `predict_from_attributes` /
`blend_strategies` / `best_strategy` close the loop for the reweighting phase.

## License

Apache-2.0. See [LICENSE](LICENSE).

