diff --git a/pom.xml b/pom.xml index aaa166f..d6d755f 100644 --- a/pom.xml +++ b/pom.xml @@ -62,7 +62,10 @@ advisors/recursive-advisor-demo advisors/tool-argument-augmenter-demo advisors/evaluation-recursive-advisor-demo - + + + skill + diff --git a/skill/README.md b/skill/README.md new file mode 100644 index 0000000..0f4a9c9 --- /dev/null +++ b/skill/README.md @@ -0,0 +1,173 @@ +# Spring AI Skill Extension Example + +This example demonstrates the **Spring AI Skill Extension Framework** - a powerful skill management framework for Spring AI applications that enables progressive skill loading, lazy instantiation, and dynamic tool management. + +## Overview + +The **Clothing Store Purchase Assistant** example showcases an intelligent purchase assistant that helps store owners make smart inventory decisions through AI conversations. + +### Key Features + +- šŸ“¦ **Inventory Management** - Check current stock status +- šŸ’° **Pricing Analysis** - Analyze store pricing and profit margins +- šŸ­ **Supplier Catalog** - Query available items and wholesale costs +- šŸ“ˆ **Sales Trends** - Understand hot-selling items and market demand +- šŸŒ¤ļø **Weather Query** - Consider weather impact on clothing sales +- šŸ‘— **Fashion Guide** - Get fashion trends and reference materials with progressive loading +- šŸŽÆ **Purchase Strategy** - Receive intelligent restocking recommendations + +### Framework Highlights + +This example demonstrates: +- **Instance-based Registration** (eager loading) - Skills loaded at startup +- **Class-based Registration** (lazy loading) - Skills loaded on-demand +- **Progressive Content Loading** - Large skill content loaded only when needed +- **Skill References** - External reference materials loaded dynamically + +## Prerequisites + +1. **Java 17+** +2. **Maven 3.6+** +3. **Spring AI Skill Extension Core** - Version 1.1.0 (included as dependency) + +## Configuration + +Set the following environment variables before running: + +```bash +export BASE_URL="https://api.openai.com" # OpenAI API base URL +export API_KEY="your-api-key" # Your OpenAI API key +export COMPLETIONS_PATH="/v1/chat/completions" # Completions endpoint path +export MODEL="gpt-4o" # Model to use +``` + +Or on Windows: + +```cmd +set BASE_URL=https://api.openai.com +set API_KEY=your-api-key +set COMPLETIONS_PATH=/v1/chat/completions +set MODEL=gpt-4o +``` + +## Running the Example + +### Option 1: Using Maven Exec Plugin + +```bash +mvn exec:java -Dexec.mainClass="com.examples.clothing.ClothingStoreExample" +``` + +### Option 2: Run as Spring Boot Application + +```bash +mvn spring-boot:run +``` + +### Option 3: Build and Run JAR + +```bash +mvn clean package +java -jar target/spring-ai-skill-example-0.0.1-SNAPSHOT.jar +``` + +## Example Conversations + +Once the assistant starts, try these queries: + +``` +šŸ§‘ You: Show me current inventory status +šŸ¤– Assistant: [Calls checkInventory tool and shows inventory report] + +šŸ§‘ You: What's the sales trend this week? +šŸ¤– Assistant: [Calls getSalesTrends tool and analyzes hot-selling items] + +šŸ§‘ You: Show me the spring trends report +šŸ¤– Assistant: [Triggers progressive loading - loads FashionGuideSkill and its content] + +šŸ§‘ You: I have a $10,000 budget, what should I buy? +šŸ¤– Assistant: [Calls generatePurchaseStrategy and provides optimized recommendations] + +šŸ§‘ You: Check the weather in New York +šŸ¤– Assistant: [Calls WeatherSkill to get weather information] +``` + +### Special Commands + +- `/stats` - View framework statistics +- `exit` or `quit` - End the conversation + +## Architecture + +This example uses the Spring AI Skill Extension Framework which provides: + +### 1. Skill Registration + +```java +// Instance-based (eager loading) +skillKit.register(InventorySkill.create()); + +// Class-based (lazy loading) +skillKit.register(WeatherSkill.class); +``` + +### 2. Dynamic Tool Management + +The framework automatically: +- Converts skills to tool callbacks +- Manages skill lifecycle +- Handles lazy instantiation +- Enables progressive content loading + +### 3. Integration with Spring AI + +```java +SkillAwareToolCallingManager toolManager = + SkillAwareToolCallingManager.builder() + .skillKit(skillKit) + .build(); + +ChatClient chatClient = ChatClient.builder(chatModel) + .defaultAdvisors(SkillAwareAdvisor.builder() + .skillKit(skillKit) + .build()) + .build(); +``` + +## Project Structure + +``` +skill/ +ā”œā”€ā”€ src/main/java/com/semir/spring/ai/skill/examples/clothing/ +│ ā”œā”€ā”€ ClothingStoreExample.java # Main application entry +│ └── skills/ +│ ā”œā”€ā”€ InventorySkill.java # Inventory management (eager) +│ ā”œā”€ā”€ PricingSkill.java # Pricing analysis (eager) +│ ā”œā”€ā”€ TrendSkill.java # Sales trends (eager) +│ ā”œā”€ā”€ SupplierSkill.java # Supplier catalog (lazy) +│ ā”œā”€ā”€ PurchaseStrategySkill.java # Purchase strategy (lazy) +│ ā”œā”€ā”€ WeatherSkill.java # Weather info (lazy) +│ └── FashionGuideSkill.java # Fashion guide (lazy + progressive) +ā”œā”€ā”€ src/main/resources/skills/ +│ └── fashion-guide/ # Progressive loading content +│ ā”œā”€ā”€ content.md +│ └── references.json +└── pom.xml +``` + +## Dependencies + +- **Spring Boot** 4.0.0 +- **Spring AI** 1.1.0 +- **Spring AI Skill Extension Core** 1.1.0 +- **Spring AI OpenAI** (from Spring AI BOM) + +## Learn More + +- [Spring AI Skill Extension Framework](https://github.com/your-repo/spring-ai-skill-extension) +- [Spring AI Documentation](https://docs.spring.io/spring-ai/) +- [Example Source Code](src/main/java/com/examples/clothing/) + +## License + +Apache License 2.0 diff --git a/skill/pom.xml b/skill/pom.xml new file mode 100644 index 0000000..2d5a8a9 --- /dev/null +++ b/skill/pom.xml @@ -0,0 +1,98 @@ + + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 4.0.1 + + + + com.example + spring-ai-skill-example + 0.0.1-SNAPSHOT + Spring AI Skill Framework Example + Clothing Store Purchase Assistant using Spring AI Skill Extension Framework + + + 17 + 2.0.0-SNAPSHOT + + + + + + + org.springframework.ai + spring-ai-bom + ${spring-ai.version} + pom + import + + + + + + + + org.springframework.ai + spring-ai-skill + ${spring-ai.version} + + + + + org.springframework.ai + spring-ai-openai + + + + + ch.qos.logback + logback-classic + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/snapshot + + false + + + + central-portal-snapshots + Central Portal Snapshots + https://central.sonatype.com/repository/maven-snapshots/ + + false + + + true + + + + + diff --git a/skill/src/main/java/com/examples/clothing/ClothingStoreExample.java b/skill/src/main/java/com/examples/clothing/ClothingStoreExample.java new file mode 100644 index 0000000..0b7719e --- /dev/null +++ b/skill/src/main/java/com/examples/clothing/ClothingStoreExample.java @@ -0,0 +1,302 @@ +/* + * Copyright 2026 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.examples.clothing; + +import java.time.Duration; +import java.util.Scanner; +import java.util.concurrent.atomic.AtomicReference; + +import com.examples.clothing.skills.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.ai.chat.client.ChatClient; +import org.springframework.ai.openai.OpenAiChatModel; +import org.springframework.ai.openai.OpenAiChatOptions; +import org.springframework.ai.openai.api.OpenAiApi; +import org.springframework.ai.skill.core.DefaultSkillKit; +import org.springframework.ai.skill.core.SkillBox; +import org.springframework.ai.skill.core.SkillKit; +import org.springframework.ai.skill.core.SkillPoolManager; +import org.springframework.ai.skill.spi.SkillAwareAdvisor; +import org.springframework.ai.skill.spi.SkillAwareToolCallingManager; +import org.springframework.ai.skill.support.DefaultSkillPoolManager; +import org.springframework.ai.skill.support.SimpleSkillBox; +import org.springframework.http.client.JdkClientHttpRequestFactory; +import org.springframework.web.client.RestClient; + +/** + * Clothing Store Purchase Assistant Example + * + *

This example demonstrates how to build an intelligent clothing store purchase assistant + * using the SpringAI Skill Framework. Through AI conversations, store owners can receive + * purchase recommendations based on multiple factors: + * + *

+ * + *

How to Run: + * + *

+ * mvn exec:java -pl spring-ai-skill-extension-examples \
+ *   -Dexec.mainClass="com.semir.spring.ai.skill.examples.clothing.ClothingStoreExample"
+ * 
+ * + *

Example Conversations: + * + *

