You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by "atiaomar1978-hub (via GitHub)" <gi...@apache.org> on 2026/09/04 03:38:54 UTC

[PR] CAMEL-24559: Extend GenAI observability to OpenAI embeddings, moderation, and responses [camel]

atiaomar1978-hub opened a new pull request, #26106:
URL: https://github.com/apache/camel/pull/26106

   ## Summary
   
   Follow-up to CAMEL-23861 / [CAMEL-24559](https://issues.apache.org/jira/browse/CAMEL-24559): extend GenAI observability to additional OpenAI producer operations beyond `chat-completion`.
   
   This PR instruments **OpenAI embeddings, moderation, and responses** with the existing `GenAiObservability` API.
   
   | Operation | `gen_ai.operation.name` |
   |-----------|-------------------------|
   | `openai:embeddings` | `embeddings` |
   | `openai:moderation` | `moderation` (Camel extension) |
   | `openai:responses` | `chat` |
   | `openai:chat-completion` | `chat` (unchanged) |
   
   ## Changes
   
   - Instrument `OpenAIEmbeddingsProducer`, `OpenAIModerationProducer`, `OpenAIResponsesProducer`
   - Add `GenAiOperationName.MODERATION`
   - Tests: `OpenAIEmbeddingsObservabilityTest`, `OpenAIModerationObservabilityTest`, `OpenAIResponsesObservabilityTest`
   - Docs: `ai-observability.adoc`, catalog mirror, 4.23 upgrade guide
   
   ## Test plan
   
   ```bash
   ./mvnw -pl components/camel-ai/camel-openai -am test -Dtest=OpenAIEmbeddingsObservabilityTest,OpenAIModerationObservabilityTest,OpenAIResponsesObservabilityTest
   ```
   
   Images, audio, Spring AI modules, and cloud LLMs remain for future CAMEL-24559 follow-ups.
   
   ---
   _AI-generated PR description on behalf of @atiaomar1978-hub_


-- 
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: commits-unsubscribe@camel.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] CAMEL-24559: Extend GenAI observability to OpenAI embeddings, moderation, and responses [camel]

Posted by "atiaomar1978-hub (via GitHub)" <gi...@apache.org>.
atiaomar1978-hub commented on PR #26106:
URL: https://github.com/apache/camel/pull/26106#issuecomment-5535317867

   Links CAMEL-24559 — first follow-up slice. Bugbot + Grok review feedback addressed in 38b10e2fc6c.


-- 
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: commits-unsubscribe@camel.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] CAMEL-24559: Extend GenAI observability to OpenAI embeddings, moderation, and responses [camel]

Posted by "github-actions[bot] (via GitHub)" <gi...@apache.org>.
github-actions[bot] commented on PR #26106:
URL: https://github.com/apache/camel/pull/26106#issuecomment-5536481591

   <!-- ci-tested-modules -->
   :test_tube: **CI tested the following changed modules:**
   
   - `catalog/camel-catalog`
   - `components/camel-ai/camel-ai-observability-api`
   - `components/camel-ai/camel-ai-observability`
   - `components/camel-ai/camel-openai`
   - `docs`
   
   > :information_source: Dependent modules were not tested because the total number of affected modules exceeded the threshold (50). Use the `test-dependents` label to force testing all dependents.
   
   ---
   
   <details><summary>:microscope: Scalpel shadow comparison — Scalpel: 73 tested, 25 compile-only — current: 71 all tested</summary>
   
   [Maveniverse Scalpel](https://github.com/maveniverse/scalpel) detected **98 affected modules** (current approach: 71).
   
   <details><summary>:warning: Modules only in Scalpel (27)</summary>
   
   - `apache-camel`
   - `camel-allcomponents`
   - `camel-catalog`
   - `camel-catalog-console`
   - `camel-catalog-maven`
   - `camel-catalog-suggest`
   - `camel-componentdsl`
   - `camel-endpointdsl`
   - `camel-endpointdsl-support`
   - `camel-itest`
   - `camel-jbang-core`
   - `camel-jbang-it`
   - `camel-jbang-main`
   - `camel-jbang-plugin-edit`
   - `camel-jbang-plugin-generate`
   - `camel-jbang-plugin-kubernetes`
   - `camel-jbang-plugin-test`
   - `camel-kamelet-main`
   - `camel-launcher`
   - `camel-report-maven-plugin`
   - `camel-route-parser`
   - `camel-yaml-dsl`
   - `camel-yaml-dsl-deserializers`
   - `camel-yaml-dsl-maven-plugin`
   - `coverage`
   - `docs`
   - `dummy-component`
   
   </details>
   
   **Skip-tests mode would test 73 modules** (5 direct + 68 downstream), **skip tests for 25** (generated code, meta-modules)
   
   <details><summary>Modules Scalpel would test (73)</summary>
   
   - `camel-a2a`
   - `camel-ai-observability`
   - `camel-ai-observability-api`
   - `camel-as2`
   - `camel-catalog`
   - `camel-clickup`
   - `camel-consul`
   - `camel-core-all`
   - `camel-csv`
   - `camel-fhir`
   - `camel-groovy`
   - `camel-hazelcast`
   - `camel-hl7`
   - `camel-ibm-watsonx-ai`
   - `camel-iso8583`
   - `camel-java-joor-dsl`
   - `camel-jbang-mcp`
   - `camel-jbang-plugin-mcp`
   - `camel-jbang-plugin-route-parser`
   - `camel-jbang-plugin-tui`
   - `camel-jbang-plugin-validate`
   - `camel-jetty`
   - `camel-jsonpath`
   - `camel-kamelet`
   - `camel-kamelet-main-support`
   - `camel-knative-http`
   - `camel-langchain4j-agent`
   - `camel-langchain4j-chat`
   - `camel-langchain4j-embeddings`
   - `camel-langchain4j-embeddingstore`
   - `camel-launcher-container`
   - `camel-mail`
   - `camel-mail-microsoft-oauth`
   - `camel-main`
   - `camel-maven-plugin`
   - `camel-mcp-server`
   - `camel-micrometer-prometheus`
   - `camel-microprofile-health`
   - `camel-netty`
   - `camel-netty-http`
   - `camel-oauth`
   - `camel-observability-services`
   - `camel-once`
   - `camel-openai`
   - `camel-openapi-validator`
   - `camel-platform-http-main`
   - `camel-platform-http-vertx`
   - `camel-plc4x`
   - `camel-pubnub`
   - `camel-quickfix`
   - `camel-rest-openapi`
   - `camel-restdsl-openapi-plugin`
   - `camel-salesforce`
   - `camel-salesforce-codegen`
   - `camel-salesforce-maven-plugin`
   - `camel-spring-ai-chat`
   - `camel-spring-main`
   - `camel-state-store`
   - `camel-syslog`
   - `camel-telegram`
   - `camel-test-main-junit5`
   - `camel-test-main-junit6`
   - `camel-vertx-websocket`
   - `camel-webhook`
   - `camel-whatsapp`
   - `camel-xml-io-dsl`
   - `camel-xml-jaxb-dsl`
   - `camel-xml-jaxb-dsl-test-definition`
   - `camel-xml-jaxb-dsl-test-spring`
   - `camel-yaml-dsl-validator`
   - `camel-yaml-dsl-validator-maven-plugin`
   - `camel-zookeeper`
   - `docs`
   
   </details>
   
   <details><summary>Modules with tests skipped (25)</summary>
   
   - `apache-camel`
   - `camel-allcomponents`
   - `camel-catalog-console`
   - `camel-catalog-maven`
   - `camel-catalog-suggest`
   - `camel-componentdsl`
   - `camel-endpointdsl`
   - `camel-endpointdsl-support`
   - `camel-itest`
   - `camel-jbang-core`
   - `camel-jbang-it`
   - `camel-jbang-main`
   - `camel-jbang-plugin-edit`
   - `camel-jbang-plugin-generate`
   - `camel-jbang-plugin-kubernetes`
   - `camel-jbang-plugin-test`
   - `camel-kamelet-main`
   - `camel-launcher`
   - `camel-report-maven-plugin`
   - `camel-route-parser`
   - `camel-yaml-dsl`
   - `camel-yaml-dsl-deserializers`
   - `camel-yaml-dsl-maven-plugin`
   - `coverage`
   - `dummy-component`
   
   </details>
   
   > :information_source: Shadow mode — Scalpel observes but does not affect test execution. [Learn more](https://github.com/maveniverse/scalpel)
   
   </details>
   
   :warning: **Some tests are disabled on GitHub Actions** (`@DisabledIfSystemProperty(named = "ci.env.name")`) and require manual verification:
   
   - `components/camel-ai/camel-openai`: 7 test(s) disabled on GitHub Actions
   
   <details><summary>Build reactor — dependencies compiled but only changed modules were tested (5 modules)</summary>
   
   - Camel :: AI :: Observability
   - Camel :: AI :: Observability API
   - Camel :: AI :: OpenAI
   - Camel :: Catalog :: Camel Catalog
   - Camel :: Docs
   
   </details>
   
   ---
   :gear: [View full build and test results](https://github.com/apache/camel/actions/runs/33833964104)


-- 
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: commits-unsubscribe@camel.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] CAMEL-24559: Extend GenAI observability to OpenAI embeddings, moderation, and responses [camel]

Posted by "davsclaus (via GitHub)" <gi...@apache.org>.
davsclaus merged PR #26106:
URL: https://github.com/apache/camel/pull/26106


-- 
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: commits-unsubscribe@camel.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] CAMEL-24559: Extend GenAI observability to OpenAI embeddings, moderation, and responses [camel]

Posted by "github-actions[bot] (via GitHub)" <gi...@apache.org>.
github-actions[bot] commented on PR #26106:
URL: https://github.com/apache/camel/pull/26106#issuecomment-5536339340

   :star2: Thank you for your contribution to the Apache Camel project! :star2:
     :robot: CI automation will test this PR automatically.
   
     :camel: 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](https://camel.apache.org/community/team/#committers) have access to the summary.
     
     :warning: Be careful when sharing logs. Review their contents before sharing them publicly.


-- 
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: commits-unsubscribe@camel.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] CAMEL-24559: Extend GenAI observability to OpenAI embeddings, moderation, and responses [camel]

Posted by "atiaomar1978-hub (via GitHub)" <gi...@apache.org>.
atiaomar1978-hub commented on PR #26106:
URL: https://github.com/apache/camel/pull/26106#issuecomment-5535314184

   Links [CAMEL-24559](https://issues.apache.org/jira/browse/CAMEL-24559) — first follow-up slice for extending GenAI observability beyond chat/streaming.
   
   **Review status:** Bugbot (no bugs) and Grok review feedback addressed in commit `38b10e2fc6c` (SDK-only span scope, `chat` for responses, docs, stronger tests).
   
   ---
   _AI-generated comment on behalf of @atiaomar1978-hub_


-- 
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: commits-unsubscribe@camel.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] CAMEL-24559: Extend GenAI observability to OpenAI embeddings, moderation, and responses [camel]

Posted by "gnodet (via GitHub)" <gi...@apache.org>.
gnodet commented on code in PR #26106:
URL: https://github.com/apache/camel/pull/26106#discussion_r3931470723


##########
components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIEmbeddingsProducer.java:
##########
@@ -127,6 +151,10 @@ private void processInternal(Exchange exchange) throws Exception {
         calculateSimilarityIfRequested(exchange, embeddings);
     }
 
+    private static Integer toTokenCount(long tokens) {

Review Comment:
   `Math.toIntExact(tokens)` will throw `ArithmeticException` if the token count exceeds `Integer.MAX_VALUE`. Since this is called inside the `try` block that catches `Exception`, a hypothetical overflow would abort the entire exchange even though the API call itself succeeded.
   
   More importantly, this narrowing is unnecessary. `GenAiUsage` accepts `Long` directly — the existing `createChatCompletion` in `OpenAIProducer` passes `usage.promptTokens()` (a `long`) straight to `GenAiUsage.of(Long, Long, ...)` without any conversion:
   
   ```suggestion
       private static Long toTokenCount(long tokens) {
           return tokens;
       }
   ```
   
   Or just inline the `long` values directly and drop `toTokenCount` entirely, matching the `OpenAIProducer` pattern.



##########
components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIResponsesProducer.java:
##########
@@ -136,28 +142,89 @@ private void processInternal(Exchange exchange) throws Exception {
 
         Class<?> responseClass = resolveOutputClass(in, outputClass);
         if (responseClass != null) {
-            processStructured(exchange, config, paramsBuilder, responseClass);
+            processStructured(exchange, config, paramsBuilder, responseClass, model);
             return;
         }
         if (ObjectHelper.isNotEmpty(jsonSchema)) {
             OpenAIResponsesSupport.applyJsonSchemaTextFormat(paramsBuilder, jsonSchema);
         }
 
         ResponseCreateParams params = paramsBuilder.build();
-        Response response = getEndpoint().getClient().responses().create(params);
+        Response response = createResponse(exchange, model, params);
         finishExchange(exchange, config, response, OpenAIResponsesSupport.extractAssistantText(response));
     }
 
     private void processStructured(
             Exchange exchange, OpenAIConfiguration config, ResponseCreateParams.Builder paramsBuilder,
-            Class<?> responseClass)
+            Class<?> responseClass, String model)
             throws Exception {
         StructuredResponseCreateParams<?> structuredParams = paramsBuilder.text(responseClass).build();
-        StructuredResponse<?> structured = getEndpoint().getClient().responses().create(structuredParams);
-        Response raw = structured.rawResponse();
+        Response raw = createStructuredResponse(exchange, model, structuredParams);
         finishExchange(exchange, config, raw, OpenAIResponsesSupport.extractAssistantText(raw));
     }
 
+    private Response createResponse(Exchange exchange, String model, ResponseCreateParams params) throws Exception {
+        GenAiObservationContext observationContext = GenAiObservationContext.builder()
+                .operationName(GenAiOperationName.CHAT)
+                .system("openai")
+                .requestModel(model)
+                .componentScheme("openai")
+                .build();
+        GenAiObservation observation = GenAiObservability.start(exchange, observationContext);
+        try {
+            Response response = getEndpoint().getClient().responses().create(params);
+            recordResponseSuccess(observation, response);
+            return response;
+        } catch (Exception e) {
+            GenAiErrorSupport.apply(exchange, e);
+            observation.recordError(e);
+            throw e;
+        } finally {
+            observation.close();
+        }
+    }
+
+    private Response createStructuredResponse(
+            Exchange exchange, String model, StructuredResponseCreateParams<?> structuredParams)
+            throws Exception {
+        GenAiObservationContext observationContext = GenAiObservationContext.builder()
+                .operationName(GenAiOperationName.CHAT)
+                .system("openai")
+                .requestModel(model)
+                .componentScheme("openai")
+                .build();
+        GenAiObservation observation = GenAiObservability.start(exchange, observationContext);
+        try {
+            StructuredResponse<?> structured = getEndpoint().getClient().responses().create(structuredParams);
+            Response raw = structured.rawResponse();
+            recordResponseSuccess(observation, raw);
+            return raw;
+        } catch (Exception e) {
+            GenAiErrorSupport.apply(exchange, e);
+            observation.recordError(e);
+            throw e;
+        } finally {
+            observation.close();
+        }
+    }
+
+    private static void recordResponseSuccess(GenAiObservation observation, Response response) {
+        String finishReason = OpenAIResponsesSupport.extractFinishStatus(response)
+                .map(OpenAIResponsesProducer::mapFinishReason)
+                .orElse(null);
+        response.usage().ifPresentOrElse(
+                usage -> observation.recordSuccess(GenAiUsage.of(
+                        toTokenCount(usage.inputTokens()),
+                        toTokenCount(usage.outputTokens()),
+                        finishReason,
+                        response.model().toString())),
+                () -> observation.recordSuccess(GenAiUsage.of(null, null, finishReason, response.model().toString())));
+    }

Review Comment:
   Same issue as in `OpenAIEmbeddingsProducer` — `Math.toIntExact` is both unnecessary (the `Long` overload of `GenAiUsage.of` exists) and risky (throws `ArithmeticException` on overflow, which the catch block would treat as a failed operation).
   
   ```suggestion
       private static Long toTokenCount(long tokens) {
           return tokens;
       }
   ```



##########
components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIModerationProducer.java:
##########
@@ -87,19 +93,32 @@ private void processInternal(Exchange exchange) throws Exception {
             paramsBuilder.inputOfStrings(inputs);
         }
 
-        ModerationCreateResponse response = getEndpoint().getClient()
-                .moderations().create(paramsBuilder.build());
+        GenAiObservationContext observationContext = GenAiObservationContext.builder()
+                .operationName(GenAiOperationName.MODERATION)
+                .system("openai")
+                .requestModel(model)
+                .componentScheme("openai")
+                .build();
+        GenAiObservation observation = GenAiObservability.start(exchange, observationContext);
+        ModerationCreateResponse response;
+        try {
+            response = getEndpoint().getClient().moderations().create(paramsBuilder.build());
+            observation.recordSuccess(GenAiUsage.of(null, null, null, response.model()));
+        } catch (Exception e) {
+            GenAiErrorSupport.apply(exchange, e);
+            observation.recordError(e);
+            throw e;
+        } finally {
+            observation.close();

Review Comment:
   The comment that was here ("this operation is used to gate untrusted content, so a missing verdict must fail the exchange...") explained a security invariant — *why* a mismatched result count throws rather than silently proceeding. Worth keeping; it's not redundant with the observability changes.



##########
components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIResponsesProducer.java:
##########
@@ -136,28 +142,89 @@ private void processInternal(Exchange exchange) throws Exception {
 
         Class<?> responseClass = resolveOutputClass(in, outputClass);
         if (responseClass != null) {
-            processStructured(exchange, config, paramsBuilder, responseClass);
+            processStructured(exchange, config, paramsBuilder, responseClass, model);
             return;
         }
         if (ObjectHelper.isNotEmpty(jsonSchema)) {
             OpenAIResponsesSupport.applyJsonSchemaTextFormat(paramsBuilder, jsonSchema);
         }
 
         ResponseCreateParams params = paramsBuilder.build();
-        Response response = getEndpoint().getClient().responses().create(params);
+        Response response = createResponse(exchange, model, params);
         finishExchange(exchange, config, response, OpenAIResponsesSupport.extractAssistantText(response));
     }
 
     private void processStructured(
             Exchange exchange, OpenAIConfiguration config, ResponseCreateParams.Builder paramsBuilder,
-            Class<?> responseClass)
+            Class<?> responseClass, String model)
             throws Exception {
         StructuredResponseCreateParams<?> structuredParams = paramsBuilder.text(responseClass).build();
-        StructuredResponse<?> structured = getEndpoint().getClient().responses().create(structuredParams);
-        Response raw = structured.rawResponse();
+        Response raw = createStructuredResponse(exchange, model, structuredParams);
         finishExchange(exchange, config, raw, OpenAIResponsesSupport.extractAssistantText(raw));
     }
 
+    private Response createResponse(Exchange exchange, String model, ResponseCreateParams params) throws Exception {
+        GenAiObservationContext observationContext = GenAiObservationContext.builder()
+                .operationName(GenAiOperationName.CHAT)
+                .system("openai")
+                .requestModel(model)
+                .componentScheme("openai")
+                .build();

Review Comment:
   `createResponse` and `createStructuredResponse` are nearly identical — same observation context, same error handling, same `finally`. The only difference is the SDK call. Consider extracting a common helper, e.g.:
   
   ```java
   private Response observedCall(Exchange exchange, String model,
           ThrowingSupplier<Response> call) throws Exception {
       GenAiObservationContext ctx = GenAiObservationContext.builder()
               .operationName(GenAiOperationName.CHAT)
               .system("openai").requestModel(model)
               .componentScheme("openai").build();
       GenAiObservation observation = GenAiObservability.start(exchange, ctx);
       try {
           Response response = call.get();
           recordResponseSuccess(observation, response);
           return response;
       } catch (Exception e) {
           GenAiErrorSupport.apply(exchange, e);
           observation.recordError(e);
           throw e;
       } finally {
           observation.close();
       }
   }
   ```
   
   Then `createResponse` becomes `observedCall(exchange, model, () -> getEndpoint().getClient().responses().create(params))` and `createStructuredResponse` becomes `observedCall(exchange, model, () -> getEndpoint().getClient().responses().create(structuredParams).rawResponse())`.
   
   Not blocking, but reduces ~40 lines of duplication to ~2.



-- 
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: commits-unsubscribe@camel.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] CAMEL-24559: Extend GenAI observability to OpenAI embeddings, moderation, and responses [camel]

Posted by "atiaomar1978-hub (via GitHub)" <gi...@apache.org>.
atiaomar1978-hub commented on code in PR #26106:
URL: https://github.com/apache/camel/pull/26106#discussion_r3938272480


##########
components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIEmbeddingsProducer.java:
##########
@@ -127,6 +151,10 @@ private void processInternal(Exchange exchange) throws Exception {
         calculateSimilarityIfRequested(exchange, embeddings);
     }
 
+    private static Integer toTokenCount(long tokens) {

Review Comment:
   Fixed in d1a3fc28 — removed `toTokenCount` and pass `usage.promptTokens()` directly to `GenAiUsage.of(Long, ...)`, matching `OpenAIProducer.createChatCompletion`.



##########
components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIResponsesProducer.java:
##########
@@ -136,28 +142,89 @@ private void processInternal(Exchange exchange) throws Exception {
 
         Class<?> responseClass = resolveOutputClass(in, outputClass);
         if (responseClass != null) {
-            processStructured(exchange, config, paramsBuilder, responseClass);
+            processStructured(exchange, config, paramsBuilder, responseClass, model);
             return;
         }
         if (ObjectHelper.isNotEmpty(jsonSchema)) {
             OpenAIResponsesSupport.applyJsonSchemaTextFormat(paramsBuilder, jsonSchema);
         }
 
         ResponseCreateParams params = paramsBuilder.build();
-        Response response = getEndpoint().getClient().responses().create(params);
+        Response response = createResponse(exchange, model, params);
         finishExchange(exchange, config, response, OpenAIResponsesSupport.extractAssistantText(response));
     }
 
     private void processStructured(
             Exchange exchange, OpenAIConfiguration config, ResponseCreateParams.Builder paramsBuilder,
-            Class<?> responseClass)
+            Class<?> responseClass, String model)
             throws Exception {
         StructuredResponseCreateParams<?> structuredParams = paramsBuilder.text(responseClass).build();
-        StructuredResponse<?> structured = getEndpoint().getClient().responses().create(structuredParams);
-        Response raw = structured.rawResponse();
+        Response raw = createStructuredResponse(exchange, model, structuredParams);
         finishExchange(exchange, config, raw, OpenAIResponsesSupport.extractAssistantText(raw));
     }
 
+    private Response createResponse(Exchange exchange, String model, ResponseCreateParams params) throws Exception {
+        GenAiObservationContext observationContext = GenAiObservationContext.builder()
+                .operationName(GenAiOperationName.CHAT)
+                .system("openai")
+                .requestModel(model)
+                .componentScheme("openai")
+                .build();
+        GenAiObservation observation = GenAiObservability.start(exchange, observationContext);
+        try {
+            Response response = getEndpoint().getClient().responses().create(params);
+            recordResponseSuccess(observation, response);
+            return response;
+        } catch (Exception e) {
+            GenAiErrorSupport.apply(exchange, e);
+            observation.recordError(e);
+            throw e;
+        } finally {
+            observation.close();
+        }
+    }
+
+    private Response createStructuredResponse(
+            Exchange exchange, String model, StructuredResponseCreateParams<?> structuredParams)
+            throws Exception {
+        GenAiObservationContext observationContext = GenAiObservationContext.builder()
+                .operationName(GenAiOperationName.CHAT)
+                .system("openai")
+                .requestModel(model)
+                .componentScheme("openai")
+                .build();
+        GenAiObservation observation = GenAiObservability.start(exchange, observationContext);
+        try {
+            StructuredResponse<?> structured = getEndpoint().getClient().responses().create(structuredParams);
+            Response raw = structured.rawResponse();
+            recordResponseSuccess(observation, raw);
+            return raw;
+        } catch (Exception e) {
+            GenAiErrorSupport.apply(exchange, e);
+            observation.recordError(e);
+            throw e;
+        } finally {
+            observation.close();
+        }
+    }
+
+    private static void recordResponseSuccess(GenAiObservation observation, Response response) {
+        String finishReason = OpenAIResponsesSupport.extractFinishStatus(response)
+                .map(OpenAIResponsesProducer::mapFinishReason)
+                .orElse(null);
+        response.usage().ifPresentOrElse(
+                usage -> observation.recordSuccess(GenAiUsage.of(
+                        toTokenCount(usage.inputTokens()),
+                        toTokenCount(usage.outputTokens()),
+                        finishReason,
+                        response.model().toString())),
+                () -> observation.recordSuccess(GenAiUsage.of(null, null, finishReason, response.model().toString())));
+    }

Review Comment:
   Fixed in d1a3fc28 — removed `toTokenCount`; `usage.inputTokens()` and `usage.outputTokens()` are passed directly as `long` values.



##########
components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIResponsesProducer.java:
##########
@@ -136,28 +142,89 @@ private void processInternal(Exchange exchange) throws Exception {
 
         Class<?> responseClass = resolveOutputClass(in, outputClass);
         if (responseClass != null) {
-            processStructured(exchange, config, paramsBuilder, responseClass);
+            processStructured(exchange, config, paramsBuilder, responseClass, model);
             return;
         }
         if (ObjectHelper.isNotEmpty(jsonSchema)) {
             OpenAIResponsesSupport.applyJsonSchemaTextFormat(paramsBuilder, jsonSchema);
         }
 
         ResponseCreateParams params = paramsBuilder.build();
-        Response response = getEndpoint().getClient().responses().create(params);
+        Response response = createResponse(exchange, model, params);
         finishExchange(exchange, config, response, OpenAIResponsesSupport.extractAssistantText(response));
     }
 
     private void processStructured(
             Exchange exchange, OpenAIConfiguration config, ResponseCreateParams.Builder paramsBuilder,
-            Class<?> responseClass)
+            Class<?> responseClass, String model)
             throws Exception {
         StructuredResponseCreateParams<?> structuredParams = paramsBuilder.text(responseClass).build();
-        StructuredResponse<?> structured = getEndpoint().getClient().responses().create(structuredParams);
-        Response raw = structured.rawResponse();
+        Response raw = createStructuredResponse(exchange, model, structuredParams);
         finishExchange(exchange, config, raw, OpenAIResponsesSupport.extractAssistantText(raw));
     }
 
+    private Response createResponse(Exchange exchange, String model, ResponseCreateParams params) throws Exception {
+        GenAiObservationContext observationContext = GenAiObservationContext.builder()
+                .operationName(GenAiOperationName.CHAT)
+                .system("openai")
+                .requestModel(model)
+                .componentScheme("openai")
+                .build();

Review Comment:
   Fixed in d1a3fc28 — extracted shared `observedCall(exchange, model, ThrowingSupplier<Response, Exception>)` helper used by both code paths.



##########
components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIModerationProducer.java:
##########
@@ -87,19 +93,32 @@ private void processInternal(Exchange exchange) throws Exception {
             paramsBuilder.inputOfStrings(inputs);
         }
 
-        ModerationCreateResponse response = getEndpoint().getClient()
-                .moderations().create(paramsBuilder.build());
+        GenAiObservationContext observationContext = GenAiObservationContext.builder()
+                .operationName(GenAiOperationName.MODERATION)
+                .system("openai")
+                .requestModel(model)
+                .componentScheme("openai")
+                .build();
+        GenAiObservation observation = GenAiObservability.start(exchange, observationContext);
+        ModerationCreateResponse response;
+        try {
+            response = getEndpoint().getClient().moderations().create(paramsBuilder.build());
+            observation.recordSuccess(GenAiUsage.of(null, null, null, response.model()));
+        } catch (Exception e) {
+            GenAiErrorSupport.apply(exchange, e);
+            observation.recordError(e);
+            throw e;
+        } finally {
+            observation.close();

Review Comment:
   Fixed in d1a3fc28 — restored both security comments (mismatch must fail the exchange; store full response only after validation).



-- 
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: commits-unsubscribe@camel.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] CAMEL-24559: Extend GenAI observability to OpenAI embeddings, moderation, and responses [camel]

Posted by "atiaomar1978-hub (via GitHub)" <gi...@apache.org>.
atiaomar1978-hub commented on PR #26106:
URL: https://github.com/apache/camel/pull/26106#issuecomment-5547150096

   _This comment was generated by an AI agent on behalf of @atiaomar1978-hub._
   
   Review feedback from @gnodet has been addressed and the branch rebased onto latest `main`:
   
   1. **Long token counts** — Removed `Math.toIntExact` / `toTokenCount` from `OpenAIEmbeddingsProducer` and `OpenAIResponsesProducer`; token counts are passed directly as `long` values (CAMEL-24560 is already on `main`).
   
   2. **`OpenAIResponsesProducer` deduplication** — Extracted shared `observedCall(exchange, model, ThrowingSupplier)` helper used by both `createResponse` and `createStructuredResponse`.
   
   3. **Moderation security comments** — Restored the comments explaining why a mismatched moderation result count must fail the exchange (content-gating invariant) and why the full response is stored only after validation.
   
   Rebased commits on `main`:
   - `c6764d78` — initial observability instrumentation
   - `bcdb8f7f` — first review round (span scope, tests, docs)
   - `d1a3fc28` — gnodet review fixes
   
   Tests run locally after rebase: `GenAiUsageTest`, `OpenAIEmbeddingsObservabilityTest`, `OpenAIModerationObservabilityTest`, `OpenAIResponsesObservabilityTest` — all green.


-- 
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: commits-unsubscribe@camel.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org