Build AI systems with the elegance of print("Hello World").
pip install ember-aiRun our interactive setup wizard for the best experience:
ember setupOr use npx if you haven't installed Ember yet:
npx @ember-ai/setupThis will:
- Help you choose an AI provider (OpenAI, Anthropic, or Google)
- Configure your API keys securely
- Test your connection
- Save configuration to ~/.ember/config.yaml
Ember can store API keys in multiple ways:
Option 1: Environment Variables
# For OpenAI (GPT-4, GPT-3.5)
export OPENAI_API_KEY="sk-..."
# For Anthropic (Claude models)
export ANTHROPIC_API_KEY="sk-ant-..."
# For Google (Gemini models)
export GOOGLE_API_KEY="..."Option 2: Configuration File (Recommended)
# Run setup for each provider
ember setup # Interactive wizard
# Or configure manually
ember configure set credentials.openai_api_key "sk-..."
ember configure set credentials.anthropic_api_key "sk-ant-..."
ember configure set credentials.google_api_key "..."Option 3: Runtime Context
from ember.context import with_context
with with_context(credentials={"openai_api_key": "sk-..."}):
response = models("gpt-4", "Hello!")from ember.api import models
# Discover available models
print(models.list()) # Shows all available models
print(models.providers()) # Shows available providers
# Get detailed model information
info = models.discover()
for model_id, details in info.items():
print(f"{model_id}: {details['description']} (context: {details['context_window']})")
# This will work if you have OPENAI_API_KEY set
response = models("gpt-4", "Hello, world!")
print(response)If API keys are missing, you'll get a clear error message:
ModelProviderError: No API key available for model gpt-4.
Please set via OPENAI_API_KEY environment variable.
If you use an unknown model name, you'll see available options:
ModelNotFoundError: Cannot determine provider for model 'claude-3'.
Available models: claude-2.1, claude-3-haiku, claude-3-opus, claude-3-sonnet,
gemini-pro, gemini-pro-vision, gpt-3.5-turbo, gpt-3.5-turbo-16k, gpt-4,
gpt-4-turbo, gpt-4o, gpt-4o-mini, ...
from ember.api import models, Models
# Option 1: Direct strings (simple, works everywhere)
response = models("gpt-4", "Hello, world!")
response = models("claude-3-opus", "Write a haiku")
# Option 2: Constants for IDE autocomplete and typo prevention
response = models(Models.GPT_4, "Hello, world!")
response = models(Models.CLAUDE_3_OPUS, "Write a haiku")
# Both are exactly equivalent - Models.GPT_4 == "gpt-4"from ember import models
# Direct LLM calls - no setup required
response = models("gpt-4", "Explain quantum computing in one sentence")
print(response)Common model identifiers:
- OpenAI:
gpt-4,gpt-4-turbo,gpt-4o,gpt-3.5-turbo - Anthropic:
claude-3-opus,claude-3-sonnet,claude-3-haiku,claude-2.1 - Google:
gemini-pro,gemini-ultra
Models are automatically routed to the correct provider based on their name.
Ember provides four primitives that compose into powerful AI systems:
from ember import models
# Simple invocation with string model names
response = models("claude-3-opus", "Write a haiku about programming")
# Reusable configuration
assistant = models.instance("gpt-4", temperature=0.7, system="You are helpful")
response = assistant("How do I center a div?")
# Alternative: Use constants for autocomplete
from ember import Models
response = models(Models.CLAUDE_3_OPUS, "Write a haiku")from ember import operators
# Transform any function into an AI operator
@operators.op
def summarize(text: str) -> str:
return models("gpt-4", f"Summarize in one sentence: {text}")
@operators.op
def translate(text: str, language: str = "Spanish") -> str:
return models("gpt-4", f"Translate to {language}: {text}")
# Compose operators naturally
pipeline = summarize >> translate
result = pipeline("Long technical article...")from ember.api import data
# Stream data efficiently
for example in data.stream("mmlu"):
answer = models("gpt-4", example["question"])
print(f"Q: {example['question']}")
print(f"A: {answer}")
# Chain transformations
results = (data.stream("gsm8k")
.filter(lambda x: x["difficulty"] > 7)
.transform(preprocess)
.batch(32))from ember import xcs
# Automatic JIT compilation
@xcs.jit
def process_batch(items):
return [models("gpt-4", item) for item in items]
# Automatic parallelization
fast_process = xcs.vmap(process_batch)
results = fast_process(large_dataset) # Runs in parallelfrom ember import models, operators
@operators.op
def review_code(code: str) -> dict:
"""AI-powered code review"""
prompt = f"""Review this code for:
1. Bugs and errors
2. Performance issues
3. Best practices
Code:
{code}
"""
review = models("claude-3", prompt)
# Extract structured feedback
return {
"summary": models("gpt-4", f"Summarize in one line: {review}"),
"issues": review,
"severity": models("gpt-4", f"Rate severity 1-10: {review}")
}
# Use directly
feedback = review_code("""
def fibonacci(n):
if n <= 1:
return n
return fibonacci(n-1) + fibonacci(n-2)
""")from ember import models, xcs, data
# Define processing pipeline
@xcs.jit
def analyze_document(doc: dict) -> dict:
# Extract key information
summary = models("gpt-4", f"Summarize: {doc['content']}")
entities = models("gpt-4", f"Extract entities: {doc['content']}")
sentiment = models("gpt-4", f"Analyze sentiment: {doc['content']}")
return {
"id": doc["id"],
"summary": summary,
"entities": entities,
"sentiment": sentiment
}
# Process documents in parallel
documents = data.stream("research_papers").first(1000)
results = xcs.vmap(analyze_document)(documents)
# Results computed optimally with automatic parallelizationfrom ember import models, operators
@operators.op
def consensus_answer(question: str) -> str:
"""Get consensus from multiple models"""
# Query different models
gpt4_answer = models("gpt-4", question)
claude_answer = models("claude-3-opus", question)
gemini_answer = models("gemini-pro", question)
# Synthesize consensus
synthesis_prompt = f"""
Question: {question}
Model answers:
- GPT-4: {gpt4_answer}
- Claude: {claude_answer}
- Gemini: {gemini_answer}
Synthesize the best answer combining insights from all models.
"""
return models("gpt-4", synthesis_prompt)
# Use for critical decisions
answer = consensus_answer("What's the best approach to distributed systems?")Ember provides a comprehensive CLI for setup, configuration, and testing:
# Interactive setup wizard (recommended for first-time setup)
ember setup
# Test your API connection
ember test
ember test --model claude-3-opus
# List available models and providers
ember models # List all models
ember models --provider openai # Filter by provider
ember models --providers # List providers only
# Configuration management
ember configure get models.default # Get a config value
ember configure set models.default "gpt-4" # Set a config value
ember configure list # Show all configuration
ember configure show credentials # Show specific section
ember configure migrate # Migrate old config files
# Version information
ember versionThe context system supports multiple configuration sources with priority:
- Runtime context (highest priority)
- Environment variables
- Configuration file (~/.ember/config.yaml)
- Defaults (lowest priority)
from ember.context import create_context, with_context
# Create isolated contexts for different use cases
dev_context = create_context(
models={"default": "gpt-3.5-turbo", "temperature": 0.0},
credentials={"openai_api_key": "dev-key"}
)
prod_context = create_context(
models={"default": "gpt-4", "temperature": 0.7},
credentials={"openai_api_key": "prod-key"}
)
# Use different contexts in different parts of your app
with dev_context:
# All calls here use dev settings
test_response = models(None, "Test prompt")
with prod_context:
# All calls here use production settings
prod_response = models(None, "Production prompt")from ember import Operator
from pydantic import BaseModel
class CodeInput(BaseModel):
language: str
code: str
class CodeOutput(BaseModel):
is_valid: bool
errors: list[str]
suggestions: list[str]
class CodeValidator(Operator):
input_spec = CodeInput
output_spec = CodeOutput
def call(self, input: CodeInput) -> CodeOutput:
prompt = f"Validate this {input.language} code: {input.code}"
result = models("gpt-4", prompt)
# Automatic validation against output_spec
return CodeOutput(...)from ember.api import data
# Register custom dataset
@data.register("my-dataset")
def load_my_data():
with open("data.jsonl") as f:
for line in f:
yield json.loads(line)
# Use like built-in datasets
for item in data.stream("my-dataset"):
process(item)from ember import xcs
# Automatic profiling
with xcs.profile() as prof:
results = expensive_operation()
print(prof.report())
# Shows execution time, parallelism achieved, bottlenecks- Simple by Default - Basic usage requires no configuration
- Progressive Disclosure - Complexity available when needed
- Composition Over Configuration - Build complex from simple
- Performance Without Sacrifice - Fast by default, no manual tuning
Ember uses a registry-based architecture with four main components:
- Model Registry - Manages LLM providers and connections
- Operator System - Composable computation units with JAX integration
- Data Pipeline - Streaming-first data loading and transformation
- XCS Engine - Automatic optimization and parallelization
See ARCHITECTURE.md for detailed design documentation.
# Install development dependencies
pip install -e ".[dev]"
# Run tests
pytest
# Type checking
mypy src/
# Benchmarks
python -m benchmarks.suiteWe welcome contributions that align with Ember's philosophy of simplicity and power. See CONTRIBUTING.md for guidelines.
MIT License. See LICENSE for details.
Ember is inspired by the engineering excellence of:
- JAX's functional transformations
- PyTorch's intuitive API
- Langchain's comprehensive features (but simpler)
- The Unix philosophy of composable tools
Built with principles from leaders who shaped modern computing.