QuarkusMiddleTechnical

Что такое интеграция Quarkus с LangChain4j и как она поддерживает работу с AI-моделями?

quarkus-langchain4j интегрирует LangChain4j в CDI-контекст Quarkus: AI-сервисы объявляются через @RegisterAiService-интерфейсы, провайдер (OpenAI, Ollama и др.) настраивается в application.properties, поддерживаются ChatMemory, RAG и Tool Calling.

Quarkus LangChain4j

quarkus-langchain4j — расширение, интегрирующее библиотеку LangChain4j в Quarkus CDI-контекст. Позволяет декларативно описывать AI-сервисы как CDI-бины, абстрагируя провайдер (OpenAI, Azure OpenAI, Ollama, Hugging Face и др.).

Зависимости (Maven)

<dependency>
  <groupId>io.quarkiverse.langchain4j</groupId>
  <artifactId>quarkus-langchain4j-openai</artifactId>
  <version>0.20.0</version>
</dependency>
<!-- Для Ollama (локально): -->
<dependency>
  <groupId>io.quarkiverse.langchain4j</groupId>
  <artifactId>quarkus-langchain4j-ollama</artifactId>
  <version>0.20.0</version>
</dependency>

Конфигурация

# application.properties
quarkus.langchain4j.openai.api-key=${OPENAI_API_KEY}
quarkus.langchain4j.openai.chat-model.model-name=gpt-4o-mini
quarkus.langchain4j.openai.chat-model.temperature=0.3
quarkus.langchain4j.openai.timeout=30s
quarkus.langchain4j.log-requests=true
quarkus.langchain4j.log-responses=true

Декларативный AI-сервис

import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;
import io.quarkiverse.langchain4j.RegisterAiService;

@RegisterAiService
public interface ReviewAnalyzer {

    @SystemMessage("Ты — аналитик отзывов клиентов. Отвечай кратко на русском языке.")
    @UserMessage("Определи тональность отзыва: {review}. Ответь одним словом: positive/negative/neutral.")
    String analyzeSentiment(String review);
}

// Использование в ресурсе:
@Path("/reviews")
public class ReviewResource {
    @Inject ReviewAnalyzer analyzer;

    @POST
    public String analyze(String text) {
        return analyzer.analyzeSentiment(text);
    }
}

Чат с памятью (ChatMemory)

@RegisterAiService(chatMemoryProviderSupplier = RegisterAiService.BeanChatMemoryProviderSupplier.class)
public interface AssistantService {
    String chat(@MemoryId String userId, @UserMessage String message);
}

// Bean для хранения истории:
@ApplicationScoped
public class MemoryProvider implements ChatMemoryProvider {
    private final Map<String, ChatMemory> memories = new ConcurrentHashMap<>();

    @Override
    public ChatMemory get(Object memoryId) {
        return memories.computeIfAbsent(
            memoryId.toString(),
            id -> MessageWindowChatMemory.withMaxMessages(20)
        );
    }
}

RAG (Retrieval-Augmented Generation)

// Встроенное хранилище (dev/test)
@Inject EmbeddingStore<TextSegment> store;
@Inject EmbeddingModel embeddingModel;

public void ingestDocument(String text) {
    List<TextSegment> segments = new DocumentByParagraphSplitter(500, 50).split(
        Document.from(text)
    );
    List<Embedding> embeddings = embeddingModel.embedAll(segments).content();
    store.addAll(embeddings, segments);
}

// AI-сервис с retriever
@RegisterAiService(retrieverSupplier = RegisterAiService.BeanRetrieverSupplier.class)
public interface KnowledgeBot {
    String answer(@UserMessage String question);
}

Инструменты (Tool Calling)

@ApplicationScoped
public class WeatherTool {
    @Tool("Get current weather for a city")
    public String getWeather(String city) {
        return weatherApi.fetch(city); // реальный HTTP-вызов
    }
}

// AI-сервис автоматически получает инструмент через CDI
@RegisterAiService(tools = WeatherTool.class)
public interface WeatherBot {
    String chat(@UserMessage String message);
}

Подводные камни

  • В native-сборке модели, использующие рефлексию для десериализации JSON (structured output), требуют @RegisterForReflection на DTO-классах.
  • @RegisterAiService создаёт прокси через CDI — нельзя вызывать методы AI-сервиса из того же бина без инжекции (self-invocation не перехватывается).
  • Память (ChatMemory) по умолчанию in-memory — при перезапуске теряется; для production нужен Redis или DB-бэкенд.
  • Стриминг ответов (Multi<String>) требует Reactive-пути; смешивание с блокирующим кодом вызывает дедлок на IO-треде.
  • Токены тарифицируются за каждый запрос — без лимита MessageWindowChatMemory история растёт бесконечно и увеличивает стоимость.
  • quarkus.langchain4j.log-requests=true в production логирует чувствительные данные — отключить перед деплоем.
  • Версии quarkus-langchain4j и LangChain4j-core должны совпадать: используйте BOM quarkus-langchain4j-bom для согласованности.
  • Dev Services для Ollama стартуют Docker-контейнер автоматически в dev-режиме — убедитесь, что Docker доступен в CI.

Common mistakes

  • Путать термин «quarkus langchain4j» с соседним механизмом Quarkus.
  • Не называть границу lifecycle, transaction, thread или request для «quarkus langchain4j».
  • Игнорировать production-эффекты «quarkus langchain4j»: latency, SQL shape, memory, security или observability.

What the interviewer is testing

  • Попросить объяснить механизм «quarkus langchain4j» на минимальном примере.
  • Проверить, видит ли кандидат failure mode и диагностику для «quarkus langchain4j».
  • Уточнить, какие настройки или API меняют «quarkus langchain4j» в реальном сервисе.

Sources

Related topics