luigidemasi commented on code in PR #27494:
URL: https://github.com/apache/camel/pull/27494#discussion_r4208613068
##########
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:
This is already rejected during declaration construction: the previous
`immutableValue()` delegated nested maps to `immutableMap()`, which checks
`name.isBlank()`. I verified the supplied YAML example and nested maps inside
lists; they fail before adapter validation.
The single-pass cleanup in
[d338ec439c24](https://github.com/apache/camel/commit/d338ec439c247a869f0f54b88e8dab71e06f9389)
preserves this check explicitly. Added regression coverage for empty,
whitespace, null and non-string keys, both in nested maps and maps inside
lists. No additional SPI check is needed.
_AI-generated by Codex on behalf of @luigidemasi._
##########
components/camel-ai/camel-semantic/src/main/java/org/apache/camel/semantic/SemanticEvaluations.java:
##########
@@ -0,0 +1,183 @@
+/*
+ * 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.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.WeakHashMap;
+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 evaluations, replaced atomically per source when a
route resource is reloaded. */
+public final class SemanticEvaluations {
+ private static final Object CREATION_LOCK = new Object();
Review Comment:
Addressed the repeated locking in
[d338ec439c24](https://github.com/apache/camel/commit/d338ec439c247a869f0f54b88e8dab71e06f9389)
with an unlocked lookup and a second check inside the existing private
creation lock. Already-created registries no longer acquire the global lock;
first creation still briefly serializes.
I retained the private monitor because
[fcf09afc4800](https://github.com/apache/camel/commit/fcf09afc4800187e598f5b4078f9fa31d16a66a2)
deliberately removed locking on the public context. The existing test requires
initialization to proceed while another thread holds that monitor. A new
regression also verifies that a blocked registry lookup in another context
cannot delay an already-created registry; it failed before this change and now
passes.
_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]