@herbertbeckman – LinkedIn
@rndtavares – LinkedIn
Zuverlässiger KI-Agent in der Produktion mit Java Quarkus Langchain4j – Teil 1 – KI als Service
Zuverlässiger KI-Agent in Java Quarkus Langchain4j prod – Teil 2 – Speicher (dieser Artikel)
Zuverlässiger KI-Agent in der Entwicklung mit Java Quarkus Langchain4j – Teil 3 – RAG (bald verfügbar)
Zuverlässiger KI-Agent in der Produktion mit Java Quarkus Langchain4j – Teil 4 – Leitplanken (in Kürze verfügbar)
Wenn wir einen Agenten erstellen, müssen wir bedenken, dass LLMs keinerlei Informationen speichern, das heißt, sie sind zustandslos. Damit unser Agent sich Informationen „merken“ kann, müssen wir eine Speicherverwaltung implementieren. Quarkus stellt uns bereits einen konfigurierten Standardspeicher zur Verfügung. Allerdings kann es Ihren Agenten buchstäblich lahmlegen, indem es den ihm zur Verfügung gestellten RAM-Speicher sprengt, wie in dieser Quarkus-Dokumentation beschrieben, wenn nicht die nötige Sorgfalt angewendet wird. Um dieses Problem nicht mehr zu haben und unseren Agenten auch in einer skalierbaren Umgebung nutzen zu können, benötigen wir einen ChatMemoryStore.
Wir nutzen einen Chat, um mit unserem Agenten zu interagieren, und es gibt wichtige Konzepte, die wir kennen müssen, damit unsere Interaktion mit ihm bestmöglich ablaufen kann und keine Fehler in der Produktion verursacht. Zuerst müssen wir die Arten von Nachrichten kennen, die wir verwenden, wenn wir mit ihm interagieren. Dies sind:
Benutzernachrichten: Die vom Endkunden gesendete Nachricht oder Anfrage. Wenn wir die Nachricht in Quarkus DevUI senden, senden wir immer eine UserMessage. Darüber hinaus wird es auch in den Ergebnissen von Tool-Aufrufen verwendet, die wir zuvor gesehen haben.
KI-Nachrichten (AiMessage): Die Antwortnachricht vom Modell. Immer wenn LLM unserem Agenten antwortet, erhält er eine Nachricht wie diese. Der Inhalt dieser Art von Nachricht wechselt zwischen einer Textantwort und Anforderungen zur Toolausführung.
SystemMessage: Diese Nachricht kann nur einmal definiert werden und befindet sich nur zur Entwicklungszeit.
Da Sie nun die drei Arten von Nachrichten kennen, die wir haben, erklären wir Ihnen anhand einiger Grafiken, wie sie sich verhalten sollten. Alle Grafiken stammen aus der Präsentation Java meets AI: Build LLM-Powered Apps with LangChain4j von Deandrea, Andrianakis, Escoffier, ich kann das Video wärmstens empfehlen.
Die erste Grafik zeigt die Verwendung der drei Nachrichtentypen. UserMessage in Blau, SystemMessage in Rot und AiMessage in Grün.
Diese zweite Grafik zeigt, wie „Speicher“ verwaltet werden sollte. Ein interessantes Detail ist, dass wir eine bestimmte Reihenfolge in den Nachrichten einhalten und einige Prämissen respektieren müssen.
Ein weiteres wichtiges Detail, auf das Sie achten sollten, ist die Größe Ihres ChatMemory. Je größer der Speicher Ihrer Interaktion, desto höher sind die Token-Kosten, da der LLM mehr Text verarbeiten muss, um eine Antwort bereitzustellen. Richten Sie dann ein Speicherfenster ein, das am besten zu Ihrem Anwendungsfall passt. Ein Tipp ist, die durchschnittliche Anzahl der Nachrichten Ihrer Kunden zu überprüfen, um eine Vorstellung vom Umfang der Interaktion zu bekommen. Wir zeigen die Implementierung durch MessageWindowChatMemory, die Klasse, die darauf spezialisiert ist, dies für uns in Langchain4j zu verwalten.
Jetzt, da wir all diese Konzepte und Prämissen kennen, machen wir uns die Hände schmutzig!
Hier verwenden wir MongoDB als ChatMemoryStore. Wir verwenden das MongoDB-Dokument und laden eine Instanz auf Docker hoch. Konfigurieren Sie es gerne nach Ihren Wünschen.
Beginnen wir mit dem Hinzufügen der notwendigen Abhängigkeit, um mithilfe von Quarkus eine Verbindung zu MongoDB herzustellen.
<dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-mongodb-panache</artifactId> </dependency>
Nach den Abhängigkeiten müssen wir die Verbindungseinstellungen in unserem src/main/resources/application.properties hinzufügen.
quarkus.mongodb.connection-string=mongodb://${MONGODB_USER}:${MONGODB_PASSWORD}@localhost:27017 quarkus.mongodb.database=chat_memory
Wir können unsere Verbindung zur Basis immer noch nicht testen, da wir zuerst unsere Entitäten und Repositorys erstellen müssen.
Jetzt implementieren wir unsere Interaktionsentität. Diese Entität wird unsere Liste der Nachrichten erstellen lassen. Immer wenn ein neuer Kunde eine Verbindung herstellt, wird eine neue Interaktion generiert. Wenn wir diese Interaktion wiederverwenden müssen, geben wir einfach denselben Interaktionsbezeichner ein.
package <seupacote>; import dev.langchain4j.data.message.ChatMessage; import io.quarkus.mongodb.panache.common.MongoEntity; import org.bson.codecs.pojo.annotations.BsonId; import java.util.List; import java.util.Objects; @MongoEntity(collection = "interactions") public class InteractionEntity { @BsonId private String interactionId; private List<ChatMessage> messages; public InteractionEntity() { } public InteractionEntity(String interactionId, List<ChatMessage> messages) { this.interactionId = interactionId; this.messages = messages; } public String getInteractionId() { return interactionId; } public void setInteractionId(String interactionId) { this.interactionId = interactionId; } public List<ChatMessage> getMessages() { return messages; } public void setMessages(List<ChatMessage> messages) { this.messages = messages; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; InteractionEntity that = (InteractionEntity) o; return Objects.equals(interactionId, that.interactionId); } @Override public int hashCode() { return Objects.hash(interactionId, messages); } }
Wir können jetzt unser Repository erstellen.
<dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-mongodb-panache</artifactId> </dependency>
Jetzt werden wir einige langchain4j-Komponenten implementieren, den ChatMemoryStore und den ChatMemoryProvider. ChatMemoryProvider ist die Klasse, die wir in unserem Agenten verwenden werden. Darin werden wir einen ChatMemoryStore hinzufügen, der unser Repository zum Speichern von Nachrichten in unserer MongoDB verwendet. Folgen Sie ChatMemoryStore:
quarkus.mongodb.connection-string=mongodb://${MONGODB_USER}:${MONGODB_PASSWORD}@localhost:27017 quarkus.mongodb.database=chat_memory
Der ChatMemoryProvider sieht folgendermaßen aus:
package <seupacote>; import dev.langchain4j.data.message.ChatMessage; import io.quarkus.mongodb.panache.common.MongoEntity; import org.bson.codecs.pojo.annotations.BsonId; import java.util.List; import java.util.Objects; @MongoEntity(collection = "interactions") public class InteractionEntity { @BsonId private String interactionId; private List<ChatMessage> messages; public InteractionEntity() { } public InteractionEntity(String interactionId, List<ChatMessage> messages) { this.interactionId = interactionId; this.messages = messages; } public String getInteractionId() { return interactionId; } public void setInteractionId(String interactionId) { this.interactionId = interactionId; } public List<ChatMessage> getMessages() { return messages; } public void setMessages(List<ChatMessage> messages) { this.messages = messages; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; InteractionEntity that = (InteractionEntity) o; return Objects.equals(interactionId, that.interactionId); } @Override public int hashCode() { return Objects.hash(interactionId, messages); } }
Beachten Sie den MessageWindowChatMemory. Hier implementieren wir das Nachrichtenfenster, das wir am Anfang des Artikels erwähnt haben. In der Methode maxMessages() müssen Sie sie auf die Zahl ändern, die Ihrer Meinung nach für Ihr Szenario am besten geeignet ist. Ich empfehle, die größte Anzahl von Nachrichten zu verwenden, die jemals in Ihrem Szenario vorhanden waren, oder den Durchschnitt zu verwenden. Hier definieren wir die beliebige Zahl 100.
Lassen Sie uns nun unseren Agenten ändern, um unseren neuen ChatMemoryProvider zu verwenden und MemoryId hinzuzufügen. Es sollte so aussehen:
package <seupacote>; import dev.langchain4j.data.message.ChatMessage; import io.quarkus.mongodb.panache.PanacheMongoRepositoryBase; import java.util.List; public class InteractionRepository implements PanacheMongoRepositoryBase<InteractionEntity, String> { public InteractionEntity findByInteractionId(String interactionId) { return findById(interactionId); } public void updateMessages(String interactionId, List<ChatMessage> messages) { persistOrUpdate(new InteractionEntity(interactionId, messages)); } public void deleteMessages(String interactionId) { deleteById(interactionId); } }
Dies sollte unseren AgentWSEndpoint beschädigen. Ändern wir es so, dass es die Interaktionskennung erhält und wir sie als unsere MemoryId verwenden können:
package <seupacote>; import dev.langchain4j.data.message.ChatMessage; import dev.langchain4j.store.memory.chat.ChatMemoryStore; import java.util.List; import java.util.Objects; public class MongoDBChatMemoryStore implements ChatMemoryStore { private InteractionRepository interactionRepository = new InteractionRepository(); @Override public List<ChatMessage> getMessages(Object memoryId) { var interactionEntity = interactionRepository.findByInteractionId(memoryId.toString()); return Objects.isNull(interactionEntity) ? List.of() : interactionEntity.getMessages(); } @Override public void updateMessages(Object memoryId, List<ChatMessage> messages) { interactionRepository.updateMessages(memoryId.toString(), messages); } @Override public void deleteMessages(Object memoryId) { interactionRepository.deleteMessages(memoryId.toString()); } }
Wir können unseren Agenten jetzt erneut testen. Dazu stellen wir einfach eine Verbindung zum Websocket her, indem wir jederzeit eine UUID übergeben. Sie können hier eine neue UUID generieren oder den Befehl uuidgen unter Linux verwenden.
Wenn wir den Test durchführen, erhalten Sie keine Antwort vom Agenten. Dies liegt daran, dass der Agent Probleme beim Schreiben unserer Nachrichten an MongoDB hat und Ihnen dies durch eine Ausnahme anzeigt. Damit wir prüfen können, ob diese Ausnahme auftritt, müssen wir unserer Datei src/main/resources/application.properties eine neue Eigenschaft hinzufügen. Dabei handelt es sich um die Protokollebene, die wir in Quarkus sehen möchten. Fügen Sie dann die folgende Zeile hinzu:
package <seupacote>; import dev.langchain4j.memory.chat.ChatMemoryProvider; import dev.langchain4j.memory.chat.MessageWindowChatMemory; import java.util.function.Supplier; public class MongoDBChatMemoryProvider implements Supplier<ChatMemoryProvider> { private MongoDBChatMemoryStore mongoDBChatMemoryStore = new MongoDBChatMemoryStore(); @Override public ChatMemoryProvider get() { return memoryId -> MessageWindowChatMemory.builder() .maxMessages(100) .id(memoryId) .chatMemoryStore(mongoDBChatMemoryStore) .build(); } }
Testen Sie jetzt den Agenten. Die Ausnahme sollte folgende sein:
package <seupacote>; import dev.langchain4j.service.SystemMessage; import dev.langchain4j.service.UserMessage; import io.quarkiverse.langchain4j.RegisterAiService; import io.quarkiverse.langchain4j.ToolBox; import jakarta.enterprise.context.ApplicationScoped; @ApplicationScoped @RegisterAiService( chatMemoryProviderSupplier = MongoDBChatMemoryProvider.class ) public interface Agent { @ToolBox(AgentTools.class) @SystemMessage(""" Você é um agente especializado em futebol brasileiro, seu nome é FutAgentBR Você sabe responder sobre os principais títulos dos principais times brasileiros e da seleção brasileira Sua resposta precisa ser educada, você pode deve responder em Português brasileiro e de forma relevante a pergunta feita Quando você não souber a resposta, responda que você não sabe responder nesse momento mas saberá em futuras versões. """) String chat(@MemoryId String interactionId, @UserMessage String message); }
Diese Ausnahme tritt auf, weil MongoDB die ChatMessage-Schnittstelle von Langchain4j nicht verarbeiten kann. Daher müssen wir einen Codec implementieren, um dies zu ermöglichen. Quarkus selbst bietet uns bereits einen Codec an, aber wir müssen klarstellen, dass wir ihn nutzen wollen. Anschließend erstellen wir die Klassen ChatMessageCodec und ChatMessageCodecProvider wie folgt:
package <seupacote>; import io.quarkus.websockets.next.OnTextMessage; import io.quarkus.websockets.next.WebSocket; import io.quarkus.websockets.next.WebSocketConnection; import jakarta.inject.Inject; import java.util.Objects; import java.util.UUID; @WebSocket(path = "/ws/{interactionId}") public class AgentWSEndpoint { private final Agent agent; private final WebSocketConnection connection; @Inject AgentWSEndpoint(Agent agent, WebSocketConnection connection) { this.agent = agent; this.connection = connection; } @OnTextMessage String reply(String message) { var interactionId = connection.pathParam("interactionId"); return agent.chat( Objects.isNull(interactionId) || interactionId.isBlank() ? UUID.randomUUID().toString() : interactionId, message ); } }
quarkus.log.level=DEBUG
Fertig! Jetzt können wir die Nachrichten in unserer MongoDB testen und verifizieren. Bei der Abfrage können wir die drei Nachrichtentypen im Nachrichtenarray des Dokuments überprüfen.
Damit endet der zweite Teil unserer Serie. Wir hoffen, es hat euch gefallen und wir sehen uns in Teil 3.
Das obige ist der detaillierte Inhalt vonZuverlässiger KI-Agent in Produktion mit Java Quarkus Langchain – Speicherteil. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!