Semantic Evaluation
Since Camel 4.23
The Semantic language invokes named evaluations through provider-independent experts. Each expert owns its operations, accepted inputs, evaluation parameters and result meaning. Boolean evaluations produce decisions, choice evaluations produce category strings, score evaluations produce numbers, and classification evaluations produce sets of labels. 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 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.
An expert is a configured semantic evaluator, implemented by SemanticAdapter. It is not an autonomous agent. Each named evaluation can select a registry bean using expert. Without that field, camel.language.semantic.default-expert=myExpert selects the default bean. The existing camel.language.semantic.adapter=myAdapter setting remains supported as the default when default-expert is absent; it also accepts a fully qualified implementation class name.
Without either default, Camel selects the sole eligible registered instance or advertised adapter. Registry aliases for one instance count once. A registered instance supersedes discovery of its implementation class, including advertised superclasses of registered subclasses. Differently configured instances remain distinct candidates. No eligible expert, or multiple candidates, is an initialization error listing the candidates. Unknown explicit names and invalid defaults fail without fallback. Selection never uses the evaluation’s result type: two boolean experts can evaluate entirely different properties. 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, camel.language.semantic.default-expert and camel.language.semantic.default-state to the language. Embedded applications can resolve SemanticLanguage from the context and call setDefaultExpert, 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.
Expert-owned evaluations
An expert is a registry bean implementing SemanticAdapter, annotated with @SemanticExpert. The class annotation defines its operations. Each operation declares its accepted input shapes, parameters, result type and meaning, and any optional probabilities or confidence. A configured instance cannot redefine this contract. Endpoint addresses, credentials and model deployment settings remain bean configuration, separate from evaluation parameters.
Register the beans with Camel Main or Camel JBang, for example:
camel.beans.security=#class:com.example.SecurityExpert
camel.beans.decisions=#class:org.apache.camel.component.typesafeai.TypeSafeAiSemanticAdapter SecurityExpert below is an illustrative application-provided expert exposing injection and classify; these names and its taxonomy are defined by that expert. The instruction-driven decisions bean uses the TypeSafe AI component configuration.
- semantic:
expert: security
state: "${body}"
evaluation:
injection:
operation: injection
categories:
operation: classify
department:
expert: decisions
type: choice
parameters:
instructions: Which department should handle this message?
criteria:
billing: Payments, invoices and refunds
technical: Errors, outages and technical questions
sales: Product and purchasing questions The block supplies default expert and state values; individual evaluations can override them. Evaluation names belong to the application, while operation names belong to the expert. type: choice is shorthand for the expert operation named choice; it does not let the route change an operation’s result type. Specify exactly one of operation and type. Declaring an evaluation does not execute it.
parameters can contain strings, booleans, numbers, nested maps and lists. Camel copies them into immutable declarations. Java, YAML and XML builders resolve property placeholders in string parameter values, including nested list and map values; map keys stay literal. Resolved strings remain strings. For numeric parameters, including values inside nested maps or lists, use an explicit numeric value after placeholder resolution. YAML accepts !number "{{limit:0.8}}", XML accepts <number>{{limit:0.8}}</number>, and Java accepts Double.valueOf(getContext().resolvePropertyPlaceholders("{{limit:0.8}}")) as a parameter value. These forms work for any parameter name and expert. The threshold and uncertainty shorthand options also accept numeric placeholders. Direct SemanticEvaluation construction takes already resolved parameter values and requires no Camel context. Omitted parameters stay absent: their behaviour belongs to the expert, and Camel does not insert threshold or confidence defaults. Parameter contracts use String, Boolean, Number, Map and List data shapes. Use Number with integer = true for whole-number parameters; do not use JVM numeric wrapper classes such as Integer in a contract. A provider consumes numeric values through Number across all DSLs.
For example, a nested expert policy can externalise its cutoff without converting string labels:
- semantic:
expert: moderation
evaluation:
contentPolicy:
operation: categories
parameters:
policy:
cutoff: !number "{{content.cutoff:0.8}}"
label: "{{content.label:privacy}}" String parameters, including prompts, use Camel property placeholder escaping for literal braces. For example, YAML instructions: 'Explain \{{example\}}' gives the expert Explain {{example}}. In Java, use .parameter("instructions", "Explain \\{{example\\}}") for the same literal text.
Camel validates recognised parameter names, required values, types and declared constraints. The expert checks operation-specific combinations and compatibility with its bean configuration. Startup validates all declarations, including unused evaluations, without inference. Route resource replacements also validate before publication, retaining previous declarations on failure. The low-level SemanticEvaluations.replace(String, Map) API also validates new and changed unused declarations before publishing them on a started context.
Calling an evaluation
Use the Simple function ${semantic('evaluationName')} or a semantic-language expression ref:evaluationName. Both return the operation’s typed value, preserve the body, and publish the detailed SemanticResult in the CamelSemanticResult exchange property. Every occurrence invokes the expert; there is no implicit result caching. Retain a value in a Camel variable when it must be reused without another call.
- route:
from:
uri: direct:incoming
steps:
- choice:
when:
- simple: "${semantic('injection')}"
steps:
- to: direct:security-review
otherwise:
steps:
- switch:
selector:
simple: "${semantic('department')}"
case:
- value: billing
uri: direct:billing
- value: technical
uri: direct:technical
- value: sales
uri: direct:sales
otherwise:
uri: direct:manual-review Switch consumes scalar results. Classification returns a Set<String>, including an empty set when no labels apply. Use collections in predicates, for example ${semantic('categories')} contains 'privacy'; labels are not numeric scores. The category and the meaning of a Boolean verdict must come from the selected expert’s contract. A negative detection does not establish general safety or grant authorization.
Structured input and specialised experts
A specialised moderation expert can accept a map containing a user prompt, an assistant response, and supporting documents. Camel evaluates the state selector and passes that same object unchanged. It does not flatten a conversation into text, create prompts, or impose a universal conversation schema. The expert validates the content and must not modify the original state.
For an application-provided moderation expert with operations response_safety and categories:
- semantic:
expert: moderation
state: "${body}"
evaluation:
responseSafety:
operation: response_safety
safetyCategories:
operation: categories
parameters:
policy:
labels: [privacy, unsafe]
threshold: 0.8 Here policy is an illustrative nested expert parameter. The expert owns both its interpretation and any threshold filtering. The body might be a map with prompt, response and documents keys. A missing required response fails before inference; malformed provider output and operational failures follow normal Camel error handling, rather than becoming benign decisions.
Equivalent Java and XML declarations
Java uses the same declaration model and validation path:
import static org.apache.camel.semantic.SemanticEvaluationsBuilder.semanticEvaluations;
semanticEvaluations(this).expert("moderation").state("${body}")
.evaluation("responseSafety").operation("response_safety").end()
.evaluation("safetyCategories").operation("categories")
.parameter("policy", Map.of("labels", List.of("privacy", "unsafe"), "threshold", 0.8))
.register(); XML uses explicit typed values. A parameter and a map entry each contain exactly one value: string, number, boolean, null, list or map. Null is supported inside nested maps and lists when permitted by the expert. Top-level parameters must have their declared type; omit a parameter to request the expert’s omission behaviour.
<semantic xmlns="http://camel.apache.org/schema/semantic" expert="moderation" state="${body}">
<evaluation name="responseSafety" operation="response_safety"/>
<evaluation name="safetyCategories" operation="categories">
<parameters>
<parameter name="policy">
<map>
<entry key="labels"><list><string>privacy</string><string>unsafe</string></list></entry>
<entry key="threshold"><number>0.8</number></entry>
</map>
</parameter>
</parameters>
</evaluation>
</semantic> Instruction-driven shorthand
The shorthand fields and Java convenience methods below target instruction-driven operations named boolean, choice and score. Their fields become expert parameters and are validated by the selected expert. TypeSafe AI implements these operations and requires nonblank instructions. All operations use the same evaluation declaration syntax; specialised operations supply their expert-owned parameters as shown above.
Named evaluations
The YAML DSL supports declarations alongside routes, including declarations after their use:
- semantic:
evaluation:
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:
uri: direct:billing
- expression:
simple:
expression: "${exchangeProperty.department} == 'technical'"
steps:
- to:
uri: direct:technical
otherwise:
steps:
- to:
uri: direct:review Names are context-wide. Duplicate declarations across resources and unknown references fail. Reloading a resource replaces its complete set of evaluations, 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. Reload validates the candidate declaration snapshot for initialized expressions before publishing it, including expert capabilities, boolean predicate types, Simple state selectors, and compatible batch selectors across resources. Failed validation leaves the previous declarations available. Startup validates unused declarations as well. Resource reload validates new and changed declarations before publishing them, including unused evaluations. Removed declarations remain removable; an expression that still references a removed name fails when evaluated. Validation registrations are weakly held: the registry does not keep expressions or their providers alive. Constraints can remain until an unused expression is garbage-collected; stopping a route does not necessarily release an expression that is still referenced elsewhere. Loading declarations does not perform inference or start provider resources for validation.
For evaluations using expert or default-expert, the registry bean is resolved when the expression is compiled. Rebinding that name does not update already compiled expressions. After replacing the bean, recreate the consuming routes or expressions, or reload their evaluation declarations so the expressions compile against the replacement bean.
YAML reload and expert bean preparation
YAML declarations are registered after all resources in the load operation have prepared their beans and before routes are constructed. This applies both at startup and on reload, so an expert bean and its evaluations can be introduced together, including in separate resources of the same load operation. XML declaration loading follows the same preparation order; Java helpers validate when register() is called.
Java declarations
Use the fluent helper from camel-semantic inside an ordinary RouteBuilder:
import static org.apache.camel.semantic.SemanticEvaluationsBuilder.semanticEvaluations;
@Override
public void configure() {
semanticEvaluations(this)
.evaluation("department")
.type("choice")
.state("${header.myState}")
.instructions("Which department should handle this message?")
.criterion("billing", "Invoices, payments, and refunds")
.criterion("technical", "Bugs, outages, and technical problems")
.criterion("other", "Everything else")
.register();
from("direct:tickets")
.setProperty("department").language("semantic", "ref:department")
.to("direct:dispatch");
} Call .end().evaluation("anotherName") to add another evaluation to the group, then .register() once to validate and install the entire group atomically. Register before creating expressions that refer to these names. When adding builders to a running context, add the builder declaring shared evaluations before builders whose routes reference them.
Use .type("boolean") with optional .threshold(0.8), .uncertainty(0.1) and .uncertaintyPolicy("non-match") for boolean evaluations. Use .type("score") and successive .level("description") calls for ordered score levels. Applications can also register immutable SemanticEvaluation definitions using SemanticEvaluations.get(context).replace(source, evaluations). On a running context, replacement definitions are validated before publication. If validation fails, the previous definitions remain available. The public get(name) and get(names) methods always read published definitions, including calls made from an expert’s validation callback.
Each Java resource owns one group. Register all of its evaluations together; registering again replaces that resource’s previous group. Reloading a Java resource without the helper removes its declarations. Embedded builders without a resource receive distinct generated source keys. The helper reserves "java:" + resource.getLocation() as the resource source key; passing that key and an empty map to replace removes the resource’s evaluations.
XML declarations
The XML extension belongs to camel-semantic. Include camel-semantic and XML DSL (camel-xml-io-dsl). Camel discovers the extension automatically at startup: use ordinary *.xml files without registering a loader or changing the Camel core model. XML documents without semantic declarations are handled by the standard XML loader, including its bean and route configuration support. XML loaders registered by the application before route resource discovery retain precedence over automatic discovery. Loader discovery is refreshed when the context starts or routes are reloaded. Filenames may contain dots, such as my.tickets.xml. Routes retain their original resource locations and line numbers for debugging and error messages.
Both layouts are supported. To keep declarations alongside routes, use tickets.xml:
<routes>
<semantic>
<evaluation name="department" type="choice" state="${header.myState}">
<instructions>Which department should handle this message?</instructions>
<criterion key="billing" value="Invoices, payments, and refunds"/>
<criterion key="technical" value="Bugs, outages, and technical problems"/>
<criterion key="other" value="Everything else"/>
</evaluation>
</semantic>
<route id="classify-ticket">
<from uri="direct:tickets"/>
<setProperty name="department">
<language language="semantic">ref:department</language>
</setProperty>
<to uri="direct:dispatch"/>
</route>
</routes> To share declarations across route files, use a standalone evaluations.xml:
<semantic>
<evaluation name="department" type="choice" state="${header.myState}">
<instructions>Which department should handle this message?</instructions>
<criterion key="billing" value="Invoices, payments, and refunds"/>
<criterion key="technical" value="Bugs, outages, and technical problems"/>
<criterion key="other" value="Everything else"/>
</evaluation>
</semantic> Load this together with ordinary *.xml, Java or YAML route resources that use ref:department. For example, with Camel Main, set camel.main.routes-include-pattern=classpath:evaluations.xml,classpath:routes.xml. Declarations are registered before consuming routes are configured. Reload replaces the source’s evaluations; removing the semantic block, using an empty block, or deleting the resource removes obsolete definitions. XML source keys are the resource locations.
The combined format supports a routes root with one optional semantic block and ordinary route elements. The standalone format uses a semantic root. Namespace-free documents are supported, as are documents consistently using http://camel.apache.org/schema/semantic, http://camel.apache.org/schema/xml-io, or http://camel.apache.org/schema/spring. A semantic block can also declare xmlns="http://camel.apache.org/schema/semantic" inside a standard routes document; its evaluation elements inherit that namespace. The extension detects the declaration block or semantic root namespace automatically.
These extensions do not validate against Camel’s standard core XSDs. Automatic discovery applies to Camel’s route resource loader; it does not extend Spring’s XML application-context parser or direct JAXB unmarshalling.
For boolean evaluations, threshold, uncertainty and uncertaintyPolicy are optional evaluation attributes. For score evaluations, replace the named criterion elements with ordered level elements, such as <level>Routine</level><level>Urgent</level><level>Critical</level>.
The numeric threshold and uncertainty options accept property placeholders in all three DSLs. For example, Java accepts .threshold("{{semantic.threshold:0.5}}"), and XML accepts threshold="{{semantic.threshold:0.5}}". Values are resolved and validated when declarations are registered.
Java and XML declarations use the same context-wide registry, validation, defaults and adapters as YAML. They do not require camel-yaml-dsl. Their evaluations can also be selected together using refs:name1,name2, as described below.
Exporting routes
Evaluation declarations live outside Camel’s core route model. Generic model exports and runtime route dumps such as camel.main.dumpRoutes=yaml contain only routes; keep declarations separately and load them before reloading an exported route.
The MCP route conversion tool rejects Java or YAML inputs containing semantic declarations because a generic route export would lose those declarations. Convert the routes separately. Extended XML documents must also be separated into declarations and ordinary routes before using the generic converter.
Selected state
A evaluation’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. A selector must be nonblank before property placeholders are resolved. A placeholder that resolves to an empty string retains the existing Simple behavior: it selects an empty string as state. Invalid Simple syntax fails. 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 evaluations can be predicates. A category string is never implicitly a boolean. A direct semantic predicate rejects other result types at initialization. Simple’s bare or negated ${semantic('name')} predicate rejects them before inference when it is matched; comparisons and membership tests can consume their typed values. Decision policy is expert-owned. Camel never derives a Boolean from probability or applies a second threshold to the expert’s returned value. The TypeSafe AI adapter uses an inclusive Boolean probability 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. Its Choice results must name a supplied criterion, and its scores range from zero to the last rubric index and may be fractional. Other experts declare their own policy parameters, labels and score ranges in their contracts.
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.
Batching named evaluations
Use refs:name1,name2 to evaluate independent evaluations against the same selected state. The expression returns a map of normalized decisions keyed by evaluation name. For example, using the declarations above:
from("direct:tickets")
.setProperty("decision").language("semantic", "refs:actionable,department,urgency")
.choice()
.when(simple("${exchangeProperty.decision[actionable]} == true && ${exchangeProperty.decision[department]} == 'billing'"))
.to("direct:billing")
.otherwise()
.to("direct:review"); The same expression works through the generic language integration in YAML and XML. For example, replace the route above with:
- route:
from:
uri: direct:tickets
steps:
- setProperty:
name: decision
expression:
language:
language: semantic
expression: refs:actionable,department,urgency
- setHeader:
name: urgency
expression:
simple:
expression: "${exchangeProperty.decision[urgency]}"
- choice:
when:
- expression:
simple:
expression: "${exchangeProperty.decision[department]} == 'billing'"
steps:
- to:
uri: direct:billing
otherwise:
steps:
- to:
uri: direct:review A result might contain actionable → true, department → "billing" and urgency → 1.2. Reading the stored map does not invoke the provider again. Each evaluation retains its own expert-owned threshold and uncertainty policy. There is no implicit AND/OR: batch expressions, including a batch containing one boolean evaluation, cannot be used as predicates.
CamelSemanticResults holds an immutable map of the detailed SemanticResult objects, keyed by the same names. These retain available probabilities, confidence and provider metadata. CamelSemanticResult remains the single-evaluation diagnostic property for ref:name. Both diagnostic properties are cleared before every semantic evaluation; a batch publishes its decisions and diagnostics only after every answer passes validation. Missing, extra or invalid answers, operational failures and an uncertainty policy of fail fail the entire evaluation through Camel error handling. A valid negative decision or uncertainty handled by non-match remains a normal false result. A route property assigned by Set Property is owned by the route; clear it explicitly before reevaluation if an error handler could otherwise reuse a decision from an earlier attempt.
Batch references are comma-separated names with surrounding whitespace removed. Empty, duplicate and unknown references are rejected. There is no quoting or escaping in this syntax: names containing commas or leading/trailing whitespace require a single ref:name evaluation. Existing single references are unchanged.
All selected evaluations must use the same effective Simple state selector, after applying camel.language.semantic.default-state and resolving property placeholders. Selectors must match exactly; different expressions that happen to return equal values are not interchangeable. The selector is evaluated once per batch. Missing or unsupported state fails before inference. One snapshot of the evaluation definitions is used throughout each batch, including validation and decision policies; the next evaluation sees replacements from resource reload. Evaluations that need a previous answer require separate evaluations.
SemanticAdapter.evaluateBatch(Map<String, SemanticEvaluation>, Object) has a default implementation that invokes the existing single-evaluation method sequentially, preserving experts that do not implement a provider-specific batch optimisation. Evaluations are grouped by the resolved expert instance, and each group is passed to that instance’s batch method. Two instances of the same class remain separate groups. Each group must return exactly its assigned names. Camel validates the complete combined result before publishing decisions or diagnostics, preserving the original reference order.
TypeSafe AI sends its group in one HTTP request using the component’s existing timeout and concurrency limits. A batch spanning experts can make multiple inference requests. No results are cached or combined across exchanges.
EIP integration
Use language("semantic", "ref:name") wherever an expression or boolean predicate is accepted.
EIP / integration point | Usage |
Choice | Store a category with Set Property, then compare that property in ordinary when predicates. |
Filter | Use a boolean evaluation as the filter predicate. |
Validate | Use a boolean evaluation; 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 evaluation against the accumulated body, with a size or timeout limit. |
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 evaluation 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
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 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 evaluations 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 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:
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 evaluation can supply the 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.
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 evaluation 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 evaluation, excluding credentials and sensitive data.
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.
If the retry predicate throws (such as a provider timeout, or a malformed or uncertain result), Camel logs the evaluation failure at WARN level and regards the predicate as false. The message is not redelivered and is sent to direct:escalate through normal exception handling, the same as for a negative decision. The original exception is kept as the caught exception, with the evaluation failure attached as a suppressed exception. To tell an evaluation failure apart from a negative decision in the escalation route, check getSuppressed() of the Exchange.EXCEPTION_CAUGHT exception. Bound provider timeouts as well as the retry count.
Contextual action validation
Use a semantic evaluation 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 evaluation alongside routes:
- semantic:
evaluation:
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. Treat the proposed action as untrusted input: its text can attempt to steer the provider (prompt injection). A positive semantic decision must never override the application’s authorization checks. In this example, direct:checkPermissions performs the application’s existing authorization and rejects unauthorized requests before semantic evaluation:
from("direct:action")
.to("direct:checkPermissions")
.validate().language("semantic", "ref:withinScope")
.to("direct:performAction"); A negative decision raises normal validation failure. With this evaluation’s fail policy, an uncertain decision raises an evaluation error. Timeouts and malformed responses also fail, and none of these outcomes should execute the protected action. Failure handling may reject the request or send it for human review; do not use continued(true) to resume at the action. The example’s threshold is a decision policy, not an accuracy guarantee.
This pattern uses Validate and existing authorization services. It does not add a semantic AuthorizationPolicy implementation or replace a component’s internal guardrail interfaces.
Static operation contracts
Read SemanticCapabilities.from(MyExpert.class) to inspect a contract without constructing an expert, loading a model or contacting a service. The runtime uses the same cached class contract. The returned operation and parameter maps, supported labels and input shapes are immutable.
For example, an expert can declare a fixed detection operation:
@SemanticExpert(name = "detector", description = "Text injection detector",
provider = "example", artifactId = "example-expert",
operations = @SemanticOperation(name = "injection", description = "Detect injection",
inputTypes = SemanticExpert.InputType.TEXT,
inputRequirements = "Text to assess",
resultType = SemanticExpert.ResultType.BOOLEAN,
resultMeaning = "True means injection was detected",
probability = true, probabilityMeaning = "Probability of injection",
parameters = @SemanticParameter(name = "threshold", description = "Detection threshold",
type = Number.class, minimum = 0, maximum = 1,
omission = "Use the service's configured threshold")))
public class SecurityExpert implements SemanticAdapter {
// validate, validateInput and evaluate implement this declared contract.
} Each operation declares a result type and its meaning. Score operations document their scale and can declare numeric bounds. Choice and classification operations can declare a fixed label set; an instruction-driven expert validates its application-supplied vocabulary itself. Optional probability, per-label probability and confidence fields must declare their meaning. Camel does not manufacture confidence from a verdict, nor require optional information on every response. It rejects information the contract did not declare, malformed numeric values, wrong result types and results outside declared common constraints.
An expert applies explicitly requested policy exactly once, either in its service or its adapter. It preserves service defaults when parameters are absent. If requested policy needs unavailable confidence or probability, the expert rejects the declaration when known statically, or fails that evaluation when the absence is response-dependent. It must not silently ignore the request. Uncertain outcomes use an operation’s documented values or an error; Camel has no universal uncertainty sentinel. Classification filtering, including per-label thresholds, also belongs to the expert. TypeSafe AI’s uncertaintyPolicy supports fail or non-match; other experts need not support that vocabulary.
Generated expert descriptors, expert-specific offline validation and Catalog API extensions are separate work. This contract does not add a new Camel Catalog model.
Implementing an adapter
Implement org.apache.camel.semantic.SemanticAdapter. Advertise the implementation in META-INF/services/org/apache/camel/semantic-adapter using Camel’s FactoryFinder format:
class=com.example.MySemanticAdapter Camel components can generate this descriptor with @JdkService("semantic-adapter"). Discovery checks the declarations for ambiguity before resolving the implementation through FactoryFinder. Repeated declarations of the same class are accepted; different classes require explicit selection. Only the selected adapter is constructed, through Camel’s Injector. Explicit bean or class selection also works when packaging does not expose discovery resources.
The language checks the static operation contract and then calls validate for operation-specific parameter combinations and bean configuration before traffic. validateInput checks detailed message requirements before evaluation. All input checks in a batch run before any expert is invoked. evaluate returns a typed SemanticResult after applying the expert’s policy. A probability-only Boolean result is invalid: the expert must supply its Boolean verdict. Providers should also validate when their SPI is called directly. evaluate must be thread-safe, bound time and resource consumption, honor interruption and propagate operational failures without disclosing input or credentials. Implement CamelContextAware for context injection and Camel Service interfaces when the adapter owns resources. Created service adapters are stopped and shut down by Camel; registry references are not started or stopped again by the language. Provider-specific component services remain under normal Camel lifecycle management.
For TypeSafe AI, boolean, choice and score map to Noul, Choice and Score respectively. The adapter reuses the component endpoint’s managed HTTP client, including timeout, concurrency and cancellation. Routine integration tests use deterministic adapters and local HTTP fixtures; they do not establish a model’s classification quality.
The Semantic Evaluation language supports the following options which are listed below.
| Name | Default | Java Type | Description |
|---|---|---|---|
| Required The name of the language to use. | ||
|
| Whether to trim the source code to remove leading and trailing whitespaces and line breaks. | |
|
| Whether a result of the expression that is a String starting with resource: is loaded as a resource and its content becomes the result, e.g. a script that returns resource:file:order.json or resource:classpath:templates/order.json (a name without a scheme is a classpath resource). Off by default; the resource: prefix on the expression text itself is always resolved. Applies to the expression used as a value, not as a predicate. |