Catalog PyPI
Back to packageRELEASE DETAILS

0.1.2

PyPI

adaptivegraph

Learnable conditional edges for agentic workflows using contextual bandits

agenticbanditcontextual-banditlanggraphlinucbmachine-learningreinforcement-learningrouting
Published24 Dec 2025
Direct dependencies11
Transitive dependenciesNot calculated

README

Version 0.1.2 documentation

AdaptiveGraph

CI License: MIT Python 3.11+ Code style: black

from adaptivegraph import LearnableEdge

AdaptiveGraph provides LearnableEdge – a conditional edge that improves routing decisions over time through reinforcement learning.

What is LearnableEdge?

LearnableEdge replaces static routing logic with a learning-based approach. Instead of hard-coded if/else statements, it learns from feedback which routes work best for different inputs.

Key Features:

  • 🧠 Learning: Adapts routing decisions based on feedback (currently LinUCB, more policies coming)
  • ⚡ Lightweight: No training phase, no model files – learns online
  • 🎯 Balanced: Balances exploration (trying new routes) vs exploitation (using proven routes)
  • 🔧 Flexible: Works with strings, dicts, numpy arrays, or custom embeddings

Why LearnableEdge?

Traditional routing logic is static and brittle. LearnableEdge adds plasticity – it learns which routes work best through experience, automatically adapting as patterns change.

Installation

From PyPI:

pip install adaptivegraph

From source:

git clone https://github.com/BharathBillawa/adaptivegraph.git
cd adaptivegraph
pip install -e .

With optional dependencies:

# For semantic embeddings
pip install adaptivegraph[embed]

# For persistent storage
pip install adaptivegraph[faiss]

# For development
pip install adaptivegraph[dev]

# Everything
pip install adaptivegraph[all]

Quick Start

from adaptivegraph import LearnableEdge

# Create edge with routing options
edge = LearnableEdge(options=["expert_model", "fast_model", "cheap_model"])

# Make decisions
route = edge({"user": "premium", "query": "complex question"})

# Provide feedback so it learns
edge.record_feedback(result={}, reward=1.0)

See more examples:

How LearnableEdge Works

Think of LearnableEdge as a smart traffic router that learns which highway to recommend:

  1. Input State → You provide any state (string, dict, array)
  2. Encoding → Converts state into a fixed-size vector (32-dim by default)
  3. UCB Scoring → For each option, calculates:
    • Expected reward (what worked before)
    • Uncertainty bonus (exploration value)
    • UCB Score = expected_reward + α × uncertainty
  4. Selection → Picks option with highest UCB score
  5. Feedback → You call record_feedback(reward)
  6. Learning → Updates internal belief about which options work best

The algorithm balances:

  • Exploitation → Using routes with good historical performance
  • Exploration → Trying routes with high uncertainty

Features

- **Semantic Routing**: Use sentence transformers for similarity-based decisions (requires `adaptivegraph[embed]`)
- **Async Feedback**: Provide feedback hours later using event IDs
- **Trajectory Rewards**: Reward entire multi-step sessions with decay
- **Policy Persistence**: Save and restore learned routing policies

## Usage Examples

### Async Feedback
Pass `event_id` in the state to track decisions, then use it to record feedback later.
```python
# 1. Make decision with event_id
state = {"user": "premium", "query": "async test", "event_id": "req_123"}
route = edge(state)

# 2. Record feedback later using the same ID (no state needed)
edge.record_feedback(result={}, reward=1.0, event_id="req_123")
```

### Trajectory (Multi-step) Rewards
Pass `trace_id` in the state to group decisions into a session.
```python
# Step 1
edge({"step": 1, "intent": "greeting", "trace_id": "session_abc"})
# Step 2
edge({"step": 2, "intent": "params", "trace_id": "session_abc"})

# Reward the whole trace
edge.complete_trace(trace_id="session_abc", final_reward=1.0)
```

### Statistics
Inspect memory to see what the model has learned.
```python
stats = edge.memory.get_statistics()
print(f"Total decisions: {stats['total_decisions']}")
print(f"Average reward: {stats['average_reward']:.2f}")

# Access raw history
print(f"History size: {len(edge.memory.state_history)}")
```

## Notes & Gotchas
*   **Numpy Arrays**: Must match `feature_dim` (default 32). E.g., `np.random.randn(32)`.
*   **Factory Method**: `LearnableEdge.create()` defaults to `embedding="sentence-transformers"`, which requires the optional dependency.
    *   To use **without** optional dependencies, set `embedding=None` (uses hashing).
*   **Optional Dependencies**:
    *   `embedding="sentence-transformers"` requires `pip install adaptivegraph[embed]`
    *   `memory="faiss"` requires `pip install adaptivegraph[faiss]`

