Voltar para o Blog

Integrating AI and Machine Learning in Java Applications

14 min de leitura
JavaAIMachine LearningIntegration

IA não é apenas para Python! Descubra como integrar capacidades de inteligência artificial e machine learning em suas aplicações Java usando frameworks populares e APIs modernas.

Por Que IA em Java?

Java é amplamente usado em aplicações enterprise onde performance, escalabilidade e confiabilidade são críticas. Integrar IA em aplicações Java existentes permite aproveitar modelos de ML sem reescrever toda a stack.

OpenAI API com Spring Boot

Configuração Básica

// build.gradle
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-webflux'
    implementation 'com.fasterxml.jackson.core:jackson-databind'
}

// application.yml
openai:
  api:
    key: ${OPENAI_API_KEY}
    url: https://api.openai.com/v1

// Configuration
@Configuration
public class OpenAIConfig {
    
    @Value("${openai.api.key}")
    private String apiKey;
    
    @Value("${openai.api.url}")
    private String apiUrl;
    
    @Bean
    public WebClient openAIWebClient() {
        return WebClient.builder()
            .baseUrl(apiUrl)
            .defaultHeader("Authorization", "Bearer " + apiKey)
            .defaultHeader("Content-Type", "application/json")
            .build();
    }
}

Chat Completion Service

@Service
public class OpenAIService {
    
    @Autowired
    private WebClient openAIWebClient;
    
    public Mono<String> generateChatCompletion(String prompt) {
        ChatCompletionRequest request = ChatCompletionRequest.builder()
            .model("gpt-4")
            .messages(List.of(
                new Message("system", "You are a helpful assistant."),
                new Message("user", prompt)
            ))
            .temperature(0.7)
            .maxTokens(500)
            .build();
        
        return openAIWebClient.post()
            .uri("/chat/completions")
            .bodyValue(request)
            .retrieve()
            .bodyToMono(ChatCompletionResponse.class)
            .map(response -> response.getChoices().get(0).getMessage().getContent());
    }
    
    public Flux<String> generateStreamingCompletion(String prompt) {
        ChatCompletionRequest request = ChatCompletionRequest.builder()
            .model("gpt-4")
            .messages(List.of(new Message("user", prompt)))
            .stream(true)
            .build();
        
        return openAIWebClient.post()
            .uri("/chat/completions")
            .bodyValue(request)
            .retrieve()
            .bodyToFlux(String.class)
            .map(this::extractContent);
    }
}

// DTOs
@Data
@Builder
public class ChatCompletionRequest {
    private String model;
    private List<Message> messages;
    private Double temperature;
    private Integer maxTokens;
    private Boolean stream;
}

@Data
@AllArgsConstructor
public class Message {
    private String role;
    private String content;
}

@Data
public class ChatCompletionResponse {
    private List<Choice> choices;
    
    @Data
    public static class Choice {
        private Message message;
        private String finishReason;
    }
}

REST Controller para Chat

@RestController
@RequestMapping("/api/ai")
public class AIController {
    
    @Autowired
    private OpenAIService openAIService;
    
    @PostMapping("/chat")
    public Mono<ResponseEntity<ChatResponse>> chat(
            @RequestBody ChatRequest request) {
        return openAIService.generateChatCompletion(request.getMessage())
            .map(response -> ResponseEntity.ok(new ChatResponse(response)));
    }
    
    @GetMapping(value = "/chat/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<String> chatStream(@RequestParam String message) {
        return openAIService.generateStreamingCompletion(message);
    }
}

@Data
public class ChatRequest {
    @NotBlank
    private String message;
}

@Data
@AllArgsConstructor
public class ChatResponse {
    private String reply;
}

Deep Learning com DL4J (DeepLearning4J)

Configuração e Modelo Simples

// build.gradle
dependencies {
    implementation 'org.deeplearning4j:deeplearning4j-core:1.0.0-M2.1'
    implementation 'org.nd4j:nd4j-native-platform:1.0.0-M2.1'
}

@Service
public class MLModelService {
    
    private MultiLayerNetwork model;
    
    @PostConstruct
    public void initModel() {
        MultiLayerConfiguration conf = new NeuralNetConfiguration.Builder()
            .seed(123)
            .weightInit(WeightInit.XAVIER)
            .updater(new Adam(0.001))
            .list()
            .layer(new DenseLayer.Builder()
                .nIn(784)  // 28x28 pixels
                .nOut(250)
                .activation(Activation.RELU)
                .build())
            .layer(new DenseLayer.Builder()
                .nIn(250)
                .nOut(100)
                .activation(Activation.RELU)
                .build())
            .layer(new OutputLayer.Builder(LossFunctions.LossFunction.NEGATIVELOGLIKELIHOOD)
                .nIn(100)
                .nOut(10)  // 10 classes (digits 0-9)
                .activation(Activation.SOFTMAX)
                .build())
            .build();
        
        model = new MultiLayerNetwork(conf);
        model.init();
    }
    
    public void trainModel(INDArray features, INDArray labels, int epochs) {
        DataSet dataSet = new DataSet(features, labels);
        
        for (int i = 0; i < epochs; i++) {
            model.fit(dataSet);
            log.info("Epoch {} completed", i);
        }
    }
    
    public int predict(INDArray input) {
        INDArray output = model.output(input);
        return Nd4j.argMax(output, 1).getInt(0);
    }
    
    public void saveModel(String path) throws IOException {
        ModelSerializer.writeModel(model, new File(path), true);
    }
    
    public void loadModel(String path) throws IOException {
        model = ModelSerializer.restoreMultiLayerNetwork(new File(path));
    }
}

Apache Spark MLlib para Big Data

Pipeline de Machine Learning

// build.gradle
dependencies {
    implementation 'org.apache.spark:spark-core_2.12:3.5.0'
    implementation 'org.apache.spark:spark-mllib_2.12:3.5.0'
}

@Service
public class SparkMLService {
    
    private SparkSession spark;
    
    @PostConstruct
    public void initSpark() {
        spark = SparkSession.builder()
            .appName("ML Service")
            .master("local[*]")
            .config("spark.sql.warehouse.dir", "/tmp/spark-warehouse")
            .getOrCreate();
    }
    
    public PipelineModel trainClassificationModel(String dataPath) {
        // Load data
        Dataset<Row> data = spark.read()
            .format("csv")
            .option("header", "true")
            .option("inferSchema", "true")
            .load(dataPath);
        
        // Split data
        Dataset<Row>[] splits = data.randomSplit(new double[]{0.8, 0.2}, 123);
        Dataset<Row> trainingData = splits[0];
        Dataset<Row> testData = splits[1];
        
        // Create pipeline
        VectorAssembler assembler = new VectorAssembler()
            .setInputCols(new String[]{"feature1", "feature2", "feature3"})
            .setOutputCol("features");
        
        RandomForestClassifier rf = new RandomForestClassifier()
            .setLabelCol("label")
            .setFeaturesCol("features")
            .setNumTrees(100);
        
        Pipeline pipeline = new Pipeline()
            .setStages(new PipelineStage[]{assembler, rf});
        
        // Train model
        PipelineModel model = pipeline.fit(trainingData);
        
        // Evaluate
        Dataset<Row> predictions = model.transform(testData);
        MulticlassClassificationEvaluator evaluator = 
            new MulticlassClassificationEvaluator()
                .setLabelCol("label")
                .setPredictionCol("prediction")
                .setMetricName("accuracy");
        
        double accuracy = evaluator.evaluate(predictions);
        log.info("Test Accuracy: {}", accuracy);
        
        return model;
    }
    
    public double predict(PipelineModel model, Map<String, Object> features) {
        // Create DataFrame from features
        Row row = RowFactory.create(
            features.get("feature1"),
            features.get("feature2"),
            features.get("feature3")
        );
        
        Dataset<Row> input = spark.createDataFrame(
            Collections.singletonList(row),
            createSchema()
        );
        
        Dataset<Row> prediction = model.transform(input);
        return prediction.select("prediction").first().getDouble(0);
    }
    
    @PreDestroy
    public void cleanup() {
        if (spark != null) {
            spark.stop();
        }
    }
}

Hugging Face Transformers

Usando Modelos Pre-treinados

// Usando API HTTP da Hugging Face
@Service
public class HuggingFaceService {
    
    @Value("${huggingface.api.key}")
    private String apiKey;
    
    private final WebClient webClient;
    
