You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by ac...@apache.org on 2026/07/28 07:26:03 UTC

(camel) branch main updated: CAMEL-24223: camel-jbang-mcp - Locale.ROOT cost formatting and model-specific disclaimer (follow-up) (#25167)

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 <an...@gmail.com>
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 <an...@gmail.com>
    Co-authored-by: Claude Opus 4.8 (1M context) <no...@anthropic.com>
---
 .../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