See [`examples/`](examples/) and [`notebooks/`](notebooks/) for detailed usage.

Comparison

LearnableEdge vs alternatives:

  • vs If/Else Rules: Learns from feedback instead of requiring manual updates
  • vs ML Classifiers: No training data needed, learns online from real usage
  • vs LLM Routers: Faster, cheaper, and adapts based on actual outcomes
  • vs Random/Epsilon-Greedy: Uses context to make smarter decisions, not just random exploration

Performance

On a typical customer support routing task (3 options, 1000 decisions):

  • Convergence: Achieves >95% accuracy within 50-100 iterations
  • Throughput: ~10,000 decisions/second on MacBook Pro M1
  • Memory: <50MB for 10,000 experiences

Roadmap

Completed ✅

  • Core LinUCB policy
  • Persistent storage (FAISS/Pickle)
  • Semantic state encoding (Sentence Transformers)
  • Async / ID-based feedback
  • Trajectory / trace rewards
  • Policy state persistence
  • Input validation and logging

Planned 🚧

  • Thompson Sampling policy
  • Epsilon-Greedy policy
  • Shared feature LinUCB (not disjoint)
  • Distributed training support
  • Visualization dashboard
  • Automated hyperparameter tuning
  • Integration examples (FastAPI, Streamlit)

Contributing

We welcome contributions! Please see CONTRIBUTING.md for guidelines.

Quick Start for Contributors

# Clone and setup
git clone https://github.com/BharathBillawa/adaptivegraph.git
cd adaptivegraph
pip install -e ".[dev]"

# Run tests
pytest tests/ -v

# Format code
black src/ tests/
isort src/ tests/

Citation

If you use AdaptiveGraph in your research, please cite:

@software{adaptivegraph2024,
  title = {AdaptiveGraph: Learnable Conditional Edges for Agentic Workflows},
  author = {BharathPoojary},
  year = {2024},
  url = {https://github.com/BharathBillawa/adaptivegraph}
}

License

MIT License - see LICENSE for details.

Acknowledgments

  • Built for LangGraph but framework-agnostic
  • LinUCB algorithm from Li et al. (2010)
  • Inspired by contextual bandit research in recommendation systems

Support


Made with ❤️ for the AI agent community

Downloads & popularity

Awaiting observations

No metric observations yet

Download counts and popularity will appear here as the collector receives them.

Declared dependencies 11

Native requirements
11 direct targetsNot calculated transitive count
Package / targetRequirementRelationship
numpy
Environment
{
  "extras": "",
  "marker": null,
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
numpy>=1.24.0runtime
langgraph
Environment
{
  "extras": "",
  "marker": "extra == \"dev\"",
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
langgraph>=0.2.0; extra == "dev"runtime
pytest
Environment
{
  "extras": "",
  "marker": "extra == \"dev\"",
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
pytest; extra == "dev"runtime
matplotlib
Environment
{
  "extras": "",
  "marker": "extra == \"dev\"",
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
matplotlib; extra == "dev"runtime
jupyter
Environment
{
  "extras": "",
  "marker": "extra == \"dev\"",
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
jupyter; extra == "dev"runtime
black
Environment
{
  "extras": "",
  "marker": "extra == \"dev\"",
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
black>=23.0.0; extra == "dev"runtime
isort
Environment
{
  "extras": "",
  "marker": "extra == \"dev\"",
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
isort>=5.12.0; extra == "dev"runtime
flake8
Environment
{
  "extras": "",
  "marker": "extra == \"dev\"",
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
flake8>=7.0.0; extra == "dev"runtime
sentence-transformers
Environment
{
  "extras": "",
  "marker": "extra == \"embed\"",
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
sentence-transformers>=3.0.0; extra == "embed"runtime
faiss-cpu
Environment
{
  "extras": "",
  "marker": "extra == \"faiss\"",
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
faiss-cpu>=1.8.0; extra == "faiss"runtime
adaptivegraph
Environment
{
  "extras": "embed,faiss",
  "marker": "extra == \"all\"",
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
adaptivegraph[embed,faiss]; extra == "all"runtime

Distribution files

2 collected
adaptivegraph-0.1.2-py3-none-any.whl

bdist_wheel · 15.96 KB · Python py3

Source file
adaptivegraph-0.1.2.tar.gz

sdist · 61.64 KB · Python source

Source file

Repository

PENDING
bharathbillawa/adaptivegraph

Shared repository data · GitHub

License detected in repository
Not collected

Metadata history

Meaningful changes
Current collected state Current

Observed since 20 Oct, 19:06 UTC

No superseded metadata states collected. Unchanged observations do not create new revisions.

Record identity

package_version:6cbf12909a4b5347ca20f79c163bdcc61ecc1425bf02e082d6381b958567a9d2

Other collected versions

View all collected versions →