5454import io .quarkiverse .langchain4j .runtime .aiservice .AiServiceMethodImplementationSupport ;
5555import io .quarkiverse .langchain4j .runtime .aiservice .ChatMemoryRemovable ;
5656import io .quarkiverse .langchain4j .runtime .aiservice .DeclarativeAiServiceCreateInfo ;
57- import io .quarkiverse .langchain4j .runtime .aiservice .MetricsWrapper ;
57+ import io .quarkiverse .langchain4j .runtime .aiservice .MetricsCountedWrapper ;
58+ import io .quarkiverse .langchain4j .runtime .aiservice .MetricsTimedWrapper ;
5859import io .quarkiverse .langchain4j .runtime .aiservice .QuarkusAiServiceContext ;
5960import io .quarkiverse .langchain4j .runtime .aiservice .SpanWrapper ;
6061import io .quarkus .arc .Arc ;
@@ -95,6 +96,7 @@ public class AiServicesProcessor {
9596
9697 private static final DotName V = DotName .createSimple (V .class );
9798 public static final DotName MICROMETER_TIMED = DotName .createSimple ("io.micrometer.core.annotation.Timed" );
99+ public static final DotName MICROMETER_COUNTED = DotName .createSimple ("io.micrometer.core.annotation.Counted" );
98100 private static final String DEFAULT_DELIMITER = "\n " ;
99101 private static final Predicate <AnnotationInstance > IS_METHOD_PARAMETER_ANNOTATION = ai -> ai .target ()
100102 .kind () == AnnotationTarget .Kind .METHOD_PARAMETER ;
@@ -116,6 +118,7 @@ public class AiServicesProcessor {
116118 QuarkusAiServiceContext .class , "removeChatMemoryIds" , void .class , Object [].class );
117119 public static final DotName CDI_INSTANCE = DotName .createSimple (Instance .class );
118120 private static final String [] EMPTY_STRING_ARRAY = new String [0 ];
121+ private static final String METRICS_DEFAULT_NAME = "langchain4j.aiservices" ;
119122
120123 @ BuildStep
121124 public void nativeSupport (CombinedIndexBuildItem indexBuildItem ,
@@ -474,7 +477,8 @@ public void handleAiServices(AiServicesRecorder recorder,
474477 var addMicrometerMetrics = metricsCapability .isPresent ()
475478 && metricsCapability .get ().metricsSupported (MetricsFactory .MICROMETER );
476479 if (addMicrometerMetrics ) {
477- additionalBeanProducer .produce (AdditionalBeanBuildItem .builder ().addBeanClass (MetricsWrapper .class ).build ());
480+ additionalBeanProducer .produce (AdditionalBeanBuildItem .builder ().addBeanClass (MetricsTimedWrapper .class ).build ());
481+ additionalBeanProducer .produce (AdditionalBeanBuildItem .builder ().addBeanClass (MetricsCountedWrapper .class ).build ());
478482 }
479483
480484 var addOpenTelemetrySpan = capabilities .isPresent (Capability .OPENTELEMETRY_TRACER );
@@ -679,12 +683,15 @@ private AiServiceMethodCreateInfo gatherMethodMetadata(MethodInfo method, boolea
679683 AiServiceMethodCreateInfo .UserMessageInfo userMessageInfo = gatherUserMessageInfo (method , templateParams ,
680684 returnType );
681685 Optional <Integer > memoryIdParamPosition = gatherMemoryIdParamName (method );
682- Optional <AiServiceMethodCreateInfo .MetricsInfo > metricsInfo = gatherMetricsInfo (method , addMicrometerMetrics );
686+ Optional <AiServiceMethodCreateInfo .MetricsTimedInfo > metricsTimedInfo = gatherMetricsTimedInfo (method ,
687+ addMicrometerMetrics );
688+ Optional <AiServiceMethodCreateInfo .MetricsCountedInfo > metricsCountedInfo = gatherMetricsCountedInfo (method ,
689+ addMicrometerMetrics );
683690 Optional <AiServiceMethodCreateInfo .SpanInfo > spanInfo = gatherSpanInfo (method , addOpenTelemetrySpans );
684691
685692 return new AiServiceMethodCreateInfo (method .declaringClass ().name ().toString (), method .name (), systemMessageInfo ,
686693 userMessageInfo , memoryIdParamPosition , requiresModeration ,
687- returnType , metricsInfo , spanInfo );
694+ returnType , metricsTimedInfo , metricsCountedInfo , spanInfo );
688695 }
689696
690697 private List <TemplateParameterInfo > gatherTemplateParamInfo (List <MethodParameterInfo > params ) {
@@ -817,18 +824,14 @@ private AiServiceMethodCreateInfo.UserMessageInfo gatherUserMessageInfo(MethodIn
817824 }
818825 }
819826
820- private Optional <AiServiceMethodCreateInfo .MetricsInfo > gatherMetricsInfo (MethodInfo method ,
827+ private Optional <AiServiceMethodCreateInfo .MetricsTimedInfo > gatherMetricsTimedInfo (MethodInfo method ,
821828 boolean addMicrometerMetrics ) {
822829 if (!addMicrometerMetrics ) {
823830 return Optional .empty ();
824831 }
825832
826- String name = "langchain4j.aiservices" ;
827- List <String > tags = new ArrayList <>();
828- tags .add ("aiservice" );
829- tags .add (method .declaringClass ().name ().withoutPackagePrefix ());
830- tags .add ("method" );
831- tags .add (method .name ());
833+ String name = METRICS_DEFAULT_NAME ;
834+ List <String > tags = defaultMetricsTags (method );
832835
833836 AnnotationInstance timedInstance = method .annotation (MICROMETER_TIMED );
834837 if (timedInstance == null ) {
@@ -837,7 +840,7 @@ private Optional<AiServiceMethodCreateInfo.MetricsInfo> gatherMetricsInfo(Method
837840
838841 if (timedInstance == null ) {
839842 // we default to having all AiServices being timed
840- return Optional .of (new AiServiceMethodCreateInfo .MetricsInfo .Builder (name )
843+ return Optional .of (new AiServiceMethodCreateInfo .MetricsTimedInfo .Builder (name )
841844 .setExtraTags (tags .toArray (EMPTY_STRING_ARRAY )).build ());
842845 }
843846
@@ -849,7 +852,7 @@ private Optional<AiServiceMethodCreateInfo.MetricsInfo> gatherMetricsInfo(Method
849852 }
850853 }
851854
852- var builder = new AiServiceMethodCreateInfo .MetricsInfo .Builder (name );
855+ var builder = new AiServiceMethodCreateInfo .MetricsTimedInfo .Builder (name );
853856
854857 AnnotationValue extraTagsValue = timedInstance .value ("extraTags" );
855858 if (extraTagsValue != null ) {
@@ -880,6 +883,64 @@ private Optional<AiServiceMethodCreateInfo.MetricsInfo> gatherMetricsInfo(Method
880883 return Optional .of (builder .build ());
881884 }
882885
886+ private Optional <AiServiceMethodCreateInfo .MetricsCountedInfo > gatherMetricsCountedInfo (MethodInfo method ,
887+ boolean addMicrometerMetrics ) {
888+ if (!addMicrometerMetrics ) {
889+ return Optional .empty ();
890+ }
891+
892+ String name = METRICS_DEFAULT_NAME ;
893+ List <String > tags = defaultMetricsTags (method );
894+
895+ AnnotationInstance timedInstance = method .annotation (MICROMETER_COUNTED );
896+ if (timedInstance == null ) {
897+ timedInstance = method .declaringClass ().declaredAnnotation (MICROMETER_COUNTED );
898+ }
899+
900+ if (timedInstance == null ) {
901+ // we default to having all AiServices being timed
902+ return Optional .of (new AiServiceMethodCreateInfo .MetricsCountedInfo .Builder (name )
903+ .setExtraTags (tags .toArray (EMPTY_STRING_ARRAY )).build ());
904+ }
905+
906+ AnnotationValue nameValue = timedInstance .value ();
907+ if (nameValue != null ) {
908+ String nameStr = nameValue .asString ();
909+ if (nameStr != null && !nameStr .isEmpty ()) {
910+ name = nameStr ;
911+ }
912+ }
913+
914+ var builder = new AiServiceMethodCreateInfo .MetricsCountedInfo .Builder (name );
915+
916+ AnnotationValue extraTagsValue = timedInstance .value ("extraTags" );
917+ if (extraTagsValue != null ) {
918+ tags .addAll (Arrays .asList (extraTagsValue .asStringArray ()));
919+ }
920+ builder .setExtraTags (tags .toArray (EMPTY_STRING_ARRAY ));
921+
922+ AnnotationValue recordFailuresOnlyValue = timedInstance .value ("recordFailuresOnly" );
923+ if (recordFailuresOnlyValue != null ) {
924+ builder .setRecordFailuresOnly (recordFailuresOnlyValue .asBoolean ());
925+ }
926+
927+ AnnotationValue descriptionValue = timedInstance .value ("description" );
928+ if (descriptionValue != null ) {
929+ builder .setDescription (descriptionValue .asString ());
930+ }
931+
932+ return Optional .of (builder .build ());
933+ }
934+
935+ private List <String > defaultMetricsTags (MethodInfo method ) {
936+ List <String > tags = new ArrayList <>(4 );
937+ tags .add ("aiservice" );
938+ tags .add (method .declaringClass ().name ().withoutPackagePrefix ());
939+ tags .add ("method" );
940+ tags .add (method .name ());
941+ return tags ;
942+ }
943+
883944 private Optional <AiServiceMethodCreateInfo .SpanInfo > gatherSpanInfo (MethodInfo method ,
884945 boolean addOpenTelemetrySpans ) {
885946 if (!addOpenTelemetrySpans ) {
0 commit comments