davsclaus commented on code in PR #25337:
URL: https://github.com/apache/camel/pull/25337#discussion_r3724092573


##########
catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/jbang/camel-jbang-configuration-metadata.json:
##########
@@ -35,6 +35,7 @@
     { "name": "camel.jbang.maven-settings", "required": false, "description": 
"Optional location of Maven settings.xml file to configure servers, 
repositories, mirrors, and proxies. If set to false, not even the default 
\/.m2\/settings.xml will be used.", "label": "maven", "type": "string", 
"javaType": "String", "secret": false },
     { "name": "camel.jbang.maven-settings-security", "required": false, 
"description": "Optional location of Maven settings-security.xml file to 
decrypt Maven Settings (settings.xml) file", "label": "maven", "type": 
"string", "javaType": "String", "secret": false },
     { "name": "camel.jbang.mavenWrapper", "required": false, "description": 
"Include Maven Wrapper files in the exported project", "type": "boolean", 
"javaType": "boolean", "defaultValue": true, "secret": false },
+    { "name": "camel.jbang.mcp", "required": false, "description": "Embed 
dev\/diagnostics MCP tools on the local HTTP management server (\/mcp by 
default)", "type": "boolean", "javaType": "boolean", "defaultValue": false, 
"secret": false, "security": "insecure:dev" },

Review Comment:
   **[Medium]** This `camel.jbang.mcp` entry does not exist on `main` and is 
unrelated to GenAI observability. It was likely picked up from an unclean 
working tree when regenerating catalog artifacts. Please revert this file to 
its `main` state.



##########
docs/components/modules/others/nav.adoc:
##########
@@ -4,6 +4,7 @@
 * xref:others:index.adoc[Miscellaneous Components]
 ** xref:a2a-consumer.adoc[A2A - Consumer Guide]
 ** xref:a2a-producer.adoc[A2A - Producer Guide]
+*** xref:ai-observability.adoc[AI Observability]

