11package io .quarkiverse .langchain4j .runtime ;
22
33import java .lang .reflect .*;
4+ import java .time .LocalDate ;
5+ import java .time .LocalDateTime ;
6+ import java .time .OffsetDateTime ;
47import java .util .*;
58import java .util .regex .Matcher ;
69import java .util .regex .Pattern ;
710
811import com .fasterxml .jackson .databind .ObjectMapper ;
912
1013import dev .langchain4j .data .message .AiMessage ;
14+ import dev .langchain4j .data .message .ChatMessage ;
1115import dev .langchain4j .model .output .Response ;
1216import dev .langchain4j .model .output .structured .Description ;
1317import dev .langchain4j .service .Result ;
1418import dev .langchain4j .service .TokenStream ;
1519import dev .langchain4j .service .TypeUtils ;
16- //import dev.langchain4j.service.output.OutputParser;
1720import dev .langchain4j .service .output .ServiceOutputParser ;
1821import io .quarkiverse .langchain4j .QuarkusJsonCodecFactory ;
1922import io .smallrye .mutiny .Multi ;
@@ -23,13 +26,18 @@ public class QuarkusServiceOutputParser extends ServiceOutputParser {
2326
2427 @ Override
2528 public String outputFormatInstructions (Type returnType ) {
26- Class <?> rawClass = getRawClass (returnType );
29+ boolean isOptional = isJavaOptional (returnType );
30+ Type actualType = isOptional ? unwrapOptionalType (returnType ) : returnType ;
31+
32+ Class <?> rawClass = getRawClass (actualType );
2733
2834 if (rawClass != String .class && rawClass != AiMessage .class && rawClass != TokenStream .class
35+ && rawClass != ChatMessage .class
2936 && rawClass != Response .class && !Multi .class .equals (rawClass )) {
3037 try {
3138 var schema = this .toJsonSchema (returnType );
32- return "You must answer strictly with json according to the following json schema format: " + schema ;
39+ return "You must answer strictly with json according to the following json schema format. Use description metadata to fill data properly: "
40+ + schema ;
3341 } catch (Exception e ) {
3442 return "" ;
3543 }
@@ -52,7 +60,7 @@ public Object parse(Response<AiMessage> response, Type returnType) {
5260 return response ;
5361 } else {
5462 AiMessage aiMessage = response .content ();
55- if (rawReturnClass == AiMessage .class ) {
63+ if (rawReturnClass == AiMessage .class || rawReturnClass == ChatMessage . class ) {
5664 return aiMessage ;
5765 } else {
5866 String text = aiMessage .text ();
@@ -77,7 +85,10 @@ private String extractJsonBlock(String text) {
7785
7886 public String toJsonSchema (Type type ) throws Exception {
7987 Map <String , Object > schema = new HashMap <>();
80- Class <?> rawClass = getRawClass (type );
88+ boolean isOptional = isJavaOptional (type );
89+ Type actualType = isOptional ? unwrapOptionalType (type ) : type ;
90+
91+ Class <?> rawClass = getRawClass (actualType );
8192
8293 if (type instanceof WildcardType wildcardType ) {
8394 Type boundType = wildcardType .getUpperBounds ().length > 0 ? wildcardType .getUpperBounds ()[0 ]
@@ -97,29 +108,77 @@ public String toJsonSchema(Type type) throws Exception {
97108 Type elementType = getElementType (type );
98109 Map <String , Object > itemsSchema = toJsonSchemaMap (elementType );
99110 schema .put ("items" , itemsSchema );
111+ } else if (rawClass == LocalDate .class || rawClass == Date .class ) {
112+ schema .put ("type" , "string" );
113+ schema .put ("format" , "date" );
114+ } else if (rawClass == LocalDateTime .class || rawClass == OffsetDateTime .class ) {
115+ schema .put ("type" , "string" );
116+ schema .put ("format" , "date-time" );
100117 } else if (rawClass .isEnum ()) {
101118 schema .put ("type" , "string" );
102119 schema .put ("enum" , getEnumConstants (rawClass ));
103120 } else {
104121 schema .put ("type" , "object" );
105122 Map <String , Object > properties = new HashMap <>();
106123
124+ List <String > required = new ArrayList <>();
107125 for (Field field : rawClass .getDeclaredFields ()) {
108- field .setAccessible (true );
109- Map <String , Object > fieldSchema = toJsonSchemaMap (field .getGenericType ());
110- properties .put (field .getName (), fieldSchema );
111- if (field .isAnnotationPresent (Description .class )) {
112- Description description = field .getAnnotation (Description .class );
113- fieldSchema .put ("description" , description .value ());
126+ try {
127+ field .setAccessible (true );
128+ Type fieldType = field .getGenericType ();
129+
130+ // Check if the field is Optional and unwrap it if necessary
131+ boolean fieldIsOptional = isJavaOptional (fieldType );
132+ Type fieldActualType = fieldIsOptional ? unwrapOptionalType (fieldType ) : fieldType ;
133+
134+ Map <String , Object > fieldSchema = toJsonSchemaMap (fieldActualType );
135+ properties .put (field .getName (), fieldSchema );
136+
137+ if (field .isAnnotationPresent (Description .class )) {
138+ Description description = field .getAnnotation (Description .class );
139+ fieldSchema .put ("description" , String .join ("," , description .value ()));
140+ }
141+
142+ // Only add to required if it is not Optional
143+ if (!fieldIsOptional ) {
144+ required .add (field .getName ());
145+ } else {
146+ fieldSchema .put ("nullable" , true ); // Mark as nullable in the JSON schema
147+ }
148+
149+ } catch (Exception e ) {
150+
114151 }
152+
115153 }
116154 schema .put ("properties" , properties );
155+ if (!required .isEmpty ()) {
156+ schema .put ("required" , required );
157+ }
158+ }
159+ if (isOptional ) {
160+ schema .put ("nullable" , true );
117161 }
118-
119162 ObjectMapper mapper = new ObjectMapper ();
120163 return mapper .writeValueAsString (schema ); // Convert the schema map to a JSON string
121164 }
122165
166+ private boolean isJavaOptional (Type type ) {
167+ if (type instanceof ParameterizedType ) {
168+ Type rawType = ((ParameterizedType ) type ).getRawType ();
169+ return rawType == Optional .class || rawType == OptionalInt .class || rawType == OptionalLong .class
170+ || rawType == OptionalDouble .class ;
171+ }
172+ return false ;
173+ }
174+
175+ private Type unwrapOptionalType (Type optionalType ) {
176+ if (optionalType instanceof ParameterizedType ) {
177+ return ((ParameterizedType ) optionalType ).getActualTypeArguments ()[0 ];
178+ }
179+ return optionalType ;
180+ }
181+
123182 private Class <?> getRawClass (Type type ) {
124183 if (type instanceof Class <?>) {
125184 return (Class <?>) type ;
0 commit comments