This is an automated email from the ASF dual-hosted git repository.

luigidemasi pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git

commit 7b83c275063ddfca17a8e8807ce3ea8c08464971
Author: Luigi De Masi <[email protected]>
AuthorDate: Wed Oct 14 22:29:58 2026 +0200

    CAMEL-25669: Add the Semantic OpenAI expert for Camel 4.24
    
    Map native Decisions to typed Semantic results with original provider 
diagnostics, configurable threshold and uncertainty, and an explicitly 
configured model. Introduce the bridge in Camel 4.24 without adding a Semantic 
dependency to camel-openai.
    
    Co-authored-by: OpenAI Codex <[email protected]>
    Signed-off-by: Luigi De Masi <[email protected]>
---
 bom/camel-bom/pom.xml                              |   5 +
 catalog/camel-allcomponents/pom.xml                |   5 +
 .../org/apache/camel/catalog/docs.properties       |   1 +
 .../camel/catalog/docs/openai-component.adoc       |   4 +-
 .../camel/catalog/docs/semantic-language.adoc      |   4 +-
 .../apache/camel/catalog/docs/semantic-openai.adoc | 170 ++++++++++
 .../camel/catalog/docs/typesafe-ai-component.adoc  |   2 +-
 .../org/apache/camel/catalog/others.properties     |   1 +
 .../camel/catalog/others/semantic-openai.json      |  15 +
 .../src/main/docs/openai-component.adoc            |   4 +-
 components/camel-ai/camel-semantic-openai/pom.xml  |  62 ++++
 .../services/org/apache/camel/other.properties     |   7 +
 .../src/generated/resources/semantic-openai.json   |  15 +
 .../src/main/docs/semantic-openai.adoc             | 170 ++++++++++
 .../openai/OpenAIDecisionsSemanticAdapter.java     | 352 +++++++++++++++++++++
 .../openai/OpenAIDecisionsSemanticAdapterTest.java | 339 ++++++++++++++++++++
 .../src/main/docs/semantic-language.adoc           |   4 +-
 .../src/main/docs/typesafe-ai-component.adoc       |   2 +-
 components/camel-ai/pom.xml                        |   1 +
 .../others/examples/json/semantic-openai.json      |   1 +
 docs/components/modules/others/nav.adoc            |   1 +
 .../modules/others/pages/semantic-openai.adoc      |   1 +
 parent/pom.xml                                     |   5 +
 .../apache/camel/maven/packaging/MojoHelper.java   |   3 +-
 24 files changed, 1167 insertions(+), 7 deletions(-)

diff --git a/bom/camel-bom/pom.xml b/bom/camel-bom/pom.xml
index c172734deeac..fbcb17ed32d4 100644
--- a/bom/camel-bom/pom.xml
+++ b/bom/camel-bom/pom.xml
@@ -2192,6 +2192,11 @@
         <artifactId>camel-semantic</artifactId>
         <version>4.23.0-SNAPSHOT</version>
       </dependency>
+      <dependency>
+        <groupId>org.apache.camel</groupId>
+        <artifactId>camel-semantic-openai</artifactId>
+        <version>4.23.0-SNAPSHOT</version>
+      </dependency>
       <dependency>
         <groupId>org.apache.camel</groupId>
         <artifactId>camel-servicenow</artifactId>
diff --git a/catalog/camel-allcomponents/pom.xml 
b/catalog/camel-allcomponents/pom.xml
index e7a7467cfa29..6ca49cbce535 100644
--- a/catalog/camel-allcomponents/pom.xml
+++ b/catalog/camel-allcomponents/pom.xml
@@ -1977,6 +1977,11 @@
             <artifactId>camel-semantic</artifactId>
             <version>${project.version}</version>
         </dependency>
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-semantic-openai</artifactId>
+            <version>${project.version}</version>
+        </dependency>
         <dependency>
             <groupId>org.apache.camel</groupId>
             <artifactId>camel-servicenow</artifactId>
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs.properties
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs.properties
index bc3c3f2ee630..ec98922cc543 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs.properties
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs.properties
@@ -566,6 +566,7 @@ script-eip
 seda-component
 selective-consumer
 semantic-language
+semantic-openai
 service-activator
 servicenow-component
 servlet-component
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/openai-component.adoc
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/openai-component.adoc
index 24dcf60ca7c0..fa7468334fef 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/openai-component.adoc
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/openai-component.adoc
@@ -60,7 +60,9 @@ Refusal raises `OpenAIDecisionRefusalException`, invalid 
provider output raises
 `OpenAIDecisionResponseException`, and transport/API/client-start failures 
raise
 `OpenAIDecisionServiceException`. No failure is converted into a decision 
value.
 
-See the
+For named Semantic evaluations, typed Boolean/Choice/Score mapping, and body 
image input,
+add xref:others:semantic-openai.adoc[camel-semantic-openai]. The OpenAI 
component itself has no
+dependency on `camel-semantic`. See the
 https://developers.openai.com/api/docs/guides/decisions[Decisions guide] for 
the provider contract.
 
 == URI Format
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/semantic-language.adoc
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/semantic-language.adoc
index 1cb7caea48c1..de2b3d50dce9 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/semantic-language.adoc
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/semantic-language.adoc
@@ -172,7 +172,9 @@ A negative detection does not establish general safety or 
grant authorization.
 === Text and image input
 
 Experts declare supported modalities in their operation contract. `IMAGE` 
allows explicit image
-parts; it does not imply that every expert accepts images. TypeSafe AI accepts 
text and its structured state, and Wolf Defender accepts text. Unsupported 
input fails
+parts; it does not imply that every expert accepts images. The
+xref:others:semantic-openai.adoc[OpenAI Decisions expert] accepts text and 
images, TypeSafe AI
+accepts text and its structured state, and Wolf Defender accepts text. 
Unsupported input fails
 before inference, including before any expert in a mixed batch runs.
 
 For `$\{body}`, an image `Content-Type` such as `image/png` identifies a 
