Skip to content

Commit 1055e60

Browse files
authored
Merge pull request #165 from quarkiverse/#164
Limit scope of OpenAiRestApi providers
2 parents 0c90354 + 8b4789d commit 1055e60

File tree

2 files changed

+35
-34
lines changed
  • openai

2 files changed

+35
-34
lines changed

openai/openai-common/runtime/src/main/java/io/quarkiverse/langchain4j/openai/OpenAiRestApi.java

Lines changed: 34 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -15,26 +15,24 @@
1515
import java.util.regex.Pattern;
1616

1717
import jakarta.annotation.Priority;
18-
import jakarta.ws.rs.ConstrainedTo;
1918
import jakarta.ws.rs.Consumes;
2019
import jakarta.ws.rs.POST;
2120
import jakarta.ws.rs.Path;
2221
import jakarta.ws.rs.Priorities;
2322
import jakarta.ws.rs.Produces;
24-
import jakarta.ws.rs.RuntimeType;
2523
import jakarta.ws.rs.WebApplicationException;
2624
import jakarta.ws.rs.core.HttpHeaders;
2725
import jakarta.ws.rs.core.MediaType;
2826
import jakarta.ws.rs.core.MultivaluedMap;
2927
import jakarta.ws.rs.core.Response;
3028
import jakarta.ws.rs.ext.MessageBodyWriter;
31-
import jakarta.ws.rs.ext.Provider;
3229
import jakarta.ws.rs.ext.ReaderInterceptor;
3330
import jakarta.ws.rs.ext.ReaderInterceptorContext;
3431
import jakarta.ws.rs.ext.WriterInterceptor;
3532
import jakarta.ws.rs.ext.WriterInterceptorContext;
3633

3734
import org.eclipse.microprofile.rest.client.annotation.ClientHeaderParam;
35+
import org.eclipse.microprofile.rest.client.annotation.RegisterProvider;
3836
import org.jboss.logging.Logger;
3937
import org.jboss.resteasy.reactive.RestQuery;
4038
import org.jboss.resteasy.reactive.RestStreamElementType;
@@ -76,6 +74,10 @@
7674
@ClientHeaderParam(name = "api-key", value = "{apiKey}") // used by AzureAI
7775
@Consumes(MediaType.APPLICATION_JSON)
7876
@Produces(MediaType.APPLICATION_JSON)
77+
@RegisterProvider(OpenAiRestApi.OpenAiRestApiJacksonReader.class)
78+
@RegisterProvider(OpenAiRestApi.OpenAiRestApiJacksonWriter.class)
79+
@RegisterProvider(OpenAiRestApi.OpenAiRestApiReaderInterceptor.class)
80+
@RegisterProvider(OpenAiRestApi.OpenAiRestApiWriterInterceptor.class)
7981
public interface OpenAiRestApi {
8082

8183
/**
@@ -181,57 +183,58 @@ public boolean test(SseEvent<String> event) {
181183
}
182184
}
183185

184-
/**
185-
* We need a custom version of the Jackson provider because reading SSE values does not work properly with
186-
* {@code @ClientObjectMapper} due to the lack of a complete context in those requests
187-
*/
188-
@Provider
189-
@ConstrainedTo(RuntimeType.CLIENT)
190-
@Priority(Priorities.USER + 100)
191-
class OpenAiRestApiJacksonProvider extends AbstractJsonMessageBodyReader implements MessageBodyWriter<Object> {
186+
@Priority(Priorities.USER + 100) // this priority ensures that our Writer has priority over the standard Jackson one
187+
class OpenAiRestApiJacksonWriter implements MessageBodyWriter<Object> {
192188

193-
/**
194-
* Normally this is not necessary, but if one uses the 'demo' key, then the response comes back as type text/html
195-
* but the content is still JSON. Go figure...
196-
*/
197189
@Override
198-
public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
190+
public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
199191
return true;
200192
}
201193

202194
@Override
203-
public Object readFrom(Class<Object> type, Type genericType, Annotation[] annotations, MediaType mediaType,
204-
MultivaluedMap<String, String> httpHeaders, InputStream entityStream)
195+
public void writeTo(Object o, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType,
196+
MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream)
205197
throws IOException, WebApplicationException {
206-
return ObjectMapperHolder.READER
207-
.forType(ObjectMapperHolder.READER.getTypeFactory().constructType(genericType != null ? genericType : type))
208-
.readValue(entityStream);
198+
entityStream.write(ObjectMapperHolder.MAPPER.writeValueAsString(o).getBytes(StandardCharsets.UTF_8));
209199
}
200+
}
201+
202+
@Priority(Priorities.USER - 100) // this priority ensures that our Reader has priority over the standard Jackson one
203+
class OpenAiRestApiJacksonReader extends AbstractJsonMessageBodyReader {
210204

205+
/**
206+
* Normally this is not necessary, but if one uses the 'demo' Langchain4j key, then the response comes back as type
207+
* text/html
208+
* but the content is still JSON.
209+
*/
211210
@Override
212-
public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
211+
public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
213212
return true;
214213
}
215214

215+
/**
216+
* We need a custom version of the Jackson provider because reading SSE values does not work properly with
217+
* {@code @ClientObjectMapper} due to the lack of a complete context in those requests
218+
*/
216219
@Override
217-
public void writeTo(Object o, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType,
218-
MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream)
220+
public Object readFrom(Class<Object> type, Type genericType, Annotation[] annotations, MediaType mediaType,
221+
MultivaluedMap<String, String> httpHeaders, InputStream entityStream)
219222
throws IOException, WebApplicationException {
220-
entityStream.write(ObjectMapperHolder.MAPPER.writeValueAsString(o).getBytes(StandardCharsets.UTF_8));
223+
return ObjectMapperHolder.READER
224+
.forType(ObjectMapperHolder.READER.getTypeFactory().constructType(genericType != null ? genericType : type))
225+
.readValue(entityStream);
221226
}
227+
}
222228

