luigidemasi commented on code in PR #27494:
URL: https://github.com/apache/camel/pull/27494#discussion_r4210336142


##########
components/camel-ai/camel-semantic/src/test/java/org/apache/camel/semantic/SemanticInitializationTest.java:
##########
@@ -77,6 +76,38 @@ void concurrentQuestionRegistryCreationReturnsOneInstance() 
throws Exception {
         }
     }
 
+    @Test
+    void cachedRegistryLookupDoesNotWaitForAnotherContext() throws Exception {
+        ExecutorService callers = Executors.newFixedThreadPool(2);
+        CountDownLatch entered = new CountDownLatch(1);
+        CountDownLatch release = new CountDownLatch(1);
+        try (var creating = new DefaultCamelContext(); var cached = new 
DefaultCamelContext()) {
+            var expected = SemanticEvaluations.get(cached);
+            
creating.getCamelContextExtension().lazyAddContextPlugin(SemanticEvaluations.class,
 () -> {
+                entered.countDown();
+                try {
+                    assertThat(release.await(30, TimeUnit.SECONDS)).isTrue();
+                } catch (InterruptedException e) {
+                    Thread.currentThread().interrupt();
+                    throw new IllegalStateException(e);
+                }
+                return null;

Review Comment:
   The remaining first-creation serialization is intentional: the private 
creation lock still protects publication when no registry exists. The 
optimization targets already-created registries, which now avoid that lock. The 
existing `concurrentEvaluationRegistryCreationReturnsOneInstance` test 
separately covers concurrent callers creating the same context's registry. We 
are not claiming that simultaneous first creation across different contexts is 
lock-free.
   
   The null supplier is a gate for the lookup: `DefaultContextPluginManager` 
removes a lazy entry whose supplier returns null, and 
`SemanticEvaluations.get()` then creates and registers the missing registry. 
Added a comment explaining that intent in 
[384c0e7f7de1](https://github.com/apache/camel/commit/384c0e7f7de14a473b201a4ef05f6d8ee0a0423d);
 the final non-null assertion is retained. The initialization suite passes.
   
   _AI-generated by Codex on behalf of @luigidemasi._



##########
dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/SimpleChecks.java:
##########
@@ -223,6 +223,7 @@ static boolean isOfTheApplication(String error) {
 
     static boolean isMissingDependency(String error) {
         return error.startsWith("No language could be found for:")
+                || error.equals("The semantic function requires camel-semantic 
on the classpath")

Review Comment:
   Changed to `contains("requires camel-semantic")` in 
[384c0e7f7de1](https://github.com/apache/camel/commit/384c0e7f7de14a473b201a4ef05f6d8ee0a0423d),
 so the check no longer requires identical surrounding wording. Added a 
regression through `SourceValidator.validateYamlSimple()` with a differently 
worded catalog diagnostic; it fails with the old exact match and passes with 
this change. Existing tests still reject malformed semantic arguments. The 
real-diagnostic assertion now checks the same stable phrase. No Simple API 
changes are needed.
   
   _AI-generated by Codex on behalf of @luigidemasi._



##########
components/camel-ai/camel-semantic/src/main/java/org/apache/camel/semantic/SemanticCapabilities.java:
##########
@@ -138,221 +86,309 @@ public String getArtifactId() {
         return artifactId;
     }
 
-    public Set<InputType> getInputTypes() {
-        return inputTypes;
-    }
-
-    public Set<ResultType> getResultTypes() {
-        return resultTypes;
+    public Map<String, Operation> getOperations() {
+        return operations;
     }
 
-    public Instructions getInstructions() {
-        return instructions;
+    public Operation operation(String name) {
+        Operation result = operations.get(name);
+        if (result == null) {
+            throw new IllegalArgumentException(
+                    "Unknown operation '" + name + "'; supported operations: " 
+ operations.keySet());
+        }
+        return result;
     }
 
-    public boolean isCallerDefinedCriteria() {
-        return callerDefinedCriteria;
+    public void validate(SemanticEvaluation evaluation) {
+        
operation(evaluation.getOperation()).validate(evaluation.getParameters());
     }
 
-    public boolean isBooleanProbability() {
-        return booleanProbability;
+    private static String required(String value, String field) {
+        if (value == null || value.isBlank()) {
+            throw new IllegalArgumentException(field + " is required");
+        }
+        return value;
     }
 
-    public boolean isChoiceProbabilities() {
-        return choiceProbabilities;
+    private static boolean withinBounds(Number number, double minimum, double 
maximum) {
+        if (minimum == Double.POSITIVE_INFINITY || maximum == 
Double.NEGATIVE_INFINITY) {
+            return false;
+        }
+        try {
+            // Preserve decimal precision, including values just outside a 
declared boundary.
+            BigDecimal value = new BigDecimal(number.toString());
+            return (minimum == Double.NEGATIVE_INFINITY || 
value.compareTo(BigDecimal.valueOf(minimum)) >= 0)
+                    && (maximum == Double.POSITIVE_INFINITY || 
value.compareTo(BigDecimal.valueOf(maximum)) <= 0);
+        } catch (NumberFormatException invalid) {
+            return false;
+        }
     }
 
-    public Set<ResultType> getConfidenceTypes() {
-        return confidenceTypes;
-    }
+    /** Operation metadata and common validation, independent of any expert 
instance. */
+    public static final class Operation {
+        private final SemanticOperation declaration;
+        private final Set<InputType> inputTypes;
+        private final Set<String> labels;
+        private final Map<String, Parameter> parameters;
+
+        private Operation(SemanticOperation declaration) {
+            this.declaration = declaration;
+            required(declaration.name(), "Operation name");
+            required(declaration.description(), "Operation description");
+            required(declaration.inputRequirements(), "Operation input 
requirements");
+            required(declaration.resultMeaning(), "Operation result meaning");
+            inputTypes = Set.copyOf(List.of(declaration.inputTypes()));
+            labels = Collections.unmodifiableSet(new 
LinkedHashSet<>(List.of(declaration.labels())));
+            if (inputTypes.isEmpty() || Double.isNaN(declaration.minimum()) || 
Double.isNaN(declaration.maximum())
+                    || declaration.minimum() > declaration.maximum() || 
labels.stream().anyMatch(String::isBlank)) {
+                throw new IllegalArgumentException("Invalid operation 
contract: " + declaration.name());
+            }
+            if (declaration.probability() || declaration.probabilities()) {
+                required(declaration.probabilityMeaning(), "Probability 
meaning");
+            }
+            if (declaration.confidence()) {
+                required(declaration.confidenceMeaning(), "Confidence 
meaning");
+            }
+            Map<String, Parameter> values = new LinkedHashMap<>();
+            for (SemanticParameter parameter : declaration.parameters()) {
+                Parameter contract = new Parameter(parameter);
+                if (values.putIfAbsent(contract.getName(), contract) != null) {
+                    throw new IllegalArgumentException("Duplicate parameter: " 
+ contract.getName());
+                }
+            }
+            parameters = Collections.unmodifiableMap(values);
+        }
 
-    public String getProbabilityMeaning() {
-        return probabilityMeaning;
-    }
+        public String getName() {
+            return declaration.name();
+        }
 
-    public String getConfidenceMeaning() {
-        return confidenceMeaning;
-    }
+        public String getDescription() {
+            return declaration.description();
+        }
 
-    public String getTrueMeaning() {
-        return trueMeaning;
-    }
+        public Set<InputType> getInputTypes() {
+            return inputTypes;
+        }
 
-    public int getMaxChoices() {
-        return maxChoices;
-    }
+        public String getInputRequirements() {
+            return declaration.inputRequirements();
+        }
 
-    public int getMaxScoreLevels() {
-        return maxScoreLevels;
-    }
+        public Map<String, Parameter> getParameters() {
+            return parameters;
+        }
 
-    /** Validate the common contract before the provider checks any 
model-specific restrictions. */
-    public void validate(SemanticQuestion question) {
-        if ((!known || instructions == Instructions.REQUIRED) && 
question.getInstructions() == null) {
-            throw new IllegalArgumentException("Question instructions are 
required");
+        public ResultType getResultType() {
+            return declaration.resultType();
         }
-        if (!known) {
-            return;
+
+        public String getResultMeaning() {
+            return declaration.resultMeaning();
         }
-        if 
(!resultTypes.contains(ResultType.valueOf(question.getType().name()))) {
-            throw new IllegalArgumentException(
-                    "Requires " + question.getType() + "; supports " + 
resultTypes + ": " + description);
+
+        public Set<String> getLabels() {
+            return labels;
         }
-        if (instructions == Instructions.UNSUPPORTED && 
question.getInstructions() != null) {
-            throw new IllegalArgumentException("Instructions are unsupported 
by this fixed expert");
+
+        public double getMinimum() {
+            return declaration.minimum();
         }
-        if (!callerDefinedCriteria && (!question.getCriteria().isEmpty() || 
!question.getLevels().isEmpty())) {
-            throw new IllegalArgumentException("Caller-defined criteria and 
score levels are unsupported by this fixed expert");
+
+        public double getMaximum() {
+            return declaration.maximum();
         }
-        if (question.getType() == SemanticQuestion.Type.CHOICE && maxChoices > 0
-                && question.getCriteria().size() > maxChoices) {
-            throw new IllegalArgumentException("Supports at most " + 
maxChoices + " choice criteria");
+
+        public boolean isProbability() {
+            return declaration.probability();
         }
-        if (question.getType() == SemanticQuestion.Type.SCORE && 
maxScoreLevels > 0
-                && question.getLevels().size() > maxScoreLevels) {
-            throw new IllegalArgumentException("Supports at most " + 
maxScoreLevels + " score levels");
+
+        public boolean isProbabilities() {
+            return declaration.probabilities();
         }
-        if (question.getType() == SemanticQuestion.Type.BOOLEAN && 
!booleanProbability
-                && (question.getThreshold() != 0.5 || 
question.getUncertainty() != 0
-                        || question.getUncertaintyPolicy() != 
SemanticQuestion.UncertaintyPolicy.FAIL)) {
-            throw new IllegalArgumentException("Boolean decision policy 
requires positive-class probability support");
+
+        public String getProbabilityMeaning() {
+            return declaration.probabilityMeaning();
         }
-    }
 
-    /** Input shape can only be checked once the state selector has been 
evaluated. */
-    public void validateInput(Object state) {
-        if (known && !(state instanceof String && 
inputTypes.contains(InputType.TEXT)
-                || (state instanceof Map<?, ?> || state instanceof List<?>) && 
inputTypes.contains(InputType.STRUCTURED))) {
-            throw new IllegalArgumentException("Unsupported selected state; 
accepts " + inputTypes + " input");
+        public boolean isConfidence() {
+            return declaration.confidence();
         }
-    }
 
-    /** Named construction of an immutable, explicitly supported contract. */
-    public static final class Builder {
-        private String name;
-        private String description;
-        private String provider;
-        private String artifactId;
-        private Set<InputType> inputTypes = Set.of();
-        private Set<ResultType> resultTypes = Set.of();
-        private Instructions instructions = Instructions.REQUIRED;
-        private boolean callerDefinedCriteria;
-        private boolean booleanProbability;
-        private boolean choiceProbabilities;
-        private Set<ResultType> confidenceTypes = Set.of();
-        private String probabilityMeaning;
-        private String confidenceMeaning;
-        private String trueMeaning;
-        private int maxChoices;
-        private int maxScoreLevels;
+        public String getConfidenceMeaning() {
+            return declaration.confidenceMeaning();
+        }
 
-        private Builder() {
+        public void validate(Map<String, Object> values) {
+            for (String name : values.keySet()) {
+                if (!parameters.containsKey(name)) {
+                    throw new IllegalArgumentException("Unknown parameter '" + 
name + "' for operation '" + getName() + "'");
+                }
+            }
+            parameters.forEach((name, parameter) -> 
parameter.validate(values.get(name), values.containsKey(name)));

Review Comment:
   I checked both entry points. `SemanticEvaluation.immutableMap()` rejects 
null, empty and whitespace-only parameter names before common contract 
validation, and `SemanticCapabilities.Parameter` rejects blank names in the 
expert's annotation. A blank name therefore cannot be a registered parameter.
   
   Direct calls to `Operation.validate()` also reject these keys as unknown 
parameters. I verified all three cases with executable probes. There is no 
bypass; an additional guard here would only change the direct-call diagnostic 
and duplicate the declaration check, so I have left this code unchanged.
   
   _AI-generated by Codex on behalf of @luigidemasi._



##########
components/camel-ai/camel-semantic/src/main/java/org/apache/camel/semantic/yaml/SemanticDefinitionDeserializer.java:
##########
@@ -69,127 +79,150 @@ public void preParse(YamlDeserializationContext dc, Node 
root) {
         if (!(root instanceof SequenceNode sequence)) {
             return;
         }
-        Map<String, SemanticQuestion> definitions = new LinkedHashMap<>();
+        Map<String, SemanticEvaluation> definitions = new LinkedHashMap<>();
         for (Node node : sequence.getValue()) {
             if (!(node instanceof MappingNode mapping)) {
-                // Leave malformed entries to the route loader without 
replacing the resource's questions.
+                // Leave malformed entries to the route loader without 
replacing the resource's evaluations.
                 return;
             }
             for (NodeTuple tuple : mapping.getValue()) {
                 if ("semantic".equals(asText(tuple.getKeyNode()))) {
-                    read(dc.getCamelContext(), 
tuple.getValueNode()).forEach((name, question) -> {
-                        if (definitions.putIfAbsent(name, question) != null) {
+                    read(dc.getCamelContext(), 
tuple.getValueNode()).forEach((name, evaluation) -> {
+                        if (definitions.putIfAbsent(name, evaluation) != null) 
{
                             throw new YamlDeserializationException(
-                                    tuple.getValueNode(), "Duplicate semantic 
question: " + name);
+                                    tuple.getValueNode(), "Duplicate semantic 
evaluation: " + name);
                         }
                     });
                 }
             }
         }
         CamelContext context = dc.getCamelContext();
-        SemanticQuestions questions = definitions.isEmpty()
-                ? 
context.getCamelContextExtension().getContextPlugin(SemanticQuestions.class)
-                : SemanticQuestions.get(context);
-        if (questions != null) {
+        SemanticEvaluations evaluations = definitions.isEmpty()
+                ? 
context.getCamelContextExtension().getContextPlugin(SemanticEvaluations.class)
+                : SemanticEvaluations.get(context);
+        if (evaluations != null) {
             try {
-                questions.replace(dc.getResource(), definitions);
+                evaluations.replace(dc.getResource(), definitions);
             } catch (IllegalArgumentException e) {
                 throw new YamlDeserializationException(root, e.getMessage(), 
e);
             }
         }
     }
 
-    private static Map<String, SemanticQuestion> read(CamelContext context, 
Node node) {
+    private static Map<String, SemanticEvaluation> read(CamelContext context, 
Node node) {
         Map<String, Node> semantic = fields(node, "semantic declaration");
-        if (!semantic.keySet().equals(Set.of("question"))) {
-            throw new YamlDeserializationException(node, "Semantic declaration 
requires only question");
+        for (String field : semantic.keySet()) {
+            if (!Set.of("evaluation", "expert", "state").contains(field)) {
+                throw new YamlDeserializationException(
+                        semantic.get(field), "Unknown property '" + field + "' 
in semantic declaration");
+            }
         }
-        Map<String, SemanticQuestion> result = new LinkedHashMap<>();
-        fields(semantic.get("question"), "semantic questions").forEach((name, 
definition) -> {
+        if (!semantic.containsKey("evaluation")) {
+            throw new YamlDeserializationException(node, "Semantic declaration 
requires evaluation");
+        }
+        Map<String, SemanticEvaluation> result = new LinkedHashMap<>();
+        fields(semantic.get("evaluation"), "semantic 
evaluations").forEach((name, definition) -> {
             if (name.isBlank()) {
-                throw new YamlDeserializationException(definition, "Semantic 
question requires a nonblank name");
+                throw new YamlDeserializationException(definition, "Semantic 
evaluation requires a nonblank name");
+            }
+            Map<String, Node> values = fields(definition, "semantic evaluation 
'" + name + "'");
+            for (String common : List.of("expert", "state")) {
+                if (!values.containsKey(common) && 
semantic.containsKey(common)) {
+                    values.put(common, semantic.get(common));
+                }
             }
-            Map<String, Node> values = fields(definition, "semantic question 
'" + name + "'");
-            String expert = values.containsKey("expert") ? 
asText(values.get("expert")) : "default/automatic";
+            String expert = "default/automatic";
             try {
-                result.put(name, readQuestion(context, name, expert, 
definition, values));
+                if (values.containsKey("expert")) {
+                    expert = "invalid expert reference";
+                    expert = asText(values.get("expert"));
+                }
+                result.put(name, readEvaluation(context, name, expert, 
definition, values));
             } catch (IllegalArgumentException | InvalidNodeTypeException e) {
                 throw new YamlDeserializationException(
-                        definition, "Invalid semantic question '" + name + "': 
" + e.getMessage()
+                        definition, "Invalid semantic evaluation '" + name + 
"': " + e.getMessage()
                                     + " (expert '" + expert + "')",
                         e);
             }
         });
         return result;
     }
 
-    private static SemanticQuestion readQuestion(
+    private static SemanticEvaluation readEvaluation(
             CamelContext context, String name, String expert, Node definition, 
Map<String, Node> values) {
-        String description = "semantic question '" + name + "'";
+        String description = "semantic evaluation '" + name + "'";
         String expertContext = " (expert '" + expert + "')";
         values.forEach((field, value) -> {
             if (!FIELDS.contains(field)) {
                 throw new YamlDeserializationException(
                         value, "Unknown property '" + field + "' in " + 
description + expertContext);
             }
         });
-        if (!values.containsKey("type")) {
-            throw new YamlDeserializationException(definition, "Semantic 
question type is required: " + name + expertContext);
+        if (values.containsKey("type") == values.containsKey("operation")) {
+            throw new YamlDeserializationException(
+                    definition, "Specify exactly one operation or type: " + 
name + expertContext);
         }
-        SemanticQuestion.Type type = enumeration(values.get("type"), name, 
expert, "type", SemanticQuestion.Type.class);
-        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"), "criteria for " + description + 
expertContext)
-                        .forEach((key, value) -> criteria.put(key, 
asText(value)));
-            }
+        String operation = values.containsKey("operation")
+                ? asText(values.get("operation"))
+                : asText(values.get("type")).toLowerCase(Locale.ROOT);
+        SemanticEvaluationBuilder builder = new 
SemanticEvaluationBuilder().operation(operation)
+                
.expert(asText(values.get("expert"))).state(asText(values.get("state")));
+        if (values.containsKey("parameters")) {
+            fields(values.get("parameters"), "evaluation parameters")
+                    .forEach((key, value) -> builder.parameter(key, 
value(context, value)));
         }
-        if (type != SemanticQuestion.Type.BOOLEAN && 
(values.containsKey("threshold") || values.containsKey("uncertainty")
-                || values.containsKey("uncertaintyPolicy"))) {
-            throw new YamlDeserializationException(
-                    definition, "Threshold and uncertainty policy require a 
boolean question: " + name + expertContext);
+        for (String field : List.of("instructions", "criteria", "threshold", 
"uncertainty", "uncertaintyPolicy")) {
+            Node node = values.get(field);
+            if (node == null) {
+                continue;
+            }
+            switch (field) {
+                case "instructions" -> builder.instructions(asText(node));
+                case "threshold", "uncertainty" -> {
+                    try {
+                        builder.parameter(field, 
Double.valueOf(context.resolvePropertyPlaceholders(asText(node))));
+                    } catch (NumberFormatException invalid) {
+                        throw new YamlDeserializationException(
+                                node,
+                                "Invalid numeric value for '" + field + "' in 
" + description + expertContext);
+                    }
+                }
+                case "uncertaintyPolicy" -> 
builder.uncertaintyPolicy(asText(node));
+                case "criteria" -> {
+                    if (node instanceof MappingNode) {
+                        Map<String, String> criteria = new LinkedHashMap<>();
+                        fields(node, "criteria").forEach((key, child) -> 
criteria.put(key, asText(child)));
+                        builder.parameter(field, criteria);
+                    } else if (node instanceof SequenceNode sequence) {
+                        builder.parameter(field,
+                                
sequence.getValue().stream().map(SemanticDefinitionDeserializer::asText).toList());
+                    } else {
+                        builder.parameter(field, value(context, node));
+                    }
+                }
+                default -> throw new IllegalStateException(field);
+            }
         }
-        SemanticQuestion.UncertaintyPolicy policy = 
values.containsKey("uncertaintyPolicy")
-                ? enumeration(values.get("uncertaintyPolicy"), name, expert, 
"uncertaintyPolicy",
-                        SemanticQuestion.UncertaintyPolicy.class)
-                : SemanticQuestion.UncertaintyPolicy.FAIL;
-        return new SemanticQuestion(
-                type, asText(values.get("instructions")), 
asText(values.get("state")),
-                criteria, levels, number(context, values, name, expert, 
"threshold", 0.5),
-                number(context, values, name, expert, "uncertainty", 0), 
policy, asText(values.get("expert")));
+        return builder.build(context);
     }
 
-    private static <T extends Enum<T>> T enumeration(Node node, String 
question, String expert, String field, Class<T> type) {
-        try {
-            return asEnum(node, type);
-        } catch (InvalidEnumException e) {
-            throw new YamlDeserializationException(
-                    node, "Invalid value for '" + field + "' in semantic 
question '" + question + "': " + asText(node)
-                          + " (expert '" + expert + "')",
-                    e);
+    private static Object value(CamelContext context, Node node) {
+        if (node instanceof MappingNode) {
+            Map<String, Object> result = new LinkedHashMap<>();
+            fields(node, "parameter map").forEach((key, child) -> 
result.put(key, value(context, child)));
+            return result;
         }
-    }
-
-    private static double number(
-            CamelContext context, Map<String, Node> values, String question, 
String expert, String name, double fallback) {
-        if (!values.containsKey(name)) {
-            return fallback;
+        if (node instanceof SequenceNode sequence) {
+            return sequence.getValue().stream().map(child -> value(context, 
child)).toList();
         }
-        Node node = values.get(name);
-        String raw = asText(node);
-        try {
-            return 
Double.parseDouble(context.resolvePropertyPlaceholders(raw));
-        } catch (NumberFormatException e) {
-            throw new YamlDeserializationException(
-                    node,
-                    "Invalid numeric value for '" + name + "' in semantic 
question '" + question + "': " + raw
-                          + " (expert '" + expert + "')",
-                    e);
+        if (Tag.FLOAT.equals(node.getTag()) || NUMBER.equals(node.getTag())) {
+            try {
+                return new 
BigDecimal(context.resolvePropertyPlaceholders(asText(node)));
+            } catch (NumberFormatException invalid) {
+                throw new YamlDeserializationException(node, "Invalid numeric 
parameter");
+            }
         }
+        return new 
StandardConstructor(LoadSettings.builder().build()).constructSingleDocument(Optional.of(node));

Review Comment:
   The allocation is real, but `StandardConstructor` is not stateless. In the 
pinned SnakeYAML Engine 3.1.1 sources, its `BaseConstructor` maintains mutable 
`constructedObjects`, `recursiveObjects`, `maps2fill` and `sets2fill`. Clearing 
them between documents permits sequential reuse, not concurrent calls from 
different loaders or contexts.
   
   I have retained local construction to avoid introducing shared mutable 
state. Sharing only immutable settings or reusing a constructor within one 
parse could be considered if profiling establishes a loading bottleneck; the 
proposed static constructor is unsafe.
   
   _AI-generated by Codex on behalf of @luigidemasi._



##########
components/camel-ai/camel-semantic/src/main/java/org/apache/camel/semantic/SemanticEvaluationBuilder.java:
##########
@@ -0,0 +1,184 @@
+/*
+ * 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.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.util.StringHelper;
+
+/** Fluent definition of an expert-owned evaluation. All DSLs build the same 
immutable declaration. */
+public final class SemanticEvaluationBuilder {
+    private final SemanticEvaluationsBuilder parent;
+    private final Map<String, Object> parameters = new LinkedHashMap<>();
+    private final Map<String, String> criteria = new LinkedHashMap<>();
+    private final List<String> levels = new ArrayList<>();
+    private String threshold;
+    private String uncertainty;
+    private boolean normalizeUncertaintyPolicy;
+    private String operation;
+    private String expert;
+    private String state;
+
+    /** Create a standalone declaration, completed with {@link 
#build(CamelContext)}. */
+    public SemanticEvaluationBuilder() {
+        this(null);
+    }
+
+    SemanticEvaluationBuilder(SemanticEvaluationsBuilder parent) {
+        this.parent = parent;
+    }
+
+    public SemanticEvaluationBuilder operation(String operation) {
+        this.operation = operation;
+        return this;
+    }
+
+    /** Select an instruction-driven operation by type, such as boolean, 
choice or score. */
+    public SemanticEvaluationBuilder type(String type) {
+        return operation(type.toLowerCase(Locale.ROOT));
+    }
+
+    public SemanticEvaluationBuilder expert(String expert) {
+        this.expert = expert;
+        return this;
+    }
+
+    String getExpert() {
+        return expert != null ? expert : parent != null ? parent.getExpert() : 
null;
+    }
+
+    public SemanticEvaluationBuilder state(String state) {
+        this.state = state;
+        return this;
+    }
+
+    public SemanticEvaluationBuilder parameter(String name, Object value) {
+        if (parameters.containsKey(name)) {
+            throw new IllegalArgumentException("Duplicate semantic parameter: 
" + name);
+        }
+        parameters.put(name, value);
+        return this;
+    }
+
+    public SemanticEvaluationBuilder parameters(Map<String, ?> values) {
+        values.forEach(this::parameter);
+        return this;
+    }
+
+    public SemanticEvaluationBuilder instructions(String instructions) {
+        return parameter("instructions", instructions);
+    }
+
+    public SemanticEvaluationBuilder criterion(String name, String 
description) {
+        if (criteria.containsKey(name)) {
+            throw new IllegalArgumentException("Duplicate semantic criterion: 
" + name);
+        }
+        criteria.put(name, description);
+        return this;
+    }
+
+    public SemanticEvaluationBuilder level(String level) {
+        levels.add(level);
+        return this;
+    }
+
+    public SemanticEvaluationBuilder threshold(double threshold) {
+        return parameter("threshold", threshold);
+    }
+
+    public SemanticEvaluationBuilder threshold(String threshold) {
+        this.threshold = threshold;
+        return this;
+    }
+
+    public SemanticEvaluationBuilder uncertainty(double uncertainty) {
+        return parameter("uncertainty", uncertainty);
+    }
+
+    public SemanticEvaluationBuilder uncertainty(String uncertainty) {
+        this.uncertainty = uncertainty;
+        return this;
+    }
+
+    public SemanticEvaluationBuilder uncertaintyPolicy(String policy) {
+        parameter("uncertaintyPolicy", policy);
+        normalizeUncertaintyPolicy = true;
+        return this;
+    }
+
+    public SemanticEvaluationsBuilder end() {
+        if (parent == null) {
+            throw new IllegalStateException("Complete a standalone declaration 
with build(context)");
+        }
+        return parent;
+    }
+
+    public void register() {
+        end().register();
+    }
+
+    /** Build an immutable declaration, resolving placeholders in parameter 
values while retaining their types. */
+    public SemanticEvaluation build(CamelContext context) {
+        Map<String, Object> values = new LinkedHashMap<>(parameters);
+        if (!criteria.isEmpty() || !levels.isEmpty()) {
+            if (values.containsKey("criteria") || !criteria.isEmpty() && 
!levels.isEmpty()) {
+                throw new IllegalArgumentException("Duplicate parameter 
'criteria'");
+            }
+            values.put("criteria", !criteria.isEmpty() ? criteria : levels);
+        }
+        for (String numeric : List.of("threshold", "uncertainty")) {
+            String text = numeric.equals("threshold") ? threshold : 
uncertainty;
+            if (text != null) {
+                if (values.containsKey(numeric)) {
+                    throw new IllegalArgumentException("Duplicate parameter '" 
+ numeric + "'");
+                }
+                try {
+                    values.put(numeric, 
Double.valueOf(context.resolvePropertyPlaceholders(text)));
+                } catch (NumberFormatException invalid) {
+                    throw new IllegalArgumentException("Parameter '" + numeric 
+ "' must be a valid number");
+                }
+            }
+        }
+        values.replaceAll((name, value) -> resolve(context, value));
+        if (normalizeUncertaintyPolicy && values.get("uncertaintyPolicy") 
instanceof String policy) {
+            values.put("uncertaintyPolicy",
+                    
StringHelper.asEnumConstantValue(policy).toLowerCase(Locale.ROOT).replace('_', 
'-'));
+        }
+        return new SemanticEvaluation(
+                operation, getExpert(), state != null ? state : parent != null 
? parent.getState() : null, values);
+    }
+
+    private static Object resolve(CamelContext context, Object value) {
+        if (value instanceof String text) {
+            return context.resolvePropertyPlaceholders(text);
+        }
+        if (value instanceof Map<?, ?> map) {
+            Map<Object, Object> resolved = new LinkedHashMap<>();

Review Comment:
   Non-string keys are already rejected by 
`SemanticEvaluation.immutableValue()` with `IllegalArgumentException("Parameter 
maps require string keys")`; they are not silently accepted. `resolve()` also 
processes Java builder input, where arbitrary nested maps can reach this 
intermediate step.
   
   The proposed cast would instead throw `ClassCastException` before the common 
declaration validation and its error wrapping. I retained the intermediate map 
and reran the existing `nonStringNestedKeysFailWithValidationError()` 
regression during assessment. A checked conversion would duplicate the 
validation without changing accepted input.
   
   _AI-generated by Codex on behalf of @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: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to