ADR-013: LangChain4j Integration for Intelligent CLI Workflow Routing

<!-- MADR 3.0 Template - Markdown Any Decision Records --> <!-- Reference: https://adr.github.io/madr/ -->

Status

Implemented

Date

2025-12-06

Deciders

Context and Problem Statement

The idempiere-cli currently uses traditional command-line parsing with Picocli where users must know exact command syntax. As the CLI grows with more commands (generators, registry, migration, etc.), discoverability becomes challenging. Users need to remember specific command names, flags, and argument orders.

We want to enable natural language command routing where users can describe what they want to accomplish, and an AI agent intelligently routes the request to the appropriate CLI command or workflow. This builds on our existing AI integration strategy (ADR-010, ADR-011) and leverages the Java ecosystem.

Decision Drivers

Considered Options

  1. LangChain4j with Tool-based Routing - Java-native AI framework with @Tool annotations
  2. LangGraph (Python) via HTTP Bridge - Python implementation with Java HTTP client
  3. Custom Rule-Based Router - Pattern matching without LLM
  4. Direct OpenAI Function Calling - Raw API integration without framework

Decision Outcome

Chosen option: "LangChain4j with Tool-based Routing", because it provides native Java integration with our Quarkus stack, supports multiple LLM backends (including local models via Ollama), and offers a clean annotation-based tool definition that aligns with our existing Picocli command structure.

Confirmation

The decision is confirmed when:

Pros and Cons of the Options

Option 1: LangChain4j with Tool-based Routing

Native Java framework for LLM applications with structured tool calling.

Option 2: LangGraph via HTTP Bridge

Use Python LangGraph server with Java HTTP client.

Option 3: Custom Rule-Based Router

Pattern matching and keyword extraction without LLM.

Option 4: Direct OpenAI Function Calling

Raw API integration without LangChain4j abstraction.

More Information

Architecture

┌─────────────────────────────────────────────────────────────────┐
│                      User Natural Language Input                 │
│              &quot;List all tables with audit columns&quot;                │
└──────────────────────────┬──────────────────────────────────────┘
                           │
┌──────────────────────────▼──────────────────────────────────────┐
│                    LangChain4j Agent                             │
│  ┌────────────────────────────────────────────────────────────┐ │
│  │ ChatLanguageModel (configurable backend)                   │ │
│  │   - OpenAiChatModel (gpt-4, gpt-3.5-turbo)                │ │
│  │   - AnthropicChatModel (claude-3)                          │ │
│  │   - OllamaChatModel (llama3, mistral) ← LOCAL              │ │
│  │   - HuggingFaceChatModel                                   │ │
│  └────────────────────────────────────────────────────────────┘ │
│                                                                  │
│  ┌────────────────────────────────────────────────────────────┐ │
│  │ Routing Decision (Tool Selection)                          │ │
│  │   Based on: user intent, tool descriptions, AD context     │ │
│  └────────────────────────────────────────────────────────────┘ │
└──────────────────────────┬──────────────────────────────────────┘
                           │
┌──────────────────────────▼──────────────────────────────────────┐
│                    CLI Tools (LangChain4j @Tool)                 │
│                                                                  │
│  ┌──────────────┐ ┌──────────────┐ ┌──────────────┐             │
│  │ RegistryTool │ │ GenerateTool │ │ MigrationTool│             │
│  │              │ │              │ │              │             │
│  │ @Tool        │ │ @Tool        │ │ @Tool        │             │
│  │ listTables() │ │ model()      │ │ create()     │             │
│  │ getTable()   │ │ process()    │ │ apply()      │             │
│  │ findColumns()│ │ window()     │ │ rollback()   │             │
│  └──────────────┘ └──────────────┘ └──────────────┘             │
│                                                                  │
│  ┌──────────────┐ ┌──────────────┐ ┌──────────────┐             │
│  │ TableTool    │ │ ColumnTool   │ │ DoctorTool   │             │
│  │              │ │              │ │              │             │
│  │ @Tool        │ │ @Tool        │ │ @Tool        │             │
│  │ create()     │ │ add()        │ │ diagnose()   │             │
│  │ sync()       │ │ modify()     │ │ validate()   │             │
│  └──────────────┘ └──────────────┘ └──────────────┘             │
└──────────────────────────┬──────────────────────────────────────┘
                           │