+ * User: "Show me current inventory status"
+ * AI: [Calls checkInventory tool and shows inventory report]
+ *
+ * User: "What's the sales trend this week?"
+ * AI: [Calls getSalesTrends tool and analyzes hot-selling items]
+ *
+ * User: "I have a $10,000 budget, what should I buy?"
+ * AI: [Calls generatePurchaseStrategy and provides optimized recommendations]
+ * 
+ * + * @author Semir + */ +public class ClothingStoreExample { + + private static final Logger logger = LoggerFactory.getLogger(ClothingStoreExample.class); + + public static void main(String[] args) { + System.out.println("=".repeat(80)); + System.out.println("šŸŖ Clothing Store Purchase Assistant"); + System.out.println("=".repeat(80)); + System.out.println(); + + try { + // 1. Initialize framework components + SkillPoolManager poolManager = new DefaultSkillPoolManager(); + SimpleSkillBox skillBox = new SimpleSkillBox(); + SkillKit skillKit = DefaultSkillKit.builder() + .skillBox(skillBox) + .poolManager(poolManager) + .build(); + + // 2. Register all skills + System.out.println("šŸ“¦ Registering skills...\n"); + + skillBox.addSource("example"); + + // Instance-based registration (eager loading) + skillKit.register(InventorySkill.create()); + System.out.println(" āœ“ Inventory Management Skill registered"); + + skillKit.register(PricingSkill.create()); + System.out.println(" āœ“ Pricing Analysis Skill registered"); + + skillKit.register(TrendSkill.create()); + System.out.println(" āœ“ Sales Trend Skill registered"); + + // Class-based registration (lazy loading) + skillKit.register(SupplierSkill.class); + System.out.println(" āœ“ Supplier Catalog Skill registered (lazy)"); + + skillKit.register(PurchaseStrategySkill.class); + System.out.println(" āœ“ Purchase Strategy Skill registered (lazy)"); + + // Weather Skill (weather impacts clothing sales) + skillKit.register(WeatherSkill.class); + System.out.println(" āœ“ Weather Skill registered (lazy)"); + + // Fashion Guide Skill (provides fashion trends and reference materials) + skillKit.register(FashionGuideSkill.class); + System.out.println(" āœ“ Fashion Guide Skill registered (lazy)"); + + System.out.println("\nāœ… All skills registered and automatically added to SkillBox!\n"); + + // 4. Create AI Chat Client + System.out.println("šŸ¤– Initializing AI Assistant...\n"); + + SkillAwareToolCallingManager toolManager = + SkillAwareToolCallingManager.builder().skillKit(skillKit).build(); + ChatClient chatClient = createChatClient(toolManager, skillKit); + + System.out.println("āœ… AI Assistant ready!\n"); + System.out.println("=".repeat(80)); + System.out.println(); + + // 5. Start interactive chat + interactiveChat(chatClient, skillBox, poolManager); + + } catch (Exception e) { + logger.error("Error running clothing store example", e); + System.err.println("āŒ Error: " + e.getMessage()); + e.printStackTrace(); + } + } + + private static ChatClient createChatClient(SkillAwareToolCallingManager toolManager, SkillKit skillKit) { + + String baseUrl = System.getenv("BASE_URL"); + String apiKey = System.getenv("API_KEY"); + String completionsPath = System.getenv("COMPLETIONS_PATH"); + String model = System.getenv("MODEL"); + + JdkClientHttpRequestFactory requestFactory = new JdkClientHttpRequestFactory(); + requestFactory.setReadTimeout(Duration.ofSeconds(120)); + + RestClient.Builder builder = RestClient.builder().requestFactory(requestFactory); + + OpenAiApi openAiApi = OpenAiApi.builder() + .baseUrl(baseUrl) + .apiKey(apiKey) + .completionsPath(completionsPath) + .restClientBuilder(builder) + .build(); + + OpenAiChatOptions options = OpenAiChatOptions.builder() + .model(model) + .maxTokens(16000) + .temperature(0.8) + .build(); + + OpenAiChatModel openAiChatModel = OpenAiChatModel.builder() + .openAiApi(openAiApi) + .defaultOptions(options) + .toolCallingManager(toolManager) + .build(); + + // Define system prompt + String systemPrompt = + """ + You are an expert clothing store purchase assistant helping store owners make smart inventory decisions. + + Your role: + - Analyze inventory status and identify restocking needs + - Consider sales trends and seasonal factors + - Provide data-driven purchase recommendations + - Help optimize budget allocation + - Calculate ROI and profit projections + - Consider weather when suggesting seasonal items + - Provide fashion trend insights and reference materials + + Guidelines: + - Always check inventory before making recommendations + - Prioritize items with stock-out risk + - Consider profit margins and sales velocity + - Recommend bulk purchases when beneficial + - When asked about fashion references, use loadSkillReference tool + - Be concise but thorough in your analysis + - Use data from tools rather than assumptions + + When answering, always use the skills provided. Respond professionally and amicably, like a business advisor. + """; + + // Create ChatClient + return ChatClient.builder(openAiChatModel) + .defaultSystem(systemPrompt) + .defaultAdvisors(SkillAwareAdvisor.builder().skillKit(skillKit).build()) + .build(); + } + + private static void interactiveChat(ChatClient chatClient, SkillBox skillBox, SkillPoolManager skillPoolManager) { + Scanner scanner = new Scanner(System.in); + + System.out.println("šŸ’¬ Chat with the AI Assistant (type 'exit' or 'quit' to end)\n"); + System.out.println("Example questions:"); + System.out.println(" • \"Show me current inventory status\""); + System.out.println(" • \"Check the weather in New York\""); + System.out.println(" • \"Show me the spring trends report\" ← Triggers reference loading!"); + System.out.println(" • \"Give me the buying guide link\" ← Triggers reference loading!"); + System.out.println(" • \"Generate a purchase strategy with $10,000 budget\""); + System.out.println(); + System.out.println("-".repeat(80)); + System.out.println(); + + while (true) { + // User input + System.out.print("šŸ§‘ You: "); + String userInput = scanner.nextLine().trim(); + + if (userInput.isEmpty()) { + continue; + } + + // Exit command + if (userInput.equalsIgnoreCase("exit") || userInput.equalsIgnoreCase("quit")) { + System.out.println("\nšŸ‘‹ Thank you for using Clothing Store Assistant. Goodbye!"); + break; + } + + // Special command: view statistics + if (userInput.equalsIgnoreCase("/stats")) { + printStats(skillBox, skillPoolManager); + continue; + } + + try { + System.out.print("\nšŸ¤– Assistant: "); + AtomicReference lastContent = new AtomicReference<>(""); + + // Stream response + chatClient.prompt().user(userInput).stream() + .chatResponse() + .doOnNext(chatResponse -> { + if (chatResponse.getResult() != null + && chatResponse.getResult().getOutput() != null) { + String currentContent = + chatResponse.getResult().getOutput().getText(); + if (currentContent != null) { + String lastContentValue = lastContent.get(); + if (currentContent.startsWith(lastContentValue)) { + // Incremental output + String newContent = currentContent.substring(lastContentValue.length()); + System.out.print(newContent); + System.out.flush(); + lastContent.set(currentContent); + } else { + // Full output + System.out.print(currentContent); + System.out.flush(); + lastContent.set(currentContent); + } + } + } + }) + .blockLast(); + + System.out.println("\n"); + System.out.println("-".repeat(80)); + + } catch (Exception e) { + System.err.println("\nāŒ Error occurred: " + e.getMessage()); + logger.error("Error during chat interaction", e); + } + } + + scanner.close(); + } + + private static void printStats(SkillBox skillBox, SkillPoolManager skillPoolManager) { + System.out.println("\nšŸ“Š Framework Statistics:"); + System.out.println(" • Total registered skills: 7"); + System.out.println(" • Skills in box: " + skillBox.getSkillCount()); + System.out.println(" • Registered definitions: " + + skillPoolManager.getDefinitions().size()); + System.out.println(" • Categories: Inventory, Pricing, Supplier, Trend, Weather, Fashion, Purchase"); + System.out.println(); + } +} diff --git a/skill/src/main/java/com/examples/clothing/README.md b/skill/src/main/java/com/examples/clothing/README.md new file mode 100644 index 0000000..45a68d8 --- /dev/null +++ b/skill/src/main/java/com/examples/clothing/README.md @@ -0,0 +1,328 @@ +# Clothing Store Purchase Assistant Example + +A complete clothing store purchase decision scenario demonstrating how to build **real-world AI intelligent assistants** using the Spring AI Skill Framework. + +## Scenario Overview + +This example implements an **Intelligent Clothing Store Purchase Assistant**. Store owners engage in **natural language conversations**, and the AI automatically invokes appropriate tools to analyze inventory, trends, costs, and provides optimized purchase recommendations. + +### Why This Scenario? + +Compared to generic scenarios like travel planning, clothing store purchase decisions offer several advantages: + +1. **Strong Data Specificity** - Uses fixed simulated data (inventory, prices, suppliers), less likely to trigger the LLM's general knowledge +2. **Clear Business Logic** - Purchase decisions require synthesizing multiple data sources, demonstrating Skills collaboration +3. **High Practicality** - Real business scenario, easy to understand and extend +4. **Good Verifiability** - Fixed data facilitates verification of tool execution + +### Core Features + +āœ… **Real AI Conversations** - Integrated with ChatClient, supports streaming responses +āœ… **Intelligent Tool Invocation** - AI automatically selects and calls appropriate Skills +āœ… **Multi-dimensional Analysis** - Synthesizes inventory, trends, costs, weather and other factors +āœ… **Fixed Simulated Data** - Facilitates verification and debugging +āœ… **Professional Recommendations** - Provides ROI analysis and priority ranking + +### Business Workflow + +``` +Store owner needs to restock + ↓ +1. InventorySkill - Check current inventory status + ↓ +2. TrendSkill - Analyze sales trends and popular items + ↓ +3. WeatherSkill - Check weather (affects seasonal items) + ↓ +4. SupplierSkill - Query supplier products and costs + ↓ +5. PricingSkill - Analyze profit margins and pricing + ↓ +6. PurchaseStrategySkill - Generate intelligent purchase recommendations +``` + +## Skills Overview + +### 1. InventorySkill (Inventory Management) + +**Registration Method**: `registerFromInstance` + +**Features**: +- View current inventory status +- Query inventory by category +- Identify low stock and out-of-stock risks +- Provide inventory summary reports + +**Tools**: +- `checkInventory(category)` - Query specified category or all inventory + +**Fixed Data**: +- Winter Coats: 15 units (āš ļø Low Stock) +- Sweaters: 45 units (āœ… Good Stock) +- Jeans: 8 units (šŸ”“ Critical) +- Dresses: 25 units (āœ… Good Stock) +- Shirts: 12 units (āš ļø Low Stock) + +### 2. PricingSkill (Price Management) + +**Registration Method**: `registerFromInstance` + +**Features**: +- Query product retail prices +- Calculate costs and profit margins +- Analyze pricing competitiveness +- Provide pricing recommendations + +**Tools**: +- `getPricing(skuOrCategory)` - Get pricing information and profit analysis + +**Fixed Data**: +- Winter Coats: $120 retail, $75 cost, 37.5% margin +- Sweaters: $65 retail, $38 cost, 41.5% margin +- Jeans: $80 retail, $45 cost, 43.75% margin +- Dresses: $95 retail, $52 cost, 45.3% margin +- Shirts: $45 retail, $25 cost, 44.4% margin + +### 3. SupplierSkill (Supplier Management) + +**Registration Method**: `registerFromClass` (lazy loading) + +**Features**: +- Browse supplier product catalog +- Query wholesale costs +- Understand minimum order quantities +- Calculate bulk discounts + +**Tools**: +- `getSupplierCatalog(category)` - Get supplier catalog +- `getSupplierQuote(supplierSku, quantity)` - Get quotation + +**Fixed Data**: +- SUP-COAT-W01: $75/unit, min 10, bulk 5% off at 50+ +- SUP-SWTR-W01: $38/unit, min 20, bulk 8% off at 100+ +- SUP-JEAN-C01: $45/unit, min 15, bulk 7% off at 50+ +- SUP-SHRT-C01: $25/unit, min 20, bulk 10% off at 100+ +- SUP-DRSS-F01: $52/unit, min 12, bulk 6% off at 40+ + +### 4. TrendSkill (Sales Trends) + +**Registration Method**: `registerFromInstance` + +**Features**: +- Analyze hot-selling items +- Track sales velocity +- Forecast future demand +- Identify market trends + +**Tools**: +- `getSalesTrends(period)` - Get sales trend reports +- `getPredictedDemand(category)` - Get demand forecasts + +**Fixed Data**: +- Sweaters: 85 units sold, +35% trend (Hot!) +- Winter Coats: 62 units sold, +45% trend (Hot!) +- Jeans: 48 units sold, stable +- Dresses: 35 units sold, -15% trend +- Shirts: 28 units sold, stable + +### 5. WeatherSkill (Weather Query) + +**Registration Method**: `registerFromClass` (reuses existing) + +**Features**: +- Query city weather +- Influence seasonal product recommendations + +**Tools**: +- `getWeather(city)` - Get weather information + +### 6. PurchaseStrategySkill (Purchase Strategy) + +**Registration Method**: `registerFromClass` (lazy loading) + +**Features**: +- Generate intelligent purchase recommendations +- Optimize budget allocation +- ROI analysis and forecasting +- Risk assessment +- Priority ranking + +**Tools**: +- `generatePurchaseStrategy(budget, priority)` - Generate purchase strategy +- `optimizePurchaseOrder(items, quantities)` - Optimize orders + +## Running the Example + +```bash +mvn exec:java -pl springai-skill-framework-examples \ + -Dexec.mainClass="clothing.examples.com.semir.spring.ai.skill.ClothingStoreExample" +``` + +## Usage Examples + +After starting, you can converse with the AI like this: + +### Example Conversation 1: Check Inventory + +``` +šŸ§‘ You: Show me current inventory status + +šŸ¤– Assistant: [AI automatically calls checkInventory tool] +Based on the current inventory report, here's the status: + +**Critical Items** šŸ”“: +- Jeans (JEAN-001): Only 8 units left - URGENT restock needed! + +**Low Stock Items** āš ļø: +- Winter Coats (COAT-001): 15 units - needs restock soon +- Shirts (SHRT-001): 12 units - monitor closely + +**Good Stock** āœ…: +- Sweaters (SWTR-001): 45 units +- Dresses (DRSS-001): 25 units + +I recommend prioritizing Jeans for immediate restocking. +``` + +### Example Conversation 2: Analyze Sales Trends + +``` +šŸ§‘ You: What items are trending this week? + +šŸ¤– Assistant: [AI calls getSalesTrends tool] +Here are the hot-selling items this week: + +šŸ”„ **Top Performers**: +1. Wool Sweaters - 85 units sold (+35% growth!) +2. Winter Coats - 62 units sold (+45% growth!) + +These winter items are in peak demand. With the cold weather, +customers are buying heavily. Combined with your low stock on coats, +I strongly recommend restocking both items immediately. +``` + +### Example Conversation 3: Generate Purchase Strategy + +``` +šŸ§‘ You: I have a $10,000 budget. What should I buy? + +šŸ¤– Assistant: [AI comprehensively calls multiple tools: checkInventory, getSalesTrends, getSupplierCatalog, generatePurchaseStrategy] +Based on comprehensive analysis, here's your optimized purchase strategy: + +**Priority 1 (URGENT)** šŸ”“: +- Jeans: 70 units at $41.85/unit = $2,929.50 +- Expected ROI: 91% +- Reason: Critical stock-out within 1 day + +**Priority 2 (HIGH)** āš ļø: +- Winter Coats: 50 units at $71.25/unit = $3,562.50 +- Expected ROI: 68% +- Reason: Hot seller (+45%), low stock + +**Priority 3 (MEDIUM)**: +- Sweaters: 100 units at $34.96/unit = $3,496.00 +- Expected ROI: 86% +- Reason: Top seller (+35%), qualify for 8% bulk discount + +**Total Investment**: $9,988.00 +**Budget Remaining**: $12.00 +**Projected Revenue**: $18,100 +**Projected Profit**: $8,112 (81% ROI) + +This strategy maximizes your ROI while addressing critical stock issues. +``` + +### Example Conversation 4: Consider Weather Factors + +``` +šŸ§‘ You: Check the weather in New York and suggest what to stock + +šŸ¤– Assistant: [AI calls getWeather and getPredictedDemand] +New York Weather: ā„ļø 5°C, Snowy, Humidity 78% + +With this cold, snowy weather, I recommend: +- **Priority**: Winter Coats and Sweaters (high demand expected) +- **Avoid**: Dresses and light items (off-season) +- **Moderate**: Basic items like Jeans (consistent demand) + +Cold weather creates urgency for warm clothing. Stock up on +winter items now to capture this demand spike. +``` + +## Special Commands + +- `exit` or `quit` - Exit the program +- `/stats` - View framework statistics + +## Architecture Design Highlights + +### 1. Diverse Registration Methods + +```java +// Instance-based registration (eager loading, frequently accessed) +skillKit.registerFromInstance(InventorySkill.create()); +skillKit.registerFromInstance(PricingSkill.create()); +skillKit.registerFromInstance(TrendSkill.create()); + +// Class-based registration (lazy loading, on-demand) +skillKit.registerFromClass(SupplierSkill.class); +skillKit.registerFromClass(PurchaseStrategySkill.class); +skillKit.registerFromClass(WeatherSkillPojo.class); +``` + +### 2. Fixed Data Design + +All Skills use fixed simulated data, facilitating: +- āœ… Verification of correct tool execution +- āœ… Debugging and testing +- āœ… Avoiding LLM's use of general knowledge +- āœ… Ensuring reproducible results + +### 3. Real Business Scenario + +Complete purchase decision workflow: +- **Data Collection** - Inventory, trends, prices +- **Analysis & Decision** - ROI, priority, risk +- **Execution Recommendations** - Specific orders, quantities, costs + +### 4. Intelligent Tool Collaboration + +AI automatically selects appropriate tool combinations based on conversation: +- Ask about inventory → InventorySkill +- Ask about trends → TrendSkill +- Need purchase recommendations → Calls multiple Skills (Inventory + Trend + Supplier + Purchase) +- Consider weather → WeatherSkill + PredictedDemand + +## Extension Suggestions + +### Add More Skills + +1. **CustomerFeedbackSkill** - Analyze customer reviews and feedback +2. **CompetitorSkill** - Competitor price and product analysis +3. **SeasonalPlanningSkill** - Seasonal product planning +4. **PromotionSkill** - Promotional activity recommendations +5. **ReturnAnalysisSkill** - Return rate analysis + +### Integrate Real Data + +Can replace fixed data with: +- Database connections (real inventory) +- ERP system integration +- Real-time sales data +- Real supplier APIs + +## Related Examples + +- **TravelPlanningExample** - Travel planning scenario (AI conversation) +- **SkillKitExample** - Data analysis scenario (AI conversation) +- **BasicUsageExample** - Basic framework usage + +## Technical Summary + +This example demonstrates: +āœ“ Professional scenario Skill design approach +āœ“ Fixed data usage and verification +āœ“ Multi-Skill intelligent collaboration patterns +āœ“ Real business decision workflows +āœ“ AI-driven tool selection and invocation +āœ“ Complete error handling and user experience diff --git a/skill/src/main/java/com/examples/clothing/README_REFERENCES.md b/skill/src/main/java/com/examples/clothing/README_REFERENCES.md new file mode 100644 index 0000000..bbc9cba --- /dev/null +++ b/skill/src/main/java/com/examples/clothing/README_REFERENCES.md @@ -0,0 +1,195 @@ +# Fashion Guide Skill - References Feature Demo + +## Overview + +In the Clothing Store Example, we added the **Fashion Guide Skill**, which demonstrates how to use the `@SkillReferences` annotation to provide reference materials for Skills. + +## Features + +### Fashion Guide Skill Provides + +1. **Quick Trend Overview** - Via `getTrendOverview` tool +2. **Detailed Reference Materials** - Document links provided through `@SkillReferences` annotation + +### Reference Materials List + +- `spring-trends` - Spring fashion trends report +- `summer-trends` - Summer fashion trends report +- `buying-guide` - Clothing purchasing guide +- `color-trends` - Seasonal color trends guide +- `style-guide` - Style and outfit recommendations +- `market-report` - Market analysis report +- `sustainability-guide` - Sustainable fashion guide + +## Example Questions to Trigger Reference Loading + +### 1. Direct Reference Inquiry + +``` +šŸ§‘ You: Show me the spring trends report + +šŸ¤– Assistant: +[AI first calls getTrendOverview for overview] +[Then calls loadSkillReference("fashion-guide", "spring-trends")] +[Returns link: https://fashion-industry.com/reports/2025-spring-summer-trends.pdf] +``` + +### 2. Buying Guide Inquiry + +``` +šŸ§‘ You: Give me the buying guide link + +šŸ¤– Assistant: +[AI calls loadSkillReference("fashion-guide", "buying-guide")] +[Returns link: https://fashion-industry.com/guides/clothing-buyer-handbook.pdf] +``` + +### 3. Color Trends Inquiry + +``` +šŸ§‘ You: Where can I find the color trends document? + +šŸ¤– Assistant: +[AI calls loadSkillReference("fashion-guide", "color-trends")] +[Returns link: https://fashion-industry.com/trends/2025-color-palette.pdf] +``` + +### 4. Comprehensive Query + +``` +šŸ§‘ You: What are the current fashion trends? Also give me the detailed report. + +šŸ¤– Assistant: +[AI first calls getTrendOverview to show quick overview] +[Then calls loadSkillReference("fashion-guide", "spring-trends") to provide detailed report link] +``` + +## Running the Example + +```bash +cd springai-skill-framework-examples +mvn compile exec:java -Dexec.mainClass="clothing.examples.com.semir.spring.ai.skill.ClothingStoreExample" +``` + +## How It Works + +### 1. Skill Definition + +```java +@Skill(name = "fashion-guide", description = "...", source = "example") +public class FashionGuideSkill { + + @SkillReferences + public Map references() { + Map refs = new HashMap<>(); + refs.put("spring-trends", "https://fashion-industry.com/reports/2025-spring-summer-trends.pdf"); + refs.put("buying-guide", "https://fashion-industry.com/guides/clothing-buyer-handbook.pdf"); + // ... more references + return refs; + } +} +``` + +### 2. LLM Invocation Flow + +1. **User Question**: "Show me the spring trends report" +2. **AI Intent Recognition**: Need to load fashion-guide's spring-trends reference +3. **Tool Invocation**: `loadSkillReference("fashion-guide", "spring-trends")` +4. **Framework Processing**: + - Check if fashion-guide skill exists + - Check if it supports ReferencesLoader + - Call `skill.as(ReferencesLoader.class).getReferences()` + - Return the link for `spring-trends` +5. **Return Result**: "https://fashion-industry.com/reports/2025-spring-summer-trends.pdf" + +### 3. System Prompt Guidance + +The system prompt explicitly tells the AI how to use references: + +``` +**IMPORTANT: Fashion Guide Skill has Reference Materials** +When users ask about: +- "spring trends report" or "summer trends" +- "buying guide" or "color trends" +- "style guide" or any fashion reference materials + +You should: +1. First use getTrendOverview tool for a quick summary +2. Then use loadSkillReference tool to get the actual document link + Example: loadSkillReference("fashion-guide", "spring-trends") +3. Provide the reference link to the user +``` + +## Technical Details + +### Automatic References Support + +Because `FashionGuideSkill` uses the `@SkillReferences` annotation, the framework automatically: + +1. **Annotation Scanning**: `AnnotationDrivenSkill` scans `@SkillReferences` methods +2. **Dynamic Proxy**: Creates dynamic proxy for `ReferencesLoader` interface +3. **Support Check**: `skill.supports(ReferencesLoader.class)` returns `true` +4. **Capability Conversion**: `skill.as(ReferencesLoader.class)` returns proxy instance + +### SimpleSkillLoaderTool + +Provides two tools for LLM: + +1. `loadSkillContent(skillName)` - Load Skill content +2. `loadSkillReference(skillName, referenceKey)` - Load specific reference material + +## Comparison with Other Skills + +| Skill | Has References | Description | +|-------|----------------|-------------| +| Inventory Management | āŒ | Provides tools, no references | +| Pricing Analysis | āŒ | Provides tools, no references | +| Supplier Catalog | āŒ | Provides tools, no references | +| Sales Trends | āŒ | Provides tools, no references | +| Weather | āŒ | Provides tools, no references | +| **Fashion Guide** | āœ… | **Provides tools + references** | +| Purchase Strategy | āŒ | Provides tools, no references | + +## Best Practices + +### 1. Clear Reference Key Naming + +```java +// āœ… Good naming +refs.put("spring-trends", "..."); +refs.put("buying-guide", "..."); +refs.put("color-trends", "..."); + +// āŒ Poor naming +refs.put("ref1", "..."); +refs.put("doc", "..."); +``` + +### 2. Document in Skill Content + +```java +@SkillContent +public String content() { + return """ + ## Reference Materials Available + - spring-trends: Spring/Summer fashion trend report + - buying-guide: Clothing buying guide + ... + """; +} +``` + +### 3. System Prompt Guidance + +Explicitly tell the AI when and how to use `loadSkillReference`. + +## Summary + +By adding the Fashion Guide Skill to the Clothing Store Example, we demonstrate: + +1. āœ… How to use the `@SkillReferences` annotation +2. āœ… How to load reference materials via LLM tools +3. āœ… How to design questions to trigger reference loading +4. āœ… How to integrate references functionality in real scenarios + +This design fully aligns with the framework's extensibility architecture, providing Skills with rich reference material support capabilities. diff --git a/skill/src/main/java/com/examples/clothing/skills/FashionGuideSkill.java b/skill/src/main/java/com/examples/clothing/skills/FashionGuideSkill.java new file mode 100644 index 0000000..7f75d9c --- /dev/null +++ b/skill/src/main/java/com/examples/clothing/skills/FashionGuideSkill.java @@ -0,0 +1,128 @@ +/* + * Copyright 2026 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.examples.clothing.skills; + + +import org.springframework.ai.skill.annotation.Skill; +import org.springframework.ai.skill.annotation.SkillContent; +import org.springframework.ai.skill.annotation.SkillInit; +import org.springframework.ai.skill.capability.SkillReferences; + +import java.util.HashMap; +import java.util.Map; + +/** + * Fashion Guide Skill + * + *

Provides fashion trends, industry guides, and style recommendations for clothing stores. + * Includes extensive reference material links to help store owners stay informed about latest fashion dynamics. + * + *

Key Features: + *

    + *
  • Provides current season fashion trend overview
  • + *
  • Provides detailed reference material links (via @SkillReferences)
  • + *
  • Helps stores understand market dynamics
  • + *
+ * + *

Reference Materials: + *

    + *
  • spring-trends - Spring fashion trend report
  • + *
  • summer-trends - Summer fashion trend report
  • + *
  • buying-guide - Clothing purchase guide
  • + *
  • color-trends - Seasonal color trend guide
  • + *
  • style-guide - Style and outfit recommendations
  • + *
+ * + * @author Semir + */ +@Skill( + name = "fashion-guide", + description = "Provides fashion trends, industry guides, and style recommendations for clothing stores", + source = "example") +public class FashionGuideSkill { + + @SkillContent + public String content() { + return """ + # Fashion Guide Skill + + I provide comprehensive industry guidance for clothing store owners. + + ## What I Can Help With + + 1. **Reference Materials** - Access detailed reports and guides + 2. **Market Insights** - Stay updated with industry dynamics + + ## How to Use + + ### Access Detailed References + I provide extensive reference materials that you can access using the `loadSkillReference` tool: + + - **spring-trends**: Detailed Spring/Summer fashion trend report + - **summer-trends**: Summer seasonal trend analysis + - **buying-guide**: Comprehensive clothing buying guide + - **color-trends**: Current season's popular color palette + - **style-guide**: Style and outfit recommendations + + ### Example Questions + - "Show me the spring trends report" (triggers reference loading) + - "Where can I find the buying guide?" (triggers reference loading) + - "Give me the color trends document" (triggers reference loading) + + ## Tips + - Check trend overview first for a quick understanding + - Use reference materials for detailed analysis + - Combine with inventory and sales data for better decisions + """; + } + + /** + * Provides fashion reference material links + * + *

These links point to detailed fashion trend reports, buying guides, and style recommendation documents. + * LLM can load these materials through the loadSkillReference tool. + */ + @SkillReferences + public Map references() { + Map refs = new HashMap<>(); + + // Seasonal trend reports + refs.put("spring-trends", "https://fashion-industry.com/reports/2025-spring-summer-trends.pdf"); + refs.put("summer-trends", "https://fashion-industry.com/reports/2025-summer-seasonal-analysis.pdf"); + + // Buying guide + refs.put("buying-guide", "https://fashion-industry.com/guides/clothing-buyer-handbook.pdf"); + + // Color trends + refs.put("color-trends", "https://fashion-industry.com/trends/2025-color-palette.pdf"); + + // Style guide + refs.put("style-guide", "https://fashion-industry.com/guides/style-outfit-recommendations.pdf"); + + // Industry report + refs.put("market-report", "https://fashion-industry.com/reports/q1-2025-market-analysis.pdf"); + + // Sustainable fashion guide + refs.put("sustainability-guide", "https://fashion-industry.com/guides/sustainable-fashion-practices.pdf"); + + return refs; + } + + @SkillInit + public static FashionGuideSkill create() { + return new FashionGuideSkill(); + } +} diff --git a/skill/src/main/java/com/examples/clothing/skills/InventorySkill.java b/skill/src/main/java/com/examples/clothing/skills/InventorySkill.java new file mode 100644 index 0000000..a54d3b9 --- /dev/null +++ b/skill/src/main/java/com/examples/clothing/skills/InventorySkill.java @@ -0,0 +1,149 @@ +/* + * Copyright 2026 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.examples.clothing.skills; + +import org.springframework.ai.skill.annotation.Skill; +import org.springframework.ai.skill.annotation.SkillContent; +import org.springframework.ai.skill.annotation.SkillInit; +import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.ai.skill.annotation.SkillTools; +import org.springframework.ai.support.ToolCallbacks; +import org.springframework.ai.tool.ToolCallback; +import org.springframework.ai.tool.annotation.Tool; + +/** + * Inventory Management Skill + * + *

Provides store inventory querying functionality + * + * @author Semir + */ +@Skill( + name = "inventory", + description = "Inventory management system for checking current stock levels of clothing items", + source = "example", + extensions = {"version=1.0.0", "category=retail"}) +public class InventorySkill { + + private static final Logger logger = LoggerFactory.getLogger(InventorySkill.class); + + private InventorySkill() {} + + @SkillInit + public static InventorySkill create() { + return new InventorySkill(); + } + + @SkillContent + public String content() { + return """ + # Inventory Management Skill + + Check current stock levels for all clothing categories in the store. + + ## Features + + - View current inventory by category + - Check stock levels and sizes + - Identify low-stock items + - Get inventory summary + + ## Available Tools + + - `checkInventory` - Check current inventory for a specific category or all categories + + ## Usage + + Ask questions like: + - "What's the current inventory for winter coats?" + - "Show me all inventory" + - "Which items are low in stock?" + """; + } + + @SkillTools + public List tools() { + return List.of(ToolCallbacks.from(this)); + } + + @Tool( + description = + "Check current inventory levels. Use category parameter to filter by clothing type (e.g., 'coat', 'sweater', 'jeans', 'dress', 'all'). Returns current stock count, sizes available, and stock status.") + public String checkInventory(String category) { + logger.info("checkInventory called with category={}", category); + System.out.println(String.format("[TOOL] checkInventory called with category=%s", category)); + + StringBuilder result = new StringBuilder(); + result.append("# Current Inventory Report\n\n"); + result.append(String.format("**Category**: %s\n", category)); + result.append(String.format("**Report Date**: %s\n\n", java.time.LocalDate.now())); + + if (category.equalsIgnoreCase("all") || category.equalsIgnoreCase("coat")) { + result.append("## Winter Coats\n"); + result.append("- **SKU**: COAT-001\n"); + result.append("- **Current Stock**: 15 units\n"); + result.append("- **Sizes**: S(3), M(5), L(4), XL(3)\n"); + result.append("- **Status**: āš ļø Low Stock\n"); + result.append("- **Retail Price**: $120/unit\n\n"); + } + + if (category.equalsIgnoreCase("all") || category.equalsIgnoreCase("sweater")) { + result.append("## Sweaters\n"); + result.append("- **SKU**: SWTR-001\n"); + result.append("- **Current Stock**: 45 units\n"); + result.append("- **Sizes**: S(10), M(15), L(12), XL(8)\n"); + result.append("- **Status**: āœ… Good Stock\n"); + result.append("- **Retail Price**: $65/unit\n\n"); + } + + if (category.equalsIgnoreCase("all") || category.equalsIgnoreCase("jeans")) { + result.append("## Jeans\n"); + result.append("- **SKU**: JEAN-001\n"); + result.append("- **Current Stock**: 8 units\n"); + result.append("- **Sizes**: 28(1), 30(2), 32(3), 34(2)\n"); + result.append("- **Status**: šŸ”“ Critical - Restock Needed\n"); + result.append("- **Retail Price**: $80/unit\n\n"); + } + + if (category.equalsIgnoreCase("all") || category.equalsIgnoreCase("dress")) { + result.append("## Dresses\n"); + result.append("- **SKU**: DRSS-001\n"); + result.append("- **Current Stock**: 25 units\n"); + result.append("- **Sizes**: S(8), M(10), L(5), XL(2)\n"); + result.append("- **Status**: āœ… Good Stock\n"); + result.append("- **Retail Price**: $95/unit\n\n"); + } + + if (category.equalsIgnoreCase("all") || category.equalsIgnoreCase("shirt")) { + result.append("## Shirts\n"); + result.append("- **SKU**: SHRT-001\n"); + result.append("- **Current Stock**: 12 units\n"); + result.append("- **Sizes**: S(2), M(4), L(4), XL(2)\n"); + result.append("- **Status**: āš ļø Low Stock\n"); + result.append("- **Retail Price**: $45/unit\n\n"); + } + + result.append("## Summary\n"); + result.append("- **Total Items**: 105 units\n"); + result.append("- **Low Stock Items**: 2 (Coats, Shirts)\n"); + result.append("- **Critical Items**: 1 (Jeans)\n"); + result.append("- **Action Required**: Consider restocking Jeans immediately\n"); + + return result.toString(); + } +} diff --git a/skill/src/main/java/com/examples/clothing/skills/PricingSkill.java b/skill/src/main/java/com/examples/clothing/skills/PricingSkill.java new file mode 100644 index 0000000..56f3b30 --- /dev/null +++ b/skill/src/main/java/com/examples/clothing/skills/PricingSkill.java @@ -0,0 +1,160 @@ +/* + * Copyright 2026 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.examples.clothing.skills; + + +import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.ai.skill.annotation.Skill; +import org.springframework.ai.skill.annotation.SkillContent; +import org.springframework.ai.skill.annotation.SkillInit; +import org.springframework.ai.skill.annotation.SkillTools; +import org.springframework.ai.support.ToolCallbacks; +import org.springframework.ai.tool.ToolCallback; +import org.springframework.ai.tool.annotation.Tool; + +/** + * Store Pricing Management Skill + * + *

Provides store product pricing and profit margin analysis + * + * @author Semir + */ +@Skill( + name = "pricing", + description = "Store pricing system for checking retail prices and profit margins", + source = "example", + extensions = {"version=1.0.0", "category=retail"}) +public class PricingSkill { + + private static final Logger logger = LoggerFactory.getLogger(PricingSkill.class); + + private PricingSkill() {} + + @SkillInit + public static PricingSkill create() { + return new PricingSkill(); + } + + @SkillContent + public String content() { + return """ + # Pricing Management Skill + + Analyze store pricing strategy and profit margins for clothing items. + + ## Features + + - Check retail prices by SKU + - Calculate profit margins + - Compare pricing across categories + - Analyze pricing competitiveness + + ## Available Tools + + - `getPricing` - Get detailed pricing information for specific items or categories + + ## Usage + + Ask questions like: + - "What's the pricing for winter coats?" + - "Show me profit margins for all items" + - "Get pricing for SKU COAT-001" + """; + } + + @SkillTools + public List tools() { + return List.of(ToolCallbacks.from(this)); + } + + @Tool( + description = + "Get pricing information including retail price, cost, and profit margin. Use sku parameter for specific item (e.g., 'COAT-001') or category name for all items in that category (e.g., 'coat', 'sweater', 'all').") + public String getPricing(String skuOrCategory) { + logger.info("getPricing called with skuOrCategory={}", skuOrCategory); + System.out.println(String.format("[TOOL] getPricing called with skuOrCategory=%s", skuOrCategory)); + + StringBuilder result = new StringBuilder(); + result.append("# Pricing Analysis Report\n\n"); + result.append(String.format("**Query**: %s\n", skuOrCategory)); + result.append(String.format("**Report Date**: %s\n\n", java.time.LocalDate.now())); + + if (skuOrCategory.equalsIgnoreCase("all") + || skuOrCategory.equalsIgnoreCase("coat") + || skuOrCategory.equalsIgnoreCase("COAT-001")) { + result.append("## Winter Coats (COAT-001)\n"); + result.append("- **Retail Price**: $120/unit\n"); + result.append("- **Cost Price**: $75/unit\n"); + result.append("- **Profit Margin**: $45 (37.5%)\n"); + result.append("- **Recommended Price Range**: $110-$135\n"); + result.append("- **Competitiveness**: āœ… Competitive\n\n"); + } + + if (skuOrCategory.equalsIgnoreCase("all") + || skuOrCategory.equalsIgnoreCase("sweater") + || skuOrCategory.equalsIgnoreCase("SWTR-001")) { + result.append("## Sweaters (SWTR-001)\n"); + result.append("- **Retail Price**: $65/unit\n"); + result.append("- **Cost Price**: $38/unit\n"); + result.append("- **Profit Margin**: $27 (41.5%)\n"); + result.append("- **Recommended Price Range**: $60-$75\n"); + result.append("- **Competitiveness**: āœ… Competitive\n\n"); + } + + if (skuOrCategory.equalsIgnoreCase("all") + || skuOrCategory.equalsIgnoreCase("jeans") + || skuOrCategory.equalsIgnoreCase("JEAN-001")) { + result.append("## Jeans (JEAN-001)\n"); + result.append("- **Retail Price**: $80/unit\n"); + result.append("- **Cost Price**: $45/unit\n"); + result.append("- **Profit Margin**: $35 (43.75%)\n"); + result.append("- **Recommended Price Range**: $75-$90\n"); + result.append("- **Competitiveness**: āœ… Competitive\n\n"); + } + + if (skuOrCategory.equalsIgnoreCase("all") + || skuOrCategory.equalsIgnoreCase("dress") + || skuOrCategory.equalsIgnoreCase("DRSS-001")) { + result.append("## Dresses (DRSS-001)\n"); + result.append("- **Retail Price**: $95/unit\n"); + result.append("- **Cost Price**: $52/unit\n"); + result.append("- **Profit Margin**: $43 (45.3%)\n"); + result.append("- **Recommended Price Range**: $90-$110\n"); + result.append("- **Competitiveness**: āœ… Competitive\n\n"); + } + + if (skuOrCategory.equalsIgnoreCase("all") + || skuOrCategory.equalsIgnoreCase("shirt") + || skuOrCategory.equalsIgnoreCase("SHRT-001")) { + result.append("## Shirts (SHRT-001)\n"); + result.append("- **Retail Price**: $45/unit\n"); + result.append("- **Cost Price**: $25/unit\n"); + result.append("- **Profit Margin**: $20 (44.4%)\n"); + result.append("- **Recommended Price Range**: $40-$50\n"); + result.append("- **Competitiveness**: āœ… Competitive\n\n"); + } + + result.append("## Overall Pricing Analysis\n"); + result.append("- **Average Profit Margin**: 42.5%\n"); + result.append("- **Highest Margin**: Dresses (45.3%)\n"); + result.append("- **Lowest Margin**: Winter Coats (37.5%)\n"); + result.append("- **Pricing Strategy**: Premium positioning with healthy margins\n"); + + return result.toString(); + } +} diff --git a/skill/src/main/java/com/examples/clothing/skills/PurchaseStrategySkill.java b/skill/src/main/java/com/examples/clothing/skills/PurchaseStrategySkill.java new file mode 100644 index 0000000..f20b64e --- /dev/null +++ b/skill/src/main/java/com/examples/clothing/skills/PurchaseStrategySkill.java @@ -0,0 +1,323 @@ +/* + * Copyright 2026 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.examples.clothing.skills; + +import org.springframework.ai.skill.annotation.Skill; +import org.springframework.ai.skill.annotation.SkillContent; +import org.springframework.ai.skill.annotation.SkillInit; +import org.springframework.ai.skill.annotation.SkillTools; +import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.ai.support.ToolCallbacks; +import org.springframework.ai.tool.ToolCallback; +import org.springframework.ai.tool.annotation.Tool; + +/** + * Purchase Strategy Skill + * + *

Provides intelligent restocking recommendations and purchase optimization strategies + * + * @author Semir + */ +@Skill( + name = "purchase", + description = "Smart purchasing strategy system that provides optimized restocking recommendations", + source = "example", + extensions = {"version=1.0.0", "category=retail", "ai-powered=true"}) +public class PurchaseStrategySkill { + + private static final Logger logger = LoggerFactory.getLogger(PurchaseStrategySkill.class); + + private PurchaseStrategySkill() {} + + @SkillInit + public static PurchaseStrategySkill create() { + return new PurchaseStrategySkill(); + } + + @SkillContent + public String content() { + return """ + # Purchase Strategy Skill + + Generate intelligent purchasing recommendations based on inventory, sales trends, and market conditions. + + ## Features + + - Smart restocking recommendations + - Budget optimization + - ROI analysis + - Seasonal planning + - Risk assessment + + ## Available Tools + + - `generatePurchaseStrategy` - Generate comprehensive purchasing strategy + - `optimizePurchaseOrder` - Optimize a specific purchase order for maximum ROI + + ## Usage + + Ask questions like: + - "Generate a purchase strategy for next week" + - "What should I order with a $10,000 budget?" + - "Optimize my purchase order for maximum profit" + """; + } + + @SkillTools + public List tools() { + return List.of(ToolCallbacks.from(this)); + } + + @Tool( + description = + "Generate comprehensive purchase strategy based on current conditions. Parameters: budget (available budget in USD, e.g., 10000), priority (focus area: 'profit', 'volume', 'balanced', 'risk-averse'). Returns prioritized purchase recommendations with ROI projections.") + public String generatePurchaseStrategy(double budget, String priority) { + logger.info("generatePurchaseStrategy called with budget={}, priority={}", budget, priority); + System.out.println(String.format( + "[TOOL] generatePurchaseStrategy called with budget=%.2f, priority=%s", budget, priority)); + + StringBuilder result = new StringBuilder(); + result.append("# Purchase Strategy Report\n\n"); + result.append(String.format("**Available Budget**: $%.2f\n", budget)); + result.append(String.format("**Strategy Priority**: %s\n", priority)); + result.append(String.format("**Report Date**: %s\n\n", java.time.LocalDate.now())); + + result.append("## Current Situation Analysis\n\n"); + result.append("### Critical Findings\n"); + result.append("- šŸ”“ **Critical Stock-out Risk**: Jeans (8 units, ~1 day left)\n"); + result.append("- āš ļø **Low Stock Alert**: Winter Coats (15 units, ~2 days left)\n"); + result.append("- āš ļø **Low Stock Alert**: Shirts (12 units, ~3 days left)\n"); + result.append("- āœ… **Adequate Stock**: Sweaters (45 units, ~4 days left)\n"); + result.append("- āœ… **Good Stock**: Dresses (25 units, ~2 weeks left)\n\n"); + + result.append("### Sales Trends\n"); + result.append("- **Hot Sellers**: Winter Coats (+45%), Sweaters (+35%)\n"); + result.append("- **Stable**: Jeans, Shirts\n"); + result.append("- **Declining**: Dresses (-15%)\n\n"); + + result.append("## Recommended Purchase Orders\n\n"); + + double totalCost = 0; + double projectedRevenue = 0; + + result.append("### Priority 1: URGENT - Jeans\n"); + result.append("- **Supplier SKU**: SUP-JEAN-C01\n"); + result.append("- **Recommended Quantity**: 70 units\n"); + result.append("- **Unit Cost**: $45.00 (with 7% bulk discount: $41.85)\n"); + result.append("- **Total Cost**: $2,929.50\n"); + result.append("- **Expected Revenue**: $5,600 (70 units Ɨ $80)\n"); + result.append("- **Projected Profit**: $2,670.50 (91% ROI)\n"); + result.append("- **Justification**: Critical stock-out prevention, consistent demand\n"); + result.append("- **Delivery**: 3-5 business days\n\n"); + totalCost += 2929.50; + projectedRevenue += 5600; + + result.append("### Priority 2: HIGH - Winter Coats\n"); + result.append("- **Supplier SKU**: SUP-COAT-W01\n"); + result.append("- **Recommended Quantity**: 50 units\n"); + result.append("- **Unit Cost**: $75.00 (with 5% bulk discount: $71.25)\n"); + result.append("- **Total Cost**: $3,562.50\n"); + result.append("- **Expected Revenue**: $6,000 (50 units Ɨ $120)\n"); + result.append("- **Projected Profit**: $2,437.50 (68% ROI)\n"); + result.append("- **Justification**: High demand (+45%), peak season, low stock\n"); + result.append("- **Delivery**: 3-5 business days\n\n"); + totalCost += 3562.50; + projectedRevenue += 6000; + + if (budget >= 8000) { + result.append("### Priority 3: MEDIUM - Sweaters\n"); + result.append("- **Supplier SKU**: SUP-SWTR-W01\n"); + result.append("- **Recommended Quantity**: 100 units\n"); + result.append("- **Unit Cost**: $38.00 (with 8% bulk discount: $34.96)\n"); + result.append("- **Total Cost**: $3,496.00\n"); + result.append("- **Expected Revenue**: $6,500 (100 units Ɨ $65)\n"); + result.append("- **Projected Profit**: $3,004.00 (86% ROI)\n"); + result.append("- **Justification**: Top seller (+35%), stock sufficient for 4 days only\n"); + result.append("- **Delivery**: 2-4 business days\n\n"); + totalCost += 3496.00; + projectedRevenue += 6500; + } + + if (budget >= 11000) { + result.append("### Priority 4: MEDIUM - Shirts\n"); + result.append("- **Supplier SKU**: SUP-SHRT-C01\n"); + result.append("- **Recommended Quantity**: 40 units\n"); + result.append("- **Unit Cost**: $25.00\n"); + result.append("- **Total Cost**: $1,000.00\n"); + result.append("- **Expected Revenue**: $1,800 (40 units Ɨ $45)\n"); + result.append("- **Projected Profit**: $800.00 (80% ROI)\n"); + result.append("- **Justification**: Low stock, consistent basic item demand\n"); + result.append("- **Delivery**: 2-3 business days\n\n"); + totalCost += 1000.00; + projectedRevenue += 1800; + } + + result.append("## Financial Summary\n\n"); + result.append(String.format("- **Total Investment**: $%.2f\n", totalCost)); + result.append(String.format("- **Budget Remaining**: $%.2f\n", budget - totalCost)); + result.append(String.format("- **Budget Utilization**: %.1f%%\n", (totalCost / budget) * 100)); + result.append(String.format("- **Projected Revenue**: $%.2f\n", projectedRevenue)); + result.append(String.format("- **Projected Profit**: $%.2f\n", projectedRevenue - totalCost)); + result.append( + String.format("- **Expected ROI**: %.1f%%\n\n", ((projectedRevenue - totalCost) / totalCost) * 100)); + + result.append("## Risk Assessment\n\n"); + result.append("- **Stock-out Risk**: šŸ”“ High (Jeans critical)\n"); + result.append("- **Over-stock Risk**: āœ… Low (all orders based on demand)\n"); + result.append("- **Weather Risk**: āš ļø Medium (winter items dependent on cold weather)\n"); + result.append("- **Cash Flow Risk**: āœ… Low (Net 30 payment terms)\n\n"); + + result.append("## Implementation Plan\n\n"); + result.append("**Immediate Actions (Today)**:\n"); + result.append("1. Place order for Jeans (70 units) - URGENT\n"); + result.append("2. Place order for Winter Coats (50 units) - HIGH PRIORITY\n\n"); + + result.append("**This Week**:\n"); + result.append("3. Monitor Sweater sales velocity\n"); + result.append("4. Place order for Sweaters (100 units) if budget allows\n\n"); + + result.append("**Next Week**:\n"); + result.append("5. Review shirt inventory after first orders arrive\n"); + result.append("6. Adjust strategy based on actual sales data\n\n"); + + result.append("## Additional Recommendations\n"); + result.append("- šŸ’” **Promotional Opportunity**: Consider 10-15% discount on Dresses to clear inventory\n"); + result.append("- šŸ“Š **Inventory Target**: Maintain 7-10 days of stock for fast-moving items\n"); + result.append("- šŸŽÆ **Focus**: Winter items are in peak demand - maximize this opportunity\n"); + result.append("- āš™ļø **Process**: Implement daily inventory checks during peak season\n"); + + return result.toString(); + } + + @Tool( + description = + "Optimize a specific purchase order for maximum ROI. Parameters: items (comma-separated list of supplier SKUs, e.g., 'SUP-COAT-W01,SUP-JEAN-C01'), quantities (comma-separated quantities matching items, e.g., '50,70'). Returns optimization suggestions including cost efficiency and profit maximization tips.") + public String optimizePurchaseOrder(String items, String quantities) { + logger.info("optimizePurchaseOrder called with items={}, quantities={}", items, quantities); + System.out.println( + String.format("[TOOL] optimizePurchaseOrder called with items=%s, quantities=%s", items, quantities)); + + StringBuilder result = new StringBuilder(); + result.append("# Purchase Order Optimization\n\n"); + result.append(String.format("**Order Date**: %s\n", java.time.LocalDate.now())); + result.append(String.format("**Order ID**: PO-%05d\n\n", 12345)); + + String[] itemArray = items.split(","); + String[] qtyArray = quantities.split(","); + + if (itemArray.length != qtyArray.length) { + return "Error: Number of items and quantities must match."; + } + + result.append("## Order Analysis\n\n"); + + double totalCost = 0; + double optimizedCost = 0; + + for (int i = 0; i < itemArray.length; i++) { + String sku = itemArray[i].trim(); + int qty = Integer.parseInt(qtyArray[i].trim()); + + result.append(String.format("### Item %d: %s\n", i + 1, sku)); + result.append(String.format("- **Requested Quantity**: %d units\n", qty)); + + double unitCost; + int bulkThreshold; + double bulkDiscount; + int minOrder; + + switch (sku.toUpperCase()) { + case "SUP-COAT-W01": + unitCost = 75.0; + bulkThreshold = 50; + bulkDiscount = 0.05; + minOrder = 10; + break; + case "SUP-SWTR-W01": + unitCost = 38.0; + bulkThreshold = 100; + bulkDiscount = 0.08; + minOrder = 20; + break; + case "SUP-JEAN-C01": + unitCost = 45.0; + bulkThreshold = 50; + bulkDiscount = 0.07; + minOrder = 15; + break; + case "SUP-SHRT-C01": + unitCost = 25.0; + bulkThreshold = 100; + bulkDiscount = 0.10; + minOrder = 20; + break; + case "SUP-DRSS-F01": + unitCost = 52.0; + bulkThreshold = 40; + bulkDiscount = 0.06; + minOrder = 12; + break; + default: + continue; + } + + double itemCost = unitCost * qty; + totalCost += itemCost; + + result.append(String.format("- **Unit Cost**: $%.2f\n", unitCost)); + result.append(String.format("- **Current Total**: $%.2f\n", itemCost)); + + if (qty >= bulkThreshold) { + double discountedCost = unitCost * (1 - bulkDiscount) * qty; + optimizedCost += discountedCost; + result.append(String.format("- āœ… **Bulk Discount Applied**: %.0f%% off\n", bulkDiscount * 100)); + result.append(String.format("- **Optimized Total**: $%.2f\n", discountedCost)); + result.append(String.format("- **Savings**: $%.2f\n\n", itemCost - discountedCost)); + } else { + optimizedCost += itemCost; + int neededForDiscount = bulkThreshold - qty; + double potentialSavings = unitCost * bulkDiscount * bulkThreshold; + result.append(String.format( + "- āš ļø **Optimization Opportunity**: Order %d more units to get %.0f%% discount\n", + neededForDiscount, bulkDiscount * 100)); + result.append(String.format("- **Potential Savings**: $%.2f\n\n", potentialSavings)); + } + } + + result.append("## Optimization Summary\n\n"); + result.append(String.format("- **Original Cost**: $%.2f\n", totalCost)); + result.append(String.format("- **Optimized Cost**: $%.2f\n", optimizedCost)); + result.append(String.format("- **Total Savings**: $%.2f\n", totalCost - optimizedCost)); + result.append( + String.format("- **Cost Reduction**: %.1f%%\n\n", ((totalCost - optimizedCost) / totalCost) * 100)); + + result.append("## Recommendations\n\n"); + result.append("1. **Consolidate Orders**: Combine orders to reach bulk discount thresholds\n"); + result.append("2. **Timing**: Place orders early in the week for faster delivery\n"); + result.append("3. **Payment**: Use Net 30 terms to preserve cash flow\n"); + result.append("4. **Shipping**: Orders over $2,000 get free shipping - optimize accordingly\n"); + result.append("5. **Relationships**: Regular orders may qualify for additional supplier discounts\n\n"); + + result.append("## Next Steps\n"); + result.append("- Review and approve optimized order\n"); + result.append("- Contact supplier to confirm availability\n"); + result.append("- Schedule delivery to match expected demand\n"); + + return result.toString(); + } +} diff --git a/skill/src/main/java/com/examples/clothing/skills/SupplierSkill.java b/skill/src/main/java/com/examples/clothing/skills/SupplierSkill.java new file mode 100644 index 0000000..7048242 --- /dev/null +++ b/skill/src/main/java/com/examples/clothing/skills/SupplierSkill.java @@ -0,0 +1,278 @@ +/* + * Copyright 2026 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.examples.clothing.skills; + +import org.springframework.ai.skill.annotation.Skill; +import org.springframework.ai.skill.annotation.SkillContent; +import org.springframework.ai.skill.annotation.SkillInit; +import org.springframework.ai.skill.annotation.SkillTools; +import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.ai.support.ToolCallbacks; +import org.springframework.ai.tool.ToolCallback; +import org.springframework.ai.tool.annotation.Tool; + +/** + * Supplier Management Skill + * + *

Provides supplier catalog and wholesale cost querying + * + * @author Semir + */ +@Skill( + name = "supplier", + description = "Supplier catalog system for checking available items and wholesale costs", + source = "example", + extensions = {"version=1.0.0", "category=retail"}) +public class SupplierSkill { + + private static final Logger logger = LoggerFactory.getLogger(SupplierSkill.class); + + private SupplierSkill() {} + + @SkillInit + public static SupplierSkill create() { + return new SupplierSkill(); + } + + @SkillContent + public String content() { + return """ + # Supplier Catalog Skill + + Access supplier inventory and wholesale pricing information. + + ## Features + + - Browse supplier catalog + - Check wholesale prices + - View available quantities + - Get delivery information + + ## Available Tools + + - `getSupplierCatalog` - Get supplier catalog for specific categories or all items + - `getSupplierQuote` - Get detailed quote including bulk discounts + + ## Usage + + Ask questions like: + - "What items are available from the supplier?" + - "Get supplier catalog for winter items" + - "Get a quote for 50 units of coats" + """; + } + + @SkillTools + public List tools() { + return List.of(ToolCallbacks.from(this)); + } + + @Tool( + description = + "Get supplier catalog showing available items for purchase. Use category parameter to filter (e.g., 'winter', 'summer', 'formal', 'casual', 'all'). Returns item details, wholesale cost, minimum order quantity, and availability.") + public String getSupplierCatalog(String category) { + logger.info("getSupplierCatalog called with category={}", category); + System.out.println(String.format("[TOOL] getSupplierCatalog called with category=%s", category)); + + StringBuilder result = new StringBuilder(); + result.append("# Supplier Catalog\n\n"); + result.append(String.format("**Category**: %s\n", category)); + result.append(String.format("**Catalog Date**: %s\n", java.time.LocalDate.now())); + result.append("**Supplier**: Fashion Wholesale Co.\n\n"); + + if (category.equalsIgnoreCase("all") + || category.toLowerCase().contains("winter") + || category.toLowerCase().contains("coat")) { + result.append("## Winter Collection\n\n"); + + result.append("### Premium Winter Coats\n"); + result.append("- **Supplier SKU**: SUP-COAT-W01\n"); + result.append("- **Wholesale Cost**: $75/unit\n"); + result.append("- **Min Order**: 10 units\n"); + result.append("- **Available Stock**: 500+ units\n"); + result.append("- **Sizes**: S, M, L, XL, XXL\n"); + result.append("- **Colors**: Black, Navy, Gray, Burgundy\n"); + result.append("- **Delivery**: 3-5 business days\n"); + result.append("- **Bulk Discount**: 5% off for 50+ units\n\n"); + + result.append("### Wool Sweaters\n"); + result.append("- **Supplier SKU**: SUP-SWTR-W01\n"); + result.append("- **Wholesale Cost**: $38/unit\n"); + result.append("- **Min Order**: 20 units\n"); + result.append("- **Available Stock**: 800+ units\n"); + result.append("- **Sizes**: S, M, L, XL\n"); + result.append("- **Colors**: Multiple colors available\n"); + result.append("- **Delivery**: 2-4 business days\n"); + result.append("- **Bulk Discount**: 8% off for 100+ units\n\n"); + } + + if (category.equalsIgnoreCase("all") + || category.toLowerCase().contains("casual") + || category.toLowerCase().contains("jeans")) { + result.append("## Casual Collection\n\n"); + + result.append("### Premium Denim Jeans\n"); + result.append("- **Supplier SKU**: SUP-JEAN-C01\n"); + result.append("- **Wholesale Cost**: $45/unit\n"); + result.append("- **Min Order**: 15 units\n"); + result.append("- **Available Stock**: 600+ units\n"); + result.append("- **Sizes**: 28, 30, 32, 34, 36, 38\n"); + result.append("- **Styles**: Slim fit, Regular fit, Relaxed fit\n"); + result.append("- **Delivery**: 3-5 business days\n"); + result.append("- **Bulk Discount**: 7% off for 50+ units\n\n"); + + result.append("### Cotton Shirts\n"); + result.append("- **Supplier SKU**: SUP-SHRT-C01\n"); + result.append("- **Wholesale Cost**: $25/unit\n"); + result.append("- **Min Order**: 20 units\n"); + result.append("- **Available Stock**: 1000+ units\n"); + result.append("- **Sizes**: S, M, L, XL, XXL\n"); + result.append("- **Colors**: White, Blue, Black, Gray\n"); + result.append("- **Delivery**: 2-3 business days\n"); + result.append("- **Bulk Discount**: 10% off for 100+ units\n\n"); + } + + if (category.equalsIgnoreCase("all") + || category.toLowerCase().contains("formal") + || category.toLowerCase().contains("dress")) { + result.append("## Formal Collection\n\n"); + + result.append("### Evening Dresses\n"); + result.append("- **Supplier SKU**: SUP-DRSS-F01\n"); + result.append("- **Wholesale Cost**: $52/unit\n"); + result.append("- **Min Order**: 12 units\n"); + result.append("- **Available Stock**: 400+ units\n"); + result.append("- **Sizes**: S, M, L, XL\n"); + result.append("- **Styles**: A-line, Bodycon, Maxi\n"); + result.append("- **Delivery**: 4-6 business days\n"); + result.append("- **Bulk Discount**: 6% off for 40+ units\n\n"); + } + + result.append("## Payment & Delivery Terms\n"); + result.append("- **Payment**: Net 30 days for established customers\n"); + result.append("- **Shipping**: Free shipping for orders over $2,000\n"); + result.append("- **Returns**: 14-day return policy on unused items\n"); + result.append("- **Express Delivery**: Available for urgent orders (+$50)\n"); + + return result.toString(); + } + + @Tool( + description = + "Get detailed quote for a specific supplier SKU with quantity. Parameters: supplierSku (e.g., 'SUP-COAT-W01'), quantity (number of units). Returns itemized quote with unit cost, bulk discounts, total cost, and delivery estimate.") + public String getSupplierQuote(String supplierSku, int quantity) { + logger.info("getSupplierQuote called with supplierSku={}, quantity={}", supplierSku, quantity); + System.out.println(String.format( + "[TOOL] getSupplierQuote called with supplierSku=%s, quantity=%d", supplierSku, quantity)); + + StringBuilder result = new StringBuilder(); + result.append("# Supplier Quote\n\n"); + result.append(String.format("**Quote ID**: QT-%05d\n", 12345)); + result.append(String.format("**Date**: %s\n", java.time.LocalDate.now())); + result.append("**Supplier**: Fashion Wholesale Co.\n\n"); + + String itemName; + double basePrice; + int minOrder; + int bulkThreshold; + double bulkDiscount; + + switch (supplierSku.toUpperCase()) { + case "SUP-COAT-W01": + itemName = "Premium Winter Coats"; + basePrice = 75.0; + minOrder = 10; + bulkThreshold = 50; + bulkDiscount = 0.05; + break; + case "SUP-SWTR-W01": + itemName = "Wool Sweaters"; + basePrice = 38.0; + minOrder = 20; + bulkThreshold = 100; + bulkDiscount = 0.08; + break; + case "SUP-JEAN-C01": + itemName = "Premium Denim Jeans"; + basePrice = 45.0; + minOrder = 15; + bulkThreshold = 50; + bulkDiscount = 0.07; + break; + case "SUP-SHRT-C01": + itemName = "Cotton Shirts"; + basePrice = 25.0; + minOrder = 20; + bulkThreshold = 100; + bulkDiscount = 0.10; + break; + case "SUP-DRSS-F01": + itemName = "Evening Dresses"; + basePrice = 52.0; + minOrder = 12; + bulkThreshold = 40; + bulkDiscount = 0.06; + break; + default: + return String.format("Error: Supplier SKU '%s' not found in catalog.", supplierSku); + } + + result.append(String.format("## %s (%s)\n\n", itemName, supplierSku)); + result.append(String.format("**Quantity Requested**: %d units\n", quantity)); + + if (quantity < minOrder) { + result.append(String.format("āš ļø **Warning**: Minimum order quantity is %d units\n\n", minOrder)); + return result.toString(); + } + + result.append(String.format("**Unit Price**: $%.2f\n", basePrice)); + + double discount = 0; + if (quantity >= bulkThreshold) { + discount = bulkDiscount; + result.append(String.format("**Bulk Discount**: %.0f%% (for %d+ units)\n", discount * 100, bulkThreshold)); + } else { + result.append("**Bulk Discount**: Not applicable\n"); + } + + double discountedPrice = basePrice * (1 - discount); + double subtotal = discountedPrice * quantity; + double shipping = subtotal >= 2000 ? 0 : 80; + double total = subtotal + shipping; + + result.append(String.format("**Discounted Price**: $%.2f/unit\n\n", discountedPrice)); + + result.append("## Cost Breakdown\n"); + result.append(String.format("- Subtotal: $%.2f (%d units Ɨ $%.2f)\n", subtotal, quantity, discountedPrice)); + result.append( + String.format("- Shipping: $%.2f %s\n", shipping, shipping == 0 ? "(Free - order over $2,000)" : "")); + result.append(String.format("- **Total Cost**: $%.2f\n\n", total)); + + result.append("## Delivery Information\n"); + result.append("- **Estimated Delivery**: 3-5 business days\n"); + result.append("- **Express Option**: Available (+$50 for next day)\n\n"); + + result.append("## Additional Information\n"); + result.append("- **Payment Terms**: Net 30 days\n"); + result.append("- **Valid Until**: 7 days from quote date\n"); + result.append(String.format( + "- **Potential Profit**: $%.2f (if sold at retail price)\n", (120 - discountedPrice) * quantity)); + + return result.toString(); + } +} diff --git a/skill/src/main/java/com/examples/clothing/skills/TrendSkill.java b/skill/src/main/java/com/examples/clothing/skills/TrendSkill.java new file mode 100644 index 0000000..cf3ede8 --- /dev/null +++ b/skill/src/main/java/com/examples/clothing/skills/TrendSkill.java @@ -0,0 +1,236 @@ +/* + * Copyright 2026 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.examples.clothing.skills; + +import org.springframework.ai.skill.annotation.Skill; +import org.springframework.ai.skill.annotation.SkillContent; +import org.springframework.ai.skill.annotation.SkillInit; +import org.springframework.ai.skill.annotation.SkillTools; +import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.ai.support.ToolCallbacks; +import org.springframework.ai.tool.ToolCallback; +import org.springframework.ai.tool.annotation.Tool; + +/** + * Sales Trend Analysis Skill + * + *

Provides hot-selling item trends and sales data analysis + * + * @author Semir + */ +@Skill( + name = "trend", + description = "Sales trend analysis system for tracking popular items and market demand", + source = "example", + extensions = {"version=1.0.0", "category=retail"}) +public class TrendSkill { + + private static final Logger logger = LoggerFactory.getLogger(TrendSkill.class); + + private TrendSkill() {} + + @SkillInit + public static TrendSkill create() { + return new TrendSkill(); + } + + @SkillContent + public String content() { + return """ + # Sales Trend Analysis Skill + + Analyze sales trends and market demand for clothing items. + + ## Features + + - View hot-selling items + - Track sales velocity + - Analyze seasonal trends + - Predict demand patterns + + ## Available Tools + + - `getSalesTrends` - Get current sales trends and popular items + - `getPredictedDemand` - Get demand predictions for upcoming period + + ## Usage + + Ask questions like: + - "What items are trending this week?" + - "Show me sales trends for winter items" + - "What's the predicted demand for next month?" + """; + } + + @SkillTools + public List tools() { + return List.of(ToolCallbacks.from(this)); + } + + @Tool( + description = + "Get sales trends and popular items. Use period parameter to specify timeframe ('week', 'month', 'season'). Returns trending items, sales velocity, and market demand indicators.") + public String getSalesTrends(String period) { + logger.info("getSalesTrends called with period={}", period); + System.out.println(String.format("[TOOL] getSalesTrends called with period=%s", period)); + + StringBuilder result = new StringBuilder(); + result.append("# Sales Trends Report\n\n"); + result.append(String.format("**Period**: Past %s\n", period)); + result.append(String.format("**Report Date**: %s\n\n", java.time.LocalDate.now())); + + result.append("## Top Selling Items\n\n"); + + result.append("### 1. šŸ”„ Wool Sweaters (SWTR-001)\n"); + result.append("- **Units Sold**: 85 units\n"); + result.append("- **Sales Velocity**: 12 units/day\n"); + result.append("- **Revenue**: $5,525\n"); + result.append("- **Trend**: ā¬†ļø +35% vs last period\n"); + result.append("- **Reason**: Cold weather surge\n\n"); + + result.append("### 2. šŸ”„ Winter Coats (COAT-001)\n"); + result.append("- **Units Sold**: 62 units\n"); + result.append("- **Sales Velocity**: 9 units/day\n"); + result.append("- **Revenue**: $7,440\n"); + result.append("- **Trend**: ā¬†ļø +45% vs last period\n"); + result.append("- **Reason**: Winter season peak\n\n"); + + result.append("### 3. Jeans (JEAN-001)\n"); + result.append("- **Units Sold**: 48 units\n"); + result.append("- **Sales Velocity**: 7 units/day\n"); + result.append("- **Revenue**: $3,840\n"); + result.append("- **Trend**: āž”ļø Stable\n"); + result.append("- **Reason**: Consistent demand\n\n"); + + result.append("### 4. Dresses (DRSS-001)\n"); + result.append("- **Units Sold**: 35 units\n"); + result.append("- **Sales Velocity**: 5 units/day\n"); + result.append("- **Revenue**: $3,325\n"); + result.append("- **Trend**: ā¬‡ļø -15% vs last period\n"); + result.append("- **Reason**: Off-season\n\n"); + + result.append("### 5. Shirts (SHRT-001)\n"); + result.append("- **Units Sold**: 28 units\n"); + result.append("- **Sales Velocity**: 4 units/day\n"); + result.append("- **Revenue**: $1,260\n"); + result.append("- **Trend**: āž”ļø Stable\n"); + result.append("- **Reason**: Basic item\n\n"); + + result.append("## Market Insights\n"); + result.append("- **Hot Categories**: Winter wear (coats, sweaters)\n"); + result.append("- **Peak Shopping Days**: Weekends and Fridays\n"); + result.append("- **Average Transaction**: $185\n"); + result.append("- **Customer Preference**: Quality over quantity\n\n"); + + result.append("## Recommendations\n"); + result.append("- āš ļø **Action Required**: Restock winter coats immediately (high demand)\n"); + result.append("- āœ… **Sweater stock is adequate** but monitor closely\n"); + result.append("- šŸ“Š **Jeans need urgent restocking** (critical low inventory)\n"); + result.append("- šŸ’” **Consider promotion** on dresses to boost off-season sales\n"); + + return result.toString(); + } + + @Tool( + description = + "Get demand predictions for the next period. Use category parameter to filter predictions by clothing type (e.g., 'winter', 'casual', 'formal', 'all'). Returns forecasted demand, recommended stock levels, and confidence scores.") + public String getPredictedDemand(String category) { + logger.info("getPredictedDemand called with category={}", category); + System.out.println(String.format("[TOOL] getPredictedDemand called with category=%s", category)); + + StringBuilder result = new StringBuilder(); + result.append("# Demand Prediction Report\n\n"); + result.append(String.format("**Category**: %s\n", category)); + result.append("**Forecast Period**: Next 30 days\n"); + result.append(String.format("**Report Date**: %s\n\n", java.time.LocalDate.now())); + + if (category.equalsIgnoreCase("all") + || category.toLowerCase().contains("winter") + || category.toLowerCase().contains("coat")) { + result.append("## Winter Coats (COAT-001)\n"); + result.append("- **Predicted Demand**: 90-110 units\n"); + result.append("- **Current Stock**: 15 units\n"); + result.append("- **Recommended Order**: 80-100 units\n"); + result.append("- **Confidence**: 92% (High)\n"); + result.append("- **Factors**: Peak winter season, historical data\n"); + result.append("- **Risk**: āš ļø High - Stock-out likely within 2 days\n\n"); + } + + if (category.equalsIgnoreCase("all") + || category.toLowerCase().contains("winter") + || category.toLowerCase().contains("sweater")) { + result.append("## Sweaters (SWTR-001)\n"); + result.append("- **Predicted Demand**: 120-140 units\n"); + result.append("- **Current Stock**: 45 units\n"); + result.append("- **Recommended Order**: 80-100 units\n"); + result.append("- **Confidence**: 88% (High)\n"); + result.append("- **Factors**: Cold weather continuation\n"); + result.append("- **Risk**: āš ļø Medium - Stock sufficient for 4 days\n\n"); + } + + if (category.equalsIgnoreCase("all") + || category.toLowerCase().contains("casual") + || category.toLowerCase().contains("jeans")) { + result.append("## Jeans (JEAN-001)\n"); + result.append("- **Predicted Demand**: 75-85 units\n"); + result.append("- **Current Stock**: 8 units\n"); + result.append("- **Recommended Order**: 70-80 units\n"); + result.append("- **Confidence**: 85% (High)\n"); + result.append("- **Factors**: Consistent year-round demand\n"); + result.append("- **Risk**: šŸ”“ Critical - Stock-out imminent (1 day)\n\n"); + } + + if (category.equalsIgnoreCase("all") + || category.toLowerCase().contains("formal") + || category.toLowerCase().contains("dress")) { + result.append("## Dresses (DRSS-001)\n"); + result.append("- **Predicted Demand**: 40-50 units\n"); + result.append("- **Current Stock**: 25 units\n"); + result.append("- **Recommended Order**: 20-30 units\n"); + result.append("- **Confidence**: 75% (Medium)\n"); + result.append("- **Factors**: Off-season, special events\n"); + result.append("- **Risk**: āœ… Low - Stock sufficient for 2 weeks\n\n"); + } + + if (category.equalsIgnoreCase("all") + || category.toLowerCase().contains("casual") + || category.toLowerCase().contains("shirt")) { + result.append("## Shirts (SHRT-001)\n"); + result.append("- **Predicted Demand**: 50-60 units\n"); + result.append("- **Current Stock**: 12 units\n"); + result.append("- **Recommended Order**: 40-50 units\n"); + result.append("- **Confidence**: 82% (High)\n"); + result.append("- **Factors**: Basic wardrobe item\n"); + result.append("- **Risk**: āš ļø Medium - Stock sufficient for 3 days\n\n"); + } + + result.append("## Overall Forecast Summary\n"); + result.append("- **Total Predicted Sales**: $32,000-$38,000\n"); + result.append("- **Recommended Investment**: $18,000-$22,000\n"); + result.append("- **Expected ROI**: 75-85%\n\n"); + + result.append("## Strategic Recommendations\n"); + result.append("1. **Priority 1 (Urgent)**: Order jeans immediately to avoid stock-out\n"); + result.append("2. **Priority 2 (High)**: Restock winter coats within 48 hours\n"); + result.append("3. **Priority 3 (Medium)**: Order sweaters for next week\n"); + result.append("4. **Priority 4 (Low)**: Monitor shirt sales, order if needed\n"); + result.append("5. **Opportunity**: Dresses have low demand - consider promotional strategy\n"); + + return result.toString(); + } +} diff --git a/skill/src/main/java/com/examples/clothing/skills/WeatherSkill.java b/skill/src/main/java/com/examples/clothing/skills/WeatherSkill.java new file mode 100644 index 0000000..f33cace --- /dev/null +++ b/skill/src/main/java/com/examples/clothing/skills/WeatherSkill.java @@ -0,0 +1,163 @@ +/* + * Copyright 2026 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.examples.clothing.skills; + +import org.springframework.ai.skill.annotation.Skill; +import org.springframework.ai.skill.annotation.SkillContent; +import org.springframework.ai.skill.annotation.SkillInit; +import org.springframework.ai.skill.annotation.SkillTools; +import java.util.List; +import java.util.Random; +import org.springframework.ai.support.ToolCallbacks; +import org.springframework.ai.tool.ToolCallback; +import org.springframework.ai.tool.annotation.Tool; + +/** + * Weather Skill - Annotation-Based Example + * + *

Demonstrates how to implement a Skill using annotation mode: + *

    + *
  • Pure POJO class, no interface implementation required
  • + *
  • Use @Skill to annotate the class
  • + *
  • Use @SkillContent to annotate the content method
  • + *
  • Use @SkillTools to annotate the tools method
  • + *
  • Framework automatically wraps it as SkillProxy
  • + *
+ * + *

Use Cases: General users, standard scenarios, rapid development + * + *

Notes: + *

    + *
  • Method names can be freely named (doesn't have to be getContent)
  • + *
  • Framework identifies methods through annotations and invokes via reflection
  • + *
  • Slightly slower than interface mode (reflection invocation), but fast enough for most scenarios
  • + *
+ * + * @author Semir + */ +@Skill( + name = "weather", + description = "Provides weather information for cities around the world", + source = "example", + extensions = {"version=1.0.0", "author=Semir", "category=information"}) +public class WeatherSkill { + + private final Random random = new Random(); + + // Simulated weather conditions + private static final String[] WEATHER_CONDITIONS = { + "Sunny", "Cloudy", "Rainy", "Snowy", "Windy", "Foggy", "Partly Cloudy" + }; + + /** + * Private constructor + */ + private WeatherSkill() {} + + /** + * Skill initialization method - Factory method + * + *

Used for lazy loading to create WeatherSkill instance + */ + @SkillInit + public static WeatherSkill create() { + return new WeatherSkill(); + } + + /** + * Method that returns Skill content + * + *

Method name can be freely named, framework identifies it through @SkillContent annotation + */ + @SkillContent + public String content() { + return """ + # Weather Skill + + Provides weather information for cities around the world. + + ## Features + + - Get current weather for any city + - Temperature in Celsius + - Weather conditions (Sunny, Rainy, Cloudy, etc.) + + ## Available Tools + + - `getWeather(city)` - Get current weather for a specific city + + ## Usage + + Ask me "What's the weather in Beijing?" or "Tell me the weather in New York". + """; + } + + /** + * Method that returns the tool list + * + *

Returns available tool callbacks, using Spring AI's ToolCallbacks utility class + */ + @SkillTools + public List tools() { + // Use Spring AI's ToolCallbacks.from() to wrap @Tool annotated methods into tools + return List.of(ToolCallbacks.from(this)); + } + + /** + * Get weather information for a specified city (simulated data) + * + *

This is the actual tool method called by AI, annotated with @Tool + * + * @param city City name + * @return Weather information as JSON string + */ + @Tool( + description = + "Get current weather information for a specific city. Returns temperature in Celsius, weather condition, humidity percentage, and wind speed in km/h.") + public String getWeather(String city) { + if (city == null || city.trim().isEmpty()) { + return "{\"error\": \"City name is required\"}"; + } + + // Generate random temperature (-10 to 40 Celsius) + int temperature = random.nextInt(51) - 10; + + // Randomly select weather condition + String condition = WEATHER_CONDITIONS[random.nextInt(WEATHER_CONDITIONS.length)]; + + // Generate random humidity (30% - 90%) + int humidity = random.nextInt(61) + 30; + + // Generate random wind speed (0 - 30 km/h) + int windSpeed = random.nextInt(31); + + // Return weather information in JSON format + return String.format( + """ + { + "city": "%s", + "temperature": %d, + "unit": "Celsius", + "condition": "%s", + "humidity": %d, + "windSpeed": %d, + "windUnit": "km/h", + "description": "The weather in %s is %s with a temperature of %d°C, humidity at %d%%, and wind speed of %d km/h." + } + """, + city, temperature, condition, humidity, windSpeed, city, condition, temperature, humidity, windSpeed); + } +} diff --git a/skill/src/main/resources/logback.xml b/skill/src/main/resources/logback.xml new file mode 100644 index 0000000..03bad50 --- /dev/null +++ b/skill/src/main/resources/logback.xml @@ -0,0 +1,40 @@ + + + + + + + + + %d{HH:mm:ss.SSS} %highlight(%-5level) %cyan(%logger{36}) - %msg%n + utf-8 + + + + + + + + + + + + + + + +