> ## Documentation Index
> Fetch the complete documentation index at: https://docs.jelou.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Agente de IA externo

> Conecta un agente alojado fuera de Jelou y aprende exactamente qué recibe y qué debe responder

El nodo **Agente de IA externo** delega la conversación a un agente que tú alojas fuera de Jelou. Con el proveedor **HTTP (contrato de Jelou)** (`http_sync`), Jelou hace un `POST` a la URL de tu agente en cada turno y espera una respuesta JSON con un formato exacto. Esta guía describe ese formato, con ejemplos que puedes copiar.

<Note>
  Este formato también aplica a un agente HTTP desplegado en Amazon Bedrock AgentCore: lo que tu handler devuelve es lo que Jelou valida.
</Note>

## Qué recibe tu agente

Jelou envía un `POST` con `content-type: application/json`, las cabeceras de la credencial que configuraste y, si lo activaste, la firma de la solicitud. El cuerpo es este:

```json theme={null}
{
  "turnId": "trn_01J8A7K2M4XQ",
  "inputId": "inp_9f3c2a71",
  "conversation": { "key": "wa:593999999999", "channel": "whatsapp", "locale": "es-EC" },
  "user": { "ref": "usr_8d41c0aa" },
  "input": { "parts": [{ "type": "text", "text": "Hola, quiero saber mi saldo" }] },
  "context": { "variables": { "segmento": "premium" } },
  "deadlineMs": 25000,
  "trace": { "executionId": "exec_5b2e", "workflowId": "64083", "nodeId": "n_agent" },
  "interaction": "single_task"
}
```

| Campo | Descripción |
| - | - |
| `turnId` | Identificador del turno, `trn_` seguido de letras o números. |
| `inputId` | Identificador del mensaje del usuario (1 a 128 caracteres). |
| `conversation` | `key`, `channel` (`whatsapp`, `facebook`, `instagram`, `web`, `email`, `sms`, `other`) y `locale` opcional. |
| `user` | `ref` seudónimo (`usr_...`). `phone`, `name`, `email` y `legalId` llegan solo si los permites en `inputMapping.sendUserFields`. |
| `input.parts` | De 1 a 10 partes. Un mensaje de texto es `{"type":"text","text":"..."}`. |
| `input.inbound` | Solo en mensajes que no son texto (imagen, audio, ubicación, respuesta de Flow). |
| `context.variables` | Solo las variables del flujo que listes en `inputMapping.variables`. |
| `deadlineMs` | Milisegundos que tiene tu agente para responder (1000 a 120000). |
| `trace` | `executionId` y, opcionalmente, `workflowId` y `nodeId`. |
| `interaction` | `single_task` o `multi_turn`. |

## Qué debe responder tu agente

Responde con HTTP 200 y un JSON. La respuesta es **estricta**: cualquier campo que no esté en esta tabla se rechaza.

| Campo | Obligatorio | Descripción |
| - | - | - |
| `status` | Sí | `completed`, `action_required`, `failed`, `timed_out` o `blocked`. |
| `outputs` | Sí | Arreglo de hasta 20 partes. Usa `[]` si no hay nada que decir. |
| `signals` | No | Hasta 5 señales. Si lo omites, Jelou usa `[]`. |
| `turnId` | No | Si lo omites, Jelou usa el del request. Si lo envías, debe ser idéntico al recibido. |
| `provider` | No | Jelou completa `provider.kind` por ti. |
| `timing` | No | Jelou mide `timing.latencyMs` por ti. |
| `usage` | No | `inputTokens`, `outputTokens` y `reportedBy: "provider"`. |
| `error` | Solo con `failed` o `timed_out` | `code`, `retryable` y `message` opcional (hasta 500 caracteres). No se permite con otro `status`. |

Reglas por estado: `failed` y `timed_out` exigen `error` y `outputs: []`; `blocked` exige `outputs: []`; `action_required` exige una señal `action_required`.

<Warning>
  Los nombres deben ser exactamente `status` y `outputs`. Jelou no acepta alias como `output`, `messages`, `result` o `turnStatus`.
</Warning>

### Partes de `outputs`

| `type` | Campos |
| - | - |
| `text` | `text` obligatorio, de 1 a 4096 caracteres. Divide respuestas más largas en varias partes de texto. |
| `choices` | `choices` obligatorio, de 1 a 10 elementos `id` y `label` (la etiqueta admite hasta 24 caracteres). `text` opcional hasta 1024. |
| `card` | `title` obligatorio (hasta 80). Opcionales: `body` (1024), `imageUrl` (https) y `actions` (hasta 3, con `label` y `url` o `choiceId`). |
| `media` | `url` (https) y `mediaType` (por ejemplo `image/png`); `caption` opcional. |
| `data` | `data`, un objeto. |

