1313import dev .langchain4j .service .Result ;
1414import dev .langchain4j .service .TokenStream ;
1515import dev .langchain4j .service .TypeUtils ;
16- //import dev.langchain4j.service.output.OutputParser;
1716import dev .langchain4j .service .output .ServiceOutputParser ;
1817import io .quarkiverse .langchain4j .QuarkusJsonCodecFactory ;
1918import io .smallrye .mutiny .Multi ;
@@ -23,13 +22,17 @@ public class QuarkusServiceOutputParser extends ServiceOutputParser {
2322
2423 @ Override
2524 public String outputFormatInstructions (Type returnType ) {
26- Class <?> rawClass = getRawClass (returnType );
25+ boolean isOptional = isJavaOptional (returnType );
26+ Type actualType = isOptional ? unwrapOptionalType (returnType ) : returnType ;
27+
28+ Class <?> rawClass = getRawClass (actualType );
2729
2830 if (rawClass != String .class && rawClass != AiMessage .class && rawClass != TokenStream .class
2931 && rawClass != Response .class && !Multi .class .equals (rawClass )) {
3032 try {
3133 var schema = this .toJsonSchema (returnType );
32- return "You must answer strictly with json according to the following json schema format: " + schema ;
34+ return "You must answer strictly with json according to the following json schema format. Use description metadata to fill data properly: "
35+ + schema ;
3336 } catch (Exception e ) {
3437 return "" ;
3538 }
@@ -77,7 +80,10 @@ private String extractJsonBlock(String text) {
7780
7881 public String toJsonSchema (Type type ) throws Exception {
7982 Map <String , Object > schema = new HashMap <>();
80- Class <?> rawClass = getRawClass (type );
83+ boolean isOptional = isJavaOptional (type );
84+ Type actualType = isOptional ? unwrapOptionalType (type ) : type ;
85+
86+ Class <?> rawClass = getRawClass (actualType );
8187
8288 if (type instanceof WildcardType wildcardType ) {
8389 Type boundType = wildcardType .getUpperBounds ().length > 0 ? wildcardType .getUpperBounds ()[0 ]
@@ -104,22 +110,64 @@ public String toJsonSchema(Type type) throws Exception {
104110 schema .put ("type" , "object" );
105111 Map <String , Object > properties = new HashMap <>();
106112
113+ List <String > required = new ArrayList <>();
107114 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 ());
115+ try {
116+ field .setAccessible (true );
117+ Type fieldType = field .getGenericType ();
118+
119+ // Check if the field is Optional and unwrap it if necessary
120+ boolean fieldIsOptional = isJavaOptional (fieldType );
121+ Type fieldActualType = fieldIsOptional ? unwrapOptionalType (fieldType ) : fieldType ;
122+
123+ Map <String , Object > fieldSchema = toJsonSchemaMap (fieldActualType );
124+ properties .put (field .getName (), fieldSchema );
125+
126+ if (field .isAnnotationPresent (Description .class )) {
127+ Description description = field .getAnnotation (Description .class );
128+ fieldSchema .put ("description" , String .join ("," , description .value ()));
129+ }
130+
131+ // Only add to required if it is not Optional
132+ if (!fieldIsOptional ) {
133+ required .add (field .getName ());
134+ } else {
135+ fieldSchema .put ("nullable" , true ); // Mark as nullable in the JSON schema
136+ }
137+
138+ } catch (Exception e ) {
139+
114140 }
141+
115142 }
116143 schema .put ("properties" , properties );
144+ if (!required .isEmpty ()) {
145+ schema .put ("required" , required );
146+ }
147+ }
148+ if (isOptional ) {
149+ schema .put ("nullable" , true );
117150 }
118-
119151 ObjectMapper mapper = new ObjectMapper ();
120152 return mapper .writeValueAsString (schema ); // Convert the schema map to a JSON string
121153 }
122154
155+ private boolean isJavaOptional (Type type ) {
156+ if (type instanceof ParameterizedType ) {
157+ Type rawType = ((ParameterizedType ) type ).getRawType ();
158+ return rawType == Optional .class || rawType == OptionalInt .class || rawType == OptionalLong .class
159+ || rawType == OptionalDouble .class ;
160+ }
161+ return false ;
162+ }
163+
164+ private Type unwrapOptionalType (Type optionalType ) {
165+ if (optionalType instanceof ParameterizedType ) {
166+ return ((ParameterizedType ) optionalType ).getActualTypeArguments ()[0 ];
167+ }
168+ return optionalType ;
169+ }
170+
123171 private Class <?> getRawClass (Type type ) {
124172 if (type instanceof Class <?>) {
125173 return (Class <?>) type ;
0 commit comments