223-
public static class ObjectMapperHolder {
224-
public static final ObjectMapper MAPPER = QuarkusJsonCodecFactory.SnakeCaseObjectMapperHolder.MAPPER;
229+
public class ObjectMapperHolder {
230+
public static final ObjectMapper MAPPER = QuarkusJsonCodecFactory.SnakeCaseObjectMapperHolder.MAPPER;
225231

226-
private static final ObjectReader READER = MAPPER.reader();
227-
}
232+
private static final ObjectReader READER = MAPPER.reader();
228233
}
229234

230235
/**
231236
* This method validates that the response is not empty, which happens when the API returns an error object
232237
*/
233-
@Provider
234-
@ConstrainedTo(RuntimeType.CLIENT)
235238
class OpenAiRestApiReaderInterceptor implements ReaderInterceptor {
236239

237240
@Override
@@ -272,8 +275,6 @@ private Object validateResponse(Object result) {
272275
* The point of this is to properly set the {@code stream} value of the request
273276
* so users don't have to remember to set it manually
274277
*/
275-
@Provider
276-
@ConstrainedTo(RuntimeType.CLIENT)
277278
class OpenAiRestApiWriterInterceptor implements WriterInterceptor {
278279
@Override
279280
public void aroundWriteTo(WriterInterceptorContext context) throws IOException, WebApplicationException {

openai/openai-vanilla/deployment/src/test/java/io/quarkiverse/langchain4j/openai/test/JsonParsingTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public class JsonParsingTest {
2222

2323
@Test
2424
void testChatCompletion() throws JsonProcessingException {
25-
ObjectMapper mapperToUse = OpenAiRestApi.OpenAiRestApiJacksonProvider.ObjectMapperHolder.MAPPER;
25+
ObjectMapper mapperToUse = OpenAiRestApi.ObjectMapperHolder.MAPPER;
2626

2727
ChatCompletionResponse chatCompletionResponse = mapperToUse.readValue(
2828
"{\"id\":\"chatcmpl-8AAeH0Sdve2wfHWhFIXq1gFkkqoIU\",\"object\":\"chat.completion.chunk\",\"created\":1697434905,\"model\":\"gpt-3.5-turbo-0613\",\"choices\":[{\"index\":0,\"delta\":{},\"finish_reason\":\"length\"}]}",

0 commit comments

Comments
 (0)