Skip to content

Commit a322610

Browse files
authored
Merge pull request #1402 from jannikmaierhoefer/add-observability-docs-page
docs: add page on observability
2 parents 8471eeb + afe426b commit a322610

File tree

3 files changed

+287
-181
lines changed

3 files changed

+287
-181
lines changed

docs/modules/ROOT/nav.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,4 @@
4646
** xref:fault-tolerance.adoc[Fault Tolerance]
4747
** xref:websockets.adoc[WebSockets]
4848
** xref:enable-disable-integrations.adoc[Enabling and Disabling Integrations]
49+
** xref:observability.adoc[Observability]

docs/modules/ROOT/pages/ai-services.adoc

Lines changed: 4 additions & 181 deletions
Original file line numberDiff line numberDiff line change
@@ -334,187 +334,6 @@ public class MyCustomModerationSupplier implements Supplier<ModerationModel> {
334334
}
335335
----
336336

337-
== Observability
338-
339-
Observability is built into services created via `@RegisterAiService` and is provided in the following form:
340-
341-
* Metrics are enabled when `quarkus-micrometer` is part of the application
342-
* Traces are enabled when `quarkus-opentelemetry` is part of the application
343-
344-
=== Metrics
345-
346-
Each AI method is automatically timed and the timer data is available using the `langchain4j.aiservices.timed` metric with the appropriate tags.
347-
A counter is also available using the `langchain4j.aiservices.counted` metric.
348-
349-
For example, if the AI service looks like:
350-
351-
[source,java]
352-
----
353-
@RegisterAiService
354-
public interface PoemAiService {
355-
356-
@SystemMessage("You are a professional poet")
357-
@UserMessage("Write a poem about {topic}. The poem should be {lines} lines long")
358-
String writeAPoem(String topic, int lines);
359-
}
360-
----
361-
362-
and one chooses to use `quarkus-micrometer-registry-prometheus`, then the metrics could be:
363-
364-
[source]
365-
----
366-
# HELP langchain4j_aiservices_timed_seconds
367-
# TYPE langchain4j_aiservices_timed_seconds summary
368-
langchain4j_aiservices_timed_seconds_count{aiservice="PoemAiService",method="writeAPoem",} 1.0
369-
langchain4j_aiservices_timed_seconds_sum{aiservice="PoemAiService",method="writeAPoem",} 4.241446681
370-
# HELP langchain4j_aiservices_timed_seconds_max
371-
# TYPE langchain4j_aiservices_timed_seconds_max gauge
372-
langchain4j_aiservices_timed_seconds_max{aiservice="PoemAiService",method="writeAPoem",} 4.241446681
373-
374-
# HELP langchain4j_aiservices_counted_total
375-
# TYPE langchain4j_aiservices_counted_total counter
376-
langchain4j_aiservices_counted_total{aiservice="PoemAiService",exception="none",method="writeAPoem",result="success",} 1.0
377-
----
378-
379-
=== Tracing
380-
381-
Each AI method creates its own span using the `langchain4j.aiservices.$interface_name.$method_name` template for the name.
382-
Furthermore, tool invocations also create a span using `langchain4j.tools.$tool_name` template for the name.
383-
384-
385-
For example, if the AI service looks like:
386-
387-
[source,java]
388-
----
389-
@RegisterAiService(tools = EmailService.class)
390-
public interface PoemAiService {
391-
392-
@SystemMessage("You are a professional poet")
393-
@UserMessage("Write a poem about {topic}. The poem should be {lines} lines long. Then send this poem by email.")
394-
String writeAPoem(String topic, int lines);
395-
396-
}
397-
----
398-
399-
a tool that looks like:
400-
401-
[source,java]
402-
----
403-
@ApplicationScoped
404-
public class EmailService {
405-
406-
@Inject
407-
Mailer mailer;
408-
409-
@Tool("send the given content by email")
410-
public void sendAnEmail(String content) {
411-
Log.info("Sending an email: " + content);
412-
mailer.send(Mail.withText("[email protected]", "A poem for you", content));
413-
}
414-
415-
}
416-
----
417-
418-
and invocation of the AI service that looks like:
419-
420-
[source,java]
421-
----
422-
@Path("/email-me-a-poem")
423-
public class EmailMeAPoemResource {
424-
425-
private final MyAiService service;
426-
427-
public EmailMeAPoemResource(MyAiService service) {
428-
this.service = service;
429-
}
430-
431-
@GET
432-
public String emailMeAPoem() {
433-
return service.writeAPoem("Quarkus", 4);
434-
}
435-
436-
}
437-
----
438-
439-
then an example trace is:
440-
441-
image::trace.png[width=1000,align="center"]
442-
443-
In the trace above we can see the parent span which corresponds to the handling the GET HTTP request, but the real
444-
interesting thing is the `langchain4j.aiservices.MyAiService.writeAPoem` span which corresponds to the invocation of the AI service.
445-
The child spans of this span correspond (from to right) to calling the OpenAI API, invoking the `sendEmail` tool and finally invoking calling the OpenAI API again.
446-
447-
==== Custom span data
448-
if you have the need for custom span data, you can simply add a bean implemtenting `ChatModelSpanContributor`.
449-
[source,java]
450-
----
451-
import io.quarkiverse.langchain4j.runtime.listeners.ChatModelSpanContributor;
452-
import dev.langchain4j.model.chat.listener.ChatModelErrorContext;
453-
import dev.langchain4j.model.chat.listener.ChatModelRequestContext;
454-
import dev.langchain4j.model.chat.listener.ChatModelResponseContext;
455-
import io.opentelemetry.api.trace.Span;
456-
457-
@ApplicationScoped
458-
public class CustomSpanDataContributor implements ChatModelSpanContributor {
459-
public void onRequest(ChatModelRequestContext requestContext, Span currentSpan) {
460-
span.addAttribute("example", "request");
461-
}
462-
463-
public void onResponse(ChatModelResponseContext responseContext, Span currentSpan) {
464-
span.addAttribute("example", "response");
465-
}
466-
467-
default void onError(ChatModelErrorContext errorContext, Span currentSpan) {
468-
span.addAttribute("example", "failure");
469-
}
470-
}
471-
----
472-
473-
==== LangFuse
474-
475-
Traces can be exported to link:https://langfuse.com[LangFuse] simply by making the following configuration in `application.properties`:
476-
477-
[source,properties]
478-
----
479-
quarkus.otel.exporter.otlp.headers=Authorization=Basic <base64 of public:key>
480-
quarkus.otel.exporter.otlp.endpoint=https://cloud.langfuse.com/api/public/otel
481-
quarkus.otel.exporter.otlp.traces.protocol=http/protobuf
482-
----
483-
484-
=== Auditing
485-
486-
The extension allows users to audit the process of implementing an AiService by observing normal CDI events. The following example shows a class that audits all events.
487-
488-
NOTE: These methods do not all need to live in the same class and the name of the class and the methods do not matter. It is only shown this way for demonstration purposes.
489-
490-
[source,java]
491-
----
492-
@ApplicationScoped
493-
public class AuditingListener {
494-
public void initialMessagesCreated(@Observes InitialMessagesCreatedEvent initialMessagesCreatedEvent) {
495-
// Invoked when the original user and system messages have been created
496-
}
497-
498-
public void llmInteractionComplete(@Observes LLMInteractionCompleteEvent llmInteractionCompleteEvent) {
499-
// Invoked when the final result of the AiService method has been computed
500-
}
501-
502-
public void llmInteractionFailed(@Observes LLMInteractionFailureEvent llmInteractionFailureEvent) {
503-
// Invoked when there was an exception computing the result of the AiService method
504-
}
505-
506-
public void responseFromLLMReceived(@Observes ResponseFromLLMReceivedEvent responseFromLLMReceivedEvent) {
507-
// Invoked with a response from an LLM.
508-
// It is important to note that this can be invoked multiple times when tools exist.
509-
}
510-
511-
public void toolExecuted(@Observes ToolExecutedEvent toolExecutedEvent) {
512-
// Invoked with a tool response from an LLM.
513-
// It is important to note that this can be invoked multiple times when tools exist.
514-
}
515-
}
516-
----
517-
518337
== Working with images
519338

520339
An _AI Service_ can also be used when working with images, both to describe an image and to generate one.
@@ -551,3 +370,7 @@ Generating images with an _AI Service_ comes with restrictions compared to text
551370
* Although auditing does work, it is however limited
552371
* Guardrails only work on the input
553372
====
373+
374+
== Observability
375+
376+
Refer to link:https://docs.quarkiverse.io/quarkus-langchain4j/dev/observability.html[this page] to learn how to trace LangChain4j applications.

0 commit comments

Comments
 (0)