┌──────────────────────────▼──────────────────────────────────────┐
│                    Existing CLI Services                         │
│                                                                  │
│  RegistryService, GeneratorRegistry, MigrationService, etc.     │
└─────────────────────────────────────────────────────────────────┘

Implementation Notes

1. Maven Dependencies

&lt;!-- LangChain4j Core --&gt;
&lt;dependency&gt;
    &lt;groupId&gt;dev.langchain4j&lt;/groupId&gt;
    &lt;artifactId&gt;langchain4j&lt;/artifactId&gt;
    &lt;version&gt;0.35.0&lt;/version&gt;
&lt;/dependency&gt;

&lt;!-- Quarkus Integration --&gt;
&lt;dependency&gt;
    &lt;groupId&gt;io.quarkiverse.langchain4j&lt;/groupId&gt;
    &lt;artifactId&gt;quarkus-langchain4j-core&lt;/artifactId&gt;
    &lt;version&gt;0.20.0&lt;/version&gt;
&lt;/dependency&gt;

&lt;!-- LLM Providers (choose based on needs) --&gt;
&lt;dependency&gt;
    &lt;groupId&gt;dev.langchain4j&lt;/groupId&gt;
    &lt;artifactId&gt;langchain4j-ollama&lt;/artifactId&gt;
    &lt;version&gt;0.35.0&lt;/version&gt;
&lt;/dependency&gt;

&lt;dependency&gt;
    &lt;groupId&gt;dev.langchain4j&lt;/groupId&gt;
    &lt;artifactId&gt;langchain4j-anthropic&lt;/artifactId&gt;
    &lt;version&gt;0.35.0&lt;/version&gt;
&lt;/dependency&gt;

&lt;dependency&gt;
    &lt;groupId&gt;dev.langchain4j&lt;/groupId&gt;
    &lt;artifactId&gt;langchain4j-open-ai&lt;/artifactId&gt;
    &lt;version&gt;0.35.0&lt;/version&gt;
&lt;/dependency&gt;

2. CLI Tool Definitions

package org.idempiere.cli.ai.tools;

import dev.langchain4j.agent.tool.Tool;
import jakarta.inject.Inject;

/**
 * LangChain4j tools wrapping CLI registry commands.
 * Tool descriptions guide the LLM in selecting appropriate actions.
 */
public class RegistryTools {

    @Inject
    RegistryService registryService;

    @Tool(&quot;Lists all tables in the Application Dictionary. &quot; +
          &quot;Use when user wants to see available tables, search for tables, &quot; +
          &quot;or explore the database schema.&quot;)
    public String listTables(String pattern) {
        return registryService.listTables(pattern);
    }

    @Tool(&quot;Gets detailed metadata for a specific table including columns, &quot; +
          &quot;access level, and relationships. Use when user asks about &quot; +
          &quot;a specific table structure.&quot;)
    public String getTableDetails(String tableName) {
        return registryService.getTableDetails(tableName);
    }

    @Tool(&quot;Finds columns matching a pattern across all tables. &quot; +
          &quot;Use when user wants to find where a specific field is used.&quot;)
    public String findColumns(String columnPattern) {
        return registryService.findColumns(columnPattern);
    }

    @Tool(&quot;Lists all windows in the Application Dictionary. &quot; +
          &quot;Use when user asks about UI windows or forms.&quot;)
    public String listWindows(String pattern) {
        return registryService.listWindows(pattern);
    }
}
package org.idempiere.cli.ai.tools;

import dev.langchain4j.agent.tool.Tool;
import dev.langchain4j.agent.tool.P;
import jakarta.inject.Inject;

/**
 * LangChain4j tools wrapping CLI generator commands.
 */
public class GeneratorTools {

    @Inject
    GeneratorRegistry generatorRegistry;

    @Tool(&quot;Generates a Java model class for an iDempiere table. &quot; +
          &quot;Creates the M{TableName}.java file with getters/setters. &quot; +
          &quot;Use when user wants to create model code for a table.&quot;)
    public String generateModel(
            @P(&quot;Table name (e.g., C_BPartner, M_Product)&quot;) String tableName,
            @P(&quot;Output directory for generated file&quot;) String outputDir) {
        return generatorRegistry.generate(&quot;model&quot;, tableName, outputDir);
    }

