This is an automated email from the ASF dual-hosted git repository.
oscerd pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new 1d9d9a5b1516 CAMEL-24223: camel-jbang-mcp - Locale.ROOT cost
formatting and model-specific disclaimer (follow-up) (#25167)
1d9d9a5b1516 is described below
commit 1d9d9a5b1516a87250856225ab057adb90aa5bb8
Author: Andrea Cosentino <[email protected]>
AuthorDate: Tue Jul 28 09:25:56 2026 +0200
CAMEL-24223: camel-jbang-mcp - Locale.ROOT cost formatting and
model-specific disclaimer (follow-up) (#25167)
CAMEL-24223: Locale.ROOT cost formatting, model-specific disclaimer,
tighter tests
Addresses the remaining review points:
- formatCost now uses Locale.ROOT so the decimal separator is always '.'
(a comma separator under e.g. a French locale would break MCP clients that
parse the returned strings).
- The top-level disclaimer now states explicitly that token-based (LLM)
components are priced as a specific model, not a provider-wide figure, and
that Nova Lite / Haiku / local models (Ollama) cost far less, often ~zero
—
making clear these are specific-model estimates.
- shouldExtractSchemesCorrectly now uses containsExactlyInAnyOrder(file,
docling, aws-bedrock, aws2-s3), catching both a false "uri" capture and a
dropped "file" scheme; shouldEstimateCostForAiPipeline pins the breakdown
to
exactly 3 priced components and asserts the computed aws-bedrock
per-execution
cost rather than only the count.
Signed-off-by: Andrea Cosentino <[email protected]>
Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
---
.../jbang/core/commands/mcp/RouteCostEstimateTools.java | 13 +++++++++----
.../core/commands/mcp/RouteCostEstimateToolsTest.java | 15 ++++++++++++---
2 files changed, 21 insertions(+), 7 deletions(-)
diff --git
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/RouteCostEstimateTools.java
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/RouteCostEstimateTools.java
index a1aa84fb5eca..57e5fd519e3a 100644
---
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/RouteCostEstimateTools.java
+++
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/RouteCostEstimateTools.java
@@ -20,6 +20,7 @@ import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -134,8 +135,10 @@ public class RouteCostEstimateTools {
breakdown.isEmpty() ? null : breakdown,
projection, summary,
optimizationTips.isEmpty() ? null : optimizationTips,
- "Cost estimates are approximate based on published AWS pricing
(us-east-1). "
- +
"Actual costs vary by region, volume tier, and model.",
+ "Cost estimates are approximate based on published AWS pricing
(us-east-1). Token-based (LLM) "
+ +
"components are priced as a specific model — see each component's pricingNote —
and are NOT a "
+ +
"provider-wide figure: a route on Nova Lite, Haiku or a local/self-hosted model
(Ollama) will cost "
+ + "far
less, often ~zero. Actual costs vary by region, volume tier, and model.",
"2025-Q2");
}
@@ -179,10 +182,12 @@ public class RouteCostEstimateTools {
}
private static String formatCost(double cost) {
+ // Locale.ROOT so the decimal separator is always '.' regardless of
the server locale — MCP clients
+ // parse these strings and a comma separator (e.g. "$0,003000" under a
French locale) would break them.
if (cost < 0.01) {
- return String.format("$%.6f", cost);
+ return String.format(Locale.ROOT, "$%.6f", cost);
}
- return String.format("$%.4f", cost);
+ return String.format(Locale.ROOT, "$%.4f", cost);
}
private static Map<String, ComponentCostProfile> buildCostProfiles() {
diff --git
a/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/RouteCostEstimateToolsTest.java
b/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/RouteCostEstimateToolsTest.java
index f66289e24488..889bd6ee5cbb 100644
---
a/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/RouteCostEstimateToolsTest.java
+++
b/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/RouteCostEstimateToolsTest.java
@@ -76,10 +76,18 @@ class RouteCostEstimateToolsTest {
assertThat(result).isNotNull();
assertThat(result.costBreakdown()).isNotNull();
- assertThat(result.costBreakdown()).hasSizeGreaterThanOrEqualTo(2);
+ // docling, aws-bedrock and aws2-s3 have cost profiles; the file:
scheme does not, so exactly 3 are priced.
+ assertThat(result.costBreakdown()).hasSize(3);
assertThat(result.summary().mostExpensiveComponent()).isEqualTo("aws-bedrock");
assertThat(result.projection().messagesPerHour()).isEqualTo(100);
assertThat(result.disclaimer()).isNotBlank();
+
+ // aws-bedrock is estimated at Claude Sonnet 4 pricing: 1000 in *
$3/MTok + 500 out * $15/MTok.
+ double expectedBedrock = 1000 * 3.0 / 1_000_000 + 500 * 15.0 /
1_000_000;
+ assertThat(result.costBreakdown())
+ .filteredOn(c -> "aws-bedrock".equals(c.scheme()))
+ .singleElement()
+ .satisfies(c ->
assertThat(c.estimatedCostPerExecution()).isEqualTo(expectedBedrock));
}
@Test
@@ -132,8 +140,9 @@ class RouteCostEstimateToolsTest {
void shouldExtractSchemesCorrectly() {
List<String> schemes = tools.extractSchemes(AI_PIPELINE_ROUTE);
- assertThat(schemes).contains("docling", "aws-bedrock", "aws2-s3");
- assertThat(schemes).doesNotContain("uri");
+ // Exact set: the from: uri is file, then the three to: schemes. This
catches both a false "uri" capture
+ // (from the multi-line from:\n uri: form) and a dropped "file"
scheme.
+ assertThat(schemes).containsExactlyInAnyOrder("file", "docling",
"aws-bedrock", "aws2-s3");
}
@Test