{"id":4017,"date":"2026-08-17T10:00:00","date_gmt":"2026-08-17T14:00:00","guid":{"rendered":"https:\/\/www.mymiller.name\/wordpress\/?p=4017"},"modified":"2026-08-16T08:28:43","modified_gmt":"2026-08-16T12:28:43","slug":"extending-spring-ai-with-agent-skills-executing-python-scripts-via-skill-md","status":"publish","type":"post","link":"https:\/\/www.mymiller.name\/wordpress\/spring_ai\/extending-spring-ai-with-agent-skills-executing-python-scripts-via-skill-md\/","title":{"rendered":"Extending Spring AI with Agent Skills: Executing Python Scripts via SKILL.md"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">As AI agents evolve from simple chat interfaces to autonomous problem-solving systems, managing prompt bloat and custom tool integrations becomes a critical architectural challenge. If you embed every instruction, workflow, and script binding directly into system prompts, your model context windows quickly saturate, token costs soar, and agent behavior becomes erratic.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Enter <strong>Agent Skills<\/strong>\u2014an open standard (originally released by Anthropic and maintained at <a href=\"https:\/\/agentskills.io\/\">agentskills.io<\/a>) designed to package specialized capabilities into self-contained, version-controlled directories.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In this article, we\u2019ll explore how to leverage the <strong><code>SKILL.md<\/code><\/strong> manifest inside a <strong>Spring AI<\/strong> application, configure local LLMs using <strong>Ollama<\/strong>, and empower your agent to dynamically discover, activate, and execute <strong>Python scripts<\/strong> stored inside skill directories.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">1. What is an Agent Skill and <code>SKILL.md<\/code>?<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">An Agent Skill is a standardized folder layout containing instructions, references, and executable code. At the heart of every skill is a <strong><code>SKILL.md<\/code><\/strong> manifest file.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Key Concepts &amp; Progressive Disclosure<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Instead of dumping every instruction into the agent\u2019s context window on every request, Agent Skills utilize <strong>progressive disclosure<\/strong>:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Discovery (Low Context):<\/strong> At startup, the agent reads only the YAML frontmatter (<code>name<\/code> and <code>description<\/code>) of all available skills. This provides awareness with minimal token overhead.<\/li>\n\n\n\n<li><strong>Activation (On-Demand Context):<\/strong> When a user&#8217;s task matches a skill&#8217;s description, the agent reads the full markdown body of <code>SKILL.md<\/code> into the prompt context.<\/li>\n\n\n\n<li><strong>Execution (Tool Calling):<\/strong> The agent follows step-by-step instructions in <code>SKILL.md<\/code> to trigger local tools or scripts (e.g., Python scripts located in <code>scripts\/<\/code>).<\/li>\n<\/ol>\n\n\n\n<pre class=\"wp-block-code\"><code>my-python-skill\/\n\u251c\u2500\u2500 SKILL.md               # Manifest: Metadata + Agent Instructions\n\u251c\u2500\u2500 scripts\/\n\u2502   \u2514\u2500\u2500 analyze_data.py    # Executable Python script\n\u251c\u2500\u2500 references\/            # Optional: Extra docs or schemas\n\u2514\u2500\u2500 assets\/                # Optional: Output templates\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">2. Project Setup: Spring Boot, Spring AI, and Gradle<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Let&#8217;s set up a modern Spring Boot project using <strong>Gradle<\/strong> and <strong>Spring AI<\/strong> with <strong>Ollama<\/strong> as the local model provider.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Gradle Configuration (<code>build.gradle.kts<\/code>)<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>plugins {\n    java\n    id(\"org.springframework.boot\") version \"3.3.2\"\n    id(\"io.spring.dependency-management\") version \"1.1.6\"\n}\n\ngroup = \"com.example.ai\"\nversion = \"0.0.1-SNAPSHOT\"\n\njava {\n    toolchain {\n        languageVersion.set(JavaLanguageVersion.of(21))\n    }\n}\n\nrepositories {\n    mavenCentral()\n    maven { url = uri(\"&#91;https:\/\/repo.spring.io\/milestone](https:\/\/repo.spring.io\/milestone)\") }\n}\n\next {\n    set(\"springAiVersion\", \"1.0.0-M1\")\n}\n\ndependencies {\n    implementation(\"org.springframework.boot:spring-boot-starter-web\")\n    implementation(\"org.springframework.ai:spring-ai-ollama-spring-boot-starter\")\n    \n    \/\/ JSON processing for tool argument parsing\n    implementation(\"com.fasterxml.jackson.module:jackson-module-kotlin\")\n\n    testImplementation(\"org.springframework.boot:spring-boot-starter-test\")\n}\n\ndependencyManagement {\n    imports {\n        mavenBom(\"org.springframework.ai:spring-ai-bom:${property(\"springAiVersion\")}\")\n    }\n}\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Application Configuration (<code>application.yml<\/code>)<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Configure your Spring AI connection to local Ollama instance running a tool-capable model like <code>qwen2.5<\/code> or <code>llama3.1<\/code>.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>spring:\n  application:\n    name: spring-ai-skills-demo\n  ai:\n    ollama:\n      base-url: http:\/\/localhost:11434\n      chat:\n        options:\n          model: qwen2.5:7b\n          temperature: 0.2\n\napp:\n  skills:\n    directory: .\/skills\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">3. Creating the Python Skill (<code>SKILL.md<\/code> + Script)<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Let&#8217;s build a practical skill: <strong>Statistical Data Analyzer<\/strong>. This skill accepts raw numerical data, runs a Python script to calculate statistical metrics, and returns formatted insights.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Directory Structure<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Place this directory inside your project root under <code>.\/skills\/data-analyzer<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>skills\/\n\u2514\u2500\u2500 data-analyzer\/\n    \u251c\u2500\u2500 SKILL.md\n    \u2514\u2500\u2500 scripts\/\n        \u2514\u2500\u2500 calculate_stats.py\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Step A: The Python Script (<code>scripts\/calculate_stats.py<\/code>)<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">This script accepts numbers as command-line arguments or via a JSON string argument, computes statistics using Python&#8217;s standard library, and prints a JSON payload to standard output.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>#!\/usr\/bin\/env python3\nimport sys\nimport json\nimport statistics\n\ndef analyze(numbers):\n    if not numbers:\n        return {\"error\": \"No numbers provided\"}\n    \n    return {\n        \"count\": len(numbers),\n        \"mean\": round(statistics.mean(numbers), 2),\n        \"median\": round(statistics.median(numbers), 2),\n        \"stdev\": round(statistics.stdev(numbers), 2) if len(numbers) &gt; 1 else 0.0,\n        \"min\": min(numbers),\n        \"max\": max(numbers)\n    }\n\nif __name__ == \"__main__\":\n    try:\n        # Expecting numbers passed as JSON array string: '&#91;10, 20, 30]'\n        if len(sys.argv) &gt; 1:\n            raw_input = sys.argv&#91;1]\n            numbers = json.loads(raw_input)\n            result = analyze(&#91;float(x) for x in numbers])\n            print(json.dumps(result))\n        else:\n            print(json.dumps({\"error\": \"Missing required input array\"}))\n    except Exception as e:\n        print(json.dumps({\"error\": str(e)}))\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Step B: The Manifest File (<code>SKILL.md<\/code>)<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The <code>SKILL.md<\/code> combines YAML frontmatter (for discovery) with precise agent instructions (for execution).<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>---\nname: data-analyzer\ndescription: Performs statistical analysis on sets of numbers (calculates mean, median, standard deviation, min, and max) using Python. Use this skill whenever the user asks for mathematical statistics or numerical data metrics.\n---\n\n# Statistical Data Analyzer Skill\n\n## Overview\nWhen requested to perform statistical calculations on a series of numbers, follow this workflow:\n\n1. Extract all numerical values from the user's prompt into a JSON array (e.g., `&#91;12.5, 45.0, 7.8, 92.1]`).\n2. Execute the bundled Python script `scripts\/calculate_stats.py` using the shell execution tool, passing the JSON array string as the first parameter.\n3. Parse the JSON response from the Python script stdout.\n4. Format the final response clearly for the user using markdown tables and concise bullet points.\n\n## Script Execution Format\nCommand syntax:\n`python3 skills\/data-analyzer\/scripts\/calculate_stats.py \"&#91;10, 20, 30, 40]\"`\n\n## Rules &amp; Constraints\n- Do NOT perform calculations yourself in context; always delegate calculation to the Python script to avoid arithmetic hallucination.\n- Ensure the argument passed to the script is valid JSON array format.\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">4. Architectural Implementation in Spring AI<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">To bridge <code>SKILL.md<\/code> and Python execution with Spring AI, we need three core architectural components:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Skill Discovery Service:<\/strong> Scans the <code>.\/skills<\/code> directory and registers available skills.<\/li>\n\n\n\n<li><strong>Python Runner Tool:<\/strong> A Spring AI <code>@Tool<\/code> function that safely executes Python scripts requested by the agent.<\/li>\n\n\n\n<li><strong>Agent Orchestrator:<\/strong> Integrates <code>ChatClient<\/code> with dynamic prompt injection and tool binding.<\/li>\n<\/ol>\n\n\n\n<h3 class=\"wp-block-heading\">Step 1: Skill Model &amp; Repository<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>package com.example.ai.skill;\n\nimport java.nio.file.Path;\n\npublic record SkillMetaData(\n    String name,\n    String description,\n    Path skillDirectory,\n    String instructions\n) {}\n<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>package com.example.ai.skill;\n\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.stereotype.Service;\n\nimport java.io.IOException;\nimport java.nio.file.*;\nimport java.util.*;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\n@Service\npublic class SkillRegistry {\n\n    private final Map&lt;String, SkillMetaData&gt; skills = new HashMap&lt;&gt;();\n    private static final Pattern FRONTMATTER_PATTERN = Pattern.compile(\"^---\\\\s*\\\\n(.*?)\\\\n---\\\\s*\\\\n(.*)$\", Pattern.DOTALL);\n\n    public SkillRegistry(@Value(\"${app.skills.directory:.\/skills}\") String skillsDir) throws IOException {\n        loadSkills(Paths.get(skillsDir));\n    }\n\n    private void loadSkills(Path rootPath) throws IOException {\n        if (!Files.exists(rootPath)) return;\n\n        try (DirectoryStream&lt;Path&gt; stream = Files.newDirectoryStream(rootPath)) {\n            for (Path dir : stream) {\n                Path skillFile = dir.resolve(\"SKILL.md\");\n                if (Files.isRegularFile(skillFile)) {\n                    parseSkillFile(dir, skillFile);\n                }\n            }\n        }\n    }\n\n    private void parseSkillFile(Path dir, Path skillFile) throws IOException {\n        String content = Files.readString(skillFile);\n        Matcher matcher = FRONTMATTER_PATTERN.matcher(content);\n\n        if (matcher.find()) {\n            String yaml = matcher.group(1);\n            String instructions = matcher.group(2).trim();\n\n            String name = extractYamlValue(yaml, \"name\").orElse(dir.getFileName().toString());\n            String description = extractYamlValue(yaml, \"description\").orElse(\"\");\n\n            skills.put(name, new SkillMetaData(name, description, dir, instructions));\n        }\n    }\n\n    private Optional&lt;String&gt; extractYamlValue(String yaml, String key) {\n        for (String line : yaml.split(\"\\n\")) {\n            if (line.startsWith(key + \":\")) {\n                return Optional.of(line.substring(key.length() + 1).trim().replaceAll(\"^\\\"|\\\"$\", \"\"));\n            }\n        }\n        return Optional.empty();\n    }\n\n    public Collection&lt;SkillMetaData&gt; getDiscoveredSkills() {\n        return skills.values();\n    }\n\n    public Optional&lt;SkillMetaData&gt; getSkill(String name) {\n        return Optional.ofNullable(skills.get(name));\n    }\n}\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Step 2: Defining the Python Tool Execution Engine<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Spring AI uses Java records and <code>@Tool<\/code> annotations to declare function calling options to LLMs. We create a tool that allows the LLM to run python scripts inside skill directories.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>package com.example.ai.tool;\n\nimport org.springframework.ai.tool.annotation.Tool;\nimport org.springframework.stereotype.Component;\n\nimport java.io.BufferedReader;\nimport java.io.InputStreamReader;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.concurrent.TimeUnit;\n\n@Component\npublic class PythonExecutionTool {\n\n    public record ExecutionRequest(\n        String relativeScriptPath, \n        String argumentsJson\n    ) {}\n\n    public record ExecutionResponse(\n        int exitCode, \n        String stdout, \n        String stderr\n    ) {}\n\n    @Tool(description = \"Executes a Python script located within a skill directory and returns stdout\/stderr.\")\n    public ExecutionResponse executePythonScript(ExecutionRequest request) {\n        try {\n            \/\/ Sanitize and resolve path\n            Path scriptPath = Paths.get(\".\/skills\").resolve(request.relativeScriptPath()).normalize();\n            \n            \/\/ Basic Path Traversal Defense\n            if (!scriptPath.toAbsolutePath().startsWith(Paths.get(\".\/skills\").toAbsolutePath())) {\n                return new ExecutionResponse(-1, \"\", \"Access denied: Path traversal detected.\");\n            }\n\n            ProcessBuilder pb = new ProcessBuilder(\"python3\", scriptPath.toString(), request.argumentsJson());\n            Process process = pb.start();\n\n            boolean finished = process.waitFor(10, TimeUnit.SECONDS);\n            if (!finished) {\n                process.destroyForcibly();\n                return new ExecutionResponse(-1, \"\", \"Execution timed out after 10 seconds.\");\n            }\n\n            String stdout = new String(process.getInputStream().readAllBytes()).trim();\n            String stderr = new String(process.getErrorStream().readAllBytes()).trim();\n\n            return new ExecutionResponse(process.exitValue(), stdout, stderr);\n        } catch (Exception e) {\n            return new ExecutionResponse(-1, \"\", \"Execution exception: \" + e.getMessage());\n        }\n    }\n}\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Step 3: Agent Controller &amp; Dynamic Prompt Injection<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Now we assemble our <code>ChatClient<\/code>, dynamically inject skill catalogs, and activate specific <code>SKILL.md<\/code> instructions when relevant tasks match.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>package com.example.ai.controller;\n\nimport com.example.ai.skill.SkillMetaData;\nimport com.example.ai.skill.SkillRegistry;\nimport com.example.ai.tool.PythonExecutionTool;\nimport org.springframework.ai.chat.client.ChatClient;\nimport org.springframework.web.bind.annotation.*;\n\nimport java.util.Map;\nimport java.util.stream.Collectors;\n\n@RestController\n@RequestMapping(\"\/api\/agent\")\npublic class AgentController {\n\n    private final ChatClient chatClient;\n    private final SkillRegistry skillRegistry;\n    private final PythonExecutionTool pythonTool;\n\n    public AgentController(ChatClient.Builder builder, SkillRegistry skillRegistry, PythonExecutionTool pythonTool) {\n        this.chatClient = builder.build();\n        this.skillRegistry = skillRegistry;\n        this.pythonTool = pythonTool;\n    }\n\n    @PostMapping(\"\/chat\")\n    public Map&lt;String, String&gt; processUserMessage(@RequestBody Map&lt;String, String&gt; request) {\n        String userQuery = request.get(\"message\");\n\n        \/\/ Step 1: Build low-token discovery summary of available skills\n        String availableSkillsSummary = skillRegistry.getDiscoveredSkills().stream()\n                .map(s -&gt; String.format(\"- Skill '%s': %s\", s.name(), s.description()))\n                .collect(Collectors.joining(\"\\n\"));\n\n        \/\/ Step 2: Check for matched skill activation\n        StringBuilder activatedInstructions = new StringBuilder();\n        for (SkillMetaData skill : skillRegistry.getDiscoveredSkills()) {\n            \/\/ Simple keyword\/intent match (or can be determined by LLM planning step)\n            if (userQuery.toLowerCase().contains(\"statistic\") || userQuery.toLowerCase().contains(\"metric\") || userQuery.toLowerCase().contains(\"average\")) {\n                activatedInstructions.append(\"\\n--- ACTIVATED SKILL: \").append(skill.name()).append(\" ---\\n\");\n                activatedInstructions.append(skill.instructions());\n                activatedInstructions.append(\"\\n-----------------------------------\\n\");\n                break;\n            }\n        }\n\n        \/\/ Step 3: Construct System Prompt\n        String systemPrompt = \"\"\"\n            You are an AI assistant powered by Spring AI.\n            You have access to specialized local skills and a Python Execution Tool.\n            \n            AVAILABLE SKILLS CATALOG:\n            %s\n            \n            %s\n            \n            If a skill is activated, follow its exact instructions and call the executePythonScript tool when needed.\n            \"\"\".formatted(availableSkillsSummary, activatedInstructions.toString());\n\n        \/\/ Step 4: Execute query with Spring AI Tool Binding\n        String response = chatClient.prompt()\n                .system(systemPrompt)\n                .user(userQuery)\n                .tools(pythonTool)\n                .call()\n                .content();\n\n        return Map.of(\"response\", response);\n    }\n}\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">5. Testing the Agent Workflow<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Let&#8217;s test the complete system by sending a request to our Spring Boot endpoint.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Sample Request (<code>POST \/api\/agent\/chat<\/code>)<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>{\n  \"message\": \"Can you calculate the summary statistics for these benchmark test scores: &#91;88.5, 92.0, 79.5, 95.0, 84.0, 91.5]?\"\n}\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Execution Flow Behind the Scenes<\/h3>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Discovery:<\/strong> The controller loads <code>data-analyzer<\/code> metadata into the prompt.<\/li>\n\n\n\n<li><strong>Activation:<\/strong> The system detects statistical context, loading <code>data-analyzer\/SKILL.md<\/code> into the prompt.<\/li>\n\n\n\n<li><strong>LLM Decision:<\/strong> Ollama (<code>qwen2.5<\/code>) reads the instructions in <code>SKILL.md<\/code> and realizes it should execute <code>data-analyzer\/scripts\/calculate_stats.py<\/code>.<\/li>\n\n\n\n<li><strong>Tool Execution:<\/strong> <code>PythonExecutionTool<\/code> triggers <code>python3 .\/skills\/data-analyzer\/scripts\/calculate_stats.py \"[88.5, 92.0, 79.5, 95.0, 84.0, 91.5]\"<\/code> and receives stdout:<code>{\"count\": 6, \"mean\": 88.42, \"median\": 90.0, \"stdev\": 5.83, \"min\": 79.5, \"max\": 95.0}<\/code><\/li>\n\n\n\n<li><strong>Final Output:<\/strong> The LLM receives the tool&#8217;s JSON output and formats a response back to the user:<\/li>\n<\/ol>\n\n\n\n<pre class=\"wp-block-code\"><code>Here are the statistical metrics for your benchmark test scores:\n\n| Metric | Value |\n| :--- | :--- |\n| **Count** | 6 |\n| **Mean** | 88.42 |\n| **Median** | 90.00 |\n| **Std Dev** | 5.83 |\n| **Min** | 79.50 |\n| **Max** | 95.00 |\n\n*Calculated via local `data-analyzer` Python skill runtime.*\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">6. Architectural Best Practices &amp; Security Guidelines<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">When allowing AI agents to run local code via <code>SKILL.md<\/code> instructions, consider the following architecture standards:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Path Traversal Sandboxing:<\/strong> Always normalize paths and verify that executed script targets remain firmly locked within the <code>.\/skills\/<\/code> directory tree.<\/li>\n\n\n\n<li><strong>Process Timeouts:<\/strong> Never run subprocesses without strict timeouts (<code>process.waitFor(N, TimeUnit.SECONDS)<\/code>).<\/li>\n\n\n\n<li><strong>Containerized Execution (Production):<\/strong> In production environments, isolate script executions within lightweight Docker\/Podman containers or sandboxed environments (e.g., gVisor) to prevent malicious or accidental system modifications.<\/li>\n\n\n\n<li><strong>Deterministic Script Outputs:<\/strong> Ensure Python scripts output clean, structured JSON to stdout so that the LLM can reliably parse the results without ambiguity.<\/li>\n<\/ol>\n\n\n\n<h2 class=\"wp-block-heading\">Conclusion<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The <code>SKILL.md<\/code> standard provides a clean, modular pattern for building AI agent capabilities. By decoupling system prompts from procedural domain logic and tool scripts, you achieve a maintainable, token-efficient, and easily testable application architecture in <strong>Spring AI<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Whether you&#8217;re executing complex Python data processing scripts, shell automation tasks, or external integrations, combining <code>SKILL.md<\/code> with Spring AI and local models via Ollama gives you total control over your AI application stack!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>As AI agents evolve from simple chat interfaces to autonomous problem-solving systems, managing prompt bloat and custom tool integrations becomes a critical architectural challenge. If you embed every instruction, workflow, and script binding directly into system prompts, your model context windows quickly saturate, token costs soar, and agent behavior becomes erratic. Enter Agent Skills\u2014an open [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":4023,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_coblocks_attr":"","_coblocks_dimensions":"","_coblocks_responsive_height":"","_coblocks_accordion_ie_support":"","_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_feature_clip_id":0,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2},"jetpack_post_was_ever_published":false},"categories":[495,443],"tags":[],"series":[],"class_list":["post-4017","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-ai","category-spring_ai"],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"https:\/\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2026\/08\/1786883002818.avif","jetpack-related-posts":[{"id":3987,"url":"https:\/\/www.mymiller.name\/wordpress\/spring_ai\/enterprise-ai-at-scale-why-spring-ai-and-java-are-built-for-the-long-run\/","url_meta":{"origin":4017,"position":0},"title":"Enterprise AI at Scale: Why Spring AI and Java are Built for the Long Run","author":"Jeffery Miller","date":"July 23, 2026","format":false,"excerpt":"While Python remains the undisputed king of AI research, data exploration, and model training, the landscape shifts dramatically when moving from experimental notebooks to high-throughput, mission-critical production systems. For enterprise engineering teams building user-facing applications, workflow automations, and LLM-powered services, the real challenge isn't training a model\u2014it's integrating, scaling, securing,\u2026","rel":"","context":"In &quot;AI&quot;","block_context":{"text":"AI","link":"https:\/\/www.mymiller.name\/wordpress\/category\/ai\/"},"img":{"alt_text":"","src":"https:\/\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2026\/07\/Gemini_Generated_Image_rht72frht72frht7-scaled.avif","width":350,"height":200,"srcset":"https:\/\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2026\/07\/Gemini_Generated_Image_rht72frht72frht7-scaled.avif 1x, https:\/\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2026\/07\/Gemini_Generated_Image_rht72frht72frht7-scaled.avif 1.5x, https:\/\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2026\/07\/Gemini_Generated_Image_rht72frht72frht7-scaled.avif 2x, https:\/\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2026\/07\/Gemini_Generated_Image_rht72frht72frht7-scaled.avif 3x"},"classes":[]},{"id":3965,"url":"https:\/\/www.mymiller.name\/wordpress\/angular\/bringing-worlds-to-life-integrating-ai-personas-in-multi-user-dungeons-muds\/","url_meta":{"origin":4017,"position":1},"title":"Bringing Worlds to Life: Integrating AI Personas in Multi-User Dungeons (MUDs)","author":"Jeffery Miller","date":"April 20, 2026","format":false,"excerpt":"A few weeks ago, I found myself pondering the ultimate objective for an artificial intelligence system. The answer kept returning to a single concept: the ability to truly mimic a human. This spark of an idea gave rise to a challenge\u2014I needed a sandbox where I could work with AI\u2026","rel":"","context":"In &quot;Angular&quot;","block_context":{"text":"Angular","link":"https:\/\/www.mymiller.name\/wordpress\/category\/angular\/"},"img":{"alt_text":"","src":"https:\/\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2026\/04\/Gemini_Generated_Image_hsr3ethsr3ethsr3-scaled.avif","width":350,"height":200,"srcset":"https:\/\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2026\/04\/Gemini_Generated_Image_hsr3ethsr3ethsr3-scaled.avif 1x, https:\/\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2026\/04\/Gemini_Generated_Image_hsr3ethsr3ethsr3-scaled.avif 1.5x, https:\/\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2026\/04\/Gemini_Generated_Image_hsr3ethsr3ethsr3-scaled.avif 2x, https:\/\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2026\/04\/Gemini_Generated_Image_hsr3ethsr3ethsr3-scaled.avif 3x"},"classes":[]},{"id":3970,"url":"https:\/\/www.mymiller.name\/wordpress\/architecture\/vibe-coding-the-next-generation-how-we-built-aimud-using-an-ai-ensemble\/","url_meta":{"origin":4017,"position":2},"title":"Vibe Coding the Next Generation: How We Built AIMUD Using an AI Ensemble","author":"Jeffery Miller","date":"April 21, 2026","format":false,"excerpt":"In the traditional world of software engineering, building a Multi-User Dungeon (MUD) is a rite of passage. It requires handling complex state, real-time networking, concurrency, and deep game logic. Usually, this takes months of meticulous, line-by-line keyboard grinding. But for AIMUD, we didn't just code; we vibe coded. By leveraging\u2026","rel":"","context":"In &quot;AI&quot;","block_context":{"text":"AI","link":"https:\/\/www.mymiller.name\/wordpress\/category\/ai\/"},"img":{"alt_text":"","src":"https:\/\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2026\/04\/Gemini_Generated_Image_6veptk6veptk6vep-scaled.avif","width":350,"height":200,"srcset":"https:\/\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2026\/04\/Gemini_Generated_Image_6veptk6veptk6vep-scaled.avif 1x, https:\/\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2026\/04\/Gemini_Generated_Image_6veptk6veptk6vep-scaled.avif 1.5x, https:\/\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2026\/04\/Gemini_Generated_Image_6veptk6veptk6vep-scaled.avif 2x, https:\/\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2026\/04\/Gemini_Generated_Image_6veptk6veptk6vep-scaled.avif 3x"},"classes":[]},{"id":3931,"url":"https:\/\/www.mymiller.name\/wordpress\/uncategorized\/advanced-spring-ai-creating-agentic-workflows-with-function-calling\/","url_meta":{"origin":4017,"position":3},"title":"Advanced Spring AI: Creating Agentic Workflows with Function Calling","author":"Jeffery Miller","date":"November 24, 2025","format":false,"excerpt":"The landscape of AI is rapidly evolving, moving beyond simple request-response models to more sophisticated, agentic systems. These systems empower Large Language Models (LLMs) to not just generate text, but to act within your applications, making them an active and integral part of your business logic. Spring AI is at\u2026","rel":"","context":"Similar post","block_context":{"text":"Similar post","link":""},"img":{"alt_text":"","src":"https:\/\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2025\/11\/Gemini_Generated_Image_kg5i0ykg5i0ykg5i.avif","width":350,"height":200,"srcset":"https:\/\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2025\/11\/Gemini_Generated_Image_kg5i0ykg5i0ykg5i.avif 1x, https:\/\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2025\/11\/Gemini_Generated_Image_kg5i0ykg5i0ykg5i.avif 1.5x, https:\/\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2025\/11\/Gemini_Generated_Image_kg5i0ykg5i0ykg5i.avif 2x"},"classes":[]},{"id":3564,"url":"https:\/\/www.mymiller.name\/wordpress\/spring_ai\/anomaly-detection-in-spring-boot-gateway-with-ai-and-dl4j-unsupervised-learning-approach\/","url_meta":{"origin":4017,"position":4},"title":"Anomaly Detection in Spring Boot Gateway with AI and DL4J: Unsupervised Learning Approach","author":"Jeffery Miller","date":"July 23, 2026","format":false,"excerpt":"In this article, we\u2019ll focus on using unsupervised learning with DL4J to detect anomalies in data traffic passing through your Spring Boot Gateway. This is especially useful when you don\u2019t have labeled data on what constitutes \u201cnormal\u201d vs. \u201canomalous\u201d traffic. Potential Features for Anomaly Detection in API Gateway Traffic The\u2026","rel":"","context":"In &quot;Spring AI&quot;","block_context":{"text":"Spring AI","link":"https:\/\/www.mymiller.name\/wordpress\/category\/spring_ai\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2024\/06\/Gemini_Generated_Image_rzr70rzr70rzr70r.jpg?fit=1200%2C1200&ssl=1&resize=350%2C200","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2024\/06\/Gemini_Generated_Image_rzr70rzr70rzr70r.jpg?fit=1200%2C1200&ssl=1&resize=350%2C200 1x, https:\/\/i0.wp.com\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2024\/06\/Gemini_Generated_Image_rzr70rzr70rzr70r.jpg?fit=1200%2C1200&ssl=1&resize=525%2C300 1.5x, https:\/\/i0.wp.com\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2024\/06\/Gemini_Generated_Image_rzr70rzr70rzr70r.jpg?fit=1200%2C1200&ssl=1&resize=700%2C400 2x, https:\/\/i0.wp.com\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2024\/06\/Gemini_Generated_Image_rzr70rzr70rzr70r.jpg?fit=1200%2C1200&ssl=1&resize=1050%2C600 3x"},"classes":[]},{"id":3995,"url":"https:\/\/www.mymiller.name\/wordpress\/spring_ai\/mastering-retrieval-augmented-generation-rag-with-spring-ai\/","url_meta":{"origin":4017,"position":5},"title":"Mastering Retrieval-Augmented Generation (RAG) with Spring AI","author":"Jeffery Miller","date":"July 27, 2026","format":false,"excerpt":"Retrieval-Augmented Generation (RAG) has become the gold standard architecture for extending the capabilities of Large Language Models (LLMs) with enterprise domain knowledge. By combining the natural language understanding of LLMs with real-time retrieval from dynamic data stores, RAG eliminates hallucinations, enhances accuracy, and avoids the costly alternative of fine-tuning models\u2026","rel":"","context":"In &quot;AI&quot;","block_context":{"text":"AI","link":"https:\/\/www.mymiller.name\/wordpress\/category\/ai\/"},"img":{"alt_text":"","src":"https:\/\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2026\/07\/Gemini_Generated_Image_v05lpdv05lpdv05l-scaled.avif","width":350,"height":200,"srcset":"https:\/\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2026\/07\/Gemini_Generated_Image_v05lpdv05lpdv05l-scaled.avif 1x, https:\/\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2026\/07\/Gemini_Generated_Image_v05lpdv05lpdv05l-scaled.avif 1.5x, https:\/\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2026\/07\/Gemini_Generated_Image_v05lpdv05lpdv05l-scaled.avif 2x, https:\/\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2026\/07\/Gemini_Generated_Image_v05lpdv05lpdv05l-scaled.avif 3x"},"classes":[]}],"jetpack_sharing_enabled":true,"jetpack_likes_enabled":true,"_links":{"self":[{"href":"https:\/\/www.mymiller.name\/wordpress\/wp-json\/wp\/v2\/posts\/4017","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.mymiller.name\/wordpress\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.mymiller.name\/wordpress\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.mymiller.name\/wordpress\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.mymiller.name\/wordpress\/wp-json\/wp\/v2\/comments?post=4017"}],"version-history":[{"count":1,"href":"https:\/\/www.mymiller.name\/wordpress\/wp-json\/wp\/v2\/posts\/4017\/revisions"}],"predecessor-version":[{"id":4018,"href":"https:\/\/www.mymiller.name\/wordpress\/wp-json\/wp\/v2\/posts\/4017\/revisions\/4018"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.mymiller.name\/wordpress\/wp-json\/wp\/v2\/media\/4023"}],"wp:attachment":[{"href":"https:\/\/www.mymiller.name\/wordpress\/wp-json\/wp\/v2\/media?parent=4017"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.mymiller.name\/wordpress\/wp-json\/wp\/v2\/categories?post=4017"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.mymiller.name\/wordpress\/wp-json\/wp\/v2\/tags?post=4017"},{"taxonomy":"series","embeddable":true,"href":"https:\/\/www.mymiller.name\/wordpress\/wp-json\/wp\/v2\/series?post=4017"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}