CAMEL-24223: Add camel_route_cost_estimate MCP tool#25052
Conversation
New MCP tool that estimates API costs for a Camel route based on component usage patterns. Covers Bedrock (per-token), Textract (per-page), S3/SQS/SNS/Kinesis (per-request), OpenAI, LangChain4j, and Docling (free). Provides per-execution cost, hourly/daily/monthly projections, most-expensive component identification, and optimization tips. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: Andrea Cosentino <ancosen@gmail.com>
There was a problem hiding this comment.
Nice idea for an MCP tool — cost awareness is valuable for AI and cloud service routes.
Understanding the intent as a cloud/AI service cost estimation tool (not a general route cost calculator), the narrow component coverage is fine — the tool targets a specific set of pay-per-use services. A couple of suggestions to make that scope clearer and improve accuracy:
-
Tool name/description should reflect the scope: The current name
camel_route_cost_estimateand description ("Estimate API costs for a Camel route") suggest general route costing. Consider renaming to something likecamel_route_cloud_cost_estimateor updating the description to explicitly list the covered services (Bedrock, Textract, S3, SQS, SNS, Kinesis, OpenAI, LangChain4j, Docling) so users know what to expect. -
Model-specific pricing: The Bedrock and langchain4j profiles hardcode Claude Sonnet 4 pricing, but the actual model matters a lot (Nova Lite vs Claude is 10-50x difference; langchain4j with Ollama is free). See inline comments for details.
This review does not replace specialized AI review tools (CodeRabbit, Sourcery) or static analyzers (SonarCloud).
This review was generated by an AI agent and may contain inaccuracies. Please verify all suggestions before applying.
| @Override | ||
| double estimateCostPerExecution(int pages, int inputTokens, int outputTokens) { | ||
| return (inputTokens * 3.0 / 1_000_000) + (outputTokens * 15.0 / 1_000_000); | ||
| } |
There was a problem hiding this comment.
This hardcodes Claude Sonnet 4 pricing ($3/$15 per MTok), but users choose their Bedrock model — Nova Lite, Haiku, Titan, Mistral, etc. all have very different pricing. A route using Nova Lite would be dramatically overestimated.
Since the model isn't detectable from the route definition, at minimum the display name and pricing note should make clear this is a specific model estimate, not a Bedrock-wide one. Ideally the tool would accept a model parameter or default to a mid-range estimate rather than the most expensive model.
| double estimateCostPerExecution(int pages, int inputTokens, int outputTokens) { | ||
| return 0.014 / 1_000_000; | ||
| } | ||
| }); |
There was a problem hiding this comment.
This uses the same Claude Sonnet 4 pricing ($3/$15 per MTok) as the Bedrock profile, but langchain4j-chat is provider-agnostic — it works with OpenAI, Ollama (free), Anthropic, Mistral, local models, etc. The display name says "model-dependent" but the calculation is fixed to one specific (expensive) model.
A user running langchain4j with Ollama would see significant phantom costs. At minimum, the pricing note should warn that the estimate assumes a specific cloud-hosted model and may be zero for self-hosted providers.
| Map<String, ComponentCostProfile> profiles = new LinkedHashMap<>(); | ||
|
|
||
| profiles.put("aws-bedrock", new ComponentCostProfile( | ||
| "AWS Bedrock (Claude Sonnet 4)", "per-token", |
There was a problem hiding this comment.
All pricing data is hardcoded in Java source with no mechanism to update it without a code change, rebuild, and release. Cloud providers change pricing regularly (AWS has changed Bedrock pricing multiple times since launch). Users running an older Camel version will silently get increasingly inaccurate estimates.
Consider externalizing the cost profiles to a JSON resource file (e.g., cost-profiles.json on the classpath) that could be updated independently, or at least add a clear note in the tool output stating when the pricing data was last updated.
|
🌟 Thank you for your contribution to the Apache Camel project! 🌟 🐫 Apache Camel Committers, please review the following items:
|
gnodet
left a comment
There was a problem hiding this comment.
A few technical observations on this new MCP tool, complementing davsclaus's existing review. See inline comments for details.
The most significant confirmed issues are: (1) package-private records violating the universal public record convention across all existing tool classes, risking Jackson serialization failures at runtime; (2) missing the standard try/catch exception handling pattern; and (3) the scheme extraction regex failing on XML routes despite the tool description claiming XML support, plus a subtle YAML parsing bug.
Regarding the langchain4j-chat hardcoded pricing — davsclaus already flagged this. +1 on that feedback.
This review was generated by an AI agent and may contain inaccuracies. Please verify all suggestions before applying.
Claude Code on behalf of Guillaume Nodet
| abstract double estimateCostPerExecution(int pages, int inputTokens, int outputTokens); | ||
| } | ||
|
|
||
| // ---- Result records ---- |
There was a problem hiding this comment.
All four result records use package-private visibility. Every other tool class in this package (verified across 16+ files with 75+ records) uses public record. These records are serialized by Jackson via the MCP framework from a different package — package-private visibility could cause InaccessibleObjectException at runtime.
This is a recurring pattern from prior PRs (#25048, #25050, #25051). Please add public to all four records: CostEstimateResult, ComponentCostBreakdown, CostProjection, CostSummary.
| @ToolArg(description = "Average document pages per message for Textract/Docling (default: 5)") Integer avgPages, | ||
| @ToolArg(description = "Average LLM input tokens per request (default: 1000)") Integer avgInputTokens, | ||
| @ToolArg(description = "Average LLM output tokens per request (default: 500)") Integer avgOutputTokens) { | ||
|
|
There was a problem hiding this comment.
The estimateRouteCost method is missing the standard try/catch exception handling pattern used by ~84% of existing tools in this package:
try {
// main logic
} catch (ToolCallException e) {
throw e;
} catch (Throwable e) {
throw new ToolCallException("Failed to estimate route cost: " + e.getMessage(), null);
}Without this, any unexpected exception (regex failure, NPE) propagates as an unhandled error instead of a proper ToolCallException.
| private static final Pattern SCHEME_PATTERN = Pattern.compile( | ||
| "(?:uri:\\s*[\"']?|from:\\s+[\"']?|to:\\s+[\"']?|toD:\\s+[\"']?)([a-zA-Z][a-zA-Z0-9+.-]*):(?://)?", | ||
| Pattern.MULTILINE); | ||
|
|
There was a problem hiding this comment.
The SCHEME_PATTERN regex only matches YAML-style syntax but the @Tool description claims "Analyzes a YAML or XML route definition". XML routes use <from uri="scheme:..."/> syntax that won't match this pattern, silently returning zero schemes.
Additionally, for standard YAML routes in from:\n uri: "scheme:..." format, the regex captures uri as a false scheme name rather than the actual component scheme.
Consider adopting the approach from ExplainTools — iterating CatalogService.loadCatalog().findComponentNames() and checking content.contains(comp + ":") — which handles any format uniformly. Alternatively, remove the XML claim from the @Tool description if only YAML is supported.
|
|
||
| assertThat(result).isNotNull(); | ||
| assertThat(result.costBreakdown()).isNotNull(); | ||
| assertThat(result.costBreakdown()).hasSizeGreaterThanOrEqualTo(2); |
There was a problem hiding this comment.
The assertions could be more precise:
hasSizeGreaterThanOrEqualTo(2)— the expected count is exactly 3 (docling, aws-bedrock, aws2-s3 all have cost profiles)startsWith("$")(line 116) — only checks formatting, not the computed valueisNotEmpty()for optimization tips (line 124) — doesn't verify which tips are returned for the given route
Also, there's no test for XML route format despite the tool description claiming XML support. Adding one would immediately reveal the regex limitation noted above.
|
🧪 CI tested the following changed modules:
🔬 Scalpel shadow comparison — Scalpel: 1 tested, 0 compile-only — current: 1 all testedMaveniverse Scalpel detected 1 affected modules (current approach: 1). Skip-tests mode would test 1 modules (1 direct + 0 downstream), skip tests for 0 (generated code, meta-modules) Modules Scalpel would test (1)
|
- Make all result records public (convention across tool classes) - Add try/catch exception handling pattern - Add XML route support via separate XML_SCHEME_PATTERN regex - Clarify model-dependent pricing in display names and notes (Bedrock and langchain4j-chat profiles) - Add XML scheme extraction test Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: Andrea Cosentino <ancosen@gmail.com>
|
Thank you both for the reviews. All findings addressed: Structural fixes:
Model-dependent pricing (davsclaus + gnodet):
Test additions:
Claude Code on behalf of oscerd |
gnodet
left a comment
There was a problem hiding this comment.
Re-review: three of four original findings were addressed (public records, try/catch, XML support). However, the YAML regex bug persists — from:\s+ matches across newlines, causing uri to be falsely captured as a scheme in multi-line YAML routes.
This review was generated by an AI agent and may contain inaccuracies. Please verify all suggestions before applying.
Claude Code on behalf of Guillaume Nodet
|
|
||
| private static final Pattern YAML_SCHEME_PATTERN = Pattern.compile( | ||
| "(?:uri:\\s*[\"']?|from:\\s+[\"']?|to:\\s+[\"']?|toD:\\s+[\"']?)([a-zA-Z][a-zA-Z0-9+.-]*):(?://)?", | ||
| Pattern.MULTILINE); |
There was a problem hiding this comment.
YAML regex captures uri as a false scheme in multi-line routes. The \s+ in from:\s+ matches newlines, so from:\n uri: "aws-bedrock:model" captures uri instead of aws-bedrock. This silently omits the most expensive component from the cost estimate.
The uri:\s* alternative already handles the multi-line case correctly — the from:/to:/toD: alternatives only need to match inline same-line syntax, so [ \t]+ (horizontal whitespace) is sufficient:
| private static final Pattern YAML_SCHEME_PATTERN = Pattern.compile( | |
| "(?:uri:\\s*[\"']?|from:\\s+[\"']?|to:\\s+[\"']?|toD:\\s+[\"']?)([a-zA-Z][a-zA-Z0-9+.-]*):(?://)?", | |
| Pattern.MULTILINE); | |
| private static final Pattern YAML_SCHEME_PATTERN = Pattern.compile( | |
| "(?:uri:\\s*[\"']?|from:[ \\t]+[\"']?|to:[ \\t]+[\"']?|toD:[ \\t]+[\"']?)([a-zA-Z][a-zA-Z0-9+.-]*):(?://)?", | |
| Pattern.MULTILINE); |
| assertThat(result.optimizationTips()).isNotEmpty(); | ||
| } | ||
|
|
||
| @Test |
There was a problem hiding this comment.
Minor: contains("docling", "aws-bedrock", "aws2-s3") won't detect the false uri entry caused by the regex bug. Using containsExactlyInAnyOrder("file", "docling", "aws-bedrock", "aws2-s3") would catch both the false positive and the missing file scheme.
Replace \s+ with [ \t]+ in from:/to:/toD: alternatives so the pattern only matches inline syntax (same line) and doesn't cross newlines to falsely capture 'uri' as a scheme from multi-line YAML. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: Andrea Cosentino <ancosen@gmail.com>
|
Re-review finding addressed: ✅ Fixed YAML regex false scheme capture — replaced Test now asserts Claude Code on behalf of oscerd |
gnodet
left a comment
There was a problem hiding this comment.
All previously requested changes are now addressed across the three commits.
YAML regex fix verified: The three from:\s+, to:\s+, toD:\s+ alternatives now correctly use [ \t]+, which restricts matching to horizontal whitespace (space and tab), preventing the pattern from crossing newlines into a subsequent uri: key. The uri:\s* alternative correctly retains \s* since it is the terminal key that directly precedes the scheme value.
The new doesNotContain("uri") assertion in shouldExtractSchemesCorrectly directly validates the bug scenario: multi-line YAML from:\n uri: "scheme:..." no longer falsely captures uri as a scheme.
This review was generated by an AI agent and may contain inaccuracies. Please verify all suggestions before applying.
Claude Code on behalf of Guillaume Nodet
davsclaus
left a comment
There was a problem hiding this comment.
Nice work on this MCP tool — cost awareness for AI/cloud routes is genuinely useful. The technical issues from the prior round are all resolved; a few additional observations below.
Confirmed Issues (still outstanding from prior review)
1. Tool name implies general route costing, but scope is narrow (9 services)
The name camel_route_cost_estimate and description "Estimate API costs for a Camel route" suggest comprehensive coverage. In reality, only Bedrock, Textract, S3, SQS, SNS, Kinesis, OpenAI, LangChain4j, and Docling are covered. Renaming (e.g., camel_route_cloud_cost_estimate) or explicitly listing covered services in the @Tool description would set accurate expectations. (Aligns with davsclaus's finding #1)
2. Bedrock/LangChain4j estimates default to one of the most expensive models
The Bedrock profile uses Claude Sonnet 4 pricing ($3/$15 per MTok). A route using Nova Lite ($0.06/$0.24) would be overestimated by ~50x. The updated display name ("estimated as Claude Sonnet 4") helps, but users relying on the numeric output will get misleading projections. Same applies to langchain4j-chat which silently assumes the same expensive model. (Aligns with davsclaus's findings #2 and #3)
New Findings
3. Missing aws-bedrock-agent-runtime cost profile
Camel has three Bedrock schemes: aws-bedrock, aws-bedrock-agent, aws-bedrock-agent-runtime. The cost profiles only match aws-bedrock. Routes using aws-bedrock-agent-runtime (knowledge base retrieval, RAG, flow invocations) have per-session costs that won't be detected. At minimum, aws-bedrock-agent-runtime should have a cost profile entry.
4. No pricing data timestamp in output
The disclaimer says "approximate based on published AWS pricing" but doesn't say when the data was last verified. A simple field like pricingDataAsOf: "2025-Q2" in the result would help users gauge accuracy as the tool ages — a lightweight alternative to externalizing the full pricing model.
Questions
5. langchain4j-embeddings pricing consistency
This profile uses Titan Embed pricing ($0.02/MTok) but LangChain4j embeddings support any provider (including free local models). Should this follow the same "model-dependent" labeling as langchain4j-chat?
This review covers convention compliance and rules-based review. It does not replace specialized AI review tools (CodeRabbit, Sourcery) or static analyzers (SonarCloud).
This review was generated by an AI agent and may contain inaccuracies. Please verify all suggestions before applying.
Claude Code on behalf of davsclaus
|
|
||
| @Tool(annotations = @Tool.Annotations(readOnlyHint = true, destructiveHint = false, openWorldHint = false), | ||
| description = "Estimate API costs for a Camel route based on its component usage. " | ||
| + "Analyzes a YAML or XML route definition and identifies components with " |
There was a problem hiding this comment.
The @Tool description says "Estimate API costs for a Camel route" which implies general coverage, but only 9 specific services have cost profiles. Consider either:
- Renaming to
camel_route_cloud_cost_estimateto signal the scope, or - Adding to the description: "Currently covers: Bedrock, Textract, S3, SQS, SNS, Kinesis, OpenAI, LangChain4j, Docling."
| @Override | ||
| double estimateCostPerExecution(int pages, int inputTokens, int outputTokens) { | ||
| return (inputTokens * 3.0 / 1_000_000) + (outputTokens * 15.0 / 1_000_000); | ||
| } |
There was a problem hiding this comment.
Camel has three Bedrock schemes: aws-bedrock, aws-bedrock-agent, aws-bedrock-agent-runtime. Routes using aws-bedrock-agent-runtime for knowledge-base retrieval, RAG, or flow invocations have per-session/per-invocation costs that won't be detected by this tool. Consider adding a cost profile for aws-bedrock-agent-runtime.
| 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."); | ||
| } |
There was a problem hiding this comment.
The disclaimer mentions "approximate based on published AWS pricing" but doesn't indicate when the pricing data was last verified. Adding a pricingDataAsOf field (e.g., "2025-Q2") to the result would help users gauge accuracy over time without needing to externalize the pricing model.
| double estimateCostPerExecution(int pages, int inputTokens, int outputTokens) { | ||
| return (inputTokens * 3.0 / 1_000_000) + (outputTokens * 15.0 / 1_000_000); | ||
| } | ||
| }); |
There was a problem hiding this comment.
This uses Titan Embed pricing ($0.02/MTok), but LangChain4j embeddings support any provider — including free local models via Ollama. For consistency with the langchain4j-chat profile, the display name could follow the same "(model-dependent)" pattern and the pricing note should mention that self-hosted models are free.
- List covered services in @tool description - Add aws-bedrock-agent-runtime cost profile for RAG/Flows - Add pricingDataAsOf field to result (2025-Q2) - Update langchain4j-embeddings note: free with Ollama/local models Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: Andrea Cosentino <ancosen@gmail.com>
|
All davsclaus suggestions applied:
Claude Code on behalf of oscerd |
gnodet
left a comment
There was a problem hiding this comment.
Re-review (1 new commit since last approval)
Commit 3bd06cd correctly addresses all 4 of davsclaus's feedback items: (1) @tool description now lists all 11 covered services, (2) aws-bedrock-agent-runtime cost profile added with retrieval-unit pricing, (3) pricingDataAsOf field added to CostEstimateResult, (4) langchain4j-embeddings pricing note updated. The YAML regex fix ([ \t]+ instead of \s+) from the prior commit remains correctly in place. CI passes.
This review was generated by an AI agent and may contain inaccuracies. Please verify all suggestions before applying.
Claude Code on behalf of Guillaume Nodet
Summary
Adds a new MCP tool
camel_route_cost_estimatethat estimates API costs for a Camel route based on its component usage patterns.Example output structure
{ "costBreakdown": [...], "projection": {"messagesPerHour": 100, "hourly": "$1.05", "daily": "$25.20", "monthly": "$756.00"}, "summary": {"mostExpensiveComponent": "aws-bedrock"}, "optimizationTips": [...] }Test plan
Claude Code on behalf of oscerd
🤖 Generated with Claude Code