Production-ready Python SDK for FAIM (Foundation AI Models) - a high-performance time-series forecasting platform powered by foundation models.
- 🚀 Multiple Foundation Models: FlowState, Amazon Chronos 2.0, TiRex
- 🔒 Type-Safe API: Full type hints with Pydantic validation
- ⚡ High Performance: Optimized Apache Arrow serialization with zero-copy operations
- 🎯 Probabilistic & Deterministic: Point forecasts, quantiles, and samples
- 🔄 Async Support: Built-in async/await support for concurrent requests
- 📊 Rich Error Handling: Machine-readable error codes with detailed diagnostics
- 🧪 Battle-Tested: Production-ready with comprehensive error handling
- 📈 Evaluation Tools: Built-in metrics (MSE, MASE, CRPS) and visualization utilities
pip install faim-sdkGet your API key at https://faim.it.com/
from faim_sdk import ForecastClient
# Initialize client with your API key
client = ForecastClient(api_key="your-api-key")import numpy as np
from faim_sdk import ForecastClient, Chronos2ForecastRequest
# Initialize client
client = ForecastClient(api_key="your-api-key")
# Prepare your time-series data
# Shape: (batch_size, sequence_length, features)
data = np.random.randn(32, 100, 1).astype(np.float32)
# Create probabilistic forecast request
request = Chronos2ForecastRequest(
x=data,
horizon=24, # Forecast 24 steps ahead
output_type="quantiles",
quantiles=[0.1, 0.5, 0.9] # 10th, 50th (median), 90th percentiles
)
# Generate forecast - model inferred automatically from request type
response = client.forecast(request)
# Access predictions
print(response.quantiles.shape) # (32, 24, 3, 1)
print(response.metadata) # Model version, inference time, etc.All models require 3D input arrays:
# Shape: (batch_size, sequence_length, features)
x = np.array([
[[1.0], [2.0], [3.0]], # Series 1
[[4.0], [5.0], [6.0]] # Series 2
]) # Shape: (2, 3, 1)- batch_size: Number of independent time series
- sequence_length: Historical data points (context window)
- features: Number of variables per time step (use 1 for univariate)
Important: 2D input will raise a validation error. Always provide 3D arrays.
Point Forecasts (3D):
response.point # Shape: (batch_size, horizon, features)Quantile Forecasts (4D):
response.quantiles # Shape: (batch_size, horizon, num_quantiles, features)
# Example: (32, 24, 5, 1) = 32 series, 24 steps ahead, 5 quantiles, 1 feature- Chronos2: ✅ Supports multivariate forecasting (multiple features)
- FlowState:
⚠️ Univariate only - automatically transforms multivariate input - TiRex:
⚠️ Univariate only - automatically transforms multivariate input
When you provide multivariate input (features > 1) to FlowState or TiRex, the SDK automatically:
- Issues a warning
- Forecasts each feature independently
- Reshapes the output back to your original structure
# Multivariate input to FlowState
data = np.random.randn(2, 100, 3) # 2 series, 3 features
request = FlowStateForecastRequest(x=data, horizon=24, prediction_type="mean")
# Warning: "FlowState model only supports univariate forecasting..."
response = client.forecast(request)
# Output is automatically reshaped
print(response.point.shape) # (2, 24, 3) - original structure preservedfrom faim_sdk import FlowStateForecastRequest
request = FlowStateForecastRequest(
x=data,
horizon=24,
model_version="latest",
output_type="point",
scale_factor=1.0, # Optional: normalization factor, for details check: https://huggingface.co/ibm-granite/granite-timeseries-flowstate-r1
prediction_type="mean" # Options: "mean", "median"
)
response = client.forecast(request)
print(response.point.shape) # (batch_size, 24, features)from faim_sdk import Chronos2ForecastRequest
# Quantile-based probabilistic forecast
request = Chronos2ForecastRequest(
x=data,
horizon=24,
output_type="quantiles",
quantiles=[0.05, 0.25, 0.5, 0.75, 0.95] # Full distribution
)
response = client.forecast(request)
print(response.quantiles.shape) # (batch_size, 24, 5)from faim_sdk import TiRexForecastRequest
request = TiRexForecastRequest(
x=data,
horizon=24,
output_type="point"
)
response = client.forecast(request)
print(response.point.shape) # (batch_size, 24, features)All forecasts return a ForecastResponse object with predictions and metadata:
response = client.forecast(request)
# Access predictions based on output_type
if response.point is not None:
predictions = response.point # Shape: (batch_size, horizon, features)
if response.quantiles is not None:
quantiles = response.quantiles # Shape: (batch_size, horizon, num_quantiles)
# Lower quantiles for uncertainty bounds
lower_bound = quantiles[:, :, 0] # 10th percentile
median = quantiles[:, :, 1] # 50th percentile (median)
upper_bound = quantiles[:, :, 2] # 90th percentile
if response.samples is not None:
samples = response.samples # Shape: (batch_size, horizon, num_samples)
# Access metadata
print(response.metadata)
# {'model_name': 'chronos2', 'model_version': '1.0', 'inference_time_ms': 123}The SDK includes a comprehensive evaluation toolkit (faim_sdk.eval) for measuring forecast quality with standard metrics and visualizations.
For visualization support, install with the viz extra:
pip install faim-sdk[viz]Measures average squared difference between predictions and ground truth.
from faim_sdk.eval import mse
# Evaluate point forecast
mse_score = mse(test_data, response.point, reduction='mean')
print(f"MSE: {mse_score:.4f}")
# Per-sample MSE
mse_per_sample = mse(test_data, response.point, reduction='none')
print(f"MSE per sample shape: {mse_per_sample.shape}") # (batch_size,)Scale-independent metric comparing forecast to naive baseline (better than MAPE for series with zeros).
from faim_sdk.eval import mase
# MASE requires training data for baseline
mase_score = mase(test_data, response.point, train_data, reduction='mean')
print(f"MASE: {mase_score:.4f}")
# Interpretation:
# MASE < 1: Better than naive baseline
# MASE = 1: Equivalent to naive baseline
# MASE > 1: Worse than naive baselineProper scoring rule for probabilistic forecasts - generalizes MAE to distributions.
from faim_sdk.eval import crps_from_quantiles
# Evaluate probabilistic forecast with quantiles
crps_score = crps_from_quantiles(
test_data,
response.quantiles,
quantile_levels=[0.1, 0.5, 0.9],
reduction='mean'
)
print(f"CRPS: {crps_score:.4f}")Plot forecasts with training context and ground truth:
from faim_sdk.eval import plot_forecast
# Plot single sample (remember to index batch dimension!)
fig, ax = plot_forecast(
train_data=train_data[0], # (seq_len, features) - 2D array
forecast=response.point[0], # (horizon, features) - 2D array
test_data=test_data[0], # (horizon, features) - optional
title="Time Series Forecast"
)
# Save to file
fig.savefig("forecast.png", dpi=300, bbox_inches="tight")# Option 1: All features on same plot
fig, ax = plot_forecast(
train_data[0],
response.point[0],
test_data[0],
features_on_same_plot=True,
feature_names=["Temperature", "Humidity", "Pressure"]
)
# Option 2: Separate subplots per feature
fig, axes = plot_forecast(
train_data[0],
response.point[0],
test_data[0],
features_on_same_plot=False,
feature_names=["Temperature", "Humidity", "Pressure"]
)import numpy as np
from faim_sdk import ForecastClient, Chronos2ForecastRequest
from faim_sdk.eval import mse, mase, crps_from_quantiles, plot_forecast
# Initialize client
client = ForecastClient()
# Prepare data splits
train_data = np.random.randn(32, 100, 1)
test_data = np.random.randn(32, 24, 1)
# Generate forecast
request = Chronos2ForecastRequest(
x=train_data,
horizon=24,
output_type="quantiles",
quantiles=[0.1, 0.5, 0.9]
)
response = client.forecast(request)
# Evaluate point forecast (use median)
point_pred = response.quantiles[:, :, 1:2] # Extract median, keep 3D shape
mse_score = mse(test_data, point_pred)
mase_score = mase(test_data, point_pred, train_data)
# Evaluate probabilistic forecast
crps_score = crps_from_quantiles(
test_data,
response.quantiles,
quantile_levels=[0.1, 0.5, 0.9]
)
print(f"MSE: {mse_score:.4f}")
print(f"MASE: {mase_score:.4f}")
print(f"CRPS: {crps_score:.4f}")
# Visualize best and worst predictions
mse_per_sample = mse(test_data, point_pred, reduction='none')
best_idx = np.argmin(mse_per_sample)
worst_idx = np.argmax(mse_per_sample)
fig1, ax1 = plot_forecast(
train_data[best_idx],
point_pred[best_idx],
test_data[best_idx],
title=f"Best Forecast (MSE: {mse_per_sample[best_idx]:.4f})"
)
fig1.savefig("best_forecast.png")
fig2, ax2 = plot_forecast(
train_data[worst_idx],
point_pred[worst_idx],
test_data[worst_idx],
title=f"Worst Forecast (MSE: {mse_per_sample[worst_idx]:.4f})"
)
fig2.savefig("worst_forecast.png")The SDK provides machine-readable error codes for robust error handling:
from faim_sdk import (
ForecastClient,
Chronos2ForecastRequest,
ValidationError,
AuthenticationError,
RateLimitError,
ModelNotFoundError,
ErrorCode
)
try:
request = Chronos2ForecastRequest(x=data, horizon=24, quantiles=[0.1, 0.5, 0.9])
response = client.forecast(request)
except AuthenticationError as e:
# Handle authentication failures (401, 403)
print(f"Authentication failed: {e.message}")
print(f"Request ID: {e.error_response.request_id}")
except ValidationError as e:
# Handle invalid request parameters (422)
if e.error_code == ErrorCode.INVALID_SHAPE:
print(f"Shape error: {e.error_response.detail}")
# Fix shape and retry
elif e.error_code == ErrorCode.MISSING_REQUIRED_FIELD:
print(f"Missing field: {e.error_response.detail}")
except RateLimitError as e:
# Handle rate limiting (429)
print("Rate limit exceeded - implementing exponential backoff")
retry_after = e.error_response.metadata.get('retry_after', 60)
time.sleep(retry_after)
except ModelNotFoundError as e:
# Handle model/version not found (404)
print(f"Model not found: {e.message}")FAIMError (base)
├── APIError
│ ├── AuthenticationError (401, 403)
│ ├── InsufficientFundsError (402)
│ ├── ModelNotFoundError (404)
│ ├── PayloadTooLargeError (413)
│ ├── ValidationError (422)
│ ├── RateLimitError (429)
│ ├── InternalServerError (500)
│ └── ServiceUnavailableError (503, 504)
├── NetworkError
├── SerializationError
├── TimeoutError
└── ConfigurationError
The SDK supports async operations for concurrent requests:
import asyncio
from faim_sdk import ForecastClient, Chronos2ForecastRequest
async def forecast_multiple_series():
client = ForecastClient(
api_key="your-api-key"
)
# Create multiple requests
requests = [
Chronos2ForecastRequest(x=data1, horizon=24),
Chronos2ForecastRequest(x=data2, horizon=24),
Chronos2ForecastRequest(x=data3, horizon=24),
]
# Execute concurrently
async with client:
tasks = [
client.forecast_async(req)
for req in requests
]
responses = await asyncio.gather(*tasks)
return responses
# Run async forecasts
responses = asyncio.run(forecast_multiple_series())See the examples/ directory for complete Jupyter notebook examples:
toy_example.ipynb- A toy example showing how to get started with FAIM and generate both point and probabilistic forecasts.
- Python >= 3.10
- numpy >= 1.26.0
- pyarrow >= 11.0.0
- httpx >= 0.23.0
- pydantic >= 2.0.0
-
Batch Processing: Process multiple time series in a single request for optimal throughput
# Good: Single request with 32 series data = np.random.randn(32, 100, 1) # Less efficient: 32 separate requests # for series in data: client.forecast(...)
-
Compression: Use
compression="zstd"for large payloads (default, recommended) -
Async for Concurrent Requests: Use
forecast_async()withasyncio.gather()for parallel processing -
Connection Pooling: Reuse client instances across requests instead of creating new ones
- Email: support@faim.it.com
Apache License 2.0 - See LICENSE file for details.
If you use FAIM in your research, please cite:
@software{faim_sdk,
title = {FAIM SDK: Foundation AI Models for Time Series Forecasting},
author = {FAIM Team},
year = {2024},
url = {https://github.com/S-FM/faim-python-client}
}