# 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> sendMessage( @PathVariable UUID sessionId, @Valid @RequestBody ChatRequest request) { return chatService.chat(sessionId, request.content()) .map(token -> ServerSentEvent.builder() .data("{\"type\":\"chunk\",\"content\":\"" + escapeJson(token) + "\"}") .build()) .concatWith(chatService.finalizeMessage(sessionId) .map(msg -> ServerSentEvent.builder() .data("{\"type\":\"done\",\"messageId\":\"" + msg.id() + "\"," + "\"sessionId\":\"" + sessionId + "\"," + "\"tokenCount\":" + msg.tokenCount() + "}") .build())) .onErrorResume(e -> Flux.just( ServerSentEvent.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)