16
16
*/
17
17
package org .apache .camel .quarkus .component .support .langchain4j .deployment ;
18
18
19
+ import java .util .Collection ;
20
+ import java .util .HashSet ;
19
21
import java .util .Set ;
20
22
import java .util .stream .Collectors ;
23
+ import java .util .stream .Stream ;
21
24
25
+ import com .fasterxml .jackson .databind .JsonDeserializer ;
26
+ import com .fasterxml .jackson .databind .JsonSerializer ;
27
+ import com .fasterxml .jackson .databind .PropertyNamingStrategies ;
28
+ import dev .langchain4j .guardrail .InputGuardrail ;
29
+ import dev .langchain4j .guardrail .JsonExtractorOutputGuardrail ;
30
+ import dev .langchain4j .guardrail .OutputGuardrail ;
31
+ import dev .langchain4j .service .MemoryId ;
32
+ import dev .langchain4j .service .SystemMessage ;
33
+ import dev .langchain4j .service .UserMessage ;
34
+ import dev .langchain4j .service .V ;
35
+ import io .quarkus .bootstrap .model .ApplicationModel ;
22
36
import io .quarkus .deployment .annotations .BuildProducer ;
23
37
import io .quarkus .deployment .annotations .BuildStep ;
38
+ import io .quarkus .deployment .annotations .BuildSteps ;
24
39
import io .quarkus .deployment .builditem .CombinedIndexBuildItem ;
25
40
import io .quarkus .deployment .builditem .IndexDependencyBuildItem ;
41
+ import io .quarkus .deployment .builditem .nativeimage .NativeImageProxyDefinitionBuildItem ;
42
+ import io .quarkus .deployment .builditem .nativeimage .NativeImageResourcePatternsBuildItem ;
26
43
import io .quarkus .deployment .builditem .nativeimage .ReflectiveClassBuildItem ;
27
44
import io .quarkus .deployment .builditem .nativeimage .RuntimeInitializedClassBuildItem ;
28
45
import io .quarkus .deployment .builditem .nativeimage .ServiceProviderBuildItem ;
46
+ import io .quarkus .deployment .pkg .builditem .CurateOutcomeBuildItem ;
47
+ import io .quarkus .deployment .pkg .steps .NativeOrNativeSourcesBuild ;
48
+ import io .quarkus .maven .dependency .ResolvedDependency ;
49
+ import opennlp .tools .sentdetect .SentenceDetectorFactory ;
50
+ import org .jboss .jandex .AnnotationInstance ;
51
+ import org .jboss .jandex .AnnotationTarget ;
29
52
import org .jboss .jandex .ClassInfo ;
30
53
import org .jboss .jandex .DotName ;
54
+ import org .jboss .jandex .IndexView ;
55
+ import org .jboss .jandex .MethodInfo ;
56
+ import org .jboss .jandex .MethodParameterInfo ;
57
+ import org .jboss .jandex .Type ;
31
58
59
+ @ BuildSteps (onlyIf = NativeOrNativeSourcesBuild .class )
32
60
class SupportLangchain4jProcessor {
61
+ private static final Class <?>[] AI_SERVICE_ANNOTATION_CLASSES = {
62
+ MemoryId .class ,
63
+ SystemMessage .class ,
64
+ UserMessage .class ,
65
+ V .class
66
+ };
33
67
34
68
@ BuildStep
35
- void indexDependencies (BuildProducer <IndexDependencyBuildItem > indexedDependencies ) {
36
- indexedDependencies .produce (new IndexDependencyBuildItem ("dev.langchain4j" , "langchain4j-http-client-jdk" ));
37
- indexedDependencies .produce (new IndexDependencyBuildItem ("dev.langchain4j" , "langchain4j-ollama" ));
69
+ void indexDependencies (CurateOutcomeBuildItem curateOutcome , BuildProducer <IndexDependencyBuildItem > indexedDependencies ) {
70
+ ApplicationModel applicationModel = curateOutcome .getApplicationModel ();
71
+ for (ResolvedDependency dependency : applicationModel .getDependencies ()) {
72
+ if (dependency .getGroupId ().equals ("dev.langchain4j" )) {
73
+ indexedDependencies .produce (new IndexDependencyBuildItem (dependency .getGroupId (), dependency .getArtifactId ()));
74
+ }
75
+ }
38
76
}
39
77
40
78
@ BuildStep
@@ -43,24 +81,131 @@ ServiceProviderBuildItem registerServiceProviders() {
43
81
}
44
82
45
83
@ BuildStep
46
- void registerForReflection (CombinedIndexBuildItem combinedIndex , BuildProducer <ReflectiveClassBuildItem > reflectiveClass ) {
47
- Set <String > ollamaModelClasses = combinedIndex .getIndex ()
48
- .getClassesInPackage ("dev.langchain4j.model.ollama" )
49
- .stream ()
84
+ void registerLangChain4jJacksonTypesForReflection (
85
+ CombinedIndexBuildItem combinedIndex ,
86
+ BuildProducer <ReflectiveClassBuildItem > reflectiveClass ) {
87
+ IndexView index = combinedIndex .getIndex ();
88
+
89
+ // Discover all LangChain4j Jackson model types
90
+ Set <String > langChain4jModelClasses = langChain4jTypesStream (index .getKnownClasses ())
50
91
.filter (classInfo -> classInfo .annotations ().stream ()
51
92
.anyMatch (annotationInstance -> annotationInstance .name ().toString ()
52
93
.startsWith ("com.fasterxml.jackson.annotation" )))
53
94
.map (ClassInfo ::name )
54
95
.map (DotName ::toString )
55
96
.collect (Collectors .toSet ());
56
97
57
- reflectiveClass .produce (ReflectiveClassBuildItem .builder (ollamaModelClasses .toArray (new String [0 ]))
98
+ reflectiveClass .produce (ReflectiveClassBuildItem .builder (langChain4jModelClasses .toArray (new String [0 ]))
58
99
.methods (true )
59
100
.build ());
101
+
102
+ // Discover all LangChain4j Jackson serializer / deserializer types
103
+ Set <String > jacksonSupportClasses = langChain4jTypesStream (index .getAllKnownSubclasses (JsonSerializer .class ))
104
+ .map (classInfo -> classInfo .name ().toString ())
105
+ .collect (Collectors .toSet ());
106
+
107
+ langChain4jTypesStream (index .getAllKnownSubclasses (JsonDeserializer .class ))
108
+ .map (classInfo -> classInfo .name ().toString ())
109
+ .forEach (jacksonSupportClasses ::add );
110
+
111
+ reflectiveClass .produce (ReflectiveClassBuildItem .builder (jacksonSupportClasses .toArray (new String [0 ])).build ());
112
+
113
+ // Misc Jackson support
114
+ ReflectiveClassBuildItem .builder (PropertyNamingStrategies .SnakeCaseStrategy .class ).build ();
115
+ }
116
+
117
+ @ BuildStep
118
+ void registerLangChain4jAiServiceTypesForReflection (
119
+ CombinedIndexBuildItem combinedIndex ,
120
+ BuildProducer <ReflectiveClassBuildItem > reflectiveClass ,
121
+ BuildProducer <NativeImageProxyDefinitionBuildItem > nativeImageProxy ) {
122
+
123
+ IndexView index = combinedIndex .getIndex ();
124
+ Set <String > aiServiceInterfaces = new HashSet <>();
125
+ Set <String > aiServiceTypes = new HashSet <>();
126
+
127
+ for (Class <?> aiServiceClass : AI_SERVICE_ANNOTATION_CLASSES ) {
128
+ for (AnnotationInstance annotationInstance : index .getAnnotations (aiServiceClass )) {
129
+ AnnotationTarget annotationTarget = annotationInstance .target ();
130
+
131
+ if (annotationTarget .kind ().equals (AnnotationTarget .Kind .CLASS )) {
132
+ aiServiceInterfaces .add (annotationTarget .asClass ().name ().toString ());
133
+ } else if (annotationTarget .kind ().equals (AnnotationTarget .Kind .METHOD )) {
134
+ MethodInfo method = annotationTarget .asMethod ();
135
+ aiServiceInterfaces .add (method .declaringClass ().name ().toString ());
136
+ if (!method .returnType ().kind ().equals (Type .Kind .VOID )) {
137
+ aiServiceTypes .add (method .returnType ().name ().toString ());
138
+ }
139
+ } else if (annotationTarget .kind ().equals (AnnotationTarget .Kind .METHOD_PARAMETER )) {
140
+ MethodParameterInfo methodParameter = annotationTarget .asMethodParameter ();
141
+ aiServiceTypes .add (methodParameter .type ().name ().toString ());
142
+
143
+ MethodInfo method = methodParameter .method ();
144
+ aiServiceInterfaces .add (method .declaringClass ().name ().toString ());
145
+ if (!method .returnType ().kind ().equals (Type .Kind .VOID )) {
146
+ aiServiceTypes .add (method .returnType ().name ().toString ());
147
+ }
148
+ }
149
+ }
150
+ }
151
+
152
+ // Any types participating in JsonExtractorOutputGuardrail operations require reflection
153
+ index .getAllKnownSubclasses (JsonExtractorOutputGuardrail .class )
154
+ .stream ()
155
+ .filter (classInfo -> classInfo .superClassType () != null )
156
+ .filter (classInfo -> classInfo .superClassType ().kind ().equals (Type .Kind .PARAMETERIZED_TYPE ))
157
+ .map (ClassInfo ::superClassType )
158
+ .map (Type ::asParameterizedType )
159
+ .flatMap (type -> type .arguments ().stream ())
160
+ .findFirst ()
161
+ .ifPresent (typeParameter -> {
162
+ aiServiceTypes .add (typeParameter .name ().toString ());
163
+ });
164
+
165
+ // AI service interfaces must be registered as native image proxies
166
+ aiServiceInterfaces
167
+ .stream ()
168
+ .map (NativeImageProxyDefinitionBuildItem ::new )
169
+ .forEach (nativeImageProxy ::produce );
170
+
171
+ // Register any types related to the AI service for reflection
172
+ reflectiveClass .produce (ReflectiveClassBuildItem .builder (aiServiceTypes .toArray (new String [0 ]))
173
+ .methods ()
174
+ .build ());
175
+
176
+ // Guardrails are instantiated dynamically
177
+ Set <String > guardrailTypes = index .getAllKnownImplementations (InputGuardrail .class )
178
+ .stream ()
179
+ .map (classInfo -> classInfo .name ().toString ())
180
+ .collect (Collectors .toSet ());
181
+
182
+ index .getAllKnownImplementations (OutputGuardrail .class )
183
+ .stream ()
184
+ .map (classInfo -> classInfo .name ().toString ())
185
+ .forEach (guardrailTypes ::add );
186
+
187
+ reflectiveClass .produce (ReflectiveClassBuildItem .builder (guardrailTypes .toArray (new String [0 ])).build ());
188
+ }
189
+
190
+ @ BuildStep
191
+ void registerLangChain4jNlpTypesForReflection (BuildProducer <ReflectiveClassBuildItem > reflectiveClass ) {
192
+ reflectiveClass .produce (ReflectiveClassBuildItem .builder (SentenceDetectorFactory .class ).build ());
60
193
}
61
194
62
195
@ BuildStep
63
196
RuntimeInitializedClassBuildItem runtimeInitializedClasses () {
64
197
return new RuntimeInitializedClassBuildItem ("dev.langchain4j.internal.RetryUtils" );
65
198
}
199
+
200
+ @ BuildStep
201
+ NativeImageResourcePatternsBuildItem nativeImageResources () {
202
+ return NativeImageResourcePatternsBuildItem .builder ()
203
+ .includeGlob ("opennlp/*.bin" )
204
+ .build ();
205
+ }
206
+
207
+ static Stream <ClassInfo > langChain4jTypesStream (Collection <ClassInfo > classes ) {
208
+ return classes .stream ()
209
+ .filter (classInfo -> classInfo .name ().toString ().startsWith ("dev.langchain4j" ));
210
+ }
66
211
}
0 commit comments