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.
282 lines
18 KiB
Markdown
282 lines
18 KiB
Markdown
# Arquitetura do Módulo de IA
|
||
|
||
## Visão Geral
|
||
|
||
O módulo `ai/` implementa um sistema de **Retrieval-Augmented Generation (RAG)** que permite ao usuário interagir com uma LLM através de um chat, fazendo perguntas sobre funções, estruturas, protocolos de rede e outros aspectos de um binário que passou por análise estática.
|
||
|
||
O sistema combina **busca semântica via embeddings** (pgvector) com **navegação estruturada via call graph** (xrefs) para fornecer contexto relevante à LLM durante a geração de respostas.
|
||
|
||
## Stack Tecnológica
|
||
|
||
| Componente | Tecnologia | Papel |
|
||
|---|---|---|
|
||
| **Framework** | Spring Boot 4.0.6 + Spring Modulith 2.0.6 | Base da aplicação, injeção de dependências, eventos |
|
||
| **AI Framework** | Spring AI 1.0.x | Abstração de LLM providers, embeddings, chat streaming |
|
||
| **Chat Local** | Gemma 4 12B (via Ollama) | LLM para chat executando localmente, 256K contexto |
|
||
| **Chat Cloud** | DeepSeek (`deepseek-chat`) | LLM alternativa via API cloud (OpenAI-compatible) |
|
||
| **Embeddings** | Qwen3 Embedding 8B (via Ollama) | Geração de embeddings para busca semântica, dims 32–4096 |
|
||
| **Vector Store** | pgvector (PostgreSQL 18) | Armazenamento e busca por similaridade de cosseno |
|
||
| **Vector Index** | HNSW (Hierarchical Navigable Small World) | Índice para busca vetorial rápida |
|
||
| **Message Broker** | RabbitMQ 4 | Processamento assíncrono de jobs de embedding |
|
||
| **Arquitetura** | Spring Modulith | Módulo desacoplado com eventos de domínio |
|
||
|
||
## Diagrama de Componentes
|
||
|
||
```
|
||
┌──────────────────────────────────────────────────────────────────────────┐
|
||
│ ai.decompile.ai (Módulo AI) │
|
||
│ │
|
||
│ ┌──────────────────────┐ ┌──────────────────────┐ │
|
||
│ │ EmbeddingService │ │ ChatService │ │
|
||
│ │ │ │ │ │
|
||
│ │ • embed(text) │ │ • createSession() │ │
|
||
│ │ • embedBatch() │ │ • chat(sessionId, │ │
|
||
│ │ • indexBinary(id) │ │ message) │ │
|
||
│ │ • searchSimilar() │ │ • getHistory() │ │
|
||
│ │ • deleteByBinary() │ │ • deleteSession() │ │
|
||
│ └────────┬─────────────┘ └──────────┬───────────┘ │
|
||
│ │ │ │
|
||
│ ┌────────┴─────────────────────────────┴──────────────────┐ │
|
||
│ │ RagService │ │
|
||
│ │ │ │
|
||
│ │ • retrieveContext(binaryId, query) → List<Chunk> │ │
|
||
│ │ • buildSystemPrompt(chunks, binaryMeta) → String │ │
|
||
│ │ • expandCallGraph(functionId, hops) → List<Function> │ │
|
||
│ │ • deduplicateAndRank(results) → List<Chunk> │ │
|
||
│ └────────┬──────────────────────────────────────────────────┘ │
|
||
│ │ │
|
||
│ ┌────────┴────────────────┬──────────────────────────────┐ │
|
||
│ │ EmbeddingChunkRepo │ ChatSessionRepo │ │
|
||
│ │ • findSimilar(vector, │ • findByBinaryId() │ │
|
||
│ │ binaryId, topK) │ • findById() │ │
|
||
│ │ • deleteByBinaryId() │ │ │
|
||
│ │ │ ChatMessageRepo │ │
|
||
│ │ │ • findBySessionId() │ │
|
||
│ └──────────────────────────┴──────────────────────────────┘ │
|
||
│ │
|
||
│ ┌──────────────────────────────────────────────────────────┐ │
|
||
│ │ ChatController (REST + SSE) │ │
|
||
│ │ │ │
|
||
│ │ POST /binaries/{id}/chat/sessions — criar sessão │ │
|
||
│ │ GET /binaries/{id}/chat/sessions — listar sessões │ │
|
||
│ │ GET /chat/sessions/{id}/messages — histórico │ │
|
||
│ │ POST /chat/sessions/{id}/messages — enviar (SSE) │ │
|
||
│ │ DELETE /chat/sessions/{id} — deletar sessão │ │
|
||
│ └──────────────────────────────────────────────────────────┘ │
|
||
└──────────────────────────────────────────────────────────────────────────┘
|
||
|
||
┌──────────────────────────────────────────────────────────────────────────┐
|
||
│ Infraestrutura Externa │
|
||
│ │
|
||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
||
│ │ Ollama │ │ DeepSeek │ │ PostgreSQL │ │
|
||
│ │ (localhost) │ │ (Cloud API) │ │ (pgvector) │ │
|
||
│ │ │ │ │ │ │ │
|
||
│ │ qwen3-embed │ │ deepseek- │ │ embedding_ │ │
|
||
│ │ gemma4:12b │ │ chat │ │ chunk │ │
|
||
│ └──────────────┘ └──────────────┘ │ chat_session │ │
|
||
│ │ chat_message │ │
|
||
│ ┌──────────────┐ └──────────────┘ │
|
||
│ │ RabbitMQ │ │
|
||
│ │ │ │
|
||
│ │ decompile. │ │
|
||
│ │ jobs.queue │ │
|
||
│ └──────────────┘ │
|
||
└──────────────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
## Decisões Arquiteturais
|
||
|
||
### 1. Uso do Spring AI como Camada de Abstração
|
||
|
||
**Decisão**: Utilizar Spring AI 1.0.x como camada de abstração para providers de LLM e embeddings.
|
||
|
||
**Justificativa**:
|
||
- Fornece interfaces padronizadas (`ChatClient`, `StreamingChatClient`, `EmbeddingModel`)
|
||
- Suporte nativo a streaming via `Flux<String>` (Reactor)
|
||
- Auto-configuração para Ollama e OpenAI (DeepSeek é compatível com API da OpenAI)
|
||
- Integração com ecossistema Spring Boot
|
||
|
||
**Trade-off**: Spring AI é relativamente novo e pode ter breaking changes entre versões. Mitigamos encapsulando os chamados diretos atrás dos nossos serviços (`EmbeddingService`, `ChatService`).
|
||
|
||
### 2. Embedding Store Próprio (não Spring AI VectorStore)
|
||
|
||
**Decisão**: Implementar repositório pgvector próprio (`EmbeddingChunkRepository`) em vez de usar `spring-ai-pgvector-store`.
|
||
|
||
**Justificativa**:
|
||
- Schema altamente específico ao domínio (FK para `binary_id`, `source_id` para `static_function.id`)
|
||
- Necessidade de campos de metadados customizados em `jsonb`
|
||
- Semântica de deleção específica (hard delete por `binary_id` na re-indexação)
|
||
- Suporte a múltiplas estratégias de retrieval: busca semântica (pgvector `<=>`), busca textual (ILIKE fallback), call graph expansion
|
||
|
||
### 2.1 Hybrid Retrieval
|
||
|
||
**Decisão**: Pipeline de retrieval com três camadas: semântica → textual fallback → call graph.
|
||
|
||
**Fluxo**:
|
||
1. **Semântica**: pgvector cosine similarity (top-K)
|
||
2. **Fallback**: se < 40% do topK preenchido, busca textual via `LIKE` nos chunks
|
||
3. **Call Graph**: detecta funções/endereços na query, expande 1-hop via xrefs, inclui assembly inline
|
||
|
||
**Justificativa**:
|
||
- Queries com nomes específicos de função (`sub_f5`, `0x401000`) frequentemente falham na busca semântica (nome curto, sem contexto)
|
||
- O fallback textual captura menções diretas ao nome da função nos chunks
|
||
- A expansão por call graph garante que a função requisitada SEMPRE apareça no contexto, mesmo sem embedding
|
||
|
||
### 3. Indexação Assíncrona via Job Separado
|
||
|
||
**Decisão**: Geração de embeddings como job assíncrono (`GENERATE_EMBEDDINGS`) via RabbitMQ, desacoplado do job de análise estática.
|
||
|
||
**Justificativa**:
|
||
- Análise estática e embedding são operações com perfis de falha diferentes
|
||
- Permite re-indexar sem re-analisar (ex: trocar modelo de embedding)
|
||
- Permite retry independente se a API de embedding falhar
|
||
- Segue o padrão arquitetural já estabelecido (ANALYZE_FILE, STATIC_ANALYSIS)
|
||
|
||
### 4. Multi-Provider com Seleção por Sessão
|
||
|
||
**Decisão**: Suporte a múltiplos providers de LLM (Ollama/Gemma4 + DeepSeek) com seleção no momento da criação da sessão.
|
||
|
||
**Justificativa**:
|
||
- Usuário pode escolher entre privacidade/local (Ollama) ou qualidade/cloud (DeepSeek)
|
||
- Providers são "pluggable" — adicionar novo provider requer apenas implementar a interface e registrar
|
||
- A seleção é persistida na `chat_session.chat_model` e usada em todas as mensagens daquela sessão
|
||
|
||
### 5. Streaming via Server-Sent Events (SSE)
|
||
|
||
**Decisão**: Respostas do chat utilizam SSE para streaming de tokens.
|
||
|
||
**Justificativa**:
|
||
- UX muito melhor que esperar resposta completa
|
||
- Nativo do Spring WebMVC / Reactor (`Flux<ServerSentEvent<String>>`)
|
||
- Compatível com `StreamingChatClient` do Spring AI
|
||
- Fácil de consumir no frontend (`EventSource` API)
|
||
|
||
### 6. Chunking por Função (não por tamanho fixo)
|
||
|
||
**Decisão**: Cada função estática vira um chunk de embedding. Funções muito grandes são truncadas em `chunk-max-chars` (default 3000).
|
||
|
||
**Justificativa**:
|
||
- Alinha com a unidade semântica natural da análise de binários (a função)
|
||
- Permite que metadados de call graph (callers/callees) enriqueçam cada chunk
|
||
- Facilita a expansão via navegação de xrefs (saber exatamente qual função foi recuperada)
|
||
- Simplifica o tracking (`source_type=STATIC_FUNCTION`, `source_id=function.id`)
|
||
|
||
## Módulos Spring Modulith
|
||
|
||
### Dependências do módulo `ai/`
|
||
|
||
```
|
||
ai/
|
||
├── allowedDependencies:
|
||
│ ├── workspace::entities (Binary, Project para relações FK)
|
||
│ ├── analysis::entities (StaticFunction, StaticAnalysis, etc.)
|
||
│ ├── analysis::services (AnalysisService para buscar funções/xrefs)
|
||
│ └── common (exceções, configurações compartilhadas)
|
||
```
|
||
|
||
### Dependências do módulo `job/` (atualizado)
|
||
|
||
```
|
||
job/
|
||
├── allowedDependencies (novos):
|
||
│ ├── ...
|
||
│ ├── ai::services (EmbeddingService para o handler de embeddings)
|
||
│ ├── ai::dto (tipos de dados do módulo AI)
|
||
│ └── ai::events (eventos publicados pelo módulo AI)
|
||
```
|
||
|
||
### Dependências do módulo `analysis/` (atualizado)
|
||
|
||
```
|
||
analysis/
|
||
├── allowedDependencies (novos):
|
||
│ ├── ...
|
||
│ └── ai::events (para publicar StaticAnalysisCompletedEvent)
|
||
└── NOTA: o evento será definido no analysis/event para evitar dependência circular
|
||
```
|
||
|
||
## Fluxo de Eventos Inter-Módulos
|
||
|
||
```
|
||
workspace analysis job ai
|
||
│ │ │ │
|
||
│ BinaryUp- │ │ │
|
||
│ loadedEvent───┤ │ │
|
||
│ │ │ │
|
||
│ │ StaticAnalysisRequestedEvent │
|
||
│ ├───────────────►│ │
|
||
│ │ │ STATIC_ │
|
||
│ │ │ ANALYSIS │
|
||
│ │ │ (handler) │
|
||
│ │ │ │
|
||
│ │◄───────────────┤ saveResult() │
|
||
│ │ │ │
|
||
│ │ StaticAnalysisCompletedEvent │
|
||
│ ├────────────────────────────────►│
|
||
│ │ │ │
|
||
│ │ │ GENERATE_ │
|
||
│ │ │ EMBEDDINGS │
|
||
│ │ │ (handler)────►│
|
||
│ │ │ │
|
||
│ │ │◄───────────────│
|
||
│ │ │ indexBinary() │
|
||
```
|
||
|
||
> **Nota**: O `StaticAnalysisCompletedEvent` será definido em `analysis.event` e o subscriber residirá em `job.service` (padrão consistente com `StaticAnalysisRequestedEvent` + `StaticAnalysisEventSubscriber`).
|
||
|
||
## Configuração Multi-Provider
|
||
|
||
O módulo suporta dois providers de chat e um provider de embeddings, todos abstraídos pelo Spring AI:
|
||
|
||
```
|
||
┌─────────────────────┐
|
||
│ ChatClientProvider │ (interface própria)
|
||
│ │
|
||
│ getClient(model)─────┤
|
||
└──────────┬──────────┘
|
||
│
|
||
┌───────────────┼───────────────┐
|
||
│ │
|
||
┌──────────┴──────────┐ ┌──────────────┴──────────┐
|
||
│ DeepSeekChatClient │ │ OllamaChatClient │
|
||
│ (OpenAiChatModel) │ │ (OllamaChatModel) │
|
||
│ │ │ │
|
||
│ base-url: │ │ base-url: │
|
||
│ api.deepseek.com │ │ localhost:11434 │
|
||
│ model: deepseek-chat│ │ model: gemma4:12b │
|
||
└─────────────────────┘ └─────────────────────────┘
|
||
|
||
┌─────────────────────┐
|
||
│ EmbeddingModel │ (Spring AI interface)
|
||
│ │
|
||
│ embed(docs) │
|
||
└──────────┬──────────┘
|
||
│
|
||
┌──────────┴──────────┐
|
||
│ OllamaEmbeddingModel │
|
||
│ │
|
||
│ base-url: │
|
||
│ localhost:11434 │
|
||
│ model: qwen3-embedding│
|
||
└──────────────────────┘
|
||
```
|
||
|
||
## Considerações de Performance
|
||
|
||
| Aspecto | Estratégia |
|
||
|---|---|
|
||
| **Embedding em batch** | Funções enviadas em lotes de 20 (`app.ai.embedding.batch-size`) |
|
||
| **Busca vetorial** | Índice HNSW com `vector_cosine_ops` — sub-100ms para milhares de vetores |
|
||
| **Contexto da LLM** | Truncagem de chunks + histórico para caber em `max-context-tokens` (8000 default) |
|
||
| **Streaming** | Tokens fluem via SSE assim que gerados, sem buffer completo |
|
||
| **Conexões Ollama** | Pool de conexões HTTP gerenciado pelo Spring AI |
|
||
| **Virtual Threads** | Habilitados (`spring.threads.virtual.enabled: true`) — I/O de rede não bloqueia |
|
||
|
||
## Segurança
|
||
|
||
- API keys (DeepSeek) armazenadas exclusivamente em `.env`, nunca no repositório
|
||
- `.env` listado no `.gitignore`
|
||
- Nenhuma chave ou segredo em plaintext no `application.yml`
|
||
- Variáveis de ambiente referenciadas como `${DEEPSEEK_API_KEY}` com fallback vazio
|