    public HuggingFaceService(WebClient.Builder builder) {
        this.webClient = builder
            .baseUrl("https://api-inference.huggingface.co/models")
            .defaultHeader("Authorization", "Bearer " + apiKey)
            .build();
    }
    
    public Mono<String> analyzeSentiment(String text) {
        return webClient.post()
            .uri("/distilbert-base-uncased-finetuned-sst-2-english")
            .bodyValue(Map.of("inputs", text))
            .retrieve()
            .bodyToMono(String.class);
    }
    
    public Mono<String> generateText(String prompt) {
        return webClient.post()
            .uri("/gpt2")
            .bodyValue(Map.of(
                "inputs", prompt,
                "parameters", Map.of(
                    "max_length", 100,
                    "temperature", 0.7
                )
            ))
            .retrieve()
            .bodyToMono(String.class);
    }
    
    public Mono<String> classifyText(String text, List<String> labels) {
        return webClient.post()
            .uri("/facebook/bart-large-mnli")
            .bodyValue(Map.of(
                "inputs", text,
                "parameters", Map.of("candidate_labels", labels)
            ))
            .retrieve()
            .bodyToMono(String.class);
    }
}

Processamento de Linguagem Natural com OpenNLP

Análise de Texto

// build.gradle
dependencies {
    implementation 'org.apache.opennlp:opennlp-tools:2.3.0'
}

@Service
public class NLPService {
    
    private TokenizerME tokenizer;
    private POSTaggerME posTagger;
    private NameFinderME nameFinder;
    
    @PostConstruct
    public void loadModels() throws IOException {
        // Load tokenizer model
        try (InputStream tokenStream = getClass()
                .getResourceAsStream("/models/en-token.bin")) {
            TokenizerModel tokenModel = new TokenizerModel(tokenStream);
            tokenizer = new TokenizerME(tokenModel);
        }
        
        // Load POS tagger model
        try (InputStream posStream = getClass()
                .getResourceAsStream("/models/en-pos-maxent.bin")) {
            POSModel posModel = new POSModel(posStream);
            posTagger = new POSTaggerME(posModel);
        }
        
        // Load name finder model
        try (InputStream nameStream = getClass()
                .getResourceAsStream("/models/en-ner-person.bin")) {
            TokenNameFinderModel nameModel = new TokenNameFinderModel(nameStream);
            nameFinder = new NameFinderME(nameModel);
        }
    }
    
    public TextAnalysisResult analyzeText(String text) {
        // Tokenize
        String[] tokens = tokenizer.tokenize(text);
        
        // POS tagging
        String[] tags = posTagger.tag(tokens);
        
        // Named entity recognition
        Span[] nameSpans = nameFinder.find(tokens);
        String[] names = Span.spansToStrings(nameSpans, tokens);
        
        return TextAnalysisResult.builder()
            .tokens(Arrays.asList(tokens))
            .posTags(Arrays.asList(tags))
            .entities(Arrays.asList(names))
            .build();
    }
    
    public SentimentScore analyzeSentiment(String text) {
        // Simple sentiment analysis based on keywords
        String lowerText = text.toLowerCase();
        
        List<String> positiveWords = Arrays.asList(
            "good", "great", "excellent", "amazing", "wonderful"
        );
        List<String> negativeWords = Arrays.asList(
            "bad", "terrible", "awful", "horrible", "poor"
        );
        
        long positive = positiveWords.stream()
            .filter(lowerText::contains)
            .count();
        
        long negative = negativeWords.stream()
            .filter(lowerText::contains)
            .count();
        
        double score = (positive - negative) / (double) (positive + negative + 1);
        
        return new SentimentScore(score, 
            score > 0.3 ? "positive" : (score < -0.3 ? "negative" : "neutral"));
    }
}

@Data
@Builder
public class TextAnalysisResult {
    private List<String> tokens;
    private List<String> posTags;
    private List<String> entities;
}

@Data
@AllArgsConstructor
public class SentimentScore {
    private double score;
    private String sentiment;
}

Embeddings e Busca Semântica

Vector Database com Pinecone

@Service
public class EmbeddingService {
    
    @Autowired
    private OpenAIService openAIService;
    
    @Autowired
    private WebClient pineconeClient;
    
    public Mono<List<Double>> generateEmbedding(String text) {
        return openAIService.createEmbedding(text)
            .map(EmbeddingResponse::getData)
            .map(data -> data.get(0).getEmbedding());
    }
    
    public Mono<Void> storeEmbedding(String id, String text, Map<String, String> metadata) {
        return generateEmbedding(text)
            .flatMap(embedding -> {
                VectorUpsertRequest request = VectorUpsertRequest.builder()
                    .vectors(List.of(Vector.builder()
                        .id(id)
                        .values(embedding)
                        .metadata(metadata)
                        .build()))
                    .build();
                
                return pineconeClient.post()
                    .uri("/vectors/upsert")
                    .bodyValue(request)
                    .retrieve()
                    .bodyToMono(Void.class);
            });
    }
    
    public Mono<List<SearchResult>> semanticSearch(String query, int topK) {
        return generateEmbedding(query)
            .flatMap(embedding -> {
                QueryRequest request = QueryRequest.builder()
                    .vector(embedding)
                    .topK(topK)
                    .includeMetadata(true)
                    .build();
                
                return pineconeClient.post()
                    .uri("/query")
                    .bodyValue(request)
                    .retrieve()
                    .bodyToMono(QueryResponse.class);
            })
            .map(QueryResponse::getMatches);
    }
}

@Data
@Builder
public class Vector {
    private String id;
    private List<Double> values;
    private Map<String, String> metadata;
}

@Data
public class SearchResult {
    private String id;
    private double score;
    private Map<String, String> metadata;
}

Casos de Uso Práticos

1. Chatbot Inteligente

@Service
public class ChatbotService {
    
    @Autowired
    private OpenAIService openAIService;
    
    @Autowired
    private EmbeddingService embeddingService;
    
    private final Map<String, List<Message>> conversationHistory = new ConcurrentHashMap<>();
    
    public Mono<String> chat(String userId, String message) {
        // Get conversation history
        List<Message> history = conversationHistory
            .computeIfAbsent(userId, k -> new ArrayList<>());
        
        // Add user message
        history.add(new Message("user", message));
        
        // Search relevant context
        return embeddingService.semanticSearch(message, 3)
            .flatMap(results -> {
                String context = results.stream()
                    .map(r -> r.getMetadata().get("content"))
                    .collect(Collectors.joining("\n"));
                
                // Build prompt with context
                List<Message> messages = new ArrayList<>();
                messages.add(new Message("system", 
                    "You are a helpful assistant. Use this context: " + context));
                messages.addAll(history);
                
                return openAIService.generateChatCompletion(messages);
            })
            .doOnNext(reply -> {
                // Add assistant reply to history
                history.add(new Message("assistant", reply));
                
                // Keep only last 10 messages
                if (history.size() > 10) {
                    history.subList(0, history.size() - 10).clear();
                }
            });
    }
}

2. Sistema de Recomendação

@Service
public class RecommendationService {
    
    @Autowired
    private SparkMLService sparkMLService;
    
    public List<ProductRecommendation> getRecommendations(
            Long userId, int count) {
        
        // Get user preferences
        UserPreferences prefs = getUserPreferences(userId);
        
        // Generate features
        Map<String, Object> features = Map.of(
            "age", prefs.getAge(),
            "avgPurchaseAmount", prefs.getAvgPurchaseAmount(),
            "categoryPreference", prefs.getCategoryPreference()
        );
        
        // Get predictions from ML model
        double score = sparkMLService.predict(model, features);
        
        // Query similar products
        return productRepository.findSimilarProducts(score, count);
    }
}

Melhores Práticas

  • Cache resultados: APIs de IA são caras - cache quando possível
  • Implemente rate limiting: Proteja contra uso excessivo
  • Use async/reactive: Requisições AI são lentas - não bloqueie threads
  • Monitor custos: APIs pagas podem acumular custos rapidamente
  • Fallback strategies: Tenha planos B quando IA falha
  • Valide outputs: IA pode gerar respostas inesperadas
  • Fine-tune quando necessário: Modelos gerais nem sempre são ideais
  • Segurança: Nunca exponha API keys, use secrets management

Conclusão

Integrar IA em aplicações Java não é mais um desafio técnico - é uma questão de escolher as ferramentas certas e aplicá-las nos casos de uso apropriados.

De chatbots a sistemas de recomendação, as possibilidades são infinitas. Comece pequeno, experimente, e gradualmente construa soluções mais sofisticadas.