-
Notifications
You must be signed in to change notification settings - Fork 11
feat: [Orchestration] Filtering details and additional convenience on exception #497
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
feat: [Orchestration] Filtering details and additional convenience on exception #497
Conversation
…on-handling-factory # Conflicts: # orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationClient.java
…on-handling-factory
- remove crafty object mapping in response handler
*/ | ||
@Nullable | ||
@Getter(onMethod_ = @Beta) | ||
public ClientError clientError; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(MInor)
Please make it (package) private.
Please add Setter, maybe?
@Nonnull final Class<T> successType; | ||
|
||
/** The HTTP error response type */ | ||
@Nonnull protected final Class<R> errorType; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Question)
Why protected?
@@ -48,8 +50,8 @@ public class ClientResponseHandler<T, E extends ClientException> | |||
*/ | |||
@Beta | |||
@Nonnull | |||
public ClientResponseHandler<T, E> objectMapper(@Nonnull final ObjectMapper jackson) { | |||
objectMapper = jackson; | |||
public ClientResponseHandler<T, R, E> objectMapper(@Nonnull final ObjectMapper jackson) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Question)
Is the fluent API reasonable here? Would the code look cleaner if this was a regular void setter?
if (response.getCode() >= 300) { | ||
buildExceptionAndThrow(response); | ||
throw buildException(response); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Minor)
In Java the "expensive" part of exceptions is the implicit buildup of current stacktrace. That happens at exception instantiation, not when throwing. That also means this will look surprising that the stacktrace points at "new" and not at "throw" (here). Therefore, this line has a code smell.
final HttpEntity responseEntity = response.getEntity(); | ||
if (responseEntity == null) { | ||
throw exceptionConstructor.apply("Response was empty.", null); | ||
throw exceptionFactory.create("Response was empty.", null); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Minor)
I know this is semi-related but can we change the message to "HTTP response is empty"?
exception.addSuppressed(maybeContent.getCause()); | ||
throw exception; | ||
baseException.addSuppressed(maybeContent.getCause()); | ||
return baseException; | ||
} | ||
val content = maybeContent.get(); | ||
if (content.isBlank()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Major) content
may be null
.
null); | ||
val entity = response.getEntity(); | ||
@Nonnull | ||
protected E buildException(@Nonnull final ClassicHttpResponse httpResponse) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Comment)
I was thinking, If we moved this whole method and related logic into ClientExceptionFactory
then we could get rid of generic argument R extends ClientError
in this class :/ But this would risk further duplicate code though. It was just a thought
*/ | ||
@Beta | ||
@Nullable | ||
public Integer getStatusCode() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Comment)
We can leave it in for now. But we will likely extend the exception class with optional HttpResponse
field reference. Once we have that, then this method will appear very confusing and we may have to change it.
It's beta anyway.
} | ||
|
||
/** Exception thrown output filtering in orchestration when finish reason is content filter */ | ||
public static class OrchestrationOutputFilterException extends OrchestrationFilterException { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Minor/Discussion)
I would rename it to "Output". Such that class reference becomes
OrchestrationFilterException.Output
public class OrchestrationFilterException extends OrchestrationClientException { | ||
|
||
/** Details about the filter that caused the exception. */ | ||
@Getter @Nonnull protected Map<String, Object> filterDetails; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Minor)
IntelliJ is correctly warning me: This field cannot be "Nonnull" when default constructor of this class sets it to "null". I think there are multiple ways to address that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Easiest (and obvious?) way is probably initializing it with empty map.
.flatMap(moduleResults -> Optional.ofNullable(moduleResults.getInputFiltering())) | ||
.flatMap(inputFiltering -> Optional.ofNullable(inputFiltering.getData())) | ||
.filter(Map.class::isInstance) | ||
.map(map -> (Map<String, Object>) map) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Minor)
Technically this is an unsafe cast. We could suppress the warning. (do you see the warning?). Since JSON only assumes dictionaries/maps with String as key-type, this could be considered safe.
Casting to Map<String, Map<String,Object>>
would be dangerous. I think.
throw exceptionConstructor.apply(message, baseException); | ||
final R clientError = maybeClientError.get(); | ||
val extendErrorMessage = "%s: %s".formatted(baseErrorMessage, clientError.getMessage()); | ||
return exceptionFactory.fromClientError(extendErrorMessage, clientError); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@newtork this return
doesn't reuse the baseException
, it creates a new exception. The forgotten baseException
might have a performance cost, plus get picked up by logging frameworks
Context
AI/ai-sdk-java-backlog#253.
Previously, we didn't propagate the deserialized error response along with any thrown exception. This also meant our users have no access to diagnostic details returned by orchestration service (also applicable for openai).
The PR aims to rectify the above problem and especiallyy provide convenience for input and output filtering exceptions.
Here are two user issues created requesting the same convenience. They have both suggested some api. Maybe you can also look at this to have a better picture on what convenience may be added
#491
#468
Feature scope:
ClientExceptionFactory
getClientError
that returns module specific error objects wrapping deserialized server response.OrchestrationInputFilteringException
andOrchestrationOutputFilteringException
to distinguish filtering exception types with catch blocks.Map<String, Object> getFilterDetails()
to inspect the filtering details available.getStatusCode
toOrchestrationClientException
to get the http status codeClientResponseHandler
ClientStreamingHandler
Definition of Done
Aligned changes with the JavaScript SDK