Skip to content

CAMEL-23861: GenAI observability Phase 3 - Spring AI chat and camel-main config - #25507

Open
atiaomar1978-hub wants to merge 4 commits into
apache:mainfrom
atiaomar1978-hub:feature/CAMEL-23861-genai-observability-phase3-c587
Open

CAMEL-23861: GenAI observability Phase 3 - Spring AI chat and camel-main config#25507
atiaomar1978-hub wants to merge 4 commits into
apache:mainfrom
atiaomar1978-hub:feature/CAMEL-23861-genai-observability-phase3-c587

Conversation

@atiaomar1978-hub

Copy link
Copy Markdown
Contributor

AI-generated on behalf of atiaomar1978-hub

Summary

Phase 3 of CAMEL-23861 extends GenAI observability beyond Phase 1 (langchain4j/openai) and Phase 2 (TUI usage view):

  • Spring AI chat (spring-ai-chat) producers now emit GenAI OpenTelemetry span attributes and record token usage via GenAiObservability, matching the langchain4j-chat pattern.
  • Camel Main typed configuration for the global toggle camel.ai.observability.enabled (CAMEL-24386): application.properties, programmatic main.configure().ai().observability().withEnabled(false), and metadata/docs.
  • GenAiModelResolver extended with reflection-based Spring AI provider/model resolution (no hard dependency on Spring AI in the API module).

Changes

Area Details
SpringAiChatProducer Wraps chat, structured output, and entity calls with GenAiObservability
GenAiModelResolver Spring AI package + options/response metadata resolution
camel-main AiConfigurationProperties / AiObservabilityConfigurationProperties, auto-config in BaseMainSupport
PrepareCamelMainMojo Metadata prefix fix: camel.ai.observability.enabled (not camel.main.enabled)
Docs ai-observability.adoc, upgrade guide 4.23 entry for Spring AI + Main config
Tests SpringAiChatObservabilityTest, AiObservabilityConfigurationPropertiesTest, GenAiModelResolverTest (Spring AI)

Testing

./mvnw -pl components/camel-ai/camel-ai-observability-api,components/camel-ai/camel-ai-observability,components/camel-spring-parent/camel-spring-ai/camel-spring-ai-chat,core/camel-main -am test \
  -Dtest=GenAiModelResolverTest,SpringAiChatObservabilityTest,AiObservabilityConfigurationPropertiesTest,GenAiObservabilityTest

Related

…ain config

Extend GenAI observability to spring-ai-chat producers with OpenTelemetry
span attributes and token usage recording. Add Spring AI model resolution to
GenAiModelResolver and first-class camel.ai.observability.enabled support in
Camel Main (typed configuration, auto-configuration, and metadata).

Includes SpringAiChatObservabilityTest, AiObservabilityConfigurationPropertiesTest,
upgrade guide notes, and PrepareCamelMainMojo metadata prefix fix.

Co-authored-by: Cursor Agent <noreply@cursor.com>
@atiaomar1978-hub
atiaomar1978-hub marked this pull request as ready for review August 14, 2026 17:30
- Do not record request model as response model on entity conversion path
- Resolve model metadata from ChatClient when ChatModel is not configured
- Harden GenAiModelResolver reflection when optional methods are absent

Co-authored-by: Cursor Agent <noreply@cursor.com>
@atiaomar1978-hub

Copy link
Copy Markdown
Contributor Author

AI-generated on behalf of atiaomar1978-hub

Bugbot follow-up (commit pushed)

Addressed both review findings:

  1. Entity path observabilityprocessEntityRequest no longer passes requestModel as responseModel; entity conversions record usage without fabricating a response model when ChatResponse is unavailable.

  2. ChatClient-only configurationbuildObservationContext now resolves metadata from chatClient when chatModel is unset, and GenAiModelResolver uses optional reflection (getChatModel, getDefaultChatOptions) without failing when methods are absent.

Please re-review when CI is green.

cursoragent and others added 2 commits August 14, 2026 17:53
- Rewrite GenAiModelResolver with reflection-only LangChain4j detection
- Cache ChatModel for observability when only ChatClient is configured
- Use responseEntity() for entity path to capture token usage in spans
- Add camel.ai.observability prefix to Main GROUP_PREFIXES
- Remove optional langchain4j-core dependency from camel-spring-ai-chat
- Expand Spring AI observability tests (ChatClient-only, entity, disabled)

Co-authored-by: Omar Atie <atiaomar1978-hub@users.noreply.github.com>
- Fix SpringAiChatObservabilityTest tracer wiring for isolated contexts
- Regenerate camel-main catalog with camel.ai.observability.enabled key

Co-authored-by: Omar Atie <atiaomar1978-hub@users.noreply.github.com>
@atiaomar1978-hub

Copy link
Copy Markdown
Contributor Author

AI-generated on behalf of atiaomar1978-hub

Addressed Grok review findings in commits 0b38fcef and 4ef2ca5f:

Critical

  • Regenerated catalog metadata — property is now camel.ai.observability.enabled (removed stale camel.main.enabled)
  • GenAiModelResolver uses reflection-only LangChain4j detection — removed optional langchain4j-core from camel-spring-ai-chat
  • ChatClient-only routes cache/extract underlying ChatModel at startup for observability metadata

