Una arquitectura sencilla para integrar modelos de lenguaje con aplicaciones empresariales sin convertir al LLM en el dueño del estado de la aplicación.
Cuando comenzamos a integrar modelos de lenguaje en una aplicación empresarial, es fácil terminar colocando demasiada lógica alrededor del LLM. El modelo interpreta preguntas, decide qué función ejecutar, consulta información, mantiene el contexto y finalmente genera una respuesta.
Sin embargo, no es necesario que todo eso viva dentro del servidor de IA.
En este artículo voy a plantear una arquitectura diferente: el modelo de lenguaje permanece stateless, el servidor de IA también puede ser stateless y la aplicación empresarial mantiene la conversación, el estado y la memoria.
El resultado es una arquitectura especialmente interesante para un ERP, donde la lógica de negocio y los datos deben permanecer bajo el control de la aplicación.
1. El LLM es stateless
Primero hay que entender algo fundamental sobre los modelos de lenguaje.
Un LLM no tiene una memoria persistente de nuestras conversaciones. Cada llamada recibe una entrada y genera una salida.
Contexto + mensaje
↓
LLM
↓
respuesta
Si enviamos:
Usuario: Hola
y posteriormente hacemos otra llamada enviando solamente:
Usuario: ¿Qué te dije antes?
el modelo no tiene forma de recuperar automáticamente el mensaje anterior.
Para que pueda responder necesitamos enviarle nuevamente el contexto:
Historial:
Usuario: Hola
Usuario: ¿Qué te dije antes?
2. La memoria pertenece a la aplicación
Una aplicación puede aparentar tener memoria porque guarda información externamente y la incorpora a las siguientes llamadas al LLM.
Esa memoria puede estar formada por:
- historial de mensajes;
- resúmenes de conversaciones;
- datos importantes;
- estado de la conversación;
- documentos recuperados mediante RAG;
- resultados obtenidos anteriormente.
Por lo tanto podemos separar claramente dos responsabilidades:
| Componente | Responsabilidad |
|---|---|
| LLM | Interpretar contexto y generar resultados. |
| Servidor de IA | Procesar las solicitudes contra los modelos. |
| ERP | Mantener estado, memoria y lógica de negocio. |
3. ¿Por qué separar el ERP del servidor de IA?
Supongamos que tenemos un ERP desarrollado en Java y queremos utilizar un servidor independiente para acceder a modelos como Ollama, Gemini u otros proveedores.
En lugar de introducir toda la lógica de IA dentro del ERP, podemos crear un servicio independiente.
┌──────────────────────┐
│ ERP │
│ │
│ lógica de negocio │
│ funciones │
│ memoria │
│ conversaciones │
│ estado │
│ SQLite / BD │
└──────────┬───────────┘
│
│ HTTP API
▼
┌──────────────────────┐
│ IA SERVER │
│ │
│ LLM 1 │
│ LLM 2 │
│ RAG │
│ prompts │
│ │
│ STATELESS │
└──────────────────────┘
El servidor de IA no necesita saber quién es el usuario ni qué ocurrió en la conversación anterior.
El ERP le proporciona todo el contexto necesario para cada llamada.
4. Una arquitectura de dos llamadas
Una de las decisiones más interesantes de esta arquitectura es limitar cada interacción a un máximo de dos llamadas al LLM.
La primera llamada interpreta la solicitud y produce un plan estructurado.
La segunda llamada utiliza los resultados obtenidos por el ERP y genera la respuesta final.
Usuario
│
▼
ERP
│
│ 1. Pregunta + contexto
▼
IA SERVER
│
▼
LLM 1
│
│ análisis estructurado
▼
ERP
│
│ ejecuta funciones
│ consulta BD
│ obtiene resultados
▼
IA SERVER
│
▼
LLM 2
│
│ respuesta + memoria sugerida
▼
ERP
│
▼
Usuario
La ventaja de este modelo es que el flujo es completamente predecible.
No estamos construyendo un agente que puede decidir realizar diez llamadas consecutivas a distintas herramientas. La aplicación controla cuándo se llama al modelo y cuándo se ejecuta código empresarial.
5. Primera llamada: interpretar la pregunta
Supongamos que el usuario escribe:
¿Cuánto vendimos en 2025?
El ERP envía la pregunta al servidor de IA junto con el contexto de la conversación.
El primer LLM puede devolver una estructura como:
{
"operaciones": [
{
"funcion": "ventas_anuales",
"parametros": {
"anio": 2025
}
}
],
"pregunta": "¿Cuánto vendimos en 2025?"
}
Este JSON no es una respuesta para el usuario. Es un plan de ejecución.
El ERP toma ese plan y decide qué función Java debe ejecutar.
6. Una pregunta puede necesitar varias operaciones
Una ventaja importante de utilizar una lista de operaciones es que una sola pregunta puede requerir varias consultas.
Por ejemplo:
¿Cuánto vendimos en 2025 y cuál fue nuestro mejor cliente?
El primer LLM podría generar:
{
"operaciones": [
{
"funcion": "ventas_anuales",
"parametros": {
"anio": 2025
}
},
{
"funcion": "mejor_cliente",
"parametros": {
"anio": 2025
}
}
],
"pregunta": "¿Cuánto vendimos en 2025 y cuál fue nuestro mejor cliente?"
}
El ERP ejecuta ambas operaciones sin realizar otra llamada al LLM.
LLM 1
│
├── ventas_anuales(2025) ──→ BD
│
└── mejor_cliente(2025) ──→ BD
│
▼
resultados
│
▼
LLM 2
De esta manera podemos realizar varias operaciones de negocio manteniendo solamente dos llamadas al modelo.
7. El ERP es quien ejecuta las funciones
Esta separación es importante.
El servidor de IA no debería necesitar acceso directo a la base de datos del ERP ni conocer sus servicios internos.
El ERP recibe:
{
"funcion": "ventas_anuales",
"parametros": {
"anio": 2025
}
}
y utiliza su propia lógica:
Resultado resultado =
servicio.obtenerVentasAnuales(2025);
Luego el resultado vuelve al servidor de IA para la segunda llamada.
8. Segunda llamada: generar la respuesta
Una vez que el ERP ejecutó todas las operaciones, construye el contexto para el segundo LLM.
Por ejemplo:
Pregunta:
¿Cuánto vendimos en 2025 y cuál fue nuestro mejor cliente?
Resultados:
Ventas 2025:
₲850.000.000
Mejor cliente:
Empresa ABC
Compras del cliente:
₲120.000.000
El segundo LLM utiliza esa información para generar una respuesta comprensible:
En 2025 las ventas fueron de ₲850.000.000.
El mejor cliente fue Empresa ABC,
con compras por ₲120.000.000.
9. Convertir la segunda respuesta en JSON
Si además queremos que el segundo LLM ayude a mantener la memoria de la conversación, podemos pedirle una respuesta estructurada.
Por ejemplo:
{
"respuesta": "En 2025 las ventas fueron de ₲850.000.000. El mejor cliente fue Empresa ABC, con compras por ₲120.000.000.",
"memoria": [
{
"contenido": "La conversación está relacionada con las ventas de 2025.",
"importancia": 0.8
},
{
"contenido": "El mejor cliente de 2025 fue Empresa ABC.",
"importancia": 0.9
}
],
"estado": {
"tema": "ventas",
"anio": 2025,
"cliente_actual": "Empresa ABC"
}
}
El ERP no muestra todo ese JSON al usuario.
Extrae solamente:
resultado.respuesta
y muestra esa parte en la interfaz.
Al mismo tiempo puede guardar:
resultado.memoria
resultado.estado
en su propia base de datos.
10. Memoria conversacional sin enviar todo el historial
Guardar toda la conversación no significa que tengamos que enviarla completa al LLM en cada petición.
Podemos mantener dos niveles de contexto.
CONVERSACIÓN
Memoria relevante
────────────────────────────
• Estamos hablando de ventas de 2025.
• El mejor cliente es Empresa ABC.
Último intercambio
────────────────────────────
Usuario:
¿Cuál fue mi mejor cliente?
Asistente:
El mejor cliente fue Empresa ABC.
Nueva pregunta
────────────────────────────
Usuario:
¿Y cuánto compró?
El modelo recibe únicamente la información necesaria para entender la nueva pregunta.
Esto reduce el tamaño del contexto y evita enviar cientos de mensajes antiguos.
11. Estado de conversación
Además de memoria textual, puede ser útil mantener un estado estructurado.
{
"tema": "ventas",
"anio": 2025,
"cliente_actual": "Empresa ABC"
}
Si posteriormente el usuario pregunta:
¿Y cuánto compró?
el ERP puede proporcionar al LLM:
Tema actual: ventas
Año: 2025
Cliente actual: Empresa ABC
Esto permite resolver referencias como:
- este año;
- el año pasado;
- ese cliente;
- ese producto;
- la anterior;
- el mismo período.
12. SQLite puede ser suficiente
Para almacenar el estado de una conversación no necesitamos comenzar necesariamente con una base de datos vectorial.
Una implementación inicial puede utilizar SQLite, especialmente cuando la memoria que necesitamos conservar es principalmente conversacional y estructurada.
IA del ERP
│
├── conversations
│ ├── id
│ ├── usuario_id
│ └── fecha
│
├── messages
│ ├── id
│ ├── conversation_id
│ ├── role
│ ├── content
│ └── fecha
│
└── conversation_state
├── conversation_id
└── state_json
La tabla messages puede conservar el historial de la conversación, mientras que conversation_state puede almacenar la información que resulta especialmente importante para continuarla.
Por ejemplo, el estado podría contener:
{
"tema": "ventas",
"anio": 2025,
"cliente_actual": "Empresa ABC",
"memoria_relevante": [
"El mejor cliente de 2025 fue Empresa ABC."
]
}
De esta manera, el ERP no necesita enviar todo el historial al LLM en cada solicitud. Puede seleccionar los últimos mensajes y combinarlos con el estado relevante de la conversación.
Más adelante, si la aplicación necesita buscar información semánticamente entre grandes cantidades de documentos o conversaciones, se puede incorporar RAG, embeddings y una base de datos vectorial.
13. El servidor de IA puede ser completamente stateless
Si el ERP mantiene el estado, cada petición al servidor de IA contiene todo lo necesario para realizar su trabajo.
Por ejemplo:
POST /ai/analyze
{
"message": "¿Y cuánto compró?",
"context": {
"memoria": [
"Estamos hablando de ventas de 2025.",
"El mejor cliente es Empresa ABC."
],
"estado": {
"tema": "ventas",
"anio": 2025,
"cliente_actual": "Empresa ABC"
},
"ultimo_intercambio": {
"usuario": "¿Cuál fue mi mejor cliente?",
"asistente": "El mejor cliente fue Empresa ABC."
}
}
}
El servidor procesa la solicitud y devuelve el resultado. No necesita conservar la conversación.
14. Escalabilidad
Esta característica facilita mucho la escalabilidad.
┌────────────────┐
│ ERP │
│ memoria/BD │
└───────┬────────┘
│
┌──────────┼──────────┐
│ │ │
▼ ▼ ▼
IA Server IA Server IA Server
1 2 3
│ │ │
└──────────┼──────────┘
▼
LLM
Cualquier instancia del servidor de IA puede atender cualquier solicitud porque ninguna depende de una sesión almacenada localmente.
15. ¿Dónde entra LangChain4j?
Después de analizar esta arquitectura aparece una pregunta natural: ¿realmente necesitamos LangChain4j?
La respuesta es: no necesariamente.
LangChain4j puede aportar abstracciones para:
- Tool Calling;
- memoria;
- RAG;
- embeddings;
- structured output;
- integración con diferentes modelos.
Pero una biblioteca no debería incorporarse solamente porque proporciona funcionalidades que también podemos implementar directamente.
En esta arquitectura ya tenemos una decisión importante:
LLM 1
↓
plan estructurado
↓
ERP ejecuta operaciones
↓
LLM 2
↓
respuesta estructurada
El flujo es sencillo, controlado y tiene un número máximo de llamadas al modelo.
16. La diferencia con Tool Calling
Con Tool Calling, el modelo puede recibir herramientas como:
ventasAnuales(anio)
mejorCliente(anio)
listarVentas(anio)
y seleccionar directamente una de ellas.
En la arquitectura propuesta, en cambio, el primer LLM produce un plan:
{
"operaciones": [
{
"funcion": "ventas_anuales",
"parametros": {
"anio": 2025
}
}
]
}
El ERP interpreta ese plan y ejecuta la operación.
La segunda opción proporciona un nivel de control muy claro sobre la ejecución.
17. Una arquitectura sencilla y controlable
Finalmente, podemos resumir todo el diseño de esta manera:
USUARIO
│
▼
┌──────────────┐
│ ERP │
│ │
│ memoria │
│ estado │
│ historial │
└──────┬───────┘
│
pregunta + contexto
│
▼
┌──────────────┐
│ IA SERVER │
│ │
│ LLM 1 │
└──────┬───────┘
│
plan estructurado
│
▼
┌──────────────┐
│ ERP │
│ │
│ ejecuta │
│ operaciones │
└──────┬───────┘
│
resultados + contexto
│
▼
┌──────────────┐
│ IA SERVER │
│ │
│ LLM 2 │
└──────┬───────┘
│
respuesta + memoria
│
▼
┌──────────────┐
│ ERP │
│ │
│ guarda │
│ memoria │
│ muestra │
│ respuesta │
└──────────────┘
Conclusión
Una arquitectura de IA para un ERP no tiene por qué convertirse necesariamente en un sistema de agentes complejo.
Un modelo de lenguaje es esencialmente stateless: recibe contexto y produce una salida. La memoria puede mantenerse perfectamente en la aplicación empresarial.
Esto permite separar responsabilidades de una forma muy clara:
- El LLM interpreta y genera lenguaje.
- El IA Server proporciona los modelos y servicios de IA.
- El ERP mantiene la conversación y el estado.
- El ERP ejecuta las funciones de negocio.
- La base de datos del ERP conserva la memoria.
Con dos llamadas al LLM se puede construir un flujo bastante potente: una primera llamada para interpretar y planificar las operaciones y una segunda para utilizar los resultados y generar la respuesta final.
Además, al permitir que la primera llamada produzca múltiples operaciones, una pregunta compleja puede resolverse mediante varias consultas al sistema sin aumentar el número de llamadas al modelo.
El resultado es una arquitectura sencilla, controlable y escalable, donde la IA no se convierte en el dueño de los datos ni del estado de la aplicación.
Comentarios
Publicar un comentario