2323import java .util .function .Predicate ;
2424import java .util .stream .Collectors ;
2525
26+ import jakarta .annotation .PreDestroy ;
27+ import jakarta .enterprise .context .Dependent ;
2628import jakarta .enterprise .inject .Instance ;
29+ import jakarta .inject .Inject ;
2730
2831import org .jboss .jandex .AnnotationInstance ;
2932import org .jboss .jandex .AnnotationTarget ;
5053import io .quarkiverse .langchain4j .runtime .aiservice .AiServiceMethodCreateInfo ;
5154import io .quarkiverse .langchain4j .runtime .aiservice .AiServiceMethodImplementationSupport ;
5255import io .quarkiverse .langchain4j .runtime .aiservice .ChatMemoryRemovable ;
53- import io .quarkiverse .langchain4j .runtime .aiservice .DeclarativeAiServiceBeanDestroyer ;
5456import io .quarkiverse .langchain4j .runtime .aiservice .DeclarativeAiServiceCreateInfo ;
5557import io .quarkiverse .langchain4j .runtime .aiservice .MetricsWrapper ;
5658import io .quarkiverse .langchain4j .runtime .aiservice .QuarkusAiServiceContext ;
5961import io .quarkus .arc .ArcContainer ;
6062import io .quarkus .arc .InstanceHandle ;
6163import io .quarkus .arc .deployment .AdditionalBeanBuildItem ;
64+ import io .quarkus .arc .deployment .GeneratedBeanBuildItem ;
65+ import io .quarkus .arc .deployment .GeneratedBeanGizmoAdaptor ;
6266import io .quarkus .arc .deployment .SyntheticBeanBuildItem ;
6367import io .quarkus .arc .deployment .UnremovableBeanBuildItem ;
6468import io .quarkus .arc .processor .BuiltinScope ;
@@ -311,16 +315,18 @@ public void handleDeclarativeServices(AiServicesRecorder recorder,
311315 : null );
312316
313317 SyntheticBeanBuildItem .ExtendedBeanConfigurator configurator = SyntheticBeanBuildItem
314- .configure (declarativeAiServiceClassInfo . name () )
318+ .configure (QuarkusAiServiceContext . class )
315319 .createWith (recorder .createDeclarativeAiService (
316320 new DeclarativeAiServiceCreateInfo (serviceClassName , chatLanguageModelSupplierClassName ,
317321 toolClassNames , chatMemoryProviderSupplierClassName ,
318322 retrieverSupplierClassName ,
319323 auditServiceClassSupplierName ,
320324 moderationModelSupplierClassName )))
321- .destroyer (DeclarativeAiServiceBeanDestroyer .class )
322325 .setRuntimeInit ()
323- .scope (bi .getCdiScope ());
326+ .addQualifier ()
327+ .annotation (Langchain4jDotNames .QUARKUS_AI_SERVICE_CONTEXT_QUALIFIER ).addValue ("value" , serviceClassName )
328+ .done ()
329+ .scope (Dependent .class );
324330 if ((chatLanguageModelSupplierClassName == null ) && selectedChatModelProvider .isPresent ()) { // TODO: is second condition needed?
325331 configurator .addInjectionPoint (ClassType .create (Langchain4jDotNames .CHAT_MODEL ));
326332 needsChatModelBean = true ;
@@ -392,6 +398,7 @@ public void handleAiServices(AiServicesRecorder recorder,
392398 CombinedIndexBuildItem indexBuildItem ,
393399 List <DeclarativeAiServiceBuildItem > declarativeAiServiceItems ,
394400 BuildProducer <GeneratedClassBuildItem > generatedClassProducer ,
401+ BuildProducer <GeneratedBeanBuildItem > generatedBeanProducer ,
395402 BuildProducer <ReflectiveClassBuildItem > reflectiveClassProducer ,
396403 BuildProducer <AiServicesMethodBuildItem > aiServicesMethodProducer ,
397404 BuildProducer <AdditionalBeanBuildItem > additionalBeanProducer ,
@@ -476,7 +483,8 @@ public void handleAiServices(AiServicesRecorder recorder,
476483
477484 Map <String , AiServiceClassCreateInfo > perClassMetadata = new HashMap <>();
478485 if (!ifacesForCreate .isEmpty ()) {
479- ClassOutput classOutput = new GeneratedClassGizmoAdaptor (generatedClassProducer , true );
486+ ClassOutput generatedClassOutput = new GeneratedClassGizmoAdaptor (generatedClassProducer , true );
487+ ClassOutput generatedBeanOutput = new GeneratedBeanGizmoAdaptor (generatedBeanProducer );
480488 for (ClassInfo iface : ifacesForCreate ) {
481489 Set <MethodInfo > allMethods = new HashSet <>(iface .methods ());
482490 JandexUtil .getAllSuperinterfaces (iface , index ).forEach (ci -> allMethods .addAll (ci .methods ()));
@@ -497,13 +505,22 @@ public void handleAiServices(AiServicesRecorder recorder,
497505 boolean isRegisteredService = registeredAiServiceClassNames .contains (ifaceName );
498506
499507 ClassCreator .Builder classCreatorBuilder = ClassCreator .builder ()
500- .classOutput (classOutput )
508+ .classOutput (isRegisteredService ? generatedBeanOutput : generatedClassOutput )
501509 .className (implClassName )
502510 .interfaces (ifaceName , ChatMemoryRemovable .class .getName ());
503511 if (isRegisteredService ) {
504512 classCreatorBuilder .interfaces (AutoCloseable .class );
505513 }
506514 try (ClassCreator classCreator = classCreatorBuilder .build ()) {
515+ if (isRegisteredService ) {
516+ // we need to make this a bean, so we need to add the proper scope annotation
517+ ScopeInfo scopeInfo = declarativeAiServiceItems .stream ()
518+ .filter (bi -> bi .getServiceClassInfo ().equals (iface ))
519+ .findFirst ().orElseThrow (() -> new IllegalStateException (
520+ "Unable to determine the CDI scope of " + iface ))
521+ .getCdiScope ();
522+ classCreator .addAnnotation (scopeInfo .getDotName ().toString ());
523+ }
507524
508525 FieldDescriptor contextField = classCreator .getFieldCreator ("context" , QuarkusAiServiceContext .class )
509526 .setModifiers (Modifier .PRIVATE | Modifier .FINAL )
@@ -516,37 +533,67 @@ public void handleAiServices(AiServicesRecorder recorder,
516533 String methodId = createMethodId (methodInfo );
517534 perMethodMetadata .put (methodId ,
518535 gatherMethodMetadata (methodInfo , addMicrometerMetrics , addOpenTelemetrySpan ));
519- MethodCreator constructor = classCreator .getMethodCreator (MethodDescriptor .INIT , "V" ,
520- QuarkusAiServiceContext .class );
521- constructor .invokeSpecialMethod (OBJECT_CONSTRUCTOR , constructor .getThis ());
522- constructor .writeInstanceField (contextField , constructor .getThis (), constructor .getMethodParam (0 ));
523- constructor .returnValue (null );
524-
525- MethodCreator mc = classCreator .getMethodCreator (MethodDescriptor .of (methodInfo ));
526- ResultHandle contextHandle = mc .readInstanceField (contextField , mc .getThis ());
527- ResultHandle methodCreateInfoHandle = mc .invokeStaticMethod (RECORDER_METHOD_CREATE_INFO ,
528- mc .load (ifaceName ),
529- mc .load (methodId ));
530- ResultHandle paramsHandle = mc .newArray (Object .class , methodInfo .parametersCount ());
531- for (int i = 0 ; i < methodInfo .parametersCount (); i ++) {
532- mc .writeArrayValue (paramsHandle , i , mc .getMethodParam (i ));
536+ {
537+ MethodCreator ctor = classCreator .getMethodCreator (MethodDescriptor .INIT , "V" ,
538+ QuarkusAiServiceContext .class );
539+ ctor .setModifiers (Modifier .PUBLIC );
540+ ctor .addAnnotation (Inject .class );
541+ ctor .getParameterAnnotations (0 )
542+ .addAnnotation (Langchain4jDotNames .QUARKUS_AI_SERVICE_CONTEXT_QUALIFIER .toString ())
543+ .add ("value" , ifaceName );
544+ ctor .invokeSpecialMethod (OBJECT_CONSTRUCTOR , ctor .getThis ());
545+ ctor .writeInstanceField (contextField , ctor .getThis (),
546+ ctor .getMethodParam (0 ));
547+ ctor .returnValue (null );
533548 }
534549
535- ResultHandle supportHandle = getFromCDI (mc , AiServiceMethodImplementationSupport .class .getName ());
536- ResultHandle inputHandle = mc .newInstance (
537- MethodDescriptor .ofConstructor (AiServiceMethodImplementationSupport .Input .class ,
538- QuarkusAiServiceContext .class , AiServiceMethodCreateInfo .class , Object [].class ),
539- contextHandle , methodCreateInfoHandle , paramsHandle );
540-
541- ResultHandle resultHandle = mc .invokeVirtualMethod (SUPPORT_IMPLEMENT , supportHandle , inputHandle );
542- mc .returnValue (resultHandle );
550+ {
551+ MethodCreator noArgsCtor = classCreator .getMethodCreator (MethodDescriptor .INIT , "V" );
552+ noArgsCtor .setModifiers (Modifier .PUBLIC );
553+ noArgsCtor .invokeSpecialMethod (OBJECT_CONSTRUCTOR , noArgsCtor .getThis ());
554+ noArgsCtor .writeInstanceField (contextField , noArgsCtor .getThis (), noArgsCtor .loadNull ());
555+ noArgsCtor .returnValue (null );
556+ }
543557
544- aiServicesMethodProducer .produce (new AiServicesMethodBuildItem (methodInfo ));
558+ { // actual method we need to implement
559+ MethodCreator mc = classCreator .getMethodCreator (MethodDescriptor .of (methodInfo ));
560+
561+ // copy annotations
562+ for (AnnotationInstance annotationInstance : methodInfo .declaredAnnotations ()) {
563+ // TODO: we need to review this
564+ if (annotationInstance .name ().toString ()
565+ .startsWith ("org.eclipse.microprofile.faulttolerance" )) {
566+ mc .addAnnotation (annotationInstance );
567+ }
568+ }
569+
570+ ResultHandle contextHandle = mc .readInstanceField (contextField , mc .getThis ());
571+ ResultHandle methodCreateInfoHandle = mc .invokeStaticMethod (RECORDER_METHOD_CREATE_INFO ,
572+ mc .load (ifaceName ),
573+ mc .load (methodId ));
574+ ResultHandle paramsHandle = mc .newArray (Object .class , methodInfo .parametersCount ());
575+ for (int i = 0 ; i < methodInfo .parametersCount (); i ++) {
576+ mc .writeArrayValue (paramsHandle , i , mc .getMethodParam (i ));
577+ }
578+
579+ ResultHandle supportHandle = getFromCDI (mc , AiServiceMethodImplementationSupport .class .getName ());
580+ ResultHandle inputHandle = mc .newInstance (
581+ MethodDescriptor .ofConstructor (AiServiceMethodImplementationSupport .Input .class ,
582+ QuarkusAiServiceContext .class , AiServiceMethodCreateInfo .class ,
583+ Object [].class ),
584+ contextHandle , methodCreateInfoHandle , paramsHandle );
585+
586+ ResultHandle resultHandle = mc .invokeVirtualMethod (SUPPORT_IMPLEMENT , supportHandle , inputHandle );
587+ mc .returnValue (resultHandle );
588+
589+ aiServicesMethodProducer .produce (new AiServicesMethodBuildItem (methodInfo ));
590+ }
545591 }
546592
547593 if (isRegisteredService ) {
548594 MethodCreator mc = classCreator .getMethodCreator (
549595 MethodDescriptor .ofMethod (implClassName , "close" , void .class ));
596+ mc .addAnnotation (PreDestroy .class );
550597 ResultHandle contextHandle = mc .readInstanceField (contextField , mc .getThis ());
551598 mc .invokeVirtualMethod (QUARKUS_AI_SERVICES_CONTEXT_CLOSE , contextHandle );
552599 mc .returnVoid ();
0 commit comments