    @Tool(&quot;Generates a process class (SvrProcess) for iDempiere. &quot; +
          &quot;Use when user wants to create a new background process or report.&quot;)
    public String generateProcess(
            @P(&quot;Process class name&quot;) String className,
            @P(&quot;Package name&quot;) String packageName) {
        return generatorRegistry.generate(&quot;process&quot;, className, packageName);
    }

    @Tool(&quot;Generates a window/tab/field configuration. &quot; +
          &quot;Use when user wants to create UI components.&quot;)
    public String generateWindow(
            @P(&quot;Window name&quot;) String windowName,
            @P(&quot;Base table name&quot;) String tableName) {
        return generatorRegistry.generate(&quot;window&quot;, windowName, tableName);
    }
}

3. AI Agent Service Interface

package org.idempiere.cli.ai;

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

/**
 * LangChain4j AI Service for CLI command routing.
 * The agent uses tools to execute CLI operations based on natural language.
 */
@RegisterAiService(tools = {
    RegistryTools.class,
    GeneratorTools.class,
    MigrationTools.class,
    TableTools.class,
    DoctorTools.class
})
public interface CliRouterAgent {

    @SystemMessage(&quot;&quot;&quot;
        You are an iDempiere CLI assistant that helps users interact with
        the Application Dictionary and generate code. You have access to
        tools that wrap CLI commands.

        When the user makes a request:
        1. Understand what they want to accomplish
        2. Select the appropriate tool(s) to fulfill the request
        3. Execute the tools and provide helpful output

        iDempiere context:
        - Tables follow naming conventions: C_ (Customer), M_ (Material), AD_ (Dictionary)
        - Common tables: C_BPartner (Business Partners), M_Product, C_Order, C_Invoice
        - Application Dictionary (AD_*) tables define metadata
        &quot;&quot;&quot;)
    String route(@UserMessage String userRequest);
}

4. Command Integration

package org.idempiere.cli.commands;

import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.ollama.OllamaChatModel;
import dev.langchain4j.model.anthropic.AnthropicChatModel;
import dev.langchain4j.service.AiServices;
import org.idempiere.cli.ai.CliRouterAgent;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import picocli.CommandLine.Parameters;

import jakarta.inject.Inject;
import java.util.concurrent.Callable;

@Command(name = &quot;ask&quot;,
         description = &quot;Natural language interface to CLI commands&quot;,
         mixinStandardHelpOptions = true)
public class AskCommand implements Callable&lt;Integer&gt; {

    @Parameters(description = &quot;Natural language request&quot;)
    String request;

    @Option(names = {&quot;--provider&quot;, &quot;-p&quot;},
            description = &quot;LLM provider: ollama, anthropic, openai&quot;,
            defaultValue = &quot;ollama&quot;)
    String provider;

    @Option(names = {&quot;--model&quot;, &quot;-m&quot;},
            description = &quot;Model name (e.g., llama3, claude-3-sonnet)&quot;)
    String model;

    @Inject
    RegistryTools registryTools;

    @Inject
    GeneratorTools generatorTools;

    @Override
    public Integer call() {
        ChatLanguageModel llm = createModel();

        CliRouterAgent agent = AiServices.builder(CliRouterAgent.class)
                .chatLanguageModel(llm)
                .tools(registryTools, generatorTools)
                .build();

        String result = agent.route(request);
        System.out.println(result);

        return 0;
    }