`byte[]`, `InputStream`,
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/semantic-openai.adoc
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/semantic-openai.adoc
new file mode 100644
index 000000000000..7ad4b3546904
--- /dev/null
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/semantic-openai.adoc
@@ -0,0 +1,170 @@
+= Semantic OpenAI Component
+:doctitle: Semantic OpenAI
+:shortname: semantic-openai
+:artifactid: camel-semantic-openai
+:description: Assess text and images with typed OpenAI Decisions through 
Semantic experts
+:since: 4.24
+:supportlevel: Preview
+:tabs-sync-option:
+
+*Since Camel {since}*
+
+This module supplies an explicit expert for the 
xref:languages:semantic-language.adoc[Semantic language].
+It asks the https://developers.openai.com/api/docs/guides/decisions[OpenAI 
Decisions API]
+for an assessment. The Camel route decides what action to take with that 
assessment.
+It has no routing endpoint of its own. It depends on `camel-semantic` and 
`camel-openai`;
+`camel-openai` remains independently usable and does not depend on Semantic.
+
+[source,xml]
+----
+<dependency>
+    <groupId>org.apache.camel</groupId>
+    <artifactId>camel-semantic-openai</artifactId>
+    <version>x.x.x</version>
+</dependency>
+----
+
+== Register an expert
+
+The adapter uses an `openai:decisions` endpoint's managed client, credentials, 
base URL,
+proxy, TLS, timeout and retry configuration. Configure the component as 
described in
+xref:ROOT:openai-component.adoc[OpenAI]. It does not create a second HTTP 
client or
+initialize route tools or MCP connections for Decisions calls.
+
+[source,properties]
+----
+camel.component.openai.api-key={{env:OPENAI_API_KEY}}
+----
+
+[source,yaml]
+----
+- beans:
+    - name: vision
+      type: org.apache.camel.semantic.openai.OpenAIDecisionsSemanticAdapter
+      properties:
+        endpointUri: "openai:decisions?model=gpt-6-luna"
+- semantic:
+    evaluation:
+      damaged:
+        expert: vision
+        operation: boolean
+        state: "${body}"
+        parameters:
+          instructions: Is the package visibly damaged?
+          threshold: 0.5
+          uncertainty: 0.1
+          uncertaintyPolicy: fail
+- route:
+    from:
+      uri: direct:inspect
+      steps:
+        - setVariable:
+            name: damaged
+            simple: "${semantic('damaged')}"
+        - to: direct:route-policy
+----
+
+`endpointUri` defaults to `openai:decisions`; set it before the first 
evaluation.
+Configure a Decisions model explicitly on the endpoint, for example 
`model=gpt-6-luna`.
+An endpoint without a model is rejected before inference.
+The selected endpoint/model must implement Decisions; OpenAI-compatible chat 
support alone
+is insufficient. There is no fallback to chat completion or structured 
prompting.
+When constructing the bean in Java, set its `CamelContext` before binding it 
to the registry.
+Every evaluation must explicitly name an expert. Capability inspection and 
declaration
+validation do not perform inference.
+
+== Input
+
+Supported modalities are text, images and ordered combinations of text and 
images.
+Audio, video, file IDs, hosted image URLs and other modalities are rejected; 
no transcription,
+OCR, URL fetching or modality conversion service is invoked.
+The native image representation follows the Decisions guide: an inline base64 
data URL for
+PNG, JPEG, GIF or WebP. Use the Camel-managed OpenAI Java SDK (4.78.0 or 
newer, which includes
+Decisions). The generic API reference may describe additional forms; this 
adapter validates
+the forms supported by this integration.
+
+For a single image in the message body, set `Content-Type` to the image MIME 
type, for example
+`image/png`. The body may be a `byte[]`, `InputStream`, Camel `StreamCache`, 
or plain base64
+`String`. A string starting with `data:image/` is self-describing and needs no 
header:
+
+[source,text]
+----
+data:image/png;base64,iVBORw0KGgo...
+----
+
+The example is abbreviated. Plain strings without an image content type remain 
text; Camel
+does not guess whether an arbitrary string is base64. Content-Type applies 
when the selected
+state is `$\{body}`; it does not reclassify a separately selected header or 
variable.
+Streams are cached and reset before evaluation. Downstream steps receive a 
replayable stream
+cache containing the same bytes. Byte arrays and strings keep their original 
body identity.
+
+Use an explicit envelope for multiple ordered parts, or an image selected from 
a header or variable:
+
+[source,json]
+----
+{
+  "$semanticInput": [
+    {"type": "text", "text": "Inspect the package in this photograph."},
+    {"type": "image", "data": "data:image/png;base64,...", "detail": "high"}
+  ]
+}
+----
+
+Replace the abbreviated payload with actual base64. `detail` is optional and 
may be `auto`,
+`low`, `high` or `original`. In Java, `SemanticInput.ofParts`, `text`, and 
`image` build this
+JSON-compatible envelope; `image(byte[], mediaType)` encodes binary image data.
+An ordinary application map is not implicitly treated as media.
+
+The expert accepts at most 128 images and 200 questions per request. Every 
part and question
+is checked before contacting the provider. Other experts validate their own 
supported input:
+TypeSafe AI accepts text and its structured application state, while Wolf 
Defender accepts text.
+Neither silently converts images into text. In a batch, all experts validate 
input before any
+expert starts inference.
+
+== Result contract
+
+[cols="1,1,3"]
+|===
+|Operation |Value type |Meaning
+|`boolean` |`Boolean` |Provider predicate probability compared once against 
the configured threshold.
+|`choice` |`String` |One of the declared category keys.
+|`score` |`Number` |Provider probability-weighted zero-based rubric index, 
including its fractional part.
+|===
+
+Every operation requires `instructions`. `choice` also requires a `criteria` 
map with 2–255
+distinct category keys and descriptions. `score` requires an ordered, nonempty 
`criteria` list
+of distinct labels and accepts an optional `descriptions` map keyed by those 
labels.
+
+For example, `[low, medium, high]` defines indices 0, 1 and 2. A score of 
`1.1` is an assessment
+between medium and high; it is not a Boolean probability and is not rounded to 
a label.
+Audit and playground output retain the ordered rubric and descriptions. Score 
probability
+keys are the decimal zero-based indices (`"0"`, `"1"`, `"2"`).
+
+`boolean` defaults to an inclusive threshold of `0.5`, matching TypeSafe AI's 
convention.
+An optional `threshold` overrides it. `uncertainty` defaults to zero; a 
positive value defines
+an inclusive band of that half-width around the threshold. Both edges must be 
within `[0,1]`.
+Inside the band, `uncertaintyPolicy=fail` raises an exception; explicit 
`non-match` returns false.
+The original provider probability is retained in either successful result. 
Camel does not
+invent a confidence value or recalibrate provider uncertainty.
+
+Detailed probability, distribution and optional confidence are available 
through
+`CamelSemanticResult` (single evaluation) or `CamelSemanticResults` (batch). 
Missing provider
+fields remain absent. Named `refs:` batches use one Decisions request per 
expert instance and
+retain declaration order. A failed or refused answer prevents publishing 
partial batch results.
+
+== Failures
+
+Refusal raises `OpenAIDecisionRefusalException`. Invalid or inconsistent 
provider output raises
+`OpenAIDecisionResponseException`. Transport/API/client-start failures raise
+`OpenAIDecisionServiceException`. These exceptions belong to `camel-openai` 
and can be handled
+with the route's normal `onException` policy. Unsupported input and invalid 
declarations fail
+validation; infrastructure failure is never mapped to false or a score.
+
+== Opt-in API smoke tests
+
+`camel-openai` includes `OpenAIDecisionsExternalServiceIT` for native text 
batches and mixed
+text/image input. It is disabled by default. To run it against an explicitly 
configured service,
+run `mvn verify -Pit -Dit.test=OpenAIDecisionsExternalServiceIT 
-Dopenai.live.tests=true` in
+`components/camel-ai/camel-openai`, providing `openai.live.baseUrl`, 
`openai.live.apiKey` and
+`openai.live.decisions.model` as system properties. These tests send requests 
to the service
+and may incur charges. Assertions check response types and ranges, not exact 
probabilities.
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/typesafe-ai-component.adoc
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/typesafe-ai-component.adoc
index 87d2621f0118..ac5c4d881a60 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/typesafe-ai-component.adoc
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/typesafe-ai-component.adoc
@@ -313,4 +313,4 @@ The Semantic adapter accepts text and TypeSafe AI's 
supported structured applica
 It rejects explicit `$semanticInput` image/media envelopes, including nested 
media envelopes,
 and binary state before initializing transport. Selecting a body with an image 
`Content-Type`
 or a self-describing image data URL does not make this text/structured expert 
multimodal.
-Use an image-capable expert for images.
+Use an image-capable expert such as xref:others:semantic-openai.adoc[Semantic 
OpenAI] for images.
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/others.properties
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/others.properties
index 1698000d5524..062ad4905c0b 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/others.properties
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/others.properties
@@ -51,6 +51,7 @@ resilience4j
 resilience4j-micrometer
 resourceresolver-github
 rxjava
+semantic-openai
 shiro
 spring-cloud-config
 spring-main
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/others/semantic-openai.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/others/semantic-openai.json
new file mode 100644
index 000000000000..816a5b5e9edf
--- /dev/null
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/others/semantic-openai.json
@@ -0,0 +1,15 @@
+{
+  "other": {
+    "kind": "other",
+    "name": "semantic-openai",
+    "title": "Semantic OpenAI",
+    "description": "Assess text and images with typed OpenAI Decisions through 
Semantic experts",
+    "deprecated": false,
+    "firstVersion": "4.24.0",
+    "label": "ai",
+    "supportLevel": "Preview",
+    "groupId": "org.apache.camel",
+    "artifactId": "camel-semantic-openai",
+    "version": "4.23.0-SNAPSHOT"
+  }
+}
diff --git 
a/components/camel-ai/camel-openai/src/main/docs/openai-component.adoc 
b/components/camel-ai/camel-openai/src/main/docs/openai-component.adoc
index 24dcf60ca7c0..fa7468334fef 100644
--- a/components/camel-ai/camel-openai/src/main/docs/openai-component.adoc
+++ b/components/camel-ai/camel-openai/src/main/docs/openai-component.adoc
@@ -60,7 +60,9 @@ Refusal raises `OpenAIDecisionRefusalException`, invalid 
provider output raises
 `OpenAIDecisionResponseException`, and transport/API/client-start failures 
raise
 `OpenAIDecisionServiceException`. No failure is converted into a decision 
value.
 
-See the
+For named Semantic evaluations, typed Boolean/Choice/Score mapping, and body 
image input,
+add xref:others:semantic-openai.adoc[camel-semantic-openai]. The OpenAI 
component itself has no
+dependency on `camel-semantic`. See the
 https://developers.openai.com/api/docs/guides/decisions[Decisions guide] for 
the provider contract.
 
 == URI Format
diff --git a/components/camel-ai/camel-semantic-openai/pom.xml 
b/components/camel-ai/camel-semantic-openai/pom.xml
new file mode 100644
index 000000000000..5dc0208e0dc6
--- /dev/null
+++ b/components/camel-ai/camel-semantic-openai/pom.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You under the Apache License, Version 2.0
+    (the "License"); you may not use this file except in compliance with
+    the License.  You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd";>
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.apache.camel</groupId>
+        <artifactId>camel-ai-parent</artifactId>
+        <version>4.23.0-SNAPSHOT</version>
+    </parent>
+    <artifactId>camel-semantic-openai</artifactId>
+    <name>Camel :: AI :: Semantic OpenAI</name>
+    <description>Assess text and images with typed OpenAI Decisions through 
Semantic experts</description>
+    <properties>
+        <firstVersion>4.24.0</firstVersion>
+        <title>Semantic OpenAI</title>
+        <label>ai</label>
+    </properties>
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-semantic</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-openai</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-test-junit6</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.assertj</groupId>
+            <artifactId>assertj-core</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-core</artifactId>
+            <version>${mockito-version}</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+</project>
diff --git 
a/components/camel-ai/camel-semantic-openai/src/generated/resources/META-INF/services/org/apache/camel/other.properties
 
