ADR-055: Tool Ecosystem Architectural Review

Status: ✅ Implemented (100% Complete) Date: 2025-12-12 (Analysis), 2025-12-15 (Implementation Complete) Context: Comprehensive review after incremental AI integration additions


🎉 Implementation Summary (2025-12-15)

All 5 priorities completed in 5 phases across 3 days:

Priority Recommendation Est. Time Actual Implementation Commit Status
P1 Unify ToolResult 1 week Pre-completed (ADR-054) N/A ✅ Done
P3 ConnectionProvider 1 week Phase 1 (2 hours) 42f864c ✅ Done
P2 SQL Consolidation 3 days Phase 2 (3 hours) 6bc01bb ✅ Done
P4 Split God Object 1 week Phase 3 (4 hours) 60359c2 ✅ Done
P5 ChatToolProvider 3 days Phase 4+5 (2 hours) 0bf39f6, a3ec2f7 ✅ Done

Actual Timeline: 3 days (vs estimated 3-4 weeks) Commits: 5 total (42f864c, 6bc01bb, 60359c2, 0bf39f6, a3ec2f7)

Key Achievements:

Performance Impact:

Architecture Impact:


Executive Summary (Original Analysis)

The iDempiere CLI evolved incrementally into a multi-layered tool ecosystem serving three AI integration patterns (MCP, LangChain4j, Chat API). While code reuse through shared logic classes is excellent, 5 critical architectural issues stem from organic growth without periodic refactoring.

Critical Issues Found

🔴 PRIORITY 1: Duplicate ToolResult Classes

Problem: Two incompatible ToolResult classes exist:

Location Package Lines Used By
ai/shared/ToolResult.java org.idempiere.cli.ai.shared 356 MCP, LangChain4j, shared logic
chatapi/tool/ToolResult.java org.idempiere.cli.chatapi.tool 193 Chat API only

Impact:

Field Differences:

Field ai/shared chatapi/tool Impact
message ✓ - Different - chatapi uses "content"
content - ✓ Different field name
data Map<String,Object> Object Different types
context ✓ - AD context missing in chatapi
executionTimeMs - ✓ Performance tracking missing in shared
metadata - ✓ Metadata missing in shared

Recommendation:

// Merge into single class: ai/shared/ToolResult.java
public class ToolResult {
    private boolean success;
    private String message;           // Human-readable
    private String content;            // Detailed content
    private String errorCode;
    private Map&lt;String, Object&gt; data;
    private Map&lt;String, Object&gt; context;      // AD enrichment
    private Map&lt;String, Object&gt; metadata;     // Execution metadata
    private Long executionTimeMs;             // Performance

    public String toJson() { ... }
    public String toLangChainFormat() { ... }
    public ToolResponse toMcpResponse() { ... }
}

🔴 PRIORITY 2: DatabaseQueryTool Reimplements QueryToolLogic

Problem: Two SQL execution implementations:

QueryToolLogic (ai/shared, 379 lines):

DatabaseQueryTool (chatapi/tool/impl, ~200 lines):

Impact:

Recommendation: Eliminate DatabaseQueryTool entirely

// Update QueryToolLogic to accept optional ChatContext
public ToolResult executeQuery(String sql, Integer limit,
                                String language, ChatContext context) {
    // Apply context filtering if provided
    if (context != null &amp;&amp; context.clientId() != null) {
        // Filter by client
    }
    // Existing validation, execution, enrichment
}

// Update ChatToolProvider to delegate
@Inject QueryToolLogic queryLogic;

@Tool(&quot;Execute SQL...&quot;)
public String executeQuery(String sql, Integer maxRows) {
    return queryLogic.executeQuery(sql, maxRows, null, null).toJson();
}

🔴 PRIORITY 3: No Shared Connection Management

Problem: Fragmented database connection strategies:

QueryToolLogic        → DriverManager.getConnection() (no pool)
RegistryToolLogic     → DriverManager.getConnection() (no pool)
DoctorToolLogic       → DriverManager.getConnection() (no pool)
ADContextService      → DriverManager.getConnection() (no pool)
DatabaseQueryTool     → DataSource injection (proper pool)

Impact:

Recommendation: Create ConnectionProvider Service

@ApplicationScoped
public class DatabaseConnectionProvider {
    @Inject IdempiereConfig config;
    @Inject @Named(&quot;idempiere&quot;) Instance&lt;DataSource&gt; dataSource;

    public Connection getConnection() throws SQLException {
        // Prefer DataSource if available
        if (dataSource.isResolvable()) {
            return dataSource.get().getConnection();
        }
        // Fallback to direct connection
        return DriverManager.getConnection(
            config.getJdbcUrl(),
            config.getDbUser(),
            config.getDbPassword()
        );
    }
}

// Update all logic classes:
@Inject DatabaseConnectionProvider connectionProvider;

try (Connection conn = connectionProvider.getConnection()) {
    // Use connection
}

🔴 PRIORITY 4: RegistryToolLogic God Object (1001 lines)

Problem: Single class with 10+ responsibilities:

