diff --git a/README.md b/README.md index 4071a31..b5f4cd5 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ A Claude Code plugin marketplace with 3 focused plugins for Java developers. All | Plugin | Skills | Commands | Agents | Install when | |---|---|---|---|---| | `java-core` | 14 | 2 | `java-architect`, `java-build-resolver` | Every Java project | -| `java-spring` | 6 | 2 | `java-spring-expert` | Spring Boot projects | +| `java-spring` | 7 | 2 | `java-spring-expert` | Spring Boot projects | | `java-quality` | 3 | 1 | `java-security-reviewer`, `java-performance-reviewer`, `java-test-engineer` | Quality enforcement | ## Quick Setup (5 minutes) @@ -75,6 +75,7 @@ Skills activate automatically based on context, or invoke them explicitly. | `/java-spring:java-crud` | Generate a complete CRUD feature in an existing project | | `/java-spring:java-security` | Review or generate Spring Security config — JWT, OAuth2, method security, CORS (Boot 2.x & 3.x) | | `/java-spring:java-openapi` | Generate or review OpenAPI/Swagger docs — `@Tag`, `@Operation`, `@Schema`, JWT auth scheme (springdoc v1/v2) | +| `/java-spring:java-spring-ai` | Add AI features to Spring Boot — ChatClient, RAG, tool calling, memory (Spring AI 1.x / LangChain4J) | ### java-quality diff --git a/plugins/java-spring/skills/java-spring-ai/SKILL.md b/plugins/java-spring/skills/java-spring-ai/SKILL.md new file mode 100644 index 0000000..4a896bb --- /dev/null +++ b/plugins/java-spring/skills/java-spring-ai/SKILL.md @@ -0,0 +1,140 @@ +--- +name: java-spring-ai +description: Use when the user asks to add AI features, integrate Spring AI or LangChain4J, build a chatbot, implement RAG (retrieval-augmented generation), use vector stores, stream LLM responses, or call AI tools/functions in a Spring Boot project. +version: 1.0.0 +authors: [java-plugins contributors] +tags: [java, spring-boot, spring-ai, langchain4j, llm, rag, vector-store, ai] +allowed-tools: [Read, Glob, Grep, Edit, Write] +--- + +# Spring AI / LangChain4J Skill + +Detect the framework in use, then apply the correct patterns. + +## Step 1 — Detect framework and version + +Check `pom.xml` or `build.gradle`: +- `spring-ai-*` dependency → **Spring AI** (note version: 1.0.x GA or 0.8.x milestone) +- `langchain4j-*` dependency → **LangChain4J** (note version: 0.x or 1.x) +- Neither present → offer to add one (recommend Spring AI for Spring Boot 3.x, LangChain4J for Boot 2.x) + +Check Spring Boot version: +- Boot 3.x → Spring AI 1.x preferred, LangChain4J 0.35+ +- Boot 2.x → LangChain4J 0.30.x (Spring AI requires Boot 3.x) + +--- + +## Mode: `review` + +User asks to review existing AI code. Check for: + +**Spring AI:** +- [ ] `ChatClient` built via `ChatClient.Builder` (not raw `ChatModel`) for fluent API +- [ ] Prompt templates use `PromptTemplate` with variables — no string concatenation +- [ ] Streaming uses `stream().content()` or `Flux` — not blocking `.call()` for real-time responses +- [ ] `@Retryable` or Spring AI retry config on ChatClient calls — LLMs are flaky +- [ ] Secrets (`spring.ai.openai.api-key`) come from env vars or Vault, never hardcoded +- [ ] `VectorStore` queries use `SearchRequest.query(text).withTopK(n)` — not raw SQL +- [ ] RAG advisor (`QuestionAnswerAdvisor`) attached to ChatClient — not manual context injection +- [ ] Token usage logged at DEBUG, not INFO (avoid log noise) + +**LangChain4J:** +- [ ] AI services use `@AiService` interface — not `ChatLanguageModel.generate()` directly +- [ ] System prompts in `@SystemMessage` annotation — not hardcoded strings +- [ ] Memory uses `MessageWindowChatMemory` or `TokenWindowChatMemory` — not unlimited history +- [ ] Streaming via `StreamingChatLanguageModel` with `TokenStream` — not blocking +- [ ] Embeddings via `EmbeddingModel` + `EmbeddingStore` for RAG — not in-memory list search +- [ ] Tools annotated with `@Tool` on service methods — not manual function dispatch +- [ ] API key from `@Value("${langchain4j.openai.api-key}")` — never literal + +--- + +## Mode: `chat` + +User asks to add a basic chatbot or chat endpoint. + +### Spring AI +1. Add dependency (see `references/patterns.md` → Spring AI Setup) +2. Inject `ChatClient.Builder`, build a `ChatClient` bean +3. Create `ChatController` with `@PostMapping("/chat")` +4. Use `chatClient.prompt().user(message).call().content()` for simple response +5. For streaming: return `Flux` with `chatClient.prompt().user(message).stream().content()` +6. Add `ANTHROPIC_API_KEY` / `OPENAI_API_KEY` to `application.yml` via `${env-var}` + +### LangChain4J +1. Add `langchain4j-spring-boot-starter` + provider dependency +2. Define `@AiService` interface with `@SystemMessage` +3. Register as Spring bean via `AiServices.builder(MyAssistant.class).chatLanguageModel(model).build()` +4. Expose via `@RestController` + +--- + +## Mode: `rag` + +User asks to implement RAG (chat over documents, knowledge base, semantic search). + +### Spring AI RAG +1. Choose vector store: PgVector (PostgreSQL), Chroma, Redis, Weaviate, Qdrant (see `references/patterns.md`) +2. Add `spring-ai-{store}-store-spring-boot-starter` +3. Ingest pipeline: + - `DocumentReader` (PDF, text, web) → `TokenTextSplitter` → `VectorStore.add()` + - Run at startup via `ApplicationRunner` or dedicated `@PostMapping("/ingest")` +4. Query pipeline: + - Attach `QuestionAnswerAdvisor(vectorStore)` to `ChatClient` + - Spring AI auto-retrieves context and injects into prompt +5. Tune: `SearchRequest.withTopK(5).withSimilarityThreshold(0.7)` + +### LangChain4J RAG +1. Add `EmbeddingStore` (Chroma, Qdrant, in-memory for dev) +2. `EmbeddingStoreIngestor` with `DocumentSplitter` and `EmbeddingModel` +3. `EmbeddingStoreContentRetriever` → `RetrievalAugmentor` → `AiServices` builder + +--- + +## Mode: `tools` + +User asks to give the AI the ability to call Java methods (function/tool calling). + +### Spring AI +1. Define a `@Bean` of type `Function` — Spring AI auto-registers it +2. Or use `@Description` on a `record` parameter for rich schema +3. Pass function names to `ChatClient`: `.options(OpenAiChatOptions.builder().withFunction("myFunction").build())` +4. Spring AI handles the tool call loop automatically + +### LangChain4J +1. Annotate service methods with `@Tool("description of what this tool does")` +2. Register the service as a tool: `AiServices.builder(...).tools(myToolService).build()` +3. The model decides when to call — no manual dispatch needed + +--- + +## Mode: `memory` + +User asks to add conversation memory / chat history. + +### Spring AI +- `MessageChatMemoryAdvisor` with `InMemoryChatMemory` for single-instance apps +- `JdbcChatMemory` for persistent / multi-instance memory (requires `spring-ai-jdbc` store) +- Key: pass `conversationId` (e.g., session ID or user ID) to scope memory per user + +### LangChain4J +- `MessageWindowChatMemory.withMaxMessages(20)` — keeps last N messages +- `TokenWindowChatMemory` — keeps messages within token budget +- For persistence: implement `ChatMemoryStore` backed by Redis or JDBC + +--- + +## Output format + +For **review mode**: list findings as `[CRITICAL] / [HIGH] / [MEDIUM] / [LOW]` with file:line references. + +For **implementation modes** (chat, rag, tools, memory): +1. Show exact Maven/Gradle dependencies with versions +2. Show full working code snippets (not pseudocode) +3. Show `application.yml` configuration +4. Note: state the minimum Spring Boot and Java version required + +Always note version-specific differences: +- Spring AI 1.0.x (GA) vs 0.8.x (milestone) — API changes between these +- LangChain4J 1.x vs 0.x — `AiServices` API changed in 1.x +- Spring Boot 3.x required for Spring AI; Boot 2.x → use LangChain4J diff --git a/plugins/java-spring/skills/java-spring-ai/references/patterns.md b/plugins/java-spring/skills/java-spring-ai/references/patterns.md new file mode 100644 index 0000000..0579261 --- /dev/null +++ b/plugins/java-spring/skills/java-spring-ai/references/patterns.md @@ -0,0 +1,421 @@ +# Spring AI & LangChain4J — Reference Patterns + +## Spring AI Setup + +### Maven (Spring Boot 3.x) +```xml + + + + + org.springframework.ai + spring-ai-bom + 1.0.0 + pom + import + + + + + + + org.springframework.ai + spring-ai-openai-spring-boot-starter + + + org.springframework.ai + spring-ai-anthropic-spring-boot-starter + + + org.springframework.ai + spring-ai-azure-openai-spring-boot-starter + +``` + +### application.yml +```yaml +spring: + ai: + openai: + api-key: ${OPENAI_API_KEY} + chat: + options: + model: gpt-4o + temperature: 0.7 + anthropic: + api-key: ${ANTHROPIC_API_KEY} + chat: + options: + model: claude-sonnet-4-6 +``` + +--- + +## Spring AI — ChatClient (simple chat) + +```java +@Configuration +public class AiConfig { + + @Bean + public ChatClient chatClient(ChatClient.Builder builder) { + return builder + .defaultSystem("You are a helpful Java expert assistant.") + .build(); + } +} + +@RestController +@RequestMapping("/api/chat") +@RequiredArgsConstructor +public class ChatController { + + private final ChatClient chatClient; + + // Blocking — single response + @PostMapping + public String chat(@RequestBody String message) { + return chatClient.prompt() + .user(message) + .call() + .content(); + } + + // Streaming — Server-Sent Events + @GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE) + public Flux stream(@RequestParam String message) { + return chatClient.prompt() + .user(message) + .stream() + .content(); + } +} +``` + +--- + +## Spring AI — Prompt Templates + +```java +@Service +@RequiredArgsConstructor +public class ReviewService { + + private final ChatClient chatClient; + + private static final String REVIEW_TEMPLATE = """ + Review the following {language} code for bugs and improvements: + + ```{language} + {code} + ``` + + Focus on: {focus} + """; + + public String reviewCode(String language, String code, String focus) { + return chatClient.prompt() + .user(u -> u.text(REVIEW_TEMPLATE) + .param("language", language) + .param("code", code) + .param("focus", focus)) + .call() + .content(); + } +} +``` + +--- + +## Spring AI — RAG with PgVector + +### Maven dependency +```xml + + org.springframework.ai + spring-ai-pgvector-store-spring-boot-starter + +``` + +### application.yml +```yaml +spring: + ai: + vectorstore: + pgvector: + initialize-schema: true + dimensions: 1536 # match your embedding model + distance-type: COSINE_DISTANCE + datasource: + url: jdbc:postgresql://localhost:5432/mydb +``` + +### Ingest documents +```java +@Component +@RequiredArgsConstructor +public class DocumentIngestor { + + private final VectorStore vectorStore; + private final ResourceLoader resourceLoader; + + public void ingest(String resourcePath) { + Resource resource = resourceLoader.getResource(resourcePath); + List docs = new TokenTextSplitter().apply( + new TikaDocumentReader(resource).get() + ); + vectorStore.add(docs); + } +} +``` + +### RAG ChatClient with QuestionAnswerAdvisor +```java +@Bean +public ChatClient ragChatClient(ChatClient.Builder builder, VectorStore vectorStore) { + return builder + .defaultAdvisors( + new QuestionAnswerAdvisor( + vectorStore, + SearchRequest.defaults().withTopK(5).withSimilarityThreshold(0.7) + ), + new SimpleLoggerAdvisor() + ) + .build(); +} +``` + +--- + +## Spring AI — Function / Tool Calling + +```java +// Define the function +public record WeatherRequest(String city, String unit) {} +public record WeatherResponse(double temperature, String description) {} + +@Bean +@Description("Get the current weather for a city") +public Function weatherFunction(WeatherService weatherService) { + return req -> weatherService.getWeather(req.city(), req.unit()); +} + +// Use in ChatClient +@Service +@RequiredArgsConstructor +public class WeatherChatService { + + private final ChatClient chatClient; + + public String chat(String message) { + return chatClient.prompt() + .user(message) + .functions("weatherFunction") // bean name + .call() + .content(); + } +} +``` + +--- + +## Spring AI — Conversation Memory + +```java +@Bean +public ChatClient chatClientWithMemory(ChatClient.Builder builder) { + return builder + .defaultAdvisors( + new MessageChatMemoryAdvisor(new InMemoryChatMemory()) + ) + .build(); +} + +// Scope memory per conversation (pass conversationId) +public String chat(String message, String conversationId) { + return chatClient.prompt() + .user(message) + .advisors(a -> a.param(CHAT_MEMORY_CONVERSATION_ID_KEY, conversationId) + .param(CHAT_MEMORY_RETRIEVE_SIZE_KEY, 20)) + .call() + .content(); +} +``` + +--- + +## LangChain4J Setup (Spring Boot 2.x or 3.x) + +### Maven +```xml + + dev.langchain4j + langchain4j-spring-boot-starter + 0.36.0 + + + + dev.langchain4j + langchain4j-open-ai-spring-boot-starter + 0.36.0 + + + dev.langchain4j + langchain4j-anthropic-spring-boot-starter + 0.36.0 + +``` + +### application.yml +```yaml +langchain4j: + open-ai: + chat-model: + api-key: ${OPENAI_API_KEY} + model-name: gpt-4o + temperature: 0.7 +``` + +--- + +## LangChain4J — AI Service + +```java +// Define the interface +public interface JavaAssistant { + + @SystemMessage(""" + You are an expert Java developer. + You provide concise, version-appropriate advice for Java {javaVersion}. + """) + String chat(@UserMessage String message, + @V("javaVersion") String javaVersion); +} + +// Register as Spring bean +@Configuration +public class AiConfig { + + @Bean + public JavaAssistant javaAssistant(ChatLanguageModel model) { + return AiServices.builder(JavaAssistant.class) + .chatLanguageModel(model) + .chatMemory(MessageWindowChatMemory.withMaxMessages(20)) + .build(); + } +} + +// Use in controller +@RestController +@RequestMapping("/api/ai") +@RequiredArgsConstructor +public class AiController { + + private final JavaAssistant assistant; + + @PostMapping("/chat") + public String chat(@RequestBody ChatRequest request) { + return assistant.chat(request.message(), request.javaVersion()); + } +} +``` + +--- + +## LangChain4J — Tool Calling + +```java +@Component +public class DatabaseTools { + + private final UserRepository userRepository; + + @Tool("Find a user by their email address") + public String findUserByEmail(String email) { + return userRepository.findByEmail(email) + .map(u -> "Found: " + u.getName() + " (id=" + u.getId() + ")") + .orElse("User not found"); + } + + @Tool("Count total users in the system") + public long countUsers() { + return userRepository.count(); + } +} + +// Register tools in AiServices +@Bean +public SupportAssistant supportAssistant( + ChatLanguageModel model, + DatabaseTools dbTools) { + return AiServices.builder(SupportAssistant.class) + .chatLanguageModel(model) + .tools(dbTools) + .build(); +} +``` + +--- + +## LangChain4J — RAG with Embeddings + +```java +// In-memory (dev/test) +@Bean +public EmbeddingStore embeddingStore() { + return new InMemoryEmbeddingStore<>(); +} + +// Ingest +@Component +@RequiredArgsConstructor +public class KnowledgeBaseLoader implements ApplicationRunner { + + private final EmbeddingModel embeddingModel; + private final EmbeddingStore embeddingStore; + + @Override + public void run(ApplicationArguments args) { + Document doc = FileSystemDocumentLoader.loadDocument("docs/faq.txt"); + EmbeddingStoreIngestor.builder() + .documentSplitter(DocumentSplitters.recursive(500, 50)) + .embeddingModel(embeddingModel) + .embeddingStore(embeddingStore) + .build() + .ingest(doc); + } +} + +// Query with RAG +@Bean +public KnowledgeAssistant knowledgeAssistant( + ChatLanguageModel model, + EmbeddingModel embeddingModel, + EmbeddingStore embeddingStore) { + + ContentRetriever retriever = EmbeddingStoreContentRetriever.builder() + .embeddingStore(embeddingStore) + .embeddingModel(embeddingModel) + .maxResults(5) + .minScore(0.7) + .build(); + + return AiServices.builder(KnowledgeAssistant.class) + .chatLanguageModel(model) + .contentRetriever(retriever) + .build(); +} +``` + +--- + +## Vector Store Comparison + +| Store | Best for | Spring AI | LangChain4J | +|---|---|---|---| +| PgVector | Existing PostgreSQL | `spring-ai-pgvector-store` | `langchain4j-pgvector` | +| Redis | Low-latency, existing Redis | `spring-ai-redis-store` | `langchain4j-redis` | +| Chroma | Local dev, easy setup | `spring-ai-chroma-store` | `langchain4j-chroma` | +| Qdrant | Production, high scale | `spring-ai-qdrant-store` | `langchain4j-qdrant` | +| Weaviate | Hybrid search | `spring-ai-weaviate-store` | `langchain4j-weaviate` | +| In-memory | Tests / prototypes | `SimpleVectorStore` | `InMemoryEmbeddingStore` |