b/components/camel-ai/camel-semantic-openai/src/generated/resources/META-INF/services/org/apache/camel/other.properties
new file mode 100644
index 000000000000..f68bc6f1c1d8
--- /dev/null
+++ 
b/components/camel-ai/camel-semantic-openai/src/generated/resources/META-INF/services/org/apache/camel/other.properties
@@ -0,0 +1,7 @@
+# Generated by camel build tools - do NOT edit this file!
+name=semantic-openai
+groupId=org.apache.camel
+artifactId=camel-semantic-openai
+version=4.23.0-SNAPSHOT
+projectName=Camel :: AI :: Semantic OpenAI
+projectDescription=Assess text and images with typed OpenAI Decisions through 
Semantic experts
diff --git 
a/components/camel-ai/camel-semantic-openai/src/generated/resources/semantic-openai.json
 
b/components/camel-ai/camel-semantic-openai/src/generated/resources/semantic-openai.json
new file mode 100644
index 000000000000..816a5b5e9edf
--- /dev/null
+++ 
b/components/camel-ai/camel-semantic-openai/src/generated/resources/semantic-openai.json
@@ -0,0 +1,15 @@
+{
+  "other": {
+    "kind": "other",
+    "name": "semantic-openai",
+    "title": "Semantic OpenAI",
+    "description": "Assess text and images with typed OpenAI Decisions through 
Semantic experts",
+    "deprecated": false,
+    "firstVersion": "4.24.0",
+    "label": "ai",
+    "supportLevel": "Preview",
+    "groupId": "org.apache.camel",
+    "artifactId": "camel-semantic-openai",
+    "version": "4.23.0-SNAPSHOT"
+  }
+}
diff --git 
a/components/camel-ai/camel-semantic-openai/src/main/docs/semantic-openai.adoc 
b/components/camel-ai/camel-semantic-openai/src/main/docs/semantic-openai.adoc
new file mode 100644
index 000000000000..7ad4b3546904
--- /dev/null
+++ 
b/components/camel-ai/camel-semantic-openai/src/main/docs/semantic-openai.adoc
@@ -0,0 +1,170 @@
+= Semantic OpenAI Component
+:doctitle: Semantic OpenAI
+:shortname: semantic-openai
+:artifactid: camel-semantic-openai
+:description: Assess text and images with typed OpenAI Decisions through 
Semantic experts
+:since: 4.24
+:supportlevel: Preview
+:tabs-sync-option:
+
+*Since Camel {since}*
+
+This module supplies an explicit expert for the 
xref:languages:semantic-language.adoc[Semantic language].
+It asks the https://developers.openai.com/api/docs/guides/decisions[OpenAI 
Decisions API]
+for an assessment. The Camel route decides what action to take with that 
assessment.
+It has no routing endpoint of its own. It depends on `camel-semantic` and 
`camel-openai`;
+`camel-openai` remains independently usable and does not depend on Semantic.
+
+[source,xml]
+----
+<dependency>
+    <groupId>org.apache.camel</groupId>
+    <artifactId>camel-semantic-openai</artifactId>
+    <version>x.x.x</version>
+</dependency>
+----
+
+== Register an expert
+
+The adapter uses an `openai:decisions` endpoint's managed client, credentials, 
base URL,
+proxy, TLS, timeout and retry configuration. Configure the component as 
described in
+xref:ROOT:openai-component.adoc[OpenAI]. It does not create a second HTTP 
client or
+initialize route tools or MCP connections for Decisions calls.
+
+[source,properties]
+----
+camel.component.openai.api-key={{env:OPENAI_API_KEY}}
+----
+
+[source,yaml]
+----
+- beans:
+    - name: vision
+      type: org.apache.camel.semantic.openai.OpenAIDecisionsSemanticAdapter
+      properties:
+        endpointUri: "openai:decisions?model=gpt-6-luna"
+- semantic:
+    evaluation:
+      damaged:
+        expert: vision
+        operation: boolean
+        state: "${body}"
+        parameters:
+          instructions: Is the package visibly damaged?
+          threshold: 0.5
+          uncertainty: 0.1
+          uncertaintyPolicy: fail
+- route:
+    from:
+      uri: direct:inspect
+      steps:
+        - setVariable:
+            name: damaged
+            simple: "${semantic('damaged')}"
+        - to: direct:route-policy
+----
+
+`endpointUri` defaults to `openai:decisions`; set it before the first 
evaluation.
+Configure a Decisions model explicitly on the endpoint, for example 
`model=gpt-6-luna`.
+An endpoint without a model is rejected before inference.
+The selected endpoint/model must implement Decisions; OpenAI-compatible chat 
support alone
+is insufficient. There is no fallback to chat completion or structured 
prompting.
+When constructing the bean in Java, set its `CamelContext` before binding it 
to the registry.
+Every evaluation must explicitly name an expert. Capability inspection and 
declaration
+validation do not perform inference.
+
+== Input
+
+Supported modalities are text, images and ordered combinations of text and 
images.
+Audio, video, file IDs, hosted image URLs and other modalities are rejected; 
no transcription,
+OCR, URL fetching or modality conversion service is invoked.
+The native image representation follows the Decisions guide: an inline base64 
data URL for
+PNG, JPEG, GIF or WebP. Use the Camel-managed OpenAI Java SDK (4.78.0 or 
newer, which includes
+Decisions). The generic API reference may describe additional forms; this 
adapter validates
+the forms supported by this integration.
+
+For a single image in the message body, set `Content-Type` to the image MIME 
type, for example
+`image/png`. The body may be a `byte[]`, `InputStream`, Camel `StreamCache`, 
or plain base64
+`String`. A string starting with `data:image/` is self-describing and needs no 
header:
+
+[source,text]
+----
+data:image/png;base64,iVBORw0KGgo...
+----
+
+The example is abbreviated. Plain strings without an image content type remain 
text; Camel
+does not guess whether an arbitrary string is base64. Content-Type applies 
when the selected
+state is `$\{body}`; it does not reclassify a separately selected header or 
variable.
+Streams are cached and reset before evaluation. Downstream steps receive a 
replayable stream
+cache containing the same bytes. Byte arrays and strings keep their original 
body identity.
+
+Use an explicit envelope for multiple ordered parts, or an image selected from 
a header or variable:
+
+[source,json]
+----
+{
+  "$semanticInput": [
+    {"type": "text", "text": "Inspect the package in this photograph."},
+    {"type": "image", "data": "data:image/png;base64,...", "detail": "high"}
+  ]
+}
+----
+
+Replace the abbreviated payload with actual base64. `detail` is optional and 
may be `auto`,
+`low`, `high` or `original`. In Java, `SemanticInput.ofParts`, `text`, and 
`image` build this
+JSON-compatible envelope; `image(byte[], mediaType)` encodes binary image data.
+An ordinary application map is not implicitly treated as media.
+
+The expert accepts at most 128 images and 200 questions per request. Every 
part and question
+is checked before contacting the provider. Other experts validate their own 
supported input:
+TypeSafe AI accepts text and its structured application state, while Wolf 
Defender accepts text.
+Neither silently converts images into text. In a batch, all experts validate 
input before any
+expert starts inference.
+
+== Result contract
+
+[cols="1,1,3"]
+|===
+|Operation |Value type |Meaning
+|`boolean` |`Boolean` |Provider predicate probability compared once against 
the configured threshold.
+|`choice` |`String` |One of the declared category keys.
+|`score` |`Number` |Provider probability-weighted zero-based rubric index, 
including its fractional part.
+|===
+
+Every operation requires `instructions`. `choice` also requires a `criteria` 
map with 2–255
+distinct category keys and descriptions. `score` requires an ordered, nonempty 
`criteria` list
+of distinct labels and accepts an optional `descriptions` map keyed by those 
labels.
+
+For example, `[low, medium, high]` defines indices 0, 1 and 2. A score of 
`1.1` is an assessment
+between medium and high; it is not a Boolean probability and is not rounded to 
a label.
+Audit and playground output retain the ordered rubric and descriptions. Score 
probability
+keys are the decimal zero-based indices (`"0"`, `"1"`, `"2"`).
+
+`boolean` defaults to an inclusive threshold of `0.5`, matching TypeSafe AI's 
convention.
+An optional `threshold` overrides it. `uncertainty` defaults to zero; a 
positive value defines
+an inclusive band of that half-width around the threshold. Both edges must be 
within `[0,1]`.
+Inside the band, `uncertaintyPolicy=fail` raises an exception; explicit 
`non-match` returns false.
+The original provider probability is retained in either successful result. 
Camel does not
+invent a confidence value or recalibrate provider uncertainty.
+
+Detailed probability, distribution and optional confidence are available 
through
+`CamelSemanticResult` (single evaluation) or `CamelSemanticResults` (batch). 
Missing provider
+fields remain absent. Named `refs:` batches use one Decisions request per 
expert instance and
+retain declaration order. A failed or refused answer prevents publishing 
partial batch results.
+
+== Failures
+
+Refusal raises `OpenAIDecisionRefusalException`. Invalid or inconsistent 
provider output raises
+`OpenAIDecisionResponseException`. Transport/API/client-start failures raise
+`OpenAIDecisionServiceException`. These exceptions belong to `camel-openai` 
and can be handled
+with the route's normal `onException` policy. Unsupported input and invalid 
declarations fail
+validation; infrastructure failure is never mapped to false or a score.
+
+== Opt-in API smoke tests
+
+`camel-openai` includes `OpenAIDecisionsExternalServiceIT` for native text 
batches and mixed
+text/image input. It is disabled by default. To run it against an explicitly 
configured service,
+run `mvn verify -Pit -Dit.test=OpenAIDecisionsExternalServiceIT 
-Dopenai.live.tests=true` in
+`components/camel-ai/camel-openai`, providing `openai.live.baseUrl`, 
`openai.live.apiKey` and
+`openai.live.decisions.model` as system properties. These tests send requests 
to the service
+and may incur charges. Assertions check response types and ranges, not exact 
probabilities.
diff --git 
a/components/camel-ai/camel-semantic-openai/src/main/java/org/apache/camel/semantic/openai/OpenAIDecisionsSemanticAdapter.java
 