public class RegistryToolLogic {
    // 1. Table listing/describing
    public ToolResult listTables(...) { }
    public ToolResult describeTable(...) { }

    // 2. Column operations
    public ToolResult listColumns(...) { }

    // 3. Window operations
    public ToolResult listWindows(...) { }

    // 4. Process operations
    public ToolResult listProcesses(...) { }

    // 5. Reference operations
    public ToolResult listReferences(...) { }

    // 6. Generic search
    public ToolResult search(...) { }

    // 7. Semantic search (RAG)
    public ToolResult semanticSearch(...) { }

    // 8. Smart search (hybrid)
    public ToolResult smartSearch(...) { }

    // 9. Statistics
    public ToolResult getStatistics() { }

    // 10. Context enrichment
    private void enrichTableContext(...) { }
}

Impact:

Recommendation: Split into domain-focused classes

/ai/shared/registry/
├── TableRegistryLogic.java       (~200 lines) - tables + columns
├── ProcessRegistryLogic.java     (~200 lines) - processes + parameters
├── WindowRegistryLogic.java      (~200 lines) - windows + tabs + fields
├── ReferenceRegistryLogic.java   (~200 lines) - references + validation rules
├── SemanticSearchLogic.java      (~200 lines) - RAG integration
└── RegistryStatistics.java       (~100 lines) - AD statistics

🔴 PRIORITY 5: ChatToolProvider Architecture Confusion

Problem: Inconsistent abstraction levels

@ApplicationScoped
public class ChatToolProvider {
    @Inject RestDataToolLogic restLogic;           // Delegates
    @Inject GeneratedOpenApiFactory apiFactory;    // Direct use ❌
    @Inject RagService ragService;                  // Direct use ❌
    @Inject DatabaseQueryTool databaseQueryTool;    // Custom tool ❌
    @Inject RegistryToolLogic registryLogic;        // Delegates ✅

    @Tool(&quot;Search records...&quot;)
    public String searchRecords(...) {
        // Sometimes calls apiFactory directly ❌
        apiFactory.models().modelsTableNameGet(...);
    }

    @Tool(&quot;Execute SQL...&quot;)
    public String executeQuery(...) {
        // Calls custom DatabaseQueryTool ❌
        databaseQueryTool.execute(args, null);
    }

    @Tool(&quot;Get table columns...&quot;)
    public String getTableColumns(...) {
        // Calls shared RegistryToolLogic ✅
        registryLogic.listColumns(tableName).toJson();
    }
}

Impact:

Recommendation: Choose one pattern

Option A - Eliminate (Preferred):

// In ChatAgent, inject tool logics directly
@Inject RestDataToolLogic restLogic;
@Inject QueryToolLogic queryLogic;
@Inject RegistryToolLogic registryLogic;
@Inject RagService ragService;

@Tool(&quot;Search records...&quot;)
public String searchRecords(...) {
    return restLogic.listModels(filter).toJson();
}

Option B - Make it a real orchestrator:

@ApplicationScoped
public class ChatToolOrchestrator {
    public ToolResult hybridSearch(String query, SearchOptions opts) {
        // Real orchestration logic
        if (shouldUseRag(query, opts)) {
            return ragService.search(query, opts.sourceType);
        } else if (shouldUseRest(query, opts)) {
            return restLogic.listModels(query);
        } else {
            return queryLogic.executeQuery(buildSql(query, opts));
        }
    }
}

Architecture Overview

Current State (Layered)

┌─────────────────────────────────────────────────────────────┐
│              AI Integration Wrappers                         │
│  ┌──────────┐  ┌───────────┐  ┌──────────────┐              │
│  │ MCP      │  │ LangChain │  │ Chat API     │              │
│  │ Tools    │  │ Tools     │  │ (mixed)      │              │
│  └────┬─────┘  └─────┬─────┘  └──────┬───────┘              │
└───────┼──────────────┼────────────────┼──────────────────────┘
        │              │                │
        └──────────────┼────────────────┘
                       │
┌──────────────────────┼───────────────────────────────────────┐
│            Shared Business Logic                             │
│  ┌─────────────────────────────────────────────┐             │
│  │ TableToolLogic, QueryToolLogic,             │             │
│  │ RegistryToolLogic (GOD OBJECT),             │             │
│  │ RestDataToolLogic, GeneratorToolLogic       │             │
│  └─────────────────────────────────────────────┘             │
│                                                               │
│  ⚠️ ISSUES:                                                   │
│  - 2 ToolResult classes                                      │
│  - DatabaseQueryTool duplicates QueryToolLogic               │
│  - No shared ConnectionProvider                              │
│  - RegistryToolLogic too large (1001 lines)                  │
└───────────────────────────────────────────────────────────────┘

Proposed State (Cleaner)

┌─────────────────────────────────────────────────────────────┐
│              AI Integration Wrappers                         │
│  ┌──────────┐  ┌───────────┐  ┌──────────────┐              │
│  │ MCP      │  │ LangChain │  │ Chat Agent   │              │
│  │ Tools    │  │ Tools     │  │ (direct)     │              │
│  └────┬─────┘  └─────┬─────┘  └──────┬───────┘              │
└───────┼──────────────┼────────────────┼──────────────────────┘
        │              │                │
        └──────────────┼────────────────┘
                       │
