How Embedding Models Function Within the Embabel Framework: Architecture and Code Examples

Embedding models function within the Embabel framework as interchangeable Spring beans wrapped by the EmbeddingService interface, enabling seamless integration into RAG pipelines with automatic observability tracing.

The embabel/embabel-agent repository treats embedding generation as a first-class infrastructure concern, abstracting vectorization behind a unified service layer that supports multiple providers. Understanding how embedding models function within the Embabel framework reveals a modular architecture compatible with OpenAI, Azure, OCI Gen AI, ONNX, and custom implementations while maintaining consistent configuration and monitoring patterns.

Architectural Layers

Embabel organizes embedding support into four distinct layers, each handled by specific components in the codebase.

Configuration Management

The framework determines which concrete embedding model to instantiate through property-driven configuration. The OciGenAiEnvironmentPostProcessor class reads the property embabel.models.default-embedding-model and injects it into the Spring environment as DEFAULT_EMBEDDING_PROPERTY. This approach allows operators to switch models without modifying application code.

Service Abstraction

At the core of the system lies the EmbeddingService interface, implemented primarily by SpringAiEmbeddingService. This wrapper bridges Embabel's internal API with Spring AI's EmbeddingModel, exposing the model name and provider while standardizing the embed(texts) method signature. Because the service is a Spring bean, it supports scoping (singleton, prototype) and runtime replacement.

Observability Integration

Every embedding invocation generates distributed tracing data through EmbabelSpanEventListener. This component creates a span named embabel.embedding and annotates it with attributes including gen_ai.operation.name and embabel.llm.cost, enabling monitoring of token usage and model performance in production environments.

RAG Pipeline Consumption

Components such as RagService and LuceneSearchOperations consume the EmbeddingService to vectorize documents and queries. The LuceneSearchOperations class, for example, converts text lists into EmbeddingRequest objects and processes the resulting EmbeddingResponse for indexing or similarity search.

Execution Flow

When an agent invokes an embedding operation, the framework executes a five-step lifecycle:

  1. Property Resolution – OciGenAiEnvironmentPostProcessor resolves the default model name from configuration properties.
  2. Bean Creation – Spring instantiates a SpringAiEmbeddingService bean containing the chosen EmbeddingModel implementation.
  3. Invocation – RAG components like HyDEQueryGenerator call embeddingService.embed(texts), which delegates to EmbeddingModel.call(EmbeddingRequest).
  4. Observability – EmbabelSpanEventListener intercepts the call and emits the embabel.embedding span with model metadata.
  5. Result Propagation – Generated float vectors return to the caller for downstream indexing or LLM prompting.

Code Implementation Examples

Defining a Custom Embedding Bean

You can register custom embedding models by defining a bean that returns SpringAiEmbeddingService:

@Configuration
class MyEmbeddingConfig {
    @Bean
    fun myEmbeddingService(): EmbeddingService {
        // Wrap a Spring AI model (could be an OCI Gen AI client, ONNX, etc.)
        return SpringAiEmbeddingService(
            name = "my-embedding-model",
            provider = "my-provider",
            model = MyConcreteEmbeddingModel()   // implements org.springframework.ai.embedding.EmbeddingModel
        )
    }
}

