OpenAI - Embeddings, Moderation and Audio Operations
Embeddings Operation
The embeddings operation generates vector embeddings from text, which can be used for semantic search, similarity comparison, and RAG (Retrieval-Augmented Generation) applications.
Basic Embedding
-
Java
-
YAML
from("direct:embed")
.setBody(constant("What is Apache Camel?"))
.to("openai:embeddings?embeddingModel=nomic-embed-text") - route:
from:
uri: direct:embed
steps:
- to:
uri: openai:embeddings
parameters:
embeddingModel: nomic-embed-text The response body is the embedding vector data:
-
Single input:
List<Float>(a single embedding vector) -
Batch input:
List<List<Float>>(one embedding vector per input string)
Additional metadata (model, token usage, vector size, count) is exposed via headers (see OpenAIConstants).
Batch Embedding
You can embed multiple texts in a single request by passing a List<String>:
List.of() for batch inputfrom("direct:batch-embed")
.setBody(constant(List.of("First text", "Second text", "Third text")))
.to("openai:embeddings?embeddingModel=nomic-embed-text")
.log("Generated ${header.CamelOpenAIEmbeddingCount} embeddings"); Direct Vector Database Integration
For single-input requests, the component returns a raw List<Float> embedding vector, enabling direct chaining to vector database components.
PostgreSQL + pgvector (Recommended)
Using the PGVector component:
# Index documents in PostgreSQL with pgvector
- route:
from:
uri: direct:index
steps:
- setVariable:
name: text
expression:
simple:
expression: "${body}"
- to:
uri: openai:embeddings
parameters:
embeddingModel: nomic-embed-text
- setHeader:
name: CamelPgVectorAction
constant: UPSERT
- setHeader:
name: CamelPgVectorTextContent
expression:
simple:
expression: "${variable.text}"
- to:
uri: pgvector:documents
# Similarity search
- route:
from:
uri: direct:search
steps:
- to:
uri: openai:embeddings
parameters:
embeddingModel: nomic-embed-text
- setHeader:
name: CamelPgVectorAction
constant: SIMILARITY_SEARCH
- setHeader:
name: CamelPgVectorQueryTopK
constant: 5
- to:
uri: pgvector:documents The pgvector component handles table creation, HNSW indexing, upsert with conflict resolution, and similarity search with configurable distance types (cosine, euclidean, inner product). See the PGVector component documentation for details.
For custom table schemas, complex queries (joins, CTEs), or integration with existing PostgreSQL tables, you can use camel-sql directly with the pgvector extension:
- to:
uri: sql:INSERT INTO documents (content, embedding) VALUES (:#text, :#embedding::vector) Similarity Calculation
The component can automatically calculate cosine similarity when a reference embedding is provided:
List<Float> variable for reference embeddingList<Float> referenceEmbedding = /* previously computed embedding */;
from("direct:compare")
.setBody(constant("New text to compare"))
.setHeader("CamelOpenAIReferenceEmbedding", constant(referenceEmbedding))
.to("openai:embeddings?embeddingModel=nomic-embed-text")
.log("Similarity score: ${header.CamelOpenAISimilarityScore}"); You can also use SimilarityUtils directly for manual calculations:
SimilarityUtils API for vector mathimport org.apache.camel.component.openai.SimilarityUtils;
double similarity = SimilarityUtils.cosineSimilarity(embedding1, embedding2);
double distance = SimilarityUtils.euclideanDistance(embedding1, embedding2);
List<Float> normalized = SimilarityUtils.normalize(embedding); Embeddings Output Headers
The following headers are set after an embeddings request:
| Header | Type | Description |
|---|---|---|
| String | The model used for embedding |
| Integer | Number of embeddings returned |
| Integer | Dimension of each embedding vector |
| Integer | Tokens used in the input |
| Integer | Total tokens used |
| String/List | Original input text(s) |
| Double | Cosine similarity (if reference embedding provided) |
Moderation Operation
The moderation operation checks text against the OpenAI usage policies. It is the canonical pre-filter for untrusted input on a public-facing route: rejecting policy-violating content before spending chat tokens or triggering tool calls.
The message body is passed through unchanged and the verdict is exposed as headers, so the result can be used for content-based routing while the original content stays available to the rest of the route.
| Moderation is a policy filter, not a trust boundary. The verdict is probabilistic and its categories are defined by the provider, so it is not a substitute for authentication, authorization, schema validation or defences against prompt injection. The operation only reports a verdict — a flagged body keeps flowing unless the route stops or replaces it, as in the example below. |
The operation moderates text only. The body, or each element of a list body, is converted to a String and sent as text input; the multi-modal inputs of the moderation API are not exposed. |
Guarding a Route
-
Java
-
YAML
from("platform-http:/chat")
.to("openai:moderation?moderationModel=omni-moderation-latest")
.choice()
.when(header(OpenAIConstants.MODERATION_FLAGGED).isEqualTo(true))
.setBody(constant("Your message violates our usage policy."))
.otherwise()
.to("openai:chat-completion?model=gpt-5")
.end(); - from:
uri: platform-http:/chat
steps:
- to: openai:moderation?moderationModel=omni-moderation-latest
- choice:
when:
- simple: "${header.CamelOpenAIModerationFlagged} == true"
steps:
- setBody:
constant: "Your message violates our usage policy."
otherwise:
steps:
- to: openai:chat-completion?model=gpt-5 Verdicts per Input
CamelOpenAIModerationResults always holds one verdict per moderated input, in the order of the inputs. Each entry is a map with the keys input, flagged, categories and categoryScores, which makes a batch straightforward to split and route per item:
from("direct:moderate-batch")
.to("openai:moderation")
.split(header(OpenAIConstants.MODERATION_RESULTS))
.choice()
.when(simple("${body[flagged]}"))
.to("direct:quarantine")
.otherwise()
.to("direct:downstream")
.end(); A List body moderates every element in a single API call, and CamelOpenAIModerationFlagged is then true when at least one element was flagged.
For a single input, the same categories are also exposed as plain maps in CamelOpenAIModerationCategories and CamelOpenAIModerationCategoryScores, which is convenient for acting on one category directly — for example routing anything the model is fairly confident about to human review:
from("direct:moderate")
.to("openai:moderation")
.choice()
.when(simple("${header.CamelOpenAIModerationCategoryScores[hate]} > 0.85"))
.to("direct:human-review")
.otherwise()
.to("direct:downstream")
.end(); Those two headers are not set for a list body, where CamelOpenAIModerationResults carries the verdicts.
Failure Modes
The operation is meant to gate untrusted content, so it fails the exchange rather than letting a message through without a verdict:
-
the API returning a number of results that does not match the number of inputs raises a
CamelExchangeException, instead of leavingCamelOpenAIModerationFlaggedasfalse; -
a missing body, an empty list, or a list containing
nullelements raises anIllegalArgumentException.
Moderation Output Headers
The following headers are set after a moderation request:
| Header | Type | Description |
|---|---|---|
| Boolean | Whether the input violates the usage policies. For a batch, |
| List | One verdict per input, in input order. Each entry holds |
| Map | Category name to violation flag, for a single input. Not set for a list body |
| Map | Category name to confidence score, for a single input. Not set for a list body |
| String | The model used for moderation |
The category names are the ones returned by the API, for example hate, hate/threatening, self-harm/intent, sexual/minors and violence/graphic.
The illicit and illicit/violent categories are optional in the API model. OpenAI returns them, but an OpenAI-compatible provider may not, in which case they are absent from the category map. The score map always contains every category. |
Audio Transcription Operation
The audio-transcription operation transcribes audio files to text using OpenAI’s speech-to-text models (Whisper, GPT-4o Transcribe).
Basic Audio Transcription
-
Java
-
YAML
from("file:audio?noop=true")
.to("openai:audio-transcription?audioModel=whisper-1")
.log("Transcription: ${body}"); - route:
from:
uri: direct:transcribe
steps:
- to:
uri: openai:audio-transcription
parameters:
audioModel: whisper-1
- log:
message: "Transcription: ${body}" Input Handling
The audio transcription operation accepts the following types in the message body:
-
java.io.File- Audio file reference -
java.nio.file.Path- Path to an audio file -
java.io.InputStream- Audio data stream -
byte[]- Raw audio bytes
Supported audio formats: flac, mp3, mp4, mpeg, mpga, m4a, ogg, wav, webm.
Audio Transcription Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| String | The model to use (e.g., | |
| String | Input audio language in ISO-639-1 format (e.g., | |
| String | Optional text to guide the model’s style or continue a previous segment. | |
| String |
| Output format: |
| Double | Sampling temperature (0.0 to 1.0). | |
| String | Comma-separated: |
Audio Transcription Output Headers
| Header | Type | Description |
|---|---|---|
| Double | Duration of the audio in seconds (verbose_json only) |
| String | Language detected in the audio (verbose_json only) |
Audio Models by Provider
| Provider | Model | Description |
|---|---|---|
OpenAI |
| General-purpose speech recognition |
OpenAI |
| High-accuracy transcription based on GPT-4o |
OpenAI |
| Lighter-weight GPT-4o variant |
Local Audio Transcription Servers
The audio transcription operation works with any OpenAI-compatible server that implements the POST /v1/audio/transcriptions endpoint. It has been tested with:
-
MLX Audio —
python3 -m mlx_audio.server --host 127.0.0.1 --port 8003
-
Java
-
XML
-
YAML
from("direct:transcribe")
.to("openai:audio-transcription?audioModel=mlx-community/whisper-large-v3-turbo"
+ "&baseUrl=http://localhost:8003/v1"); <route>
<from uri="direct:transcribe"/>
<to uri="openai:audio-transcription?audioModel=mlx-community/whisper-large-v3-turbo&baseUrl=http://localhost:8003/v1"/>
</route> - route:
from:
uri: direct:transcribe
steps:
- to:
uri: openai:audio-transcription
parameters:
audioModel: mlx-community/whisper-large-v3-turbo
baseUrl: http://localhost:8003/v1 | Some local servers require the model parameter to be a path (e.g., |
Audio Translation Operation
The audio-translation operation transcribes audio in any supported language and translates it into English text using OpenAI’s POST /v1/audio/translations endpoint. It mirrors the transcription operation and accepts the same body types.
Basic Audio Translation
-
Java
-
XML
-
YAML
from("file:inbox/voicemail?noop=true")
.to("openai:audio-translation?audioModel=whisper-1")
.log("English transcript: ${body}"); <route>
<from uri="file:inbox/voicemail?noop=true"/>
<to uri="openai:audio-translation?audioModel=whisper-1"/>
<log message="English transcript: ${body}"/>
</route> - route:
from:
uri: file:inbox/voicemail?noop=true
steps:
- to:
uri: openai:audio-translation
parameters:
audioModel: whisper-1
- log:
message: "English transcript: ${body}" Input Handling
The audio translation operation accepts the same message body types as transcription:
-
java.io.File- Audio file reference -
java.nio.file.Path- Path to an audio file -
java.io.InputStream- Audio data stream -
byte[]- Raw audio bytes
Audio Translation Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| String | The model to use (e.g., | |
| String | Optional text to guide the model’s style. Should be in English. | |
| String |
| Output format: |
| Double | Sampling temperature (0.0 to 1.0). |
The translation operation always outputs English text, so it does not accept an audioLanguage parameter. |
Audio Speech (Text-to-Speech) Operation
The audio-speech operation synthesizes spoken audio from text using OpenAI’s POST /v1/audio/speech endpoint. The message body is the input text, and the produced body is the generated audio as a byte[]. The Content-Type header is set based on the selected response format, so the result chains naturally into file:, object storage, or messaging endpoints.
Basic Text-to-Speech
-
Java
-
XML
-
YAML
from("direct:speak")
.to("openai:audio-speech?speechModel=gpt-4o-mini-tts&speechVoice=alloy&speechResponseFormat=mp3")
.to("file:out?fileName=answer.mp3"); <route>
<from uri="direct:speak"/>
<to uri="openai:audio-speech?speechModel=gpt-4o-mini-tts&speechVoice=alloy&speechResponseFormat=mp3"/>
<to uri="file:out?fileName=answer.mp3"/>
</route> - route:
from:
uri: direct:speak
steps:
- to:
uri: openai:audio-speech
parameters:
speechModel: gpt-4o-mini-tts
speechVoice: alloy
speechResponseFormat: mp3
- to:
uri: file:out?fileName=answer.mp3 Chaining Chat Completion into Speech
Turn an LLM answer into an mp3 and drop it on object storage:
from("direct:speak")
.to("openai:chat-completion?model=gpt-5")
.to("openai:audio-speech?speechModel=gpt-4o-mini-tts&speechVoice=alloy&speechResponseFormat=mp3")
.to("aws2-s3://announcements?keyName=answer-${exchangeId}.mp3"); Audio Speech Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| String | The model to use (e.g., | |
| String |
| The voice to use (e.g., |
| String |
| Audio format: |
| Double | Playback speed from 0.25 to 4.0 (1.0 is normal). | |
| String | Optional instructions to control the voice. Does not work with |
Audio Speech Output
The message body is the generated audio as a byte[]. The Content-Type header is taken from the HTTP response when present, otherwise derived from speechResponseFormat (e.g., audio/mpeg for mp3, audio/wav for wav).
| The generated audio is fully buffered into a |