b/components/camel-ai/camel-semantic-openai/src/main/java/org/apache/camel/semantic/openai/OpenAIDecisionsSemanticAdapter.java
new file mode 100644
index 000000000000..d1c477e9b3fa
--- /dev/null
+++ 
b/components/camel-ai/camel-semantic-openai/src/main/java/org/apache/camel/semantic/openai/OpenAIDecisionsSemanticAdapter.java
@@ -0,0 +1,352 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.semantic.openai;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.openai.models.decisions.Decision;
+import com.openai.models.decisions.DecisionChoiceOption;
+import com.openai.models.decisions.DecisionCreateParams;
+import com.openai.models.decisions.DecisionInputImage;
+import com.openai.models.decisions.DecisionInputMessage;
+import com.openai.models.decisions.DecisionInputPart;
+import com.openai.models.decisions.DecisionInputText;
+import org.apache.camel.CamelContext;
+import org.apache.camel.CamelContextAware;
+import org.apache.camel.ResolveEndpointFailedException;
+import org.apache.camel.component.openai.OpenAIDecisionServiceException;
+import org.apache.camel.component.openai.OpenAIDecisionsSupport;
+import org.apache.camel.component.openai.OpenAIEndpoint;
+import org.apache.camel.component.openai.OpenAIOperations;
+import org.apache.camel.semantic.SemanticAdapter;
+import org.apache.camel.semantic.SemanticCapabilities;
+import org.apache.camel.semantic.SemanticEvaluation;
+import org.apache.camel.semantic.SemanticExpert;
+import org.apache.camel.semantic.SemanticExpert.InputType;
+import org.apache.camel.semantic.SemanticExpert.ResultType;
+import org.apache.camel.semantic.SemanticInput;
+import org.apache.camel.semantic.SemanticOperation;
+import org.apache.camel.semantic.SemanticParameter;
+import org.apache.camel.semantic.SemanticResult;
+
+/** An explicitly registered expert; its OpenAI endpoint owns the client, 
credentials and transport lifecycle. */
+@SemanticExpert(name = "openai-decisions", provider = "openai", artifactId = 
"camel-semantic-openai",
+                description = "Typed text and image assessments using the 
OpenAI Decisions API",
+                operations = {
+                        @SemanticOperation(name = "boolean", description = 
"Assess whether a condition holds",
+                                           inputTypes = { InputType.TEXT, 
InputType.IMAGE },
+                                           inputRequirements = "Text, an image 
body with Content-Type, an image data URL, or an ordered $semanticInput 
envelope",
+                                           resultType = ResultType.BOOLEAN,
+                                           resultMeaning = "Predicate 
probability at or above threshold, subject to the configured uncertainty 
policy",
+                                           probability = true,
+                                           probabilityMeaning = "Provider 
probability that the condition is true",
+                                           parameters = {
+                                                   @SemanticParameter(name = 
"instructions",
+                                                                      
description = "Question to assess against the input",
+                                                                      required 
= true, minSize = 1, maxSize = 1048576),
+                                                   @SemanticParameter(name = 
"threshold",
+                                                                      
description = "Inclusive positive probability threshold",
+                                                                      type = 
Number.class, minimum = 0, maximum = 1,
+                                                                      omission 
= "Use 0.5"),
+                                                   @SemanticParameter(name = 
"uncertainty",
+                                                                      
description = "Half-width of the uncertainty band around the threshold",
+                                                                      type = 
Number.class, minimum = 0, maximum = 1,
+                                                                      omission 
= "No uncertainty band"),
+                                                   @SemanticParameter(name = 
"uncertaintyPolicy",
+                                                                      
description = "Behavior inside the uncertainty band",
+                                                                      values = 
{ "fail", "non-match" },
+                                                                      omission 
= "Fail the evaluation") }),
+                        @SemanticOperation(name = "choice", description = 
"Select one declared category",
+                                           inputTypes = { InputType.TEXT, 
InputType.IMAGE },
+                                           inputRequirements = "Text, an image 
body with Content-Type, an image data URL, or an ordered $semanticInput 
envelope",
+                                           resultType = ResultType.CHOICE, 
choiceLabelsParameter = "criteria",
+                                           noMatchDeclared = true, 
completeProbabilities = true,
+                                           resultMeaning = "The 
provider-selected category key",
+                                           probabilities = true,
+                                           probabilityMeaning = "Provider 
probability for each declared category",
+                                           confidence = true,
+                                           confidenceMeaning = "Confidence 
reported by OpenAI; no derived confidence is added",
+                                           parameters = {
+                                                   @SemanticParameter(name = 
"instructions",
+                                                                      
description = "Question to assess against the input",
+                                                                      required 
= true, minSize = 1, maxSize = 1048576),
+                                                   @SemanticParameter(name = 
"criteria",
+                                                                      
description = "Category keys and descriptions",
+                                                                      type = 
Map.class, itemType = String.class,
+                                                                      required 
= true,
+                                                                      minSize 
= 2, maxSize = 255) }),
+                        @SemanticOperation(name = "score", description = 
"Assess input against ordered rubric levels",
+                                           inputTypes = { InputType.TEXT, 
InputType.IMAGE },
+                                           inputRequirements = "Text, an image 
body with Content-Type, an image data URL, or an ordered $semanticInput 
envelope",
+                                           resultType = ResultType.SCORE, 
minimum = 0, scoreLevelsParameter = "criteria",
+                                           scoreDescriptionsParameter = 
"descriptions", completeProbabilities = true,
+                                           resultMeaning = "Provider 
probability-weighted zero-based rubric index; fractional scores are preserved",
+                                           probabilities = true,
+                                           probabilityMeaning = "Provider 
probability for each zero-based rubric index",
+                                           confidence = true,
+                                           confidenceMeaning = "Confidence 
reported by OpenAI; no derived confidence is added",
+                                           parameters = {
+                                                   @SemanticParameter(name = 
"instructions",
+                                                                      
description = "Question to assess against the input",
+                                                                      required 
= true, minSize = 1, maxSize = 1048576),
+                                                   @SemanticParameter(name = 
"criteria",
+                                                                      
description = "Distinct rubric labels ordered from lowest to highest",
+                                                                      type = 
List.class, itemType = String.class,
+                                                                      required 
= true, minSize = 1),
+                                                   @SemanticParameter(name = 
"descriptions",
+                                                                      
description = "Optional descriptions keyed by rubric label",
+                                                                      type = 
Map.class, itemType = String.class,
+                                                                      omission 
= "Use the rubric labels") }) })
+public class OpenAIDecisionsSemanticAdapter implements SemanticAdapter, 
CamelContextAware {
+    private static final SemanticCapabilities CAPABILITIES = 
SemanticCapabilities.from(OpenAIDecisionsSemanticAdapter.class);
+    private CamelContext camelContext;
+    private String endpointUri = "openai:decisions";
+    private volatile OpenAIEndpoint endpoint;
+
+    @Override
+    public CamelContext getCamelContext() {
+        return camelContext;
+    }
+
+    @Override
+    public void setCamelContext(CamelContext camelContext) {
+        this.camelContext = camelContext;
+    }
+
+    public String getEndpointUri() {
+        return endpointUri;
+    }
+
+    /** Configured Decisions endpoint, including model and transport options. 
Set before the first evaluation. */
+    public synchronized void setEndpointUri(String endpointUri) {
+        if (endpoint != null || endpointUri == null || endpointUri.isBlank()) {
+            throw new IllegalArgumentException("Set a nonblank Decisions 
endpoint URI before evaluation");
+        }
+        this.endpointUri = endpointUri;
+    }
+
+    private OpenAIEndpoint endpoint() {
+        if (camelContext != null && (camelContext.isStopping() || 
camelContext.isStopped())) {
+            throw new OpenAIDecisionServiceException("OpenAI Decisions 
requires a running CamelContext");
+        }
+        if (endpoint == null) {
+            synchronized (this) {
+                if (endpoint == null) {
+                    if (camelContext == null) {
+                        throw new IllegalStateException("OpenAI Decisions 
expert requires a CamelContext");
+                    }
+                    OpenAIEndpoint resolved;
+                    try {
+                        resolved = camelContext.getEndpoint(endpointUri, 
OpenAIEndpoint.class);
+                    } catch (ResolveEndpointFailedException e) {
+                        throw new OpenAIDecisionServiceException("Unable to 
initialize the OpenAI Decisions endpoint", e);
+                    }
+                    if (resolved.getOperation() != OpenAIOperations.decisions) 
{
+                        throw new IllegalArgumentException("OpenAI Decisions 
expert requires a Decisions endpoint");
+                    }
+                    endpoint = resolved;
+                }
+            }
+        }
+        return endpoint;
+    }
+
+    @Override
+    public void validate(SemanticEvaluation evaluation) {
+        CAPABILITIES.validate(evaluation);
+        if ("boolean".equals(evaluation.getOperation())) {
+            double threshold = number(evaluation, "threshold", 0.5);
+            double uncertainty = number(evaluation, "uncertainty", 0);
+            if (threshold - uncertainty < 0 || threshold + uncertainty > 1) {
+                throw new IllegalArgumentException("Threshold and uncertainty 
must keep the band within [0,1]");
+            }
+        } else if ("choice".equals(evaluation.getOperation())) {
+            descriptions(evaluation, "criteria").forEach((label, description) 
-> {
+                text(label);
+                text(description);
+            });
+        } else {
+            List<String> levels = levels(evaluation);
+            if (new HashSet<>(levels).size() != levels.size()) {
+                throw new IllegalArgumentException("Score rubric labels must 
be distinct");
+            }
+            levels.forEach(OpenAIDecisionsSemanticAdapter::text);
+            descriptions(evaluation, "descriptions").forEach((label, 
description) -> {
+                if (!levels.contains(label)) {
+                    throw new IllegalArgumentException("Score descriptions 
must refer to declared rubric labels");
+                }
+                text(description);
+            });
+        }
+    }
+
+    private static void text(String text) {
+        if (text.isBlank() || text.length() > 1_048_576) {
+            throw new IllegalArgumentException("Criteria require nonblank text 
within the provider text limit");
+        }
+    }
+
+    @Override
+    public void validateInput(SemanticEvaluation evaluation, Object state) {
+        state = SemanticInput.normalize(state);
+        CAPABILITIES.operation(evaluation.getOperation()).validateInput(state);
+    }
+
+    @Override
+    public void validateState(Object state) {
+        
OpenAIDecisionsSupport.validateInput(input(SemanticInput.normalize(state)));
+    }
+
+    @Override
+    public SemanticResult evaluate(SemanticEvaluation evaluation, Object 
state) throws Exception {
+        return evaluateBatch(Map.of("question", evaluation), 
state).get("question");
+    }
+
+    @Override
+    public Map<String, SemanticResult> evaluateBatch(Map<String, 
SemanticEvaluation> evaluations, Object state)
+            throws Exception {
+        if (evaluations.isEmpty() || evaluations.size() > 200) {
+            throw new IllegalArgumentException("OpenAI Decisions requires 1 to 
200 evaluations per batch");
+        }
+        Object input = SemanticInput.normalize(state);
+        evaluations.forEach((name, evaluation) -> {
+            text(name);
+            validate(evaluation);
+            validateInput(evaluation, input);
+        });
+        OpenAIEndpoint current = endpoint();
+        String model = current.getConfiguration().getModel();
+        if (model == null || model.isBlank()) {
+            throw new IllegalArgumentException("Configure a Decisions model on 
the OpenAI endpoint");
+        }
+        var request = 
DecisionCreateParams.builder().model(model).input(input(input));
+        evaluations.forEach((name, evaluation) -> 
request.addQuestion(question(name, evaluation)));
+        Decision response = current.decide(request.build());
+        Map<String, Object> metadata = metadata(response);
+        Map<String, SemanticResult> results = new LinkedHashMap<>();
+        int index = 0;
+        for (var entry : evaluations.entrySet()) {
+            SemanticResult result = result(entry.getValue(), 
response.answers().get(index++), metadata);
+            
CAPABILITIES.operation(entry.getValue().getOperation()).validateResult(result);
+            results.put(entry.getKey(), result);
+        }
+        return results;
+    }
+
+    private static DecisionCreateParams.Input input(Object state) {
+        if (state instanceof String text) {
+            return DecisionCreateParams.Input.ofString(text);
+        }
+        List<DecisionInputPart> parts = new ArrayList<>();
+        for (Object value : SemanticInput.parts(state)) {
+            Map<?, ?> part = (Map<?, ?>) value;
+            if ("text".equals(part.get("type"))) {
+                
parts.add(DecisionInputPart.ofInputText(DecisionInputText.builder().text((String)
 part.get("text")).build()));
+            } else {
+                var image = DecisionInputImage.builder().imageUrl((String) 
part.get("data"));
+                if (part.get("detail") instanceof String detail) {
+                    image.detail(DecisionInputImage.Detail.of(detail));
+                }
+                parts.add(DecisionInputPart.ofInputImage(image.build()));
+            }
+        }
+        return DecisionCreateParams.Input.ofDecisionInputMessages(List.of(
+                DecisionInputMessage.builder().contentOfParts(parts).build()));
+    }
+
+    private static DecisionCreateParams.Question question(String name, 
SemanticEvaluation evaluation) {
+        String instructions = (String) 
evaluation.getParameters().get("instructions");
+        if ("boolean".equals(evaluation.getOperation())) {
+            return 
DecisionCreateParams.Question.ofPredicate(DecisionCreateParams.Question.Predicate.builder()
+                    .name(name).instructions(instructions).build());
+        } else if ("choice".equals(evaluation.getOperation())) {
+            var question = 
DecisionCreateParams.Question.Choice.builder().name(name).instructions(instructions);
+            descriptions(evaluation, "criteria").forEach((label, description) 
-> question.addChoice(
+                    
DecisionChoiceOption.builder().value(label).description(description).build()));
+            return DecisionCreateParams.Question.ofChoice(question.build());
+        }
+        var question = 
DecisionCreateParams.Question.Score.builder().name(name).instructions(instructions);
+        Map<String, String> descriptions = descriptions(evaluation, 
"descriptions");
+        for (String label : levels(evaluation)) {
+            var level = 
DecisionCreateParams.Question.Score.Level.builder().label(label);
+            if (descriptions.containsKey(label)) {
+                level.description(descriptions.get(label));
+            }
+            question.addLevel(level.build());
+        }
+        return DecisionCreateParams.Question.ofScore(question.build());
+    }
+
+    private static SemanticResult result(SemanticEvaluation evaluation, 
Decision.Answer answer, Map<String, Object> metadata) {
+        if (answer.isPredicate()) {
+            double probability = answer.asPredicate().probability();
+            double threshold = number(evaluation, "threshold", 0.5);
+            double uncertainty = number(evaluation, "uncertainty", 0);
+            boolean value = probability >= threshold;
+            if (uncertainty > 0 && probability >= threshold - uncertainty && 
probability <= threshold + uncertainty) {
+                if 
(!"non-match".equals(evaluation.getParameters().get("uncertaintyPolicy"))) {
+                    throw new IllegalStateException("OpenAI decision 
probability is inside the configured uncertainty band");
+                }
+                value = false;
+            }
+            return new SemanticResult(value, probability, Map.of(), null, 
metadata);
+        }
+        Map<String, Double> probabilities = new LinkedHashMap<>();
+        if (answer.isChoice()) {
+            var choice = answer.asChoice();
+            choice._probabilities().asKnown()
+                    .ifPresent(values -> values.forEach(p -> 
probabilities.put(p.value().asString(), p.probability())));
+            return new SemanticResult(
+                    choice.choice().asString(), null, probabilities, 
choice._confidence().asKnown().orElse(null), metadata);
+        }
+        var score = answer.asScore();
+        score._probabilities().asKnown()
+                .ifPresent(values -> values.forEach(p -> 
probabilities.put(Long.toString(p.value()), p.probability())));
+        return new SemanticResult(score.score(), null, probabilities, 
score._confidence().asKnown().orElse(null), metadata);
+    }
+
+    private static Map<String, Object> metadata(Decision response) {
+        Map<String, Object> metadata = new LinkedHashMap<>();
+        metadata.put("provider", "openai");
+        metadata.put("model", response.model());
+        response._usage().asKnown().ifPresent(usage -> metadata.put("usage", 
Map.of(
+                "inputTokens", usage.inputTokens(), "outputTokens", 
usage.outputTokens(), "totalTokens", usage.totalTokens())));
+        return metadata;
+    }
+
+    @SuppressWarnings("unchecked")
+    private static List<String> levels(SemanticEvaluation evaluation) {
+        return (List<String>) evaluation.getParameters().get("criteria");
+    }
+
+    @SuppressWarnings("unchecked")
+    private static Map<String, String> descriptions(SemanticEvaluation 
evaluation, String parameter) {
+        Map<?, ?> values = (Map<?, ?>) 
evaluation.getParameters().getOrDefault(parameter, Map.of());
+        if (values.keySet().stream().anyMatch(key -> !(key instanceof 
String))) {
+            throw new IllegalArgumentException("Criteria and description keys 
must be strings");
+        }
+        return (Map<String, String>) values;
+    }
+
+    private static double number(SemanticEvaluation evaluation, String 
parameter, double defaultValue) {
+        return ((Number) evaluation.getParameters().getOrDefault(parameter, 
defaultValue)).doubleValue();
+    }
+}
diff --git 
a/components/camel-ai/camel-semantic-openai/src/test/java/org/apache/camel/semantic/openai/OpenAIDecisionsSemanticAdapterTest.java
 
b/components/camel-ai/camel-semantic-openai/src/test/java/org/apache/camel/semantic/openai/OpenAIDecisionsSemanticAdapterTest.java
new file mode 100644
index 000000000000..0e328d589780
--- /dev/null
+++ 
b/components/camel-ai/camel-semantic-openai/src/test/java/org/apache/camel/semantic/openai/OpenAIDecisionsSemanticAdapterTest.java
@@ -0,0 +1,339 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.semantic.openai;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+
+import com.openai.client.OpenAIClient;
+import com.openai.models.decisions.Decision;
+import com.openai.models.decisions.DecisionCreateParams;
+import org.apache.camel.Endpoint;
+import org.apache.camel.Exchange;
+import org.apache.camel.Expression;
+import org.apache.camel.StreamCache;
+import org.apache.camel.component.openai.OpenAIComponent;
+import org.apache.camel.component.openai.OpenAIConfiguration;
+import org.apache.camel.component.openai.OpenAIDecisionRefusalException;
+import org.apache.camel.component.openai.OpenAIDecisionServiceException;
+import org.apache.camel.component.openai.OpenAIEndpoint;
+import org.apache.camel.component.openai.OpenAIOperations;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.apache.camel.language.semantic.SemanticLanguage;
+import org.apache.camel.semantic.SemanticCapabilities;
+import org.apache.camel.semantic.SemanticEvaluation;
+import org.apache.camel.semantic.SemanticEvaluations;
+import org.apache.camel.semantic.SemanticInput;
+import org.apache.camel.semantic.SemanticResult;
+import org.apache.camel.semantic.internal.SemanticAuditExtensions;
+import org.apache.camel.support.DefaultExchange;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+class OpenAIDecisionsSemanticAdapterTest {
+    private DefaultCamelContext context;
+    private OpenAIDecisionsSemanticAdapter adapter;
+    private OpenAIClient client;
+    private Function<DecisionCreateParams, Decision> respond;
+
+    @BeforeEach
+    void setup() throws Exception {
+        context = new DefaultCamelContext();
+        client = mock(OpenAIClient.class, RETURNS_DEEP_STUBS);
+        
when(client.decisions().create(any(DecisionCreateParams.class))).thenAnswer(call
 -> respond.apply(call.getArgument(0)));
+        OpenAIComponent component = new OpenAIComponent();
+        component.setCamelContext(context);
+        OpenAIEndpoint endpoint = new OpenAIEndpoint("openai:decisions", 
component, configuration()) {
+            @Override
+            protected OpenAIClient createClient() {
+                return client;
+            }
+        };
+        endpoint.setOperation(OpenAIOperations.decisions);
+        context.addEndpoint("openai:decisions", endpoint);
+        adapter = new OpenAIDecisionsSemanticAdapter();
+        adapter.setCamelContext(context);
+        context.getRegistry().bind("expert", adapter);
+        context.start();
+    }
+
+    @AfterEach
+    void stop() throws Exception {
+        context.close();
+    }
+
+    @Test
+    void preservesBooleanProbabilityAndExchangeBody() {
+        var evaluation = evaluation("boolean", Map.of());
+        Expression expression = expression(evaluation);
+        verify(client.decisions(), 
never()).create(any(DecisionCreateParams.class));
+        respond = request -> response(predicate("question", 0.96));
+        var exchange = new DefaultExchange(context);
+        exchange.getMessage().setBody("original evidence");
+        assertThat(expression.evaluate(exchange, Boolean.class)).isTrue();
+        assertThat(exchange.getMessage().getBody()).isEqualTo("original 
evidence");
+        SemanticResult result = exchange.getProperty(SemanticLanguage.RESULT, 
SemanticResult.class);
+        assertThat(result.getProbability()).isEqualTo(0.96);
+        assertThat(result.getConfidence()).isNull();
+        assertThat(result.getMetadata()).containsEntry("provider", "openai");
+    }
+
+    @Test
+    void imageBodyFormsReachTheProviderAndStreamsRemainReplayable() {
+        Expression expression = expression(evaluation("boolean", Map.of()));
+        respond = request -> {
+            
assertThat(request.input().asDecisionInputMessages().get(0).content().asParts().get(0)
+                    
.asInputImage().imageUrl()).isEqualTo("data:image/png;base64,AQID");
+            return response(predicate("question", 0.9));
+        };
+        byte[] bytes = new byte[] { 1, 2, 3 };
+        for (Object body : List.of(bytes, new ByteArrayInputStream(bytes), 
"AQID", "data:image/png;base64,AQID")) {
+            var exchange = new DefaultExchange(context);
+            exchange.getMessage().setBody(body);
+            exchange.getMessage().setHeader(Exchange.CONTENT_TYPE, 
"image/png");
+            assertThat(expression.evaluate(exchange, Boolean.class)).isTrue();
+            if (exchange.getMessage().getBody() instanceof StreamCache cache) {
+                
assertThat(exchange.getMessage().getBody(byte[].class)).containsExactly(bytes);
+                cache.reset();
+                assertThat(expression.evaluate(exchange, 
Boolean.class)).isTrue();
+            } else {
+                assertThat(exchange.getMessage().getBody()).isSameAs(body);
+            }
+        }
+        verify(client.decisions(), 
times(5)).create(any(DecisionCreateParams.class));
+    }
+
+    @Test
+    void selectedTextHeaderDoesNotInheritTheImageBodyContentType() {
+        var evaluation = new SemanticEvaluation("boolean", "expert", 
"${header.evidence}", Map.of("instructions", "Assess"));
+        Expression expression = expression(evaluation);
+        respond = request -> {
+            assertThat(request.input().asString()).isEqualTo("Selected text");
+            return response(predicate("question", 0.9));
+        };
+        var exchange = new DefaultExchange(context);
+        exchange.getMessage().setBody(new byte[] { 1 });
+        exchange.getMessage().setHeader(Exchange.CONTENT_TYPE, "image/png");
+        exchange.getMessage().setHeader("evidence", "Selected text");
+        assertThat(expression.evaluate(exchange, Boolean.class)).isTrue();
+    }
+
+    @Test
+    void appliesThresholdAndUncertaintyExactlyOnce() throws Exception {
+        respond = request -> response(predicate("question", 0.5));
+        assertThat(adapter.evaluate(evaluation("boolean", Map.of()), 
"input").getValue()).isEqualTo(true);
+        assertThat(adapter.evaluate(evaluation("boolean", Map.of("threshold", 
0.6)), "input").getValue()).isEqualTo(false);
+        assertThatThrownBy(() -> adapter.evaluate(evaluation("boolean", 
Map.of("uncertainty", 0.1)), "input"))
+                
.isInstanceOf(IllegalStateException.class).hasMessageContaining("uncertainty 
band");
+        var result = adapter.evaluate(evaluation("boolean", 
Map.of("uncertainty", 0.1, "uncertaintyPolicy", "non-match")),
+                "input");
+        assertThat(result.getValue()).isEqualTo(false);
+        assertThat(result.getProbability()).isEqualTo(0.5);
+        assertThatThrownBy(() -> adapter.validate(evaluation("boolean", 
Map.of("threshold", 0.95, "uncertainty", 0.1))))
+                .isInstanceOf(IllegalArgumentException.class);
+    }
+
+    @Test
+    void mapsFractionalScoreAndOrderedRichRubric() throws Exception {
+        var evaluation = evaluation("score", Map.of("criteria", List.of("low", 
"medium", "high"),
+                "descriptions", Map.of("medium", "Workaround exists", "high", 
"Completely blocked")));
+        respond = request -> 
response(Decision.Answer.ofScore(Decision.Answer.Score.builder().name("question").score(1.1)
+                .confidence(0.82)
+                
.addProbability(Decision.Answer.Score.Probability.builder().value(0).label("low").probability(0.1).build())
+                
.addProbability(Decision.Answer.Score.Probability.builder().value(1).label("medium").probability(0.7).build())
+                
.addProbability(Decision.Answer.Score.Probability.builder().value(2).label("high").probability(0.2).build())
+                .build()));
+        var result = adapter.evaluate(evaluation, "input");
+        assertThat(result.getValue()).isEqualTo(1.1);
+        
assertThat(result.getProbabilities()).containsExactlyInAnyOrderEntriesOf(Map.of("0",
 0.1, "1", 0.7, "2", 0.2));
+        assertThat(result.getConfidence()).isEqualTo(0.82);
+        ArgumentCaptor<DecisionCreateParams> capture = 
ArgumentCaptor.forClass(DecisionCreateParams.class);
+        verify(client.decisions()).create(capture.capture());
+        
assertThat(capture.getValue().questions().get(0).asScore().levels()).extracting(l
 -> l.label())
+                .containsExactly("low", "medium", "high");
+        
assertThat(capture.getValue().questions().get(0).asScore().levels().get(1).description()).contains("Workaround
 exists");
+        var interpretation
+                = SemanticAuditExtensions.capture(evaluation, 
SemanticCapabilities.from(adapter.getClass()).operation("score"));
+        assertThat(interpretation).containsEntry("scoreLevels", List.of("low", 
"medium", "high"))
+                .containsEntry("scoreDescriptions", Map.of("medium", 
"Workaround exists", "high", "Completely blocked"));
+    }
+
+    @Test
+    void returnsStringChoiceWithNativeDistribution() throws Exception {
+        respond = request -> 
response(Decision.Answer.ofChoice(Decision.Answer.Choice.builder().name("question")
+                .choice("billing").confidence(0.8)
+                
.addProbability(Decision.Answer.Choice.Probability.builder().value("billing").probability(0.9).build())
+                
.addProbability(Decision.Answer.Choice.Probability.builder().value("technical").probability(0.1).build())
+                .build()));
+        var result = adapter.evaluate(
+                evaluation("choice", Map.of("criteria", Map.of("billing", 
"Payments", "technical", "Bugs"))), "input");
+        assertThat(result.getValue()).isEqualTo("billing");
+        assertThat(result.getProbabilities()).containsEntry("billing", 0.9);
+        assertThat(result.getConfidence()).isEqualTo(0.8);
+    }
+
+    @Test
+    void validatesAllInputsAndParametersBeforeCallingProvider() {
+        var evaluation = evaluation("boolean", Map.of());
+        for (Object input : List.of(new byte[] { 1 }, Map.of("state", "text"),
+                Map.of(SemanticInput.PARTS, List.of(Map.of("type", "audio", 
"data", "content"))),
+                
SemanticInput.ofParts(List.of(SemanticInput.image("https://example.com/image.png";)))))
 {
+            assertThatThrownBy(() -> adapter.evaluate(evaluation, 
input)).isInstanceOf(IllegalArgumentException.class);
+        }
+        var batch = new LinkedHashMap<String, SemanticEvaluation>();
+        batch.put("valid", evaluation);
+        batch.put("invalid", evaluation("score", Map.of("criteria", 
List.of("same", "same"))));
+        assertThatThrownBy(() -> adapter.evaluateBatch(batch, 
"input")).isInstanceOf(IllegalArgumentException.class);
+        verify(client.decisions(), 
never()).create(any(DecisionCreateParams.class));
+    }
+
+    @Test
+    void sharedMediaBatchPreservesPartOrderAndFailsAtomically() throws 
Exception {
+        var batch = new LinkedHashMap<String, SemanticEvaluation>();
+        batch.put("first", evaluation("boolean", Map.of()));
+        batch.put("second", evaluation("boolean", Map.of()));
+        var input = 
SemanticInput.ofParts(List.of(SemanticInput.text("Inspect"),
+                Map.of("type", "image", "data", "data:image/png;base64,AA==", 
"detail", "high"),
+                SemanticInput.text("and explain")));
+        respond = request -> response(predicate("first", 0.9), 
predicate("second", 0.2));
+        assertThat(adapter.evaluateBatch(batch, input)).hasSize(2);
+        ArgumentCaptor<DecisionCreateParams> capture = 
ArgumentCaptor.forClass(DecisionCreateParams.class);
+        verify(client.decisions()).create(capture.capture());
+        var parts = 
capture.getValue().input().asDecisionInputMessages().get(0).content().asParts();
+        assertThat(parts.get(0).asInputText().text()).isEqualTo("Inspect");
+        
assertThat(parts.get(1).asInputImage().detail().orElseThrow().asString()).isEqualTo("high");
+        assertThat(parts.get(2).asInputText().text()).isEqualTo("and explain");
+        respond = request -> response(predicate("first", 0.9),
+                
Decision.Answer.ofRefusal(Decision.Answer.Refusal.builder().name("second").build()));
+        assertThatThrownBy(() -> adapter.evaluateBatch(batch, 
input)).isInstanceOf(OpenAIDecisionRefusalException.class);
+    }
+
+    @Test
+    void failedEvaluationClearsPreviousResult() {
+        Expression expression = expression(evaluation("boolean", Map.of()));
+        var exchange = new DefaultExchange(context);
+        exchange.getMessage().setBody("text");
+        respond = request -> response(predicate("question", 0.9));
+        assertThat(expression.evaluate(exchange, Boolean.class)).isTrue();
+        respond = request -> 
response(Decision.Answer.ofRefusal(Decision.Answer.Refusal.builder().name("question").build()));
+        assertThatThrownBy(() -> expression.evaluate(exchange, Boolean.class))
+                .isInstanceOf(OpenAIDecisionRefusalException.class);
+        assertThat(exchange.getProperty(SemanticLanguage.RESULT)).isNull();
+    }
+
+    @Test
+    void lazyClientStartupFailureUsesTheDedicatedServiceException() {
+        context.addComponent("broken", new OpenAIComponent() {
+            @Override
+            protected Endpoint createEndpoint(String uri, String remaining, 
Map<String, Object> parameters) {
+                var endpoint = new OpenAIEndpoint(uri, this, configuration()) {
+                    @Override
+                    protected OpenAIClient createClient() throws Exception {
+                        throw new IOException("Client configuration 
unavailable");
+                    }
+                };
+                endpoint.setOperation(OpenAIOperations.decisions);
+                return endpoint;
+            }
+        });
+        adapter.setEndpointUri("broken:decisions");
+        assertThatThrownBy(() -> adapter.evaluate(evaluation("boolean", 
Map.of()), "text"))
+                
.isInstanceOf(OpenAIDecisionServiceException.class).hasRootCauseInstanceOf(IOException.class);
+        verify(client.decisions(), 
never()).create(any(DecisionCreateParams.class));
+    }
+
+    @Test
+    void stoppedContextCannotRestartTheProviderClient() throws Exception {
+        context.stop();
+        assertThatThrownBy(() -> adapter.evaluate(evaluation("boolean", 
Map.of()), "text"))
+                .isInstanceOf(OpenAIDecisionServiceException.class);
+        verify(client.decisions(), 
never()).create(any(DecisionCreateParams.class));
+    }
+
+    @Test
+    void directLanguageEvaluationRejectsMalformedImageBeforeInference() {
+        var language = (SemanticLanguage) context.resolveLanguage("semantic");
+        assertThatThrownBy(() -> language.evaluate(evaluation("boolean", 
Map.of()), "data:image/png;base64,invalid!"))
+                
.isInstanceOf(IllegalArgumentException.class).hasMessageContaining("base64");
+        verify(client.decisions(), 
never()).create(any(DecisionCreateParams.class));
+    }
+
+    @Test
+    void requiresAnExplicitModelBeforeInference() {
+        context.getEndpoint("openai:decisions", 
OpenAIEndpoint.class).getConfiguration().setModel(null);
+        assertThatThrownBy(() -> adapter.evaluate(evaluation("boolean", 
Map.of()), "text"))
+                
.isInstanceOf(IllegalArgumentException.class).hasMessageContaining("Configure a 
Decisions model");
+        verify(client.decisions(), 
never()).create(any(DecisionCreateParams.class));
+    }
+
+    @Test
+    void binaryBodySelectorsRequireTheExplicitBodyImagePath() {
+        var evaluation = new SemanticEvaluation("boolean", "expert", 
"${bodyAs(byte[])}", Map.of("instructions", "Assess"));
+        var exchange = new DefaultExchange(context);
+        exchange.getMessage().setBody(new byte[] { 1, 2, 3 });
+        exchange.getMessage().setHeader(Exchange.CONTENT_TYPE, "image/png");
+        assertThatThrownBy(() -> expression(evaluation).evaluate(exchange, 
Boolean.class))
+                
.hasRootCauseInstanceOf(IllegalArgumentException.class).hasMessageContaining("Unsupported
 state type");
+        verify(client.decisions(), 
never()).create(any(DecisionCreateParams.class));
+    }
+
+    private static OpenAIConfiguration configuration() {
+        var configuration = new OpenAIConfiguration();
+        configuration.setModel("gpt-6-luna");
+        return configuration;
+    }
+
+    private Expression expression(SemanticEvaluation evaluation) {
+        SemanticEvaluations.get(context).replace("test", Map.of("q", 
evaluation));
+        return context.resolveLanguage("semantic").createExpression("ref:q");
+    }
+
+    private static SemanticEvaluation evaluation(String operation, Map<String, 
Object> parameters) {
+        var values = new LinkedHashMap<String, Object>(parameters);
+        values.put("instructions", "Assess the evidence");
+        return new SemanticEvaluation(operation, "expert", null, values);
+    }
+
+    private static Decision.Answer predicate(String name, double probability) {
+        return 
Decision.Answer.ofPredicate(Decision.Answer.Predicate.builder().name(name).probability(probability).build());
+    }
+
+    private static Decision response(Decision.Answer... answers) {
+        return Decision.builder().model("gpt-6-luna").answers(List.of(answers))
+                
.usage(Decision.Usage.builder().inputTokens(1).outputTokens(0).totalTokens(1)
+                        .inputTokensDetails(
+                                
Decision.Usage.InputTokensDetails.builder().cachedTokens(0).cacheWriteTokens(0).build())
+                        
.outputTokensDetails(Decision.Usage.OutputTokensDetails.builder().reasoningTokens(0).build()).build())
+                .build();
+    }
+}
diff --git 
a/components/camel-ai/camel-semantic/src/main/docs/semantic-language.adoc 
b/components/camel-ai/camel-semantic/src/main/docs/semantic-language.adoc
index 1cb7caea48c1..de2b3d50dce9 100644
--- a/components/camel-ai/camel-semantic/src/main/docs/semantic-language.adoc
+++ b/components/camel-ai/camel-semantic/src/main/docs/semantic-language.adoc
@@ -172,7 +172,9 @@ A negative detection does not establish general safety or 
grant authorization.
 === Text and image input
 
 Experts declare supported modalities in their operation contract. `IMAGE` 
allows explicit image
-parts; it does not imply that every expert accepts images. TypeSafe AI accepts 
text and its structured state, and Wolf Defender accepts text. Unsupported 
input fails
+parts; it does not imply that every expert accepts images. The
+xref:others:semantic-openai.adoc[OpenAI Decisions expert] accepts text and 
images, TypeSafe AI
+accepts text and its structured state, and Wolf Defender accepts text. 
Unsupported input fails
 before inference, including before any expert in a mixed batch runs.
 
 For `$\{body}`, an image `Content-Type` such as `image/png` identifies a 
`byte[]`, `InputStream`,
diff --git 
a/components/camel-ai/camel-typesafe-ai/src/main/docs/typesafe-ai-component.adoc
 
b/components/camel-ai/camel-typesafe-ai/src/main/docs/typesafe-ai-component.adoc
index 87d2621f0118..ac5c4d881a60 100644
--- 
a/components/camel-ai/camel-typesafe-ai/src/main/docs/typesafe-ai-component.adoc
+++ 
b/components/camel-ai/camel-typesafe-ai/src/main/docs/typesafe-ai-component.adoc
@@ -313,4 +313,4 @@ The Semantic adapter accepts text and TypeSafe AI's 
supported structured applica
 It rejects explicit `$semanticInput` image/media envelopes, including nested 
media envelopes,
 and binary state before initializing transport. Selecting a body with an image 
`Content-Type`
 or a self-describing image data URL does not make this text/structured expert 
multimodal.
-Use an image-capable expert for images.
+Use an image-capable expert such as xref:others:semantic-openai.adoc[Semantic 
OpenAI] for images.
diff --git a/components/camel-ai/pom.xml b/components/camel-ai/pom.xml
index 7d14a9355155..bbb32c3af7ab 100644
--- a/components/camel-ai/pom.xml
+++ b/components/camel-ai/pom.xml
@@ -44,6 +44,7 @@
         <module>camel-docling</module>
         <module>camel-huggingface</module>
         <module>camel-semantic</module>
+        <module>camel-semantic-openai</module>
         <module>camel-typesafe-ai</module>
         <module>camel-wolf-defender</module>
         <module>camel-kserve</module>
diff --git a/docs/components/modules/others/examples/json/semantic-openai.json 
b/docs/components/modules/others/examples/json/semantic-openai.json
new file mode 120000
index 000000000000..628af3bf2776
--- /dev/null
+++ b/docs/components/modules/others/examples/json/semantic-openai.json
@@ -0,0 +1 @@
+../../../../../../components/camel-ai/camel-semantic-openai/src/generated/resources/semantic-openai.json
\ No newline at end of file
diff --git a/docs/components/modules/others/nav.adoc 
b/docs/components/modules/others/nav.adoc
index ffd67939a772..45606aec326d 100644
--- a/docs/components/modules/others/nav.adoc
+++ b/docs/components/modules/others/nav.adoc
@@ -87,6 +87,7 @@
 ** xref:salesforce-reports.adoc[Salesforce - Reports API]
 ** xref:salesforce-rest-api.adoc[Salesforce - REST API]
 ** xref:salesforce-streaming.adoc[Salesforce - Streaming and Pub/Sub]
+** xref:semantic-openai.adoc[Semantic OpenAI]
 ** xref:shiro.adoc[Shiro]
 ** xref:spring-cloud-config.adoc[Spring Cloud Config]
 ** xref:spring-main.adoc[Spring Main]
diff --git a/docs/components/modules/others/pages/semantic-openai.adoc 
b/docs/components/modules/others/pages/semantic-openai.adoc
new file mode 120000
index 000000000000..2aba60fadf6d
--- /dev/null
+++ b/docs/components/modules/others/pages/semantic-openai.adoc
@@ -0,0 +1 @@
+../../../../../components/camel-ai/camel-semantic-openai/src/main/docs/semantic-openai.adoc
\ No newline at end of file
diff --git a/parent/pom.xml b/parent/pom.xml
index 73ac4037d8c7..61e7efc31fd7 100644
--- a/parent/pom.xml
+++ b/parent/pom.xml
@@ -2703,6 +2703,11 @@
                 <artifactId>camel-semantic</artifactId>
                 <version>${project.version}</version>
             </dependency>
+            <dependency>
+                <groupId>org.apache.camel</groupId>
+                <artifactId>camel-semantic-openai</artifactId>
+                <version>${project.version}</version>
+            </dependency>
             <dependency>
                 <groupId>org.apache.camel</groupId>
                 <artifactId>camel-servicenow</artifactId>
diff --git 
a/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/MojoHelper.java
 
b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/MojoHelper.java
index abf900939b5f..1cf6a7833448 100644
--- 
a/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/MojoHelper.java
+++ 
b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/MojoHelper.java
@@ -42,7 +42,8 @@ public final class MojoHelper {
                         dir.resolve("camel-ai-resource"), 
dir.resolve("camel-ai-tool"),
                         dir.resolve("camel-chatscript"), 
dir.resolve("camel-djl"),
                         dir.resolve("camel-huggingface"),
-                        dir.resolve("camel-semantic"), 
dir.resolve("camel-typesafe-ai"), dir.resolve("camel-wolf-defender"),
+                        dir.resolve("camel-semantic"), 
dir.resolve("camel-semantic-openai"), dir.resolve("camel-typesafe-ai"),
+                        dir.resolve("camel-wolf-defender"),
                         dir.resolve("camel-langchain4j-agent"), 
dir.resolve("camel-langchain4j-chat"),
                         dir.resolve("camel-langchain4j-embeddings"), 
dir.resolve("camel-langchain4j-embeddingstore"),
                         dir.resolve("camel-langchain4j-ingest"),

Reply via email to