┌──────────────────────┼───────────────────────────────────────┐
│            Shared Business Logic                             │
│  ┌─────────────────────────────────────────────┐             │
│  │ Unified ToolResult (single class)           │             │
│  │                                              │             │
│  │ Focused Logic Classes:                      │             │
│  │ - TableRegistryLogic (~200 lines)           │             │
│  │ - ProcessRegistryLogic (~200 lines)         │             │
│  │ - WindowRegistryLogic (~200 lines)          │             │
│  │ - QueryToolLogic (unified SQL)              │             │
│  │ - RestDataToolLogic                         │             │
│  │ - GeneratorToolLogic                        │             │
│  │                                              │             │
│  │ ConnectionProvider (unified DB access)      │             │
│  └─────────────────────────────────────────────┘             │
│                                                               │
│  ✅ IMPROVEMENTS:                                             │
│  - Single ToolResult class                                   │
│  - No DatabaseQueryTool (use QueryToolLogic)                 │
│  - Shared ConnectionProvider with pooling                    │
│  - Small, focused classes (SRP)                              │
└───────────────────────────────────────────────────────────────┘

Migration Plan

Phase 1: Unify ToolResult (1 week) - HIGHEST PRIORITY

Tasks:

  1. Add missing fields to ai/shared/ToolResult:
    • content field
    • metadata map
    • executionTimeMs field
  2. Add conversion method: toChatApiResponse()
  3. Update all shared logic classes to use unified ToolResult
  4. Update MCP/LangChain4j wrappers
  5. Update DatabaseQueryTool temporarily
  6. Deprecate chatapi/tool/ToolResult
  7. Plan removal after Phase 2

Success Criteria:


Phase 2: Consolidate SQL Execution (3 days)

Tasks:

  1. Add optional ChatContext parameter to QueryToolLogic.executeQuery()
  2. Add client filtering support when context provided
  3. Update ChatToolProvider to use QueryToolLogic instead of DatabaseQueryTool
  4. Deprecate DatabaseQueryTool
  5. Update tests
  6. Remove chatapi/tool/ToolResult (now safe)

Success Criteria:


Phase 3: Create ConnectionProvider (1 week)

Tasks:

  1. Create DatabaseConnectionProvider service
  2. Add DataSource preference with DriverManager fallback
  3. Update QueryToolLogic to use ConnectionProvider
  4. Update RegistryToolLogic to use ConnectionProvider
  5. Update DoctorToolLogic to use ConnectionProvider
  6. Update ADContextService to use ConnectionProvider
  7. Add connection pool configuration

Success Criteria:


Phase 4: Split RegistryToolLogic (1 week)

Tasks:

  1. Extract TableRegistryLogic (tables + columns)
  2. Extract ProcessRegistryLogic (processes + parameters)
  3. Extract WindowRegistryLogic (windows + tabs + fields)
  4. Extract ReferenceRegistryLogic (references + validation rules)
  5. Extract SemanticSearchLogic (RAG integration)
  6. Extract RegistryStatistics (AD statistics)
  7. Update all wrappers to use new classes
  8. Deprecate monolithic RegistryToolLogic
  9. Update tests

Success Criteria:


Phase 5: Cleanup ChatToolProvider (3 days)

Tasks:

  1. Decide: Eliminate or make real orchestrator
  2. If eliminate: Move @Tool methods to ChatAgent directly
  3. If orchestrator: Add real orchestration logic
  4. Update tests
  5. Remove old code

Success Criteria:


Code Smells Detected

1. God Object

2. Feature Envy

3. Duplicate Code

4. Inappropriate Intimacy


Strengths to Preserve

✅ Excellent Code Reuse

✅ Clean Wrapper Pattern

✅ Good Dependency Injection

✅ Hybrid Data Access (RegistryToolLogic)


Recommendations Summary

Priority Issue Effort Impact Phase
🔴 P1 Duplicate ToolResult 1 week High Phase 1
🔴 P2 DatabaseQueryTool duplication 3 days High Phase 2
🔴 P3 No ConnectionProvider 1 week Medium Phase 3
🔴 P4 RegistryToolLogic god object 1 week Medium Phase 4
🔴 P5 ChatToolProvider confusion 3 days Low Phase 5

Total Estimated Effort: 3-4 weeks

Expected Benefits:


Conclusion

The tool ecosystem shows excellent foundational design (code reuse, DI, separation of concerns) but suffers from incremental growth without refactoring. Each new AI integration (MCP → LangChain4j → Chat API) added layers without consolidating.

Root Cause: Organic evolution without periodic architectural review.

Solution: Systematic 4-week refactoring following phased migration plan.

Next Steps:

  1. Review this ADR with team
  2. Approve migration plan
  3. Start Phase 1 (unify ToolResult)
  4. Proceed sequentially through phases
  5. Maintain backward compatibility during migration

The result will be a cleaner, more maintainable architecture ready for future AI integrations.


References

Path: /docs/developers/architecture/idempiere-hub/055-tool-ecosystem-architectural-review