Skip to content

Nodos

Los chatbots se construyen en base a nodos. En Botlang, un nodo es una función que recibe un contexto (diccionario) y un mensaje, y retorna una tupla (c, m, n). Donde c y m son el contexto y mensaje de salida, respectivamente, y n es el nodo siguiente.

De este modo, cuando un usuario envía un mensaje al chatbot, este se le pasa al nodo correspondiente (dependiendo del flujo definido) junto al contexto (datos del usuario y otras variables almacenadas). El nodo luego entrega una respuesta y un puntero al nodo a invocar al recibir un nuevo mensaje (puede ser el mismo). Esto permite implementar flujos de conversación complejos.

Bot node

En Botlang los nodos son valores de tipo bot node (funciones con ciertas particularidades). La tupla de salida se encapsula en un valor de tipo node result.

El flujo de un chatbot comienza con el valor del nodo ubicado al final del código principal del chatbot (punto de entrada).

Ejemplo de flujo simple:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
(define node-b (bot-node (context message)
    (if (equal? message (plain "chao"))
        (node-result context "Adiós" end-node)
        (node-result context message node-b)
    )
)

(define node-a (bot-node (c m)
    (node-result c "Hola! Te remedo." node-b)
)

node-a

Y la conversación:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
>> Hola
Hola! Te remedo.

>> Qué tal?
Qué tal?

>> Deja de imitarme
Deja de imitarme

>> Chao
Adiós

Cuando el flujo encuentra un nodo terminal (en este caso end-node), la conversación finaliza. Si el usuario vuelve a enviar un mensaje, este cae en el nodo de entrada.

Nodos terminales

Hay dos tipos:

  • end-node nodo terminal por defecto es end-node.
  • (terminal-node arg) termina la sesión con un mensaje de estado (arg). Dicho mensaje puede usarse para controlar el paso a un asistente humano, por ejemplo.

slots-node

Slots esta pensado para ahorrar la cantidad de nodos en un bot, y para un comportamiento de toma de datos en la que permite al usuario cambiar de tema y luego seguir íngresando datos. Un slot-node recibe un nombre, contexto y mensaje y en su cuerpo puede tener tres tipos de "casos":

  • before recibe un bloque que se ejecuta antes del comportamiento normal del nodo.

  • digress recibe un bloque que se evalúa para identificar un "cambio de tema". Si dicho valor es un node-result, el slot-node lo retorna. Si el valor es nil, el slot-node continúa con la ejecución de los slots. Cuando se activa digress el siguiente mensaje del usuario volverá al slot-node.

  • slot recibe una llave, contexto, cuerpo y opcionalmente un mensaje. Ejecutará el cuerpo y, en caso que su valor sea distinto de nil, lo asignará a llave en el contexto. Si se entrega el mensaje opcional, el slot obligará a que el valor asociado a llave sea distinto de nil. Si no lo es, el slot-node retornará el mensaje y apuntará a sí mismo como nodo siguiente.

  • then recibe un cuerpo que se evalúa luego de ejecutar todos los slots y retorna un node-result.

A continuación un ejemplo de uso:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
(defun default-behavior (c m)
    (cond
        [(match? "ctm" m)
            (node-result c "No me insultes" (return end-node)) ;retorna al slot-node
        ]
        [(match? "chao" m) (node-result c "Adiós" end-node)]
        [else nil]
    )
)

(slots-node node2 (c m)
    [slot confirm c
        (cond
            [(match? "si" m) #t]
            [(match? "no" m) #f]
            [else nil]
        )
        "¿Confirmas tu pedido?"
    ]
    [then (begin
        (remove! c "type")
        (remove! c "size")
        (remove! c "with-cream")
        (if (get c "confirm")
            (node-result c "Confirmado" end-node)
            (node-result c "Bueno, ¿qué café quieres?" node1)
        )
    )]
)

(slots-node node1 (c m)
    [digress (default-behavior c m)]
    [slot type c
        (match ".*(americano|latte|mocha).*" m 1)
        "¿De qué tipo quieres tu café?"
    ]
    [slot size c
        (match ".*(chico|mediano|grande).*" m 1)
        "¿De qué tamaño quieres tu café?"
    ]
    [slot with-cream c
        (cond
            [(match? "si(\\s.*)?" m) #t]
            [(match? "no(\\s.*)?" m) #f]
            [else nil]
        )
        "¿Lo quieres con crema?"
    ]
    [then
        (node-result c
            (append
                "¿Tu pedido es un " (get c "type") " " (get c "size") " "
                (if (get c "with-cream") "con crema?" "sin crema?")
            )
            node2
        )
    ]
)

node1

Conversación:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
>> quiero un americano grande
¿Lo quieres con crema?

>> chao
Adiós

>> quiero un americano chico
¿Lo quieres con crema?

>> si
¿Tu pedido es un americano chico con crema?

>> no
Bueno, ¿qué café quieres?

>> un latte
¿De qué tamaño quieres tu café?

>> ctm
No me insultes

>> Bueno, lo quiero mediano
¿Lo quieres con crema?

>> no
¿Tu pedido es un latte mediano sin crema?

>> lalalala
¿Confirmas tu pedido?

>> miau
¿Confirmas tu pedido?

>> si
Confirmado