# 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` ```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: ```java // 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` ```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` ```java public enum JobType { ANALYZE_FILE, STATIC_ANALYSIS, GENERATE_EMBEDDINGS // <-- novo } ``` ### 3.2 Novo Handler: `EmbeddingGenerationHandler` **Arquivo a criar**: `job/service/handler/EmbeddingGenerationHandler.java` ```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` ```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) ```java @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: ```bash # 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 ```