backend/docs/ai/integration-points.md
Rodrigo Verdiani 90ee74b3b9 docs: add AI module documentation and AGENTS.md
Comprehensive documentation for the AI module covering architecture,
API design, data model, RAG pipeline, configuration, integration points,
and frontend integration guide. Includes IDA5 extraction points reference
and AGENTS.md with project conventions and coding standards.
2026-06-08 20:13:21 -03:00

16 KiB

Pontos de Integração — Módulo AI com Sistema Existente

Visão Geral

O módulo ai/ não existe isoladamente — ele se integra profundamente com os módulos existentes (workspace, analysis, job) através de eventos de domínio, dependências Modulith e jobs assíncronos.

Este documento descreve cada ponto de integração, o fluxo de dados e as modificações necessárias nos módulos existentes.


1. Diagrama de Sequência Completo

Usuário         Controller    AnalysisSvc    JobModule       AI Module      Ollama/LLM
  │                 │              │              │               │              │
  │ POST /upload    │              │              │               │              │
  ├────────────────►│              │              │               │              │
  │                 │  save        │              │               │              │
  │                 ├─────────────►│              │               │              │
  │                 │              │ BinaryUp-    │               │              │
  │                 │              │ loadedEvent  │               │              │
  │                 │              ├─────────────►│               │              │
  │                 │              │              │ ANALYZE_FILE  │              │
  │                 │              │              │ (DiE Docker)  │              │
  │                 │              │              │               │              │
  │ POST /analyze   │              │              │               │              │
  ├────────────────►│              │              │               │              │
  │                 │              │ StaticAnlys  │               │              │
  │                 │              │ RequestedEvt │               │              │
  │                 │              ├─────────────►│               │              │
  │                 │              │              │ STATIC_ANALYS │              │
  │                 │              │              │ (IDA5 Docker) │              │
  │                 │              │◄─────────────┤ saveResult()  │              │
  │                 │              │              │               │              │
  │                 │              │ StaticAnlys  │               │              │
  │                 │              │ CompletedEvt │               │              │
  │                 │              ├─────────────►│               │              │
  │                 │              │              │ GENERATE_EMB  │              │
  │                 │              │              ├──────────────►│              │
  │                 │              │              │               │ indexBinary() │
  │                 │              │              │               ├──────────────►│
  │                 │              │              │               │◄──────────────┤
  │                 │              │              │◄──────────────┤  embeddings   │
  │                 │              │              │               │               │
  │                 │              │              │  (binário indexado)           │
  │                 │              │              │               │               │
  │ POST /chat/...  │              │              │               │               │
  ├────────────────►│              │              │               │               │
  │                 │              │              │               │ chat()        │
  │                 │              │              │               ├──────────────►│
  │                 │◄─────────────┼──────────────┼───────────────┤ SSE stream    │
  │◄────────────────┤              │              │               │               │

2. Eventos

2.1 Novo Evento: StaticAnalysisCompletedEvent

Definição: analysis/event/StaticAnalysisCompletedEvent.java

package ai.decompile.analysis.event;

import java.util.UUID;

/**
 * Published when a static analysis job completes successfully.
 * Triggers the embedding generation pipeline.
 */
public record StaticAnalysisCompletedEvent(
    UUID binaryId,
    UUID projectId,
    UUID workspaceId,
    UUID analysisId,
    String engine
) {}

Quem publica: StaticAnalysisHandler.process() no módulo job

Quem consome: EmbeddingEventSubscriber no módulo job

Momento da publicação: Após analysisService.saveResult() e binaryService.markStaticAnalysisDone() concluírem com sucesso.

2.2 Publicação no StaticAnalysisHandler

Arquivo a modificar: job/service/handler/StaticAnalysisHandler.java

Mudança: Adicionar publicação do evento após salvar resultados:

// Dentro de process(), após saveResult() e markStaticAnalysisDone():
@Autowired
private ApplicationEventPublisher eventPublisher;

// ... no final de process():
var binary = binaryService.getBinary(job.getBinaryId());
eventPublisher.publishEvent(
    new StaticAnalysisCompletedEvent(
        binary.getId(),
        binary.getProject().getId(),
        binary.getProject().getWorkspace().getId(),
        analysisResponse.id(),
        engineName
    )
);

Alternativa com Spring Modulith: Usar @ApplicationModuleListener exige que o evento seja publicado via ApplicationEventPublisher do Spring. O Spring Modulith intercepta e persiste na tabela event_publication para garantia de entrega.

2.3 Novo Subscriber: EmbeddingEventSubscriber

Arquivo a criar: job/service/EmbeddingEventSubscriber.java

package ai.decompile.job.service;

import ai.decompile.analysis.event.StaticAnalysisCompletedEvent;
import ai.decompile.job.messaging.JobMessagePublisher;
import ai.decompile.job.model.entity.Job;
import ai.decompile.job.model.enums.JobStatus;
import ai.decompile.job.model.enums.JobType;
import ai.decompile.job.model.repository.JobRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.modulith.events.ApplicationModuleListener;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Log4j2
@Component
@RequiredArgsConstructor
public class EmbeddingEventSubscriber {

    private final JobRepository jobRepository;
    private final JobMessagePublisher jobMessagePublisher;

    @ApplicationModuleListener
    public void on(StaticAnalysisCompletedEvent event) {
        log.info(">>> EmbeddingEventSubscriber: Received StaticAnalysisCompletedEvent binary={}", event.binaryId());

        var job = createJob(event);
        log.info(">>> EmbeddingEventSubscriber: Created GENERATE_EMBEDDINGS job {} for binary {}", job.getId(), event.binaryId());
        jobMessagePublisher.publish(job.getId());
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    Job createJob(StaticAnalysisCompletedEvent event) {
        return jobRepository.save(
            Job.builder()
                .type(JobType.GENERATE_EMBEDDINGS)
                .status(JobStatus.ENQUEUED)
                .projectId(event.projectId())
                .binaryId(event.binaryId())
                .payload("{\"analysisId\":\"" + event.analysisId() + "\"}")
                .retryCount(0)
                .build()
        );
    }
}

3. Jobs

3.1 Novo JobType

Arquivo a modificar: job/model/enums/JobType.java

public enum JobType {
    ANALYZE_FILE,
    STATIC_ANALYSIS,
    GENERATE_EMBEDDINGS   // <-- novo
}

3.2 Novo Handler: EmbeddingGenerationHandler

Arquivo a criar: job/service/handler/EmbeddingGenerationHandler.java

package ai.decompile.job.service.handler;

import ai.decompile.ai.service.EmbeddingService;
import ai.decompile.job.model.entity.Job;
import ai.decompile.job.model.enums.JobStatus;
import ai.decompile.job.model.enums.JobType;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Component;

@Log4j2
@Component
public class EmbeddingGenerationHandler implements JobHandler {

    private final EmbeddingService embeddingService;
    private final ObjectMapper objectMapper;

    public EmbeddingGenerationHandler(EmbeddingService embeddingService, ObjectMapper objectMapper) {
        this.embeddingService = embeddingService;
        this.objectMapper = objectMapper;
    }

    @Override
    public void process(Job job) {
        log.info("Processing GENERATE_EMBEDDINGS job {} for binary {}", job.getId(), job.getBinaryId());

        try {
            int chunks = embeddingService.indexBinary(job.getBinaryId());

            job.setResult("{\"chunkCount\":" + chunks + "}");
            job.setStatus(JobStatus.COMPLETED);

            log.info("Job {} completed. Generated {} embedding chunks for binary {}",
                     job.getId(), chunks, job.getBinaryId());
        } catch (Exception e) {
            log.error("Job {} failed: {}", job.getId(), e.getMessage(), e);
            job.setStatus(JobStatus.FAILED);
            job.setErrorMessage(e.getMessage());
        }
    }

    @Override
    public JobType supportedType() {
        return JobType.GENERATE_EMBEDDINGS;
    }
}

Nota: EmbeddingGenerationHandler não precisa de @ConditionalOnProperty como StaticAnalysisHandler, pois embeddings não dependem do Docker. Ollama roda no host.

3.3 Atualizar JobHandlerFactory

Nenhuma mudança necessária — o factory já descobre automaticamente todos os beans JobHandler via injeção de lista.

4. Dependências Modulith

4.1 Atualizar job/package-info.java

Arquivo a modificar: job/package-info.java

@org.springframework.modulith.ApplicationModule(
    displayName = "Job",
    allowedDependencies = {
        "workspace::events",
        "workspace::entities",
        "workspace::services",
        "die::service",
        "die::model",
        "analysis::events",
        "analysis::services",
        "analysis::dto",
        "engine::services",
        "ai::services",        // <-- novo: EmbeddingService
        "ai::model::entity",   // <-- novo: EmbeddingChunk (se necessário)
        "common"
    })
package ai.decompile.job;

4.2 analysis/package-info.java

Não precisa ser alterado. O evento StaticAnalysisCompletedEvent é definido no próprio módulo analysis (analysis.event), então não há nova dependência.

4.3 ai/package-info.java (novo)

@org.springframework.modulith.ApplicationModule(
    displayName = "AI",
    allowedDependencies = {
        "workspace::entities",       // Binary, Project (para JPA relationships)
        "workspace::services",       // BinaryService (para buscar filePath, metadados)
        "analysis::entities",        // StaticFunction, StaticAnalysis, StaticXref, StaticLabel
        "analysis::services",        // AnalysisService (para buscar funções/xrefs)
        "common"                     // exceções, config
    })
package ai.decompile.ai;

5. Modificações em Arquivos Existentes

5.1 pom.xml

Adicionar dependências Spring AI (Spring AI BOM, ollama, openai, pgvector-store).

5.2 application.yml

Adicionar blocos spring.ai.* e app.ai.* (ver configuration.md).

5.3 job/model/enums/JobType.java

Adicionar GENERATE_EMBEDDINGS.

5.4 job/package-info.java

Adicionar ai::services e ai::model::entity aos allowedDependencies.

5.5 job/service/handler/StaticAnalysisHandler.java

Adicionar publicação de StaticAnalysisCompletedEvent ao final de process().

5.6 .gitignore

Adicionar .env.

6. Arquivos Novos (por módulo)

Módulo ai/

src/main/java/ai/decompile/ai/
├── package-info.java
├── config/
│   └── AiConfig.java
├── model/
│   ├── entity/
│   │   ├── package-info.java
│   │   ├── EmbeddingChunk.java
│   │   ├── ChatSession.java
│   │   └── ChatMessage.java
│   ├── repository/
│   │   ├── package-info.java
│   │   ├── EmbeddingChunkRepository.java
│   │   ├── ChatSessionRepository.java
│   │   └── ChatMessageRepository.java
│   └── dto/
│       ├── package-info.java
│       ├── ChatRequest.java
│       ├── SessionResponse.java
│       └── MessageResponse.java
├── service/
│   ├── package-info.java
│   ├── EmbeddingService.java
│   ├── RagService.java
│   └── ChatService.java
└── controller/
    ├── package-info.java
    └── ChatController.java

Módulo analysis/

src/main/java/ai/decompile/analysis/
└── event/
    └── StaticAnalysisCompletedEvent.java    # novo

Módulo job/

src/main/java/ai/decompile/job/
├── service/
│   ├── EmbeddingEventSubscriber.java        # novo
│   └── handler/
│       └── EmbeddingGenerationHandler.java   # novo
└── model/enums/
    └── JobType.java                         # modificado (+ GENERATE_EMBEDDINGS)

Database

src/main/resources/db/migration/
└── V008__ai_embedding_and_chat.sql          # novo

Raiz do projeto

backend/
├── .env                                     # novo
├── .env.example                             # novo (template sem secrets)
└── .gitignore                               # modificado (+ .env)

7. Ordem de Implementação Recomendada

Ordem Tarefa Depende de
1 V008__ai_embedding_and_chat.sql Nada
2 Dependências no pom.xml Nada
3 application.yml — blocos AI Nada
4 .env + .env.example + .gitignore Nada
5 Entidades JPA (EmbeddingChunk, ChatSession, ChatMessage) V008, pom.xml
6 Repositórios JPA Entidades
7 AiConfig.java pom.xml, application.yml
8 EmbeddingService Repositórios, AiConfig
9 RagService EmbeddingService, AnalysisService
10 ChatService RagService, AiConfig
11 ChatController ChatService
12 StaticAnalysisCompletedEvent Nada
13 EmbeddingEventSubscriber Evento, JobService
14 EmbeddingGenerationHandler EmbeddingService
15 JobType.GENERATE_EMBEDDINGS Nada
16 job/package-info.java update Dependências ai
17 StaticAnalysisHandler — publish event Evento
18 ai/package-info.java Dependências workspace, analysis
19 Testes de integração Tudo acima

8. Verificação de Integridade

Após implementar todos os pontos de integração, verificar:

# 1. Compilação limpa
./mvnw clean compile

# 2. Migration roda sem erros
./mvnw flyway:migrate

# 3. Testes de módulo Modulith
./mvnw test -Dtest="*Modulith*"

# 4. Fluxo completo (manual):
#    - Upload de binário
#    - Análise estática
#    - Verificar job GENERATE_EMBEDDINGS criado e concluído
#    - Verificar registros em embedding_chunk
#    - Criar sessão de chat
#    - Enviar mensagem e receber SSE stream