backend/docs/ai/api-design.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

405 lines
9.6 KiB
Markdown

# API Design — Módulo AI
## Endpoints
Todos os endpoints são prefixados com o contexto da aplicação e seguem as convenções REST do projeto (OpenAPI 3.1 via SpringDoc).
| Método | Path | Descrição | Tag |
|---|---|---|---|
| `POST` | `/binaries/{binaryId}/chat/sessions` | Criar nova sessão de chat | Chat |
| `GET` | `/binaries/{binaryId}/chat/sessions` | Listar sessões do binário | Chat |
| `GET` | `/chat/sessions/{sessionId}` | Detalhes da sessão | Chat |
| `DELETE` | `/chat/sessions/{sessionId}` | Deletar sessão | Chat |
| `GET` | `/chat/sessions/{sessionId}/messages` | Histórico de mensagens | Chat |
| `POST` | `/chat/sessions/{sessionId}/messages` | Enviar mensagem (SSE stream) | Chat |
---
## 1. Criar Sessão
### `POST /binaries/{binaryId}/chat/sessions`
Cria uma nova sessão de chat vinculada a um binário.
**Query Parameters**:
| Parâmetro | Tipo | Obrigatório | Default | Descrição |
|---|---|---|---|---|
| `model` | String | Não | `app.ai.default-chat-provider` | Modelo LLM: `gemma4:12b`, `gemma4:e4b`, `deepseek-chat` |
**Request Body**: Vazio
**Response** (`201 Created`):
```json
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"binaryId": "660e8400-e29b-41d4-a716-446655440001",
"projectId": "770e8400-e29b-41d4-a716-446655440002",
"title": "malware.exe — 2026-06-08T15:30:00Z",
"chatModel": "gemma4:12b",
"messageCount": 0,
"createdAt": "2026-06-08T15:30:00Z",
"updatedAt": "2026-06-08T15:30:00Z"
}
```
**Erros**:
| Código | Condição |
|---|---|
| `404` | Binário não encontrado |
| `400` | Modelo não suportado (ex: `?model=invalid-model`) |
**Exemplo curl**:
```bash
curl -X POST "http://localhost:8080/binaries/{binaryId}/chat/sessions?model=deepseek-chat"
```
---
## 2. Listar Sessões
### `GET /binaries/{binaryId}/chat/sessions`
Lista todas as sessões de chat de um binário, ordenadas por data de atualização (mais recente primeiro).
**Response** (`200 OK`):
```json
[
{
"id": "550e8400-...",
"binaryId": "660e8400-...",
"projectId": "770e8400-...",
"title": "Analisando funções de rede",
"chatModel": "deepseek-chat",
"messageCount": 12,
"createdAt": "2026-06-08T15:30:00Z",
"updatedAt": "2026-06-08T16:45:00Z"
},
{
"id": "550e8401-...",
"binaryId": "660e8400-...",
"projectId": "770e8400-...",
"title": "Investigando crypto",
"chatModel": "gemma4:12b",
"messageCount": 5,
"createdAt": "2026-06-08T14:00:00Z",
"updatedAt": "2026-06-08T14:20:00Z"
}
]
```
**Exemplo curl**:
```bash
curl "http://localhost:8080/binaries/{binaryId}/chat/sessions"
```
---
## 3. Detalhes da Sessão
### `GET /chat/sessions/{sessionId}`
Retorna os detalhes de uma sessão específica.
**Response** (`200 OK`):
```json
{
"id": "550e8400-...",
"binaryId": "660e8400-...",
"projectId": "770e8400-...",
"title": "Analisando funções de rede",
"chatModel": "deepseek-chat",
"messageCount": 12,
"createdAt": "2026-06-08T15:30:00Z",
"updatedAt": "2026-06-08T16:45:00Z"
}
```
**Erros**: `404` se sessão não encontrada.
---
## 4. Deletar Sessão
### `DELETE /chat/sessions/{sessionId}`
Deleta a sessão e todas as suas mensagens (cascade).
**Response**: `204 No Content`
**Exemplo curl**:
```bash
curl -X DELETE "http://localhost:8080/chat/sessions/{sessionId}"
```
---
## 5. Histórico de Mensagens
### `GET /chat/sessions/{sessionId}/messages`
Retorna o histórico de mensagens da sessão, ordenado cronologicamente (mais antigo primeiro).
**Response** (`200 OK`):
```json
[
{
"id": "aa0e8400-...",
"sessionId": "550e8400-...",
"role": "USER",
"content": "Quais funções lidam com rede?",
"tokenCount": 12,
"createdAt": "2026-06-08T15:30:30Z"
},
{
"id": "bb0e8400-...",
"sessionId": "550e8400-...",
"role": "ASSISTANT",
"content": "O binário importa funções de rede da WS2_32.dll. As funções sub_401200 e sub_401300 utilizam send() e recv()...",
"tokenCount": 85,
"createdAt": "2026-06-08T15:30:35Z"
}
]
```
**Erros**: `404` se sessão não encontrada.
---
## 6. Enviar Mensagem (SSE Streaming)
### `POST /chat/sessions/{sessionId}/messages`
Envia uma mensagem do usuário e retorna a resposta da LLM em streaming via Server-Sent Events (SSE).
**Headers**:
```
Content-Type: application/json
Accept: text/event-stream
```
**Request Body**:
```json
{
"content": "Quais funções nesse binário lidam com criptografia?"
}
```
**Response**: `200 OK` com `Content-Type: text/event-stream`
### Protocolo SSE
O stream consiste em uma sequência de eventos `data:`:
#### Evento: `chunk`
Fragmento de texto da resposta, enviado assim que gerado pela LLM:
```
data: {"type":"chunk","content":"O binário"}
data: {"type":"chunk","content":" utiliza "}
data: {"type":"chunk","content":"as seguintes"}
data: {"type":"chunk","content":" funções"}
data: {"type":"chunk","content":" relacionadas"}
data: {"type":"chunk","content":" a criptografia:"}
data: {"type":"chunk","content":"\n\n1. **sub_405000**"}
data: {"type":"chunk","content":" em 0x405000"}
...
```
#### Evento: `done`
Indica o fim do stream, com metadados da mensagem:
```
data: {"type":"done","messageId":"cc0e8400-e29b-...","sessionId":"550e8400-...","tokenCount":142}
```
#### Evento: `error`
Indica um erro durante o processamento:
```
data: {"type":"error","code":"EMBEDDING_FAILED","message":"Failed to generate embedding: Ollama connection refused"}
```
Após um evento `error`, a conexão é fechada. Nenhum evento `done` é enviado.
### Tipos de Evento SSE
| `type` | Descrição | Campos |
|---|---|---|
| `chunk` | Fragmento de texto da resposta | `content: String` |
| `done` | Stream concluído com sucesso | `messageId: UUID`, `sessionId: UUID`, `tokenCount: Integer` |
| `error` | Erro durante o processamento | `code: String`, `message: String` |
### Fluxo Interno Durante o SSE
```
1. Salva UserMessage (role=USER) no banco
2. Embedda a query do usuário (qwen3-embedding)
3. Busca similaridade no pgvector (top-K chunks)
4. (Opcional) Expande call graph
5. Constrói system prompt com contexto RAG
6. Monta lista de mensagens: [SystemPrompt, history, UserMessage]
7. Chama StreamingChatClient da LLM
8. Para cada token gerado: envia SSE "chunk"
9. Ao finalizar: salva AssistantMessage (role=ASSISTANT) no banco
10. Envia SSE "done" com messageId + tokenCount
11. Se erro em qualquer passo: envia SSE "error" e fecha conexão
```
### Exemplo curl (com streaming)
```bash
curl -X POST "http://localhost:8080/chat/sessions/{sessionId}/messages" \
-H "Content-Type: application/json" \
-H "Accept: text/event-stream" \
-d '{"content": "Explique a função sub_401000"}'
```
### Implementação no Controller
```java
@PostMapping(value = "/chat/sessions/{sessionId}/messages",
produces = MediaType.TEXT_EVENT_STREAM_VALUE)
@Operation(summary = "Send a message and stream the AI response",
tags = {"Chat"})
public Flux<ServerSentEvent<String>> sendMessage(
@PathVariable UUID sessionId,
@Valid @RequestBody ChatRequest request) {
return chatService.chat(sessionId, request.content())
.map(token -> ServerSentEvent.<String>builder()
.data("{\"type\":\"chunk\",\"content\":\"" + escapeJson(token) + "\"}")
.build())
.concatWith(chatService.finalizeMessage(sessionId)
.map(msg -> ServerSentEvent.<String>builder()
.data("{\"type\":\"done\",\"messageId\":\"" + msg.id() + "\","
+ "\"sessionId\":\"" + sessionId + "\","
+ "\"tokenCount\":" + msg.tokenCount() + "}")
.build()))
.onErrorResume(e -> Flux.just(
ServerSentEvent.<String>builder()
.data("{\"type\":\"error\",\"code\":\"INTERNAL\","
+ "\"message\":\"" + escapeJson(e.getMessage()) + "\"}")
.build()));
}
```
---
## DTOs
### `ChatRequest`
```java
public record ChatRequest(
@NotBlank @Size(min = 1, max = 4000)
String content
) {}
```
### `ChatResponse` (evento `done`)
```java
public record ChatResponse(
UUID messageId,
UUID sessionId,
int tokenCount
) {}
```
### `SessionResponse`
```java
public record SessionResponse(
UUID id,
UUID binaryId,
UUID projectId,
String title,
String chatModel,
int messageCount,
Instant createdAt,
Instant updatedAt
) {}
```
### `MessageResponse`
```java
public record MessageResponse(
UUID id,
UUID sessionId,
String role,
String content,
Integer tokenCount,
Instant createdAt
) {}
```
### `StreamChunk` (evento SSE `chunk`)
```java
public record StreamChunk(
String type, // "chunk", "done", "error"
String content // null para "done" e "error"
) {}
```
---
## Tratamento de Erros
| Código HTTP | `type` SSE | Condição |
|---|---|---|
| `400` | — | Request body inválido (content vazio, >4000 chars) |
| `404` | — | Sessão não encontrada |
| `404` | — | Binário não encontrado |
| `503` | `error` | Ollama indisponível (embeddings ou chat) |
| `503` | `error` | DeepSeek API indisponível |
| `500` | `error` | Erro interno (pgvector, RabbitMQ, etc.) |
| `402` | `error` | DeepSeek API: saldo insuficiente / quota excedida |
### Resposta de erro padrão (não-SSE)
```json
{
"status": 404,
"message": "Chat session with id '550e8400-...' not found",
"timestamp": "2026-06-08T15:30:00Z"
}
```
---
## OpenAPI / Swagger
Todos os endpoints são documentados com `@Operation` e aparecem no Swagger UI (`/swagger-ui.html`).
**Tag**: `Chat`
**Schemas**:
- `ChatRequest`
- `SessionResponse`
- `MessageResponse`
- `StreamChunk` (não documentado como schema, pois é SSE)