Review Comment:
   **[High]** Two issues here:
   
   1. This file is auto-generated (line 1–2: _"this file is auto generated and 
changes to it will be overwritten — make edits in docs/*nav.adoc.template files 
instead"_). If the entry is truly being generated by `PrepareDocSymlinksMojo`, 
then the issue is in the mojo or template, not here.
   
   2. The `***` (three-star) prefix nests "AI Observability" as a **sub-item of 
"A2A - Producer Guide"**, which is semantically wrong. It should be a peer 
entry at the `**` level, sorted alphabetically after the A2A entries. If the 
`:group: AI` doc attribute is causing this nesting, the mojo logic needs fixing.



##########
components/camel-ai/camel-ai-observability/src/main/java/org/apache/camel/component/ai/observability/GenAiObservability.java:
##########
@@ -0,0 +1,216 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.ai.observability;
+
+import java.lang.reflect.Constructor;
+import java.util.Optional;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.Exchange;
+import org.apache.camel.telemetry.Span;
+import org.apache.camel.telemetry.SpanLifecycleManager;
+import org.apache.camel.telemetry.SpanStorageManagerExchange;
+import org.apache.camel.telemetry.Tracer;
+import 
org.apache.camel.telemetry.propagation.CamelHeadersSpanContextPropagationExtractor;
+import org.apache.camel.util.ObjectHelper;
+
+/**
+ * Entry point for GenAI observability in Camel AI producers.
+ */
+public final class GenAiObservability {
+
+    private static final String CLIENT_SPAN_KIND = "CLIENT";
+    private static final String METER_REGISTRY_CLASS = 
"io.micrometer.core.instrument.MeterRegistry";
+    private static final String MICROMETER_SUPPORT_CLASS
+            = 
"org.apache.camel.component.ai.observability.GenAiMicrometerSupport";
+    private static final GenAiObservation NOOP = new NoopGenAiObservation();
+
+    private GenAiObservability() {
+    }
+
+    /**
+     * Whether GenAI observability is enabled for the given context.
+     */
+    public static boolean isEnabled(CamelContext camelContext) {
+        if (camelContext == null) {
+            return false;
+        }
+        Optional<String> property
+                = 
camelContext.getPropertiesComponent().resolveProperty(GenAiObservabilityProperties.ENABLED);
+        if (property.isPresent()) {
+            return Boolean.parseBoolean(property.get().trim());
+        }
+        return true;
+    }
+
+    /**
+     * Starts a GenAI observation for a single LLM client call. Returns a 
no-op when disabled or no backend is
+     * available.
+     */
+    public static GenAiObservation start(Exchange exchange, 
GenAiObservationContext context) {
+        if (exchange == null || context == null || 
!isEnabled(exchange.getContext())) {
+            return NOOP;
+        }
+        Tracer tracer = exchange.getContext().hasService(Tracer.class);
+        GenAiMetricsBackend metricsBackend = 
resolveMetricsBackend(exchange.getContext());
+        if (tracer == null && (metricsBackend == null || 
!metricsBackend.isAvailable())) {
+            return NOOP;
+        }
+        return new DefaultGenAiObservation(exchange, context, tracer, 
metricsBackend);
+    }
+
+    private static GenAiMetricsBackend resolveMetricsBackend(CamelContext 
camelContext) {
+        try {
+            Class.forName(METER_REGISTRY_CLASS);
+            Class<?> supportClass = Class.forName(MICROMETER_SUPPORT_CLASS);
+            Constructor<?> constructor = 
supportClass.getDeclaredConstructor(CamelContext.class);
+            constructor.setAccessible(true);

Review Comment:
   **[Low]** `setAccessible(true)` is unnecessary here. 
`GenAiMicrometerSupport` is package-private and this code is in the same 
package — `getDeclaredConstructor()` + `newInstance()` can access 
package-private constructors from within the same package without 
`setAccessible`. Removing it avoids potential module-system warnings.
   
   ```suggestion
               return (GenAiMetricsBackend) 
constructor.newInstance(camelContext);
   ```



##########
components/camel-ai/camel-langchain4j-chat/pom.xml:
##########
@@ -63,10 +63,21 @@
             <version>${jackson2-version}</version>
         </dependency>
 
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-ai-observability</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
         <!-- for testing -->
         <dependency>
             <groupId>org.apache.camel</groupId>
-            <artifactId>camel-test-spring-junit6</artifactId>
+            <artifactId>camel-test-junit6</artifactId>

Review Comment:
   **[Medium]** This test dependency change from `camel-test-spring-junit6` to 
`camel-test-junit6` is unrelated to GenAI observability. Please revert or 
submit separately.



##########
components/camel-ai/camel-langchain4j-chat/pom.xml:
##########
@@ -63,10 +63,21 @@
             <version>${jackson2-version}</version>
         </dependency>
 
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-ai-observability</artifactId>

Review Comment:
   **[High]** `camel-ai-observability` is added as a **mandatory** 
(non-optional) compile dependency here and on all other AI components. This 
means every user of `camel-langchain4j-chat` (and `-tools`, `-agent`, 
`-embeddings`, `camel-openai`) will transitively pull in 
`camel-ai-observability` + `camel-telemetry`, even if they have no interest in 
observability.
   
   In Camel's architecture, telemetry is typically injected at the framework 
level (via `camel-opentelemetry2` and route-level interceptors), not coupled 
into each component's producer. Having each producer call 
`GenAiObservability.start()` directly deviates from this pattern and creates a 
hard coupling.
   
   Consider either:
   - Making this dependency `<optional>true</optional>` and using 
classpath-based discovery (check if the class is present before calling it)
   - Or using an SPI/service-loader pattern so the observability hooks are 
discovered when `camel-ai-observability` is on the classpath, without requiring 
a compile dependency



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to