Skip to content

CAMEL-24223: Add camel_route_cost_estimate MCP tool#25052

Open
oscerd wants to merge 4 commits into
apache:mainfrom
oscerd:worktree-camel-24223-cost-estimate
Open

CAMEL-24223: Add camel_route_cost_estimate MCP tool#25052
oscerd wants to merge 4 commits into
apache:mainfrom
oscerd:worktree-camel-24223-cost-estimate

Conversation

@oscerd

@oscerd oscerd commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds a new MCP tool camel_route_cost_estimate that estimates API costs for a Camel route based on its component usage patterns.

  • 9 component cost profiles: Bedrock (per-token), Textract (per-page), S3/SQS/SNS/Kinesis (per-request), OpenAI (per-token), LangChain4j Chat/Embeddings (per-token), Docling (free)
  • Per-execution cost breakdown: sorted by most expensive component first
  • Projections: hourly, daily, and monthly cost estimates at configurable throughput
  • Optimization tips: context-aware suggestions (e.g., "use Docling instead of Textract for text extraction")
  • Configurable parameters: messages/hour, avg pages, avg input/output tokens

Example output structure

{
  "costBreakdown": [...],
  "projection": {"messagesPerHour": 100, "hourly": "$1.05", "daily": "$25.20", "monthly": "$756.00"},
  "summary": {"mostExpensiveComponent": "aws-bedrock"},
  "optimizationTips": [...]
}

Test plan

  • 10 new tests covering AI pipelines, Textract routes, simple routes, cost projection, optimization tips, scheme extraction, defaults, combined processors
  • All 310 existing MCP server tests pass (no regressions)

Claude Code on behalf of oscerd

🤖 Generated with Claude Code

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>

@davsclaus davsclaus left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

  1. Tool name/description should reflect the scope: The current name camel_route_cost_estimate and description ("Estimate API costs for a Camel route") suggest general route costing. Consider renaming to something like camel_route_cloud_cost_estimate or updating the description to explicitly list the covered services (Bedrock, Textract, S3, SQS, SNS, Kinesis, OpenAI, LangChain4j, Docling) so users know what to expect.

  2. 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);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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;
}
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@github-actions

Copy link
Copy Markdown
Contributor

🌟 Thank you for your contribution to the Apache Camel project! 🌟
🤖 CI automation will test this PR automatically.

🐫 Apache Camel Committers, please review the following items:

  • First-time contributors require MANUAL approval for the GitHub Actions to run
  • You can use the command /component-test (camel-)component-name1 (camel-)component-name2.. to request a test from the test bot although they are normally detected and executed by CI.
  • You can label PRs using skip-tests and test-dependents to fine-tune the checks executed by this PR.
  • Build and test logs are available in the summary page. Only Apache Camel committers have access to the summary.

⚠️ Be careful when sharing logs. Review their contents before sharing them publicly.

@github-actions github-actions Bot added the dsl label Jul 23, 2026

@gnodet gnodet left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 ----

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 value
  • isNotEmpty() 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.

@github-actions

github-actions Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

🧪 CI tested the following changed modules:

  • dsl/camel-jbang/camel-jbang-mcp

🔬 Scalpel shadow comparison — Scalpel: 1 tested, 0 compile-only — current: 1 all tested

Maveniverse 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)
  • camel-jbang-mcp

ℹ️ Shadow mode — Scalpel observes but does not affect test execution. Learn more

⚠️ Some tests are disabled on GitHub Actions (@DisabledIfSystemProperty(named = "ci.env.name")) and require manual verification:

  • dsl/camel-jbang/camel-jbang-mcp: 1 test(s) disabled on GitHub Actions

⚙️ View full build and test results

- 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>
@oscerd

oscerd commented Jul 23, 2026

Copy link
Copy Markdown
Contributor Author

Thank you both for the reviews. All findings addressed:

Structural fixes:

  • ✅ All four result records now public record (matching convention across 16+ tool classes)
  • ✅ Added standard try/catch exception handling pattern
  • ✅ Added XML route support via XML_SCHEME_PATTERN regex — tool now correctly parses <from uri="scheme:..."/> syntax

Model-dependent pricing (davsclaus + gnodet):

  • ✅ Bedrock display name now reads "AWS Bedrock (model-dependent, estimated as Claude Sonnet 4)" with a pricing note showing the range: Nova Lite ~$0.06/$0.24 vs Opus ~$15/$75
  • ✅ LangChain4j Chat pricing note clarified: "free with Ollama/local models"
  • Agreed that hardcoded pricing is a limitation — the disclaimer already notes costs are approximate. Externalizing pricing data to a config file is a good follow-up.

Test additions:

  • ✅ New test for XML route scheme extraction

Claude Code on behalf of oscerd

@gnodet gnodet left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment on lines +44 to +47

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);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

Suggested change
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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
@oscerd

oscerd commented Jul 23, 2026

Copy link
Copy Markdown
Contributor Author

Re-review finding addressed:

✅ Fixed YAML regex false scheme capture — replaced \s+ with [ \t]+ in from:/to:/toD: alternatives so the pattern only matches inline syntax (same line) and doesn't cross newlines. Multi-line from:\n uri: "scheme:..." now correctly matches only via the uri: alternative.

Test now asserts doesNotContain("uri") on extracted schemes.

Claude Code on behalf of oscerd

@gnodet gnodet left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@oscerd
oscerd requested a review from davsclaus July 24, 2026 07:12

@davsclaus davsclaus left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 "

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_estimate to 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);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.");
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);
}
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
@oscerd

oscerd commented Jul 24, 2026

Copy link
Copy Markdown
Contributor Author

All davsclaus suggestions applied:

  1. @tool description now lists all covered services (Bedrock runtime + agent-runtime, Textract, S3, SQS, SNS, Kinesis, OpenAI, LangChain4j, Docling)
  2. ✅ Added aws-bedrock-agent-runtime cost profile for RAG/Flows (model invocation + retrieval units)
  3. ✅ Added pricingDataAsOf field to result ("2025-Q2")
  4. ✅ LangChain4j embeddings note updated: "free with Ollama/local models"

Claude Code on behalf of oscerd

@gnodet gnodet left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants