Skip to content

Commit d1a3fc2

Browse files
cursoragentCursor Agent
andcommitted
CAMEL-24559: Address gnodet review feedback on OpenAI observability
- Pass OpenAI token counts as Long without Math.toIntExact (includes CAMEL-24560 GenAiUsage Long token fields cherry-picked from follow-up) - Extract observedCall helper in OpenAIResponsesProducer to deduplicate observation boilerplate between createResponse and createStructuredResponse - Restore moderation security comments explaining why mismatched result counts must fail the exchange Co-authored-by: Cursor Agent <noreply@cursor.com>
1 parent bcdb8f7 commit d1a3fc2

3 files changed

Lines changed: 19 additions & 36 deletions

File tree

components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIEmbeddingsProducer.java

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ private void processInternal(Exchange exchange) throws Exception {
117117
response = getEndpoint().getClient().embeddings().create(params);
118118
var usage = response.usage();
119119
observation.recordSuccess(GenAiUsage.of(
120-
usage != null ? toTokenCount(usage.promptTokens()) : null,
120+
usage != null ? usage.promptTokens() : null,
121121
null,
122122
null,
123123
response.model()));
@@ -151,10 +151,6 @@ private void processInternal(Exchange exchange) throws Exception {
151151
calculateSimilarityIfRequested(exchange, embeddings);
152152
}
153153

154-
private static Integer toTokenCount(long tokens) {
155-
return Math.toIntExact(tokens);
156-
}
157-
158154
@SuppressWarnings("unchecked")
159155
private List<String> extractInputs(Message in) throws Exception {
160156
Object body = in.getBody();

components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIModerationProducer.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ private void processInternal(Exchange exchange) throws Exception {
103103
ModerationCreateResponse response;
104104
try {
105105
response = getEndpoint().getClient().moderations().create(paramsBuilder.build());
106-
observation.recordSuccess(GenAiUsage.of(null, null, null, response.model()));
106+
observation.recordSuccess(GenAiUsage.of((Long) null, null, null, response.model()));
107107
} catch (Exception e) {
108108
GenAiErrorSupport.apply(exchange, e);
109109
observation.recordError(e);
@@ -112,13 +112,16 @@ private void processInternal(Exchange exchange) throws Exception {
112112
observation.close();
113113
}
114114

115+
// this operation is used to gate untrusted content, so a missing verdict must fail the exchange
116+
// instead of leaving CamelOpenAIModerationFlagged false and letting the message through
115117
if (response.results().size() != inputs.size()) {
116118
throw new CamelExchangeException(
117119
"Moderation returned " + response.results().size() + " result(s) for " + inputs.size()
118120
+ " input(s)",
119121
exchange);
120122
}
121123

124+
// stored only once the response is known to be complete, so a failed exchange carries no verdict
122125
if (config.isStoreFullResponse()) {
123126
exchange.setProperty(OpenAIConstants.MODERATION_RESPONSE, response);
124127
}

components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIResponsesProducer.java

Lines changed: 14 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020

2121
import com.openai.models.responses.Response;
2222
import com.openai.models.responses.ResponseCreateParams;
23-
import com.openai.models.responses.StructuredResponse;
2423
import com.openai.models.responses.StructuredResponseCreateParams;
2524
import org.apache.camel.AsyncCallback;
2625
import org.apache.camel.Exchange;
@@ -34,6 +33,7 @@
3433
import org.apache.camel.support.DefaultAsyncProducer;
3534
import org.apache.camel.support.ResourceHelper;
3635
import org.apache.camel.util.ObjectHelper;
36+
import org.apache.camel.util.function.ThrowingSupplier;
3737

3838
/**
3939
* OpenAI producer for the Responses API (non-streaming).
@@ -164,29 +164,18 @@ private void processStructured(
164164
}
165165

166166
private Response createResponse(Exchange exchange, String model, ResponseCreateParams params) throws Exception {
167-
GenAiObservationContext observationContext = GenAiObservationContext.builder()
168-
.operationName(GenAiOperationName.CHAT)
169-
.system("openai")
170-
.requestModel(model)
171-
.componentScheme("openai")
172-
.build();
173-
GenAiObservation observation = GenAiObservability.start(exchange, observationContext);
174-
try {
175-
Response response = getEndpoint().getClient().responses().create(params);
176-
recordResponseSuccess(observation, response);
177-
return response;
178-
} catch (Exception e) {
179-
GenAiErrorSupport.apply(exchange, e);
180-
observation.recordError(e);
181-
throw e;
182-
} finally {
183-
observation.close();
184-
}
167+
return observedCall(exchange, model, () -> getEndpoint().getClient().responses().create(params));
185168
}
186169

187170
private Response createStructuredResponse(
188171
Exchange exchange, String model, StructuredResponseCreateParams<?> structuredParams)
189172
throws Exception {
173+
return observedCall(exchange, model,
174+
() -> getEndpoint().getClient().responses().create(structuredParams).rawResponse());
175+
}
176+
177+
private Response observedCall(Exchange exchange, String model, ThrowingSupplier<Response, Exception> call)
178+
throws Exception {
190179
GenAiObservationContext observationContext = GenAiObservationContext.builder()
191180
.operationName(GenAiOperationName.CHAT)
192181
.system("openai")
@@ -195,10 +184,9 @@ private Response createStructuredResponse(
195184
.build();
196185
GenAiObservation observation = GenAiObservability.start(exchange, observationContext);
197186
try {
198-
StructuredResponse<?> structured = getEndpoint().getClient().responses().create(structuredParams);
199-
Response raw = structured.rawResponse();
200-
recordResponseSuccess(observation, raw);
201-
return raw;
187+
Response response = call.get();
188+
recordResponseSuccess(observation, response);
189+
return response;
202190
} catch (Exception e) {
203191
GenAiErrorSupport.apply(exchange, e);
204192
observation.recordError(e);
@@ -214,15 +202,11 @@ private static void recordResponseSuccess(GenAiObservation observation, Response
214202
.orElse(null);
215203
response.usage().ifPresentOrElse(
216204
usage -> observation.recordSuccess(GenAiUsage.of(
217-
toTokenCount(usage.inputTokens()),
218-
toTokenCount(usage.outputTokens()),
205+
usage.inputTokens(),
206+
usage.outputTokens(),
219207
finishReason,
220208
response.model().toString())),
221-
() -> observation.recordSuccess(GenAiUsage.of(null, null, finishReason, response.model().toString())));
222-
}
223-
224-
private static Integer toTokenCount(long tokens) {
225-
return Math.toIntExact(tokens);
209+
() -> observation.recordSuccess(GenAiUsage.of((Long) null, null, finishReason, response.model().toString())));
226210
}
227211

228212
private void finishExchange(Exchange exchange, OpenAIConfiguration config, Response response, String body) {

0 commit comments

Comments
 (0)