Source: [FakeAiConfiguration.kt](https://github.com/embabel/embabel-agent/blob/main/embabel-agent-test-support/embabel-agent-test-internal/src/main/kotlin/com/embabel/common/test/ai/config/FakeAiConfiguration.kt)

Consuming Embeddings in RAG Components

The following pattern demonstrates how RAG services utilize the embedding layer:

@Service
public class LuceneSearchOperations {
    private final EmbeddingService embeddingService;

    public LuceneSearchOperations(EmbeddingService embeddingService) {
        this.embeddingService = embeddingService;
    }

    public List<Vector> embedTexts(List<String> texts) {
        // Convert to Spring AI request
        EmbeddingRequest request = new EmbeddingRequest(texts, EmbeddingOptions.builder().build());
        EmbeddingResponse response = embeddingService.getModel().call(request);
        return response.getResult();
    }
}

Source: [LuceneSearchOperationsTestBase.kt](https://github.com/embabel/embabel-agent/blob/main/embabel-agent-rag/embabel-agent-rag-lucene/src/test/kotlin/com/embabel/agent/rag/lucene/LuceneSearchOperationsTestBase.kt)

Testing Embedding Observability

The EmbabelSpanEventListener creates traceable events for every embedding call:

@Test
@DisplayName("embedding invocation becomes a span with model and input tokens")
void embeddingInvocationSpan() {
    listener().onProcessEvent(embeddingEvent());
    Map<String, String> kv = kvOf("embabel.embedding");
    assertEquals("embeddings", kv.get("gen_ai.operation.name"));
    assertEquals("text-embedding-3", kv.get("gen_ai.request.model"));
}

Source: [EmbabelSpanEventListenerTest.java](https://github.com/embabel/embabel-agent/blob/main/embabel-agent-observability/src/test/java/com/embabel/agent/observability/tracing/EmbabelSpanEventListenerTest.java)

Implementing Fake Models for Testing

For unit tests, FakeEmbeddingModel provides deterministic embeddings without external API calls:

data class FakeEmbeddingModel(
    private val dimensions: Int = 8
) : EmbeddingModel {
    override fun call(request: EmbeddingRequest): EmbeddingResponse {
        val output = LinkedList<Embedding>()
        request.text.forEachIndexed { i, _ ->
            output.add(Embedding(generateRandomFloatArray(dimensions), i))
        }
        return EmbeddingResponse(output)
    }
}

Source: [FakeEmbeddingModel.kt](https://github.com/embabel/embabel-agent/blob/main/embabel-agent-test-support/embabel-agent-test-common/src/main/kotlin/com/embabel/common/test/ai/FakeEmbeddingModel.kt)

Summary

  • Embedding models function within the Embabel framework as pluggable Spring beans managed through the EmbeddingService interface, decoupling vectorization logic from RAG pipelines.
  • The SpringAiEmbeddingService class bridges Embabel's API with Spring AI's EmbeddingModel, supporting providers including OCI Gen AI, OpenAI, and ONNX.
  • Configuration occurs through the OciGenAiEnvironmentPostProcessor, which reads embabel.models.default-embedding-model to determine the active implementation.
  • Every embedding operation emits an embabel.embedding span via EmbabelSpanEventListener, capturing model name and cost attributes for production monitoring.
  • The FakeEmbeddingModel enables lightweight testing without external dependencies, generating deterministic vectors for unit test suites.

Frequently Asked Questions

What interface defines embedding operations in Embabel?

The EmbeddingService interface defines the contract for embedding operations, implemented by SpringAiEmbeddingService to wrap Spring AI's EmbeddingModel. This abstraction allows any concrete model—whether OpenAI, Azure, or custom ONNX implementations—to function within the Embabel framework without changing consumer code.

How does Embabel determine which embedding model to use?

The OciGenAiEnvironmentPostProcessor class reads the property embabel.models.default-embedding-model during application startup and injects it into the Spring environment. Spring then instantiates the corresponding EmbeddingModel bean and wraps it in a SpringAiEmbeddingService, making the model available for injection into RAG components.

Can I trace embedding costs and token usage in production?

Yes. The EmbabelSpanEventListener automatically creates an embabel.embedding span for every embedding request, recording attributes such as gen_ai.operation.name, gen_ai.request.model, and embabel.llm.cost. This integration enables monitoring of embedding expenses and performance characteristics through distributed tracing systems.

How do I test components that depend on embeddings without calling external APIs?

Embabel provides FakeEmbeddingModel in the test-support module, which implements Spring AI's EmbeddingModel interface to generate deterministic random vectors. This fake implementation allows unit tests for classes like LuceneSearchOperations to run quickly and reliably without network dependencies or API costs.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →