Comprehensive developer toolkit providing reusable skills for Java/Spring Boot, TypeScript/NestJS/React/Next.js, Python, PHP, AWS CloudFormation, AI/RAG, DevOps, and more.
82
82%
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Risky
Do not use without reviewing
Detailed configuration options and advanced setup patterns for LangChain4j with Spring Boot.
application.yml
langchain4j:
# OpenAI Configuration
open-ai:
chat-model:
api-key: ${OPENAI_API_KEY}
model-name: gpt-4o-mini
temperature: 0.7
max-tokens: 1000
log-requests: true
log-responses: true
timeout: PT60S
max-retries: 3
organization: ${OPENAI_ORGANIZATION:}
embedding-model:
api-key: ${OPENAI_API_KEY}
model-name: text-embedding-3-small
dimensions: 1536
timeout: PT60S
streaming-chat-model:
api-key: ${OPENAI_API_KEY}
model-name: gpt-4o-mini
temperature: 0.7
max-tokens: 2000
# Azure OpenAI Configuration
azure-open-ai:
chat-model:
endpoint: ${AZURE_OPENAI_ENDPOINT}
api-key: ${AZURE_OPENAI_KEY}
deployment-name: gpt-4o
service-version: 2024-02-15-preview
temperature: 0.7
max-tokens: 1000
log-requests-and-responses: true
embedding-model:
endpoint: ${AZURE_OPENAI_ENDPOINT}
api-key: ${AZURE_OPENAI_KEY}
deployment-name: text-embedding-3-small
dimensions: 1536
# Anthropic Configuration
anthropic:
chat-model:
api-key: ${ANTHROPIC_API_KEY}
model-name: claude-3-5-sonnet-20241022
max-tokens: 4000
temperature: 0.7
streaming-chat-model:
api-key: ${ANTHROPIC_API_KEY}
model-name: claude-3-5-sonnet-20241022
# Ollama Configuration
ollama:
chat-model:
base-url: http://localhost:11434
model-name: llama3.1
temperature: 0.8
timeout: PT60S
# Memory Configuration
memory:
store-type: in-memory # in-memory, postgresql, mysql, mongodb
max-messages: 20
window-size: 10
# Vector Store Configuration
vector-store:
type: in-memory # in-memory, pinecone, weaviate, qdrant, postgresql
pinecone:
api-key: ${PINECONE_API_KEY}
index-name: my-index
namespace: production
qdrant:
host: localhost
port: 6333
collection-name: documents
weaviate:
host: localhost
port: 8080
collection-name: Documents
postgresql:
table: document_embeddings
dimension: 1536application-dev.yml
langchain4j:
open-ai:
chat-model:
api-key: ${OPENAI_API_KEY_DEV}
model-name: gpt-4o-mini
temperature: 0.8 # Higher temperature for experimentation
log-requests: true
log-responses: true
vector-store:
type: in-memoryapplication-prod.yml
langchain4j:
open-ai:
chat-model:
api-key: ${OPENAI_API_KEY_PROD}
model-name: gpt-4o
temperature: 0.3 # Lower temperature for consistency
log-requests: false
log-responses: false
vector-store:
type: pinecone
pinecone:
api-key: ${PINECONE_API_KEY_PROD}
index-name: production-knowledge-base@Configuration
@Profile("custom-openai")
public class CustomOpenAiConfiguration {
@Bean
@Primary
public ChatModel customOpenAiChatModel(
@Value("${custom.openai.api.key}") String apiKey,
@Value("${custom.openai.model}") String model,
@Value("${custom.openai.temperature}") Double temperature) {
OpenAiChatModelBuilder builder = OpenAiChatModel.builder()
.apiKey(apiKey)
.modelName(model)
.temperature(temperature);
if (Boolean.TRUE.equals(env.getProperty("custom.openai.log-requests", Boolean.class))) {
builder.logRequests(true);
}
if (Boolean.TRUE.equals(env.getProperty("custom.openai.log-responses", Boolean.class))) {
builder.logResponses(true);
}
return builder.build();
}
@Bean
@ConditionalOnProperty(name = "custom.openai.proxy.enabled", havingValue = "true")
public ChatModel proxiedChatModel(ChatModel delegate) {
return new ProxiedChatModel(delegate,
env.getProperty("custom.openai.proxy.url"),
env.getProperty("custom.openai.proxy.username"),
env.getProperty("custom.openai.proxy.password"));
}
}
class ProxiedChatModel implements ChatModel {
private final ChatModel delegate;
private final String proxyUrl;
private final String username;
private final String password;
public ProxiedChatModel(ChatModel delegate, String proxyUrl, String username, String password) {
this.delegate = delegate;
this.proxyUrl = proxyUrl;
this.username = username;
this.password = password;
}
@Override
public Response<AiMessage> generate(ChatRequest request) {
// Apply proxy configuration
// Make request through proxy
return delegate.generate(request);
}
}@Configuration
public class MultiProviderConfiguration {
@Bean("openAiChatModel")
public ChatModel openAiChatModel(
@Value("${openai.api.key}") String apiKey,
@Value("${openai.model.name}") String modelName) {
return OpenAiChatModel.builder()
.apiKey(apiKey)
.modelName(modelName)
.temperature(0.7)
.logRequests(env.acceptsProfiles("dev"))
.build();
}
@Bean("anthropicChatModel")
public ChatModel anthropicChatModel(
@Value("${anthropic.api.key}") String apiKey,
@Value("${anthropic.model.name}") String modelName) {
return AnthropicChatModel.builder()
.apiKey(apiKey)
.modelName(modelName)
.maxTokens(4000)
.build();
}
@Bean("ollamaChatModel")
@ConditionalOnProperty(name = "ollama.enabled", havingValue = "true")
public ChatModel ollamaChatModel(
@Value("${ollama.base-url}") String baseUrl,
@Value("${ollama.model.name}") String modelName) {
return OllamaChatModel.builder()
.baseUrl(baseUrl)
.modelName(modelName)
.temperature(0.8)
.build();
}
}@AiService(wiringMode = EXPLICIT, chatModel = "productionChatModel")
interface ProductionAssistant {
@SystemMessage("You are a production-grade AI assistant providing high-quality, reliable responses.")
String chat(String message);
}
@AiService(wiringMode = EXPLICIT, chatModel = "developmentChatModel")
interface DevelopmentAssistant {
@SystemMessage("You are a development assistant helping with code and debugging. " +
"Be experimental and creative in your responses.")
String chat(String message);
}
@AiService(wiringMode = EXPLICIT,
chatModel = "specializedChatModel",
tools = "businessTools")
interface SpecializedAssistant {
@SystemMessage("You are a specialized assistant with access to business tools. " +
"Use the available tools to provide accurate information.")
String chat(String message);
}
@Component("businessTools")
public class BusinessLogicTools {
@Tool("Calculate discount based on customer status")
public BigDecimal calculateDiscount(
@P("Purchase amount") BigDecimal amount,
@P("Customer status") String customerStatus) {
return switch (customerStatus.toLowerCase()) {
case "vip" -> amount.multiply(new BigDecimal("0.15"));
case "premium" -> amount.multiply(new BigDecimal("0.10"));
case "standard" -> amount.multiply(new BigDecimal("0.05"));
default -> BigDecimal.ZERO;
};
}
}@Configuration
@RequiredArgsConstructor
public class PostgresEmbeddingStoreConfiguration {
@Bean
public EmbeddingStore<TextSegment> postgresEmbeddingStore(
DataSource dataSource,
@Value("${spring.datasource.schema}") String schema) {
return PgVectorEmbeddingStore.builder()
.dataSource(dataSource)
.table("document_embeddings")
.dimension(1536)
.initializeSchema(true)
.schema(schema)
.indexName("document_embeddings_idx")
.build();
}
@Bean
public ContentRetriever postgresContentRetriever(
EmbeddingStore<TextSegment> embeddingStore,
EmbeddingModel embeddingModel) {
return EmbeddingStoreContentRetriever.builder()
.embeddingStore(embeddingStore)
.embeddingModel(embeddingModel)
.maxResults(5)
.minScore(0.7)
.build();
}
}@Configuration
@Profile("pinecone")
public class PineconeConfiguration {
@Bean
public EmbeddingStore<TextSegment> pineconeEmbeddingStore(
@Value("${pinecone.api.key}") String apiKey,
@Value("${pinecone.index.name}") String indexName,
@Value("${pinecone.namespace}") String namespace) {
PineconeEmbeddingStore store = PineconeEmbeddingStore.builder()
.apiKey(apiKey)
.indexName(indexName)
.namespace(namespace)
.build();
// Initialize if needed
if (!store.indexExists()) {
store.createIndex(1536);
}
return store;
}
}@Component
public class CustomEmbeddingStore implements EmbeddingStore<TextSegment> {
private final Map<UUID, TextSegment> embeddings = new ConcurrentHashMap<>();
private final Map<UUID, float[]> vectors = new ConcurrentHashMap<>();
@Override
public void add(Embedding embedding, TextSegment textSegment) {
UUID id = UUID.randomUUID();
embeddings.put(id, textSegment);
vectors.put(id, embedding.vector());
}
@Override
public void addAll(List<Embedding> embeddings, List<TextSegment> textSegments) {
for (int i = 0; i < embeddings.size(); i++) {
add(embeddings.get(i), textSegments.get(i));
}
}
@Override
public List<Embedding> findRelevant(Embedding embedding, int maxResults) {
return vectors.entrySet().stream()
.sorted(Comparator.comparingDouble(e -> cosineSimilarity(e.getValue(), embedding.vector())))
.limit(maxResults)
.map(e -> new EmbeddingImpl(e.getValue(), embeddings.get(e.getKey()).id()))
.collect(Collectors.toList());
}
private double cosineSimilarity(float[] vec1, float[] vec2) {
// Implementation of cosine similarity
return 0.0;
}
}@Configuration
public class MemoryConfiguration {
@Bean
@Profile("in-memory")
public ChatMemoryStore inMemoryChatMemoryStore() {
return new InMemoryChatMemoryStore();
}
@Bean
@Profile("database")
public ChatMemoryStore databaseChatMemoryStore(ChatMessageRepository messageRepository) {
return new DatabaseChatMemoryStore(messageRepository);
}
@Bean
public ChatMemoryProvider chatMemoryProvider(ChatMemoryStore memoryStore) {
return memoryId -> MessageWindowChatMemory.builder()
.id(memoryId)
.maxMessages(getMaxMessages())
.chatMemoryStore(memoryStore)
.build();
}
private int getMaxMessages() {
return env.getProperty("langchain4j.memory.max-messages", int.class, 20);
}
}@Component
@RequiredArgsConstructor
public class DatabaseChatMemoryStore implements ChatMemoryStore {
private final ChatMessageRepository repository;
@Override
public List<ChatMessage> getMessages(Object memoryId) {
return repository.findByMemoryIdOrderByCreatedAtAsc(memoryId.toString())
.stream()
.map(this::toMessage)
.collect(Collectors.toList());
}
@Override
public void updateMessages(Object memoryId, List<ChatMessage> messages) {
String id = memoryId.toString();
repository.deleteByMemoryId(id);
List<ChatMessageEntity> entities = messages.stream()
.map(msg -> toEntity(id, msg))
.collect(Collectors.toList());
repository.saveAll(entities);
}
private ChatMessage toMessage(ChatMessageEntity entity) {
return switch (entity.getMessageType()) {
case USER -> UserMessage.from(entity.getContent());
case AI -> AiMessage.from(entity.getContent());
case SYSTEM -> SystemMessage.from(entity.getContent());
};
}
private ChatMessageEntity toEntity(String memoryId, ChatMessage message) {
ChatMessageEntity entity = new ChatMessageEntity();
entity.setMemoryId(memoryId);
entity.setContent(message.text());
entity.setCreatedAt(LocalDateTime.now());
entity.setMessageType(determineMessageType(message));
return entity;
}
private MessageType determineMessageType(ChatMessage message) {
if (message instanceof UserMessage) return MessageType.USER;
if (message instanceof AiMessage) return MessageType.AI;
if (message instanceof SystemMessage) return MessageType.SYSTEM;
throw new IllegalArgumentException("Unknown message type: " + message.getClass());
}
}@Configuration
public class ObservabilityConfiguration {
@Bean
public ChatModelListener chatModelListener(MeterRegistry meterRegistry) {
return new MonitoringChatModelListener(meterRegistry);
}
@Bean
public HealthIndicator aiHealthIndicator(ChatModel chatModel) {
return new AiHealthIndicator(chatModel);
}
}
class MonitoringChatModelListener implements ChatModelListener {
private final MeterRegistry meterRegistry;
private final Counter requestCounter;
private final Timer responseTimer;
public MonitoringChatModelListener(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
this.requestCounter = Counter.builder("ai.requests.total")
.description("Total AI requests")
.register(meterRegistry);
this.responseTimer = Timer.builder("ai.response.duration")
.description("AI response time")
.register(meterRegistry);
}
@Override
public void onRequest(ChatModelRequestContext requestContext) {
requestCounter.increment();
logRequest(requestContext);
}
@Override
public void onResponse(ChatModelResponseContext responseContext) {
responseTimer.record(responseContext.duration());
logResponse(responseContext);
}
private void logRequest(ChatModelRequestContext requestContext) {
meterRegistry.gauge("ai.request.tokens",
requestContext.request().messages().size());
}
private void logResponse(ChatModelResponseContext responseContext) {
Response<AiMessage> response = responseContext.response();
meterRegistry.gauge("ai.response.tokens",
response.tokenUsage().totalTokenCount());
}
}@Component
@RequiredArgsConstructor
public class AiHealthIndicator implements HealthIndicator {
private final ChatModel chatModel;
private final EmbeddingModel embeddingModel;
@Override
public Health health() {
try {
// Test chat model
Health.Builder builder = Health.up();
String chatResponse = chatModel.chat("ping");
builder.withDetail("chat_model", "healthy");
if (chatResponse == null || chatResponse.trim().isEmpty()) {
return Health.down().withDetail("reason", "Empty response");
}
// Test embedding model
List<String> testTexts = List.of("test", "ping", "hello");
List<Embedding> embeddings = embeddingModel.embedAll(testTexts).content();
if (embeddings.isEmpty()) {
return Health.down().withDetail("reason", "No embeddings generated");
}
builder.withDetail("embedding_model", "healthy")
.withDetail("embedding_dimension", embeddings.get(0).vector().length);
return builder.build();
} catch (Exception e) {
return Health.down()
.withDetail("error", e.getMessage())
.withDetail("exception_class", e.getClass().getSimpleName());
}
}
}@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.requestMatchers("/api/ai/**").hasRole("AI_USER")
.requestMatchers("/actuator/ai/**").hasRole("AI_ADMIN")
.anyRequest().permitAll()
.and()
.httpBasic();
return http.build();
}
@Bean
public ApiKeyAuthenticationFilter apiKeyAuthenticationFilter() {
return new ApiKeyAuthenticationFilter("/api/ai/**");
}
}
class ApiKeyAuthenticationFilter extends OncePerRequestFilter {
private final String pathPrefix;
public ApiKeyAuthenticationFilter(String pathPrefix) {
this.pathPrefix = pathPrefix;
}
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
if (request.getRequestURI().startsWith(pathPrefix)) {
String apiKey = request.getHeader("X-API-Key");
if (apiKey == null || !isValidApiKey(apiKey)) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Invalid API key");
return;
}
}
filterChain.doFilter(request, response);
}
private boolean isValidApiKey(String apiKey) {
// Validate API key against database or security service
return true;
}
}@Component
@RequiredArgsConstructor
@Slf4j
public class AiConfigurationValidator implements InitializingBean {
private final AiProperties properties;
@Override
public void afterPropertiesSet() {
validateConfiguration();
}
private void validateConfiguration() {
if (properties.getOpenai() != null) {
validateOpenAiConfiguration();
}
if (properties.getAzureOpenAi() != null) {
validateAzureConfiguration();
}
if (properties.getAnthropic() != null) {
validateAnthropicConfiguration();
}
log.info("AI configuration validation completed successfully");
}
private void validateOpenAiConfiguration() {
OpenAiProperties openAi = properties.getOpenai();
if (openAi.getChatModel() != null &&
(openAi.getChatModel().getApiKey() == null ||
openAi.getChatModel().getApiKey().isEmpty())) {
log.warn("OpenAI chat model API key is not configured");
}
if (openAi.getChatModel() != null &&
openAi.getChatModel().getMaxTokens() != null &&
openAi.getChatModel().getMaxTokens() > 8192) {
log.warn("OpenAI max tokens {} exceeds recommended limit of 8192",
openAi.getChatModel().getMaxTokens());
}
}
private void validateAzureConfiguration() {
AzureOpenAiProperties azure = properties.getAzureOpenAi();
if (azure.getChatModel() != null &&
(azure.getChatModel().getEndpoint() == null ||
azure.getChatModel().getApiKey() == null)) {
log.error("Azure OpenAI endpoint or API key is not configured");
}
}
private void validateAnthropicConfiguration() {
AnthropicProperties anthropic = properties.getAnthropic();
if (anthropic.getChatModel() != null &&
(anthropic.getChatModel().getApiKey() == null ||
anthropic.getChatModel().getApiKey().isEmpty())) {
log.warn("Anthropic chat model API key is not configured");
}
}
}
@Configuration
@ConfigurationProperties(prefix = "langchain4j")
@Validated
@Data
public class AiProperties {
private OpenAiProperties openai;
private AzureOpenAiProperties azureOpenAi;
private AnthropicProperties anthropic;
private MemoryProperties memory;
private VectorStoreProperties vectorStore;
// Validation annotations for properties
}
@Data
@Validated
public class OpenAiProperties {
private ChatModelProperties chatModel;
private EmbeddingModelProperties embeddingModel;
private StreamingChatModelProperties streamingChatModel;
@Valid
@NotNull
public ChatModelProperties getChatModel() {
return chatModel;
}
}# application-dev.yml
langchain4j:
open-ai:
chat-model:
api-key: ${OPENAI_API_KEY_DEV}
model-name: gpt-4o-mini
temperature: 0.8
log-requests: true
log-responses: true
memory:
store-type: in-memory
max-messages: 10
vector-store:
type: in-memory
logging:
level:
dev.langchain4j: DEBUG
org.springframework.ai: DEBUG# application-prod.yml
langchain4j:
open-ai:
chat-model:
api-key: ${OPENAI_API_KEY_PROD}
model-name: gpt-4o
temperature: 0.3
log-requests: false
log-responses: false
max-tokens: 4000
memory:
store-type: postgresql
max-messages: 5
vector-store:
type: pinecone
pinecone:
index-name: production-knowledge-base
namespace: prod
logging:
level:
dev.langchain4j: WARN
org.springframework.ai: WARN
management:
endpoints:
web:
exposure:
include: health, metrics, info
endpoint:
health:
show-details: when-authorizedThis configuration guide provides comprehensive options for setting up LangChain4j with Spring Boot, covering various providers, storage backends, monitoring, and security considerations.
plugins
developer-kit-ai
skills
chunking-strategy
prompt-engineering
developer-kit-aws
skills
aws
aws-cli-beast
aws-cost-optimization
aws-drawio-architecture-diagrams
aws-sam-bootstrap
aws-cloudformation
aws-cloudformation-auto-scaling
references
aws-cloudformation-bedrock
references
aws-cloudformation-cloudfront
references
aws-cloudformation-cloudwatch
references
aws-cloudformation-dynamodb
references
aws-cloudformation-ec2
aws-cloudformation-ecs
references
aws-cloudformation-elasticache
aws-cloudformation-iam
references
aws-cloudformation-lambda
references
aws-cloudformation-rds
aws-cloudformation-s3
references
aws-cloudformation-security
references
aws-cloudformation-task-ecs-deploy-gh
aws-cloudformation-vpc
developer-kit-core
skills
developer-kit-java
skills
aws-lambda-java-integration
aws-rds-spring-boot-integration
aws-sdk-java-v2-bedrock
aws-sdk-java-v2-core
aws-sdk-java-v2-dynamodb
aws-sdk-java-v2-kms
aws-sdk-java-v2-lambda
aws-sdk-java-v2-messaging
aws-sdk-java-v2-rds
aws-sdk-java-v2-s3
aws-sdk-java-v2-secrets-manager
graalvm-native-image
langchain4j
langchain4j-mcp-server-patterns
langchain4j-ai-services-patterns
references
langchain4j-mcp-server-patterns
references
langchain4j-rag-implementation-patterns
references
langchain4j-spring-boot-integration
langchain4j-testing-strategies
langchain4j-tool-function-calling-patterns
langchain4j-vector-stores-configuration
references
qdrant
references
spring-ai-mcp-server-patterns
references
spring-boot-actuator
spring-boot-cache
spring-boot-crud-patterns
spring-boot-dependency-injection
spring-boot-event-driven-patterns
spring-boot-openapi-documentation
spring-boot-project-creator
spring-boot-resilience4j
spring-boot-rest-api-standards
spring-boot-saga-pattern
spring-boot-security-jwt
assets
references
scripts
spring-boot-test-patterns
spring-data-jpa
references
spring-data-neo4j
references
unit-test-application-events
unit-test-bean-validation
unit-test-boundary-conditions
unit-test-caching
unit-test-config-properties
unit-test-controller-layer
unit-test-exception-handler
unit-test-json-serialization
unit-test-mapper-converter
unit-test-parameterized
unit-test-scheduled-async
unit-test-service-layer
unit-test-utility-methods
unit-test-wiremock-rest-api
developer-kit-php
skills
aws-lambda-php-integration
developer-kit-python
skills
aws-lambda-python-integration
developer-kit-tools
developer-kit-typescript
skills
aws-lambda-typescript-integration
better-auth
drizzle-orm-patterns
dynamodb-toolbox-patterns
references
nestjs
nestjs-best-practices
nestjs-code-review
nestjs-drizzle-crud-generator
scripts
nextjs-app-router
nextjs-authentication
nextjs-code-review
nextjs-data-fetching
references
nextjs-deployment
nextjs-performance
nx-monorepo
react-code-review
react-patterns
references
shadcn-ui
tailwind-css-patterns
references
tailwind-design-system
references
turborepo-monorepo
typescript-docs
typescript-security-review
zod-validation-utilities