This is an automated email from the ASF dual-hosted git repository.

luigidemasi pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-website.git

commit ea3376fee82e9e63217c1b82d29e57a67a1cf333
Author: Luigi De Masi <[email protected]>
AuthorDate: Thu Sep 24 20:34:13 2026 +0200

    Blog: Introduce semantic decisions with Jev and Camel
    
    Co-authored-by: Codex <[email protected]>
    Signed-off-by: Luigi De Masi <[email protected]>
---
 .../camel-semantic-decisions-featured.png          | Bin 0 -> 2300371 bytes
 .../09/semantic-evaluation-system-one/index.md     | 451 +++++++++++++++++++++
 2 files changed, 451 insertions(+)

diff --git 
a/content/blog/2026/09/semantic-evaluation-system-one/camel-semantic-decisions-featured.png
 
b/content/blog/2026/09/semantic-evaluation-system-one/camel-semantic-decisions-featured.png
new file mode 100644
index 00000000..ead067af
Binary files /dev/null and 
b/content/blog/2026/09/semantic-evaluation-system-one/camel-semantic-decisions-featured.png
 differ
diff --git a/content/blog/2026/09/semantic-evaluation-system-one/index.md 
b/content/blog/2026/09/semantic-evaluation-system-one/index.md
new file mode 100644
index 00000000..9f8a36b3
--- /dev/null
+++ b/content/blog/2026/09/semantic-evaluation-system-one/index.md
@@ -0,0 +1,451 @@
+---
+title: "TypeSafe Jev meets Apache Camel: semantic decisions in Camel routes"
+date: 2026-09-24
+draft: false
+authors: [ luigidemasi ]
+categories: ["AI", "EIP"]
+keywords: ["apache camel", "typesafe ai", "jev", "system one", "semantic 
evaluation", "yaml", "routing", "validation"]
+preview: "Use Camel's Semantic language with Jev to classify messages, 
validate answers and actions, collect enough context for an investigation, and 
review outgoing messages in YAML routes."
+---
+
+Jev is gaining momentum, and there is a reason why: it addresses a practical 
problem in automation. Many workflows need a model to make a small, specific 
judgment:
+
+- *Which team should handle this request?*
+- *Does this answer address the question?*
+- *Does this action fit the approved task?*
+
+[TypeSafe AI built Jev as a System One 
model](https://typesafe.ai/blog/introducing-system-one-models-and-jev), 
designed for **fast, structured decisions**. Give it the relevant state and a 
question, and it returns a category, a score or a probability that your 
application can use directly.
+
+That is useful when an AI evaluation sits in the path of a message: the result 
needs to fit into the next routing decision, validation step or comparison.
+
+For an integration developer, the appeal is concrete. We can use a model to 
judge the meaning of a customer message, then let ordinary code decide what 
happens next. A category selects a support team. A probability feeds a 
validation predicate. A score helps order retrieved passages. **Camel still 
controls the workflow:** destinations, permissions, retry budgets and failure 
handling.
+
+An ecosystem is starting to form around this approach:
+
+- **[Jev](https://typesafe.ai/)** provides typed decisions through TypeSafe's 
hosted service.
+- **[Laya](https://github.com/NandhaKishorM/laya)** offers open model weights 
for typed decisions.
+- The **[System One SDK](https://github.com/asynq-io/system-one)** provides a 
Python interface to hosted and local backends.
+- **[System One Models](https://systemonemodels.org/)** is an independent 
directory of models, use cases, examples and guides.
+
+These are early signs of a broader interest in making semantic judgments 
available as ordinary software operations. Each model's capabilities, 
deployment requirements and behavior still need to be evaluated for the task.
+
+Camel brings these decisions into integration routes through `camel-semantic`, 
an abstraction layer above System One model providers. It makes semantic 
decisions available to existing EIPs and control flow. The [`camel-typesafe-ai` 
component](/components/next/typesafe-ai-component.html) supplies the adapter 
that connects this abstraction to TypeSafe AI's Jev models.
+
+We'll follow a support workflow through six practical examples, using YAML 
throughout: classify and filter requests, validate answers and proposed 
actions, collect enough context for an investigation, and check outgoing 
replies.
+
+> **Version requirement:** These examples require **Camel 4.23 or later**. 
Before the 4.23 release, use a current `4.23.0-SNAPSHOT` build and keep all 
Camel dependencies on the same version.
+>
+> The Semantic language is initially available with *Preview* support status.
+
+## What a System One model returns
+
+[TypeSafe AI describes Jev](https://docs.typesafe.ai/introduction) as a System 
One model: it evaluates questions about supplied state and returns structured 
decisions. Its three primitives map to familiar route operations:
+
+| TypeSafe question | Result | Camel use |
+| --- | --- | --- |
+| Choice | One supplied category, with probabilities and confidence | Store 
the category, then route on it |
+| Noul | A probability that a statement is true | Apply a threshold to obtain 
a predicate |
+| Score | A position on an ordered rubric, with probabilities and confidence | 
Store the score, then compare or sort |
+
+For example:
+
+- *“Which department handles this request?”* has a small, predefined answer 
set.
+- *“Does this answer address the request?”* is a yes/no judgment.
+- *“How useful is this passage?”* needs an ordered rubric.
+
+**Keep questions narrow** and give the model the relevant context. Separate 
questions can evaluate separate concerns; Camel combines their results and 
controls the workflow. A structured result can still be wrong, so evaluate 
questions and thresholds against representative messages before relying on them.
+
+## A common Camel layer for System One models
+
+`camel-semantic` is **Camel's abstraction layer above System One model 
providers**. It exposes semantic decisions as standard Camel expressions and 
predicates, so they can participate directly in existing EIPs and control flow:
+
+- **Choice:** select a branch using a semantic decision.
+- **Filter and Validate:** check whether a message should proceed.
+- **Loop:** decide whether another iteration is needed.
+- **Redelivery:** decide whether an operation is worth retrying.
+
+Routes define the questions, select the state to evaluate and set the decision 
policy. A provider adapter performs the evaluation and returns the result 
through a common contract. This keeps provider-specific request and response 
handling out of the route's decision logic.
+
+`camel-typesafe-ai` supplies **the Jev adapter for `camel-semantic`**. The 
component handles communication with TypeSafe AI, including credentials, HTTP 
transport, timeouts and concurrency limits. Its adapter:
+
+- Translates Camel's question definitions into TypeSafe requests.
+- Queries Jev with the selected message state.
+- Maps Jev's answers back to Camel's common semantic results.
+
+For a boolean question, the adapter requests a Noul probability from Jev, and 
Camel applies the configured threshold and uncertainty policy to produce a 
predicate result. A category can be evaluated once, stored in an exchange 
variable and used by ordinary Choice branches. Loop and redelivery predicates 
can use semantic decisions while retaining explicit iteration and retry limits 
in the route.
+
+Every example below invokes the **`semantic` language** from the route. Camel 
delegates evaluation to the TypeSafe adapter, which queries Jev and returns the 
result to the EIP.
+
+With the TypeSafe adapter as the sole provider on the classpath, Camel 
discovers it automatically. **Each semantic invocation performs an 
evaluation**; results are reused only when the route stores them explicitly.
+
+## Configure the provider
+
+The examples use `camel-yaml-dsl`, `camel-semantic` and `camel-typesafe-ai`, 
all at the **same Camel version**.
+
+The adapter uses the TypeSafe AI component configuration. For Camel Main or 
JBang, put these settings in `application.properties`:
+
+```properties
+camel.component.typesafe-ai.api-key={{env:TYPESAFE_API_KEY}}
+camel.component.typesafe-ai.model=jev-1.13.0
+camel.component.typesafe-ai.request-timeout=5000
+camel.component.typesafe-ai.max-concurrent-requests=8
+```
+
+Provide `TYPESAFE_API_KEY` through your deployment environment. The 
five-second timeout is an example budget, not a latency claim.
+
+**Pin the [model version](https://docs.typesafe.ai/models)** when tuning 
decision thresholds: an alias such as `jev-latest` can move independently of 
your routes.
+
+Each example keeps its question beside the route or interceptor for 
readability. Questions can also be declared in separate YAML resources, or 
after the routes that refer to them. **Question names must be unique across the 
CamelContext.** Reloading a resource replaces that resource's questions, 
including removing declarations no longer present. Loading a question 
definition does not call the provider.
+
+A question reads the **body by default**. Its `state` option can select a 
variable, header or exchange property using Simple. The selected value must be 
a string, map or list.
+
+For an HTTP stream, convert explicitly with `state: ${bodyAs(String)}`. Use 
stream caching if subsequent processors also need that stream.
+
+For a boolean question, `threshold: 0.8` selects the decision boundary. With 
`uncertainty: 0.05`, probabilities from **0.75 through 0.85** fall in the 
uncertainty band, including the endpoints:
+
+- **`fail`** raises an evaluation error within that band.
+- **`non-match`** returns false within that band.
+
+These values are *illustrative policy choices*, not an 80% accuracy guarantee. 
Provider errors remain errors under either policy.
+
+Each `direct:` destination such as `direct:billing` or `direct:performAction` 
represents an application route you supply. They make the integration boundary 
explicit; these fragments are not a complete support application.
+
+## 1. Classify once, then route
+
+The first useful decision is ownership. Pass a string such as *“I was charged 
twice for my subscription”* to `direct:classify`:
+
+```yaml
+- semantic:
+    question:
+      department:
+        type: choice
+        instructions: Which team should handle this support request?
+        criteria:
+          billing: Invoices, payments, subscriptions and refunds
+          technical: Product failures, outages and configuration problems
+          other: Requests that do not belong to either team
+
+- route:
+    id: classify-ticket
+    from:
+      uri: direct:classify
+      steps:
+        - setVariable:
+            name: department
+            expression:
+              language:
+                language: semantic
+                expression: ref:department
+        - choice:
+            when:
+              - simple: "${variable.department} == 'billing'"
+                steps:
+                  - to: direct:billing
+              - simple: "${variable.department} == 'technical'"
+                steps:
+                  - to: direct:technical
+            otherwise:
+              steps:
+                - to: direct:review
+```
+
+The route has three responsibilities:
+
+1. **Evaluate once:** Set Variable stores the category and preserves the 
message body.
+2. **Reuse the result:** Choice compares the stored category, so additional 
branches do not add provider calls.
+3. **Handle other requests:** the `other` criterion covers requests outside 
the two specialist teams; `otherwise` sends them for review.
+
+This also gives us a reusable department variable for metrics or later 
routing. Exchange variables hold application state without adding message 
headers; copy a value into a header only when a destination needs it. It stays 
valid only as long as the relevant input stays the same. Reevaluate if a later 
step changes the content on which the decision depends.
+
+The same approach works for Recipient List, Routing Slip, Enrich and To 
Dynamic: map a category to destinations defined by the route author. For 
instance, `billing` can select a billing knowledge source. Keep endpoint URIs 
and processing sequences in application configuration.
+
+## 2. Filter messages before doing more work
+
+Some messages never need to enter the support workflow. Use the boolean 
`relevant` question directly as a Filter predicate:
+
+```yaml
+- semantic:
+    question:
+      relevant:
+        type: boolean
+        instructions: Does this message ask for help with our product or 
account?
+        threshold: 0.8
+        uncertainty: 0.05
+        uncertaintyPolicy: non-match
+
+- route:
+    id: filter-ticket
+    from:
+      uri: direct:incoming
+      steps:
+        - filter:
+            expression:
+              language:
+                language: semantic
+                expression: ref:relevant
+            steps:
+              - to: direct:classify
+```
+
+With this question's `non-match` policy:
+
+- A **positive decision** reaches classification.
+- A **negative or uncertain decision** skips the Filter's child steps.
+- A **timeout or malformed response** fails the exchange.
+
+There are no steps after Filter in this route, so processing ends there. In a 
longer route, **steps following Filter would still run**.
+
+If rejected messages need an audit trail or human review, use Choice with an 
explicit rejection branch instead of discarding them.
+
+This predicate can also run inside Split when an existing collection contains 
records that need individual checks. The split supplies the records; semantic 
evaluation does not extract a collection from prose.
+
+## 3. Check an answer before delivering it
+
+A generated answer may be fluent but miss the customer's actual request. 
Supply both pieces of information in the body:
+
+```json
+{
+  "request": "How do I download the invoice for last month's payment?",
+  "answer": "Open Billing, select the payment, and choose Download invoice."
+}
+```
+
+Then validate it before the delivery route:
+
+```yaml
+- semantic:
+    question:
+      answersRequest:
+        type: boolean
+        instructions: Does the proposed answer address the supplied customer 
request?
+        threshold: 0.8
+        uncertainty: 0.05
+        uncertaintyPolicy: fail
+
+- route:
+    id: check-answer
+    from:
+      uri: direct:checkAnswer
+      steps:
+        - validate:
+            expression:
+              language:
+                language: semantic
+                expression: ref:answersRequest
+        - to: direct:deliverAnswer
+```
+
+The delivery step runs only after validation succeeds:
+
+- A **false result** raises Camel's normal validation exception.
+- An **uncertain result, timeout or invalid response** also prevents delivery.
+
+Configure the application's error handling to request a revision or send the 
case for review.
+
+This checks whether the answer *addresses the request*. It does not, by 
itself, verify every factual claim in the answer. Add authoritative reference 
material and a separate, narrowly defined check when factual support matters.
+
+The same pattern can validate the input before calling a generative AI 
component. It works at the route boundary; it does not install a LangChain4j 
internal guardrail implementation.
+
+## 4. Check whether an allowed action fits the task
+
+An action can be permitted by the user's role and still be the wrong action 
for the current task. A support agent asked to explain an invoice should not 
decide to cancel the subscription.
+
+**Keep ordinary authorization first**, then add a contextual check:
+
+```yaml
+- semantic:
+    question:
+      withinScope:
+        type: boolean
+        instructions: Does the proposed action serve the supplied approved 
task?
+        threshold: 0.8
+        uncertainty: 0.05
+        uncertaintyPolicy: fail
+
+- route:
+    id: check-action
+    from:
+      uri: direct:checkAction
+      steps:
+        - to: direct:checkPermissions
+        - to: direct:loadApprovedTask
+        - validate:
+            expression:
+              language:
+                language: semantic
+                expression: ref:withinScope
+        - to: direct:performAction
+```
+
+The first two steps are application responsibilities:
+
+- **`direct:checkPermissions`** checks identity, permissions and tenant 
boundaries. It must reject unauthorized requests.
+- **`direct:loadApprovedTask`** loads the task from trusted application state 
and produces a body such as:
+
+```json
+{
+  "approvedTask": "Explain the duplicate subscription charge; do not change 
the account.",
+  "proposedAction": {
+    "operation": "cancelSubscription",
+    "reason": "Avoid another charge"
+  }
+}
+```
+
+**The approved task must come from trusted application state.** The proposed 
action must not be able to overwrite it. Only after both checks succeed does 
`direct:performAction` execute.
+
+This is useful for AI tool and MCP-backed routes as well as ordinary 
application commands; [Camel's tool authorization 
example](/blog/2026/09/securing-ai-agent-tools/) shows the underlying 
permission pattern.
+
+> **Stop before the action.** A negative decision, uncertainty or evaluation 
failure must stop execution or divert it to review. Do not configure 
`continued: true` on those failures, because that would resume processing 
toward the action.
+
+Semantic validation adds a contextual judgment; **identity and permissions 
remain authoritative**.
+
+## 5. Collect enough context to investigate a problem
+
+A useful bug report often arrives in pieces. A customer might send:
+
+1. “The export is broken.”
+2. “In Chrome, I open Billing → Invoices, select last month and click Export 
CSV.”
+3. “The page shows 230 invoices, but the downloaded CSV contains only the 
first 50. I need every invoice in that period.”
+
+**The useful decision is whether the messages together explain the problem 
well enough to investigate.** A message count cannot tell us that: three 
messages saying “it still doesn't work” add little, while one detailed message 
might be sufficient.
+
+[Aggregate](/components/next/eips/aggregate-eip.html) can collect the messages 
for each case and use a semantic completion predicate to decide when to send 
them onward. The application supplies a `caseKey` that identifies the tenant 
and case, and each incoming body contains one message as text.
+
+```yaml
+- semantic:
+    question:
+      readyForInvestigation:
+        type: boolean
+        instructions: >-
+          Do these messages together describe a problem well enough to
+          start an investigation? Require concrete steps or a triggering
+          action, relevant environment or context, the expected outcome,
+          and the observed outcome. Vague statements such as "it is broken"
+          are not enough. Details may be spread across several messages.
+        state: "${exchangeProperty.CamelGroupedExchange}"
+        threshold: 0.8
+        uncertainty: 0.05
+        uncertaintyPolicy: non-match
+
+- route:
+    id: collect-support-details
+    from:
+      uri: direct:supportDetail
+      steps:
+        - aggregate:
+            aggregationStrategy: 
"#class:org.apache.camel.processor.aggregate.GroupedBodyAggregationStrategy"
+            correlationExpression:
+              simple: "${header.caseKey}"
+            completionPredicate:
+              expression:
+                language:
+                  language: semantic
+                  expression: ref:readyForInvestigation
+            completionSize: 8
+            completionTimeout: 60000
+            steps:
+              - choice:
+                  when:
+                    - simple: "${exchangeProperty.CamelAggregatedCompletedBy} 
== 'predicate'"
+                      steps:
+                        - to: direct:investigateCase
+                  otherwise:
+                    steps:
+                      - to: direct:caseReview
+```
+
+`GroupedBodyAggregationStrategy` collects the message bodies into a list. 
While the group is open, that list lives in `CamelGroupedExchange`, so the 
question's `state` selects it explicitly. When aggregation completes, the list 
becomes the outgoing body.
+
+The question is evaluated against the accumulated messages after each arrival:
+
+- **Enough detail:** the predicate completes the group and sends it to 
`direct:investigateCase`.
+- **Missing detail or an uncertain decision:** Camel keeps collecting messages 
for that case.
+- **Eight messages or about one minute of inactivity:** Camel completes the 
group even if the predicate has not matched, and sends it to 
`direct:caseReview` for follow-up. A timeout does not imply that the report is 
ready.
+
+Both destinations are application routes that receive the collected messages. 
Provider failures propagate through normal Camel error handling. The 
application owns case identity and lifecycle, including how late messages are 
handled after a group completes.
+
+Here, **the semantic predicate controls when Aggregate has enough information 
to proceed**. Camel supplies the grouping, limits and dispatch; there is no 
separate step to store the semantic result in a variable, header or property.
+
+## 6. Share a check across outgoing messages
+
+Several routes may send replies to customers. Instead of repeating the same 
check in each route, use [Intercept Send To 
Endpoint](/components/next/eips/intercept.html) at their shared sending 
boundary.
+
+Here the body contains the prepared reply text. A semantic predicate checks 
whether it promises financial compensation and should go to human review:
+
+```yaml
+- semantic:
+    question:
+      needsReview:
+        type: boolean
+        instructions: Does this outgoing reply promise a refund, discount or 
other financial compensation?
+        threshold: 0.8
+        uncertainty: 0.05
+        uncertaintyPolicy: fail
+
+- interceptSendToEndpoint:
+    uri: "direct:outbound-*"
+    skipSendToOriginalEndpoint: true
+    onWhen:
+      expression:
+        language:
+          language: semantic
+          expression: ref:needsReview
+    steps:
+      - to: direct:humanReview
+      - stop: {}
+
+- route:
+    id: reply-by-email
+    from:
+      uri: direct:replyByEmail
+      steps:
+        - to: direct:outbound-email
+
+- route:
+    id: reply-by-chat
+    from:
+      uri: direct:replyByChat
+      steps:
+        - to: direct:outbound-chat
+```
+
+The interceptor covers matching sends from both routes:
+
+- **Review needed:** the reply goes to `direct:humanReview`. The original send 
is skipped, and `stop` ends processing of that exchange.
+- **Review not needed:** the reply reaches the selected outbound route 
normally.
+- **Uncertain decision or provider failure:** the exchange fails before either 
outbound route is called.
+
+`direct:outbound-email` and `direct:outbound-chat` are application routes that 
perform the actual delivery. The review route must retain the reply for a 
separate approval workflow; it does not send it automatically.
+
+**Keep the interceptor before the routes in the YAML file.** It evaluates each 
matching send, so a route that sends twice performs two checks. Avoid 
continuing past evaluation failures in the error handler, just as in the 
action-validation example.
+
+## Choosing the next integration point
+
+The same approach extends to other parts of a Camel workflow. The six examples 
above cover classification, filtering, validation, aggregation and shared 
checks. Other useful integration points include:
+
+| If the workflow needs… | Apply the same idea through… | Keep in application 
control |
+| --- | --- | --- |
+| Several destinations or a processing sequence | Recipient List, Routing 
Slip, Enrich or Poll Enrich | Map categories to configured endpoints or 
sequences |
+| Another refinement step | Loop | An iteration or time budget and an explicit 
stopping condition |
+| The next processing step | Dynamic Router | Map labels to configured 
endpoint URIs, enforce a hop budget and return `null` to finish |
+| Different limits for different workloads | Throttle | Fixed rates or 
concurrency limits for each classified group |
+| Follow-up after processing | On Completion | Auditing or follow-up only; it 
cannot prevent an action already performed |
+
+The practical benefit is that a semantic judgment becomes a small, visible 
part of the route. The surrounding Camel workflow still owns destinations, 
permissions, retry limits and the point at which an action happens.
+
+## What I would like to see next
+
+For me, `camel-semantic` is a starting point for a closer relationship between 
semantic evaluation and Camel's EIPs.
+
+Filter, Validate and Aggregate already consume semantic predicates directly. 
The classification example shows where I would like that integration to go 
further: it uses a separate step to evaluate the question and store its answer 
before Choice uses it.
+
+My vision is for **the semantic question to become a parameter of the EIP 
itself**. The route author would supply the question, the state to evaluate and 
the rules for using the answer. The EIP would request the evaluation through 
`camel-semantic` and consume the result internally, without requiring an 
intermediate variable, header or exchange property.
+
+I am keeping the form of that integration open. It could mean extending 
existing EIPs, or introducing new EIPs with built-in semantic evaluation that 
live alongside the existing ones. What matters to me is being able to express 
the question where the decision is made, with Camel managing the evaluation.
+
+Either approach would need to make **evaluation timing explicit**: when to 
evaluate a question, when to reuse its answer and when updated state requires a 
fresh evaluation. Thresholds, uncertainty handling and provider failures would 
remain explicit parts of the route's behavior.
+
+Keeping a result for auditing or reuse by later processors would still be an 
option. It would no longer be a required step just to connect a semantic 
question to the EIP that needs its answer.
+
+I would keep this integration built on `camel-semantic` and its provider 
adapters. The route would describe the decision and the workflow, while the 
adapter would remain responsible for querying Jev or another System One model.

Reply via email to