API de análisis de documentos con Spring Boot y Spring AI. Análisis estructurado de CVs a partir de texto plano o PDF, con extracción de datos vía LLM. Memoria de chat persistida en Redis.
Este proyecto es el Proyecto 1 del AI Engineer Roadmap · Java + Spring AI, un roadmap de formación como AI Engineer en Java. El repo hub contiene el roadmap completo, los cuatro proyectos entregables y la bitácora de progreso.
- Java 21
- Spring Boot 4.1.0
- Spring AI 2.0.0
- Anthropic Claude Sonnet 4.5 (vía
spring-ai-starter-model-anthropic) - Redis 7.4 (vía
spring-boot-starter-data-rediscon Lettuce 7.5.2) para persistencia de memoria conversacional
Conversación con memoria persistida en Redis. MessageChatMemoryAdvisor con MessageWindowChatMemory de ventana 10 mensajes, respaldado por RedisChatMemoryRepository custom. La memoria sobrevive a reinicios de la JVM y se segmenta por conversationId: cada valor distinto abre un hilo de conversación independiente. TTL de 24h por conversación.
Query params: message, conversationId.
curl -G "http://localhost:8080/chat" \
--data-urlencode "message=¿Qué es Spring AI?" \
--data-urlencode "conversationId=sesion-1"Observabilidad: LlmLoggingAdvisor mide latencia, tokens y coste estimado por llamada.
Extracción estructurada de datos de un CV en texto plano. Prompt externalizado en src/main/resources/prompts/analyze-cv.st. La conversión del output del modelo a CvSummary usa BeanOutputConverter.
Body JSON:
curl -X POST "http://localhost:8080/analyze" \
-H "Content-Type: application/json" \
-d '{"cv": "texto del CV aquí"}'Igual que /analyze pero recibe el PDF directamente como Media de Spring AI, sin extracción de texto intermedia: el PDF nativo se envía a Claude. Prompt propio en analyze-cv-pdf.st.
Body multipart/form-data, parte file:
curl -X POST "http://localhost:8080/analyze/pdf" \
-F "file=@/ruta/al/cv.pdf"Común a /analyze y /analyze/pdf.
- Campos raíz:
fullName,yearsOfExperience,topSkills,seniorityLevel. languages: lista deLanguage(name, level).education: lista deEducation(degree, institution, year).workExperience: lista deWorkExperience(role, company, startYear, endYear, responsibilities).
RedisChatMemoryRepository (package chat/) implementa ChatMemoryRepository de Spring AI 2.0 sobre StringRedisTemplate. La política de retención (ventana de N mensajes) vive en MessageWindowChatMemory; el repositorio se limita a persistencia CRUD.
- Modelo de almacenamiento: cada conversación es una Redis List con clave
chat:memory:{conversationId}. Cada elemento es un JSON string. - Serialización: DTO propio
MessageRecord(String messageType, String text)para desacoplar el formato en disco de la jerarquía internaMessagede Spring AI. Al leer, unswitchsobremessageTypereconstruyeUserMessage,AssistantMessageoSystemMessage. - Idempotencia de
saveAll:DEL+RPUSH+EXPIRE. Reemplaza toda la lista en cada llamada (comportamiento contractual deChatMemoryRepository). - TTL: 24 horas por conversación, refrescado en cada
saveAll. findConversationIds: implementado conSCAN(noKEYS) para no bloquear Redis en producción.
LlmLoggingAdvisor (package observability/), advisor custom de Spring AI (CallAdvisor). Mide latencia, extrae tokens de la respuesta y calcula coste estimado por llamada. Los precios de input/output por millón de tokens están externalizados en application.properties (llm.pricing.input-per-mtok, llm.pricing.output-per-mtok), no hardcodeados en el advisor.
Estado actual: completamente operativo en /chat, /analyze y /analyze/pdf. Configurado con getOrder() = 100 para ejecutarse después de MessageChatMemoryAdvisor, evitando duplicación de mensajes en Redis y garantizando observabilidad en todo el flujo conversacional.
Salida de logs:
[main] INFO LlmLoggingAdvisor - llm call completed latency_ms=450 tokens_in=25 tokens_out=187 cost_usd=0.000128
GlobalExceptionHandler (@RestControllerAdvice) centraliza los errores en ProblemDetail (RFC 7807):
MultipartException→ 400 Bad Request.JacksonException(fallo al parsear el output del LLM aCvSummary) → 502 Bad Gateway. Se loguea la excepción original y el path de la petición.
| Propiedad | Valor |
|---|---|
spring.ai.anthropic.chat.model |
claude-sonnet-4-5 |
spring.ai.anthropic.chat.temperature |
0.3 |
spring.ai.anthropic.chat.max-tokens |
1024 |
spring.servlet.multipart.max-file-size |
10MB |
spring.ai.anthropic.api-key |
${ANTHROPIC_API_KEY} (variable de entorno, nunca en el repo) |
spring.data.redis.host |
localhost (en despliegue containerizado se sobrescribe a redis) |
spring.data.redis.port |
6379 |
llm.pricing.input-per-mtok |
0.003 (USD por millón de tokens de entrada) |
llm.pricing.output-per-mtok |
0.015 (USD por millón de tokens de salida) |
-
Clona el repositorio:
git clone https://github.com/Toleflaco/document-analyzer-ai.git cd document-analyzer-ai -
Consigue una API key de Anthropic: regístrate en console.anthropic.com, genera una clave y guárdala en un lugar seguro (no en el repo).
-
Levanta la stack (app + Redis):
export ANTHROPIC_API_KEY=<tu-clave-real> docker compose up --build
El servidor levanta en http://localhost:8080. Redis escucha en localhost:6379.
-
Clona el repositorio:
git clone https://github.com/Toleflaco/document-analyzer-ai.git cd document-analyzer-ai -
Consigue una API key de Anthropic (igual que arriba).
-
Levanta SOLO Redis:
docker run -d -p 6379:6379 redis:7.4-alpine
-
Arranca la aplicación desde el IDE o terminal:
export ANTHROPIC_API_KEY=<tu-clave-real> ./mvnw spring-boot:run
El servidor levanta en http://localhost:8080.
Una vez arrancado:
# Chat conversacional (memoria persistida en Redis)
curl -G "http://localhost:8080/chat" \
--data-urlencode "message=¿Qué es Spring AI?" \
--data-urlencode "conversationId=sesion-1"
# Analizar CV en texto plano
curl -X POST "http://localhost:8080/analyze" \
-H "Content-Type: application/json" \
-d '{"cv":"Manuel Toledano\nDesarrollador Java\n18 años experiencia..."}'
# Analizar CV en PDF
curl -X POST "http://localhost:8080/analyze/pdf" \
-F "file=@cv.pdf"Proyecto vehículo cerrado de la Fase 1 del AI Engineer Roadmap · Java + Spring AI. Las siguientes fases del roadmap se cubren en proyectos independientes:
- Fase 2: erp-mcp-server — MCP server con Spring AI
- Fase 3: Knowledge base empresarial con RAG (planificado)
- Fase 4–5: Observabilidad y despliegue en AWS (planificado)
Última actualización: 2026-09-20