Skip to main content

LLM and embedding provider implementations for bruno-core framework

Project description

bruno-llm

Python Version License: MIT Tests Coverage Code style: ruff

bruno-llm provides production-ready LLM and embedding provider implementations for the bruno-core framework. Swap between different language model and embedding providers (Ollama, OpenAI, Anthropic, etc.) through unified interfaces with advanced features like caching, streaming, context management, cost tracking, and semantic search capabilities.

โœจ Key Features

Core Capabilities

  • ๐Ÿ”Œ Unified Interface: All providers implement LLMInterface and EmbeddingInterface from bruno-core
  • ๐Ÿง  Embedding Support: Text embeddings for semantic search, similarity, and RAG applications
  • โšก Async-First: Built on asyncio for non-blocking I/O
  • ๐Ÿญ Factory Pattern: Easy provider instantiation and fallback chains for both LLM and embedding providers
  • ๐Ÿ”„ Streaming Support: Real-time token streaming with aggregation
  • ๐Ÿ“ Type Safe: Complete type hints with Pydantic v2 validation

Advanced Features

  • ๐Ÿ’พ Response Caching: LRU cache with TTL to reduce API costs
  • ๐Ÿง  Context Management: Intelligent message truncation with multiple strategies
  • ๐Ÿ’ฐ Cost Tracking: Detailed usage analytics with CSV/JSON export
  • ๐Ÿ” Smart Retry: Exponential backoff with configurable strategies
  • โฑ๏ธ Rate Limiting: Token bucket algorithm for API compliance
  • ๐Ÿ”Œ Middleware System: Extensible request/response pipeline

Quality & Testing

  • ๐Ÿงช Well Tested: 288+ tests with 89% code coverage
  • ๐Ÿ›ก๏ธ Error Handling: Comprehensive exception hierarchy
  • ๐Ÿ“Š Production Ready: Used in real-world applications
  • ๐Ÿ“– Documented: Extensive documentation and examples

๐Ÿš€ Quick Start

Installation

# Basic installation (includes Ollama support)
pip install bruno-llm

# With OpenAI support
pip install bruno-llm[openai]

# For development
pip install bruno-llm[dev]

# Install from source
git clone https://github.com/meggy-ai/bruno-llm.git
cd bruno-llm
pip install -e .

Basic Usage

Using the Factory Pattern (Recommended)

import asyncio
from bruno_llm import LLMFactory
from bruno_core.models import Message, MessageRole

async def main():
    # Create provider using factory
    llm = LLMFactory.create(
        provider="ollama",
        config={"model": "llama2", "base_url": "http://localhost:11434"}
    )

    # Or from environment variables
    # llm = LLMFactory.create_from_env("openai")

    # Check connection
    if await llm.check_connection():
        print("โœ… Connected!")

    # Generate response
    messages = [
        Message(role=MessageRole.USER, content="What is Python?")
    ]
    response = await llm.generate(messages, max_tokens=100)
    print(response)

    # Stream response
    async for chunk in llm.stream(messages):
        print(chunk, end="", flush=True)

    await llm.close()

asyncio.run(main())

Ollama (Local LLM)

from bruno_llm.providers.ollama import OllamaProvider

# Initialize provider
llm = OllamaProvider(model="llama2")

# Generate response
response = await llm.generate(messages)

OpenAI (Cloud API)

from bruno_llm.providers.openai import OpenAIProvider

# Initialize provider
llm = OpenAIProvider(api_key="sk-...", model="gpt-4")

# Generate with cost tracking
response = await llm.generate(messages)
cost = llm.cost_tracker.get_total_cost()
print(f"Cost: ${cost:.4f}")

Embedding Usage

Quick Start with Embeddings

from bruno_llm.embedding_factory import EmbeddingFactory

# Create embedding provider
embedder = EmbeddingFactory.create("openai", {
    "api_key": "sk-...",
    "model": "text-embedding-3-small"
})

