Semantic Evaluation
Since Camel 4.23
The Semantic language evaluates named questions through a provider-independent adapter. Boolean questions produce decisions, choice questions produce category strings, and score questions produce numbers on an ordered rubric. Calls are synchronous and may block while inference runs. Provider errors propagate through normal Camel error handling.
Dependencies and providers
Add org.apache.camel:camel-semantic and a provider, such as 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 question 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 question’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.
Fixed and instruction-driven experts
Instructions are optional in the common declaration model. The selected expert decides whether they are required, optional or unsupported. TypeSafe AI still requires nonblank instructions. A fixed detector can reject instructions and caller-defined criteria because its model already defines the question it answers. Unsupported result types, instructions, criteria and decision policies fail during initialization, before inference. Replacements for declarations already referenced by initialized expressions are validated before publication; failed replacements retain the previous definitions.
For example, given registered instances security (a fixed injection detector) and general (an instruction-driven expert):
- semantic:
question:
injection:
expert: security
type: boolean
state: "${body}"
threshold: "{{security.injection.threshold}}"
uncertainty: "{{security.injection.uncertainty}}"
uncertaintyPolicy: fail
department:
expert: general
type: choice
instructions: Which department should handle this message?
criteria:
billing: Invoices, payments and refunds
technical: Bugs, outages and technical problems Use ref:injection and ref:department in ordinary semantic expressions. Expert names can contain property placeholders, resolved during runtime selection. Java uses .expert("security"); XML uses <question name="injection" type="boolean" expert="security"/>. An omitted instruction stays absent in all three DSLs; an explicitly blank instruction is invalid.
The selected text is data. A fixed injection detector must not prepend a synthetic instruction. Its boolean probability is the score for the positive class (INJECTION), even when BENIGN wins. Threshold and uncertainty policies produce the decision. A negative result means this evaluation did not detect injection; it does not establish general safety or grant authorization. Probability is distinct from a rubric SCORE and from optional provider confidence. Missing optional metadata remains absent, and invalid output or provider failure remains an error. Continue, quarantine and review are application decisions expressed using Camel EIPs.
Named questions
The YAML DSL supports declarations alongside routes, including declarations after their use:
- semantic:
question:
department:
type: choice
instructions: Which department should handle this message?
criteria:
billing: Invoices, payments and refunds
technical: Bugs, outages and technical problems
actionable:
type: boolean
instructions: Does this message contain an actionable request?
threshold: 0.8
uncertainty: 0.1
uncertaintyPolicy: fail
urgency:
type: score
instructions: Assess urgency
criteria:
- Routine request
- Time-sensitive request
- Immediate attention needed
- route:
from:
uri: direct:tickets
steps:
- setProperty:
name: department
expression:
language:
language: semantic
expression: ref:department
- choice:
when:
- expression:
simple:
expression: "${exchangeProperty.department} == 'billing'"
steps:
- to: direct:billing
- expression:
simple:
expression: "${exchangeProperty.department} == 'technical'"
steps:
- to: direct:technical
otherwise:
steps:
- to: direct:review Names are context-wide. Duplicate declarations across resources and unknown references fail. Reloading a resource replaces its complete set of questions, including removing declarations no longer present. Development-mode route reload also removes definitions from deleted or renamed files before parsing replacements. This replacement does not make the surrounding route reload transactional. Existing expressions resolve the current definition on their next evaluation. 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. Unused declarations are validated when first referenced, as at startup. 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 questions 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 question declarations so the expressions compile against the replacement bean.
YAML reload and expert bean preparation
YAML declarations are registered during the loader’s existing pre-parse phase, before beans in the resource set are prepared. On initial loading, expert validation waits until expressions initialize, after bean preparation. On reload, replacements for initialized expressions are validated during pre-parsing, so the selected expert must already be registered.
For example, changing an existing question to expert: newSecurity and introducing a newSecurity bean in the same reload fails if that bean is not yet in the registry. Keep expert beans registered outside the reloaded resource, prepare the new bean before reloading the declarations, or restart the application when changing both together. XML declaration loading runs after resource-set bean preparation; Java helpers validate when register() is called. The three DSLs share declaration fields and validation rules, but this YAML bean-order constraint remains because the existing resolver hook runs before bean preparation.
Java declarations
Use the fluent helper from camel-semantic inside an ordinary RouteBuilder:
import static org.apache.camel.semantic.SemanticQuestionsBuilder.semanticQuestions;
@Override
public void configure() {
semanticQuestions(this)
.question("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().question("anotherName") to add another question 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 questions before builders whose routes reference them.
Use .type("boolean") with optional .threshold(0.8), .uncertainty(0.1) and .uncertaintyPolicy("non-match") for boolean questions. Use .type("score") and successive .level("description") calls for ordered score levels. Applications can also register immutable SemanticQuestion definitions using SemanticQuestions.get(context).replace(source, questions).
Each Java resource owns one group. Register all of its questions 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 questions.
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>
<question 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"/>
</question>
</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 questions.xml:
<semantic>
<question 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"/>
</question>
</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:questions.xml,classpath:routes.xml. Declarations are registered before consuming routes are configured. Reload replaces the source’s questions; 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 question 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 questions, threshold, uncertainty and uncertaintyPolicy are optional question attributes. For score questions, 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 questions can also be selected together using refs:name1,name2, as described below.
Exporting routes
Question 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 question’s optional state Simple expression overrides camel.language.semantic.default-state, whose default is ${body}. Selectors are compiled before evaluation; selected strings, maps and lists are passed as data and are never evaluated recursively. A missing selected header fails instead of falling back to the body. 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 questions can be predicates. A category string is never implicitly a boolean. A probability-based boolean uses an inclusive threshold (default 0.5). A nonzero uncertainty defines an inclusive band around that threshold. fail (the default) raises an error within the band; non-match returns false. An already-boolean provider supports the default policy without inventing a probability. Additional policy requirements must be supported by the provider. Choice results must name a declared criterion. Scores range from zero to the last rubric index and may be fractional.
Probabilities, provider confidence and selected values are separate. Missing optional fields remain absent. Results can retain provider/model identity and usage metadata. These fields do not imply equivalent quality or calibration when switching providers. Timeouts, malformed answers and unsupported capabilities are errors, distinct from valid negative decisions or unmatched Choice results.
Batching named questions
Use refs:name1,name2 to evaluate independent questions against the same selected state. The expression returns a map of normalized decisions keyed by question 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: direct:billing
otherwise:
steps:
- to: 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 question retains its own threshold and uncertainty policy. There is no implicit AND/OR: batch expressions, including a batch containing one boolean question, 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-question 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 questions 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 question definitions is used throughout each batch, including validation and decision policies; the next evaluation sees replacements from resource reload. Questions that need a previous answer require separate evaluations.
SemanticAdapter.evaluateBatch(Map<String, SemanticQuestion>, Object) has a default implementation that invokes the existing single-question method sequentially, preserving compatibility with existing adapters. Questions 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 question as the filter predicate. |
Validate | Use a boolean question; false follows normal validation failure handling. |
Set Header / Set Property | Store a category or score for explicit reuse in later steps. |
Aggregate correlation | Use a category as a correlation expression, retaining tenant or case identifiers where needed. |
Aggregate completion | Evaluate a boolean question against the accumulated body, with a size or timeout limit. |
Recipient List | Map a category to a configured list of recipient URIs. |
Routing Slip | Map a category to a predefined processing sequence. |
Enrich | Map a category to a configured resource URI and use an ordinary aggregation strategy. |
Loop | Reevaluate a boolean question on updated state, with an explicit iteration or time budget. |
Sort | Score each item once, store the scores, then sort using a deterministic comparator. |
On Exception / retryWhile | Evaluate whether another attempt is worthwhile, with an explicit retry budget checked before inference. |
Contextual action validation | Use Validate after ordinary permission checks and before executing the action. |
Destination mappings belong to trusted route configuration; provider output should select known labels rather than supply unrestricted endpoint URIs. Expressions reevaluate on each invocation. There is no implicit exchange-wide inference cache. Store results explicitly when reuse is intended, and reevaluate after relevant input changes.
Predicates and explicit reuse
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 questions can also be used directly as ordinary when predicates. These patterns use the existing EIP model and work with the Java, XML and YAML DSLs.
Aggregation and destinations
For 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 question 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 question named retryable with state: $\{exchangeProperty.retryState}. In this example, the request body is a string. onExceptionOccurred prepares the state before the retry predicate runs; onRedelivery runs later and is too late for this purpose. Include only the failure details needed by the question, excluding credentials and sensitive data.
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 question for an additional check such as "Does this proposed action serve the approved task?" after normal identity, permission and tenant checks have succeeded. The semantic decision must not grant permissions that those checks denied.
For example, declare this question alongside routes:
- semantic:
question:
withinScope:
type: boolean
instructions: Does the proposed action serve the approved task?
state: ${body}
threshold: 0.8
uncertainty: 0.05
uncertaintyPolicy: fail The selected state should contain the approved task and the proposed action. Obtain the approved task from trusted application state; do not let the proposed action redefine it. 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 question’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.
Expert capabilities
SemanticAdapter.capabilities() describes accepted text or structured input, supported result types, instruction and criteria support, optional probability/confidence semantics, and known limits. The default reads @SemanticExpert from the provider class. Unannotated legacy adapters report known=false, with no claimed capabilities. The language still requires instructions for these legacy adapters before calling their existing validate method. Providers that accept absent instructions must explicitly advertise optional or unsupported instructions. An adapter can override capabilities for its configured model using the named builder:
@Override
public SemanticCapabilities capabilities() {
return SemanticAdapter.super.capabilities().toBuilder()
.maxChoices(32)
.build();
} This example narrows an annotated adapter’s limits. SemanticCapabilities.builder() creates an explicit descriptor from scratch; input and result sets are empty until supplied. Descriptors are immutable. Capability reporting and declaration validation must not perform inference or load models. Runtime input and response validation still apply.
Providers declare static capabilities with org.apache.camel.semantic.SemanticExpert from camel-semantic. The annotation describes the implementation; it neither selects an expert nor describes application bean names. Runtime capabilities and validate remain authoritative for configured instances. No dedicated expert catalog model or discovery API is provided.
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 known capabilities and then calls validate for model-specific restrictions before traffic. 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. |