Backend Abstraction (Backend Trait)
Relevant source files
The following files were used as context for generating this wiki page:
The backend abstraction in caro is a critical architectural component designed to decouple the core application logic from the specific Large Language Model (LLM) inference engines. This is achieved through the CommandGenerator trait, which defines a unified interface for various backends. This design allows caro to support multiple command generation engines—such as a default embedded model, local services like Ollama, and remote services like vLLM—interchangeably.
The primary goal of this abstraction is to provide flexibility and extensibility. Developers can add support for new LLM backends by simply creating a new struct and implementing the CommandGenerator trait, without modifying the core command generation pipeline. This modularity is essential for testing, maintenance, and adapting to new inference technologies.
Sources: specs/001-create-a-comprehensive/plan.md, specs/002-implement-tdd-green/plan.md, src/backends/mod.rs
The CommandGenerator Trait
The CommandGenerator trait is the cornerstone of the backend abstraction. It defines the contract that all command generation backends must adhere to.
Sources: src/backends/mod.rs, specs/001-create-a-comprehensive/contracts/backend-trait.rs
Trait Definition
The trait is defined with async_trait to support asynchronous operations, which is necessary for handling network requests to remote backends or loading models without blocking the main thread.
This diagram shows the CommandGenerator trait and its four primary methods.
Sources: src/backends/mod.rs:24-49, specs/002-implement-tdd-green/plan.md:231-238
Methods
The trait specifies four essential methods for any backend implementation.
| Method | Description | Return Type |
|---|---|---|
generate_command | Takes a CommandRequest and asynchronously returns a GeneratedCommand. This is the primary method for generating a shell command. | Result<GeneratedCommand, GeneratorError> |
is_available | Asynchronously checks if the backend is available and ready to serve requests (e.g., network connectivity, model loaded). | Future<bool> |
backend_info | Asynchronously returns metadata about the backend, such as its type, model name, and capabilities. | Future<BackendInfo> |
shutdown | Performs graceful shutdown and resource cleanup for the backend. | Future<()> |
Sources: src/backends/mod.rs:24-49, specs/001-create-a-comprehensive/contracts/backend-trait.rs
A code snippet of the trait definition:
// Source: src/backends/mod.rs:24-49
#[async_trait]
pub trait CommandGenerator: Send + Sync {
/// Asynchronously generates a command based on the provided request.
async fn generate_command(
&self,
request: &CommandRequest,
) -> Result<GeneratedCommand, GeneratorError>;
/// Checks if the backend is available and ready to serve requests.
async fn is_available(&self) -> bool;
/// Returns metadata about the backend.
async fn backend_info(&self) -> BackendInfo;
/// Performs graceful shutdown and resource cleanup.
async fn shutdown(&self) -> Result<(), GeneratorError>;
}
Core Data Structures
The CommandGenerator trait relies on a set of shared data structures for requests, responses, and metadata.
Sources: specs/002-implement-tdd-green/plan.md:138-181
Data Flow
The basic data flow for command generation is straightforward: the CliApp creates a CommandRequest, passes it to a CommandGenerator implementation, and receives a GeneratedCommand.
This diagram illustrates the flow of data from request to generated command through the backend trait.
Sources: specs/002-implement-tdd-green/plan.md:183-193
Key Structures and Enums
| Name | Type | Description | File |
|---|---|---|---|
CommandRequest | Struct | Encapsulates the user's natural language input, shell type, safety level, and other context for the generation request. | src/models/command_request.rs |
GeneratedCommand | Struct | Contains the generated shell command, an explanation, confidence score, and other metadata from the backend. | src/models/generated_command.rs |
BackendInfo | Struct | Provides metadata about a backend, including its type, model name, and performance characteristics. | src/models/backend_config.rs |
BackendType | Enum | An enumeration of the different supported backend types, such as Mock, Ollama, Vllm, and Mlx. | src/models/enums.rs |
GeneratorError | Enum | Defines a set of possible errors that can occur during command generation, used with thiserror. | src/backends/mod.rs |
Sources: specs/001-create-a-comprehensive/tasks.md:T016-T023, specs/002-implement-tdd-green/plan.md:138-181
Backend Implementations
The architecture is designed to support multiple concrete implementations of the CommandGenerator trait.
This diagram shows the various backend structs that implement the CommandGenerator trait.
Sources: specs/004-implement-ollama-and/plan.md, specs/001-create-a-comprehensive/tasks.md:T025-T027
The planned backends include:
- EmbeddedModelBackend: The default, "batteries-included" backend that works offline. It has two variants:
- MlxBackend: Optimized for Apple Silicon (M1/M2/M3/M4) using the MLX framework.
- CpuBackend: A cross-platform fallback using Burn or Candle for CPU-based inference.
- OllamaBackend: Integrates with a locally running Ollama service via its HTTP API.
- VllmBackend: Integrates with a remote, high-performance vLLM server.
- MockBackend: A mock implementation used for testing the trait contract and integration points without needing a real LLM.
Sources: specs/004-implement-ollama-and/plan.md, specs/002-implement-tdd-green/plan.md:234-237
Backend Selection and Fallback
The system includes a prioritized logic for selecting which backend to use and a fallback mechanism for resilience.
Selection Priority
The backend is chosen based on the following priority order:
- CLI Flag: A user-provided
--backendflag overrides all other settings for a single command. - Configuration File: A
preferred_backendsetting in~/.caro/config.toml. - Auto-Detection / Default: If no preference is set, the system defaults to the
EmbeddedModelBackend.
Sources: specs/004-implement-ollama-and/spec.md:FR-010
Fallback Mechanism
To ensure caro is always functional, it implements a fallback strategy. If a configured remote backend (like Ollama or vLLM) is unavailable or fails, the system automatically and transparently falls back to the EmbeddedModelBackend. This guarantees that the tool works offline and is resilient to network or service issues.
The following diagram illustrates the selection and fallback flow:
This flowchart shows the logic for selecting a backend, checking its availability, and falling back to the embedded model if necessary.
Sources: specs/004-implement-ollama-and/spec.md:FR-005, specs/004-implement-ollama-and/spec.md:FR-026
Contract Testing
To ensure all backends behave consistently, a suite of contract tests is defined in tests/backend_trait_contract.rs. These tests are written against the CommandGenerator trait and must pass for any concrete implementation. This is a key part of the Test-Driven Development (TDD) process, where the contract is defined and tested before the implementations are written.
The contract tests validate key behaviors, including:
- Basic command generation.
- Backend availability checks.
- Correctness of
BackendInfometadata. - Error handling for unavailable backends.
- Graceful shutdown.
Sources: tests/backend_trait_contract.rs, specs/001-create-a-comprehensive/tasks.md:T006, specs/002-implement-tdd-green/plan.md:198-200
Conclusion
The CommandGenerator trait and its associated backend abstraction are fundamental to caro's architecture. This design provides a robust, flexible, and testable system for integrating various LLM inference engines. By defining a clear contract and providing a reliable embedded fallback, the system ensures a consistent user experience while allowing power users to leverage more capable remote backends. This separation of concerns is key to the project's maintainability and future extensibility.
*Sources: exports/session-logs/2025-09-13-readme.txt, specs/002-implement-tdd-green/plan.md *