    private ChatLanguageModel createModel() {
        return switch (provider.toLowerCase()) {
            case &quot;ollama&quot; -&gt; OllamaChatModel.builder()
                    .baseUrl(&quot;http://localhost:11434&quot;)
                    .modelName(model != null ? model : &quot;llama3&quot;)
                    .build();

            case &quot;anthropic&quot; -&gt; AnthropicChatModel.builder()
                    .apiKey(System.getenv(&quot;ANTHROPIC_API_KEY&quot;))
                    .modelName(model != null ? model : &quot;claude-3-sonnet-20240229&quot;)
                    .build();

            case &quot;openai&quot; -&gt; throw new UnsupportedOperationException(
                    &quot;OpenAI support pending&quot;);

            default -&gt; throw new IllegalArgumentException(
                    &quot;Unknown provider: &quot; + provider);
        };
    }
}

5. Configuration (application.properties)

# LangChain4j / Quarkus AI configuration
quarkus.langchain4j.ollama.base-url=http://localhost:11434
quarkus.langchain4j.ollama.chat-model.model-id=llama3
quarkus.langchain4j.ollama.timeout=60s

# For cloud providers (optional)
quarkus.langchain4j.anthropic.api-key=${ANTHROPIC_API_KEY:}
quarkus.langchain4j.openai.api-key=${OPENAI_API_KEY:}

# Default provider selection
idempiere.cli.ai.default-provider=ollama
idempiere.cli.ai.enabled=true

Usage Examples

# Natural language queries (using local Ollama)
idempiere-cli ask &quot;list all tables with audit columns&quot;
idempiere-cli ask &quot;show me the structure of C_BPartner table&quot;
idempiere-cli ask &quot;generate a model class for M_Product&quot;
idempiere-cli ask &quot;what windows are available for managing orders?&quot;

# Specify provider
idempiere-cli ask --provider=anthropic &quot;find all columns named DocumentNo&quot;
idempiere-cli ask --provider=ollama --model=mistral &quot;create a new process&quot;

# Complex multi-step requests
idempiere-cli ask &quot;find all tables related to invoicing and show their relationships&quot;

Local LLM Setup (Ollama)

For offline/local operation without API costs:

# Install Ollama
brew install ollama

# Pull a model (choose based on hardware)
ollama pull llama3        # Recommended for tool calling
ollama pull mistral       # Alternative
ollama pull codellama     # For code generation focus

# Start Ollama server (runs on localhost:11434)
ollama serve

Integration with Existing Architecture

┌─────────────────────────────────────────────────────────────────┐
│                    idempiere-cli Architecture                    │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  ┌──────────────────┐      ┌──────────────────┐                 │
│  │ Traditional CLI  │      │  AI-Powered CLI  │                 │
│  │  (Picocli)       │      │  (LangChain4j)   │                 │
│  │                  │      │                  │                 │
│  │ idempiere-cli    │      │ idempiere-cli    │                 │
│  │   registry list  │      │   ask &quot;...&quot;      │                 │
│  │   generate model │      │                  │                 │
│  └────────┬─────────┘      └────────┬─────────┘                 │
│           │                         │                           │
│           │    ┌────────────────────┘                           │
│           │    │                                                │
│           ▼    ▼                                                │
│  ┌──────────────────────────────────────────────────────────┐  │
│  │              Shared Service Layer                         │  │
│  │                                                           │  │
│  │  RegistryService  GeneratorRegistry  MigrationService    │  │
│  │  TableService     ColumnService      DoctorService       │  │
│  └───────────────────────────┬──────────────────────────────┘  │
│                              │                                  │
│  ┌───────────────────────────▼──────────────────────────────┐  │
│  │              Data Access Layer                            │  │
│  │                                                           │  │
│  │  Direct JDBC (offline)  │  REST API (ADR-009)            │  │
│  │  cloudempiere.ai (ADR-011)                               │  │
│  └──────────────────────────────────────────────────────────┘  │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

Future Enhancements

  1. Conversation Memory: Add chat history for multi-turn interactions
  2. RAG with AD Metadata: Embed Application Dictionary for better context
  3. Workflow Chaining: Support multi-step workflows with dependencies
  4. Custom Tool Discovery: Auto-register tools from plugin SPI
  5. Fine-tuning: Train models on iDempiere-specific patterns

Integration Analysis Summary

See 013-appendix-integration-analysis.md for detailed analysis. Key points:

Related ADR Relationship Integration Strategy
ADR-008 (Registry) Data Provider LangChain4j tools consume RegistryService for AD metadata
ADR-010 (MCP Server) Parallel Interface Shared tool logic layer avoids duplication
ADR-011 (cloudempiere.ai) Enhanced Backend Optional secure backend with role-based access

Key Architectural Decision: Create org.idempiere.cli.ai.shared package with tool logic that both ADR-010 MCP wrappers and ADR-013 LangChain4j wrappers can consume, avoiding code duplication.

References


<!-- MADR 3.0 Guidelines:

  1. Required sections: Context and Problem Statement, Considered Options, Decision Outcome
  2. Recommended sections: Decision Drivers, Pros/Cons of Options, Confirmation
  3. Status values: Proposed, Accepted, Deprecated, Superseded
  4. ADRs are immutable - create new ADR to supersede, never edit existing
  5. Include diagrams where helpful (architecture, sequence, flow) -->

Path: /docs/developers/architecture/idempiere-hub/013-langchain4j-workflow-routing