Skip to main content

🔧 Foundation

Purpose: This guide provides step-by-step instructions for porting the minimal AI foundation (ChatModel infrastructure) from the Constellation POC.

Dependencies: This is infrastructure, not a user-facing feature. It serves as the foundation for all AI capabilities.

Related Documents:

Note: This guide is specific to Text Operations (TextOps) implementation. Once Text Operations is fully implemented and validated, this entire text-intelligence/ folder will be archived.


Overview

What This Guide Covers

Phase 1 ports minimal infrastructure from the CBT Buddy POC:

  • ChatModel bean + Ollama configuration
  • LangChain4j integration (basic wiring)
  • Constants class (AiConstants)
  • Application.yml AI configuration
  • Docker Compose (Ollama service)
  • Text generation infrastructure (ChatModel wired, works internally)

What This Guide Does NOT Cover:

  • ❌ RAG with document ingestion (Phase 2)
  • ❌ Embeddings and pgvector (Phase 2)
  • ❌ Document/Chunk entities (Phase 2)
  • ❌ Vector similarity search (Phase 2)
  • ❌ Productized Text Operations Service (Phase 3)

Prerequisites

  • Springular boilerplate setup
  • Docker installed and running
  • Access to Constellation POC codebase for reference
  • Basic understanding of Spring Boot configuration

Package Structure Setup

Before implementing, create the package structure:

server/src/main/java/com/saas/springular/common/ai/
├── config/
├── constants/
├── model/
├── service/
│ └── impl/
└── controller/

Create these directories (empty for now, files will be added in subsequent steps).

Layer Responsibilities:

  • config/: Spring configuration beans. Creates ChatModel beans, configures timeouts, temperatures. No business logic.
  • constants/: Single source of truth for default values (timeouts, temperatures, limits). Used in @Value defaults.
  • model/: Request/Response DTOs (will be added in Phase 3). Stateless records.
  • service/: Business logic interfaces (will be added in Phase 3).
  • service/impl/: Service implementations (will be added in Phase 3). Stateless.
  • controller/: REST endpoints (will be added in Phase 3). Delegates to service layer.

POC Components to Port

1. AIConfiguration.java (ChatModel Bean)

POC Location: constellation/backend/server/src/main/java/com/saas/springular/common/ai/config/AIConfiguration.java

What to Port:

  • Single ChatModel bean (OllamaChatModel)
  • @Value configuration injection
  • Base URL, model name, timeout configuration

What NOT to Port Yet:

  • EmbeddingModel bean (Phase 2)
  • Multiple ChatModel beans with different temperatures (Phase 3)

2. AiConstants.java

POC Location: constellation/backend/server/src/main/java/com/saas/springular/common/ai/constants/AiConstants.java

What to Port:

  • DEFAULT_CHAT_MODEL constant
  • DEFAULT_TIMEOUT_MS constant
  • DEFAULT_TEMPERATURE constant
  • EMBEDDING_DIMENSIONS constant (for future use)

3. Application.yml (Ollama Config Section)

POC Location: constellation/backend/server/src/main/resources/application.yml

What to Port:

  • ollama.base-url property
  • ollama.chat-model property
  • ollama.timeout property

4. Docker Compose (Ollama Service)

POC Location: constellation/backend/docker-compose.yml

What to Port:

  • Ollama service definition
  • Port mapping (11434:11434)
  • Image: ollama/ollama:latest

5. LangChain4j Dependencies (build.gradle)

POC Location: constellation/backend/server/build.gradle

What to Port:

  • langchain4j-ollama dependency
  • langchain4j-core dependency

6. JacksonConfiguration Enhancement

POC Location: Check for JacksonConfiguration in POC

What to Port:

  • JavaTimeModule registration (if not already present)
  • Required for JSON serialization/deserialization

Step-by-Step Porting Instructions

Step 1: Add LangChain4j Dependencies

File: server/build.gradle

Add dependencies:

dependencies {
// ... existing dependencies ...

// LangChain4j
implementation 'dev.langchain4j:langchain4j-ollama:0.29.1'
implementation 'dev.langchain4j:langchain4j-core:0.29.1'
}

Step 2: Create AiConstants Class

File: server/src/main/java/com/saas/springular/common/ai/constants/AiConstants.java

Key Points:

  • ✅ Single source of truth for all default values
  • ✅ Used in @Value defaults
  • ✅ Prevents configuration drift

Create file:

package com.saas.springular.common.ai.constants;

/**
* Constants for AI configuration.
* Single source of truth for default values used throughout the AI module.
*/
public final class AiConstants {
private AiConstants() {}

// Ollama connection defaults
public static final String DEFAULT_OLLAMA_BASE_URL = "http://localhost:11434";

// Chat model defaults
public static final String DEFAULT_CHAT_MODEL = "llama2:7b";
public static final int DEFAULT_TIMEOUT_MS = 300000; // 5 minutes (appropriate for local Ollama)
public static final double DEFAULT_TEMPERATURE = 0.7; // Balanced creativity

// Future use (Phase 2)
public static final int EMBEDDING_DIMENSIONS = 768; // For future embeddings
}

Constants Pattern:

  • All defaults centralized in one class
  • Used in @Value annotations: @Value("${ollama.timeout:" + AiConstants.DEFAULT_TIMEOUT_MS + "}")
  • Prevents magic numbers scattered throughout code

Step 3: Add Ollama Configuration to application.yml

File: server/src/main/resources/application.yml

Add section:

ollama:
base-url: ${OLLAMA_BASE_URL:http://localhost:11434}
chat-model: ${OLLAMA_MODEL:llama2:7b}
timeout: ${OLLAMA_TIMEOUT:300000} # 5 minutes (default for local Ollama)

Configuration Notes:

  • Base URL defaults to http://localhost:11434 (local Ollama)
  • Timeout defaults to 300000ms (5 minutes) - appropriate for local Ollama
  • Environment variables can override defaults for different environments

Step 4: Create AIConfiguration Class

File: server/src/main/java/com/saas/springular/common/ai/config/AIConfiguration.java

Key Points:

  • ✅ Configure timeout explicitly (required for reliability)
  • ✅ Use constants from AiConstants for defaults
  • ✅ Add logging to verify bean creation

Create file:

package com.saas.springular.common.ai.config;

import com.saas.springular.common.ai.constants.AiConstants;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.ollama.OllamaChatModel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.time.Duration;

@Slf4j
@Configuration
public class AIConfiguration {

@Value("${ollama.base-url:" + AiConstants.DEFAULT_OLLAMA_BASE_URL + "}")
private String baseUrl;

@Value("${ollama.chat-model:" + AiConstants.DEFAULT_CHAT_MODEL + "}")
private String chatModelName;

@Value("${ollama.timeout:" + AiConstants.DEFAULT_TIMEOUT_MS + "}")
private long timeoutMs;

@Bean
public ChatLanguageModel chatModel() {
log.info("Configuring OllamaChatModel: {} with base URL: {}, timeout: {}ms",
chatModelName, baseUrl, timeoutMs);

return OllamaChatModel.builder()
.baseUrl(baseUrl)
.modelName(chatModelName)
.timeout(Duration.ofMillis(timeoutMs)) // ✅ Timeout explicitly configured
.temperature(AiConstants.DEFAULT_TEMPERATURE)
.build();
}
}

Configuration Notes:

  • Timeout is configured explicitly (5 minutes default for local Ollama)
  • Logging added to verify bean creation and configuration values
  • Constants used for defaults (single source of truth)
  • Base URL includes default value for easier local development

Step 5: Add Ollama Service to Docker Compose

File: docker-compose.yml

Add service:

services:
# ... existing services ...

ollama:
image: ollama/ollama:latest
ports:
- "11434:11434"
volumes:
- ollama_data:/root/.ollama
# Optional: set environment variables for model download
environment:
- OLLAMA_HOST=0.0.0.0

volumes:
ollama_data:

Step 6: Verify JacksonConfiguration (If Needed)

Check: If JacksonConfiguration exists, ensure JavaTimeModule is registered.

File: server/src/main/java/com/saas/springular/config/JacksonConfiguration.java (if exists)

Ensure it includes:

@Configuration
public class JacksonConfiguration {

@Bean
public ObjectMapper objectMapper() {
return JsonMapper.builder()
.addModule(new JavaTimeModule())
.build();
}
}

Note: Spring Boot may auto-configure this. Verify it works for JSON serialization.


Verification Checklist

After completing Phase 1 Foundation, verify:

  • ChatModel bean loads successfully (check application startup logs for configuration log message)
  • Health check endpoint works (if applicable)
  • Timeout is configured (check AIConfiguration logs show timeout value)
  • Logging correlation ID present (if using MDC)
  • No prompt content logged at INFO level by default (review logging configuration)
  • Docker Compose starts Ollama successfully
  • Application connects to Ollama (check logs for connection errors)

Quick Test:

Create a simple test to verify bean creation:

@SpringBootTest
class AIConfigurationTest {

@Autowired
private ChatLanguageModel chatModel;

@Test
void chatModelBeanExists() {
assertThat(chatModel).isNotNull();
}
}

2. Verify Ollama Service is Running

Test: Start Docker Compose and verify Ollama is accessible:

docker-compose up -d ollama
curl http://localhost:11434/api/tags

Expected: Should return list of available models.

3. Verify Basic Text Generation Works

Test: Create a simple service test:

@SpringBootTest
class BasicTextGenerationTest {

@Autowired
private ChatLanguageModel chatModel;

@Test
@Disabled("Requires Ollama running")
void generatesText() {
String response = chatModel.chat("Say hello in one word");
assertThat(response).isNotBlank();
}
}

Note: This requires Ollama to be running and a model to be downloaded. First run ollama pull llama2:7b in the Ollama container or host. Test is disabled by default to avoid requiring Ollama for all test runs.


Time Estimate

Total Time: 1-2 hours (porting infrastructure, not inventing)

Breakdown:

  • Dependencies: 5 minutes
  • Constants: 10 minutes
  • Configuration: 15 minutes
  • AIConfiguration class: 20 minutes
  • Docker Compose: 10 minutes
  • JacksonConfiguration check: 10 minutes
  • Testing and validation: 30 minutes

Next Steps

Once Phase 1 is complete:

  1. Phase 3: Build Text Operations Service (productized capabilities)

  2. Phase 2 (Later): Port full RAG/embeddings foundation when products need it


Troubleshooting

Issue: ChatModel bean not found

Solution: Verify @Configuration annotation on AIConfiguration class, check package scanning.

Issue: Ollama connection timeout

Solution: Verify Ollama service is running, check base URL in application.yml matches Docker port.

Issue: Model not found

Solution: Pull model in Ollama: ollama pull llama2:7b (or model name from config).

Issue: Jackson serialization errors

Solution: Ensure JavaTimeModule is registered in ObjectMapper configuration.