Medium

  • Entity path uses responseEntity() to capture token usage and response model in spans
  • Added PREFIX_AI_OBSERVABILITY to Main GROUP_PREFIXES
  • Direct ChatResponse.getMetadata().getModel() in span success recording
  • Expanded tests: ChatClient-only, entity conversion, disabled observability, token span tags

All observability tests pass locally.

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

Thank you for this contribution — the observability wiring and reflection-based resolver are well-designed. I have a few findings that need to be addressed.

Confirmed issues

  1. Wrong configuration property prefix — The property uses camel.ai.observability.enabled (two-level nesting) but every existing Camel Main configuration group uses a single-level prefix under camel.: camel.health., camel.opentelemetry2., camel.errorRegistry., camel.routeController., etc. The correct name should be camel.ai-observability.enabled (hyphenated, single level). This affects PREFIX_AI_OBSERVABILITY, GROUP_PREFIXES, PrepareCamelMainMojo, the configuration properties classes, the metadata JSON, and the documentation.

  2. processStructuredOutputRequest drops token usage headers — The PR replaces request.call().chatResponse() with callWithObservability(request, exchange) and removes the populateTokenUsage(response, exchange) call. While callWithObservability records tokens on the span, it does NOT populate exchange headers (INPUT_TOKEN_COUNT, OUTPUT_TOKEN_COUNT, TOTAL_TOKEN_COUNT). Compare with the entity path which calls both recordObservationSuccess AND populateTokenUsage. The default chat path works because populateResponse() includes populateTokenUsage(). This is a user-visible regression.

  3. FQCN violationorg.springframework.ai.chat.client.ResponseEntity<ChatResponse, T> is used inline in processEntityRequest instead of adding an import. Per project conventions: "Do NOT use fully qualified class names."

Suggestions (non-blocking)

  1. Missing warning log for unconfigured AI observability properties — Every other configuration group in BaseMainSupport has a warning block for unconsumed properties. The PR doesn't add one.

  2. Fragile private field access in extractChatModelFromClient — Uses getDeclaredField("chatModel") + setAccessible(true) on Spring AI internals. The graceful fallback is good, but worth noting as a maintenance risk.


Note: This review covers project rules, conventions, and code patterns. It does not replace specialized review tools such as CodeRabbit, Sourcery, or SonarCloud.

This review was generated by an AI agent (Claude Code on behalf of davsclaus) and may contain inaccuracies. Please verify all suggestions before applying.

private static final String PREFIX_TRACE = "camel.trace.";
private static final String PREFIX_ROUTE_CONTROLLER = "camel.routeController.";
private static final String PREFIX_ERROR_REGISTRY = "camel.errorRegistry.";
private static final String PREFIX_AI_OBSERVABILITY = "camel.ai.observability.";

@davsclaus davsclaus Aug 15, 2026

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 property prefix camel.ai.observability. uses two-level nesting, but every existing Camel Main configuration group uses a single-level camelCase prefix under camel.:

  • camel.health., camel.opentelemetry2., camel.errorRegistry., camel.routeController., camel.management., etc.

The canonical name should be camel.aiObservability.enabled (camelCase, matching e.g. camel.routeController.), with camel.ai-observability.enabled (dash-style) also accepted. The rename needs to ripple through: this constant, GROUP_PREFIXES, PrepareCamelMainMojo, AiObservabilityConfigurationProperties, metadata JSON, and docs.

@@ -965,8 +989,6 @@ private <T> void processStructuredOutputRequest(
// Also set headers
exchange.getMessage().setHeader(SpringAiChatConstants.CHAT_RESPONSE, responseText);
exchange.getMessage().setHeader(SpringAiChatConstants.STRUCTURED_OUTPUT, structuredOutput);

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 old code called populateTokenUsage(response, exchange) after this line, but that call was removed. callWithObservability() records token data on the span but does NOT populate the exchange headers (INPUT_TOKEN_COUNT, OUTPUT_TOKEN_COUNT, TOTAL_TOKEN_COUNT).

Compare with:

  • The entity path (processEntityRequest) which correctly calls both recordObservationSuccess AND populateTokenUsage
  • The default chat path which calls populateResponse() (which includes populateTokenUsage)

Please restore populateTokenUsage(response, exchange) here.

GenAiObservationContext observationContext = buildObservationContext();
GenAiObservation observation = GenAiObservability.start(exchange, observationContext);
try {
org.springframework.ai.chat.client.ResponseEntity<ChatResponse, T> responseEntity

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.

Per project conventions: "Do NOT use fully qualified class names (FQCNs) in Java code. Always add an import statement and use the simple class name."

Since org.springframework.http.ResponseEntity is not imported in this file, a regular import for org.springframework.ai.chat.client.ResponseEntity would work without conflicts.

Suggested change
org.springframework.ai.chat.client.ResponseEntity<ChatResponse, T> responseEntity
ResponseEntity<ChatResponse, T> responseEntity

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants