MagicAF follows a strict three-layer architecture. Each layer has a single responsibility, and layers communicate only through well-defined trait boundaries.
Layer Model
Layer 1 — Infrastructure
The infrastructure layer provides the fundamental AI primitives. Each primitive is defined as an async trait.
| Module | Trait | Purpose |
|---|---|---|
embeddings | EmbeddingService | Produce dense vectors from text |
vector_store | VectorStore | Index, search, and delete vectors with JSON payloads |
llm | LlmService | Chat completion and text generation via OpenAI-compatible API |
Concrete implementations ship in separate crates:
| Implementation | Crate | Backend |
|---|---|---|
LocalEmbeddingService | magicaf-core | Any OpenAI-compatible /v1/embeddings endpoint |
QdrantVectorStore | magicaf-qdrant | Qdrant (REST API) |
InMemoryVectorStore | magicaf-core | In-process brute-force search (no external deps) |
LocalLlmService | magicaf-local-llm | Any OpenAI-compatible /v1/chat/completions endpoint |
Layer 2 — Orchestration
RAGWorkflow is the pipeline engine. It is fully generic over all service and adapter traits:
pub struct RAGWorkflow<S, V, L, EF, PB, RP, T>
where
S: EmbeddingService,
V: VectorStore,
L: LlmService,
EF: EvidenceFormatter,
PB: PromptBuilder,
RP: ResultParser<T>,
T: Send,
The workflow executes a fixed six-step pipeline:
- Embed the query →
EmbeddingService::embed_single - Search the vector store →
VectorStore::search - Format evidence →
EvidenceFormatter::format_evidence - Build the prompt →
PromptBuilder::build_prompt - Invoke the LLM →
LlmService::chat - Parse the result →
ResultParser::parse_result
Layer 3 — Adapters
Three traits form the domain extension seam. This is where your application-specific logic lives.
| Trait | Injected at Step | Domain Responsibility |
|---|---|---|
EvidenceFormatter | 3 | Convert search results → text block for the LLM |
PromptBuilder | 4 | Assemble system + evidence + query → prompt |
ResultParser<T> | 6 | Parse raw LLM text → typed domain result |
Default implementations ship in magicaf-core for rapid prototyping:
DefaultEvidenceFormatter— pretty-prints JSON payloadsDefaultPromptBuilder— wraps evidence in<context>tagsRawResultParser— returns the raw LLM output asStringJsonResultParser<T>— deserializes JSON into anyT: DeserializeOwned
Crate Dependency Graph
Both implementation crates depend only on magicaf-core. Downstream applications depend on all three.
Guiding Principles
- Separation of concerns. Infrastructure is independent of orchestration, which is independent of domain logic.
- Trait-driven extensibility. Every major component is accessed through a trait — swapping implementations is a one-line config change.
- DTO-based public surface. Request/response types are plain structs with
Serialize/Deserialize, easy to expose over FFI or serialize to JSON. - Error codes for FFI. Every
MagicErrorvariant maps to a stable numeric code, enabling clean error propagation across language boundaries.