### Señales (`signals`)

| `type` | Efecto |
| - | - |
| `handoff_requested` | Pasa la conversación a una persona. Campos opcionales: `reason` (hasta 500) y `queue`. El nodo sale por `agent_handoff`. |
| `conversation_ended` | Indica que la conversación terminó. |
| `variables_set` | `variables`, un objeto de 1 a 30 claves que se guarda en la variable del nodo. |
| `action_required` | `actionId`, `name` y `arguments` opcional. |
| `action_selected` | `name` y `data` opcional. |

## Respuestas de ejemplo

### Respuesta mínima

```json theme={null}
{ "status": "completed", "outputs": [{ "type": "text", "text": "Hola, con gusto te ayudo con tu saldo." }] }
```

Es equivalente a enviar la forma completa:

```json theme={null}
{
  "turnId": "trn_01J8A7K2M4XQ",
  "status": "completed",
  "outputs": [{ "type": "text", "text": "Hola, con gusto te ayudo con tu saldo." }],
  "signals": [],
  "provider": { "kind": "http_sync" },
  "timing": { "latencyMs": 0 }
}
```

### Con botones de elección

```json theme={null}
{
  "status": "completed",
  "outputs": [
    { "type": "choices", "text": "Que necesitas?", "choices": [{ "id": "saldo", "label": "Ver saldo" }, { "id": "asesor", "label": "Hablar con asesor" }] }
  ]
}
```

### Pasar a un asesor

```json theme={null}
{
  "status": "completed",
  "outputs": [{ "type": "text", "text": "Te comunico con un asesor." }],
  "signals": [{ "type": "handoff_requested", "reason": "El cliente pidio hablar con una persona" }]
}
```

### Guardar variables y terminar

```json theme={null}
{
  "status": "completed",
  "outputs": [{ "type": "text", "text": "Listo, registre tu solicitud. Gracias por escribirnos." }],
  "signals": [
    { "type": "variables_set", "variables": { "ticket": "A-1042", "categoria": "reclamo" } },
    { "type": "conversation_ended" }
  ]
}
```

### Error del lado del agente

El nodo sale por `agent_failed`.

```json theme={null}
{
  "status": "failed",
  "outputs": [],
  "error": { "code": "provider_error", "retryable": true, "message": "El sistema de saldos no respondio" }
}
```

## Códigos HTTP

Si tu agente responde con un código distinto de 2xx, el turno falla: `401` y `403` como `auth`, `404` y `410` como `not_active`, `429` como `rate_limited` y `408`, `504` o `5xx` como `provider_error`. `429` y `5xx` se reintentan si configuraste reintentos.

## Qué pasa si la respuesta no cumple el formato

El turno falla con el código `malformed` y el mensaje indica qué corregir, solo con nombres de campos y rutas (nunca con valores). Por ejemplo:

```text theme={null}
response does not satisfy AgentTurnResult: missing status; missing outputs; unknown field output (did you mean outputs?); unknown field turnStatus (did you mean status?)
```

Puedes ver el mensaje en `externalAgent.error.message` del resultado del nodo en la ejecución o en la prueba.

## Ejemplo: handler HTTP en Amazon Bedrock AgentCore (Python)

Lee el mensaje del usuario desde `input.parts` y devuelve una respuesta válida. El resultado de la función es el cuerpo JSON de la respuesta.

```python theme={null}
from bedrock_agentcore.runtime import BedrockAgentCoreApp

app = BedrockAgentCoreApp()


def answer_for(text):
    if "asesor" in text.lower():
        return {
            "status": "completed",
            "outputs": [{"type": "text", "text": "Te comunico con un asesor."}],
            "signals": [{"type": "handoff_requested", "reason": "El cliente pidio un asesor"}],
        }
    return {
        "status": "completed",
        "outputs": [{"type": "text", "text": "Recibi tu mensaje: " + text[:500]}],
    }


@app.entrypoint
def invoke(payload, context=None):
    parts = payload.get("input", {}).get("parts", [])
    text = " ".join(p.get("text", "") for p in parts if p.get("type") == "text").strip()
    try:
        result = answer_for(text)
    except Exception:
        return {
            "status": "failed",
            "outputs": [],
            "error": {"code": "provider_error", "retryable": True, "message": "El agente no pudo responder"},
        }
    result["turnId"] = payload["turnId"]
    return result


if __name__ == "__main__":
    app.run()
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.