# Generate single embedding
text = "Machine learning transforms data into insights"
embedding = await embedder.embed_text(text)
print(f"Embedding dimension: {len(embedding)}")

# Batch processing
texts = [
    "Artificial intelligence revolutionizes technology",
    "Machine learning enables pattern recognition",
    "Deep learning uses neural networks"
]
embeddings = await embedder.embed_texts(texts)

Similarity Search

# Calculate similarity between texts
query = "artificial intelligence"
documents = ["ML algorithms", "Cooking recipes", "AI systems"]

query_emb = await embedder.embed_text(query)
doc_embeddings = await embedder.embed_texts(documents)

# Find most similar
similarities = [
    embedder.calculate_similarity(query_emb, doc_emb)
    for doc_emb in doc_embeddings
]

most_similar_idx = similarities.index(max(similarities))
print(f"Most similar: {documents[most_similar_idx]}")

Simple RAG (Retrieval-Augmented Generation)

from bruno_llm.factory import LLMFactory

# Combine embeddings + LLM for RAG
llm = LLMFactory.create_from_env("openai")
embedder = EmbeddingFactory.create_from_env("openai")

# Build knowledge base
knowledge = ["Bruno-LLM provides LLM interfaces", "It supports OpenAI and Ollama"]
kb_embeddings = await embedder.embed_texts(knowledge)

# Answer question using relevant knowledge
question = "What does Bruno-LLM provide?"
q_embedding = await embedder.embed_text(question)

# Find most relevant knowledge
similarities = [
    embedder.calculate_similarity(q_embedding, kb_emb)
    for kb_emb in kb_embeddings
]
best_match_idx = similarities.index(max(similarities))
context = knowledge[best_match_idx]

# Generate answer with context
messages = [
    Message(role=MessageRole.SYSTEM, content="Answer based on context."),
    Message(role=MessageRole.USER, content=f"Context: {context}\nQuestion: {question}")
]
answer = await llm.generate(messages)

Advanced Features

Response Caching

from bruno_llm import LLMFactory
from bruno_llm.base import ResponseCache

llm = LLMFactory.create("ollama", {"model": "llama2"})
cache = ResponseCache(max_size=100, ttl=300)

# First call - cache miss
response = await llm.generate(messages, temperature=0.0)
cache.set(messages, response, temperature=0.0)

# Second call - cache hit (no API call!)
cached = cache.get(messages, temperature=0.0)
if cached:
    print("Retrieved from cache!")

Context Management

from bruno_llm.base import ContextWindowManager, ContextLimits, TruncationStrategy

# Create context manager
context_mgr = ContextWindowManager(
    model="gpt-4",
    limits=ContextLimits(max_tokens=8000, max_output_tokens=500),
    strategy=TruncationStrategy.SMART
)

# Check and truncate if needed
if not context_mgr.check_limit(messages):
    messages = context_mgr.truncate(messages)

response = await llm.generate(messages)

Stream Aggregation

from bruno_llm.base import StreamAggregator

aggregator = StreamAggregator(strategy="word")

# Get word-by-word instead of character-by-character
async for word in aggregator.aggregate(llm.stream(messages)):
    print(word, end=" ", flush=True)

Provider Fallback

from bruno_llm import LLMFactory

# Try OpenAI first, fallback to Ollama
llm = await LLMFactory.create_with_fallback(
    providers=["openai", "ollama"],
    configs=[
        {"api_key": "sk-...", "model": "gpt-4"},
        {"model": "llama2"}
    ]
)

Cost Tracking & Export

# Track usage
response = await llm.generate(messages)

# Get report
report = llm.cost_tracker.get_usage_report()
print(f"Total cost: ${report['total_cost']:.4f}")
print(f"Total tokens: {report['total_tokens']}")

# Export to CSV
llm.cost_tracker.export_to_csv("costs.csv")

