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


##########
components/camel-ai/camel-semantic/src/main/java/org/apache/camel/language/semantic/SemanticLanguage.java:
##########
@@ -495,7 +545,14 @@ private Object evaluate(Exchange exchange, boolean 
asPredicate) {
                         current = compile(Map.of(name, single));
                     }
                 }
+                if (asPredicate) {
+                    requireBoolean(current.operations.get(names.get(0)));
+                }
+                startAdapter(current.groups);

Review Comment:
   `startAdapter` is `synchronized` on the language, and `activate()` is 
synchronized too. Calling it on every evaluation serializes all concurrent 
exchanges on one monitor and does a stream scan each time, even once the 
adapter is active. Could activation happen in `compile()` only, or behind a 
volatile `activated` fast path checked before taking the lock?



##########
components/camel-ai/camel-semantic/src/main/java/org/apache/camel/language/semantic/SemanticLanguage.java:
##########
@@ -65,6 +68,7 @@ public class SemanticLanguage extends LanguageSupport {
     public static final String ADAPTER_FACTORY = "semantic-adapter";
     public static final String ADAPTER_RESOURCE = FactoryFinder.DEFAULT_PATH + 
ADAPTER_FACTORY;
 
+    private final ThreadLocal<Boolean> validating = new ThreadLocal<>();

Review Comment:
   Question: a `ThreadLocal` flag changes the behaviour of `compile`, `init` 
and `startAdapter`, and the same pattern appears with 
`SemanticQuestions.candidate`. Anything an adapter's `validate()` does on this 
thread also runs in validation mode. Would an explicit package-private 
`prepare(selected, validationOnly)` / `validate(snapshot)` path be simpler and 
safer?



##########
components/camel-ai/camel-semantic/src/main/java/org/apache/camel/semantic/SemanticQuestions.java:
##########
@@ -25,16 +25,30 @@
 import java.util.function.Consumer;
 
 import org.apache.camel.CamelContext;
+import org.apache.camel.language.semantic.SemanticLanguage;
 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 static final Object CREATION_LOCK = new Object();
+    private final CamelContext context;
     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();
+    private final ThreadLocal<Map<String, SemanticQuestion>> candidate = new 
ThreadLocal<>();

Review Comment:
   With this, `get(name)` / `get(names)` silently return a candidate snapshot 
depending on thread state, which is surprising for a public API. See the 
comment on `SemanticLanguage.validating`.



##########
core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/UnaryExpression.java:
##########
@@ -102,10 +103,7 @@ public void init(CamelContext context) {
 
             @Override
             public <T> T evaluate(Exchange exchange, Class<T> type) {
-                Object value = exp.evaluate(exchange, Object.class);
-                // the same rule the language uses for a predicate on its own, 
where ${body} is true and a missing
-                // header is false, so !${body} and !${header.foo} answer the 
opposite of those (CAMEL-24984)
-                boolean matches = ObjectHelper.evaluateValuePredicate(value);
+                boolean matches = 
ExpressionToPredicateAdapter.toPredicate(exp).matches(exchange);

Review Comment:
   This creates a new `ExpressionToPredicateAdapter` on every evaluation of 
`!${...}`. Can the predicate be built once in `createNotExpression` (`final 
Predicate predicate = ExpressionToPredicateAdapter.toPredicate(exp);`)? Please 
also keep the CAMEL-24984 comment explaining the `${body}` / missing-header 
semantics. The same applies to 
`SimpleFunctionExpression.createNegatedExpression`.



##########
core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SimpleFunctionExpression.java:
##########
@@ -148,9 +149,8 @@ public void init(CamelContext context) {
 
             @Override
             public <T> T evaluate(Exchange exchange, Class<T> type) {
-                Object value = exp.evaluate(exchange, Object.class);
-                // the same rule the language uses for a predicate on its own 
(CAMEL-24984)
-                boolean matches = ObjectHelper.evaluateValuePredicate(value);
+                // Preserve a function's predicate contract, including 
semantic's boolean-only guard.
+                boolean matches = 
ExpressionToPredicateAdapter.toPredicate(exp).matches(exchange);

Review Comment:
   Same as in `UnaryExpression`: please hoist the predicate out of `evaluate()`.



##########
components/camel-ai/camel-semantic/src/main/java/org/apache/camel/semantic/SemanticQuestion.java:
##########
@@ -16,118 +16,178 @@
  */
 package org.apache.camel.semantic;
 
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
-import java.util.Objects;
+import java.util.Set;
 
-/**
- * Immutable question definition. Probabilities and thresholds are 
provider-specific evidence, not accuracy guarantees.
- */
+/** Immutable evaluation declaration. The expert defines the operation, 
parameter vocabulary and result semantics. */
 public final class SemanticQuestion {
+    private static final Set<Class<?>> NUMBER_TYPES = Set.of(Byte.class, 
Short.class, Integer.class, Long.class,
+            Float.class, Double.class, BigInteger.class, BigDecimal.class);
+
     public enum Type {
         BOOLEAN,
         CHOICE,
-        SCORE
+        SCORE,
+        CLASSIFICATION
     }
 
     public enum UncertaintyPolicy {
         FAIL,
         NON_MATCH
     }
 
-    private final Type type;
+    private final String operation;
     private final String expert;
-    private final String instructions;
     private final String state;
-    private final Map<String, String> criteria;
-    private final List<String> levels;
-    private final double threshold;
-    private final double uncertainty;
-    private final UncertaintyPolicy uncertaintyPolicy;
+    private final Map<String, Object> parameters;
+
+    public SemanticQuestion(String operation, String expert, String state, 
Map<String, ?> parameters) {
+        if (operation == null || operation.isBlank()) {
+            throw new IllegalArgumentException("Evaluation operation is 
required");
+        }
+        if (expert != null && expert.isBlank()) {
+            throw new IllegalArgumentException("Evaluation expert must not be 
blank");
+        }
+        if (state != null && state.isBlank()) {
+            throw new IllegalArgumentException("Evaluation state selector must 
not be blank");
+        }
+        this.operation = operation;
+        this.expert = expert;
+        this.state = state;
+        this.parameters = immutableMap(parameters == null ? Map.of() : 
parameters);
+    }
 
+    /** Convenience declaration for instruction-driven operations named 
boolean, choice or score. */
     public SemanticQuestion(Type type, String instructions, String state, 
Map<String, String> criteria,
-                            List<String> levels, double threshold, double 
uncertainty, UncertaintyPolicy uncertaintyPolicy) {
-        this(type, instructions, state, criteria, levels, threshold, 
uncertainty, uncertaintyPolicy, null);
+                            List<String> levels, double threshold, double 
uncertainty, UncertaintyPolicy policy) {
+        this(type, instructions, state, criteria, levels, threshold, 
uncertainty, policy, null);
     }
 
     public SemanticQuestion(Type type, String instructions, String state, 
Map<String, String> criteria,
-                            List<String> levels, double threshold, double 
uncertainty, UncertaintyPolicy uncertaintyPolicy,
+                            List<String> levels, double threshold, double 
uncertainty, UncertaintyPolicy policy,
                             String expert) {
-        this.type = Objects.requireNonNull(type, "Question type is required");
-        if (expert != null && expert.isBlank()) {
-            throw new IllegalArgumentException("Question expert must not be 
blank");
+        this(type.name().toLowerCase(Locale.ROOT), expert, state,
+             instructionParameters(type, instructions, criteria, levels, 
threshold, uncertainty, policy));
+    }
+
+    private static Map<String, Object> instructionParameters(
+            Type type, String instructions, Map<String, String> criteria,
+            List<String> levels, double threshold, double uncertainty,
+            UncertaintyPolicy policy) {
+        Map<String, Object> values = new LinkedHashMap<>();
+        if (instructions != null) {
+            values.put("instructions", instructions);
         }
-        this.expert = expert;
-        if (instructions != null && instructions.isBlank()) {
-            throw new IllegalArgumentException("Question instructions must not 
be blank");
+        if (criteria != null && !criteria.isEmpty()) {
+            values.put("criteria", criteria);
         }
-        if (state != null && state.isBlank()) {
-            throw new IllegalArgumentException("Question state selector must 
not be blank");
+        if (levels != null && !levels.isEmpty()) {
+            values.put("criteria", levels);

Review Comment:
   If both `criteria` and `levels` are passed, `levels` silently overwrites 
`criteria`. `SemanticQuestionBuilder.build` rejects that combination, so the 
convenience constructor should probably do the same.



##########
components/camel-ai/camel-typesafe-ai/src/main/java/org/apache/camel/component/typesafeai/TypeSafeAiSemanticAdapter.java:
##########
@@ -120,10 +187,47 @@ private SemanticResult result(SemanticQuestion question, 
JsonObject answer, Json
         if (answer.get("probabilities") instanceof Map<?, ?> values) {
             values.forEach((key, value) -> probabilities.put((String) key, 
((Number) value).doubleValue()));
         }
-        return new SemanticResult(
-                question.getType() == SemanticQuestion.Type.BOOLEAN ? null : 
answer.get(type(question)),
-                question.getType() == SemanticQuestion.Type.BOOLEAN ? 
answer.getDouble("noul") : null,
+        Double probability = question.getType() == 
SemanticQuestion.Type.BOOLEAN ? answer.getDouble("noul") : null;
+        Object value = switch (question.getType()) {
+            case BOOLEAN -> booleanDecision(question, probability);

Review Comment:
   If the response has no `noul`, `probability` is null here and unboxing it 
into `booleanDecision(..., double)` throws an NPE with no message. Could we 
throw an `IllegalArgumentException` such as "TypeSafe AI boolean result is 
missing its probability" so a malformed response produces a clear error?



##########
components/camel-ai/camel-semantic/src/main/java/org/apache/camel/semantic/SemanticQuestionsBuilder.java:
##########
@@ -55,6 +57,28 @@ public static SemanticQuestionsBuilder 
semanticQuestions(RouteBuilder builder) {
         return new SemanticQuestionsBuilder(builder);
     }
 
+    public SemanticQuestionsBuilder expert(String expert) {
+        this.expert = expert;
+        return this;
+    }
+
+    public SemanticQuestionsBuilder state(String state) {
+        this.state = state;
+        return this;
+    }
+
+    String getExpert() {
+        return expert;
+    }
+
+    String getState() {
+        return state;
+    }
+
+    public SemanticQuestionBuilder evaluation(String name) {

Review Comment:
   Naming: there is now `evaluation(name)` and `question(name)` here, 
`evaluation:` and `question:` in YAML, and `<evaluation>` and `<question>` in 
XML, while the classes are still `SemanticQuestion*`. Before 4.23.0 ships, 
could we pick one term (the JIRA and docs use "evaluation") rather than 
shipping both?



##########
components/camel-ai/camel-semantic/src/main/java/org/apache/camel/semantic/SemanticQuestionBuilder.java:
##########
@@ -78,84 +96,89 @@ public SemanticQuestionBuilder criterion(String name, 
String description) {
         return this;
     }
 
-    /** Add an ordered score level. */
     public SemanticQuestionBuilder level(String level) {
         levels.add(level);
         return this;
     }
 
-    /** Boolean decision threshold; defaults to 0.5. */
     public SemanticQuestionBuilder threshold(double threshold) {
-        return threshold(Double.toString(threshold));
+        return parameter("threshold", threshold);
     }
 
-    /** Boolean decision threshold, optionally using property placeholders. */
     public SemanticQuestionBuilder threshold(String threshold) {
         this.threshold = threshold;
         return this;
     }
 
-    /** Boolean uncertainty band; defaults to zero. */
     public SemanticQuestionBuilder uncertainty(double uncertainty) {
-        return uncertainty(Double.toString(uncertainty));
+        return parameter("uncertainty", uncertainty);
     }
 
-    /** Boolean uncertainty band, optionally using property placeholders. */
     public SemanticQuestionBuilder uncertainty(String uncertainty) {
         this.uncertainty = uncertainty;
         return this;
     }
 
-    /** Boolean uncertainty policy: fail (default) or non-match. */
-    public SemanticQuestionBuilder uncertaintyPolicy(String uncertaintyPolicy) 
{
-        this.uncertaintyPolicy = uncertaintyPolicy;
+    public SemanticQuestionBuilder uncertaintyPolicy(String policy) {
+        parameter("uncertaintyPolicy", policy);
+        normalizeUncertaintyPolicy = true;
         return this;
     }
 
-    /** Return to the group to add another question. */
     public SemanticQuestionsBuilder end() {
+        if (parent == null) {
+            throw new IllegalStateException("Complete a standalone declaration 
with build(context)");
+        }
         return parent;
     }
 
-    /** Validate and register the entire group. */
     public void register() {
-        parent.register();
+        end().register();
     }
 
-    SemanticQuestion build(CamelContext context) {
-        if (type == null) {
-            throw new IllegalArgumentException("Question type is required");
+    /** Build an immutable declaration, resolving placeholders in parameter 
values while retaining their types. */
+    public SemanticQuestion 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");
+                }
+            }
         }
-        SemanticQuestion.Type questionType = enumeration(type, 
SemanticQuestion.Type.class);
-        if (questionType != SemanticQuestion.Type.BOOLEAN
-                && (threshold != null || uncertainty != null || 
uncertaintyPolicy != null)) {
-            throw new IllegalArgumentException("Threshold and uncertainty 
policy require a boolean question");
+        values.replaceAll((name, value) -> resolve(context, value));

Review Comment:
   This resolves property placeholders in every string parameter, including 
free-text `instructions` (a prompt containing `{{` will fail). A numeric value 
such as `threshold: "{{x}}"` nested in `parameters` stays a String and then 
fails Number validation, so experts other than TypeSafe cannot externalise 
numeric policy through properties. Is that intended? If so, maybe give an 
example in the docs of how to externalise a nested numeric parameter.



##########
dsl/camel-yaml-dsl/camel-yaml-dsl/src/main/java/org/apache/camel/dsl/yaml/YamlRoutesBuilderLoader.java:
##########
@@ -126,6 +126,9 @@ public boolean isSupportedExtension(String extension) {
     }
 
     protected RouteBuilder builder(final YamlDeserializationContext ctx, final 
Node root) {
+        // All resources have prepared their beans before declarations are 
validated and routes constructed.
+        setDeserializationContext(root, ctx);
+        ctx.preParse(root);

Review Comment:
   This moves the resolver `preParse` hook from the pre-parse phase into 
`builder()`. Fine today, since only camel-semantic implements the hook, but 
exceptions thrown here are no longer wrapped with `Error pre-parsing resource: 
<location>`. It is also a generic YAML-loader change hidden in a camel-semantic 
PR. Could you mention it in the PR description?



##########
components/camel-ai/camel-semantic/src/main/java/org/apache/camel/semantic/SemanticQuestion.java:
##########
@@ -16,118 +16,178 @@
  */
 package org.apache.camel.semantic;
 
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
-import java.util.Objects;
+import java.util.Set;
 
-/**
- * Immutable question definition. Probabilities and thresholds are 
provider-specific evidence, not accuracy guarantees.
- */
+/** Immutable evaluation declaration. The expert defines the operation, 
parameter vocabulary and result semantics. */
 public final class SemanticQuestion {
+    private static final Set<Class<?>> NUMBER_TYPES = Set.of(Byte.class, 
Short.class, Integer.class, Long.class,
+            Float.class, Double.class, BigInteger.class, BigDecimal.class);
+
     public enum Type {
         BOOLEAN,
         CHOICE,
-        SCORE
+        SCORE,
+        CLASSIFICATION
     }
 
     public enum UncertaintyPolicy {
         FAIL,
         NON_MATCH
     }
 
-    private final Type type;
+    private final String operation;
     private final String expert;
-    private final String instructions;
     private final String state;
-    private final Map<String, String> criteria;
-    private final List<String> levels;
-    private final double threshold;
-    private final double uncertainty;
-    private final UncertaintyPolicy uncertaintyPolicy;
+    private final Map<String, Object> parameters;
+
+    public SemanticQuestion(String operation, String expert, String state, 
Map<String, ?> parameters) {
+        if (operation == null || operation.isBlank()) {
+            throw new IllegalArgumentException("Evaluation operation is 
required");
+        }
+        if (expert != null && expert.isBlank()) {
+            throw new IllegalArgumentException("Evaluation expert must not be 
blank");
+        }
+        if (state != null && state.isBlank()) {
+            throw new IllegalArgumentException("Evaluation state selector must 
not be blank");
+        }
+        this.operation = operation;
+        this.expert = expert;
+        this.state = state;
+        this.parameters = immutableMap(parameters == null ? Map.of() : 
parameters);
+    }
 
+    /** Convenience declaration for instruction-driven operations named 
boolean, choice or score. */
     public SemanticQuestion(Type type, String instructions, String state, 
Map<String, String> criteria,
-                            List<String> levels, double threshold, double 
uncertainty, UncertaintyPolicy uncertaintyPolicy) {
-        this(type, instructions, state, criteria, levels, threshold, 
uncertainty, uncertaintyPolicy, null);
+                            List<String> levels, double threshold, double 
uncertainty, UncertaintyPolicy policy) {
+        this(type, instructions, state, criteria, levels, threshold, 
uncertainty, policy, null);
     }
 
     public SemanticQuestion(Type type, String instructions, String state, 
Map<String, String> criteria,
-                            List<String> levels, double threshold, double 
uncertainty, UncertaintyPolicy uncertaintyPolicy,
+                            List<String> levels, double threshold, double 
uncertainty, UncertaintyPolicy policy,
                             String expert) {
-        this.type = Objects.requireNonNull(type, "Question type is required");
-        if (expert != null && expert.isBlank()) {
-            throw new IllegalArgumentException("Question expert must not be 
blank");
+        this(type.name().toLowerCase(Locale.ROOT), expert, state,
+             instructionParameters(type, instructions, criteria, levels, 
threshold, uncertainty, policy));
+    }
+
+    private static Map<String, Object> instructionParameters(
+            Type type, String instructions, Map<String, String> criteria,
+            List<String> levels, double threshold, double uncertainty,
+            UncertaintyPolicy policy) {
+        Map<String, Object> values = new LinkedHashMap<>();
+        if (instructions != null) {
+            values.put("instructions", instructions);
         }
-        this.expert = expert;
-        if (instructions != null && instructions.isBlank()) {
-            throw new IllegalArgumentException("Question instructions must not 
be blank");
+        if (criteria != null && !criteria.isEmpty()) {
+            values.put("criteria", criteria);
         }
-        if (state != null && state.isBlank()) {
-            throw new IllegalArgumentException("Question state selector must 
not be blank");
+        if (levels != null && !levels.isEmpty()) {
+            values.put("criteria", levels);
         }
-        this.instructions = instructions;
-        this.state = state;
-        this.criteria = criteria == null ? Map.of() : 
Collections.unmodifiableMap(new LinkedHashMap<>(criteria));
-        this.levels = levels == null ? List.of() : List.copyOf(levels);
-        this.criteria.forEach((key, value) -> {
-            if (key == null || key.isBlank() || value == null || 
value.isBlank()) {
-                throw new IllegalArgumentException("Question criteria require 
nonblank keys and descriptions");
+        if (type == Type.BOOLEAN) {
+            values.put("threshold", threshold);
+            values.put("uncertainty", uncertainty);
+            values.put("uncertaintyPolicy", policy == 
UncertaintyPolicy.NON_MATCH ? "non-match" : "fail");
+        }
+        return values;
+    }
+
+    static Map<String, Object> immutableMap(Map<String, ?> values) {
+        Map<String, Object> copy = new LinkedHashMap<>();
+        values.forEach((name, value) -> {
+            if (name == null || name.isBlank()) {
+                throw new IllegalArgumentException("Parameter names must not 
be blank");
             }
+            copy.put(name, immutableValue(value));
         });
-        if (this.levels.stream().anyMatch(String::isBlank)) {
-            throw new IllegalArgumentException("Question score levels must not 
be blank");
-        }
-        if (type == Type.CHOICE && this.criteria.isEmpty() || type == 
Type.SCORE && this.levels.isEmpty()
-                || type != Type.SCORE && !this.levels.isEmpty() || type == 
Type.SCORE && !this.criteria.isEmpty()) {
-            throw new IllegalArgumentException(
-                    "Choice needs criteria, score needs ordered levels, 
boolean accepts true/false criteria");
+        return Collections.unmodifiableMap(copy);
+    }
+
+    private static Object immutableValue(Object value) {
+        if (value instanceof Map<?, ?> map) {
+            Map<String, Object> copy = new LinkedHashMap<>();
+            map.forEach((key, entry) -> {
+                if (!(key instanceof String name)) {
+                    throw new IllegalArgumentException("Parameter maps require 
string keys");
+                }
+                copy.put(name, entry);
+            });
+            return immutableMap(copy);
         }
-        if (type == Type.BOOLEAN && this.criteria.keySet().stream().anyMatch(k 
-> !k.equals("true") && !k.equals("false"))) {
-            throw new IllegalArgumentException("Boolean criteria keys must be 
true or false");
+        if (value instanceof List<?> list) {
+            List<Object> copy = new ArrayList<>();
+            list.forEach(entry -> copy.add(immutableValue(entry)));
+            return Collections.unmodifiableList(copy);
         }
-        if (!Double.isFinite(threshold) || !Double.isFinite(uncertainty) || 
uncertainty < 0
-                || threshold - uncertainty < 0 || threshold + uncertainty > 1) 
{
-            throw new IllegalArgumentException("Boolean threshold and 
uncertainty band must be within [0,1]");
+        if (value == null || value instanceof String || value instanceof 
Boolean
+                || NUMBER_TYPES.contains(value.getClass())) {
+            return value;
         }
-        this.threshold = threshold;
-        this.uncertainty = uncertainty;
-        this.uncertaintyPolicy = Objects.requireNonNull(uncertaintyPolicy, 
"Uncertainty policy is required");
+        throw new IllegalArgumentException("Parameters require immutable 
scalar, map or list values");
+    }
+
+    public String getOperation() {
+        return operation;
     }
 
     public String getExpert() {
         return expert;
     }
 
-    public Type getType() {
-        return type;
+    public String getState() {
+        return state;
     }
 
-    public String getInstructions() {
-        return instructions;
+    public Map<String, Object> getParameters() {
+        return parameters;
     }
 
-    public String getState() {
-        return state;
+    /**
+     * Instruction-driven operation kind; custom operations have their result 
type in the expert contract.
+     *
+     * @throws IllegalArgumentException if the operation is not a built-in 
instruction-driven kind
+     */
+    public Type getType() {
+        return Type.valueOf(operation.toUpperCase(Locale.ROOT));
     }
 
+    public String getInstructions() {
+        return (String) parameters.get("instructions");
+    }
+
+    @SuppressWarnings("unchecked")
     public Map<String, String> getCriteria() {
-        return criteria;
+        return parameters.get("criteria") instanceof Map<?, ?> map ? 
(Map<String, String>) map : Map.of();
     }
 
+    @SuppressWarnings("unchecked")
     public List<String> getLevels() {
-        return levels;
+        return parameters.get("criteria") instanceof List<?> list ? 
(List<String>) list : List.of();
     }
 
-    public double getThreshold() {
-        return threshold;
+    /** Requested threshold, or null when omitted. Defaults belong to the 
expert. */
+    public Double getThreshold() {
+        return parameters.get("threshold") instanceof Number number ? 
number.doubleValue() : null;
     }
 
-    public double getUncertainty() {
-        return uncertainty;
+    /** Requested uncertainty band, or null when omitted. Defaults belong to 
the expert. */
+    public Double getUncertainty() {
+        return parameters.get("uncertainty") instanceof Number number ? 
number.doubleValue() : null;
     }
 
+    /** Requested instruction-driven policy, or null when omitted. Custom 
policies remain available in parameters. */
     public UncertaintyPolicy getUncertaintyPolicy() {

Review Comment:
   The javadoc says custom policies remain available in parameters, but this 
getter throws `IllegalArgumentException` for any value other than 
fail/non-match. Maybe return null for unknown values, or keep this getter in 
the TypeSafe adapter only.



##########
components/camel-ai/camel-semantic/src/main/java/org/apache/camel/semantic/SemanticQuestion.java:
##########
@@ -16,118 +16,178 @@
  */
 package org.apache.camel.semantic;
 
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
-import java.util.Objects;
+import java.util.Set;
 
-/**
- * Immutable question definition. Probabilities and thresholds are 
provider-specific evidence, not accuracy guarantees.
- */
+/** Immutable evaluation declaration. The expert defines the operation, 
parameter vocabulary and result semantics. */
 public final class SemanticQuestion {
+    private static final Set<Class<?>> NUMBER_TYPES = Set.of(Byte.class, 
Short.class, Integer.class, Long.class,
+            Float.class, Double.class, BigInteger.class, BigDecimal.class);
+
     public enum Type {

Review Comment:
   `SemanticQuestion.Type` has the same four values as 
`SemanticExpert.ResultType`, but means "instruction-driven operation name" and 
throws for custom operations. Likewise `UncertaintyPolicy` and 
`getThreshold`/`getUncertainty`/`getUncertaintyPolicy` are TypeSafe concepts on 
the generic declaration. Since this is the first release, could these move to 
the TypeSafe adapter (or a small helper) so the generic `SemanticQuestion` is 
just operation/expert/state/parameters?



-- 
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