You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by "luigidemasi (via GitHub)" <gi...@apache.org> on 2026/09/23 22:05:50 UTC

[PR] CAMEL-24977: Add semantic evaluation and Choice selectors [camel]

luigidemasi opened a new pull request, #26813:
URL: https://github.com/apache/camel/pull/26813

   # Description
   
   Implements [CAMEL-24977](https://issues.apache.org/jira/browse/CAMEL-24977) with `camel-semantic`: provider-independent question/result types, an adapter SPI, and a language for named boolean, category and score evaluations. YAML routes can declare `semantic.question` definitions, select message state with Simple, and reference questions through the existing language expression/predicate contracts.
   
   The TypeSafe AI component advertises an adapter that reuses its configured transport. Adapter discovery and explicit bean/class selection validate ownership, lifecycle and capabilities without inference during setup. Boolean policy distinguishes uncertainty from operational failures; optional probabilities and confidence remain separate from the selected value.
   
   Core Choice now supports `.choice(expression).when("value")`, evaluating the selector once per entry. Literal matching, null/unmatched values, nested/repeated evaluations, error handling and precondition restrictions are documented and tested. XML, YAML, Java export, schemas, JMX and generated catalog artifacts support the new model. XML schemas permit a value-only branch; route startup validates whether each branch requires a predicate or a literal. A default `PropertyConfigurer.configureRaw` hook lets the semantic language retain adapter reference text for type checking and lifecycle ownership before Camel's normal reference resolution.
   
   Examples and deterministic tests cover Choice, Filter, Validate, metadata assignment, aggregation, destination mappings, loops and sorting. Documentation explains Camel Main/JBang properties, explicit embedded/Spring Boot configuration, and resource reload behavior.
   
   # Validation
   
   - Choice tests: 58 passed; property binding tests: 68 passed.
   - Semantic language, lifecycle, configuration and EIP tests: 20 passed.
   - TypeSafe AI module: 132 passed, including adapter tests against a local HTTP fixture.
   - Java DSL export/compilation: 130 passed; XML selector round trips: 2 passed; Choice JMX: 2 passed.
   - YAML DSL common/deserializer/route tests: 427 passed; YAML validator tests: 155 passed.
   - Catalog XML schema tests: 5 passed, including literal branches against both Spring XML and XML IO schemas.
   - Full 694-module reactor: `mvnd -B -ntp -T1 clean install -DskipTests` passed, including formatting and generated artifacts.
   
   # Target
   
   - [x] Targets `apache/camel:main`.
   
   # Tracking
   
   - [x] [CAMEL-24977](https://issues.apache.org/jira/browse/CAMEL-24977) tracks the change.
   
   # Apache Camel coding standards and style
   
   - [x] The commit has a meaningful subject and body.
   - [x] Ran the full root build with `mvnd clean install -DskipTests`; included relevant generated artifacts and formatting changes.
   
   # AI-assisted contributions
   
   - [x] The commit includes AI co-authorship attribution and the operator's sign-off and signature.
   
   Generated by Codex via /oss-fix-issue on behalf of [luigidemasi](https://github.com/luigidemasi).
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@camel.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] CAMEL-24977: Add provider-independent semantic evaluation [camel]

Posted by "davsclaus (via GitHub)" <gi...@apache.org>.
davsclaus commented on code in PR #26813:
URL: https://github.com/apache/camel/pull/26813#discussion_r4092123479


##########
dsl/camel-yaml-dsl/camel-yaml-dsl-maven-plugin/src/main/java/org/apache/camel/maven/dsl/yaml/GenerateYamlCompletionMojo.java:
##########
@@ -418,7 +426,8 @@ private void enrichNodeMetadata(ObjectNode node, String nodeName, String fqName,
         }
 
         // check for language model
-        LanguageModel langModel = catalog.languageModel(nodeName);
+        LanguageModel langModel = fqName.startsWith("org.apache.camel.model.language.")
+                ? catalog.languageModel(nodeName) : null;

Review Comment:
   🟠 **This is a fix for an unrelated bug, and it changes shipped metadata for the `bean` EIP.**
   
   Gating the language lookup on the model package stops the `bean` **EIP** node (`org.apache.camel.model.BeanDefinition`) from picking up the `bean` **language**'s catalog entry. I regenerated the schemas to confirm it is deterministic output, and it is the only non-semantic change across the three generated schema files:
   
   ```
   -      "title" : "Bean Method",  "description" : "Calls a Java bean method",  "label" : "language,core,java",
   +      "title" : "Bean",         "description" : "Invokes a method on a Java bean, ...",  "label" : "eip,endpoint",
   ```
   
   I think the new behaviour is right — completion was showing the language's description on the EIP node because the two share a name. But it is a separate bug fix to shared tooling riding along in an AI feature PR, unmentioned in the description and untested.
   
   Please either lift it into its own commit or PR, or describe it in the PR body and add a test pinning `bean` to the EIP metadata so the collision cannot quietly come back.



##########
components/camel-ai/camel-semantic/src/main/java/org/apache/camel/semantic/yaml/SemanticDefinitionDeserializer.java:
##########
@@ -0,0 +1,201 @@
+/*
+ * 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.yaml;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.dsl.yaml.common.YamlDeserializationContext;
+import org.apache.camel.dsl.yaml.common.YamlDeserializerResolver;
+import org.apache.camel.dsl.yaml.common.YamlDeserializerSupport;
+import org.apache.camel.dsl.yaml.common.exception.YamlDeserializationException;
+import org.apache.camel.semantic.SemanticQuestion;
+import org.apache.camel.semantic.SemanticQuestions;
+import org.apache.camel.spi.CamelContextCustomizer;
+import org.apache.camel.spi.annotations.YamlIn;
+import org.apache.camel.spi.annotations.YamlProperty;
+import org.apache.camel.spi.annotations.YamlType;
+import org.snakeyaml.engine.v2.api.ConstructNode;
+import org.snakeyaml.engine.v2.nodes.Node;
+import org.snakeyaml.engine.v2.nodes.NodeTuple;
+import org.snakeyaml.engine.v2.nodes.SequenceNode;
+
+/** Named semantic declarations are installed in a resource-wide pass before route references are resolved. */
+@YamlIn
+@YamlType(nodes = "semantic", properties = {
+        @YamlProperty(name = "question",
+                      type = "map:org.apache.camel.semantic.yaml.SemanticDefinitionDeserializer$QuestionSchema",
+                      required = true)
+})
+public class SemanticDefinitionDeserializer extends YamlDeserializerSupport implements ConstructNode, YamlDeserializerResolver {
+    private static final Set<String> FIELDS
+            = Set.of("type", "instructions", "state", "criteria", "threshold", "uncertainty", "uncertaintyPolicy");
+
+    @Override
+    public ConstructNode resolve(String id) {
+        return "semantic".equals(id) ? this : null;
+    }
+
+    @Override
+    public Object construct(Node node) {
+        read(node);
+        // Registration happens once for the entire resource, including declarations after routes.
+        return (CamelContextCustomizer) context -> {
+        };
+    }
+
+    @Override
+    public void preParse(YamlDeserializationContext dc, Node root) {
+        if (!(root instanceof SequenceNode sequence)) {
+            return;
+        }
+        Map<String, SemanticQuestion> definitions = new LinkedHashMap<>();
+        for (Node node : sequence.getValue()) {
+            for (NodeTuple tuple : asMappingNode(node).getValue()) {
+                if ("semantic".equals(asText(tuple.getKeyNode()))) {
+                    read(tuple.getValueNode()).forEach((name, question) -> {
+                        if (definitions.putIfAbsent(name, question) != null) {
+                            throw new IllegalArgumentException("Duplicate semantic question: " + name);
+                        }
+                    });
+                }
+            }
+        }
+        CamelContext context = dc.getCamelContext();
+        SemanticQuestions questions = definitions.isEmpty()
+                ? context.getCamelContextExtension().getContextPlugin(SemanticQuestions.class)
+                : SemanticQuestions.get(context);
+        if (questions != null) {
+            questions.replace(dc.getResource(), definitions);
+        }
+    }
+
+    private static Map<String, SemanticQuestion> read(Node node) {
+        Map<String, Node> semantic = fields(node);
+        if (!semantic.keySet().equals(Set.of("question"))) {
+            throw new IllegalArgumentException("Semantic declaration requires only question");
+        }
+        Map<String, SemanticQuestion> result = new LinkedHashMap<>();
+        fields(semantic.get("question")).forEach((name, definition) -> {
+            Map<String, Node> values = fields(definition);
+            if (!FIELDS.containsAll(values.keySet())) {
+                throw new IllegalArgumentException("Unknown property in semantic question: " + name);
+            }
+            String typeName = asText(values.get("type"));
+            if (typeName == null) {
+                throw new IllegalArgumentException("Semantic question type is required: " + name);
+            }
+            SemanticQuestion.Type type = SemanticQuestion.Type.valueOf(typeName.toUpperCase(Locale.ROOT));

Review Comment:
   Item 5 from the previous round. `Enum.valueOf` throws a bare `IllegalArgumentException` (`No enum constant SemanticQuestion.Type.BOOL`) with no YAML location and no question name — while the numeric fields a few lines below get a located `YamlDeserializationException`. `YamlDeserializerSupport.asEnum` gives you the located error and is case-insensitive:
   
   ```suggestion
               SemanticQuestion.Type type = asEnum(values.get("type"), SemanticQuestion.Type.class);
   ```
   
   The `typeName == null` check above stays useful, since `asEnum` returns `null` for a null node rather than complaining.
   
   *Correction to my round-3 review:* I wrote that this path "can NPE". That was wrong — `asText` returns `null` only for a null node and throws a located `InvalidNodeTypeException` for a non-scalar. It is a bare `IllegalArgumentException`, not an NPE. The remedy is unchanged.



##########
components/camel-ai/camel-semantic/src/main/java/org/apache/camel/semantic/yaml/SemanticDefinitionDeserializer.java:
##########
@@ -0,0 +1,201 @@
+/*
+ * 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.yaml;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.dsl.yaml.common.YamlDeserializationContext;
+import org.apache.camel.dsl.yaml.common.YamlDeserializerResolver;
+import org.apache.camel.dsl.yaml.common.YamlDeserializerSupport;
+import org.apache.camel.dsl.yaml.common.exception.YamlDeserializationException;
+import org.apache.camel.semantic.SemanticQuestion;
+import org.apache.camel.semantic.SemanticQuestions;
+import org.apache.camel.spi.CamelContextCustomizer;
+import org.apache.camel.spi.annotations.YamlIn;
+import org.apache.camel.spi.annotations.YamlProperty;
+import org.apache.camel.spi.annotations.YamlType;
+import org.snakeyaml.engine.v2.api.ConstructNode;
+import org.snakeyaml.engine.v2.nodes.Node;
+import org.snakeyaml.engine.v2.nodes.NodeTuple;
+import org.snakeyaml.engine.v2.nodes.SequenceNode;
+
+/** Named semantic declarations are installed in a resource-wide pass before route references are resolved. */
+@YamlIn
+@YamlType(nodes = "semantic", properties = {
+        @YamlProperty(name = "question",
+                      type = "map:org.apache.camel.semantic.yaml.SemanticDefinitionDeserializer$QuestionSchema",
+                      required = true)
+})
+public class SemanticDefinitionDeserializer extends YamlDeserializerSupport implements ConstructNode, YamlDeserializerResolver {
+    private static final Set<String> FIELDS
+            = Set.of("type", "instructions", "state", "criteria", "threshold", "uncertainty", "uncertaintyPolicy");
+
+    @Override
+    public ConstructNode resolve(String id) {
+        return "semantic".equals(id) ? this : null;
+    }
+
+    @Override
+    public Object construct(Node node) {
+        read(node);
+        // Registration happens once for the entire resource, including declarations after routes.
+        return (CamelContextCustomizer) context -> {
+        };
+    }
+
+    @Override
+    public void preParse(YamlDeserializationContext dc, Node root) {
+        if (!(root instanceof SequenceNode sequence)) {
+            return;
+        }
+        Map<String, SemanticQuestion> definitions = new LinkedHashMap<>();
+        for (Node node : sequence.getValue()) {
+            for (NodeTuple tuple : asMappingNode(node).getValue()) {
+                if ("semantic".equals(asText(tuple.getKeyNode()))) {
+                    read(tuple.getValueNode()).forEach((name, question) -> {
+                        if (definitions.putIfAbsent(name, question) != null) {
+                            throw new IllegalArgumentException("Duplicate semantic question: " + name);
+                        }
+                    });
+                }
+            }
+        }
+        CamelContext context = dc.getCamelContext();
+        SemanticQuestions questions = definitions.isEmpty()
+                ? context.getCamelContextExtension().getContextPlugin(SemanticQuestions.class)
+                : SemanticQuestions.get(context);
+        if (questions != null) {
+            questions.replace(dc.getResource(), definitions);
+        }
+    }
+
+    private static Map<String, SemanticQuestion> read(Node node) {
+        Map<String, Node> semantic = fields(node);
+        if (!semantic.keySet().equals(Set.of("question"))) {
+            throw new IllegalArgumentException("Semantic declaration requires only question");
+        }
+        Map<String, SemanticQuestion> result = new LinkedHashMap<>();
+        fields(semantic.get("question")).forEach((name, definition) -> {
+            Map<String, Node> values = fields(definition);
+            if (!FIELDS.containsAll(values.keySet())) {
+                throw new IllegalArgumentException("Unknown property in semantic question: " + name);
+            }
+            String typeName = asText(values.get("type"));
+            if (typeName == null) {
+                throw new IllegalArgumentException("Semantic question type is required: " + name);
+            }
+            SemanticQuestion.Type type = SemanticQuestion.Type.valueOf(typeName.toUpperCase(Locale.ROOT));
+            Map<String, String> criteria = new LinkedHashMap<>();
+            List<String> levels = List.of();
+            if (values.containsKey("criteria")) {
+                if (type == SemanticQuestion.Type.SCORE) {
+                    levels = asSequenceNode(values.get("criteria")).getValue().stream().map(YamlDeserializerSupport::asText)
+                            .toList();
+                } else {
+                    fields(values.get("criteria")).forEach((key, value) -> criteria.put(key, asText(value)));
+                }
+            }
+            if (type != SemanticQuestion.Type.BOOLEAN && (values.containsKey("threshold") || values.containsKey("uncertainty")
+                    || values.containsKey("uncertaintyPolicy"))) {
+                throw new IllegalArgumentException("Threshold and uncertainty policy require a boolean question: " + name);
+            }
+            SemanticQuestion.UncertaintyPolicy policy = values.containsKey("uncertaintyPolicy")
+                    ? SemanticQuestion.UncertaintyPolicy
+                            .valueOf(asText(values.get("uncertaintyPolicy")).replace('-', '_').toUpperCase(Locale.ROOT))
+                    : SemanticQuestion.UncertaintyPolicy.FAIL;

Review Comment:
   Same as the `type` enum above. `asEnum` also handles the dash form via `StringHelper.asEnumConstantValue`, so `non-match` still resolves to `NON_MATCH` and the manual `replace('-', '_').toUpperCase(...)` goes away:
   
   ```suggestion
               SemanticQuestion.UncertaintyPolicy policy = values.containsKey("uncertaintyPolicy")
                       ? asEnum(values.get("uncertaintyPolicy"), SemanticQuestion.UncertaintyPolicy.class)
                       : SemanticQuestion.UncertaintyPolicy.FAIL;
   ```
   
   With both `valueOf` calls gone the `java.util.Locale` import on line 21 becomes unused — OpenRewrite and impsort will flag it, so drop it in the same commit.



##########
components/camel-ai/camel-semantic/src/main/java/org/apache/camel/semantic/yaml/SemanticDefinitionDeserializer.java:
##########
@@ -0,0 +1,201 @@
+/*
+ * 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.yaml;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.dsl.yaml.common.YamlDeserializationContext;
+import org.apache.camel.dsl.yaml.common.YamlDeserializerResolver;
+import org.apache.camel.dsl.yaml.common.YamlDeserializerSupport;
+import org.apache.camel.dsl.yaml.common.exception.YamlDeserializationException;
+import org.apache.camel.semantic.SemanticQuestion;
+import org.apache.camel.semantic.SemanticQuestions;
+import org.apache.camel.spi.CamelContextCustomizer;
+import org.apache.camel.spi.annotations.YamlIn;
+import org.apache.camel.spi.annotations.YamlProperty;
+import org.apache.camel.spi.annotations.YamlType;
+import org.snakeyaml.engine.v2.api.ConstructNode;
+import org.snakeyaml.engine.v2.nodes.Node;
+import org.snakeyaml.engine.v2.nodes.NodeTuple;
+import org.snakeyaml.engine.v2.nodes.SequenceNode;
+
+/** Named semantic declarations are installed in a resource-wide pass before route references are resolved. */
+@YamlIn
+@YamlType(nodes = "semantic", properties = {
+        @YamlProperty(name = "question",
+                      type = "map:org.apache.camel.semantic.yaml.SemanticDefinitionDeserializer$QuestionSchema",
+                      required = true)
+})
+public class SemanticDefinitionDeserializer extends YamlDeserializerSupport implements ConstructNode, YamlDeserializerResolver {
+    private static final Set<String> FIELDS
+            = Set.of("type", "instructions", "state", "criteria", "threshold", "uncertainty", "uncertaintyPolicy");
+
+    @Override
+    public ConstructNode resolve(String id) {
+        return "semantic".equals(id) ? this : null;
+    }
+
+    @Override
+    public Object construct(Node node) {
+        read(node);
+        // Registration happens once for the entire resource, including declarations after routes.
+        return (CamelContextCustomizer) context -> {
+        };
+    }
+
+    @Override
+    public void preParse(YamlDeserializationContext dc, Node root) {
+        if (!(root instanceof SequenceNode sequence)) {
+            return;
+        }
+        Map<String, SemanticQuestion> definitions = new LinkedHashMap<>();
+        for (Node node : sequence.getValue()) {
+            for (NodeTuple tuple : asMappingNode(node).getValue()) {

Review Comment:
   🔵 `preParse` runs for **every** YAML resource now, including those with no `semantic:` node, so this walks every top-level node of every route file. `asMappingNode` throws `InvalidNodeTypeException` for anything that is not a mapping, which means a malformed resource fails inside the *semantic* resolver rather than in the normal deserialization path where the message makes sense to the user.
   
   An `instanceof MappingNode mapping` guard with a `continue` would leave the reporting where it belongs. The 420 `camel-yaml-dsl` tests pass as-is, so this is about the error path rather than a live break.



##########
components/camel-ai/camel-semantic/src/main/java/org/apache/camel/language/semantic/SemanticLanguage.java:
##########
@@ -0,0 +1,309 @@
+/*
+ * 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.language.semantic;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.CamelContextAware;
+import org.apache.camel.Exchange;
+import org.apache.camel.Expression;
+import org.apache.camel.Predicate;
+import org.apache.camel.RuntimeCamelException;
+import org.apache.camel.semantic.SemanticAdapter;
+import org.apache.camel.semantic.SemanticQuestion;
+import org.apache.camel.semantic.SemanticQuestions;
+import org.apache.camel.semantic.SemanticResult;
+import org.apache.camel.spi.Metadata;
+import org.apache.camel.spi.annotations.Language;
+import org.apache.camel.support.ExpressionAdapter;
+import org.apache.camel.support.LanguageSupport;
+import org.apache.camel.support.service.ServiceHelper;
+import org.apache.camel.support.service.ServiceSupport;
+
+/** Evaluates a named, provider-independent question against selected message state. */
+@Language(value = "semantic", modelName = "language")
+@Metadata(title = "Semantic", description = "Evaluate named semantic questions through a provider adapter",
+          label = "language,ai", firstVersion = "4.23.0")
+public class SemanticLanguage extends LanguageSupport {
+    public static final String RESULT = "CamelSemanticResult";
+    public static final String ADAPTER_NAME = "camelSemanticAdapter";
+    public static final String ADAPTER_RESOURCE = "META-INF/services/org.apache.camel.semantic.SemanticAdapter";
+
+    private String adapter;
+    private String defaultState = "${body}";
+    private SemanticAdapter selectedAdapter;
+
+    public String getAdapter() {
+        return adapter;
+    }
+
+    /**
+     * Adapter registry bean name or fully qualified implementation class name, without a reference prefix. Registry
+     * lookup takes precedence over class resolution. Absent selects the sole advertised adapter.
+     */
+    public void setAdapter(String adapter) {
+        this.adapter = adapter;
+    }
+
+    public String getDefaultState() {
+        return defaultState;
+    }
+
+    /** Default Simple state selector for questions without their own selector. Defaults to the body. */
+    public void setDefaultState(String defaultState) {
+        this.defaultState = defaultState;
+    }
+
+    @Override
+    public Expression createExpression(String expression) {
+        return createEvaluation(expression, false);
+    }
+
+    @Override
+    public Predicate createPredicate(String expression) {
+        return createEvaluation(expression, true);
+    }
+
+    public boolean validateExpression(String expression) {
+        if (expression == null || !expression.startsWith("ref:") || expression.substring(4).isBlank()) {
+            throw new IllegalArgumentException("Semantic expression must reference a named question using ref:name");
+        }
+        return true;
+    }
+
+    public boolean validatePredicate(String expression) {
+        return validateExpression(expression);
+    }
+
+    private Evaluation createEvaluation(String expression, boolean predicate) {
+        validateExpression(expression);
+        Evaluation evaluation = new Evaluation(expression.substring(4), predicate);
+        if (getCamelContext() != null) {
+            evaluation.init(getCamelContext());
+        }
+        return evaluation;
+    }
+
+    private synchronized SemanticAdapter adapter() {
+        if (selectedAdapter != null) {
+            return selectedAdapter;
+        }
+        CamelContext context = getCamelContext();
+        String configured = adapter == null ? null : context.resolvePropertyPlaceholders(adapter);
+        if (configured != null) {
+            if (configured.isBlank() || configured.startsWith("#")) {
+                throw new IllegalArgumentException("Semantic adapter must be a bean name or class name without a # prefix");
+            }
+            Object bean = context.getRegistry().lookupByName(configured);
+            if (bean != null) {
+                if (!(bean instanceof SemanticAdapter found)) {
+                    throw new IllegalArgumentException(
+                            "Semantic adapter bean does not implement SemanticAdapter: " + configured);
+                }
+                selectedAdapter = found;
+                return found;
+            }
+        }
+        ManagedAdapter owned = null;
+        try {
+            String className = configured;
+            if (className == null) {
+                Set<String> candidates = new TreeSet<>();
+                Enumeration<URL> resources = context.getClassResolver().loadAllResourcesAsURL(ADAPTER_RESOURCE);

Review Comment:
   Item 7, confirmed still present. This reimplements service-file discovery — reading the URLs, stripping `#` comments, trimming, deduplicating — where `FactoryFinder` already does it, handles class loading, and is what the rest of Camel uses for exactly this lookup.
   
   Not a defect: the code is correct as written. It is hand-maintained parsing of a format Camel already owns a reader for.



##########
components/camel-ai/camel-semantic/src/main/java/org/apache/camel/semantic/SemanticQuestions.java:
##########
@@ -0,0 +1,88 @@
+/*
+ * 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;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.spi.Resource;
+
+/** Context-local named questions, replaced atomically per source when a route resource is reloaded. */
+public final class SemanticQuestions {
+    private final Map<String, Map<String, SemanticQuestion>> sources = new HashMap<>();
+    private final Map<String, Resource> resources = new HashMap<>();
+    private volatile Map<String, SemanticQuestion> questions = Map.of();
+
+    public static SemanticQuestions get(CamelContext context) {
+        synchronized (context) {

Review Comment:
   Item 6, the other half. `synchronized (context)` takes the monitor of the `CamelContext`, an object reachable by every piece of framework and user code in the JVM. A private lock object, or plugin-registration support on the context extension if one exists, keeps the critical section without exporting the monitor.



##########
components/camel-ai/camel-semantic/src/main/java/org/apache/camel/semantic/yaml/SemanticDefinitionDeserializer.java:
##########
@@ -0,0 +1,201 @@
+/*
+ * 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.yaml;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.dsl.yaml.common.YamlDeserializationContext;
+import org.apache.camel.dsl.yaml.common.YamlDeserializerResolver;
+import org.apache.camel.dsl.yaml.common.YamlDeserializerSupport;
+import org.apache.camel.dsl.yaml.common.exception.YamlDeserializationException;
+import org.apache.camel.semantic.SemanticQuestion;
+import org.apache.camel.semantic.SemanticQuestions;
+import org.apache.camel.spi.CamelContextCustomizer;
+import org.apache.camel.spi.annotations.YamlIn;
+import org.apache.camel.spi.annotations.YamlProperty;
+import org.apache.camel.spi.annotations.YamlType;
+import org.snakeyaml.engine.v2.api.ConstructNode;
+import org.snakeyaml.engine.v2.nodes.Node;
+import org.snakeyaml.engine.v2.nodes.NodeTuple;
+import org.snakeyaml.engine.v2.nodes.SequenceNode;
+
+/** Named semantic declarations are installed in a resource-wide pass before route references are resolved. */
+@YamlIn
+@YamlType(nodes = "semantic", properties = {
+        @YamlProperty(name = "question",
+                      type = "map:org.apache.camel.semantic.yaml.SemanticDefinitionDeserializer$QuestionSchema",
+                      required = true)
+})
+public class SemanticDefinitionDeserializer extends YamlDeserializerSupport implements ConstructNode, YamlDeserializerResolver {
+    private static final Set<String> FIELDS
+            = Set.of("type", "instructions", "state", "criteria", "threshold", "uncertainty", "uncertaintyPolicy");
+
+    @Override
+    public ConstructNode resolve(String id) {
+        return "semantic".equals(id) ? this : null;
+    }
+
+    @Override
+    public Object construct(Node node) {
+        read(node);
+        // Registration happens once for the entire resource, including declarations after routes.
+        return (CamelContextCustomizer) context -> {
+        };
+    }
+
+    @Override
+    public void preParse(YamlDeserializationContext dc, Node root) {
+        if (!(root instanceof SequenceNode sequence)) {
+            return;
+        }
+        Map<String, SemanticQuestion> definitions = new LinkedHashMap<>();
+        for (Node node : sequence.getValue()) {
+            for (NodeTuple tuple : asMappingNode(node).getValue()) {
+                if ("semantic".equals(asText(tuple.getKeyNode()))) {
+                    read(tuple.getValueNode()).forEach((name, question) -> {
+                        if (definitions.putIfAbsent(name, question) != null) {
+                            throw new IllegalArgumentException("Duplicate semantic question: " + name);
+                        }
+                    });
+                }
+            }
+        }
+        CamelContext context = dc.getCamelContext();
+        SemanticQuestions questions = definitions.isEmpty()
+                ? context.getCamelContextExtension().getContextPlugin(SemanticQuestions.class)
+                : SemanticQuestions.get(context);
+        if (questions != null) {
+            questions.replace(dc.getResource(), definitions);
+        }
+    }
+
+    private static Map<String, SemanticQuestion> read(Node node) {
+        Map<String, Node> semantic = fields(node);
+        if (!semantic.keySet().equals(Set.of("question"))) {
+            throw new IllegalArgumentException("Semantic declaration requires only question");
+        }
+        Map<String, SemanticQuestion> result = new LinkedHashMap<>();
+        fields(semantic.get("question")).forEach((name, definition) -> {
+            Map<String, Node> values = fields(definition);
+            if (!FIELDS.containsAll(values.keySet())) {
+                throw new IllegalArgumentException("Unknown property in semantic question: " + name);
+            }
+            String typeName = asText(values.get("type"));
+            if (typeName == null) {
+                throw new IllegalArgumentException("Semantic question type is required: " + name);
+            }
+            SemanticQuestion.Type type = SemanticQuestion.Type.valueOf(typeName.toUpperCase(Locale.ROOT));
+            Map<String, String> criteria = new LinkedHashMap<>();
+            List<String> levels = List.of();
+            if (values.containsKey("criteria")) {
+                if (type == SemanticQuestion.Type.SCORE) {
+                    levels = asSequenceNode(values.get("criteria")).getValue().stream().map(YamlDeserializerSupport::asText)
+                            .toList();
+                } else {
+                    fields(values.get("criteria")).forEach((key, value) -> criteria.put(key, asText(value)));
+                }
+            }
+            if (type != SemanticQuestion.Type.BOOLEAN && (values.containsKey("threshold") || values.containsKey("uncertainty")
+                    || values.containsKey("uncertaintyPolicy"))) {
+                throw new IllegalArgumentException("Threshold and uncertainty policy require a boolean question: " + name);
+            }
+            SemanticQuestion.UncertaintyPolicy policy = values.containsKey("uncertaintyPolicy")
+                    ? SemanticQuestion.UncertaintyPolicy
+                            .valueOf(asText(values.get("uncertaintyPolicy")).replace('-', '_').toUpperCase(Locale.ROOT))
+                    : SemanticQuestion.UncertaintyPolicy.FAIL;
+            result.put(name, new SemanticQuestion(

Review Comment:
   The remainder of item 5: `SemanticQuestion`'s constructor validates blank instructions, criteria shape, score levels, the threshold/uncertainty band and the boolean criteria keys — every one with a bare `IllegalArgumentException` carrying neither the YAML location nor the question name. A file missing `instructions` reports `Question instructions must not be blank` with nothing pointing at which question, or which line.
   
   Wrapping just this construction closes the whole set at once:
   
   ```java
   try {
       result.put(name, new SemanticQuestion(...));
   } catch (IllegalArgumentException | NullPointerException e) {
       throw new YamlDeserializationException(definition, "Invalid semantic question '" + name + "': " + e.getMessage(), e);
   }
   ```



##########
components/camel-ai/camel-semantic/src/main/java/org/apache/camel/language/semantic/SemanticLanguage.java:
##########
@@ -0,0 +1,309 @@
+/*
+ * 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.language.semantic;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.CamelContextAware;
+import org.apache.camel.Exchange;
+import org.apache.camel.Expression;
+import org.apache.camel.Predicate;
+import org.apache.camel.RuntimeCamelException;
+import org.apache.camel.semantic.SemanticAdapter;
+import org.apache.camel.semantic.SemanticQuestion;
+import org.apache.camel.semantic.SemanticQuestions;
+import org.apache.camel.semantic.SemanticResult;
+import org.apache.camel.spi.Metadata;
+import org.apache.camel.spi.annotations.Language;
+import org.apache.camel.support.ExpressionAdapter;
+import org.apache.camel.support.LanguageSupport;
+import org.apache.camel.support.service.ServiceHelper;
+import org.apache.camel.support.service.ServiceSupport;
+
+/** Evaluates a named, provider-independent question against selected message state. */
+@Language(value = "semantic", modelName = "language")
+@Metadata(title = "Semantic", description = "Evaluate named semantic questions through a provider adapter",
+          label = "language,ai", firstVersion = "4.23.0")
+public class SemanticLanguage extends LanguageSupport {
+    public static final String RESULT = "CamelSemanticResult";
+    public static final String ADAPTER_NAME = "camelSemanticAdapter";
+    public static final String ADAPTER_RESOURCE = "META-INF/services/org.apache.camel.semantic.SemanticAdapter";
+
+    private String adapter;
+    private String defaultState = "${body}";
+    private SemanticAdapter selectedAdapter;
+
+    public String getAdapter() {
+        return adapter;
+    }
+
+    /**
+     * Adapter registry bean name or fully qualified implementation class name, without a reference prefix. Registry
+     * lookup takes precedence over class resolution. Absent selects the sole advertised adapter.
+     */
+    public void setAdapter(String adapter) {
+        this.adapter = adapter;
+    }
+
+    public String getDefaultState() {
+        return defaultState;
+    }
+
+    /** Default Simple state selector for questions without their own selector. Defaults to the body. */
+    public void setDefaultState(String defaultState) {
+        this.defaultState = defaultState;
+    }
+
+    @Override
+    public Expression createExpression(String expression) {
+        return createEvaluation(expression, false);
+    }
+
+    @Override
+    public Predicate createPredicate(String expression) {
+        return createEvaluation(expression, true);
+    }
+
+    public boolean validateExpression(String expression) {
+        if (expression == null || !expression.startsWith("ref:") || expression.substring(4).isBlank()) {
+            throw new IllegalArgumentException("Semantic expression must reference a named question using ref:name");
+        }
+        return true;
+    }
+
+    public boolean validatePredicate(String expression) {
+        return validateExpression(expression);
+    }
+
+    private Evaluation createEvaluation(String expression, boolean predicate) {
+        validateExpression(expression);
+        Evaluation evaluation = new Evaluation(expression.substring(4), predicate);
+        if (getCamelContext() != null) {
+            evaluation.init(getCamelContext());
+        }
+        return evaluation;
+    }
+
+    private synchronized SemanticAdapter adapter() {
+        if (selectedAdapter != null) {
+            return selectedAdapter;
+        }
+        CamelContext context = getCamelContext();
+        String configured = adapter == null ? null : context.resolvePropertyPlaceholders(adapter);
+        if (configured != null) {
+            if (configured.isBlank() || configured.startsWith("#")) {
+                throw new IllegalArgumentException("Semantic adapter must be a bean name or class name without a # prefix");
+            }
+            Object bean = context.getRegistry().lookupByName(configured);
+            if (bean != null) {
+                if (!(bean instanceof SemanticAdapter found)) {
+                    throw new IllegalArgumentException(
+                            "Semantic adapter bean does not implement SemanticAdapter: " + configured);
+                }
+                selectedAdapter = found;
+                return found;
+            }
+        }
+        ManagedAdapter owned = null;
+        try {
+            String className = configured;
+            if (className == null) {
+                Set<String> candidates = new TreeSet<>();
+                Enumeration<URL> resources = context.getClassResolver().loadAllResourcesAsURL(ADAPTER_RESOURCE);
+                while (resources.hasMoreElements()) {
+                    try (BufferedReader reader = new BufferedReader(
+                            new InputStreamReader(
+                                    resources.nextElement().openStream(), StandardCharsets.UTF_8))) {
+                        reader.lines().map(line -> line.split("#", 2)[0].trim()).filter(line -> !line.isEmpty())
+                                .forEach(candidates::add);
+                    }
+                }
+                if (candidates.size() != 1) {
+                    throw new IllegalArgumentException(
+                            "Semantic language requires exactly one advertised adapter; found "
+                                                       + candidates + ". Configure camel.language.semantic.adapter explicitly");
+                }
+                className = candidates.iterator().next();
+            }
+            Class<?> resolved = context.getClassResolver().resolveClass(className);
+            if (resolved == null) {
+                throw new IllegalArgumentException("No semantic adapter bean or class found: " + className);
+            }
+            Class<? extends SemanticAdapter> type = resolved.asSubclass(SemanticAdapter.class);
+            synchronized (context.getRegistry()) {

Review Comment:
   Item 6, confirmed still present at head. Synchronizing on the registry means any other code that happens to lock that object participates in this class's lock ordering, and neither side can see the other. A private lock owned by this class gives the same mutual exclusion over the check-then-bind without publishing the monitor.
   
   Same point applies to `SemanticQuestions:32`, which locks the `CamelContext` itself.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@camel.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] CAMEL-24977: Add semantic evaluation and Choice selectors [camel]

Posted by "github-actions[bot] (via GitHub)" <gi...@apache.org>.
github-actions[bot] commented on PR #26813:
URL: https://github.com/apache/camel/pull/26813#issuecomment-5804121854

   <!-- ci-tested-modules -->
   :test_tube: **CI tested the following changed modules:**
   
   - `bom/camel-bom`
   - `catalog/camel-allcomponents`
   - `catalog/camel-catalog`
   - `components/camel-ai`
   - `components/camel-ai/camel-semantic`
   - `components/camel-ai/camel-typesafe-ai`
   - `core/camel-api`
   - `core/camel-core-engine`
   - `core/camel-core-model`
   - `core/camel-core-processor`
   - `core/camel-core-reifier`
   - `core/camel-core`
   - `core/camel-java-io`
   - `core/camel-main`
   - `core/camel-management`
   - `core/camel-support`
   - `core/camel-util`
   - `core/camel-xml-io`
   - `core/camel-xml-jaxb`
   - `core/camel-yaml-io`
   - `docs`
   - `dsl/camel-jbang/camel-jbang-core`
   - `dsl/camel-xml-io-dsl`
   - `dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers`
   - `dsl/camel-yaml-dsl/camel-yaml-dsl-maven-plugin`
   - `dsl/camel-yaml-dsl/camel-yaml-dsl-validator`
   - `dsl/camel-yaml-dsl/camel-yaml-dsl`
   - `parent`
   - `tooling/maven/camel-package-maven-plugin`
   
   > :information_source: Dependent modules were not tested because the total number of affected modules exceeded the threshold (50). Use the `test-dependents` label to force testing all dependents.
   
   ---
   
   <details><summary>:microscope: Scalpel shadow comparison — Scalpel: 585 of 694 tested, 0 compile-only — current: 70 all tested</summary>
   
   [Maveniverse Scalpel](https://github.com/maveniverse/scalpel) detected **585 affected modules** (current approach: 70).
   
   **Skip-tests mode would test 585 modules** (34 direct + 35 downstream), **skip tests for 0** (generated code, meta-modules)
   
   <details><summary>:warning: Modules only in Scalpel (585)</summary>
   
   - `archetypes`
   - `camel-a2a`
   - `camel-activemq`
   - `camel-activemq6`
   - `camel-ai-observability`
   - `camel-ai-observability-api`
   - `camel-ai-parent`
   - `camel-ai-resource`
   - `camel-ai-tool`
   - `camel-alibaba-common`
   - `camel-alibaba-eventbridge`
   - `camel-alibaba-fc`
   - `camel-alibaba-kms`
   - `camel-alibaba-mns`
   - `camel-alibaba-oss`
   - `camel-alibaba-ots`
   - `camel-alibaba-parent`
   - `camel-alibaba-sls`
   - `camel-alibaba-sms`
   - `camel-amqp`
   - `camel-api`
   - `camel-api-component-maven-plugin`
   - `camel-arangodb`
   - `camel-archetype-api-component`
   - `camel-archetype-component`
   - `camel-archetype-dataformat`
   - `camel-archetype-java`
   - `camel-archetype-main`
   - `camel-as2`
   - `camel-as2-api`
   - `camel-as2-parent`
   - `camel-asn1`
   - `camel-asterisk`
   - `camel-atmosphere-websocket`
   - `camel-atom`
   - `camel-attachments`
   - `camel-avro`
   - `camel-avro-rpc`
   - `camel-avro-rpc-jetty`
   - `camel-avro-rpc-parent`
   - `camel-avro-rpc-spi`
   - `camel-aws-bedrock`
   - `camel-aws-cloudtrail`
   - `camel-aws-common`
   - `camel-aws-config`
   - `camel-aws-parameter-store`
   - `camel-aws-parent`
   - `camel-aws-secrets-manager`
   - `camel-aws-security-hub`
   - `camel-aws2-athena`
   - `camel-aws2-comprehend`
   - `camel-aws2-cw`
   - `camel-aws2-ddb`
   - `camel-aws2-ec2`
   - `camel-aws2-ecs`
   - `camel-aws2-eks`
   - `camel-aws2-eventbridge`
   - `camel-aws2-iam`
   - `camel-aws2-kinesis`
   - `camel-aws2-kms`
   - `camel-aws2-lambda`
   - `camel-aws2-mq`
   - `camel-aws2-msk`
   - `camel-aws2-polly`
   - `camel-aws2-redshift`
   - `camel-aws2-rekognition`
   - `camel-aws2-s3`
   - `camel-aws2-s3-vectors`
   - `camel-aws2-ses`
   - `camel-aws2-sns`
   - `camel-aws2-sqs`
   - `camel-aws2-step-functions`
   - `camel-aws2-sts`
   - `camel-aws2-textract`
   - `camel-aws2-timestream`
   - `camel-aws2-transcribe`
   - `camel-aws2-translate`
   - `camel-azure-common`
   - `camel-azure-cosmosdb`
   - `camel-azure-eventgrid`
   - `camel-azure-eventhubs`
   - `camel-azure-files`
   - `camel-azure-functions`
   - `camel-azure-key-vault`
   - `camel-azure-parent`
   - `camel-azure-schema-registry`
   - `camel-azure-servicebus`
   - `camel-azure-storage-blob`
   - `camel-azure-storage-datalake`
   - `camel-azure-storage-queue`
   - `camel-barcode`
   - `camel-base`
   - `camel-base-engine`
   - `camel-base64`
   - `camel-bean`
   - `camel-bean-validator`
   - `camel-beanio`
   - `camel-bindy`
   - `camel-bom`
   - `camel-bonita`
   - `camel-box`
   - `camel-box-api`
   - `camel-box-parent`
   - `camel-braintree`
   - `camel-browse`
   - `camel-caffeine`
   - `camel-camunda`
   - `camel-cassandraql`
   - `camel-catalog-common`
   - `camel-cbor`
   - `camel-chatscript`
   - `camel-chunk`
   - `camel-cli-connector`
   - `camel-cli-debug`
   - `camel-clickhouse`
   - `camel-clickup`
   - `camel-cloudevents`
   - `camel-cluster`
   - `camel-cm-sms`
   - `camel-coap`
   - `camel-cometd`
   - `camel-component-maven-plugin`
   - `camel-console`
   - `camel-consul`
   - `camel-controlbus`
   - `camel-core`
   - `camel-core-all`
   - `camel-core-catalog`
   - `camel-core-engine`
   - `camel-core-languages`
   - `camel-core-model`
   - `camel-core-processor`
   - `camel-core-reifier`
   - `camel-core-xml`
   - `camel-couchbase`
   - `camel-couchdb`
   - `camel-cron`
   - `camel-crypto`
   - `camel-crypto-pgp`
   - `camel-csv`
   - `camel-cxf-common`
   - `camel-cxf-parent`
   - `camel-cxf-rest`
   - `camel-cxf-soap`
   - `camel-cxf-spring-common`
   - `camel-cxf-spring-rest`
   - `camel-cxf-spring-soap`
   - `camel-cxf-spring-transport`
   - `camel-cxf-transport`
   - `camel-cyberark-vault`
   - `camel-dapr`
   - `camel-dataformat`
   - `camel-dataset`
   - `camel-datasonnet`
   - `camel-dataweave`
   - `camel-debezium-common`
   - `camel-debezium-common-parent`
   - `camel-debezium-db2`
   - `camel-debezium-maven-plugin`
   - `camel-debezium-mongodb`
   - `camel-debezium-mysql`
   - `camel-debezium-oracle`
   - `camel-debezium-parent`
   - `camel-debezium-postgres`
   - `camel-debezium-sqlserver`
   - `camel-debug`
   - `camel-dependencies`
   - `camel-dfdl`
   - `camel-dhis2`
   - `camel-dhis2-api`
   - `camel-dhis2-parent`
   - `camel-diagram`
   - `camel-direct`
   - `camel-disruptor`
   - `camel-djl`
   - `camel-dns`
   - `camel-docker`
   - `camel-docling`
   - `camel-drill`
   - `camel-dropbox`
   - `camel-dsl-modeline`
   - `camel-dsl-support`
   - `camel-duckdb`
   - `camel-dynamic-router`
   - `camel-ehcache`
   - `camel-eip-documentation-enricher-maven-plugin`
   - `camel-elasticsearch`
   - `camel-elasticsearch-rest-client`
   - `camel-etc`
   - `camel-event`
   - `camel-exec`
   - `camel-fastjson`
   - `camel-fhir`
   - `camel-fhir-api`
   - `camel-fhir-parent`
   - `camel-file`
   - `camel-file-watch`
   - `camel-flatpack`
   - `camel-flink`
   - `camel-flowable`
   - `camel-fop`
   - `camel-fory`
   - `camel-freemarker`
   - `camel-ftp`
   - `camel-ftp-common`
   - `camel-geocoder`
   - `camel-git`
   - `camel-github2`
   - `camel-google-bigquery`
   - `camel-google-calendar`
   - `camel-google-common`
   - `camel-google-drive`
   - `camel-google-firestore`
   - `camel-google-functions`
   - `camel-google-mail`
   - `camel-google-parent`
   - `camel-google-pubsub`
   - `camel-google-secret-manager`
   - `camel-google-sheets`
   - `camel-google-speech-to-text`
   - `camel-google-storage`
   - `camel-google-text-to-speech`
   - `camel-google-vertexai`
   - `camel-google-vision`
   - `camel-graphql`
   - `camel-grok`
   - `camel-groovy`
   - `camel-grpc`
   - `camel-gson`
   - `camel-hashicorp-vault`
   - `camel-hazelcast`
   - `camel-health`
   - `camel-hivemq`
   - `camel-hl7`
   - `camel-http`
   - `camel-http-base`
   - `camel-http-common`
   - `camel-huawei-parent`
   - `camel-huaweicloud-common`
   - `camel-huaweicloud-dms`
   - `camel-huaweicloud-frs`
   - `camel-huaweicloud-functiongraph`
   - `camel-huaweicloud-iam`
   - `camel-huaweicloud-imagerecognition`
   - `camel-huaweicloud-obs`
   - `camel-huaweicloud-smn`
   - `camel-huggingface`
   - `camel-ibm-cos`
   - `camel-ibm-parent`
   - `camel-ibm-secrets-manager`
   - `camel-ibm-watson-discovery`
   - `camel-ibm-watson-language`
   - `camel-ibm-watson-speech-to-text`
   - `camel-ibm-watson-text-to-speech`
   - `camel-ibm-watsonx-ai`
   - `camel-ibm-watsonx-data`
   - `camel-ical`
   - `camel-iggy`
   - `camel-ignite`
   - `camel-infinispan`
   - `camel-infinispan-common`
   - `camel-infinispan-embedded`
   - `camel-infinispan-parent`
   - `camel-influxdb`
   - `camel-influxdb2`
   - `camel-iso8583`
   - `camel-jackson`
   - `camel-jackson-avro`
   - `camel-jackson-protobuf`
   - `camel-jackson3`
   - `camel-jackson3-avro`
   - `camel-jackson3-protobuf`
   - `camel-jackson3xml`
   - `camel-jacksonxml`
   - `camel-jactl`
   - `camel-jandex`
   - `camel-jasypt`
   - `camel-java-io`
   - `camel-java-joor-dsl`
   - `camel-javascript`
   - `camel-jaxb`
   - `camel-jbang-console`
   - `camel-jbang-mcp`
   - `camel-jbang-parent`
   - `camel-jbang-plugin-mcp`
   - `camel-jbang-plugin-route-parser`
   - `camel-jbang-plugin-tui`
   - `camel-jbang-plugin-validate`
   - `camel-jcache`
   - `camel-jcr`
   - `camel-jdbc`
   - `camel-jetty`
   - `camel-jetty-common`
   - `camel-jfr`
   - `camel-jgroups`
   - `camel-jgroups-raft`
   - `camel-jira`
   - `camel-jms`
   - `camel-jmx`
   - `camel-jolt`
   - `camel-jooq`
   - `camel-joor`
   - `camel-jpa`
   - `camel-jq`
   - `camel-jsch`
   - `camel-jslt`
   - `camel-json-validator`
   - `camel-jsonapi`
   - `camel-jsonata`
   - `camel-jsonb`
   - `camel-jsonpath`
   - `camel-jsoup`
   - `camel-jt400`
   - `camel-jta`
   - `camel-jte`
   - `camel-kafka`
   - `camel-kamelet`
   - `camel-kamelet-main-support`
   - `camel-keycloak`
   - `camel-knative`
   - `camel-knative-api`
   - `camel-knative-http`
   - `camel-knative-parent`
   - `camel-kserve`
   - `camel-kubernetes`
   - `camel-kudu`
   - `camel-langchain4j-agent`
   - `camel-langchain4j-agent-api`
   - `camel-langchain4j-chat`
   - `camel-langchain4j-core`
   - `camel-langchain4j-embeddings`
   - `camel-langchain4j-embeddingstore`
   - `camel-langchain4j-embeddingstore-api`
   - `camel-langchain4j-ingest`
   - `camel-langchain4j-tokenizer`
   - `camel-langchain4j-web-search`
   - `camel-language`
   - `camel-launcher-container`
   - `camel-ldap`
   - `camel-ldif`
   - `camel-log`
   - `camel-lra`
   - `camel-lucene`
   - `camel-lumberjack`
   - `camel-lzf`
   - `camel-mail`
   - `camel-mail-microsoft-oauth`
   - `camel-main`
   - `camel-management`
   - `camel-management-api`
   - `camel-mapstruct`
   - `camel-master`
   - `camel-maven-plugin`
   - `camel-mcp-server`
   - `camel-mcp-server-api`
   - `camel-mdc`
   - `camel-metrics`
   - `camel-micrometer`
   - `camel-micrometer-observability`
   - `camel-micrometer-prometheus`
   - `camel-microprofile-config`
   - `camel-microprofile-fault-tolerance`
   - `camel-microprofile-health`
   - `camel-microprofile-parent`
   - `camel-milo`
   - `camel-milvus`
   - `camel-mina`
   - `camel-mina-sftp`
   - `camel-minio`
   - `camel-mllp`
   - `camel-mock`
   - `camel-mongodb`
   - `camel-mongodb-gridfs`
   - `camel-mustache`
   - `camel-mvel`
   - `camel-mybatis`
   - `camel-nats`
   - `camel-neo4j`
   - `camel-netty`
   - `camel-netty-http`
   - `camel-oaipmh`
   - `camel-oauth`
   - `camel-observability-services`
   - `camel-observation`
   - `camel-ocsf`
   - `camel-ognl`
   - `camel-olingo2`
   - `camel-olingo2-api`
   - `camel-olingo2-parent`
   - `camel-olingo4`
   - `camel-olingo4-api`
   - `camel-olingo4-parent`
   - `camel-once`
   - `camel-opa`
   - `camel-openai`
   - `camel-openapi-java`
   - `camel-openapi-rest-dsl-generator`
   - `camel-openapi-validator`
   - `camel-opensearch`
   - `camel-openstack`
   - `camel-opentelemetry`
   - `camel-opentelemetry-metrics`
   - `camel-opentelemetry2`
   - `camel-optaplanner`
   - `camel-package-maven-plugin`
   - `camel-paho`
   - `camel-paho-mqtt5`
   - `camel-parquet-avro`
   - `camel-pdf`
   - `camel-pg-replication-slot`
   - `camel-pgevent`
   - `camel-pgvector`
   - `camel-pinecone`
   - `camel-platform-http`
   - `camel-platform-http-jolokia`
   - `camel-platform-http-main`
   - `camel-platform-http-vertx`
   - `camel-plc4x`
   - `camel-pqc`
   - `camel-printer`
   - `camel-protobuf`
   - `camel-pubnub`
   - `camel-pulsar`
   - `camel-python`
   - `camel-python3`
   - `camel-qdrant`
   - `camel-quartz`
   - `camel-quickfix`
   - `camel-quickjs`
   - `camel-reactive-streams`
   - `camel-reactor`
   - `camel-redis`
   - `camel-ref`
   - `camel-resilience4j`
   - `camel-resilience4j-micrometer`
   - `camel-resourceresolver-github`
   - `camel-rest`
   - `camel-rest-openapi`
   - `camel-rest-postman`
   - `camel-restdsl-openapi-plugin`
   - `camel-robotframework`
   - `camel-rocketmq`
   - `camel-rss`
   - `camel-rxjava`
   - `camel-saga`
   - `camel-salesforce`
   - `camel-salesforce-codegen`
   - `camel-salesforce-parent`
   - `camel-sap-netweaver`
   - `camel-saxon`
   - `camel-scheduler`
   - `camel-schematron`
   - `camel-seda`
   - `camel-semantic`
   - `camel-servicenow`
   - `camel-servicenow-maven-plugin`
   - `camel-servicenow-parent`
   - `camel-servlet`
   - `camel-shell`
   - `camel-shiro`
   - `camel-sjms`
   - `camel-sjms2`
   - `camel-slack`
   - `camel-smb`
   - `camel-smooks`
   - `camel-smpp`
   - `camel-snakeyaml`
   - `camel-snmp`
   - `camel-soap`
   - `camel-solr`
   - `camel-spiffe`
   - `camel-splunk-hec`
   - `camel-spring`
   - `camel-spring-ai-chat`
   - `camel-spring-ai-embeddings`
   - `camel-spring-ai-image`
   - `camel-spring-ai-parent`
   - `camel-spring-ai-vector-store`
   - `camel-spring-batch`
   - `camel-spring-cloud-config`
   - `camel-spring-jdbc`
   - `camel-spring-ldap`
   - `camel-spring-main`
   - `camel-spring-parent`
   - `camel-spring-rabbitmq`
   - `camel-spring-redis`
   - `camel-spring-security`
   - `camel-spring-ws`
   - `camel-spring-xml`
   - `camel-sql`
   - `camel-ssh`
   - `camel-state-store`
   - `camel-state-store-parent`
   - `camel-stax`
   - `camel-stitch`
   - `camel-stream`
   - `camel-streamcaching-test`
   - `camel-stringtemplate`
   - `camel-stripe`
   - `camel-stub`
   - `camel-support`
   - `camel-swift`
   - `camel-syslog`
   - `camel-tahu`
   - `camel-tarfile`
   - `camel-telegram`
   - `camel-telemetry`
   - `camel-telemetry-dev`
   - `camel-tensorflow-serving`
   - `camel-test-infra-all`
   - `camel-test-infra-artemis`
   - `camel-test-infra-cli`
   - `camel-test-infra-core`
   - `camel-test-infra-jetty`
   - `camel-test-infra-qdrant`
   - `camel-test-infra-smb`
   - `camel-test-junit5`
   - `camel-test-junit6`
   - `camel-test-main-junit5`
   - `camel-test-main-junit6`
   - `camel-test-parent`
   - `camel-test-spring-junit5`
   - `camel-test-spring-junit6`
   - `camel-thrift`
   - `camel-thymeleaf`
   - `camel-tika`
   - `camel-timer`
   - `camel-tooling-maven`
   - `camel-toon`
   - `camel-tracing`
   - `camel-twilio`
   - `camel-twitter`
   - `camel-typesafe-ai`
   - `camel-ubl`
   - `camel-undertow`
   - `camel-undertow-spring-security`
   - `camel-univocity-parsers`
   - `camel-util`
   - `camel-validator`
   - `camel-velocity`
   - `camel-vertx`
   - `camel-vertx-common`
   - `camel-vertx-http`
   - `camel-vertx-parent`
   - `camel-vertx-websocket`
   - `camel-wal`
   - `camel-wasm`
   - `camel-weather`
   - `camel-weaviate`
   - `camel-web3j`
   - `camel-webhook`
   - `camel-whatsapp`
   - `camel-wordpress`
   - `camel-workday`
   - `camel-xchange`
   - `camel-xj`
   - `camel-xml-io`
   - `camel-xml-io-dsl`
   - `camel-xml-io-util`
   - `camel-xml-jaxb`
   - `camel-xml-jaxb-dsl`
   - `camel-xml-jaxb-dsl-test`
   - `camel-xml-jaxb-dsl-test-definition`
   - `camel-xml-jaxb-dsl-test-spring`
   - `camel-xml-jaxp`
   - `camel-xml-jaxp-util`
   - `camel-xmlsecurity`
   - `camel-xmpp`
   - `camel-xpath`
   - `camel-xslt`
   - `camel-xslt-saxon`
   - `camel-yaml-dsl-common`
   - `camel-yaml-dsl-parent`
   - `camel-yaml-dsl-validator`
   - `camel-yaml-dsl-validator-maven-plugin`
   - `camel-yaml-io`
   - `camel-zendesk`
   - `camel-zip-deflater`
   - `camel-zipfile`
   - `camel-zookeeper`
   - `camel-zookeeper-master`
   - `components`
   - `core`
   - `dsl`
   - `sync-properties-maven-plugin`
   
   </details>
   
   
   <details><summary>Modules only in current approach (70)</summary>
   
   - `[ERROR]`
   - `[ERROR]`
   - `[ERROR] Command execution failed.`
   - `[ERROR] Failed to execute goal org.codehaus.mojo:exec-maven-plugin:3.6.4:exec (default-cli) on project camel-package-maven-plugin: Command execution failed. Process exited with an error: 127 (Exit value: 127) -> [Help 1]`
   - `[ERROR] For more information about the errors and possible solutions, please read the following articles:`
   - `[ERROR] Re-run Maven using the -X switch to enable full debug logging.`
   - `[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.`
   - `[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoExecutionException`
   - `at com.gradle.maven.cache.extension.c.a.a (SourceFile:46)`
   - `at com.gradle.maven.cache.extension.c.a.a (SourceFile:46)`
   - `at com.gradle.maven.cache.extension.c.b.a (SourceFile:62)`
   - `at com.gradle.maven.cache.extension.c.b.c (SourceFile:118)`
   - `at com.gradle.maven.cache.extension.c.c.a (SourceFile:26)`
   - `at com.gradle.maven.cache.extension.c.d.a (SourceFile:27)`
   - `at com.gradle.maven.cache.extension.c.g.a (SourceFile:27)`
   - `at com.gradle.maven.cache.extension.c.h$1.run (SourceFile:35)`
   - `at com.gradle.maven.cache.extension.c.h.a (SourceFile:31)`
   - `at com.gradle.maven.cache.extension.c.j.a (SourceFile:28)`
   - `at com.gradle.maven.cache.extension.c.m.a (SourceFile:80)`
   - `at com.gradle.maven.cache.extension.c.n.a (SourceFile:53)`
   - `at com.gradle.maven.cache.extension.c.o.a (SourceFile:18)`
   - `at com.gradle.maven.cache.extension.c.p.a (SourceFile:27)`
   - `at com.gradle.maven.cache.extension.c.q.a (SourceFile:23)`
   - `at com.gradle.maven.cache.extension.g.b.lambda$createProxy$0 (SourceFile:77)`
   - `at com.gradle.maven.scan.extension.internal.e.b.executeMojo (SourceFile:116)`
   - `at java.lang.reflect.Method.invoke (Method.java:565)`
   - `at java.lang.reflect.Method.invoke (Method.java:565)`
   - `at java.lang.reflect.Method.invoke (Method.java:565)`
   - `at jdk.internal.reflect.DirectMethodHandleAccessor.invoke (DirectMethodHandleAccessor.java:104)`
   - `at jdk.internal.reflect.DirectMethodHandleAccessor.invoke (DirectMethodHandleAccessor.java:104)`
   - `at jdk.internal.reflect.DirectMethodHandleAccessor.invoke (DirectMethodHandleAccessor.java:104)`
   - `at jdk.proxy5.$Proxy69.execute (Unknown Source)`
   - `at org.apache.commons.exec.DefaultExecutor.execute (DefaultExecutor.java:306)`
   - `at org.apache.commons.exec.DefaultExecutor.executeInternal (DefaultExecutor.java:408)`
   - `at org.apache.maven.DefaultMaven.doExecute (DefaultMaven.java:173)`
   - `at org.apache.maven.DefaultMaven.doExecute (DefaultMaven.java:261)`
   - `at org.apache.maven.DefaultMaven.execute (DefaultMaven.java:101)`
   - `at org.apache.maven.cli.MavenCli.doMain (MavenCli.java:285)`
   - `at org.apache.maven.cli.MavenCli.execute (MavenCli.java:919)`
   - `at org.apache.maven.cli.MavenCli.main (MavenCli.java:207)`
   - `at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject (LifecycleModuleBuilder.java:105)`
   - `at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject (LifecycleModuleBuilder.java:73)`
   - `at org.apache.maven.lifecycle.internal.LifecycleStarter.execute (LifecycleStarter.java:118)`
   - `at org.apache.maven.lifecycle.internal.MojoExecutor$1.run (MojoExecutor.java:162)`
   - `at org.apache.maven.lifecycle.internal.MojoExecutor.access$000 (MojoExecutor.java:75)`
   - `at org.apache.maven.lifecycle.internal.MojoExecutor.doExecute (MojoExecutor.java:316)`
   - `at org.apache.maven.lifecycle.internal.MojoExecutor.doExecute2 (MojoExecutor.java:328)`
   - `at org.apache.maven.lifecycle.internal.MojoExecutor.execute (MojoExecutor.java:159)`
   - `at org.apache.maven.lifecycle.internal.MojoExecutor.execute (MojoExecutor.java:174)`
   - `at org.apache.maven.lifecycle.internal.MojoExecutor.execute (MojoExecutor.java:212)`
   - `at org.apache.maven.lifecycle.internal.builder.singlethreaded.SingleThreadedBuilder.build (SingleThreadedBuilder.java:53)`
   - `at org.apache.maven.plugin.DefaultMojosExecutionStrategy.execute (DefaultMojosExecutionStrategy.java:39)`
   - `at org.apache.maven.wrapper.BootstrapMainStarter.start (BootstrapMainStarter.java:52)`
   - `at org.apache.maven.wrapper.MavenWrapperMain.main (MavenWrapperMain.java:73)`
   - `at org.apache.maven.wrapper.WrapperExecutor.execute (WrapperExecutor.java:161)`
   - `at org.codehaus.mojo.exec.ExecMojo.execute (ExecMojo.java:464)`
   - `at org.codehaus.mojo.exec.ExecMojo.executeCommandLine (ExecMojo.java:873)`
   - `at org.codehaus.mojo.exec.ExecMojo.executeCommandLine (ExecMojo.java:913)`
   - `at org.codehaus.plexus.classworlds.launcher.Launcher.launch (Launcher.java:201)`
   - `at org.codehaus.plexus.classworlds.launcher.Launcher.launchEnhanced (Launcher.java:255)`
   - `at org.codehaus.plexus.classworlds.launcher.Launcher.main (Launcher.java:314)`
   - `at org.codehaus.plexus.classworlds.launcher.Launcher.mainWithExitCode (Launcher.java:362)`
   - `at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute (SourceFile:27)`
   - `at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute (SourceFile:30)`
   - `at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute (SourceFile:60)`
   - `at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute (SourceFile:67)`
   - `at org.gradle.internal.operations.DefaultBuildOperationRunner.execute (SourceFile:167)`
   - `at org.gradle.internal.operations.DefaultBuildOperationRunner.execute (SourceFile:60)`
   - `at org.gradle.internal.operations.DefaultBuildOperationRunner.run (SourceFile:48)`
   - `org.apache.commons.exec.ExecuteException: Process exited with an error: 127 (Exit value: 127)`
   
   </details>
   
   
   <details><summary>Modules Scalpel would test (585)</summary>
   
   - `archetypes` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-a2a` ← depends on affected reactor module org.apache.camel:camel-platform-http-vertx
   - `camel-activemq` ← depends on affected reactor module org.apache.camel:camel-test-infra-artemis
   - `camel-activemq6` ← depends on affected reactor module org.apache.camel:camel-test-infra-artemis
   - `camel-ai-observability` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-ai-observability-api` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-ai-parent` ← downstream of org.apache.camel:camel-core-model
   - `camel-ai-resource` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-ai-tool` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-alibaba-common` ← depends on affected reactor module org.apache.camel:camel-support
   - `camel-alibaba-eventbridge` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-alibaba-fc` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-alibaba-kms` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-alibaba-mns` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-alibaba-oss` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-alibaba-ots` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-alibaba-parent` ← downstream of org.apache.camel:camel-core-model
   - `camel-alibaba-sls` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-alibaba-sms` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-amqp` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-api` ← core/camel-api/src/main/java/org/apache/camel/spi/PropertyConfigurer.java
   - `camel-api-component-maven-plugin` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-arangodb` ← depends on affected reactor module org.apache.camel:camel-test-infra-core
   - `camel-archetype-api-component` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-archetype-component` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-archetype-dataformat` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-archetype-java` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-archetype-main` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-as2` ← depends on affected reactor module org.apache.camel:camel-jetty
   - `camel-as2-api` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-as2-parent` ← downstream of org.apache.camel:camel-core-model
   - `camel-asn1` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-asterisk` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-atmosphere-websocket` ← depends on affected reactor module org.apache.camel:camel-test-infra-jetty
   - `camel-atom` ← depends on affected reactor module org.apache.camel:camel-test-infra-jetty
   - `camel-attachments` ← depends on affected reactor module org.apache.camel:camel-bean
   - `camel-avro` ← depends on affected reactor module org.apache.camel:camel-spring-xml
   - `camel-avro-rpc` ← depends on affected reactor module org.apache.camel:camel-spring-xml
   - `camel-avro-rpc-jetty` ← depends on affected reactor module org.apache.camel:camel-api
   - `camel-avro-rpc-parent` ← downstream of org.apache.camel:camel-core-model
   - `camel-avro-rpc-spi` ← downstream of org.apache.camel:camel-core-model
   - `camel-aws-bedrock` ← depends on affected reactor module org.apache.camel:camel-base64
   - `camel-aws-cloudtrail` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-aws-common` ← depends on affected reactor module org.apache.camel:camel-support
   - `camel-aws-config` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-aws-parameter-store` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-aws-parent` ← downstream of org.apache.camel:camel-core-model
   - `camel-aws-secrets-manager` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-aws-security-hub` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-aws2-athena` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-aws2-comprehend` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-aws2-cw` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-aws2-ddb` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-aws2-ec2` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-aws2-ecs` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-aws2-eks` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-aws2-eventbridge` ← depends on affected reactor module org.apache.camel:camel-aws2-sqs
   - `camel-aws2-iam` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-aws2-kinesis` ← depends on affected reactor module org.apache.camel:camel-core-catalog
   - `camel-aws2-kms` ← depends on affected reactor module org.apache.camel:camel-core-catalog
   - `camel-aws2-lambda` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-aws2-mq` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-aws2-msk` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-aws2-polly` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-aws2-redshift` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-aws2-rekognition` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-aws2-s3` ← depends on affected reactor module org.apache.camel:camel-zip-deflater
   - `camel-aws2-s3-vectors` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-aws2-ses` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-aws2-sns` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-aws2-sqs` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-aws2-step-functions` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-aws2-sts` ← depends on affected reactor module org.apache.camel:camel-core-catalog
   - `camel-aws2-textract` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-aws2-timestream` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-aws2-transcribe` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-aws2-translate` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-azure-common` ← downstream of org.apache.camel:camel-core-model
   - `camel-azure-cosmosdb` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-azure-eventgrid` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-azure-eventhubs` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-azure-files` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-azure-functions` ← depends on affected reactor module org.apache.camel:camel-test-infra-core
   - `camel-azure-key-vault` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-azure-parent` ← downstream of org.apache.camel:camel-core-model
   - `camel-azure-schema-registry` ← depends on affected reactor module org.apache.camel:camel-mock
   - `camel-azure-servicebus` ← depends on affected reactor module org.apache.camel:camel-test-infra-core
   - `camel-azure-storage-blob` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-azure-storage-datalake` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-azure-storage-queue` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-barcode` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-base` ← depends on affected reactor module org.apache.camel:camel-util
   - `camel-base-engine` ← depends on affected reactor module org.apache.camel:camel-base
   - `camel-base64` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-bean` ← depends on affected reactor module org.apache.camel:camel-support
   - `camel-bean-validator` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-beanio` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-bindy` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-bom` ← own pom bom/camel-bom/pom.xml changed
   - `camel-bonita` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-box` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-box-api` ← depends on affected reactor module org.apache.camel:camel-api
   - `camel-box-parent` ← downstream of org.apache.camel:camel-core-model
   - `camel-braintree` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-browse` ← depends on affected reactor module org.apache.camel:camel-support
   - `camel-caffeine` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-camunda` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-cassandraql` ← depends on affected reactor module org.apache.camel:camel-test-infra-core
   - `camel-catalog-common` ← depends on affected reactor module org.apache.camel:camel-support
   - `camel-cbor` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-chatscript` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-chunk` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-cli-connector` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-cli-debug` ← depends on affected reactor module org.apache.camel:camel-cli-connector
   - `camel-clickhouse` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-clickup` ← depends on affected reactor module org.apache.camel:camel-netty-http
   - `camel-cloudevents` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-cluster` ← depends on affected reactor module org.apache.camel:camel-core-model
   - `camel-cm-sms` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-coap` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-cometd` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-component-maven-plugin` ← depends on affected reactor module org.apache.camel:camel-package-maven-plugin
   - `camel-console` ← depends on affected reactor module org.apache.camel:camel-core
   - `camel-consul` ← depends on affected reactor module org.apache.camel:camel-spring-main
   - `camel-controlbus` ← depends on affected reactor module org.apache.camel:camel-support
   - `camel-core` ← core/camel-core/src/test/java/org/apache/camel/processor/ChoiceSelectorTest.java, core/camel-core/src/test/java/org/apache/camel/support/PropertyBindingSupportConfigurerTest.java
   - `camel-core-all` ← depends on affected reactor module org.apache.camel:camel-xml-jaxp
   - `camel-core-catalog` ← depends on affected reactor module org.apache.camel:camel-api
   - `camel-core-engine` ← core/camel-core-engine/src/main/docs/modules/eips/pages/choice-eip.adoc
   - `camel-core-languages` ← depends on affected reactor module org.apache.camel:camel-support
   - `camel-core-model` ← core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/choice.json, core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/when.json, core/camel-core-model/src/main/java/org/apache/camel/model/BasicExpressionNode.java, core/camel-core-model/src/main/java/org/apache/camel/model/ChoiceDefinition.java, core/camel-core-model/src/main/java/org/apache/camel/model/ProcessorDefinition.java, core/camel-core-model/src/main/java/org/apache/camel/model/WhenDefinition.java
   - `camel-core-processor` ← core/camel-core-processor/src/main/java/org/apache/camel/processor/ChoiceProcessor.java
   - `camel-core-reifier` ← core/camel-core-reifier/src/main/java/org/apache/camel/reifier/ChoiceReifier.java
   - `camel-core-xml` ← depends on affected reactor module org.apache.camel:camel-core-engine
   - `camel-couchbase` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-couchdb` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-cron` ← depends on affected reactor module org.apache.camel:camel-timer
   - `camel-crypto` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-crypto-pgp` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-csv` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-cxf-common` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-cxf-parent` ← downstream of org.apache.camel:camel-core-model
   - `camel-cxf-rest` ← depends on affected reactor module org.apache.camel:camel-undertow
   - `camel-cxf-soap` ← depends on affected reactor module org.apache.camel:camel-undertow
   - `camel-cxf-spring-common` ← depends on affected reactor module org.apache.camel:camel-http-base
   - `camel-cxf-spring-rest` ← depends on affected reactor module org.apache.camel:camel-undertow
   - `camel-cxf-spring-soap` ← depends on affected reactor module org.apache.camel:camel-undertow
   - `camel-cxf-spring-transport` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-cxf-transport` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-cyberark-vault` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-dapr` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-dataformat` ← depends on affected reactor module org.apache.camel:camel-support
   - `camel-dataset` ← depends on affected reactor module org.apache.camel:camel-mock
   - `camel-datasonnet` ← depends on affected reactor module org.apache.camel:camel-core
   - `camel-dataweave` ← downstream of org.apache.camel:camel-core-model
   - `camel-debezium-common` ← depends on affected reactor module org.apache.camel:camel-support
   - `camel-debezium-common-parent` ← depends on affected reactor module org.apache.camel:camel-support
   - `camel-debezium-db2` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-debezium-maven-plugin` ← depends on affected reactor module org.apache.camel:camel-support
   - `camel-debezium-mongodb` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-debezium-mysql` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-debezium-oracle` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-debezium-parent` ← downstream of org.apache.camel:camel-core-model
   - `camel-debezium-postgres` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-debezium-sqlserver` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-debug` ← depends on affected reactor module org.apache.camel:camel-xml-io
   - `camel-dependencies` ← downstream of org.apache.camel:sync-properties-maven-plugin
   - `camel-dfdl` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-dhis2` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-dhis2-api` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-dhis2-parent` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-diagram` ← depends on affected reactor module org.apache.camel:camel-management
   - `camel-direct` ← depends on affected reactor module org.apache.camel:camel-support
   - `camel-disruptor` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-djl` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-dns` ← depends on affected reactor module org.apache.camel:camel-log
   - `camel-docker` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-docling` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-drill` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-dropbox` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-dsl-modeline` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-dsl-support` ← depends on affected reactor module org.apache.camel:camel-core-model
   - `camel-duckdb` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-dynamic-router` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-ehcache` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-eip-documentation-enricher-maven-plugin` ← depends on affected reactor module org.apache.camel:camel-util
   - `camel-elasticsearch` ← depends on affected reactor module org.apache.camel:camel-core-catalog
   - `camel-elasticsearch-rest-client` ← depends on affected reactor module org.apache.camel:camel-core-catalog
   - `camel-etc` ← effective property workspace
   - `camel-event` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-exec` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-fastjson` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-fhir` ← depends on affected reactor module org.apache.camel:camel-hl7
   - `camel-fhir-api` ← depends on affected reactor module org.apache.camel:camel-support
   - `camel-fhir-parent` ← downstream of org.apache.camel:camel-core-model
   - `camel-file` ← depends on affected reactor module org.apache.camel:camel-core-engine
   - `camel-file-watch` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-flatpack` ← depends on affected reactor module org.apache.camel:camel-direct
   - `camel-flink` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-flowable` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-fop` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-fory` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-freemarker` ← depends on affected reactor module org.apache.camel:camel-management
   - `camel-ftp` ← depends on affected reactor module org.apache.camel:camel-spring-xml
   - `camel-ftp-common` ← depends on affected reactor module org.apache.camel:camel-core-catalog
   - `camel-geocoder` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-git` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-github2` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-google-bigquery` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-google-calendar` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-google-common` ← depends on affected reactor module org.apache.camel:camel-core
   - `camel-google-drive` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-google-firestore` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-google-functions` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-google-mail` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-google-parent` ← downstream of org.apache.camel:camel-core-model
   - `camel-google-pubsub` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-google-secret-manager` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-google-sheets` ← depends on affected reactor module org.apache.camel:camel-mock
   - `camel-google-speech-to-text` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-google-storage` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-google-text-to-speech` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-google-vertexai` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-google-vision` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-graphql` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-grok` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-groovy` ← depends on affected reactor module org.apache.camel:camel-main
   - `camel-grpc` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-gson` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-hashicorp-vault` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-hazelcast` ← depends on affected reactor module org.apache.camel:camel-spring-main
   - `camel-health` ← depends on affected reactor module org.apache.camel:camel-core-engine
   - `camel-hivemq` ← depends on affected reactor module org.apache.camel:camel-test-infra-core
   - `camel-hl7` ← depends on affected reactor module org.apache.camel:camel-core
   - `camel-http` ← depends on affected reactor module org.apache.camel:camel-test-infra-jetty
   - `camel-http-base` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-http-common` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-huawei-parent` ← downstream of org.apache.camel:camel-core-model
   - `camel-huaweicloud-common` ← depends on affected reactor module org.apache.camel:camel-support
   - `camel-huaweicloud-dms` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-huaweicloud-frs` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-huaweicloud-functiongraph` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-huaweicloud-iam` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-huaweicloud-imagerecognition` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-huaweicloud-obs` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-huaweicloud-smn` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-huggingface` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-ibm-cos` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-ibm-parent` ← downstream of org.apache.camel:camel-core-model
   - `camel-ibm-secrets-manager` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-ibm-watson-discovery` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-ibm-watson-language` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-ibm-watson-speech-to-text` ← depends on affected reactor module org.apache.camel:camel-ibm-watson-text-to-speech
   - `camel-ibm-watson-text-to-speech` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-ibm-watsonx-ai` ← depends on affected reactor module org.apache.camel:camel-vertx-websocket
   - `camel-ibm-watsonx-data` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-ical` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-iggy` ← depends on affected reactor module org.apache.camel:camel-test-infra-core
   - `camel-ignite` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-infinispan` ← depends on affected reactor module org.apache.camel:camel-stub
   - `camel-infinispan-common` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-infinispan-embedded` ← depends on affected reactor module org.apache.camel:camel-log
   - `camel-infinispan-parent` ← downstream of org.apache.camel:camel-core-model
   - `camel-influxdb` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-influxdb2` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-iso8583` ← depends on affected reactor module org.apache.camel:camel-groovy
   - `camel-jackson` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-jackson-avro` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-jackson-protobuf` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-jackson3` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-jackson3-avro` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-jackson3-protobuf` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-jackson3xml` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-jacksonxml` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-jactl` ← depends on affected reactor module org.apache.camel:camel-core
   - `camel-jandex` ← depends on affected reactor module org.apache.camel:camel-mock
   - `camel-jasypt` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-java-io` ← core/camel-java-io/src/generated/java/org/apache/camel/java/out/JavaDslModelWriter.java, core/camel-java-io/src/test/java/org/apache/camel/java/out/JavaDslModelWriterTest.java, core/camel-java-io/src/test/resources/choiceSelector.xml
   - `camel-java-joor-dsl` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-javascript` ← depends on affected reactor module org.apache.camel:camel-core
   - `camel-jaxb` ← depends on affected reactor module org.apache.camel:camel-spring-xml
   - `camel-jbang-console` ← depends on affected reactor module org.apache.camel:camel-management
   - `camel-jbang-mcp` ← depends on affected reactor module org.apache.camel:camel-java-joor-dsl
   - `camel-jbang-parent` ← downstream of org.apache.camel:camel-package-maven-plugin
   - `camel-jbang-plugin-mcp` ← depends on affected reactor module org.apache.camel:camel-jbang-core
   - `camel-jbang-plugin-route-parser` ← depends on affected reactor module org.apache.camel:camel-route-parser
   - `camel-jbang-plugin-tui` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-jbang-plugin-validate` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-jcache` ← depends on affected reactor module org.apache.camel:camel-core-languages
   - `camel-jcr` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-jdbc` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-jetty` ← depends on affected reactor module org.apache.camel:camel-main
   - `camel-jetty-common` ← depends on affected reactor module org.apache.camel:camel-jaxb
   - `camel-jfr` ← depends on affected reactor module org.apache.camel:camel-mock
   - `camel-jgroups` ← depends on affected reactor module org.apache.camel:camel-master
   - `camel-jgroups-raft` ← depends on affected reactor module org.apache.camel:camel-master
   - `camel-jira` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-jms` ← depends on affected reactor module org.apache.camel:camel-test-infra-artemis
   - `camel-jmx` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-jolt` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-jooq` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-joor` ← depends on affected reactor module org.apache.camel:camel-core
   - `camel-jpa` ← effective property camel.surefire.fork.additional-vmargs
   - `camel-jq` ← depends on affected reactor module org.apache.camel:camel-core
   - `camel-jsch` ← depends on affected reactor module org.apache.camel:camel-ssh
   - `camel-jslt` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-json-validator` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-jsonapi` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-jsonata` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-jsonb` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-jsonpath` ← depends on affected reactor module org.apache.camel:camel-core
   - `camel-jsoup` ← depends on affected reactor module org.apache.camel:camel-mock
   - `camel-jt400` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-jta` ← depends on affected reactor module org.apache.camel:camel-sql
   - `camel-jte` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-kafka` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-kamelet` ← depends on affected reactor module org.apache.camel:camel-main
   - `camel-kamelet-main-support` ← depends on affected reactor module org.apache.camel:camel-main
   - `camel-keycloak` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-knative` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-knative-api` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-knative-http` ← depends on affected reactor module org.apache.camel:camel-http
   - `camel-knative-parent` ← downstream of org.apache.camel:camel-core-model
   - `camel-kserve` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-kubernetes` ← depends on affected reactor module org.apache.camel:camel-log
   - `camel-kudu` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-langchain4j-agent` ← depends on affected reactor module org.apache.camel:camel-telemetry
   - `camel-langchain4j-agent-api` ← depends on affected reactor module org.apache.camel:camel-api
   - `camel-langchain4j-chat` ← depends on affected reactor module org.apache.camel:camel-telemetry
   - `camel-langchain4j-core` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-langchain4j-embeddings` ← depends on affected reactor module org.apache.camel:camel-test-infra-qdrant
   - `camel-langchain4j-embeddingstore` ← depends on affected reactor module org.apache.camel:camel-test-infra-qdrant
   - `camel-langchain4j-embeddingstore-api` ← depends on affected reactor module org.apache.camel:camel-api
   - `camel-langchain4j-ingest` ← depends on affected reactor module org.apache.camel:camel-test-infra-qdrant
   - `camel-langchain4j-tokenizer` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-langchain4j-web-search` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-language` ← depends on affected reactor module org.apache.camel:camel-support
   - `camel-launcher-container` ← effective property docker.context
   - `camel-ldap` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-ldif` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-log` ← depends on affected reactor module org.apache.camel:camel-support
   - `camel-lra` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-lucene` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-lumberjack` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-lzf` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-mail` ← depends on affected reactor module org.apache.camel:camel-quartz
   - `camel-mail-microsoft-oauth` ← depends on affected reactor module org.apache.camel:camel-mail
   - `camel-main` ← core/camel-main/src/generated/resources/org/apache/camel/main/languages.properties
   - `camel-management` ← core/camel-management/src/main/java/org/apache/camel/management/mbean/ManagedChoice.java, core/camel-management/src/test/java/org/apache/camel/management/ManagedChoiceTest.java
   - `camel-management-api` ← depends on affected reactor module org.apache.camel:camel-util
   - `camel-mapstruct` ← depends on affected reactor module org.apache.camel:camel-mock
   - `camel-master` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-maven-plugin` ← depends on affected reactor module org.apache.camel:camel-spring-main
   - `camel-mcp-server` ← depends on affected reactor module org.apache.camel:camel-openai
   - `camel-mcp-server-api` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-mdc` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-metrics` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-micrometer` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-micrometer-observability` ← depends on affected reactor module org.apache.camel:camel-undertow
   - `camel-micrometer-prometheus` ← depends on affected reactor module org.apache.camel:camel-platform-http-main
   - `camel-microprofile-config` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-microprofile-fault-tolerance` ← depends on affected reactor module org.apache.camel:camel-management
   - `camel-microprofile-health` ← depends on affected reactor module org.apache.camel:camel-main
   - `camel-microprofile-parent` ← downstream of org.apache.camel:camel-core-model
   - `camel-milo` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-milvus` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-mina` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-mina-sftp` ← depends on affected reactor module org.apache.camel:camel-core-languages
   - `camel-minio` ← depends on affected reactor module org.apache.camel:camel-core-catalog
   - `camel-mllp` ← depends on affected reactor module org.apache.camel:camel-test-infra-artemis
   - `camel-mock` ← depends on affected reactor module org.apache.camel:camel-support
   - `camel-mongodb` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-mongodb-gridfs` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-mustache` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-mvel` ← depends on affected reactor module org.apache.camel:camel-core
   - `camel-mybatis` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-nats` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-neo4j` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-netty` ← depends on affected reactor module org.apache.camel:camel-main
   - `camel-netty-http` ← depends on affected reactor module org.apache.camel:camel-jetty
   - `camel-oaipmh` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-oauth` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-observability-services` ← depends on affected reactor module org.apache.camel:camel-management
   - `camel-observation` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-ocsf` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-ognl` ← depends on affected reactor module org.apache.camel:camel-core
   - `camel-olingo2` ← depends on affected reactor module org.apache.camel:camel-test-infra-jetty
   - `camel-olingo2-api` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-olingo2-parent` ← downstream of org.apache.camel:camel-core-model
   - `camel-olingo4` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-olingo4-api` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-olingo4-parent` ← downstream of org.apache.camel:camel-core-model
   - `camel-once` ← depends on affected reactor module org.apache.camel:camel-groovy
   - `camel-opa` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-openai` ← depends on affected reactor module org.apache.camel:camel-jackson
   - `camel-openapi-java` ← depends on affected reactor module org.apache.camel:camel-management
   - `camel-openapi-rest-dsl-generator` ← depends on affected reactor module org.apache.camel:camel-xml-io
   - `camel-openapi-validator` ← depends on affected reactor module org.apache.camel:camel-platform-http-vertx
   - `camel-opensearch` ← depends on affected reactor module org.apache.camel:camel-test-infra-core
   - `camel-openstack` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-opentelemetry` ← effective property opentracing-agent.lib
   - `camel-opentelemetry-metrics` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-opentelemetry2` ← depends on affected reactor module org.apache.camel:camel-undertow
   - `camel-optaplanner` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-package-maven-plugin` ← tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/MojoHelper.java
   - `camel-paho` ← depends on affected reactor module org.apache.camel:camel-test-infra-artemis
   - `camel-paho-mqtt5` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-parquet-avro` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-pdf` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-pg-replication-slot` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-pgevent` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-pgvector` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-pinecone` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-platform-http` ← depends on affected reactor module org.apache.camel:camel-http
   - `camel-platform-http-jolokia` ← depends on affected reactor module org.apache.camel:camel-core
   - `camel-platform-http-main` ← depends on affected reactor module org.apache.camel:camel-openapi-java
   - `camel-platform-http-vertx` ← depends on affected reactor module org.apache.camel:camel-vertx-http
   - `camel-plc4x` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-pqc` ← depends on affected reactor module org.apache.camel:camel-crypto
   - `camel-printer` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-protobuf` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-pubnub` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-pulsar` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-python` ← depends on affected reactor module org.apache.camel:camel-core
   - `camel-python3` ← depends on affected reactor module org.apache.camel:camel-core
   - `camel-qdrant` ← depends on affected reactor module org.apache.camel:camel-test-infra-qdrant
   - `camel-quartz` ← depends on affected reactor module org.apache.camel:camel-management
   - `camel-quickfix` ← depends on affected reactor module org.apache.camel:camel-jetty
   - `camel-quickjs` ← depends on affected reactor module org.apache.camel:camel-core
   - `camel-reactive-streams` ← depends on affected reactor module org.apache.camel:camel-management
   - `camel-reactor` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-redis` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-ref` ← depends on affected reactor module org.apache.camel:camel-support
   - `camel-resilience4j` ← depends on affected reactor module org.apache.camel:camel-management
   - `camel-resilience4j-micrometer` ← depends on affected reactor module org.apache.camel:camel-management
   - `camel-resourceresolver-github` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-rest` ← depends on affected reactor module org.apache.camel:camel-core-languages
   - `camel-rest-openapi` ← depends on affected reactor module org.apache.camel:camel-vertx-http
   - `camel-rest-postman` ← depends on affected reactor module org.apache.camel:camel-vertx-http
   - `camel-restdsl-openapi-plugin` ← depends on affected reactor module org.apache.camel:camel-jetty
   - `camel-robotframework` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-rocketmq` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-rss` ← depends on affected reactor module org.apache.camel:camel-test-infra-jetty
   - `camel-rxjava` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-saga` ← depends on affected reactor module org.apache.camel:camel-support
   - `camel-salesforce` ← depends on affected reactor module org.apache.camel:camel-core-catalog
   - `camel-salesforce-codegen` ← depends on affected reactor module org.apache.camel:camel-salesforce
   - `camel-salesforce-parent` ← effective property salesforce.component.root
   - `camel-sap-netweaver` ← depends on affected reactor module org.apache.camel:camel-velocity
   - `camel-saxon` ← depends on affected reactor module org.apache.camel:camel-core
   - `camel-scheduler` ← depends on affected reactor module org.apache.camel:camel-support
   - `camel-schematron` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-seda` ← depends on affected reactor module org.apache.camel:camel-support
   - `camel-semantic` ← components/camel-ai/camel-semantic/src/generated/resources/META-INF/org/apache/camel/language/semantic/semantic.json, components/camel-ai/camel-semantic/src/generated/resources/META-INF/services/org/apache/camel/language.properties, components/camel-ai/camel-semantic/src/generated/resources/META-INF/services/org/apache/camel/language/semantic, components/camel-ai/camel-semantic/src/main/docs/semantic-language.adoc, components/camel-ai/camel-semantic/src/main/java/org/apache/camel/language/semantic/SemanticLanguage.java, components/camel-ai/camel-semantic/src/main/java/org/apache/camel/language/semantic/SemanticLanguageConfigurer.java, components/camel-ai/camel-semantic/src/main/java/org/apache/camel/semantic/SemanticAdapter.java, components/camel-ai/camel-semantic/src/main/java/org/apache/camel/semantic/SemanticQuestion.java, components/camel-ai/camel-semantic/src/main/java/org/apache/camel/semantic/SemanticQuestions.java, components/camel-ai/camel-semantic/
 src/main/java/org/apache/camel/semantic/SemanticResult.java, components/camel-ai/camel-semantic/src/main/resources/META-INF/services/org/apache/camel/configurer/org.apache.camel.language.semantic.SemanticLanguage, components/camel-ai/camel-semantic/src/main/resources/META-INF/services/org/apache/camel/configurer/semantic-language, components/camel-ai/camel-semantic/src/test/java/org/apache/camel/semantic/SemanticEipTest.java, components/camel-ai/camel-semantic/src/test/java/org/apache/camel/semantic/SemanticLanguageTest.java, components/camel-ai/camel-semantic/src/test/java/org/apache/camel/semantic/SemanticPropertiesTest.java, own pom components/camel-ai/camel-semantic/pom.xml changed
   - `camel-servicenow` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-servicenow-maven-plugin` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-servicenow-parent` ← effective property servicenow.component.root
   - `camel-servlet` ← depends on affected reactor module org.apache.camel:camel-gson
   - `camel-shell` ← depends on affected reactor module org.apache.camel:camel-test-junit5
   - `camel-shiro` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-sjms` ← depends on affected reactor module org.apache.camel:camel-test-infra-artemis
   - `camel-sjms2` ← depends on affected reactor module org.apache.camel:camel-test-infra-artemis
   - `camel-slack` ← depends on affected reactor module org.apache.camel:camel-undertow
   - `camel-smb` ← depends on affected reactor module org.apache.camel:camel-test-infra-smb
   - `camel-smooks` ← depends on affected reactor module org.apache.camel:camel-attachments
   - `camel-smpp` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-snakeyaml` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-snmp` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-soap` ← depends on affected reactor module org.apache.camel:camel-cxf-soap
   - `camel-solr` ← depends on affected reactor module org.apache.camel:camel-test-infra-core
   - `camel-spiffe` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-splunk-hec` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-spring` ← depends on affected reactor module org.apache.camel:camel-cron
   - `camel-spring-ai-chat` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-spring-ai-embeddings` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-spring-ai-image` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-spring-ai-parent` ← downstream of org.apache.camel:camel-core-model
   - `camel-spring-ai-vector-store` ← depends on affected reactor module org.apache.camel:camel-test-infra-qdrant
   - `camel-spring-batch` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-spring-cloud-config` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-spring-jdbc` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-spring-ldap` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-spring-main` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-spring-parent` ← downstream of org.apache.camel:camel-core-model
   - `camel-spring-rabbitmq` ← depends on affected reactor module org.apache.camel:camel-http
   - `camel-spring-redis` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-spring-security` ← depends on affected reactor module org.apache.camel:camel-core-xml
   - `camel-spring-ws` ← depends on affected reactor module org.apache.camel:camel-jaxb
   - `camel-spring-xml` ← depends on affected reactor module org.apache.camel:camel-cron
   - `camel-sql` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-ssh` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-state-store` ← depends on affected reactor module org.apache.camel:camel-mock
   - `camel-state-store-parent` ← downstream of org.apache.camel:camel-core-model
   - `camel-stax` ← depends on affected reactor module org.apache.camel:camel-jaxb
   - `camel-stitch` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-stream` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-streamcaching-test` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-stringtemplate` ← depends on affected reactor module org.apache.camel:camel-management
   - `camel-stripe` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-stub` ← depends on affected reactor module org.apache.camel:camel-seda
   - `camel-support` ← core/camel-support/src/main/java/org/apache/camel/support/PropertyBindingSupport.java
   - `camel-swift` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-syslog` ← depends on affected reactor module org.apache.camel:camel-mina
   - `camel-tahu` ← depends on affected reactor module org.apache.camel:camel-test-infra-core
   - `camel-tarfile` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-telegram` ← depends on affected reactor module org.apache.camel:camel-netty-http
   - `camel-telemetry` ← depends on affected reactor module org.apache.camel:camel-undertow
   - `camel-telemetry-dev` ← depends on affected reactor module org.apache.camel:camel-undertow
   - `camel-tensorflow-serving` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-test-infra-all` ← depends on affected reactor module org.apache.camel:camel-test-infra-qdrant
   - `camel-test-infra-artemis` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-test-infra-cli` ← depends on affected reactor module org.apache.camel:camel-support
   - `camel-test-infra-core` ← depends on affected reactor module org.apache.camel:camel-core
   - `camel-test-infra-jetty` ← depends on affected reactor module org.apache.camel:camel-util
   - `camel-test-infra-qdrant` ← depends on affected reactor module org.apache.camel:camel-util
   - `camel-test-infra-smb` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-test-junit5` ← depends on affected reactor module org.apache.camel:camel-xml-jaxb
   - `camel-test-junit6` ← depends on affected reactor module org.apache.camel:camel-xml-jaxb
   - `camel-test-main-junit5` ← depends on affected reactor module org.apache.camel:camel-yaml-io
   - `camel-test-main-junit6` ← depends on affected reactor module org.apache.camel:camel-yaml-io
   - `camel-test-parent` ← downstream of org.apache.camel:camel-core-model
   - `camel-test-spring-junit5` ← depends on affected reactor module org.apache.camel:camel-management
   - `camel-test-spring-junit6` ← depends on affected reactor module org.apache.camel:camel-management
   - `camel-thrift` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-thymeleaf` ← depends on affected reactor module org.apache.camel:camel-management
   - `camel-tika` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-timer` ← depends on affected reactor module org.apache.camel:camel-support
   - `camel-tooling-maven` ← depends on affected reactor module org.apache.camel:camel-support
   - `camel-toon` ← depends on affected reactor module org.apache.camel:camel-test-junit5
   - `camel-tracing` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-twilio` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-twitter` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-typesafe-ai` ← components/camel-ai/camel-typesafe-ai/src/main/docs/typesafe-ai-component.adoc, components/camel-ai/camel-typesafe-ai/src/main/java/org/apache/camel/component/typesafeai/TypeSafeAiSemanticAdapter.java, components/camel-ai/camel-typesafe-ai/src/main/resources/META-INF/services/org.apache.camel.semantic.SemanticAdapter, components/camel-ai/camel-typesafe-ai/src/test/java/org/apache/camel/component/typesafeai/TypeSafeAiSemanticAdapterTest.java, own pom components/camel-ai/camel-typesafe-ai/pom.xml changed
   - `camel-ubl` ← depends on affected reactor module org.apache.camel:camel-test-junit5
   - `camel-undertow` ← depends on affected reactor module org.apache.camel:camel-openapi-java
   - `camel-undertow-spring-security` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-univocity-parsers` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-util` ← core/camel-util/src/main/java/org/apache/camel/util/ArtifactUtils.java
   - `camel-validator` ← depends on affected reactor module org.apache.camel:camel-xml-jaxp
   - `camel-velocity` ← depends on affected reactor module org.apache.camel:camel-management
   - `camel-vertx` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-vertx-common` ← depends on affected reactor module org.apache.camel:camel-support
   - `camel-vertx-http` ← depends on affected reactor module org.apache.camel:camel-undertow
   - `camel-vertx-parent` ← downstream of org.apache.camel:camel-core-model
   - `camel-vertx-websocket` ← depends on affected reactor module org.apache.camel:camel-main
   - `camel-wal` ← depends on affected reactor module org.apache.camel:camel-support
   - `camel-wasm` ← effective property camel.surefire.fork.additional-vmargs
   - `camel-weather` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-weaviate` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-web3j` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-webhook` ← depends on affected reactor module org.apache.camel:camel-netty-http
   - `camel-whatsapp` ← depends on affected reactor module org.apache.camel:camel-netty-http
   - `camel-wordpress` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-workday` ← depends on affected reactor module org.apache.camel:camel-mock
   - `camel-xchange` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-xj` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-xml-io` ← core/camel-xml-io/src/generated/java/org/apache/camel/xml/in/ModelParser.java, core/camel-xml-io/src/generated/java/org/apache/camel/xml/out/ModelWriter.java, core/camel-xml-io/src/main/java/org/apache/camel/xml/LwModelToXMLDumper.java
   - `camel-xml-io-dsl` ← dsl/camel-xml-io-dsl/src/test/java/org/apache/camel/dsl/xml/io/XmlChoiceSelectorTest.java
   - `camel-xml-io-util` ← downstream of org.apache.camel:camel-package-maven-plugin
   - `camel-xml-jaxb` ← core/camel-xml-jaxb/src/main/java/org/apache/camel/xml/jaxb/JaxbHelper.java
   - `camel-xml-jaxb-dsl` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-xml-jaxb-dsl-test` ← downstream of org.apache.camel:camel-package-maven-plugin
   - `camel-xml-jaxb-dsl-test-definition` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-xml-jaxb-dsl-test-spring` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-xml-jaxp` ← depends on affected reactor module org.apache.camel:camel-support
   - `camel-xml-jaxp-util` ← downstream of org.apache.camel:camel-package-maven-plugin
   - `camel-xmlsecurity` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-xmpp` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-xpath` ← depends on affected reactor module org.apache.camel:camel-xml-jaxp
   - `camel-xslt` ← depends on affected reactor module org.apache.camel:camel-xml-jaxp
   - `camel-xslt-saxon` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-yaml-dsl-common` ← depends on affected reactor module org.apache.camel:camel-core-engine
   - `camel-yaml-dsl-parent` ← downstream of org.apache.camel:camel-package-maven-plugin
   - `camel-yaml-dsl-validator` ← dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/main/java/org/apache/camel/dsl/yaml/validator/YamlValidator.java, dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/java/org/apache/camel/dsl/yaml/validator/SemanticSchemaTest.java
   - `camel-yaml-dsl-validator-maven-plugin` ← depends on affected reactor module org.apache.camel:camel-yaml-dsl-validator
   - `camel-yaml-io` ← core/camel-yaml-io/src/generated/java/org/apache/camel/yaml/out/YamlModelWriter.java, core/camel-yaml-io/src/main/java/org/apache/camel/yaml/LwModelToYAMLDumper.java
   - `camel-zendesk` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-zip-deflater` ← depends on affected reactor module org.apache.camel:camel-test-junit6
   - `camel-zipfile` ← depends on affected reactor module org.apache.camel:camel-test-spring-junit6
   - `camel-zookeeper` ← depends on affected reactor module org.apache.camel:camel-master
   - `camel-zookeeper-master` ← depends on affected reactor module org.apache.camel:camel-ftp
   - `components` ← downstream of org.apache.camel:camel-core-model
   - `core` ← downstream of org.apache.camel:camel-package-maven-plugin
   - `dsl` ← downstream of org.apache.camel:camel-package-maven-plugin
   - `sync-properties-maven-plugin` ← depends on affected reactor module org.apache.camel:camel-util
   
   </details>
   
   > :information_source: Shadow mode — Scalpel observes but does not affect test execution. [Learn more](https://github.com/maveniverse/scalpel)
   
   </details>
   
   :warning: **Some tests are disabled on GitHub Actions** (`@DisabledIfSystemProperty(named = "ci.env.name")`) and require manual verification:
   
   - `components/camel-ai`: 27 test(s) disabled on GitHub Actions
   - `dsl/camel-jbang/camel-jbang-core`: 2 test(s) disabled on GitHub Actions
   - `core/camel-core`: 3 test(s) disabled on GitHub Actions
   
   :bulb: **Manual integration tests recommended:**
   
   > You modified `dsl/camel-jbang/camel-jbang-core`. The related integration tests in `dsl/camel-jbang/camel-jbang-it` are excluded from CI. Consider running them manually:
   > ```
   > mvn verify -f dsl/camel-jbang/camel-jbang-it -Djbang-it-test
   > ```
   
   <details><summary>Build reactor — dependencies compiled but only changed modules were tested (29 modules, 3m 7s total)</summary>
   
   **Total reactor time:** 3m 7s
   
   | Module | Duration | Status |
   | --- | --- | --- |
   | Camel :: Catalog :: Camel Catalog | 21.1s | SUCCESS |
   | Camel :: Main | 19.7s | SUCCESS |
   | Camel :: Java DSL IO | 15.4s | SUCCESS |
   | Camel :: Docs | 13.9s | SUCCESS |
   | Camel :: Maven Plugins :: Camel Maven Package | 12.3s | SUCCESS |
   | Camel :: YAML DSL | 11.5s | SUCCESS |
   | Camel :: XML IO | 11.2s | SUCCESS |
   | Camel :: AI :: TypeSafe AI | 10.6s | SUCCESS |
   | Camel :: Support | 10.4s | SUCCESS |
   | Camel :: Util | 8.7s | SUCCESS |
   | Camel :: XML DSL with camel-xml-io | 7.6s | SUCCESS |
   | Camel :: Core Model | 7.5s | SUCCESS |
   | Camel :: YAML DSL :: Validator | 7.2s | SUCCESS |
   | Camel :: YAML IO | 6.0s | SUCCESS |
   | Camel :: All Components Sync point | 4.5s | SUCCESS |
   | Camel :: YAML DSL :: Deserializers | 4.3s | SUCCESS |
   | Camel :: AI :: Semantic | 3.0s | SUCCESS |
   | Camel :: Core Processor | 2.7s | SUCCESS |
   | Camel :: Core Reifier | 2.0s | SUCCESS |
   | Camel :: API | 1.7s | SUCCESS |
   | Camel :: YAML DSL :: Maven Plugins | 1.3s | SUCCESS |
   | Camel :: Parent | 1.2s | SUCCESS |
   | Camel :: AI :: Parent | 1.0s | SUCCESS |
   | Camel :: Core Engine | 1.0s | SUCCESS |
   | Camel :: XML JAXB | 1.0s | SUCCESS |
   | Camel :: BOM | 0.2s | SUCCESS |
   | Camel :: Core | n/a |  |
   | Camel :: JBang :: Core | n/a |  |
   | Camel :: Management | n/a |  |
   
   **Top 20 slowest modules:**
   - `Camel :: Catalog :: Camel Catalog` (21.1s)
   - `Camel :: Main` (19.7s)
   - `Camel :: Java DSL IO` (15.4s)
   - `Camel :: Docs` (13.9s)
   - `Camel :: Maven Plugins :: Camel Maven Package` (12.3s)
   - `Camel :: YAML DSL` (11.5s)
   - `Camel :: XML IO` (11.2s)
   - `Camel :: AI :: TypeSafe AI` (10.6s)
   - `Camel :: Support` (10.4s)
   - `Camel :: Util` (8.7s)
   - `Camel :: XML DSL with camel-xml-io` (7.6s)
   - `Camel :: Core Model` (7.5s)
   - `Camel :: YAML DSL :: Validator` (7.2s)
   - `Camel :: YAML IO` (6.0s)
   - `Camel :: All Components Sync point` (4.5s)
   - `Camel :: YAML DSL :: Deserializers` (4.3s)
   - `Camel :: AI :: Semantic` (3.0s)
   - `Camel :: Core Processor` (2.7s)
   - `Camel :: Core Reifier` (2.0s)
   - `Camel :: API` (1.7s)
   
   </details>
   
   ---
   :gear: [View full build and test results](https://github.com/apache/camel/actions/runs/35926384938)


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@camel.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] CAMEL-24977: Add semantic evaluation and Choice selectors [camel]

Posted by "gnodet-bot (via GitHub)" <gi...@apache.org>.
gnodet-bot commented on code in PR #26813:
URL: https://github.com/apache/camel/pull/26813#discussion_r4087841742


##########
components/camel-ai/camel-typesafe-ai/src/main/java/org/apache/camel/component/typesafeai/TypeSafeAiSemanticAdapter.java:
##########
@@ -0,0 +1,86 @@
+/*
+ * 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.component.typesafeai;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.CamelContextAware;
+import org.apache.camel.semantic.SemanticAdapter;
+import org.apache.camel.semantic.SemanticQuestion;
+import org.apache.camel.semantic.SemanticResult;
+import org.apache.camel.util.json.JsonObject;
+
+/** Maps common questions to TypeSafe AI using the component's configured, managed transport. */
+public class TypeSafeAiSemanticAdapter implements SemanticAdapter, CamelContextAware {
+    private CamelContext camelContext;
+    private volatile TypeSafeAiEndpoint endpoint;
+
+    @Override
+    public CamelContext getCamelContext() {
+        return camelContext;
+    }
+
+    @Override
+    public void setCamelContext(CamelContext camelContext) {
+        this.camelContext = camelContext;
+    }
+
+    @Override
+    public void validate(SemanticQuestion question) {
+        if (question.getType() == SemanticQuestion.Type.CHOICE && question.getCriteria().size() > 255
+                || question.getType() == SemanticQuestion.Type.SCORE && question.getLevels().size() > 10) {
+            throw new IllegalArgumentException("TypeSafe AI supports at most 255 choice criteria or 10 score levels");
+        }
+        if (endpoint == null) {
+            synchronized (this) {
+                if (endpoint == null) {
+                    endpoint = camelContext.getEndpoint("typesafe-ai:semantic", TypeSafeAiEndpoint.class);
+                }
+            }
+        }
+    }
+
+    @Override
+    public SemanticResult evaluate(SemanticQuestion question, Object state) throws Exception {
+        Map<String, Object> definition = new HashMap<>();
+        definition.put("instructions", question.getInstructions());
+        String type = switch (question.getType()) {
+            case BOOLEAN -> "noul";
+            case CHOICE -> "choice";
+            case SCORE -> "score";
+        };
+        definition.put("type", type);
+        if (question.getType() == SemanticQuestion.Type.SCORE) {
+            definition.put("criteria", question.getLevels());
+        } else if (!question.getCriteria().isEmpty()) {
+            definition.put("criteria", question.getCriteria());
+        }
+        JsonObject response = endpoint.evaluate(Map.of("state", state, "questions", Map.of("question", definition)));

Review Comment:
   ⚠️ **Potential NPE:** `evaluate()` reads `endpoint` (volatile) without a null check. The `SemanticAdapter` contract requires `validate()` to be called before `evaluate()`, but nothing enforces this. If someone wires the adapter without calling `validate()`, or if concurrent route startup triggers `evaluate()` before `validate()` completes on another thread, this NPE will surface as an unreadable `NullPointerException` rather than a meaningful error. Add a guard:
   
   ```suggestion
       @Override
       public SemanticResult evaluate(SemanticQuestion question, Object state) throws Exception {
           TypeSafeAiEndpoint ep = endpoint;
           if (ep == null) {
               throw new IllegalStateException(
                   "TypeSafe AI semantic adapter endpoint not initialized — validate() must be called before evaluate()");
           }
           Map<String, Object> definition = new HashMap<>();
   ```



##########
components/camel-ai/camel-semantic/src/main/java/org/apache/camel/language/semantic/SemanticLanguage.java:
##########
@@ -0,0 +1,301 @@
+/*
+ * 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.language.semantic;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.CamelContextAware;
+import org.apache.camel.Exchange;
+import org.apache.camel.Expression;
+import org.apache.camel.Predicate;
+import org.apache.camel.RuntimeCamelException;
+import org.apache.camel.semantic.SemanticAdapter;
+import org.apache.camel.semantic.SemanticQuestion;
+import org.apache.camel.semantic.SemanticQuestions;
+import org.apache.camel.semantic.SemanticResult;
+import org.apache.camel.spi.Metadata;
+import org.apache.camel.spi.annotations.Language;
+import org.apache.camel.support.ExpressionAdapter;
+import org.apache.camel.support.LanguageSupport;
+import org.apache.camel.support.service.ServiceHelper;
+import org.apache.camel.support.service.ServiceSupport;
+
+/** Evaluates a named, provider-independent question against selected message state. */
+@Language(value = "semantic", modelName = "language")
+@Metadata(title = "Semantic", description = "Evaluate named semantic questions through a provider adapter",
+          label = "language,ai", firstVersion = "4.23.0")
+public class SemanticLanguage extends LanguageSupport {
+    public static final String RESULT = "CamelSemanticResult";
+    public static final String ADAPTER_NAME = "camelSemanticAdapter";
+    public static final String ADAPTER_RESOURCE = "META-INF/services/org.apache.camel.semantic.SemanticAdapter";
+
+    private String adapter;
+    private String defaultState = "${body}";
+    private SemanticAdapter selectedAdapter;
+
+    public String getAdapter() {
+        return adapter;
+    }
+
+    /**
+     * Adapter registry reference (#bean:name) or implementation class (plain FQCN or #class:FQCN). Absent selects the
+     * sole advertised adapter.
+     */
+    public void setAdapter(String adapter) {
+        this.adapter = adapter;
+    }
+
+    public String getDefaultState() {
+        return defaultState;
+    }
+
+    /** Default Simple state selector for questions without their own selector. Defaults to the body. */
+    public void setDefaultState(String defaultState) {
+        this.defaultState = defaultState;
+    }
+
+    @Override
+    public Expression createExpression(String expression) {
+        return createEvaluation(expression, false);
+    }
+
+    @Override
+    public Predicate createPredicate(String expression) {
+        return createEvaluation(expression, true);
+    }
+
+    public boolean validateExpression(String expression) {
+        if (expression == null || !expression.startsWith("ref:") || expression.substring(4).isBlank()) {
+            throw new IllegalArgumentException("Semantic expression must reference a named question using ref:name");
+        }
+        return true;
+    }
+
+    public boolean validatePredicate(String expression) {
+        return validateExpression(expression);
+    }
+
+    private Evaluation createEvaluation(String expression, boolean predicate) {
+        validateExpression(expression);
+        Evaluation evaluation = new Evaluation(expression.substring(4), predicate);
+        if (getCamelContext() != null) {
+            evaluation.init(getCamelContext());
+        }
+        return evaluation;
+    }
+
+    private synchronized SemanticAdapter adapter() {
+        if (selectedAdapter != null) {
+            return selectedAdapter;
+        }
+        CamelContext context = getCamelContext();
+        String configured = adapter == null ? null : context.resolvePropertyPlaceholders(adapter);
+        if (configured != null && configured.startsWith("#bean:")) {
+            Object bean = context.getRegistry().lookupByName(configured.substring(6));
+            if (!(bean instanceof SemanticAdapter found)) {
+                throw new IllegalArgumentException("Semantic adapter bean is missing or does not implement SemanticAdapter");
+            }
+            selectedAdapter = found;
+            return found;
+        }
+        ManagedAdapter owned = null;
+        try {
+            String className = configured;
+            if (className == null) {
+                Set<String> candidates = new TreeSet<>();
+                Enumeration<URL> resources = context.getClassResolver().loadAllResourcesAsURL(ADAPTER_RESOURCE);
+                while (resources.hasMoreElements()) {
+                    try (BufferedReader reader = new BufferedReader(
+                            new InputStreamReader(
+                                    resources.nextElement().openStream(), StandardCharsets.UTF_8))) {
+                        reader.lines().map(line -> line.split("#", 2)[0].trim()).filter(line -> !line.isEmpty())
+                                .forEach(candidates::add);
+                    }
+                }
+                if (candidates.size() != 1) {
+                    throw new IllegalArgumentException(
+                            "Semantic language requires exactly one advertised adapter; found "
+                                                       + candidates + ". Configure camel.language.semantic.adapter explicitly");
+                }
+                className = candidates.iterator().next();
+            }
+            if (className.startsWith("#class:")) {
+                className = className.substring(7);
+            }
+            Class<? extends SemanticAdapter> type
+                    = context.getClassResolver().resolveMandatoryClass(className).asSubclass(SemanticAdapter.class);
+            synchronized (context.getRegistry()) {
+                if (context.getRegistry().lookupByName(ADAPTER_NAME) != null) {
+                    throw new IllegalArgumentException("Semantic adapter registry name is already bound: " + ADAPTER_NAME);
+                }
+                SemanticAdapter instance = context.getInjector().newInstance(type);
+                CamelContextAware.trySetCamelContext(instance, context);
+                owned = new ManagedAdapter(context, instance);
+                context.getRegistry().bind(ADAPTER_NAME, instance);
+            }
+            context.addService(owned, true, true);
+            selectedAdapter = owned.instance;
+            return selectedAdapter;
+        } catch (Exception failure) {
+            if (owned != null) {
+                try {
+                    context.removeService(owned);
+                    ServiceHelper.stopAndShutdownService(owned);
+                } catch (Exception cleanup) {
+                    failure.addSuppressed(cleanup);
+                }
+            }
+            throw RuntimeCamelException.wrapRuntimeCamelException(failure);
+        }
+    }
+
+    private static final class ManagedAdapter extends ServiceSupport {
+        private final CamelContext context;
+        private final SemanticAdapter instance;
+
+        private ManagedAdapter(CamelContext context, SemanticAdapter instance) {
+            this.context = context;
+            this.instance = instance;
+        }
+
+        @Override
+        protected void doInit() throws Exception {
+            ServiceHelper.initService(instance);
+        }
+
+        @Override
+        protected void doStart() throws Exception {
+            ServiceHelper.startService(instance);
+        }
+
+        @Override
+        protected void doStop() throws Exception {
+            ServiceHelper.stopService(instance);
+        }
+
+        @Override
+        protected void doShutdown() throws Exception {
+            try {
+                ServiceHelper.stopAndShutdownService(instance);
+            } finally {
+                if (context.getRegistry().lookupByName(ADAPTER_NAME) == instance) {
+                    context.getRegistry().unbind(ADAPTER_NAME);
+                }
+            }
+        }
+    }
+
+    private final class Evaluation extends ExpressionAdapter {
+        private final String name;
+        private final boolean predicate;
+        private volatile Compiled compiled;
+        private SemanticQuestions questions;
+        private SemanticAdapter provider;

Review Comment:
   ⚠️ **Thread visibility gap:** `questions` and `provider` are plain (non-volatile, non-final) fields written in `init()` and read in `evaluate()`/`compile()`. Only `compiled` is `volatile`. If two threads race on route startup or the expression is shared across threads, a reader calling `evaluate()` before the JMM write of `init()` is flushed may see `null`. `compile()` is `synchronized` and reads `provider`, but `evaluate()` reads `provider` outside the lock. Make both fields `volatile`:
   
   ```suggestion
           private volatile SemanticQuestions questions;
           private volatile SemanticAdapter provider;
   ```



##########
components/camel-ai/camel-semantic/src/main/java/org/apache/camel/semantic/SemanticResult.java:
##########
@@ -0,0 +1,108 @@
+/*
+ * 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;
+
+import java.util.Map;
+
+/**
+ * A provider answer. Value is Boolean, a category String, or a numeric score. A boolean provider may supply only
+ * probability; the question then defines the decision policy. Missing probabilities/confidence remain absent. Metadata
+ * may contain provider/model identity, revision and usage. It must not contain credentials or input state.
+ */
+public final class SemanticResult {
+    private final Object value;
+    private final Double probability;
+    private final Map<String, Double> probabilities;
+    private final Double confidence;
+    private final Map<String, Object> metadata;
+
+    public SemanticResult(Object value, Double probability, Map<String, Double> probabilities,
+                          Double confidence, Map<String, Object> metadata) {
+        this.value = value;
+        this.probability = probability;
+        this.probabilities = probabilities == null ? Map.of() : Map.copyOf(probabilities);
+        this.confidence = confidence;
+        this.metadata = metadata == null ? Map.of() : Map.copyOf(metadata);
+        checkProbability(probability);
+        checkProbability(confidence);
+        this.probabilities.values().forEach(SemanticResult::checkProbability);
+    }
+
+    private static void checkProbability(Double value) {
+        if (value != null && (!Double.isFinite(value) || value < 0 || value > 1)) {
+            throw new IllegalArgumentException("Semantic probabilities and confidence must be within [0,1]");
+        }
+    }
+
+    public Object decision(SemanticQuestion question) {
+        switch (question.getType()) {
+            case BOOLEAN:
+                if (probability != null) {
+                    double threshold = question.getThreshold();
+                    double uncertainty = question.getUncertainty();
+                    if (uncertainty > 0 && probability >= threshold - uncertainty && probability <= threshold + uncertainty) {
+                        if (question.getUncertaintyPolicy() == SemanticQuestion.UncertaintyPolicy.FAIL) {
+                            throw new IllegalStateException("Semantic boolean decision is uncertain");
+                        }
+                        return false;
+                    }
+                    return probability >= threshold;
+                }
+                if (value instanceof Boolean && question.getUncertainty() == 0 && question.getThreshold() == 0.5) {

Review Comment:
   🐛 **Boolean value silently rejected when question has non-default threshold:** The condition `question.getUncertainty() == 0 && question.getThreshold() == 0.5` means that if a provider returns a raw `Boolean` (no probability) but the question declares e.g. `threshold: 0.7`, the result falls through to the `throw new IllegalArgumentException(...)` at the bottom — even though the value is perfectly valid. The threshold/uncertainty policy is only meaningful when the provider supplies a `probability`. A provider returning a direct boolean decision should be accepted unconditionally when no probability is present:
   
   ```suggestion
                   if (value instanceof Boolean boolValue) {
                       return boolValue;
                   }
   ```



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@camel.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] CAMEL-24977: Add semantic evaluation and Choice selectors [camel]

Posted by "luigidemasi (via GitHub)" <gi...@apache.org>.
luigidemasi commented on code in PR #26813:
URL: https://github.com/apache/camel/pull/26813#discussion_r4090769151


##########
components/camel-ai/camel-semantic/src/main/java/org/apache/camel/semantic/SemanticResult.java:
##########
@@ -0,0 +1,108 @@
+/*
+ * 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;
+
+import java.util.Map;
+
+/**
+ * A provider answer. Value is Boolean, a category String, or a numeric score. A boolean provider may supply only
+ * probability; the question then defines the decision policy. Missing probabilities/confidence remain absent. Metadata
+ * may contain provider/model identity, revision and usage. It must not contain credentials or input state.
+ */
+public final class SemanticResult {
+    private final Object value;
+    private final Double probability;
+    private final Map<String, Double> probabilities;
+    private final Double confidence;
+    private final Map<String, Object> metadata;
+
+    public SemanticResult(Object value, Double probability, Map<String, Double> probabilities,
+                          Double confidence, Map<String, Object> metadata) {
+        this.value = value;
+        this.probability = probability;
+        this.probabilities = probabilities == null ? Map.of() : Map.copyOf(probabilities);
+        this.confidence = confidence;
+        this.metadata = metadata == null ? Map.of() : Map.copyOf(metadata);
+        checkProbability(probability);
+        checkProbability(confidence);
+        this.probabilities.values().forEach(SemanticResult::checkProbability);
+    }
+
+    private static void checkProbability(Double value) {
+        if (value != null && (!Double.isFinite(value) || value < 0 || value > 1)) {
+            throw new IllegalArgumentException("Semantic probabilities and confidence must be within [0,1]");
+        }
+    }
+
+    public Object decision(SemanticQuestion question) {
+        switch (question.getType()) {
+            case BOOLEAN:
+                if (probability != null) {
+                    double threshold = question.getThreshold();
+                    double uncertainty = question.getUncertainty();
+                    if (uncertainty > 0 && probability >= threshold - uncertainty && probability <= threshold + uncertainty) {
+                        if (question.getUncertaintyPolicy() == SemanticQuestion.UncertaintyPolicy.FAIL) {
+                            throw new IllegalStateException("Semantic boolean decision is uncertain");
+                        }
+                        return false;
+                    }
+                    return probability >= threshold;
+                }
+                if (value instanceof Boolean && question.getUncertainty() == 0 && question.getThreshold() == 0.5) {

Review Comment:
   Thanks for raising the distinction between a provider decision and a probability. We are retaining the current behavior: Camel owns and applies the configured threshold and uncertainty policy. For a question with `threshold: 0.7`, the adapter must return a probability so Camel can enforce that rule. A bare Boolean cannot establish whether the result meets that threshold or falls inside an uncertainty band; accepting it unconditionally would leave the configured policy unenforced by Camel.
   
   Boolean-only results remain supported with the default policy (`threshold: 0.5`, no uncertainty band). An adapter could apply a custom policy internally, but accepting that final Boolean would be a different contract that delegates policy enforcement to the adapter. This implementation keeps that responsibility in Camel.
   
   Added `booleanOnlyProviderCannotSilentlyIgnoreProbabilityPolicy` in [0c5ba894fe80](https://github.com/apache/camel/commit/0c5ba894fe801efd3c184fa6f574394fc2d003ce) to cover acceptance under the default policy and rejection with a custom threshold or uncertainty band. The documented behavior is unchanged.
   
   _AI-generated by Codex on behalf of [luigidemasi](https://github.com/luigidemasi)._



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@camel.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] CAMEL-24977: Add provider-independent semantic evaluation [camel]

Posted by "luigidemasi (via GitHub)" <gi...@apache.org>.
luigidemasi commented on code in PR #26813:
URL: https://github.com/apache/camel/pull/26813#discussion_r4093267417


##########
dsl/camel-yaml-dsl/camel-yaml-dsl-maven-plugin/src/main/java/org/apache/camel/maven/dsl/yaml/GenerateYamlCompletionMojo.java:
##########
@@ -418,7 +426,8 @@ private void enrichNodeMetadata(ObjectNode node, String nodeName, String fqName,
         }
 
         // check for language model
-        LanguageModel langModel = catalog.languageModel(nodeName);
+        LanguageModel langModel = fqName.startsWith("org.apache.camel.model.language.")
+                ? catalog.languageModel(nodeName) : null;

Review Comment:
   Addressed in `9ac532851f76`. Kept the metadata distinction and added YamlCompletionTreeTest.beanEipMetadataIsNotOverwrittenByBeanLanguage. It pins the Bean EIP title, description and labels and verifies that the expression's Bean Method metadata is retained. The PR description explains both this existing collision fix and why semantic declarations must not receive language metadata.
   
   _AI-generated by Codex on behalf of [luigidemasi](https://github.com/luigidemasi)._



##########
components/camel-ai/camel-semantic/src/main/java/org/apache/camel/semantic/yaml/SemanticDefinitionDeserializer.java:
##########
@@ -0,0 +1,201 @@
+/*
+ * 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.yaml;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.dsl.yaml.common.YamlDeserializationContext;
+import org.apache.camel.dsl.yaml.common.YamlDeserializerResolver;
+import org.apache.camel.dsl.yaml.common.YamlDeserializerSupport;
+import org.apache.camel.dsl.yaml.common.exception.YamlDeserializationException;
+import org.apache.camel.semantic.SemanticQuestion;
+import org.apache.camel.semantic.SemanticQuestions;
+import org.apache.camel.spi.CamelContextCustomizer;
+import org.apache.camel.spi.annotations.YamlIn;
+import org.apache.camel.spi.annotations.YamlProperty;
+import org.apache.camel.spi.annotations.YamlType;
+import org.snakeyaml.engine.v2.api.ConstructNode;
+import org.snakeyaml.engine.v2.nodes.Node;
+import org.snakeyaml.engine.v2.nodes.NodeTuple;
+import org.snakeyaml.engine.v2.nodes.SequenceNode;
+
+/** Named semantic declarations are installed in a resource-wide pass before route references are resolved. */
+@YamlIn
+@YamlType(nodes = "semantic", properties = {
+        @YamlProperty(name = "question",
+                      type = "map:org.apache.camel.semantic.yaml.SemanticDefinitionDeserializer$QuestionSchema",
+                      required = true)
+})
+public class SemanticDefinitionDeserializer extends YamlDeserializerSupport implements ConstructNode, YamlDeserializerResolver {
+    private static final Set<String> FIELDS
+            = Set.of("type", "instructions", "state", "criteria", "threshold", "uncertainty", "uncertaintyPolicy");
+
+    @Override
+    public ConstructNode resolve(String id) {
+        return "semantic".equals(id) ? this : null;
+    }
+
+    @Override
+    public Object construct(Node node) {
+        read(node);
+        // Registration happens once for the entire resource, including declarations after routes.
+        return (CamelContextCustomizer) context -> {
+        };
+    }
+
+    @Override
+    public void preParse(YamlDeserializationContext dc, Node root) {
+        if (!(root instanceof SequenceNode sequence)) {
+            return;
+        }
+        Map<String, SemanticQuestion> definitions = new LinkedHashMap<>();
+        for (Node node : sequence.getValue()) {
+            for (NodeTuple tuple : asMappingNode(node).getValue()) {
+                if ("semantic".equals(asText(tuple.getKeyNode()))) {
+                    read(tuple.getValueNode()).forEach((name, question) -> {
+                        if (definitions.putIfAbsent(name, question) != null) {
+                            throw new IllegalArgumentException("Duplicate semantic question: " + name);
+                        }
+                    });
+                }
+            }
+        }
+        CamelContext context = dc.getCamelContext();
+        SemanticQuestions questions = definitions.isEmpty()
+                ? context.getCamelContextExtension().getContextPlugin(SemanticQuestions.class)
+                : SemanticQuestions.get(context);
+        if (questions != null) {
+            questions.replace(dc.getResource(), definitions);
+        }
+    }
+
+    private static Map<String, SemanticQuestion> read(Node node) {
+        Map<String, Node> semantic = fields(node);
+        if (!semantic.keySet().equals(Set.of("question"))) {
+            throw new IllegalArgumentException("Semantic declaration requires only question");
+        }
+        Map<String, SemanticQuestion> result = new LinkedHashMap<>();
+        fields(semantic.get("question")).forEach((name, definition) -> {
+            Map<String, Node> values = fields(definition);
+            if (!FIELDS.containsAll(values.keySet())) {
+                throw new IllegalArgumentException("Unknown property in semantic question: " + name);
+            }
+            String typeName = asText(values.get("type"));
+            if (typeName == null) {
+                throw new IllegalArgumentException("Semantic question type is required: " + name);
+            }
+            SemanticQuestion.Type type = SemanticQuestion.Type.valueOf(typeName.toUpperCase(Locale.ROOT));

Review Comment:
   Addressed in `9ac532851f76`. The parser now delegates to YamlDeserializerSupport.asEnum. InvalidEnumException is wrapped at the same YAML value node to retain the question and field names along with the location and original cause. Regression tests cover invalid and empty values.
   
   _AI-generated by Codex on behalf of [luigidemasi](https://github.com/luigidemasi)._



##########
components/camel-ai/camel-semantic/src/main/java/org/apache/camel/semantic/yaml/SemanticDefinitionDeserializer.java:
##########
@@ -0,0 +1,201 @@
+/*
+ * 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.yaml;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.dsl.yaml.common.YamlDeserializationContext;
+import org.apache.camel.dsl.yaml.common.YamlDeserializerResolver;
+import org.apache.camel.dsl.yaml.common.YamlDeserializerSupport;
+import org.apache.camel.dsl.yaml.common.exception.YamlDeserializationException;
+import org.apache.camel.semantic.SemanticQuestion;
+import org.apache.camel.semantic.SemanticQuestions;
+import org.apache.camel.spi.CamelContextCustomizer;
+import org.apache.camel.spi.annotations.YamlIn;
+import org.apache.camel.spi.annotations.YamlProperty;
+import org.apache.camel.spi.annotations.YamlType;
+import org.snakeyaml.engine.v2.api.ConstructNode;
+import org.snakeyaml.engine.v2.nodes.Node;
+import org.snakeyaml.engine.v2.nodes.NodeTuple;
+import org.snakeyaml.engine.v2.nodes.SequenceNode;
+
+/** Named semantic declarations are installed in a resource-wide pass before route references are resolved. */
+@YamlIn
+@YamlType(nodes = "semantic", properties = {
+        @YamlProperty(name = "question",
+                      type = "map:org.apache.camel.semantic.yaml.SemanticDefinitionDeserializer$QuestionSchema",
+                      required = true)
+})
+public class SemanticDefinitionDeserializer extends YamlDeserializerSupport implements ConstructNode, YamlDeserializerResolver {
+    private static final Set<String> FIELDS
+            = Set.of("type", "instructions", "state", "criteria", "threshold", "uncertainty", "uncertaintyPolicy");
+
+    @Override
+    public ConstructNode resolve(String id) {
+        return "semantic".equals(id) ? this : null;
+    }
+
+    @Override
+    public Object construct(Node node) {
+        read(node);
+        // Registration happens once for the entire resource, including declarations after routes.
+        return (CamelContextCustomizer) context -> {
+        };
+    }
+
+    @Override
+    public void preParse(YamlDeserializationContext dc, Node root) {
+        if (!(root instanceof SequenceNode sequence)) {
+            return;
+        }
+        Map<String, SemanticQuestion> definitions = new LinkedHashMap<>();
+        for (Node node : sequence.getValue()) {
+            for (NodeTuple tuple : asMappingNode(node).getValue()) {
+                if ("semantic".equals(asText(tuple.getKeyNode()))) {
+                    read(tuple.getValueNode()).forEach((name, question) -> {
+                        if (definitions.putIfAbsent(name, question) != null) {
+                            throw new IllegalArgumentException("Duplicate semantic question: " + name);
+                        }
+                    });
+                }
+            }
+        }
+        CamelContext context = dc.getCamelContext();
+        SemanticQuestions questions = definitions.isEmpty()
+                ? context.getCamelContextExtension().getContextPlugin(SemanticQuestions.class)
+                : SemanticQuestions.get(context);
+        if (questions != null) {
+            questions.replace(dc.getResource(), definitions);
+        }
+    }
+
+    private static Map<String, SemanticQuestion> read(Node node) {
+        Map<String, Node> semantic = fields(node);
+        if (!semantic.keySet().equals(Set.of("question"))) {
+            throw new IllegalArgumentException("Semantic declaration requires only question");
+        }
+        Map<String, SemanticQuestion> result = new LinkedHashMap<>();
+        fields(semantic.get("question")).forEach((name, definition) -> {
+            Map<String, Node> values = fields(definition);
+            if (!FIELDS.containsAll(values.keySet())) {
+                throw new IllegalArgumentException("Unknown property in semantic question: " + name);
+            }
+            String typeName = asText(values.get("type"));
+            if (typeName == null) {
+                throw new IllegalArgumentException("Semantic question type is required: " + name);
+            }
+            SemanticQuestion.Type type = SemanticQuestion.Type.valueOf(typeName.toUpperCase(Locale.ROOT));
+            Map<String, String> criteria = new LinkedHashMap<>();
+            List<String> levels = List.of();
+            if (values.containsKey("criteria")) {
+                if (type == SemanticQuestion.Type.SCORE) {
+                    levels = asSequenceNode(values.get("criteria")).getValue().stream().map(YamlDeserializerSupport::asText)
+                            .toList();
+                } else {
+                    fields(values.get("criteria")).forEach((key, value) -> criteria.put(key, asText(value)));
+                }
+            }
+            if (type != SemanticQuestion.Type.BOOLEAN && (values.containsKey("threshold") || values.containsKey("uncertainty")
+                    || values.containsKey("uncertaintyPolicy"))) {
+                throw new IllegalArgumentException("Threshold and uncertainty policy require a boolean question: " + name);
+            }
+            SemanticQuestion.UncertaintyPolicy policy = values.containsKey("uncertaintyPolicy")
+                    ? SemanticQuestion.UncertaintyPolicy
+                            .valueOf(asText(values.get("uncertaintyPolicy")).replace('-', '_').toUpperCase(Locale.ROOT))
+                    : SemanticQuestion.UncertaintyPolicy.FAIL;

Review Comment:
   Addressed in `9ac532851f76`. Uncertainty policy now uses the common asEnum helper, and the Locale import and custom normalization are removed. Tests verify the standard YAML parser's dash, enum-name and camelCase spellings. The schema continues to advertise the canonical non-match spelling.
   
   _AI-generated by Codex on behalf of [luigidemasi](https://github.com/luigidemasi)._



##########
components/camel-ai/camel-semantic/src/main/java/org/apache/camel/semantic/yaml/SemanticDefinitionDeserializer.java:
##########
@@ -0,0 +1,201 @@
+/*
+ * 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.yaml;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.dsl.yaml.common.YamlDeserializationContext;
+import org.apache.camel.dsl.yaml.common.YamlDeserializerResolver;
+import org.apache.camel.dsl.yaml.common.YamlDeserializerSupport;
+import org.apache.camel.dsl.yaml.common.exception.YamlDeserializationException;
+import org.apache.camel.semantic.SemanticQuestion;
+import org.apache.camel.semantic.SemanticQuestions;
+import org.apache.camel.spi.CamelContextCustomizer;
+import org.apache.camel.spi.annotations.YamlIn;
+import org.apache.camel.spi.annotations.YamlProperty;
+import org.apache.camel.spi.annotations.YamlType;
+import org.snakeyaml.engine.v2.api.ConstructNode;
+import org.snakeyaml.engine.v2.nodes.Node;
+import org.snakeyaml.engine.v2.nodes.NodeTuple;
+import org.snakeyaml.engine.v2.nodes.SequenceNode;
+
+/** Named semantic declarations are installed in a resource-wide pass before route references are resolved. */
+@YamlIn
+@YamlType(nodes = "semantic", properties = {
+        @YamlProperty(name = "question",
+                      type = "map:org.apache.camel.semantic.yaml.SemanticDefinitionDeserializer$QuestionSchema",
+                      required = true)
+})
+public class SemanticDefinitionDeserializer extends YamlDeserializerSupport implements ConstructNode, YamlDeserializerResolver {
+    private static final Set<String> FIELDS
+            = Set.of("type", "instructions", "state", "criteria", "threshold", "uncertainty", "uncertaintyPolicy");
+
+    @Override
+    public ConstructNode resolve(String id) {
+        return "semantic".equals(id) ? this : null;
+    }
+
+    @Override
+    public Object construct(Node node) {
+        read(node);
+        // Registration happens once for the entire resource, including declarations after routes.
+        return (CamelContextCustomizer) context -> {
+        };
+    }
+
+    @Override
+    public void preParse(YamlDeserializationContext dc, Node root) {
+        if (!(root instanceof SequenceNode sequence)) {
+            return;
+        }
+        Map<String, SemanticQuestion> definitions = new LinkedHashMap<>();
+        for (Node node : sequence.getValue()) {
+            for (NodeTuple tuple : asMappingNode(node).getValue()) {
+                if ("semantic".equals(asText(tuple.getKeyNode()))) {
+                    read(tuple.getValueNode()).forEach((name, question) -> {
+                        if (definitions.putIfAbsent(name, question) != null) {
+                            throw new IllegalArgumentException("Duplicate semantic question: " + name);
+                        }
+                    });
+                }
+            }
+        }
+        CamelContext context = dc.getCamelContext();
+        SemanticQuestions questions = definitions.isEmpty()
+                ? context.getCamelContextExtension().getContextPlugin(SemanticQuestions.class)
+                : SemanticQuestions.get(context);
+        if (questions != null) {
+            questions.replace(dc.getResource(), definitions);
+        }
+    }
+
+    private static Map<String, SemanticQuestion> read(Node node) {
+        Map<String, Node> semantic = fields(node);
+        if (!semantic.keySet().equals(Set.of("question"))) {
+            throw new IllegalArgumentException("Semantic declaration requires only question");
+        }
+        Map<String, SemanticQuestion> result = new LinkedHashMap<>();
+        fields(semantic.get("question")).forEach((name, definition) -> {
+            Map<String, Node> values = fields(definition);
+            if (!FIELDS.containsAll(values.keySet())) {
+                throw new IllegalArgumentException("Unknown property in semantic question: " + name);
+            }
+            String typeName = asText(values.get("type"));
+            if (typeName == null) {
+                throw new IllegalArgumentException("Semantic question type is required: " + name);
+            }
+            SemanticQuestion.Type type = SemanticQuestion.Type.valueOf(typeName.toUpperCase(Locale.ROOT));
+            Map<String, String> criteria = new LinkedHashMap<>();
+            List<String> levels = List.of();
+            if (values.containsKey("criteria")) {
+                if (type == SemanticQuestion.Type.SCORE) {
+                    levels = asSequenceNode(values.get("criteria")).getValue().stream().map(YamlDeserializerSupport::asText)
+                            .toList();
+                } else {
+                    fields(values.get("criteria")).forEach((key, value) -> criteria.put(key, asText(value)));
+                }
+            }
+            if (type != SemanticQuestion.Type.BOOLEAN && (values.containsKey("threshold") || values.containsKey("uncertainty")
+                    || values.containsKey("uncertaintyPolicy"))) {
+                throw new IllegalArgumentException("Threshold and uncertainty policy require a boolean question: " + name);
+            }
+            SemanticQuestion.UncertaintyPolicy policy = values.containsKey("uncertaintyPolicy")
+                    ? SemanticQuestion.UncertaintyPolicy
+                            .valueOf(asText(values.get("uncertaintyPolicy")).replace('-', '_').toUpperCase(Locale.ROOT))
+                    : SemanticQuestion.UncertaintyPolicy.FAIL;
+            result.put(name, new SemanticQuestion(

Review Comment:
   Addressed in `7aa7aed20d23`. Constructor validation is now wrapped with the question name and definition node. The diagnostic tests cover blank instructions and assert the source location; the numeric parsing cause remains intact as well.
   
   _AI-generated by Codex on behalf of [luigidemasi](https://github.com/luigidemasi)._



##########
components/camel-ai/camel-semantic/src/main/java/org/apache/camel/semantic/yaml/SemanticDefinitionDeserializer.java:
##########
@@ -0,0 +1,201 @@
+/*
+ * 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.yaml;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.dsl.yaml.common.YamlDeserializationContext;
+import org.apache.camel.dsl.yaml.common.YamlDeserializerResolver;
+import org.apache.camel.dsl.yaml.common.YamlDeserializerSupport;
+import org.apache.camel.dsl.yaml.common.exception.YamlDeserializationException;
+import org.apache.camel.semantic.SemanticQuestion;
+import org.apache.camel.semantic.SemanticQuestions;
+import org.apache.camel.spi.CamelContextCustomizer;
+import org.apache.camel.spi.annotations.YamlIn;
+import org.apache.camel.spi.annotations.YamlProperty;
+import org.apache.camel.spi.annotations.YamlType;
+import org.snakeyaml.engine.v2.api.ConstructNode;
+import org.snakeyaml.engine.v2.nodes.Node;
+import org.snakeyaml.engine.v2.nodes.NodeTuple;
+import org.snakeyaml.engine.v2.nodes.SequenceNode;
+
+/** Named semantic declarations are installed in a resource-wide pass before route references are resolved. */
+@YamlIn
+@YamlType(nodes = "semantic", properties = {
+        @YamlProperty(name = "question",
+                      type = "map:org.apache.camel.semantic.yaml.SemanticDefinitionDeserializer$QuestionSchema",
+                      required = true)
+})
+public class SemanticDefinitionDeserializer extends YamlDeserializerSupport implements ConstructNode, YamlDeserializerResolver {
+    private static final Set<String> FIELDS
+            = Set.of("type", "instructions", "state", "criteria", "threshold", "uncertainty", "uncertaintyPolicy");
+
+    @Override
+    public ConstructNode resolve(String id) {
+        return "semantic".equals(id) ? this : null;
+    }
+
+    @Override
+    public Object construct(Node node) {
+        read(node);
+        // Registration happens once for the entire resource, including declarations after routes.
+        return (CamelContextCustomizer) context -> {
+        };
+    }
+
+    @Override
+    public void preParse(YamlDeserializationContext dc, Node root) {
+        if (!(root instanceof SequenceNode sequence)) {
+            return;
+        }
+        Map<String, SemanticQuestion> definitions = new LinkedHashMap<>();
+        for (Node node : sequence.getValue()) {
+            for (NodeTuple tuple : asMappingNode(node).getValue()) {

Review Comment:
   Addressed in `9ac532851f76`. A non-mapping top-level entry now returns control to the normal route loader without publishing replacement questions. Returning instead of continuing prevents a malformed reload from clearing or changing existing definitions. Four regression cases cover scalar/sequence entries both alone and following changed semantic declarations, and assert the normal loader error plus retention of the original question instance.
   
   _AI-generated by Codex on behalf of [luigidemasi](https://github.com/luigidemasi)._



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@camel.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] CAMEL-24977: Add provider-independent semantic evaluation [camel]

Posted by "luigidemasi (via GitHub)" <gi...@apache.org>.
luigidemasi commented on PR #26813:
URL: https://github.com/apache/camel/pull/26813#issuecomment-5813554800

   @davsclaus, on the TUI completion context format in your [review](https://github.com/apache/camel/pull/26813#pullrequestreview-5302620572): the PR description now explicitly calls out the full ancestor path with individually URL-encoded keys. `9ac532851f76` documents the format in `SourceViewer` and `SourceEditAssist` and adds tests derived from real editor state for ordinary route key/value completion and semantic question names containing a slash. The 75 YAML/semantic completion tests pass.
   
   _AI-generated by Codex on behalf of [luigidemasi](https://github.com/luigidemasi)._


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@camel.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] CAMEL-24977: Add provider-independent semantic evaluation [camel]

Posted by "luigidemasi (via GitHub)" <gi...@apache.org>.
luigidemasi commented on code in PR #26813:
URL: https://github.com/apache/camel/pull/26813#discussion_r4091903158


##########
components/camel-ai/camel-semantic/src/main/java/org/apache/camel/language/semantic/SemanticLanguageConfigurer.java:
##########
@@ -0,0 +1,40 @@
+/*
+ * 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.language.semantic;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.spi.PropertyConfigurer;
+
+/** Preserves adapter references so the language can validate their type and manage only instances it creates. */
+public class SemanticLanguageConfigurer implements PropertyConfigurer {
+    @Override
+    public boolean configureRaw(CamelContext context, Object target, String name, Object value, boolean ignoreCase) {
+        if (ignoreCase ? "adapter".equalsIgnoreCase(name) : "adapter".equals(name)) {
+            if (!(value instanceof String)) {
+                throw new IllegalArgumentException("Semantic adapter selection must be a bean reference or class name");
+            }
+            ((SemanticLanguage) target).setAdapter((String) value);
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public boolean configure(CamelContext context, Object target, String name, Object value, boolean ignoreCase) {

Review Comment:
   Removed in 7c10c75e1bf5 together with both service registrations. The language uses standard property binding for `adapter` and `defaultState`; Camel Main tests cover bean names, class names and property placeholders.
   
   _AI-generated by Codex on behalf of [luigidemasi](https://github.com/luigidemasi)._



##########
components/camel-ai/camel-semantic/src/main/java/org/apache/camel/semantic/yaml/SemanticDefinitionDeserializer.java:
##########
@@ -0,0 +1,188 @@
+/*
+ * 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.dsl.yaml.deserializers;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.dsl.yaml.common.YamlDeserializationContext;
+import org.apache.camel.dsl.yaml.common.YamlDeserializerSupport;
+import org.apache.camel.dsl.yaml.common.exception.YamlDeserializationException;
+import org.apache.camel.semantic.SemanticQuestion;
+import org.apache.camel.semantic.SemanticQuestions;
+import org.apache.camel.spi.CamelContextCustomizer;
+import org.apache.camel.spi.annotations.YamlIn;
+import org.apache.camel.spi.annotations.YamlProperty;
+import org.apache.camel.spi.annotations.YamlType;
+import org.snakeyaml.engine.v2.api.ConstructNode;
+import org.snakeyaml.engine.v2.nodes.Node;
+import org.snakeyaml.engine.v2.nodes.NodeTuple;
+import org.snakeyaml.engine.v2.nodes.SequenceNode;
+
+/** Named semantic declarations are installed in a resource-wide pass before route references are resolved. */
+@YamlIn
+@YamlType(nodes = "semantic", properties = {
+        @YamlProperty(name = "question",
+                      type = "map:org.apache.camel.dsl.yaml.deserializers.SemanticDefinitionDeserializer$QuestionSchema",
+                      required = true)
+})
+public class SemanticDefinitionDeserializer extends YamlDeserializerSupport implements ConstructNode {
+    private static final Set<String> FIELDS
+            = Set.of("type", "instructions", "state", "criteria", "threshold", "uncertainty", "uncertaintyPolicy");
+
+    @Override
+    public Object construct(Node node) {
+        read(node);
+        // Registration happens once for the entire resource, including declarations after routes.
+        return (CamelContextCustomizer) context -> {
+        };
+    }
+
+    public static void configure(CamelContext context, YamlDeserializationContext dc, Node root) {
+        if (!(root instanceof SequenceNode sequence)) {
+            return;
+        }
+        Map<String, SemanticQuestion> definitions = new LinkedHashMap<>();
+        for (Node node : sequence.getValue()) {
+            for (NodeTuple tuple : asMappingNode(node).getValue()) {
+                if ("semantic".equals(asText(tuple.getKeyNode()))) {
+                    read(tuple.getValueNode()).forEach((name, question) -> {
+                        if (definitions.putIfAbsent(name, question) != null) {
+                            throw new IllegalArgumentException("Duplicate semantic question: " + name);
+                        }
+                    });
+                }
+            }
+        }
+        SemanticQuestions.get(context).replace(dc.getResource(), definitions);
+    }
+
+    private static Map<String, SemanticQuestion> read(Node node) {
+        Map<String, Node> semantic = fields(node);
+        if (!semantic.keySet().equals(Set.of("question"))) {
+            throw new IllegalArgumentException("Semantic declaration requires only question");
+        }
+        Map<String, SemanticQuestion> result = new LinkedHashMap<>();
+        fields(semantic.get("question")).forEach((name, definition) -> {
+            Map<String, Node> values = fields(definition);
+            if (!FIELDS.containsAll(values.keySet())) {
+                throw new IllegalArgumentException("Unknown property in semantic question: " + name);
+            }
+            String typeName = asText(values.get("type"));
+            if (typeName == null) {
+                throw new IllegalArgumentException("Semantic question type is required: " + name);
+            }
+            SemanticQuestion.Type type = SemanticQuestion.Type.valueOf(typeName.toUpperCase(Locale.ROOT));

Review Comment:
   The diagnostic gap is valid and remains open in this update; moving the parser into `camel-semantic` does not fix it. I reproduced unlocated enum failures. One detail differs from the report: an empty policy scalar produces an `IllegalArgumentException` from enum parsing, rather than an NPE, because `asText` returns an empty string. Located diagnostics for enum and structural errors still need follow-up.
   
   _AI-generated by Codex on behalf of [luigidemasi](https://github.com/luigidemasi)._



##########
components/camel-ai/camel-semantic/src/main/java/org/apache/camel/semantic/SemanticQuestions.java:
##########
@@ -0,0 +1,88 @@
+/*
+ * 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;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.spi.Resource;
+
+/** Context-local named questions, replaced atomically per source when a route resource is reloaded. */
+public final class SemanticQuestions {
+    private final Map<String, Map<String, SemanticQuestion>> sources = new HashMap<>();
+    private final Map<String, Resource> resources = new HashMap<>();
+    private volatile Map<String, SemanticQuestion> questions = Map.of();
+
+    public static SemanticQuestions get(CamelContext context) {
+        synchronized (context) {

Review Comment:
   Agreed that private coordination is preferable. This remains open in this update. A replacement must still coordinate separate language instances sharing one context/registry; simply changing both monitors to per-instance locks would lose that property. No actual deadlock was established during this assessment.
   
   _AI-generated by Codex on behalf of [luigidemasi](https://github.com/luigidemasi)._



##########
components/camel-ai/camel-semantic/src/main/java/org/apache/camel/language/semantic/SemanticLanguage.java:
##########
@@ -0,0 +1,307 @@
+/*
+ * 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.language.semantic;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.CamelContextAware;
+import org.apache.camel.Exchange;
+import org.apache.camel.Expression;
+import org.apache.camel.Predicate;
+import org.apache.camel.RuntimeCamelException;
+import org.apache.camel.semantic.SemanticAdapter;
+import org.apache.camel.semantic.SemanticQuestion;
+import org.apache.camel.semantic.SemanticQuestions;
+import org.apache.camel.semantic.SemanticResult;
+import org.apache.camel.spi.Metadata;
+import org.apache.camel.spi.annotations.Language;
+import org.apache.camel.support.ExpressionAdapter;
+import org.apache.camel.support.LanguageSupport;
+import org.apache.camel.support.service.ServiceHelper;
+import org.apache.camel.support.service.ServiceSupport;
+
+/** Evaluates a named, provider-independent question against selected message state. */
+@Language(value = "semantic", modelName = "language")
+@Metadata(title = "Semantic", description = "Evaluate named semantic questions through a provider adapter",
+          label = "language,ai", firstVersion = "4.23.0")
+public class SemanticLanguage extends LanguageSupport {
+    public static final String RESULT = "CamelSemanticResult";
+    public static final String ADAPTER_NAME = "camelSemanticAdapter";
+    public static final String ADAPTER_RESOURCE = "META-INF/services/org.apache.camel.semantic.SemanticAdapter";
+
+    private String adapter;
+    private String defaultState = "${body}";
+    private SemanticAdapter selectedAdapter;
+
+    public String getAdapter() {
+        return adapter;
+    }
+
+    /**
+     * Adapter registry reference (#bean:name) or implementation class (plain FQCN or #class:FQCN). Absent selects the
+     * sole advertised adapter.
+     */
+    public void setAdapter(String adapter) {
+        this.adapter = adapter;
+    }
+
+    public String getDefaultState() {
+        return defaultState;
+    }
+
+    /** Default Simple state selector for questions without their own selector. Defaults to the body. */
+    public void setDefaultState(String defaultState) {
+        this.defaultState = defaultState;
+    }
+
+    @Override
+    public Expression createExpression(String expression) {
+        return createEvaluation(expression, false);
+    }
+
+    @Override
+    public Predicate createPredicate(String expression) {
+        return createEvaluation(expression, true);
+    }
+
+    public boolean validateExpression(String expression) {
+        if (expression == null || !expression.startsWith("ref:") || expression.substring(4).isBlank()) {
+            throw new IllegalArgumentException("Semantic expression must reference a named question using ref:name");
+        }
+        return true;
+    }
+
+    public boolean validatePredicate(String expression) {
+        return validateExpression(expression);
+    }
+
+    private Evaluation createEvaluation(String expression, boolean predicate) {
+        validateExpression(expression);
+        Evaluation evaluation = new Evaluation(expression.substring(4), predicate);
+        if (getCamelContext() != null) {
+            evaluation.init(getCamelContext());
+        }
+        return evaluation;
+    }
+
+    private synchronized SemanticAdapter adapter() {
+        if (selectedAdapter != null) {
+            return selectedAdapter;
+        }
+        CamelContext context = getCamelContext();
+        String configured = adapter == null ? null : context.resolvePropertyPlaceholders(adapter);
+        if (configured != null && configured.startsWith("#") && !configured.startsWith("#class:")) {
+            String name = configured.startsWith("#bean:") ? configured.substring(6) : configured.substring(1);
+            if (name.contains(":")) {
+                throw new IllegalArgumentException("Semantic adapter reference must use #bean:name, #name or #class:FQCN");
+            }
+            Object bean = context.getRegistry().lookupByName(name);
+            if (!(bean instanceof SemanticAdapter found)) {
+                throw new IllegalArgumentException("Semantic adapter bean is missing or does not implement SemanticAdapter");
+            }
+            selectedAdapter = found;
+            return found;
+        }
+        ManagedAdapter owned = null;
+        try {
+            String className = configured;
+            if (className == null) {
+                Set<String> candidates = new TreeSet<>();
+                Enumeration<URL> resources = context.getClassResolver().loadAllResourcesAsURL(ADAPTER_RESOURCE);

Review Comment:
   The discovery implementation is unchanged in this update. `FactoryFinder.findClass` reads a single Camel `class=...` resource; it does not directly preserve enumeration, deduplication and ambiguity rejection across the current provider descriptors. A replacement needs to retain those semantics. Native-image discovery has not been validated, so that compatibility concern remains open.
   
   _AI-generated by Codex on behalf of [luigidemasi](https://github.com/luigidemasi)._



##########
core/camel-core-reifier/src/main/java/org/apache/camel/reifier/ChoiceReifier.java:
##########
@@ -49,6 +52,25 @@ public ChoiceReifier(Route route, ProcessorDefinition<?> definition) {
     @Override
     public Processor createProcessor() throws Exception {
         final boolean isPrecondition = Boolean.TRUE == parseBoolean(definition.getPrecondition());
+        boolean selecting = definition.getSelector() != null;
+        if (selecting && isPrecondition) {
+            throw new IllegalArgumentException("Choice selector cannot be combined with precondition mode");
+        }
+        Set<String> uniqueValues = new HashSet<>();
+        for (WhenDefinition when : definition.getWhenClauses()) {
+            if (selecting) {
+                if (when.getValue() == null || when.getExpression() != null) {
+                    throw new IllegalArgumentException("Choice selector requires literal when values without predicates");
+                }
+                if (!uniqueValues.add(parseString(when.getValue()))) {
+                    throw new IllegalArgumentException("Choice selector has duplicate when values");
+                }
+            } else if (when.getValue() != null || when.getExpression() == null) {
+                throw new IllegalArgumentException("Choice without a selector requires when predicates without values");
+            }
+        }
+        Expression selector = selecting ? createExpression(definition.getSelector().getExpressionType()) : null;

Review Comment:
   Removed in 7c10c75e1bf5. `ChoiceReifier` matches the original base and no longer accepts or dereferences a selector. Semantic category routing uses existing Set Property and Choice expressions.
   
   _AI-generated by Codex on behalf of [luigidemasi](https://github.com/luigidemasi)._



##########
core/camel-management/src/main/java/org/apache/camel/management/mbean/ManagedChoice.java:
##########
@@ -76,8 +76,9 @@ public TabularData extendedInformation() {
                 FilterProcessor filter = filters.get(i);
 
                 CompositeType ct = CamelOpenMBeanTypes.choiceCompositeType();
-                String predicate = when.getExpression().getExpression();
-                String language = when.getExpression().getLanguage();
+                String predicate = when.getValue() != null ? when.getValue() : when.getExpression().getExpression();
+                String language = getDefinition().getSelector() != null
+                        ? getDefinition().getSelector().getExpressionType().getLanguage() : when.getExpression().getLanguage();

Review Comment:
   Removed in 7c10c75e1bf5. `ManagedChoice` matches the original base; its JMX path no longer contains selector handling.
   
   _AI-generated by Codex on behalf of [luigidemasi](https://github.com/luigidemasi)._



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@camel.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] CAMEL-24977: Add provider-independent semantic evaluation [camel]

Posted by "luigidemasi (via GitHub)" <gi...@apache.org>.
luigidemasi commented on code in PR #26813:
URL: https://github.com/apache/camel/pull/26813#discussion_r4114976325


##########
components/camel-ai/camel-semantic/src/main/docs/semantic-language.adoc:
##########
@@ -0,0 +1,358 @@
+= Semantic Evaluation Language
+:doctitle: Semantic Evaluation
+:shortname: semantic
+:artifactid: camel-semantic
+:description: Evaluate named questions about message content to produce boolean decisions, categories and scores through provider adapters
+:since: 4.23
+:supportlevel: Preview
+:tabs-sync-option:
+
+*Since Camel {since}*
+
+The Semantic language evaluates named questions through a provider-independent adapter.
+Boolean questions produce decisions, choice questions produce category strings, and score
+questions produce numbers on an ordered rubric. Calls are synchronous and may block while
+inference runs. Provider errors propagate through normal Camel error handling.
+
+== Dependencies and providers
+
+Add `org.apache.camel:camel-semantic` and a provider, such as
+xref:ROOT:typesafe-ai-component.adoc[TypeSafe AI] (`camel-typesafe-ai`), using the same Camel version.
+The provider owns credentials, model selection, request timeout,
+concurrency limits and transport resources. TypeSafe AI uses
+`camel.component.typesafe-ai.*` settings even when a route contains no TypeSafe AI endpoint.
+
+One advertised adapter is selected automatically. No provider, or multiple distinct
+providers, is an error. Select an existing bean with
+`camel.language.semantic.adapter=myAdapter`, or a class using its fully qualified name.
+Use plain names without `#bean:` or `#class:` prefixes. Registry lookup takes precedence over
+class resolution. Class selection uses Camel's class resolver and injector.
+Created adapters are registered as `camelSemanticAdapter` and managed by the context.
+A collision at that name is an error. Referenced beans retain their existing lifecycle owner.
+
+Camel Main and Camel JBang bind `camel.language.semantic.adapter` and
+`camel.language.semantic.default-state` to the language. Embedded applications can resolve
+`SemanticLanguage` from the context and call `setAdapter` and `setDefaultState` before route setup.
+The catalog describes the generic `language` expression model; it does not provide a dedicated
+semantic Spring Boot configuration class. Configure the language bean explicitly in Spring Boot
+until generic-language starter configuration is available.
+
+== Named questions
+
+The xref:others:yaml-dsl.adoc[YAML DSL] supports declarations alongside routes, including declarations after their use:
+
+[source,yaml]
+----
+- semantic:
+    question:
+      department:
+        type: choice
+        instructions: Which department should handle this message?
+        criteria:
+          billing: Invoices, payments and refunds
+          technical: Bugs, outages and technical problems
+      actionable:
+        type: boolean
+        instructions: Does this message contain an actionable request?
+        threshold: 0.8
+        uncertainty: 0.1
+        uncertaintyPolicy: fail
+      urgency:
+        type: score
+        instructions: Assess urgency
+        criteria:
+          - Routine request
+          - Time-sensitive request
+          - Immediate attention needed
+- route:
+    from:
+      uri: direct:tickets
+      steps:
+        - setProperty:
+            name: department
+            expression:
+              language:
+                language: semantic
+                expression: ref:department
+        - choice:
+            when:
+              - expression:
+                  simple:
+                    expression: "${exchangeProperty.department} == 'billing'"
+                steps:
+                  - to: direct:billing
+              - expression:
+                  simple:
+                    expression: "${exchangeProperty.department} == 'technical'"
+                steps:
+                  - to: direct:technical
+            otherwise:
+              steps:
+                - to: direct:review
+----
+
+Names are context-wide. Duplicate declarations across resources and unknown references fail.
+Reloading a resource replaces its complete set of questions, including removing declarations
+no longer present. Development-mode route reload also removes definitions from deleted or renamed files
+before parsing replacements. This replacement does not make the surrounding route reload transactional.
+Existing expressions resolve the current definition on their next evaluation.
+Loading declarations does not perform inference. Java applications can register immutable
+`SemanticQuestion` definitions using `SemanticQuestions.get(context).replace(source, questions)`.
+
+A question's optional `state` Simple expression overrides
+`camel.language.semantic.default-state`, whose default is `$\{body}`. Selectors are compiled
+before evaluation; selected strings, maps and lists are passed as data and are never evaluated recursively.
+A missing selected header fails instead of falling back to the body. Blank or invalid selectors
+fail. The original message is preserved. `CamelSemanticResult` contains the latest successful
+normalized result and is cleared before each evaluation, including one that fails.
+
+State must be a string, map or list. For byte arrays or stream bodies, explicitly select
+`$\{bodyAs(String)}`. Enable stream caching before evaluating a stream when later processors
+also need to read it. Unsupported state types fail instead of being implicitly converted.
+
+YAML declarations are provided by `camel-semantic` through the YAML deserializer resolver SPI.
+Include both `camel-semantic` and `camel-yaml-dsl` when using them. The YAML DSL does not pull
+in semantic evaluation, and Java applications using `camel-semantic` do not pull in the YAML DSL.
+
+== Results and policy
+
+Only boolean questions can be predicates. A category string is never implicitly a boolean.
+A probability-based boolean uses an inclusive threshold (default `0.5`). A nonzero
+`uncertainty` defines an inclusive band around that threshold. `fail` (the default) raises
+an error within the band; `non-match` returns false. An already-boolean provider supports the
+default policy without inventing a probability. Additional policy requirements must be
+supported by the provider. Choice results must name a declared criterion. Scores range from
+zero to the last rubric index and may be fractional.
+
+Probabilities, provider confidence and selected values are separate. Missing optional fields
+remain absent. Results can retain provider/model identity and usage metadata. These fields
+do not imply equivalent quality or calibration when switching providers. Timeouts, malformed
+answers and unsupported capabilities are errors, distinct from valid negative decisions or
+unmatched Choice results.
+
+== EIP integration
+
+Use `language("semantic", "ref:name")` wherever an expression or boolean predicate is accepted.
+
+[cols="1,3"]
+|===
+|EIP / integration point |Usage
+|Choice |Store a category with Set Property, then compare that property in ordinary when predicates.
+|Filter |Use a boolean question as the filter predicate.
+|Validate |Use a boolean question; false follows normal validation failure handling.
+|Set Header / Set Property |Store a category or score for explicit reuse in later steps.
+|Aggregate correlation |Use a category as a correlation expression, retaining tenant or case identifiers where needed.
+|Aggregate completion |Evaluate a boolean question against the accumulated body, with a size or timeout limit. `eagerCheckCompletion` instead sees the incoming exchange.
+|Recipient List |Map a category to a configured list of recipient URIs.
+|Routing Slip |Map a category to a predefined processing sequence.
+|Enrich |Map a category to a configured resource URI and use an ordinary aggregation strategy.
+|Loop |Reevaluate a boolean question on updated state, with an explicit iteration or time budget.
+|Sort |Score each item once, store the scores, then sort using a deterministic comparator.
+|On Exception / retryWhile |Evaluate whether another attempt is worthwhile, with an explicit retry budget checked before inference.
+|Contextual action validation |Use Validate after ordinary permission checks and before executing the action.
+|===
+
+Destination mappings belong to trusted route configuration; provider output should select
+known labels rather than supply unrestricted endpoint URIs. Expressions reevaluate on each
+invocation. There is no implicit exchange-wide inference cache. Store results explicitly
+when reuse is intended, and reevaluate after relevant input changes.
+
+=== Predicates and explicit reuse
+
+[source,java]
+----
+from("direct:actionable")
+    .filter().language("semantic", "ref:actionable")
+    .to("direct:accepted");
+
+from("direct:validate")
+    .validate().language("semantic", "ref:actionable")
+    .to("direct:valid");
+
+from("direct:tag")
+    .setHeader("department").language("semantic", "ref:department")
+    .setProperty("urgency").language("semantic", "ref:urgency")
+    .choice()
+        .when(header("department").isEqualTo("billing")).to("direct:billing")
+        .otherwise().to("direct:technical");
+----
+
+Storing the category before xref:eips:choice-eip.adoc[Choice] performs one semantic evaluation each time execution
+reaches that Set Header or Set Property step. The branches compare the stored result without
+calling the provider again. Place that step inside a loop when the decision must be refreshed
+on each iteration. Nested decisions can use separate properties to retain their own results.
+Boolean questions can also be used directly as ordinary when predicates. These patterns use
+the existing EIP model and work with the Java, XML and YAML DSLs.
+
+=== Aggregation and destinations
+
+For xref:eips:aggregate-eip.adoc[Aggregate] and other Java APIs accepting an `Expression`, use
+`new LanguageExpression("semantic", "ref:department")` from `org.apache.camel.model.language`.
+For example, group messages with that expression and a `GroupedBodyAggregationStrategy`,
+using `completionSize(10)` and `completionTimeout(5000)` to bound the group. Include a trusted
+tenant or case identifier in the correlation key when messages must remain isolated.
+
+A completion predicate can be obtained with
+`context.resolveLanguage("semantic").createPredicate("ref:actionable")`.
+The aggregation strategy must first put the accumulated conversation in the selected state.
+Use `completionPredicate(predicate).completionSize(10)` to retain a deterministic size limit.
+
+Classify once, then map the stored label to destinations supplied by the route author:
+
+[source,java]
+----
+from("direct:dispatch")
+    .setProperty("department").language("semantic", "ref:department")
+    .process(exchange -> {
+        String department = exchange.getProperty("department", String.class);
+        exchange.getMessage().setHeader("recipients",
+            Map.of("billing", "direct:billing,direct:audit",
+                   "technical", "direct:technical").get(department));
+        exchange.getMessage().setHeader("slip",
+            Map.of("billing", "direct:invoice,direct:archive",
+                   "technical", "direct:diagnose,direct:archive").get(department));
+        exchange.getMessage().setHeader("resource",
+            Map.of("billing", "direct:billingKnowledge",
+                   "technical", "direct:technicalKnowledge").get(department));
+    })
+    .recipientList(header("recipients")).end()
+    .routingSlip(header("slip"))
+    .enrich().header("resource").aggregationStrategy((original, resource) -> {
+        original.getMessage().setHeader("knowledge", resource.getMessage().getBody());
+        return original;
+    });
+----
+
+=== Changed state and sorting
+
+A loop must have a finite budget in addition to its semantic predicate. Combine the predicate
+with a check of `CamelLoopIndex`, and let the loop body update the selected state. Each new
+iteration evaluates the current content. For example, a predicate can first check that
+`exchange.getProperty(Exchange.LOOP_INDEX, 0, Integer.class) < 5` and then call the boolean
+semantic predicate, before `loopDoWhile` invokes a refinement processor.
+
+For sorting, evaluate `ref:urgency` once for each item and build a list containing each item and
+its score. Use `.sort(body(), Comparator.comparingDouble(ScoredItem::score))` with that stored
+score. The comparator must not call the provider: sorting can compare an item multiple times
+and in an implementation-dependent order.
+
+=== Bounded semantic retry
+
+A boolean question can supply the xref:manual::exception-advanced.adoc[retryWhile predicate]
+for an exception clause. Ask whether another attempt is worthwhile using the current failure
+and selected request context. Restrict this to operations that are safe to retry.
+
+IMPORTANT: `retryWhile` replaces the `maximumRedeliveries` decision. Check the retry budget
+inside the predicate, before calling the provider. A provider that always returns true must
+not cause unlimited retries or evaluations.
+
+Declare a boolean question named `retryable` with `state: $\{exchangeProperty.retryState}`.
+In this example, the request body is a string. `onExceptionOccurred` prepares the state before
+the retry predicate runs; `onRedelivery` runs later and is too late for this purpose.
+Include only the failure details needed by the question, excluding credentials and sensitive data.
+
+[source,java]
+----
+Predicate retryable = context.resolveLanguage("semantic").createPredicate("ref:retryable");
+
+onException(IOException.class)
+    .onExceptionOccurred(exchange -> {
+        Exception failure = exchange.getProperty(Exchange.EXCEPTION_CAUGHT, Exception.class);
+        exchange.setProperty("retryState", Map.of(
+            "request", exchange.getMessage().getBody(String.class),
+            "failureType", failure.getClass().getSimpleName(),
+            "attempt", exchange.getMessage().getHeader(Exchange.REDELIVERY_COUNTER, Integer.class)));
+    })
+    .retryWhile(exchange ->
+        exchange.getMessage().getHeader(Exchange.REDELIVERY_COUNTER, 0, Integer.class) <= 3
+            && retryable.matches(exchange))
+    .redeliveryDelay(1000)
+    .handled(true)
+    .to("direct:escalate");
+----
+
+The counter starts at one when deciding the first redelivery. This permits at most three
+redeliveries after the initial attempt. Each failure refreshes the selected state; redelivery
+restarts at the failed processor, not at the beginning of the route. A negative decision or
+an exhausted budget sends the message to `direct:escalate` through normal exception handling.
+
+Evaluation errors are distinct from a negative decision. If the retry predicate throws,
+Camel reports the evaluation failure on the exchange; it does not automatically execute
+the exception clause's escalation route. Arrange caller or supervising-route handling for
+that failure. Bound provider timeouts as well as the retry count.
+
+=== Contextual action validation
+
+Use a semantic question for an additional check such as "Does this proposed action serve
+the approved task?" after normal identity, permission and tenant checks have succeeded.
+The semantic decision must not grant permissions that those checks denied.
+
+For example, declare this question alongside routes:
+
+[source,yaml]
+----
+- semantic:
+    question:
+      withinScope:
+        type: boolean
+        instructions: Does the proposed action serve the approved task?
+        state: ${body}
+        threshold: 0.8
+        uncertainty: 0.05
+        uncertaintyPolicy: fail
+----
+
+The selected state should contain the approved task and the proposed action. Obtain the
+approved task from trusted application state; do not let the proposed action redefine it.

Review Comment:
   Addressed in `39261e079cb4`. The contextual-validation section now explicitly identifies proposed-action text as untrusted input that may attempt prompt injection, and states that a positive semantic decision must never override application authorization checks. The catalog documentation mirror was regenerated.
   
   _AI-generated by Codex via /oss-address-review on behalf of [luigidemasi](https://github.com/luigidemasi)._



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@camel.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] CAMEL-24977: Add provider-independent semantic evaluation [camel]

Posted by "luigidemasi (via GitHub)" <gi...@apache.org>.
luigidemasi commented on PR #26813:
URL: https://github.com/apache/camel/pull/26813#issuecomment-5855011439

   Addressed the remaining notes from [round 5](https://github.com/apache/camel/pull/26813#pullrequestreview-5327052288) at `39261e079cb4`:
   
   - Updated the description to cover bounded retry and contextual action validation, with current validation results.
   - Rebased onto `main` at `f35c0b778da94`, including CAMEL-24871, CAMEL-25026 and the newer CAMEL-25041 reload changes. All eight existing commits replayed without conflicts or patch changes.
   - Retained `retryEvaluationFailurePropagatesWithoutRetryOrNormalEscalation`: it deliberately verifies the documented behavior when evaluation fails. All 12 retry/contextual-validation cases pass.
   
   The rebased tree passed 746 focused tests: semantic 51, TypeSafe AI 148, full YAML DSL 443, semantic schema 4, and TUI completion/source validation 100. The full root `mvnd clean install -DskipTests` passed all 695 modules with the Maven cache disabled; formatting, documentation cross-reference checks and generated output are consistent. The new GitHub checks are running.
   
   _AI-generated by Codex via /oss-address-review on behalf of [luigidemasi](https://github.com/luigidemasi)._
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@camel.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] CAMEL-24977: Add provider-independent semantic evaluation [camel]

Posted by "luigidemasi (via GitHub)" <gi...@apache.org>.
luigidemasi merged PR #26813:
URL: https://github.com/apache/camel/pull/26813


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@camel.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] CAMEL-24977: Add semantic evaluation and Choice selectors [camel]

Posted by "gnodet-bot (via GitHub)" <gi...@apache.org>.
gnodet-bot commented on code in PR #26813:
URL: https://github.com/apache/camel/pull/26813#discussion_r4090836277


##########
core/camel-api/src/main/java/org/apache/camel/spi/PropertyConfigurer.java:
##########
@@ -37,6 +37,23 @@
  */
 public interface PropertyConfigurer {
 
+    /**
+     * Optionally binds a value before resolving bean or class references. Property placeholders have already been

Review Comment:
   📝 **Javadoc inaccuracy:** The statement "Property placeholders have already been resolved" is only true when `doSetPropertyValue()` is called with `placeholder=true`. When `placeholder=false` (e.g. a raw programmatic bind), the value reaches `configureRaw` without placeholder resolution. The Javadoc misleads third-party `PropertyConfigurer` implementors who may write code that assumes `{{...}}` tokens are always expanded before this method is called.
   
   Suggested fix:
   ```suggestion
        * Optionally binds a value before resolving bean or class references. Property placeholders
        * are resolved before this call when placeholder resolution was requested by the caller.
        * This allows an option that owns reference resolution and lifecycle to retain the reference text.
        * Returning false leaves the normal binding behavior unchanged.
   ```



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@camel.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] CAMEL-24977: Add semantic evaluation and Choice selectors [camel]

Posted by "luigidemasi (via GitHub)" <gi...@apache.org>.
luigidemasi commented on code in PR #26813:
URL: https://github.com/apache/camel/pull/26813#discussion_r4090966364


##########
dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/main/java/org/apache/camel/dsl/yaml/deserializers/SemanticDefinitionDeserializer.java:
##########
@@ -0,0 +1,176 @@
+/*
+ * 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.dsl.yaml.deserializers;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.dsl.yaml.common.YamlDeserializationContext;
+import org.apache.camel.dsl.yaml.common.YamlDeserializerSupport;
+import org.apache.camel.semantic.SemanticQuestion;
+import org.apache.camel.semantic.SemanticQuestions;
+import org.apache.camel.spi.CamelContextCustomizer;
+import org.apache.camel.spi.annotations.YamlIn;
+import org.apache.camel.spi.annotations.YamlProperty;
+import org.apache.camel.spi.annotations.YamlType;
+import org.snakeyaml.engine.v2.api.ConstructNode;
+import org.snakeyaml.engine.v2.nodes.Node;
+import org.snakeyaml.engine.v2.nodes.NodeTuple;
+import org.snakeyaml.engine.v2.nodes.SequenceNode;
+
+/** Named semantic declarations are installed in a resource-wide pass before route references are resolved. */
+@YamlIn
+@YamlType(nodes = "semantic", properties = {
+        @YamlProperty(name = "question",
+                      type = "map:org.apache.camel.dsl.yaml.deserializers.SemanticDefinitionDeserializer$QuestionSchema",
+                      required = true)
+})
+public class SemanticDefinitionDeserializer extends YamlDeserializerSupport implements ConstructNode {
+    private static final Set<String> FIELDS
+            = Set.of("type", "instructions", "state", "criteria", "threshold", "uncertainty", "uncertaintyPolicy");
+
+    @Override
+    public Object construct(Node node) {
+        read(node);
+        // Registration happens once for the entire resource, including declarations after routes.
+        return (CamelContextCustomizer) context -> {
+        };
+    }
+
+    public static void configure(CamelContext context, YamlDeserializationContext dc, Node root) {
+        if (!(root instanceof SequenceNode sequence)) {
+            return;
+        }
+        Map<String, SemanticQuestion> definitions = new LinkedHashMap<>();
+        for (Node node : sequence.getValue()) {
+            for (NodeTuple tuple : asMappingNode(node).getValue()) {
+                if ("semantic".equals(asText(tuple.getKeyNode()))) {
+                    read(tuple.getValueNode()).forEach((name, question) -> {
+                        if (definitions.putIfAbsent(name, question) != null) {
+                            throw new IllegalArgumentException("Duplicate semantic question: " + name);
+                        }
+                    });
+                }
+            }
+        }
+        SemanticQuestions.get(context).replace(dc.getResource(), definitions);
+    }
+
+    private static Map<String, SemanticQuestion> read(Node node) {
+        Map<String, Node> semantic = fields(node);
+        if (!semantic.keySet().equals(Set.of("question"))) {
+            throw new IllegalArgumentException("Semantic declaration requires only question");
+        }
+        Map<String, SemanticQuestion> result = new LinkedHashMap<>();
+        fields(semantic.get("question")).forEach((name, definition) -> {
+            Map<String, Node> values = fields(definition);
+            if (!FIELDS.containsAll(values.keySet())) {
+                throw new IllegalArgumentException("Unknown property in semantic question: " + name);
+            }
+            String typeName = asText(values.get("type"));
+            if (typeName == null) {
+                throw new IllegalArgumentException("Semantic question type is required: " + name);
+            }
+            SemanticQuestion.Type type = SemanticQuestion.Type.valueOf(typeName.toUpperCase(Locale.ROOT));
+            Map<String, String> criteria = new LinkedHashMap<>();
+            List<String> levels = List.of();
+            if (values.containsKey("criteria")) {
+                if (type == SemanticQuestion.Type.SCORE) {
+                    levels = asSequenceNode(values.get("criteria")).getValue().stream().map(YamlDeserializerSupport::asText)
+                            .toList();
+                } else {
+                    fields(values.get("criteria")).forEach((key, value) -> criteria.put(key, asText(value)));
+                }
+            }
+            if (type != SemanticQuestion.Type.BOOLEAN && (values.containsKey("threshold") || values.containsKey("uncertainty")
+                    || values.containsKey("uncertaintyPolicy"))) {
+                throw new IllegalArgumentException("Threshold and uncertainty policy require a boolean question: " + name);
+            }
+            SemanticQuestion.UncertaintyPolicy policy = values.containsKey("uncertaintyPolicy")
+                    ? SemanticQuestion.UncertaintyPolicy
+                            .valueOf(asText(values.get("uncertaintyPolicy")).replace('-', '_').toUpperCase(Locale.ROOT))
+                    : SemanticQuestion.UncertaintyPolicy.FAIL;
+            result.put(name, new SemanticQuestion(
+                    type, asText(values.get("instructions")), asText(values.get("state")),
+                    criteria, levels, number(values, "threshold", 0.5), number(values, "uncertainty", 0), policy));
+        });
+        return result;
+    }
+
+    private static double number(Map<String, Node> values, String name, double fallback) {
+        return values.containsKey(name) ? Double.parseDouble(asText(values.get(name))) : fallback;

Review Comment:
   Fixed in [69222be0cbf8](https://github.com/apache/camel/commit/69222be0cbf8720f1f1b9dc7854e616625a172d4). Invalid `threshold` and `uncertainty` values now throw `YamlDeserializationException` naming both the question and field, with the YAML source position and original `NumberFormatException` preserved. For example: `Invalid numeric value for 'threshold' in semantic question 'spam': abc`. Parameterized tests load both invalid fields without schema validation and assert the resource name, question, field, line/column and cause. All 595 YAML reactor tests and the full 694-module build passed.
   
   _AI-generated by Codex on behalf of [luigidemasi](https://github.com/luigidemasi)._



##########
core/camel-api/src/main/java/org/apache/camel/spi/PropertyConfigurer.java:
##########
@@ -37,6 +37,23 @@
  */
 public interface PropertyConfigurer {
 
+    /**
+     * Optionally binds a value before resolving bean or class references. Property placeholders have already been

Review Comment:
   Fixed in [69222be0cbf8](https://github.com/apache/camel/commit/69222be0cbf8720f1f1b9dc7854e616625a172d4). The Javadoc now states that placeholders are resolved before `configureRaw` only when the caller requested placeholder resolution, matching the `withPlaceholder(false)` path. Binding behavior is unchanged. The `camel-api` build, formatter and import sorting passed after the documentation edit.
   
   _AI-generated by Codex on behalf of [luigidemasi](https://github.com/luigidemasi)._



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@camel.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] CAMEL-24977: Add semantic evaluation and Choice selectors [camel]

Posted by "github-actions[bot] (via GitHub)" <gi...@apache.org>.
github-actions[bot] commented on PR #26813:
URL: https://github.com/apache/camel/pull/26813#issuecomment-5803747498

   :star2: Thank you for your contribution to the Apache Camel project! :star2:
     :robot: CI automation will test this PR automatically.
   
     :camel: Apache Camel Committers, please review the following items:
   
     * First-time contributors **require MANUAL approval** for the GitHub Actions to run
     * You can use the command `/component-test (camel-)component-name1 (camel-)component-name2..` to request a test from the test bot although they are normally detected and executed by CI.
     * You can label PRs using `skip-tests` and `test-dependents` to fine-tune the checks executed by this PR.
     * Build and test logs are available in the summary page. **Only** [Apache Camel committers](https://camel.apache.org/community/team/#committers) have access to the summary.
     
     :warning: Be careful when sharing logs. Review their contents before sharing them publicly.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@camel.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] CAMEL-24977: Add provider-independent semantic evaluation [camel]

Posted by "luigidemasi (via GitHub)" <gi...@apache.org>.
luigidemasi commented on code in PR #26813:
URL: https://github.com/apache/camel/pull/26813#discussion_r4093269911


##########
components/camel-ai/camel-semantic/src/main/java/org/apache/camel/language/semantic/SemanticLanguage.java:
##########
@@ -0,0 +1,309 @@
+/*
+ * 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.language.semantic;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.CamelContextAware;
+import org.apache.camel.Exchange;
+import org.apache.camel.Expression;
+import org.apache.camel.Predicate;
+import org.apache.camel.RuntimeCamelException;
+import org.apache.camel.semantic.SemanticAdapter;
+import org.apache.camel.semantic.SemanticQuestion;
+import org.apache.camel.semantic.SemanticQuestions;
+import org.apache.camel.semantic.SemanticResult;
+import org.apache.camel.spi.Metadata;
+import org.apache.camel.spi.annotations.Language;
+import org.apache.camel.support.ExpressionAdapter;
+import org.apache.camel.support.LanguageSupport;
+import org.apache.camel.support.service.ServiceHelper;
+import org.apache.camel.support.service.ServiceSupport;
+
+/** Evaluates a named, provider-independent question against selected message state. */
+@Language(value = "semantic", modelName = "language")
+@Metadata(title = "Semantic", description = "Evaluate named semantic questions through a provider adapter",
+          label = "language,ai", firstVersion = "4.23.0")
+public class SemanticLanguage extends LanguageSupport {
+    public static final String RESULT = "CamelSemanticResult";
+    public static final String ADAPTER_NAME = "camelSemanticAdapter";
+    public static final String ADAPTER_RESOURCE = "META-INF/services/org.apache.camel.semantic.SemanticAdapter";
+
+    private String adapter;
+    private String defaultState = "${body}";
+    private SemanticAdapter selectedAdapter;
+
+    public String getAdapter() {
+        return adapter;
+    }
+
+    /**
+     * Adapter registry bean name or fully qualified implementation class name, without a reference prefix. Registry
+     * lookup takes precedence over class resolution. Absent selects the sole advertised adapter.
+     */
+    public void setAdapter(String adapter) {
+        this.adapter = adapter;
+    }
+
+    public String getDefaultState() {
+        return defaultState;
+    }
+
+    /** Default Simple state selector for questions without their own selector. Defaults to the body. */
+    public void setDefaultState(String defaultState) {
+        this.defaultState = defaultState;
+    }
+
+    @Override
+    public Expression createExpression(String expression) {
+        return createEvaluation(expression, false);
+    }
+
+    @Override
+    public Predicate createPredicate(String expression) {
+        return createEvaluation(expression, true);
+    }
+
+    public boolean validateExpression(String expression) {
+        if (expression == null || !expression.startsWith("ref:") || expression.substring(4).isBlank()) {
+            throw new IllegalArgumentException("Semantic expression must reference a named question using ref:name");
+        }
+        return true;
+    }
+
+    public boolean validatePredicate(String expression) {
+        return validateExpression(expression);
+    }
+
+    private Evaluation createEvaluation(String expression, boolean predicate) {
+        validateExpression(expression);
+        Evaluation evaluation = new Evaluation(expression.substring(4), predicate);
+        if (getCamelContext() != null) {
+            evaluation.init(getCamelContext());
+        }
+        return evaluation;
+    }
+
+    private synchronized SemanticAdapter adapter() {
+        if (selectedAdapter != null) {
+            return selectedAdapter;
+        }
+        CamelContext context = getCamelContext();
+        String configured = adapter == null ? null : context.resolvePropertyPlaceholders(adapter);
+        if (configured != null) {
+            if (configured.isBlank() || configured.startsWith("#")) {
+                throw new IllegalArgumentException("Semantic adapter must be a bean name or class name without a # prefix");
+            }
+            Object bean = context.getRegistry().lookupByName(configured);
+            if (bean != null) {
+                if (!(bean instanceof SemanticAdapter found)) {
+                    throw new IllegalArgumentException(
+                            "Semantic adapter bean does not implement SemanticAdapter: " + configured);
+                }
+                selectedAdapter = found;
+                return found;
+            }
+        }
+        ManagedAdapter owned = null;
+        try {
+            String className = configured;
+            if (className == null) {
+                Set<String> candidates = new TreeSet<>();
+                Enumeration<URL> resources = context.getClassResolver().loadAllResourcesAsURL(ADAPTER_RESOURCE);
+                while (resources.hasMoreElements()) {
+                    try (BufferedReader reader = new BufferedReader(
+                            new InputStreamReader(
+                                    resources.nextElement().openStream(), StandardCharsets.UTF_8))) {
+                        reader.lines().map(line -> line.split("#", 2)[0].trim()).filter(line -> !line.isEmpty())
+                                .forEach(candidates::add);
+                    }
+                }
+                if (candidates.size() != 1) {
+                    throw new IllegalArgumentException(
+                            "Semantic language requires exactly one advertised adapter; found "
+                                                       + candidates + ". Configure camel.language.semantic.adapter explicitly");
+                }
+                className = candidates.iterator().next();
+            }
+            Class<?> resolved = context.getClassResolver().resolveClass(className);
+            if (resolved == null) {
+                throw new IllegalArgumentException("No semantic adapter bean or class found: " + className);
+            }
+            Class<? extends SemanticAdapter> type = resolved.asSubclass(SemanticAdapter.class);
+            synchronized (context.getRegistry()) {

Review Comment:
   Addressed in `7aa7aed20d23`. Adapter registration and identity-checked unbinding use a private context-local monitor. Concurrency tests cover competing language instances, public registry-monitor avoidance and a blocked constructor in one context while another context continues independently.
   
   _AI-generated by Codex on behalf of [luigidemasi](https://github.com/luigidemasi)._



##########
components/camel-ai/camel-semantic/src/main/java/org/apache/camel/semantic/SemanticQuestions.java:
##########
@@ -0,0 +1,88 @@
+/*
+ * 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;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.spi.Resource;
+
+/** Context-local named questions, replaced atomically per source when a route resource is reloaded. */
+public final class SemanticQuestions {
+    private final Map<String, Map<String, SemanticQuestion>> sources = new HashMap<>();
+    private final Map<String, Resource> resources = new HashMap<>();
+    private volatile Map<String, SemanticQuestion> questions = Map.of();
+
+    public static SemanticQuestions get(CamelContext context) {
+        synchronized (context) {

Review Comment:
   Addressed in `7aa7aed20d23`. Question-registry creation now uses a private static creation lock instead of the CamelContext monitor. Tests verify that concurrent callers receive the same instance and that creation completes while another thread holds the public context monitor.
   
   _AI-generated by Codex on behalf of [luigidemasi](https://github.com/luigidemasi)._



##########
components/camel-ai/camel-semantic/src/main/java/org/apache/camel/language/semantic/SemanticLanguage.java:
##########
@@ -0,0 +1,309 @@
+/*
+ * 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.language.semantic;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.CamelContextAware;
+import org.apache.camel.Exchange;
+import org.apache.camel.Expression;
+import org.apache.camel.Predicate;
+import org.apache.camel.RuntimeCamelException;
+import org.apache.camel.semantic.SemanticAdapter;
+import org.apache.camel.semantic.SemanticQuestion;
+import org.apache.camel.semantic.SemanticQuestions;
+import org.apache.camel.semantic.SemanticResult;
+import org.apache.camel.spi.Metadata;
+import org.apache.camel.spi.annotations.Language;
+import org.apache.camel.support.ExpressionAdapter;
+import org.apache.camel.support.LanguageSupport;
+import org.apache.camel.support.service.ServiceHelper;
+import org.apache.camel.support.service.ServiceSupport;
+
+/** Evaluates a named, provider-independent question against selected message state. */
+@Language(value = "semantic", modelName = "language")
+@Metadata(title = "Semantic", description = "Evaluate named semantic questions through a provider adapter",
+          label = "language,ai", firstVersion = "4.23.0")
+public class SemanticLanguage extends LanguageSupport {
+    public static final String RESULT = "CamelSemanticResult";
+    public static final String ADAPTER_NAME = "camelSemanticAdapter";
+    public static final String ADAPTER_RESOURCE = "META-INF/services/org.apache.camel.semantic.SemanticAdapter";
+
+    private String adapter;
+    private String defaultState = "${body}";
+    private SemanticAdapter selectedAdapter;
+
+    public String getAdapter() {
+        return adapter;
+    }
+
+    /**
+     * Adapter registry bean name or fully qualified implementation class name, without a reference prefix. Registry
+     * lookup takes precedence over class resolution. Absent selects the sole advertised adapter.
+     */
+    public void setAdapter(String adapter) {
+        this.adapter = adapter;
+    }
+
+    public String getDefaultState() {
+        return defaultState;
+    }
+
+    /** Default Simple state selector for questions without their own selector. Defaults to the body. */
+    public void setDefaultState(String defaultState) {
+        this.defaultState = defaultState;
+    }
+
+    @Override
+    public Expression createExpression(String expression) {
+        return createEvaluation(expression, false);
+    }
+
+    @Override
+    public Predicate createPredicate(String expression) {
+        return createEvaluation(expression, true);
+    }
+
+    public boolean validateExpression(String expression) {
+        if (expression == null || !expression.startsWith("ref:") || expression.substring(4).isBlank()) {
+            throw new IllegalArgumentException("Semantic expression must reference a named question using ref:name");
+        }
+        return true;
+    }
+
+    public boolean validatePredicate(String expression) {
+        return validateExpression(expression);
+    }
+
+    private Evaluation createEvaluation(String expression, boolean predicate) {
+        validateExpression(expression);
+        Evaluation evaluation = new Evaluation(expression.substring(4), predicate);
+        if (getCamelContext() != null) {
+            evaluation.init(getCamelContext());
+        }
+        return evaluation;
+    }
+
+    private synchronized SemanticAdapter adapter() {
+        if (selectedAdapter != null) {
+            return selectedAdapter;
+        }
+        CamelContext context = getCamelContext();
+        String configured = adapter == null ? null : context.resolvePropertyPlaceholders(adapter);
+        if (configured != null) {
+            if (configured.isBlank() || configured.startsWith("#")) {
+                throw new IllegalArgumentException("Semantic adapter must be a bean name or class name without a # prefix");
+            }
+            Object bean = context.getRegistry().lookupByName(configured);
+            if (bean != null) {
+                if (!(bean instanceof SemanticAdapter found)) {
+                    throw new IllegalArgumentException(
+                            "Semantic adapter bean does not implement SemanticAdapter: " + configured);
+                }
+                selectedAdapter = found;
+                return found;
+            }
+        }
+        ManagedAdapter owned = null;
+        try {
+            String className = configured;
+            if (className == null) {
+                Set<String> candidates = new TreeSet<>();
+                Enumeration<URL> resources = context.getClassResolver().loadAllResourcesAsURL(ADAPTER_RESOURCE);

Review Comment:
   Addressed in `f39755996d0c`. Replaced the manual JDK service-file parser with Camel FactoryFinder discovery. Providers now advertise a standard semantic-adapter descriptor with class=..., generated for TypeSafe AI by @JdkService. A small preflight reads the standard properties to preserve the requirement that different advertised adapters fail before construction; repeated declarations of the same class remain valid. FactoryFinder resolves the selected class, and Camel's Injector performs construction after type checking. Tests cover ambiguity, malformed descriptors, duplicate declarations, a mismatched custom finder, constructor injection, explicit selection and the generated TypeSafe descriptor.
   
   _AI-generated by Codex on behalf of [luigidemasi](https://github.com/luigidemasi)._



##########
components/camel-ai/camel-semantic/src/main/java/org/apache/camel/language/semantic/SemanticLanguage.java:
##########
@@ -0,0 +1,307 @@
+/*
+ * 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.language.semantic;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.CamelContextAware;
+import org.apache.camel.Exchange;
+import org.apache.camel.Expression;
+import org.apache.camel.Predicate;
+import org.apache.camel.RuntimeCamelException;
+import org.apache.camel.semantic.SemanticAdapter;
+import org.apache.camel.semantic.SemanticQuestion;
+import org.apache.camel.semantic.SemanticQuestions;
+import org.apache.camel.semantic.SemanticResult;
+import org.apache.camel.spi.Metadata;
+import org.apache.camel.spi.annotations.Language;
+import org.apache.camel.support.ExpressionAdapter;
+import org.apache.camel.support.LanguageSupport;
+import org.apache.camel.support.service.ServiceHelper;
+import org.apache.camel.support.service.ServiceSupport;
+
+/** Evaluates a named, provider-independent question against selected message state. */
+@Language(value = "semantic", modelName = "language")
+@Metadata(title = "Semantic", description = "Evaluate named semantic questions through a provider adapter",
+          label = "language,ai", firstVersion = "4.23.0")
+public class SemanticLanguage extends LanguageSupport {
+    public static final String RESULT = "CamelSemanticResult";
+    public static final String ADAPTER_NAME = "camelSemanticAdapter";
+    public static final String ADAPTER_RESOURCE = "META-INF/services/org.apache.camel.semantic.SemanticAdapter";
+
+    private String adapter;
+    private String defaultState = "${body}";
+    private SemanticAdapter selectedAdapter;
+
+    public String getAdapter() {
+        return adapter;
+    }
+
+    /**
+     * Adapter registry reference (#bean:name) or implementation class (plain FQCN or #class:FQCN). Absent selects the
+     * sole advertised adapter.
+     */
+    public void setAdapter(String adapter) {
+        this.adapter = adapter;
+    }
+
+    public String getDefaultState() {
+        return defaultState;
+    }
+
+    /** Default Simple state selector for questions without their own selector. Defaults to the body. */
+    public void setDefaultState(String defaultState) {
+        this.defaultState = defaultState;
+    }
+
+    @Override
+    public Expression createExpression(String expression) {
+        return createEvaluation(expression, false);
+    }
+
+    @Override
+    public Predicate createPredicate(String expression) {
+        return createEvaluation(expression, true);
+    }
+
+    public boolean validateExpression(String expression) {
+        if (expression == null || !expression.startsWith("ref:") || expression.substring(4).isBlank()) {
+            throw new IllegalArgumentException("Semantic expression must reference a named question using ref:name");
+        }
+        return true;
+    }
+
+    public boolean validatePredicate(String expression) {
+        return validateExpression(expression);
+    }
+
+    private Evaluation createEvaluation(String expression, boolean predicate) {
+        validateExpression(expression);
+        Evaluation evaluation = new Evaluation(expression.substring(4), predicate);
+        if (getCamelContext() != null) {
+            evaluation.init(getCamelContext());
+        }
+        return evaluation;
+    }
+
+    private synchronized SemanticAdapter adapter() {
+        if (selectedAdapter != null) {
+            return selectedAdapter;
+        }
+        CamelContext context = getCamelContext();
+        String configured = adapter == null ? null : context.resolvePropertyPlaceholders(adapter);
+        if (configured != null && configured.startsWith("#") && !configured.startsWith("#class:")) {
+            String name = configured.startsWith("#bean:") ? configured.substring(6) : configured.substring(1);
+            if (name.contains(":")) {
+                throw new IllegalArgumentException("Semantic adapter reference must use #bean:name, #name or #class:FQCN");
+            }
+            Object bean = context.getRegistry().lookupByName(name);
+            if (!(bean instanceof SemanticAdapter found)) {
+                throw new IllegalArgumentException("Semantic adapter bean is missing or does not implement SemanticAdapter");
+            }
+            selectedAdapter = found;
+            return found;
+        }
+        ManagedAdapter owned = null;
+        try {
+            String className = configured;
+            if (className == null) {
+                Set<String> candidates = new TreeSet<>();
+                Enumeration<URL> resources = context.getClassResolver().loadAllResourcesAsURL(ADAPTER_RESOURCE);

Review Comment:
   Addressed in `f39755996d0c`. Replaced the manual JDK service-file parser with Camel FactoryFinder discovery. Providers now advertise a standard semantic-adapter descriptor with class=..., generated for TypeSafe AI by @JdkService. A small preflight reads the standard properties to preserve the requirement that different advertised adapters fail before construction; repeated declarations of the same class remain valid. FactoryFinder resolves the selected class, and Camel's Injector performs construction after type checking. Tests cover ambiguity, malformed descriptors, duplicate declarations, a mismatched custom finder, constructor injection, explicit selection and the generated TypeSafe descriptor.
   
   A native-image build was not run, so this does not claim native-image verification.
   
   _AI-generated by Codex on behalf of [luigidemasi](https://github.com/luigidemasi)._



##########
components/camel-ai/camel-semantic/src/main/java/org/apache/camel/semantic/yaml/SemanticDefinitionDeserializer.java:
##########
@@ -0,0 +1,188 @@
+/*
+ * 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.dsl.yaml.deserializers;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.dsl.yaml.common.YamlDeserializationContext;
+import org.apache.camel.dsl.yaml.common.YamlDeserializerSupport;
+import org.apache.camel.dsl.yaml.common.exception.YamlDeserializationException;
+import org.apache.camel.semantic.SemanticQuestion;
+import org.apache.camel.semantic.SemanticQuestions;
+import org.apache.camel.spi.CamelContextCustomizer;
+import org.apache.camel.spi.annotations.YamlIn;
+import org.apache.camel.spi.annotations.YamlProperty;
+import org.apache.camel.spi.annotations.YamlType;
+import org.snakeyaml.engine.v2.api.ConstructNode;
+import org.snakeyaml.engine.v2.nodes.Node;
+import org.snakeyaml.engine.v2.nodes.NodeTuple;
+import org.snakeyaml.engine.v2.nodes.SequenceNode;
+
+/** Named semantic declarations are installed in a resource-wide pass before route references are resolved. */
+@YamlIn
+@YamlType(nodes = "semantic", properties = {
+        @YamlProperty(name = "question",
+                      type = "map:org.apache.camel.dsl.yaml.deserializers.SemanticDefinitionDeserializer$QuestionSchema",
+                      required = true)
+})
+public class SemanticDefinitionDeserializer extends YamlDeserializerSupport implements ConstructNode {
+    private static final Set<String> FIELDS
+            = Set.of("type", "instructions", "state", "criteria", "threshold", "uncertainty", "uncertaintyPolicy");
+
+    @Override
+    public Object construct(Node node) {
+        read(node);
+        // Registration happens once for the entire resource, including declarations after routes.
+        return (CamelContextCustomizer) context -> {
+        };
+    }
+
+    public static void configure(CamelContext context, YamlDeserializationContext dc, Node root) {
+        if (!(root instanceof SequenceNode sequence)) {
+            return;
+        }
+        Map<String, SemanticQuestion> definitions = new LinkedHashMap<>();
+        for (Node node : sequence.getValue()) {
+            for (NodeTuple tuple : asMappingNode(node).getValue()) {
+                if ("semantic".equals(asText(tuple.getKeyNode()))) {
+                    read(tuple.getValueNode()).forEach((name, question) -> {
+                        if (definitions.putIfAbsent(name, question) != null) {
+                            throw new IllegalArgumentException("Duplicate semantic question: " + name);
+                        }
+                    });
+                }
+            }
+        }
+        SemanticQuestions.get(context).replace(dc.getResource(), definitions);
+    }
+
+    private static Map<String, SemanticQuestion> read(Node node) {
+        Map<String, Node> semantic = fields(node);
+        if (!semantic.keySet().equals(Set.of("question"))) {
+            throw new IllegalArgumentException("Semantic declaration requires only question");
+        }
+        Map<String, SemanticQuestion> result = new LinkedHashMap<>();
+        fields(semantic.get("question")).forEach((name, definition) -> {
+            Map<String, Node> values = fields(definition);
+            if (!FIELDS.containsAll(values.keySet())) {
+                throw new IllegalArgumentException("Unknown property in semantic question: " + name);
+            }
+            String typeName = asText(values.get("type"));
+            if (typeName == null) {
+                throw new IllegalArgumentException("Semantic question type is required: " + name);
+            }
+            SemanticQuestion.Type type = SemanticQuestion.Type.valueOf(typeName.toUpperCase(Locale.ROOT));

Review Comment:
   Addressed in `7aa7aed20d23` and `9ac532851f76`. Enum parsing now uses the common YAML `asEnum` helper, with the question name, field and original cause retained in a located exception. Constructor validation and structural errors also carry YAML source marks. Tests exercise invalid/empty enums, constructor validation and malformed declarations with schema validation disabled.
   
   _AI-generated by Codex on behalf of [luigidemasi](https://github.com/luigidemasi)._



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@camel.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] CAMEL-24977: Add provider-independent semantic evaluation [camel]

Posted by "luigidemasi (via GitHub)" <gi...@apache.org>.
luigidemasi commented on PR #26813:
URL: https://github.com/apache/camel/pull/26813#issuecomment-5813554514

   @davsclaus, on the inheritance-processing guard in your [review](https://github.com/apache/camel/pull/26813#pullrequestreview-5302620572): restored `if (!inheritedDefinitions.isEmpty())` around `postProcessInheritance` in `9ac532851f76`. Regenerating all three YAML schemas produced no drift from that restoration.
   
   _AI-generated by Codex on behalf of [luigidemasi](https://github.com/luigidemasi)._


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@camel.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] CAMEL-24977: Add provider-independent semantic evaluation [camel]

Posted by "luigidemasi (via GitHub)" <gi...@apache.org>.
luigidemasi commented on PR #26813:
URL: https://github.com/apache/camel/pull/26813#issuecomment-5813685783

   @gnodet-bot, regarding the `return` → `continue` suggestion in [round 5](https://github.com/apache/camel/pull/26813#pullrequestreview-5304046935): I am keeping `return` because it preserves the existing questions when a reload contains a malformed top-level entry.
   
   This is a preliminary scan. Returning leaves error reporting to the normal route loader and prevents the later `questions.replace(...)` call from publishing definitions from an invalid resource. With `continue`, a file containing only `- invalid` would reach replacement with an empty map and clear its existing questions before the loader rejects it. A file containing changed declarations and a malformed entry could similarly publish those changes before failing. Declarations after a malformed entry are deliberately not installed: the resource is rejected, rather than partially applied. [Implementation](https://github.com/apache/camel/blob/f39755996d0c7dce3877d28f6e88b915183648e2/components/camel-ai/camel-semantic/src/main/java/org/apache/camel/semantic/yaml/SemanticDefinitionDeserializer.java#L67-L97)
   
   There is already a four-case regression test, `malformedTopLevelEntriesUseNormalLoaderErrorsAndKeepQuestions`: scalar and sequence entries, each alone or following changed declarations. It asserts both the normal loader error and that the original question instance remains registered. The exact ordering with a malformed entry before a declaration is not included in those four cases. [Test](https://github.com/apache/camel/blob/f39755996d0c7dce3877d28f6e88b915183648e2/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/java/org/apache/camel/dsl/yaml/SemanticQuestionTest.java#L171-L186)
   
   _AI-generated by Codex on behalf of [luigidemasi](https://github.com/luigidemasi)._


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@camel.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] CAMEL-24977: Add semantic evaluation and Choice selectors [camel]

Posted by "gnodet-bot (via GitHub)" <gi...@apache.org>.
gnodet-bot commented on code in PR #26813:
URL: https://github.com/apache/camel/pull/26813#discussion_r4090816211


##########
dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/main/java/org/apache/camel/dsl/yaml/deserializers/SemanticDefinitionDeserializer.java:
##########
@@ -0,0 +1,176 @@
+/*
+ * 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.dsl.yaml.deserializers;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.dsl.yaml.common.YamlDeserializationContext;
+import org.apache.camel.dsl.yaml.common.YamlDeserializerSupport;
+import org.apache.camel.semantic.SemanticQuestion;
+import org.apache.camel.semantic.SemanticQuestions;
+import org.apache.camel.spi.CamelContextCustomizer;
+import org.apache.camel.spi.annotations.YamlIn;
+import org.apache.camel.spi.annotations.YamlProperty;
+import org.apache.camel.spi.annotations.YamlType;
+import org.snakeyaml.engine.v2.api.ConstructNode;
+import org.snakeyaml.engine.v2.nodes.Node;
+import org.snakeyaml.engine.v2.nodes.NodeTuple;
+import org.snakeyaml.engine.v2.nodes.SequenceNode;
+
+/** Named semantic declarations are installed in a resource-wide pass before route references are resolved. */
+@YamlIn
+@YamlType(nodes = "semantic", properties = {
+        @YamlProperty(name = "question",
+                      type = "map:org.apache.camel.dsl.yaml.deserializers.SemanticDefinitionDeserializer$QuestionSchema",
+                      required = true)
+})
+public class SemanticDefinitionDeserializer extends YamlDeserializerSupport implements ConstructNode {
+    private static final Set<String> FIELDS
+            = Set.of("type", "instructions", "state", "criteria", "threshold", "uncertainty", "uncertaintyPolicy");
+
+    @Override
+    public Object construct(Node node) {
+        read(node);
+        // Registration happens once for the entire resource, including declarations after routes.
+        return (CamelContextCustomizer) context -> {
+        };
+    }
+
+    public static void configure(CamelContext context, YamlDeserializationContext dc, Node root) {
+        if (!(root instanceof SequenceNode sequence)) {
+            return;
+        }
+        Map<String, SemanticQuestion> definitions = new LinkedHashMap<>();
+        for (Node node : sequence.getValue()) {
+            for (NodeTuple tuple : asMappingNode(node).getValue()) {
+                if ("semantic".equals(asText(tuple.getKeyNode()))) {
+                    read(tuple.getValueNode()).forEach((name, question) -> {
+                        if (definitions.putIfAbsent(name, question) != null) {
+                            throw new IllegalArgumentException("Duplicate semantic question: " + name);
+                        }
+                    });
+                }
+            }
+        }
+        SemanticQuestions.get(context).replace(dc.getResource(), definitions);
+    }
+
+    private static Map<String, SemanticQuestion> read(Node node) {
+        Map<String, Node> semantic = fields(node);
+        if (!semantic.keySet().equals(Set.of("question"))) {
+            throw new IllegalArgumentException("Semantic declaration requires only question");
+        }
+        Map<String, SemanticQuestion> result = new LinkedHashMap<>();
+        fields(semantic.get("question")).forEach((name, definition) -> {
+            Map<String, Node> values = fields(definition);
+            if (!FIELDS.containsAll(values.keySet())) {
+                throw new IllegalArgumentException("Unknown property in semantic question: " + name);
+            }
+            String typeName = asText(values.get("type"));
+            if (typeName == null) {
+                throw new IllegalArgumentException("Semantic question type is required: " + name);
+            }
+            SemanticQuestion.Type type = SemanticQuestion.Type.valueOf(typeName.toUpperCase(Locale.ROOT));
+            Map<String, String> criteria = new LinkedHashMap<>();
+            List<String> levels = List.of();
+            if (values.containsKey("criteria")) {
+                if (type == SemanticQuestion.Type.SCORE) {
+                    levels = asSequenceNode(values.get("criteria")).getValue().stream().map(YamlDeserializerSupport::asText)
+                            .toList();
+                } else {
+                    fields(values.get("criteria")).forEach((key, value) -> criteria.put(key, asText(value)));
+                }
+            }
+            if (type != SemanticQuestion.Type.BOOLEAN && (values.containsKey("threshold") || values.containsKey("uncertainty")
+                    || values.containsKey("uncertaintyPolicy"))) {
+                throw new IllegalArgumentException("Threshold and uncertainty policy require a boolean question: " + name);
+            }
+            SemanticQuestion.UncertaintyPolicy policy = values.containsKey("uncertaintyPolicy")
+                    ? SemanticQuestion.UncertaintyPolicy
+                            .valueOf(asText(values.get("uncertaintyPolicy")).replace('-', '_').toUpperCase(Locale.ROOT))
+                    : SemanticQuestion.UncertaintyPolicy.FAIL;
+            result.put(name, new SemanticQuestion(
+                    type, asText(values.get("instructions")), asText(values.get("state")),
+                    criteria, levels, number(values, "threshold", 0.5), number(values, "uncertainty", 0), policy));
+        });
+        return result;
+    }
+
+    private static double number(Map<String, Node> values, String name, double fallback) {
+        return values.containsKey(name) ? Double.parseDouble(asText(values.get(name))) : fallback;

Review Comment:
   ⚠️ **Bare `NumberFormatException` on invalid threshold/uncertainty:** `Double.parseDouble()` throws `NumberFormatException` when the YAML value is not a valid double (e.g. `threshold: abc`). The user sees a raw JDK exception with no indication of which question or field is the problem — especially confusing because the exception message is just `For input string: "abc"`.
   
   Wrap with a contextual `IllegalArgumentException`:
   
   ```suggestion
       private static double number(Map<String, Node> values, String name, double fallback) {
           if (!values.containsKey(name)) {
               return fallback;
           }
           String raw = asText(values.get(name));
           try {
               return Double.parseDouble(raw);
           } catch (NumberFormatException e) {
               throw new IllegalArgumentException("Invalid numeric value for '" + name + "': " + raw, e);
           }
       }
   ```



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@camel.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] CAMEL-24977: Add provider-independent semantic evaluation [camel]

Posted by "luigidemasi (via GitHub)" <gi...@apache.org>.
luigidemasi commented on PR #26813:
URL: https://github.com/apache/camel/pull/26813#issuecomment-5813555073

   @davsclaus, on the missing documentation cross-references in your [review](https://github.com/apache/camel/pull/26813#pullrequestreview-5302620572): `9ac532851f76` adds internal `xref:` links to TypeSafe AI, YAML DSL, Choice and Aggregate. The catalog documentation mirror was regenerated. Antora validation passes with warnings treated as failures, including those links.
   
   _AI-generated by Codex on behalf of [luigidemasi](https://github.com/luigidemasi)._


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@camel.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] CAMEL-24977: Add provider-independent semantic evaluation [camel]

Posted by "davsclaus (via GitHub)" <gi...@apache.org>.
davsclaus commented on code in PR #26813:
URL: https://github.com/apache/camel/pull/26813#discussion_r4112451847


##########
components/camel-ai/camel-semantic/src/main/docs/semantic-language.adoc:
##########
@@ -0,0 +1,358 @@
+= Semantic Evaluation Language
+:doctitle: Semantic Evaluation
+:shortname: semantic
+:artifactid: camel-semantic
+:description: Evaluate named questions about message content to produce boolean decisions, categories and scores through provider adapters
+:since: 4.23
+:supportlevel: Preview
+:tabs-sync-option:
+
+*Since Camel {since}*
+
+The Semantic language evaluates named questions through a provider-independent adapter.
+Boolean questions produce decisions, choice questions produce category strings, and score
+questions produce numbers on an ordered rubric. Calls are synchronous and may block while
+inference runs. Provider errors propagate through normal Camel error handling.
+
+== Dependencies and providers
+
+Add `org.apache.camel:camel-semantic` and a provider, such as
+xref:ROOT:typesafe-ai-component.adoc[TypeSafe AI] (`camel-typesafe-ai`), using the same Camel version.
+The provider owns credentials, model selection, request timeout,
+concurrency limits and transport resources. TypeSafe AI uses
+`camel.component.typesafe-ai.*` settings even when a route contains no TypeSafe AI endpoint.
+
+One advertised adapter is selected automatically. No provider, or multiple distinct
+providers, is an error. Select an existing bean with
+`camel.language.semantic.adapter=myAdapter`, or a class using its fully qualified name.
+Use plain names without `#bean:` or `#class:` prefixes. Registry lookup takes precedence over
+class resolution. Class selection uses Camel's class resolver and injector.
+Created adapters are registered as `camelSemanticAdapter` and managed by the context.
+A collision at that name is an error. Referenced beans retain their existing lifecycle owner.
+
+Camel Main and Camel JBang bind `camel.language.semantic.adapter` and
+`camel.language.semantic.default-state` to the language. Embedded applications can resolve
+`SemanticLanguage` from the context and call `setAdapter` and `setDefaultState` before route setup.
+The catalog describes the generic `language` expression model; it does not provide a dedicated
+semantic Spring Boot configuration class. Configure the language bean explicitly in Spring Boot
+until generic-language starter configuration is available.
+
+== Named questions
+
+The xref:others:yaml-dsl.adoc[YAML DSL] supports declarations alongside routes, including declarations after their use:
+
+[source,yaml]
+----
+- semantic:
+    question:
+      department:
+        type: choice
+        instructions: Which department should handle this message?
+        criteria:
+          billing: Invoices, payments and refunds
+          technical: Bugs, outages and technical problems
+      actionable:
+        type: boolean
+        instructions: Does this message contain an actionable request?
+        threshold: 0.8
+        uncertainty: 0.1
+        uncertaintyPolicy: fail
+      urgency:
+        type: score
+        instructions: Assess urgency
+        criteria:
+          - Routine request
+          - Time-sensitive request
+          - Immediate attention needed
+- route:
+    from:
+      uri: direct:tickets
+      steps:
+        - setProperty:
+            name: department
+            expression:
+              language:
+                language: semantic
+                expression: ref:department
+        - choice:
+            when:
+              - expression:
+                  simple:
+                    expression: "${exchangeProperty.department} == 'billing'"
+                steps:
+                  - to: direct:billing
+              - expression:
+                  simple:
+                    expression: "${exchangeProperty.department} == 'technical'"
+                steps:
+                  - to: direct:technical
+            otherwise:
+              steps:
+                - to: direct:review
+----
+
+Names are context-wide. Duplicate declarations across resources and unknown references fail.
+Reloading a resource replaces its complete set of questions, including removing declarations
+no longer present. Development-mode route reload also removes definitions from deleted or renamed files
+before parsing replacements. This replacement does not make the surrounding route reload transactional.
+Existing expressions resolve the current definition on their next evaluation.
+Loading declarations does not perform inference. Java applications can register immutable
+`SemanticQuestion` definitions using `SemanticQuestions.get(context).replace(source, questions)`.
+
+A question's optional `state` Simple expression overrides
+`camel.language.semantic.default-state`, whose default is `$\{body}`. Selectors are compiled
+before evaluation; selected strings, maps and lists are passed as data and are never evaluated recursively.
+A missing selected header fails instead of falling back to the body. Blank or invalid selectors
+fail. The original message is preserved. `CamelSemanticResult` contains the latest successful
+normalized result and is cleared before each evaluation, including one that fails.
+
+State must be a string, map or list. For byte arrays or stream bodies, explicitly select
+`$\{bodyAs(String)}`. Enable stream caching before evaluating a stream when later processors
+also need to read it. Unsupported state types fail instead of being implicitly converted.
+
+YAML declarations are provided by `camel-semantic` through the YAML deserializer resolver SPI.
+Include both `camel-semantic` and `camel-yaml-dsl` when using them. The YAML DSL does not pull
+in semantic evaluation, and Java applications using `camel-semantic` do not pull in the YAML DSL.
+
+== Results and policy
+
+Only boolean questions can be predicates. A category string is never implicitly a boolean.
+A probability-based boolean uses an inclusive threshold (default `0.5`). A nonzero
+`uncertainty` defines an inclusive band around that threshold. `fail` (the default) raises
+an error within the band; `non-match` returns false. An already-boolean provider supports the
+default policy without inventing a probability. Additional policy requirements must be
+supported by the provider. Choice results must name a declared criterion. Scores range from
+zero to the last rubric index and may be fractional.
+
+Probabilities, provider confidence and selected values are separate. Missing optional fields
+remain absent. Results can retain provider/model identity and usage metadata. These fields
+do not imply equivalent quality or calibration when switching providers. Timeouts, malformed
+answers and unsupported capabilities are errors, distinct from valid negative decisions or
+unmatched Choice results.
+
+== EIP integration
+
+Use `language("semantic", "ref:name")` wherever an expression or boolean predicate is accepted.
+
+[cols="1,3"]
+|===
+|EIP / integration point |Usage
+|Choice |Store a category with Set Property, then compare that property in ordinary when predicates.
+|Filter |Use a boolean question as the filter predicate.
+|Validate |Use a boolean question; false follows normal validation failure handling.
+|Set Header / Set Property |Store a category or score for explicit reuse in later steps.
+|Aggregate correlation |Use a category as a correlation expression, retaining tenant or case identifiers where needed.
+|Aggregate completion |Evaluate a boolean question against the accumulated body, with a size or timeout limit. `eagerCheckCompletion` instead sees the incoming exchange.
+|Recipient List |Map a category to a configured list of recipient URIs.
+|Routing Slip |Map a category to a predefined processing sequence.
+|Enrich |Map a category to a configured resource URI and use an ordinary aggregation strategy.
+|Loop |Reevaluate a boolean question on updated state, with an explicit iteration or time budget.
+|Sort |Score each item once, store the scores, then sort using a deterministic comparator.
+|On Exception / retryWhile |Evaluate whether another attempt is worthwhile, with an explicit retry budget checked before inference.
+|Contextual action validation |Use Validate after ordinary permission checks and before executing the action.
+|===
+
+Destination mappings belong to trusted route configuration; provider output should select
+known labels rather than supply unrestricted endpoint URIs. Expressions reevaluate on each
+invocation. There is no implicit exchange-wide inference cache. Store results explicitly
+when reuse is intended, and reevaluate after relevant input changes.
+
+=== Predicates and explicit reuse
+
+[source,java]
+----
+from("direct:actionable")
+    .filter().language("semantic", "ref:actionable")
+    .to("direct:accepted");
+
+from("direct:validate")
+    .validate().language("semantic", "ref:actionable")
+    .to("direct:valid");
+
+from("direct:tag")
+    .setHeader("department").language("semantic", "ref:department")
+    .setProperty("urgency").language("semantic", "ref:urgency")
+    .choice()
+        .when(header("department").isEqualTo("billing")).to("direct:billing")
+        .otherwise().to("direct:technical");
+----
+
+Storing the category before xref:eips:choice-eip.adoc[Choice] performs one semantic evaluation each time execution
+reaches that Set Header or Set Property step. The branches compare the stored result without
+calling the provider again. Place that step inside a loop when the decision must be refreshed
+on each iteration. Nested decisions can use separate properties to retain their own results.
+Boolean questions can also be used directly as ordinary when predicates. These patterns use
+the existing EIP model and work with the Java, XML and YAML DSLs.
+
+=== Aggregation and destinations
+
+For xref:eips:aggregate-eip.adoc[Aggregate] and other Java APIs accepting an `Expression`, use
+`new LanguageExpression("semantic", "ref:department")` from `org.apache.camel.model.language`.
+For example, group messages with that expression and a `GroupedBodyAggregationStrategy`,
+using `completionSize(10)` and `completionTimeout(5000)` to bound the group. Include a trusted
+tenant or case identifier in the correlation key when messages must remain isolated.
+
+A completion predicate can be obtained with
+`context.resolveLanguage("semantic").createPredicate("ref:actionable")`.
+The aggregation strategy must first put the accumulated conversation in the selected state.
+Use `completionPredicate(predicate).completionSize(10)` to retain a deterministic size limit.
+
+Classify once, then map the stored label to destinations supplied by the route author:
+
+[source,java]
+----
+from("direct:dispatch")
+    .setProperty("department").language("semantic", "ref:department")
+    .process(exchange -> {
+        String department = exchange.getProperty("department", String.class);
+        exchange.getMessage().setHeader("recipients",
+            Map.of("billing", "direct:billing,direct:audit",
+                   "technical", "direct:technical").get(department));
+        exchange.getMessage().setHeader("slip",
+            Map.of("billing", "direct:invoice,direct:archive",
+                   "technical", "direct:diagnose,direct:archive").get(department));
+        exchange.getMessage().setHeader("resource",
+            Map.of("billing", "direct:billingKnowledge",
+                   "technical", "direct:technicalKnowledge").get(department));
+    })
+    .recipientList(header("recipients")).end()
+    .routingSlip(header("slip"))
+    .enrich().header("resource").aggregationStrategy((original, resource) -> {
+        original.getMessage().setHeader("knowledge", resource.getMessage().getBody());
+        return original;
+    });
+----
+
+=== Changed state and sorting
+
+A loop must have a finite budget in addition to its semantic predicate. Combine the predicate
+with a check of `CamelLoopIndex`, and let the loop body update the selected state. Each new
+iteration evaluates the current content. For example, a predicate can first check that
+`exchange.getProperty(Exchange.LOOP_INDEX, 0, Integer.class) < 5` and then call the boolean
+semantic predicate, before `loopDoWhile` invokes a refinement processor.
+
+For sorting, evaluate `ref:urgency` once for each item and build a list containing each item and
+its score. Use `.sort(body(), Comparator.comparingDouble(ScoredItem::score))` with that stored
+score. The comparator must not call the provider: sorting can compare an item multiple times
+and in an implementation-dependent order.
+
+=== Bounded semantic retry
+
+A boolean question can supply the xref:manual::exception-advanced.adoc[retryWhile predicate]
+for an exception clause. Ask whether another attempt is worthwhile using the current failure
+and selected request context. Restrict this to operations that are safe to retry.
+
+IMPORTANT: `retryWhile` replaces the `maximumRedeliveries` decision. Check the retry budget
+inside the predicate, before calling the provider. A provider that always returns true must
+not cause unlimited retries or evaluations.
+
+Declare a boolean question named `retryable` with `state: $\{exchangeProperty.retryState}`.
+In this example, the request body is a string. `onExceptionOccurred` prepares the state before
+the retry predicate runs; `onRedelivery` runs later and is too late for this purpose.
+Include only the failure details needed by the question, excluding credentials and sensitive data.
+
+[source,java]
+----
+Predicate retryable = context.resolveLanguage("semantic").createPredicate("ref:retryable");
+
+onException(IOException.class)
+    .onExceptionOccurred(exchange -> {
+        Exception failure = exchange.getProperty(Exchange.EXCEPTION_CAUGHT, Exception.class);
+        exchange.setProperty("retryState", Map.of(
+            "request", exchange.getMessage().getBody(String.class),
+            "failureType", failure.getClass().getSimpleName(),
+            "attempt", exchange.getMessage().getHeader(Exchange.REDELIVERY_COUNTER, Integer.class)));
+    })
+    .retryWhile(exchange ->
+        exchange.getMessage().getHeader(Exchange.REDELIVERY_COUNTER, 0, Integer.class) <= 3
+            && retryable.matches(exchange))
+    .redeliveryDelay(1000)
+    .handled(true)
+    .to("direct:escalate");
+----
+
+The counter starts at one when deciding the first redelivery. This permits at most three
+redeliveries after the initial attempt. Each failure refreshes the selected state; redelivery
+restarts at the failed processor, not at the beginning of the route. A negative decision or
+an exhausted budget sends the message to `direct:escalate` through normal exception handling.
+
+Evaluation errors are distinct from a negative decision. If the retry predicate throws,
+Camel reports the evaluation failure on the exchange; it does not automatically execute
+the exception clause's escalation route. Arrange caller or supervising-route handling for
+that failure. Bound provider timeouts as well as the retry count.
+
+=== Contextual action validation
+
+Use a semantic question for an additional check such as "Does this proposed action serve
+the approved task?" after normal identity, permission and tenant checks have succeeded.
+The semantic decision must not grant permissions that those checks denied.
+
+For example, declare this question alongside routes:
+
+[source,yaml]
+----
+- semantic:
+    question:
+      withinScope:
+        type: boolean
+        instructions: Does the proposed action serve the approved task?
+        state: ${body}
+        threshold: 0.8
+        uncertainty: 0.05
+        uncertaintyPolicy: fail
+----
+
+The selected state should contain the approved task and the proposed action. Obtain the
+approved task from trusted application state; do not let the proposed action redefine it.

Review Comment:
   Optional, non-blocking: the proposed action is untrusted text that can try to steer the provider, so it's worth naming that risk here. The page already says the semantic decision must not grant permissions. This just says why.
   
   ```suggestion
   The selected state should contain the approved task and the proposed action. Obtain the
   approved task from trusted application state; do not let the proposed action redefine it.
   Treat the proposed action as untrusted input: its text can attempt to steer the provider
   (prompt injection), so a positive decision may only narrow what the permission checks allowed.
   ```



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@camel.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] CAMEL-24977: Add provider-independent semantic evaluation [camel]

Posted by "luigidemasi (via GitHub)" <gi...@apache.org>.
luigidemasi commented on code in PR #26813:
URL: https://github.com/apache/camel/pull/26813#discussion_r4091901593


##########
core/camel-core-model/src/main/java/org/apache/camel/model/ChoiceDefinition.java:
##########
@@ -45,10 +47,14 @@
           description = "Routes messages to different steps based on a series of conditions (predicates),"
                         + " similar to if-elseif-else in Java. Each condition is evaluated in order until one matches.")
 @XmlRootElement(name = "choice")
-@XmlType(propOrder = { "whenClauses", "otherwise" })
+@XmlType(propOrder = { "selector", "whenClauses", "otherwise" })
 @XmlAccessorType(XmlAccessType.FIELD)
 public class ChoiceDefinition extends NoOutputDefinition<ChoiceDefinition> {
 
+    @XmlElement
+    @Metadata(description = "Expression evaluated once per entry into this choice. Its String result is matched against literal when values. Cannot be combined with precondition mode or predicate branches.")
+    @DslArg
+    private ExpressionSubElementDefinition selector;

Review Comment:
   Removed in 7c10c75e1bf5. Category evaluation now uses Set Property/Set Header followed by ordinary Choice predicates. The core EIP model, Choice runtime, serializers, metadata and schemas match the original base. Tests cover one evaluation per step, first/later/otherwise branches and reevaluation after changed input in a loop.
   
   _AI-generated by Codex on behalf of [luigidemasi](https://github.com/luigidemasi)._



##########
core/camel-core-model/src/main/java/org/apache/camel/model/BasicExpressionNode.java:
##########
@@ -36,7 +36,7 @@ public abstract class BasicExpressionNode<T extends BasicExpressionNode<T>> exte
 
     @Metadata(required = true,
               description = "Expression used as the predicate to evaluate whether this when should trigger and route the message or not.")
-    @XmlElementRef
+    @XmlElementRef(required = false)

Review Comment:
   Restored in 7c10c75e1bf5, including `@XmlElementRef(required = true)`. Both XML schemas and the Choice/When YAML definitions match the original base; `when` continues to require its expression.
   
   _AI-generated by Codex on behalf of [luigidemasi](https://github.com/luigidemasi)._



##########
core/camel-core-processor/src/main/java/org/apache/camel/processor/ChoiceProcessor.java:
##########
@@ -71,7 +97,7 @@ public boolean process(final Exchange exchange, final AsyncCallback callback) {
             // as we should only pick one processor
             boolean matches = false;
             try {
-                matches = filter.matches(exchange);
+                matches = (selector == null || values.get(i).equals(selected)) && filter.matches(exchange);

Review Comment:
   The selector path is removed in 7c10c75e1bf5. `ChoiceProcessor` matches the original base, including its predicate evaluation, stream-cache handling and branch counters. Category results are stored before entering the existing Choice processor.
   
   _AI-generated by Codex on behalf of [luigidemasi](https://github.com/luigidemasi)._



##########
dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/pom.xml:
##########
@@ -38,6 +38,10 @@
     </properties>
 
     <dependencies>
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-semantic</artifactId>

Review Comment:
   Fixed in 7c10c75e1bf5. `camel-yaml-dsl-deserializers` has no semantic dependency or semantic-specific resolver case. `camel-semantic` contributes its resolver through the existing provider SPI and declares YAML-common as optional. A default resolver preparse callback prepares whole-resource declarations before routes, including clearing definitions for a valid empty sequence on reload. Schema generation uses a build-plugin dependency.
   
   Isolated consumer runs confirm that ordinary YAML routes run without `camel-semantic`, and semantic predicates run without YAML or SnakeYAML. Declaration ordering, cross-resource references, deletion/rename reload, schema and completion tests pass.
   
   _AI-generated by Codex on behalf of [luigidemasi](https://github.com/luigidemasi)._



##########
core/camel-api/src/main/java/org/apache/camel/spi/PropertyConfigurer.java:
##########
@@ -37,6 +37,24 @@
  */
 public interface PropertyConfigurer {
 
+    /**
+     * Optionally binds a value before resolving bean or class references. Property placeholders are resolved before
+     * this call when placeholder resolution was requested by the caller. This allows an option that owns reference
+     * resolution and lifecycle to retain the reference text. Returning false leaves the normal binding behavior
+     * unchanged.
+     *
+     * @param  camelContext the Camel context
+     * @param  target       the target instance
+     * @param  name         the property name
+     * @param  value        the value before reference resolution
+     * @param  ignoreCase   whether to ignore case for matching the property name
+     * @return              true if the property was configured
+     * @since               4.23
+     */
+    default boolean configureRaw(CamelContext camelContext, Object target, String name, Object value, boolean ignoreCase) {

Review Comment:
   Fixed in 7c10c75e1bf5. Adapter configuration now takes a plain registry bean name or FQCN; registry lookup has precedence. Standard property binding passes those strings through, so `configureRaw` and its call site are removed. Tests retain type checking before class construction, bean identity/lifecycle ownership, placeholder configuration and startup-failure cleanup. Documentation and the issue contract use the unprefixed names.
   
   _AI-generated by Codex on behalf of [luigidemasi](https://github.com/luigidemasi)._



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@camel.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] CAMEL-24977: Add provider-independent semantic evaluation [camel]

Posted by "luigidemasi (via GitHub)" <gi...@apache.org>.
luigidemasi commented on code in PR #26813:
URL: https://github.com/apache/camel/pull/26813#discussion_r4093272016


##########
components/camel-ai/camel-semantic/src/main/java/org/apache/camel/semantic/SemanticQuestions.java:
##########
@@ -0,0 +1,88 @@
+/*
+ * 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;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.spi.Resource;
+
+/** Context-local named questions, replaced atomically per source when a route resource is reloaded. */
+public final class SemanticQuestions {
+    private final Map<String, Map<String, SemanticQuestion>> sources = new HashMap<>();
+    private final Map<String, Resource> resources = new HashMap<>();
+    private volatile Map<String, SemanticQuestion> questions = Map.of();
+
+    public static SemanticQuestions get(CamelContext context) {
+        synchronized (context) {

Review Comment:
   Addressed in `7aa7aed20d23`. Question-registry creation uses a private creation lock. Adapter construction, check-and-bind and identity-checked unbinding use a private context-local monitor shared by language instances. Lifecycle callbacks run outside that ownership monitor. Tests verify public context/registry monitors cannot block initialization, competing instances cannot create duplicate adapters, and a blocked constructor does not block another context.
   
   _AI-generated by Codex on behalf of [luigidemasi](https://github.com/luigidemasi)._



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@camel.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] CAMEL-24977: Add provider-independent semantic evaluation [camel]

Posted by "luigidemasi (via GitHub)" <gi...@apache.org>.
luigidemasi commented on PR #26813:
URL: https://github.com/apache/camel/pull/26813#issuecomment-5813555347

   @davsclaus, on item 8 in your [earlier review](https://github.com/apache/camel/pull/26813#pullrequestreview-5301532081): the top-level question declarations are resource-scoped YAML DSL configuration. Registration happens through the optional resolver `preParse` callback invoked by `YamlRoutesBuilderLoader`, so definitions are available even when they appear after route references, and reload can replace or remove them by resource. Calling the node constructor alone validates the declaration but does not register it; other loaders must invoke the resource callback to support these declarations. This PR does not add semantic question elements to the core route model. Embedded applications can register definitions through the documented `SemanticQuestions.get(context).replace(source, questions)` API.
   
   _AI-generated by Codex on behalf of [luigidemasi](https://github.com/luigidemasi)._


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@camel.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] CAMEL-24977: Add semantic evaluation and Choice selectors [camel]

Posted by "luigidemasi (via GitHub)" <gi...@apache.org>.
luigidemasi commented on code in PR #26813:
URL: https://github.com/apache/camel/pull/26813#discussion_r4090767592


##########
components/camel-ai/camel-semantic/src/main/java/org/apache/camel/language/semantic/SemanticLanguage.java:
##########
@@ -0,0 +1,301 @@
+/*
+ * 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.language.semantic;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.CamelContextAware;
+import org.apache.camel.Exchange;
+import org.apache.camel.Expression;
+import org.apache.camel.Predicate;
+import org.apache.camel.RuntimeCamelException;
+import org.apache.camel.semantic.SemanticAdapter;
+import org.apache.camel.semantic.SemanticQuestion;
+import org.apache.camel.semantic.SemanticQuestions;
+import org.apache.camel.semantic.SemanticResult;
+import org.apache.camel.spi.Metadata;
+import org.apache.camel.spi.annotations.Language;
+import org.apache.camel.support.ExpressionAdapter;
+import org.apache.camel.support.LanguageSupport;
+import org.apache.camel.support.service.ServiceHelper;
+import org.apache.camel.support.service.ServiceSupport;
+
+/** Evaluates a named, provider-independent question against selected message state. */
+@Language(value = "semantic", modelName = "language")
+@Metadata(title = "Semantic", description = "Evaluate named semantic questions through a provider adapter",
+          label = "language,ai", firstVersion = "4.23.0")
+public class SemanticLanguage extends LanguageSupport {
+    public static final String RESULT = "CamelSemanticResult";
+    public static final String ADAPTER_NAME = "camelSemanticAdapter";
+    public static final String ADAPTER_RESOURCE = "META-INF/services/org.apache.camel.semantic.SemanticAdapter";
+
+    private String adapter;
+    private String defaultState = "${body}";
+    private SemanticAdapter selectedAdapter;
+
+    public String getAdapter() {
+        return adapter;
+    }
+
+    /**
+     * Adapter registry reference (#bean:name) or implementation class (plain FQCN or #class:FQCN). Absent selects the
+     * sole advertised adapter.
+     */
+    public void setAdapter(String adapter) {
+        this.adapter = adapter;
+    }
+
+    public String getDefaultState() {
+        return defaultState;
+    }
+
+    /** Default Simple state selector for questions without their own selector. Defaults to the body. */
+    public void setDefaultState(String defaultState) {
+        this.defaultState = defaultState;
+    }
+
+    @Override
+    public Expression createExpression(String expression) {
+        return createEvaluation(expression, false);
+    }
+
+    @Override
+    public Predicate createPredicate(String expression) {
+        return createEvaluation(expression, true);
+    }
+
+    public boolean validateExpression(String expression) {
+        if (expression == null || !expression.startsWith("ref:") || expression.substring(4).isBlank()) {
+            throw new IllegalArgumentException("Semantic expression must reference a named question using ref:name");
+        }
+        return true;
+    }
+
+    public boolean validatePredicate(String expression) {
+        return validateExpression(expression);
+    }
+
+    private Evaluation createEvaluation(String expression, boolean predicate) {
+        validateExpression(expression);
+        Evaluation evaluation = new Evaluation(expression.substring(4), predicate);
+        if (getCamelContext() != null) {
+            evaluation.init(getCamelContext());
+        }
+        return evaluation;
+    }
+
+    private synchronized SemanticAdapter adapter() {
+        if (selectedAdapter != null) {
+            return selectedAdapter;
+        }
+        CamelContext context = getCamelContext();
+        String configured = adapter == null ? null : context.resolvePropertyPlaceholders(adapter);
+        if (configured != null && configured.startsWith("#bean:")) {
+            Object bean = context.getRegistry().lookupByName(configured.substring(6));
+            if (!(bean instanceof SemanticAdapter found)) {
+                throw new IllegalArgumentException("Semantic adapter bean is missing or does not implement SemanticAdapter");
+            }
+            selectedAdapter = found;
+            return found;
+        }
+        ManagedAdapter owned = null;
+        try {
+            String className = configured;
+            if (className == null) {
+                Set<String> candidates = new TreeSet<>();
+                Enumeration<URL> resources = context.getClassResolver().loadAllResourcesAsURL(ADAPTER_RESOURCE);
+                while (resources.hasMoreElements()) {
+                    try (BufferedReader reader = new BufferedReader(
+                            new InputStreamReader(
+                                    resources.nextElement().openStream(), StandardCharsets.UTF_8))) {
+                        reader.lines().map(line -> line.split("#", 2)[0].trim()).filter(line -> !line.isEmpty())
+                                .forEach(candidates::add);
+                    }
+                }
+                if (candidates.size() != 1) {
+                    throw new IllegalArgumentException(
+                            "Semantic language requires exactly one advertised adapter; found "
+                                                       + candidates + ". Configure camel.language.semantic.adapter explicitly");
+                }
+                className = candidates.iterator().next();
+            }
+            if (className.startsWith("#class:")) {
+                className = className.substring(7);
+            }
+            Class<? extends SemanticAdapter> type
+                    = context.getClassResolver().resolveMandatoryClass(className).asSubclass(SemanticAdapter.class);
+            synchronized (context.getRegistry()) {
+                if (context.getRegistry().lookupByName(ADAPTER_NAME) != null) {
+                    throw new IllegalArgumentException("Semantic adapter registry name is already bound: " + ADAPTER_NAME);
+                }
+                SemanticAdapter instance = context.getInjector().newInstance(type);
+                CamelContextAware.trySetCamelContext(instance, context);
+                owned = new ManagedAdapter(context, instance);
+                context.getRegistry().bind(ADAPTER_NAME, instance);
+            }
+            context.addService(owned, true, true);
+            selectedAdapter = owned.instance;
+            return selectedAdapter;
+        } catch (Exception failure) {
+            if (owned != null) {
+                try {
+                    context.removeService(owned);
+                    ServiceHelper.stopAndShutdownService(owned);
+                } catch (Exception cleanup) {
+                    failure.addSuppressed(cleanup);
+                }
+            }
+            throw RuntimeCamelException.wrapRuntimeCamelException(failure);
+        }
+    }
+
+    private static final class ManagedAdapter extends ServiceSupport {
+        private final CamelContext context;
+        private final SemanticAdapter instance;
+
+        private ManagedAdapter(CamelContext context, SemanticAdapter instance) {
+            this.context = context;
+            this.instance = instance;
+        }
+
+        @Override
+        protected void doInit() throws Exception {
+            ServiceHelper.initService(instance);
+        }
+
+        @Override
+        protected void doStart() throws Exception {
+            ServiceHelper.startService(instance);
+        }
+
+        @Override
+        protected void doStop() throws Exception {
+            ServiceHelper.stopService(instance);
+        }
+
+        @Override
+        protected void doShutdown() throws Exception {
+            try {
+                ServiceHelper.stopAndShutdownService(instance);
+            } finally {
+                if (context.getRegistry().lookupByName(ADAPTER_NAME) == instance) {
+                    context.getRegistry().unbind(ADAPTER_NAME);
+                }
+            }
+        }
+    }
+
+    private final class Evaluation extends ExpressionAdapter {
+        private final String name;
+        private final boolean predicate;
+        private volatile Compiled compiled;
+        private SemanticQuestions questions;
+        private SemanticAdapter provider;

Review Comment:
   Addressed in [0c5ba894fe80](https://github.com/apache/camel/commit/0c5ba894fe801efd3c184fa6f574394fc2d003ce). Both `questions` and `provider` are now `volatile`, making their visibility explicit. Initialization still needs to complete before evaluation; the change does not make those two operations atomic. The semantic module tests pass.
   
   _AI-generated by Codex on behalf of [luigidemasi](https://github.com/luigidemasi)._



##########
components/camel-ai/camel-typesafe-ai/src/main/java/org/apache/camel/component/typesafeai/TypeSafeAiSemanticAdapter.java:
##########
@@ -0,0 +1,86 @@
+/*
+ * 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.component.typesafeai;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.CamelContextAware;
+import org.apache.camel.semantic.SemanticAdapter;
+import org.apache.camel.semantic.SemanticQuestion;
+import org.apache.camel.semantic.SemanticResult;
+import org.apache.camel.util.json.JsonObject;
+
+/** Maps common questions to TypeSafe AI using the component's configured, managed transport. */
+public class TypeSafeAiSemanticAdapter implements SemanticAdapter, CamelContextAware {
+    private CamelContext camelContext;
+    private volatile TypeSafeAiEndpoint endpoint;
+
+    @Override
+    public CamelContext getCamelContext() {
+        return camelContext;
+    }
+
+    @Override
+    public void setCamelContext(CamelContext camelContext) {
+        this.camelContext = camelContext;
+    }
+
+    @Override
+    public void validate(SemanticQuestion question) {
+        if (question.getType() == SemanticQuestion.Type.CHOICE && question.getCriteria().size() > 255
+                || question.getType() == SemanticQuestion.Type.SCORE && question.getLevels().size() > 10) {
+            throw new IllegalArgumentException("TypeSafe AI supports at most 255 choice criteria or 10 score levels");
+        }
+        if (endpoint == null) {
+            synchronized (this) {
+                if (endpoint == null) {
+                    endpoint = camelContext.getEndpoint("typesafe-ai:semantic", TypeSafeAiEndpoint.class);
+                }
+            }
+        }
+    }
+
+    @Override
+    public SemanticResult evaluate(SemanticQuestion question, Object state) throws Exception {
+        Map<String, Object> definition = new HashMap<>();
+        definition.put("instructions", question.getInstructions());
+        String type = switch (question.getType()) {
+            case BOOLEAN -> "noul";
+            case CHOICE -> "choice";
+            case SCORE -> "score";
+        };
+        definition.put("type", type);
+        if (question.getType() == SemanticQuestion.Type.SCORE) {
+            definition.put("criteria", question.getLevels());
+        } else if (!question.getCriteria().isEmpty()) {
+            definition.put("criteria", question.getCriteria());
+        }
+        JsonObject response = endpoint.evaluate(Map.of("state", state, "questions", Map.of("question", definition)));

Review Comment:
   Addressed in [0c5ba894fe80](https://github.com/apache/camel/commit/0c5ba894fe801efd3c184fa6f574394fc2d003ce). `evaluate()` now obtains the endpoint through a synchronized lazy initializer and checks the question capabilities itself. `validate()` only checks capabilities, so direct evaluation no longer depends on validation initializing the transport. A missing Camel context produces an explicit `IllegalStateException`. Added a regression test that evaluates without prior validation and verifies the configured credentials reach the HTTP fixture. All 134 TypeSafe AI tests pass.
   
   _AI-generated by Codex on behalf of [luigidemasi](https://github.com/luigidemasi)._



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@camel.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] CAMEL-24977: Add semantic evaluation and Choice selectors [camel]

Posted by "davsclaus (via GitHub)" <gi...@apache.org>.
davsclaus commented on code in PR #26813:
URL: https://github.com/apache/camel/pull/26813#discussion_r4091285414


##########
core/camel-core-model/src/main/java/org/apache/camel/model/BasicExpressionNode.java:
##########
@@ -36,7 +36,7 @@ public abstract class BasicExpressionNode<T extends BasicExpressionNode<T>> exte
 
     @Metadata(required = true,
               description = "Expression used as the predicate to evaluate whether this when should trigger and route the message or not.")
-    @XmlElementRef
+    @XmlElementRef(required = false)

Review Comment:
   This is the change I would most like to see reverted, and the scope split removes it.
   
   Relaxing `@XmlElementRef` to `required = false` turns `<xs:choice>` into `<xs:choice minOccurs="0"/>` in both `camel-xml-io.xsd` and `camel-spring.xsd`. `WhenDefinition` is the only subclass, so the blast radius is contained — but the effect is that a `<when>` with no predicate stops being a schema error and becomes a route-build error instead, and every XML author and IDE loses that validation at edit time. That is a real regression in the editing experience for a twenty-year-old element.
   
   A separate EIP with its own branch element carrying a `value` attribute leaves `WhenDefinition.expression` required and this line untouched.



##########
core/camel-core-model/src/main/java/org/apache/camel/model/ChoiceDefinition.java:
##########
@@ -45,10 +47,14 @@
           description = "Routes messages to different steps based on a series of conditions (predicates),"
                         + " similar to if-elseif-else in Java. Each condition is evaluated in order until one matches.")
 @XmlRootElement(name = "choice")
-@XmlType(propOrder = { "whenClauses", "otherwise" })
+@XmlType(propOrder = { "selector", "whenClauses", "otherwise" })
 @XmlAccessorType(XmlAccessType.FIELD)
 public class ChoiceDefinition extends NoOutputDefinition<ChoiceDefinition> {
 
+    @XmlElement
+    @Metadata(description = "Expression evaluated once per entry into this choice. Its String result is matched against literal when values. Cannot be combined with precondition mode or predicate branches.")
+    @DslArg
+    private ExpressionSubElementDefinition selector;

Review Comment:
   **Blocking — please take this out of the PR.** This field, the `when(String)` method, the `@XmlType(propOrder)` reordering and the `preCreateProcessor()` null-skip together change the model of a foundational EIP that has been stable for twenty years, along with its generated `choice.json`, its XSD block and its YAML schema.
   
   `camel-semantic` does not need it — `choice().when(semantic("ref:isSpam"))` already works against unmodified Choice, and a category question routes with `setHeader` plus `simple`.
   
   When the value-dispatch EIP is proposed on its own ticket, a standalone definition is entirely achievable: `OtherwiseDefinition` is already a standalone `@XmlRootElement` that `ChoiceDefinition` references through a plain `@XmlElement` field, so a new parent can reuse it unchanged, and the registration points in `ProcessorReifier` and `DefaultManagementObjectStrategy` are additive branches. Please do not reach for a shared base class — inserting an abstract parent between `ChoiceDefinition` and `NoOutputDefinition` changes choice's `<xs:extension base=...>` in the generated XSD, which is itself a change to Choice.



##########
core/camel-core-processor/src/main/java/org/apache/camel/processor/ChoiceProcessor.java:
##########
@@ -71,7 +97,7 @@ public boolean process(final Exchange exchange, final AsyncCallback callback) {
             // as we should only pick one processor
             boolean matches = false;
             try {
-                matches = filter.matches(exchange);
+                matches = (selector == null || values.get(i).equals(selected)) && filter.matches(exchange);

Review Comment:
   Two things here, both resolved by the split.
   
   **Choice's hot path.** `ChoiceProcessor.process()` is one of the most-executed methods in Camel. Adding a selector branch and a per-iteration `selector == null` guard to it, for a feature most routes never use, is exactly the kind of change a foundational EIP should not have to absorb.
   
   **It is also the slower design.** To reach branch N this builds a `FilterProcessor` per branch wrapping an `exchange -> true` predicate, so it costs N calls to `filter.matches()` plus N calls to `MessageHelper.resetStreamCache()` on the way — and those resets are pure waste, since a predicate that ignores the exchange never reads the body. A dedicated processor for the follow-up EIP can be a `Map<String, Processor>` lookup: O(1), no dummy predicates, no resets.
   
   One detail worth carrying forward when you build that processor: reusing `FilterProcessor` here is what keeps `getFilteredCount()` populated for the JMX `extendedInformation()` table, so a standalone processor will need to track per-branch counts itself.



##########
core/camel-core-reifier/src/main/java/org/apache/camel/reifier/ChoiceReifier.java:
##########
@@ -49,6 +52,25 @@ public ChoiceReifier(Route route, ProcessorDefinition<?> definition) {
     @Override
     public Processor createProcessor() throws Exception {
         final boolean isPrecondition = Boolean.TRUE == parseBoolean(definition.getPrecondition());
+        boolean selecting = definition.getSelector() != null;
+        if (selecting && isPrecondition) {
+            throw new IllegalArgumentException("Choice selector cannot be combined with precondition mode");
+        }
+        Set<String> uniqueValues = new HashSet<>();
+        for (WhenDefinition when : definition.getWhenClauses()) {
+            if (selecting) {
+                if (when.getValue() == null || when.getExpression() != null) {
+                    throw new IllegalArgumentException("Choice selector requires literal when values without predicates");
+                }
+                if (!uniqueValues.add(parseString(when.getValue()))) {
+                    throw new IllegalArgumentException("Choice selector has duplicate when values");
+                }
+            } else if (when.getValue() != null || when.getExpression() == null) {
+                throw new IllegalArgumentException("Choice without a selector requires when predicates without values");
+            }
+        }
+        Expression selector = selecting ? createExpression(definition.getSelector().getExpressionType()) : null;

Review Comment:
   This file comes out with the scope split, but the point is worth carrying to the follow-up EIP: `definition.getSelector().getExpressionType()` is dereferenced without a null check, so a `<selector/>` element with no nested expression NPEs during route build instead of producing one of the clear `IllegalArgumentException`s this method raises for every other malformed combination just above.
   
   Given how carefully the surrounding validation reports the selector/predicate/precondition conflicts, a matching message would be consistent — something along the lines of "selector requires an expression".



##########
components/camel-ai/camel-semantic/src/main/java/org/apache/camel/semantic/SemanticQuestions.java:
##########
@@ -0,0 +1,88 @@
+/*
+ * 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;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.spi.Resource;
+
+/** Context-local named questions, replaced atomically per source when a route resource is reloaded. */
+public final class SemanticQuestions {
+    private final Map<String, Map<String, SemanticQuestion>> sources = new HashMap<>();
+    private final Map<String, Resource> resources = new HashMap<>();
+    private volatile Map<String, SemanticQuestion> questions = Map.of();
+
+    public static SemanticQuestions get(CamelContext context) {
+        synchronized (context) {

Review Comment:
   Locking on the `CamelContext` instance itself. It is a shared, publicly reachable object, so any other code that ever locks on the same monitor can deadlock against this, and the lock is held far wider than the map access it is protecting.
   
   I grepped `core/` and `components/` excluding tests: there is **no other non-test code in the codebase that synchronizes on a `CamelContext`**, so this is a new pattern rather than an existing convention. SonarCloud flags it as java:S2445 ("blocks should be synchronized on private final fields").
   
   A private static lock, or a compute-if-absent style access on the context plugin map, would scope this correctly. The same applies to `synchronized (context.getRegistry())` in `SemanticLanguage.adapter()` — likewise with no precedent elsewhere in the tree.



##########
dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/pom.xml:
##########
@@ -38,6 +38,10 @@
     </properties>
 
     <dependencies>
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-semantic</artifactId>

Review Comment:
   **Blocking (the dependency-direction question you raised).** A compile-scope dependency here makes `camel-semantic` — and its `camel-core-languages` dependency — transitive for *every* YAML DSL user: JBang, Spring Boot, Quarkus, Kamelets. Combined with the unconditional `SemanticDefinitionDeserializer.configure(...)` call in `YamlRoutesBuilderLoader.preConfigureNode`, every YAML resource pays a semantic preparse pass whether or not it declares `semantic:`.
   
   The downstream consequence you asked about is real: `camel-quarkus-yaml-dsl` would pull in an artifact with no corresponding Quarkus extension, and the camel-spring-boot starter dependency graph changes for all YAML users.
   
   Suggested direction: resolve the `semantic` node lazily through `FactoryFinder`/SPI so this module keeps no compile-scope dependency on an AI component, and express the resource-wide preparse hook as an interface in `camel-yaml-dsl-common` that `camel-semantic` implements. That keeps the layering pointing the usual way — components depend on the DSL, not the reverse.



##########
components/camel-ai/camel-semantic/src/main/java/org/apache/camel/language/semantic/SemanticLanguageConfigurer.java:
##########
@@ -0,0 +1,40 @@
+/*
+ * 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.language.semantic;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.spi.PropertyConfigurer;
+
+/** Preserves adapter references so the language can validate their type and manage only instances it creates. */
+public class SemanticLanguageConfigurer implements PropertyConfigurer {
+    @Override
+    public boolean configureRaw(CamelContext context, Object target, String name, Object value, boolean ignoreCase) {
+        if (ignoreCase ? "adapter".equalsIgnoreCase(name) : "adapter".equals(name)) {
+            if (!(value instanceof String)) {
+                throw new IllegalArgumentException("Semantic adapter selection must be a bean reference or class name");
+            }
+            ((SemanticLanguage) target).setAdapter((String) value);
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public boolean configure(CamelContext context, Object target, String name, Object value, boolean ignoreCase) {

Review Comment:
   `configure()` returns `false` unconditionally, so `defaultState` — and `adapter` on any path that does not go through `configureRaw` — always falls back to reflection binding in `PropertyBindingSupport`. Generated configurers exist precisely to keep configuration reflection-free for GraalVM native and `camel-main` fast startup; this hand-written one gives that up for the whole language.
   
   Two related points:
   
   - No other language in the repo ships a `PropertyConfigurer` at all — I could not find a single `*-language` entry under any `META-INF/services/org/apache/camel/configurer/` directory. This is a new pattern for languages, not an existing one being followed.
   - The two service files are hand-maintained under `src/main/resources`, whereas every other configurer service file in the tree lives under `src/generated/resources` and is owned by the package plugin.
   
   If the `configureRaw` hook is dropped per the `PropertyConfigurer` comment, this class and both service files disappear with it. If it is kept, `configure()` should at least bind `defaultState` properly rather than forcing reflection.



##########
components/camel-ai/camel-semantic/src/main/java/org/apache/camel/language/semantic/SemanticLanguage.java:
##########
@@ -0,0 +1,307 @@
+/*
+ * 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.language.semantic;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.CamelContextAware;
+import org.apache.camel.Exchange;
+import org.apache.camel.Expression;
+import org.apache.camel.Predicate;
+import org.apache.camel.RuntimeCamelException;
+import org.apache.camel.semantic.SemanticAdapter;
+import org.apache.camel.semantic.SemanticQuestion;
+import org.apache.camel.semantic.SemanticQuestions;
+import org.apache.camel.semantic.SemanticResult;
+import org.apache.camel.spi.Metadata;
+import org.apache.camel.spi.annotations.Language;
+import org.apache.camel.support.ExpressionAdapter;
+import org.apache.camel.support.LanguageSupport;
+import org.apache.camel.support.service.ServiceHelper;
+import org.apache.camel.support.service.ServiceSupport;
+
+/** Evaluates a named, provider-independent question against selected message state. */
+@Language(value = "semantic", modelName = "language")
+@Metadata(title = "Semantic", description = "Evaluate named semantic questions through a provider adapter",
+          label = "language,ai", firstVersion = "4.23.0")
+public class SemanticLanguage extends LanguageSupport {
+    public static final String RESULT = "CamelSemanticResult";
+    public static final String ADAPTER_NAME = "camelSemanticAdapter";
+    public static final String ADAPTER_RESOURCE = "META-INF/services/org.apache.camel.semantic.SemanticAdapter";
+
+    private String adapter;
+    private String defaultState = "${body}";
+    private SemanticAdapter selectedAdapter;
+
+    public String getAdapter() {
+        return adapter;
+    }
+
+    /**
+     * Adapter registry reference (#bean:name) or implementation class (plain FQCN or #class:FQCN). Absent selects the
+     * sole advertised adapter.
+     */
+    public void setAdapter(String adapter) {
+        this.adapter = adapter;
+    }
+
+    public String getDefaultState() {
+        return defaultState;
+    }
+
+    /** Default Simple state selector for questions without their own selector. Defaults to the body. */
+    public void setDefaultState(String defaultState) {
+        this.defaultState = defaultState;
+    }
+
+    @Override
+    public Expression createExpression(String expression) {
+        return createEvaluation(expression, false);
+    }
+
+    @Override
+    public Predicate createPredicate(String expression) {
+        return createEvaluation(expression, true);
+    }
+
+    public boolean validateExpression(String expression) {
+        if (expression == null || !expression.startsWith("ref:") || expression.substring(4).isBlank()) {
+            throw new IllegalArgumentException("Semantic expression must reference a named question using ref:name");
+        }
+        return true;
+    }
+
+    public boolean validatePredicate(String expression) {
+        return validateExpression(expression);
+    }
+
+    private Evaluation createEvaluation(String expression, boolean predicate) {
+        validateExpression(expression);
+        Evaluation evaluation = new Evaluation(expression.substring(4), predicate);
+        if (getCamelContext() != null) {
+            evaluation.init(getCamelContext());
+        }
+        return evaluation;
+    }
+
+    private synchronized SemanticAdapter adapter() {
+        if (selectedAdapter != null) {
+            return selectedAdapter;
+        }
+        CamelContext context = getCamelContext();
+        String configured = adapter == null ? null : context.resolvePropertyPlaceholders(adapter);
+        if (configured != null && configured.startsWith("#") && !configured.startsWith("#class:")) {
+            String name = configured.startsWith("#bean:") ? configured.substring(6) : configured.substring(1);
+            if (name.contains(":")) {
+                throw new IllegalArgumentException("Semantic adapter reference must use #bean:name, #name or #class:FQCN");
+            }
+            Object bean = context.getRegistry().lookupByName(name);
+            if (!(bean instanceof SemanticAdapter found)) {
+                throw new IllegalArgumentException("Semantic adapter bean is missing or does not implement SemanticAdapter");
+            }
+            selectedAdapter = found;
+            return found;
+        }
+        ManagedAdapter owned = null;
+        try {
+            String className = configured;
+            if (className == null) {
+                Set<String> candidates = new TreeSet<>();
+                Enumeration<URL> resources = context.getClassResolver().loadAllResourcesAsURL(ADAPTER_RESOURCE);

Review Comment:
   This hand-rolls service-file discovery: `loadAllResourcesAsURL(...)`, then reading each URL and splitting lines on `#` to strip comments. The resource path chosen (`META-INF/services/org.apache.camel.semantic.SemanticAdapter`) is exactly the JDK `ServiceLoader` convention, and Camel already has `FactoryFinder` for the same job with class-resolver and injector integration.
   
   Two concrete consequences of doing it by hand:
   
   - GraalVM native will not register the resource automatically the way it does for `ServiceLoader`/`FactoryFinder` providers, so adapter discovery is likely to fail in native mode without an explicit resource registration.
   - The manual comment-stripping and trimming reimplements parsing rules `ServiceLoader` already specifies.
   
   If the reason for reading names rather than loading instances is to type-check before construction, `FactoryFinder.findClass(...)` gives you the `Class` without instantiating it.



##########
core/camel-api/src/main/java/org/apache/camel/spi/PropertyConfigurer.java:
##########
@@ -37,6 +37,24 @@
  */
 public interface PropertyConfigurer {
 
+    /**
+     * Optionally binds a value before resolving bean or class references. Property placeholders are resolved before
+     * this call when placeholder resolution was requested by the caller. This allows an option that owns reference
+     * resolution and lifecycle to retain the reference text. Returning false leaves the normal binding behavior
+     * unchanged.
+     *
+     * @param  camelContext the Camel context
+     * @param  target       the target instance
+     * @param  name         the property name
+     * @param  value        the value before reference resolution
+     * @param  ignoreCase   whether to ignore case for matching the property name
+     * @return              true if the property was configured
+     * @since               4.23
+     */
+    default boolean configureRaw(CamelContext camelContext, Object target, String name, Object value, boolean ignoreCase) {

Review Comment:
   **Blocking (the SPI question you raised).** I think this addition can be avoided entirely, which would be the better outcome.
   
   `PropertyBindingSupport.resolveValue` delegates string values to `resolveBean`, and `resolveBean` only transforms a string **that starts with `#`** — a plain FQCN or a plain registry bean name passes through binding completely untouched. So `setAdapter("com.foo.MyAdapter")` and `setAdapter("myAdapterBean")` already reach the language as raw text, with no instantiation and no SPI hook at all.
   
   That means this hook is needed *only* to preserve the `#bean:` and `#class:` prefixed spellings documented in `semantic-language.adoc`. If the language accepts just the unprefixed spellings, you get the same type-check-before-instantiation contract and can drop:
   
   - this `camel-api` SPI method and its `PropertyBindingSupport` call site,
   - the hand-written `SemanticLanguageConfigurer`,
   - the two hand-maintained configurer service files under `src/main/resources`.
   
   Weighed against that, the cost of keeping it is a permanent public SPI method on `camel-api` invoked on every `doSetPropertyValue` for every configurer in the framework, in order to serve one option of one preview language. I do not think that trade holds up — but flagging it for the PMC rather than deciding unilaterally, since you asked for agreement.



##########
dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/main/java/org/apache/camel/dsl/yaml/deserializers/SemanticDefinitionDeserializer.java:
##########
@@ -0,0 +1,188 @@
+/*
+ * 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.dsl.yaml.deserializers;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.dsl.yaml.common.YamlDeserializationContext;
+import org.apache.camel.dsl.yaml.common.YamlDeserializerSupport;
+import org.apache.camel.dsl.yaml.common.exception.YamlDeserializationException;
+import org.apache.camel.semantic.SemanticQuestion;
+import org.apache.camel.semantic.SemanticQuestions;
+import org.apache.camel.spi.CamelContextCustomizer;
+import org.apache.camel.spi.annotations.YamlIn;
+import org.apache.camel.spi.annotations.YamlProperty;
+import org.apache.camel.spi.annotations.YamlType;
+import org.snakeyaml.engine.v2.api.ConstructNode;
+import org.snakeyaml.engine.v2.nodes.Node;
+import org.snakeyaml.engine.v2.nodes.NodeTuple;
+import org.snakeyaml.engine.v2.nodes.SequenceNode;
+
+/** Named semantic declarations are installed in a resource-wide pass before route references are resolved. */
+@YamlIn
+@YamlType(nodes = "semantic", properties = {
+        @YamlProperty(name = "question",
+                      type = "map:org.apache.camel.dsl.yaml.deserializers.SemanticDefinitionDeserializer$QuestionSchema",
+                      required = true)
+})
+public class SemanticDefinitionDeserializer extends YamlDeserializerSupport implements ConstructNode {
+    private static final Set<String> FIELDS
+            = Set.of("type", "instructions", "state", "criteria", "threshold", "uncertainty", "uncertaintyPolicy");
+
+    @Override
+    public Object construct(Node node) {
+        read(node);
+        // Registration happens once for the entire resource, including declarations after routes.
+        return (CamelContextCustomizer) context -> {
+        };
+    }
+
+    public static void configure(CamelContext context, YamlDeserializationContext dc, Node root) {
+        if (!(root instanceof SequenceNode sequence)) {
+            return;
+        }
+        Map<String, SemanticQuestion> definitions = new LinkedHashMap<>();
+        for (Node node : sequence.getValue()) {
+            for (NodeTuple tuple : asMappingNode(node).getValue()) {
+                if ("semantic".equals(asText(tuple.getKeyNode()))) {
+                    read(tuple.getValueNode()).forEach((name, question) -> {
+                        if (definitions.putIfAbsent(name, question) != null) {
+                            throw new IllegalArgumentException("Duplicate semantic question: " + name);
+                        }
+                    });
+                }
+            }
+        }
+        SemanticQuestions.get(context).replace(dc.getResource(), definitions);
+    }
+
+    private static Map<String, SemanticQuestion> read(Node node) {
+        Map<String, Node> semantic = fields(node);
+        if (!semantic.keySet().equals(Set.of("question"))) {
+            throw new IllegalArgumentException("Semantic declaration requires only question");
+        }
+        Map<String, SemanticQuestion> result = new LinkedHashMap<>();
+        fields(semantic.get("question")).forEach((name, definition) -> {
+            Map<String, Node> values = fields(definition);
+            if (!FIELDS.containsAll(values.keySet())) {
+                throw new IllegalArgumentException("Unknown property in semantic question: " + name);
+            }
+            String typeName = asText(values.get("type"));
+            if (typeName == null) {
+                throw new IllegalArgumentException("Semantic question type is required: " + name);
+            }
+            SemanticQuestion.Type type = SemanticQuestion.Type.valueOf(typeName.toUpperCase(Locale.ROOT));

Review Comment:
   The located-diagnostic fix from the previous round reached `number(...)` but not the enum parsing in the same file. `type: foo` throws a bare `IllegalArgumentException: No enum constant org.apache.camel.semantic.SemanticQuestion.Type.FOO` with no question name, resource or line/column — exactly the failure mode that was just fixed for `threshold`.
   
   Same class of gap at lines 109-110: `UncertaintyPolicy.valueOf(asText(values.get("uncertaintyPolicy")).replace(...))` **NPEs** when the key is present but the value is empty, since `asText` returns null before `.replace` is called.
   
   Also unlocated in this file: `"Duplicate semantic question: "`, `"Unknown property in semantic question: "`, `"Semantic question type is required: "` and `"Semantic declaration requires only question"`. Schema validation catches most of these when enabled, but the parameterised tests added in this PR deliberately load without schema validation, so the path is reachable in practice.
   
   Wrapping these the same way `number(...)` now does — `YamlDeserializationException` with the offending `Node` — would make the whole file consistent.



##########
core/camel-management/src/main/java/org/apache/camel/management/mbean/ManagedChoice.java:
##########
@@ -76,8 +76,9 @@ public TabularData extendedInformation() {
                 FilterProcessor filter = filters.get(i);
 
                 CompositeType ct = CamelOpenMBeanTypes.choiceCompositeType();
-                String predicate = when.getExpression().getExpression();
-                String language = when.getExpression().getLanguage();
+                String predicate = when.getValue() != null ? when.getValue() : when.getExpression().getExpression();
+                String language = getDefinition().getSelector() != null
+                        ? getDefinition().getSelector().getExpressionType().getLanguage() : when.getExpression().getLanguage();

Review Comment:
   Same null dereference as in the reifier: `getDefinition().getSelector().getExpressionType().getLanguage()` assumes the selector always carries an expression type. Here it is worse than a build-time failure, because it surfaces while a JMX client is reading `extendedInformation()` — a monitoring call failing with an NPE is hard to diagnose.
   
   This file reverts entirely with the scope split; the null-safety point applies to the new MBean that the follow-up EIP will need.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@camel.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org