# Check budget
status = llm.cost_tracker.check_budget(budget_limit=10.0)
if not status["within_budget"]:
    print("โš ๏ธ Budget exceeded!")

๐Ÿ“ฆ Supported Providers

Provider Status Streaming Cost Tracking Token Counting
Ollama โœ… Ready โœ… Yes โœ… Yes (free) โœ… Approximate
OpenAI โœ… Ready โœ… Yes โœ… Yes โœ… tiktoken
Anthropic Claude ๐Ÿšง Planned - - -
Google Gemini ๐Ÿšง Planned - - -

Provider Setup

Ollama

# Install Ollama
# Visit: https://ollama.ai/

# Start Ollama
ollama serve

# Pull a model
ollama pull llama2
ollama pull codellama
ollama pull mistral

OpenAI

# Set API key
export OPENAI_API_KEY=sk-...

# Or in Python
llm = OpenAIProvider(api_key="sk-...")

## ๐Ÿ”ง Configuration

### Environment Variables

```bash
# Ollama
OLLAMA_BASE_URL=http://localhost:11434
OLLAMA_MODEL=llama2

# OpenAI
OPENAI_API_KEY=sk-...
OPENAI_MODEL=gpt-4
OPENAI_ORG_ID=org-...

Provider Configuration

from bruno_llm import LLMFactory

# Direct instantiation
from bruno_llm.providers.ollama import OllamaProvider

llm = OllamaProvider(
    base_url="http://localhost:11434",
    model="llama2",
    timeout=30.0
)

# Using factory
llm = LLMFactory.create(
    provider="ollama",
    config={"model": "llama2", "timeout": 60.0}
)

# From environment
llm = LLMFactory.create_from_env("openai")

๐Ÿ—๏ธ Architecture

bruno-llm is built with a modular architecture:

bruno_llm/
โ”œโ”€โ”€ base/                    # Core utilities
โ”‚   โ”œโ”€โ”€ base_provider.py    # Abstract provider base
โ”‚   โ”œโ”€โ”€ cache.py            # Response caching
โ”‚   โ”œโ”€โ”€ context.py          # Context window management
โ”‚   โ”œโ”€โ”€ cost_tracker.py     # Usage and cost tracking
โ”‚   โ”œโ”€โ”€ middleware.py       # Request/response middleware
โ”‚   โ”œโ”€โ”€ rate_limiter.py     # Rate limiting
โ”‚   โ”œโ”€โ”€ retry.py            # Retry logic
โ”‚   โ”œโ”€โ”€ streaming.py        # Stream utilities
โ”‚   โ””โ”€โ”€ token_counter.py    # Token counting
โ”œโ”€โ”€ providers/              # Provider implementations
โ”‚   โ”œโ”€โ”€ ollama/            # Ollama provider
โ”‚   โ””โ”€โ”€ openai/            # OpenAI provider
โ”œโ”€โ”€ exceptions.py           # Exception hierarchy
โ””โ”€โ”€ factory.py             # Factory pattern

๐Ÿงช Testing

# Run all tests
pytest tests/

# Run with coverage
pytest tests/ --cov=bruno_llm --cov-report=html

# Run specific test file
pytest tests/test_cache.py -v

# Skip WIP tests (default)
pytest tests/ -m "not wip"

# Run integration tests (requires Ollama/OpenAI)
pytest tests/ -m integration

Test Stats: 203 tests, 193 passing, 91% coverage

See TESTING.md for detailed testing guide.

๐Ÿ“š Documentation

๐Ÿ“– Examples

Integration with bruno-core

from bruno_core.base import BaseAssistant
from bruno_llm import LLMFactory

# Create assistant with LLM provider
llm = LLMFactory.create("ollama", {"model": "llama2"})
assistant = BaseAssistant(llm=llm, memory=your_memory)

await assistant.initialize()

# Process messages
response = await assistant.process_message(user_message)

Middleware System

from bruno_llm.base import LoggingMiddleware, CachingMiddleware, MiddlewareChain

# Create middleware chain
chain = MiddlewareChain([
    LoggingMiddleware(),
    CachingMiddleware(cache),
])

# Apply to requests (provider integration)
# Middleware hooks into before_request, after_response, on_stream_chunk, on_error

Custom Provider

from bruno_core.interfaces import LLMInterface
from bruno_llm.base import BaseProvider

class CustomProvider(BaseProvider):
    async def generate(self, messages, **kwargs):
        # Your implementation
        pass

    async def stream(self, messages, **kwargs):
        # Your streaming implementation
        pass

# Register with factory
from bruno_llm import LLMFactory
LLMFactory.register("custom", CustomProvider)

๐Ÿค Contributing

Contributions are welcome! Please see CONTRIBUTING.md for guidelines.

Development Setup

# Clone repository
git clone https://github.com/meggy-ai/bruno-llm.git
cd bruno-llm

# Create virtual environment
python -m venv .venv
source .venv/bin/activate  # On Windows: .venv\Scripts\activate

# Install in development mode
pip install -e ".[dev]"

# Run tests
pytest tests/

# Format code
ruff format .

# Lint code
ruff check .

๐Ÿ› Troubleshooting

Ollama Connection Issues

# Check if Ollama is running
import httpx
response = httpx.get("http://localhost:11434/api/tags")
print(response.json())

# Common issues:
# - Ollama not running: Start with `ollama serve`
# - Model not installed: Run `ollama pull llama2`
# - Wrong URL: Check base_url configuration

OpenAI Authentication

# Verify API key
import openai
openai.api_key = "sk-..."
models = openai.Model.list()  # Should not raise error

# Common issues:
# - Invalid API key: Check at https://platform.openai.com/api-keys
# - Rate limiting: Use rate_limiter configuration
# - Billing: Ensure account has credits

Import Errors

# Reinstall package
pip uninstall bruno-llm
pip install -e .

# Or force reinstall dependencies
pip install --force-reinstall bruno-llm[dev]

๐Ÿ“‹ API Reference

Factory

from bruno_llm import LLMFactory

# Create provider
llm = LLMFactory.create(provider: str, config: dict, **kwargs)

# From environment
llm = LLMFactory.create_from_env(provider: str, prefix: str = "")

# With fallback
llm = await LLMFactory.create_with_fallback(
    providers: list[str],
    configs: list[dict]
)

# List available providers
providers = LLMFactory.list_providers()

# Check if registered
is_available = LLMFactory.is_registered("ollama")

LLMInterface Methods

All providers implement these methods:

# Generate complete response
response: str = await llm.generate(
    messages: List[Message],
    max_tokens: int = None,
    temperature: float = 0.7,
    **kwargs
)

# Stream response
async for chunk in llm.stream(
    messages: List[Message],
    max_tokens: int = None,
    **kwargs
):
    print(chunk, end="")

# Get token count
count: int = llm.get_token_count(text: str)

# Check connection
is_connected: bool = await llm.check_connection()

# List models
models: List[str] = await llm.list_models()

# Get model info
info: dict = llm.get_model_info()

# System prompt
llm.set_system_prompt(prompt: str)
prompt: str = llm.get_system_prompt()

# Close resources
await llm.close()

Advanced Utilities

# Response caching
from bruno_llm.base import ResponseCache
cache = ResponseCache(max_size=100, ttl=300)
cache.set(messages, response, **params)
cached = cache.get(messages, **params)
stats = cache.get_stats()

# Context management
from bruno_llm.base import ContextWindowManager, ContextLimits
manager = ContextWindowManager(model="gpt-4", limits=ContextLimits(...))
is_within = manager.check_limit(messages)
truncated = manager.truncate(messages)
stats = manager.get_stats(messages)

# Stream aggregation
from bruno_llm.base import StreamAggregator
aggregator = StreamAggregator(strategy="word")
async for word in aggregator.aggregate(stream):
    print(word)

# Cost tracking
report = llm.cost_tracker.get_usage_report()
llm.cost_tracker.export_to_csv("costs.csv")
status = llm.cost_tracker.check_budget(budget_limit=10.0)

๐Ÿ”’ Security

  • API Keys: Never commit API keys to version control
  • Environment Variables: Use .env files (add to .gitignore)
  • Rate Limiting: Built-in rate limiting prevents abuse
  • Input Validation: All inputs validated with Pydantic
  • Error Handling: Sensitive data not exposed in errors

๐Ÿ“„ License

This project is licensed under the MIT License - see the LICENSE file for details.

๐Ÿ™ Acknowledgments

๐Ÿ“ฎ Support

๐Ÿ—บ๏ธ Roadmap

  • Ollama provider
  • OpenAI provider
  • Response caching
  • Context management
  • Cost tracking
  • Middleware system
  • Anthropic Claude provider
  • Google Gemini provider
  • Azure OpenAI support
  • Streaming improvements
  • Advanced retry strategies
  • Prompt templates
  • Function calling support

Made with โค๏ธ by the Meggy AI team

๐Ÿงช Development

Setup Development Environment

# Clone repository
git clone https://github.com/meggy-ai/bruno-llm.git
cd bruno-llm

# Create virtual environment
python -m venv venv
source venv/bin/activate  # On Windows: venv\Scripts\activate

# Install development dependencies
pip install -e ".[dev]"

# Install pre-commit hooks
pre-commit install

Running Tests

# Run all tests
pytest

# Run with coverage
pytest --cov=bruno_llm --cov-report=html

# Run specific provider tests
pytest tests/providers/test_ollama.py -v

# Run integration tests (requires running services)
pytest -m integration

Code Quality

# Format code
black bruno_llm tests

# Lint
ruff check bruno_llm tests

# Type check
mypy bruno_llm

# Run all checks
pre-commit run --all-files

๐Ÿค Contributing

Contributions are welcome! Please see CONTRIBUTING.md for guidelines.

Commit Conventions

Follow Conventional Commits:

  • feat: New features
  • fix: Bug fixes
  • docs: Documentation changes
  • test: Test changes
  • refactor: Code restructuring

๐Ÿ“„ License

MIT License - see LICENSE file for details.

๐Ÿ”— Related Projects

  • bruno-core - Foundation framework
  • bruno-memory - Memory backend implementations (coming soon)
  • bruno-abilities - Pre-built abilities (coming soon)
  • bruno-pa - Personal assistant application (coming soon)

๐Ÿ“ž Support


Made with โค๏ธ by the Meggy AI team

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

bruno_llm-0.2.0.tar.gz (76.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

bruno_llm-0.2.0-py3-none-any.whl (58.8 kB view details)

Uploaded Python 3

File details

Details for the file bruno_llm-0.2.0.tar.gz.

File metadata

  • Download URL: bruno_llm-0.2.0.tar.gz
  • Upload date:
  • Size: 76.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for bruno_llm-0.2.0.tar.gz
Algorithm Hash digest
SHA256 043e016a96051b15d5497aa63b317b7824dbbd229d449c261e6f08c420261d93
MD5 d5217502345476302bf52619c90f19dd
BLAKE2b-256 ea63acae74407da11b5121f9ab06ad2cdafe36ba010110ed5254db209c22630e

See more details on using hashes here.

File details

Details for the file bruno_llm-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: bruno_llm-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 58.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for bruno_llm-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 9cd8e8685ef6636f517b305adc95608ad978b2386ad221cad8ce6cec0b7a7c70
MD5 4fd80aefc44a987182899c3e5165355f
BLAKE2b-256 bcc66ec14d7e4015b1a8ae889ad7bbb87dcceac5256f3fe873835a9099bc2673

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page