55import java .util .Optional ;
66import java .util .function .Consumer ;
77
8+ import jakarta .enterprise .event .Observes ;
9+ import jakarta .inject .Singleton ;
10+
811import org .jboss .jandex .AnnotationInstance ;
912import org .jboss .jandex .AnnotationTransformation ;
1013import org .jboss .jandex .AnnotationTransformation .TransformationContext ;
1114import org .jboss .jandex .IndexView ;
1215import org .jboss .logging .Logger ;
1316
14- import io .quarkiverse .langchain4j .deployment .GuardrailObservabilityProcessorSupport .TransformType ;
17+ import dev .langchain4j .observability .api .event .InputGuardrailExecutedEvent ;
18+ import dev .langchain4j .observability .api .event .OutputGuardrailExecutedEvent ;
1519import io .quarkus .arc .deployment .AnnotationsTransformerBuildItem ;
20+ import io .quarkus .arc .deployment .GeneratedBeanBuildItem ;
21+ import io .quarkus .arc .deployment .GeneratedBeanGizmoAdaptor ;
1622import io .quarkus .deployment .Capabilities ;
1723import io .quarkus .deployment .Capability ;
1824import io .quarkus .deployment .annotations .BuildProducer ;
1925import io .quarkus .deployment .annotations .BuildStep ;
2026import io .quarkus .deployment .builditem .CombinedIndexBuildItem ;
2127import io .quarkus .deployment .metrics .MetricsCapabilityBuildItem ;
28+ import io .quarkus .gizmo .ClassCreator ;
29+ import io .quarkus .gizmo .ClassOutput ;
30+ import io .quarkus .gizmo .MethodCreator ;
31+ import io .quarkus .gizmo .MethodDescriptor ;
2232import io .quarkus .runtime .metrics .MetricsFactory ;
2333
2434/**
2838 * <p>
2939 * The main capabilities include:
3040 * <ul>
31- * <li>Applying Micrometer's `@Timed` and `@Counted` annotations to monitor method execution.</li>
41+ * <li>Registering the guardrail metric observer that collect metrics about guardrail execution</li>
42+ * <li>Applying Micrometer's `@Timed` and `@Counted` annotations to monitor method execution. (deprecated)</li>
3243 * <li>Adding OpenTelemetry's `@WithSpan` annotation for distributed tracing.</li>
3344 * </ul>
3445 */
46+ @ SuppressWarnings ("OptionalUsedAsFieldOrParameterType" )
3547public class GuardrailObservabilityProcessor {
3648 private static final Logger LOG = Logger .getLogger (GuardrailObservabilityProcessor .class );
49+ public static final String GUARDRAIL_METRICS_OBSERVER_SUPPORT_CLASS = "io.quarkiverse.langchain4j.runtime.observability.GuardrailMetricsObserverSupport" ;
50+
51+ /**
52+ * Metrics collection must only be enabled when Micrometer is available.
53+ * In practice, however, Arc always registers observers, regardless of whether
54+ * Micrometer is on the classpath or whether an observer bean is actually
55+ * registered. As a result, an observer may attempt to record metrics when
56+ * Micrometer is missing, which can trigger runtime errors and even break
57+ * native image compilation.
58+ * <p>
59+ * To prevent such failures, this method conditionally generates a bean when
60+ * Micrometer support is detected.
61+ * The generation logic depends on: {@code io.quarkiverse.langchain4j.runtime.observability.GuardrailMetricsObserverSupport}
62+ * *
63+ * </p>
64+ *
65+ * @param metricsCapability the metrics capability build item
66+ * @param generatedBean the generated bean build item producer
67+ */
68+ @ BuildStep
69+ void addMetricObserver (
70+ Optional <MetricsCapabilityBuildItem > metricsCapability ,
71+ BuildProducer <GeneratedBeanBuildItem > generatedBean ) {
72+
73+ if (metricsCapability .isPresent () && metricsCapability .get ().metricsSupported (MetricsFactory .MICROMETER )) {
74+ LOG .debug ("Generating GuardrailMetricsObserver bean for guardrail metrics collection" );
75+ ClassOutput output = new GeneratedBeanGizmoAdaptor (generatedBean );
76+ ClassCreator .Builder classCreatorBuilder = ClassCreator .builder ()
77+ .classOutput (output )
78+ .className ("io.quarkiverse.langchain4j.runtime.observability.GuardrailMetricsObserver" );
79+ try (ClassCreator classCreator = classCreatorBuilder .build ()) {
80+ classCreator .addAnnotation (Singleton .class );
81+ MethodCreator onInputGuardrailExecuted = classCreator .getMethodCreator ("onInputGuardrailExecuted" , "V" ,
82+ InputGuardrailExecutedEvent .class );
83+ onInputGuardrailExecuted .getParameterAnnotations (0 ).addAnnotation (Observes .class );
84+ var support1 = MethodDescriptor .ofMethod (
85+ GUARDRAIL_METRICS_OBSERVER_SUPPORT_CLASS ,
86+ "onInputGuardrailExecuted" , "V" , InputGuardrailExecutedEvent .class );
87+ onInputGuardrailExecuted .invokeStaticMethod (support1 , onInputGuardrailExecuted .getMethodParam (0 ));
88+ onInputGuardrailExecuted .returnVoid ();
89+
90+ MethodCreator onOutputGuardrailExecuted = classCreator .getMethodCreator ("onOutputGuardrailExecuted" , "V" ,
91+ OutputGuardrailExecutedEvent .class );
92+ onOutputGuardrailExecuted .getParameterAnnotations (0 ).addAnnotation (Observes .class );
93+ var support2 = MethodDescriptor .ofMethod (
94+ GUARDRAIL_METRICS_OBSERVER_SUPPORT_CLASS ,
95+ "onOutputGuardrailExecuted" , "V" , OutputGuardrailExecutedEvent .class );
96+ onOutputGuardrailExecuted .invokeStaticMethod (support2 , onOutputGuardrailExecuted .getMethodParam (0 ));
97+ onOutputGuardrailExecuted .returnVoid ();
98+ }
99+ }
100+
101+ }
37102
103+ /**
104+ * @deprecated These metrics are now collected via the GuardrailMetricsObserver bean.
105+ */
106+ @ Deprecated
38107 @ BuildStep
39108 void transformWithMetrics (Optional <MetricsCapabilityBuildItem > metricsCapability ,
40109 CombinedIndexBuildItem indexBuildItem ,
@@ -48,11 +117,11 @@ void transformWithMetrics(Optional<MetricsCapabilityBuildItem> metricsCapability
48117 AnnotationInstance .builder (MICROMETER_COUNTED )
49118 .add ("value" , "guardrail.invoked" )
50119 .add ("description" ,
51- "Measures the number of times this guardrail was invoked" )
120+ "Measures the number of times this guardrail was invoked (deprecated) " )
52121 .build (),
53122 AnnotationInstance .builder (MICROMETER_TIMED )
54123 .add ("value" , "guardrail.timed" )
55- .add ("description" , "Measures the runtime of this guardrail" )
124+ .add ("description" , "Measures the runtime of this guardrail (deprecated) " )
56125 .add ("percentiles" , new double [] { 0.75 , 0.95 , 0.99 })
57126 .add ("histogram" , true )
58127 .build ()),
0 commit comments