GoalChain es un marco simple pero eficaz para permitir flujos de conversación orientados a objetivos para la interacción humano-LLM y LLM-LLM.
pip install goalchain
Importemos las clases Field
, ValidationError
, Goal
y GoalChain
, que son la base del flujo de conversación.
from goalchain import Field , ValidationError , Goal , GoalChain
En este ejemplo, crearemos un asistente de IA cuyo objetivo es recopilar información de un cliente sobre el pedido de producto que desea. Definimos la información que se recopilará utilizando objetos Field
dentro de ProductOrderGoal
, que es hijo de Goal
:
También definimos un validador para la cantidad (después de convertir el tipo a int). ValidationError
se utiliza para devolver mensajes de error a la conversación. Estos mensajes deben ser legibles para los humanos.
format_hint
es una sugerencia de tipo de lenguaje natural para la salida en modo JSON del LLM.
def quantity_validator ( value ):
try :
value = int ( value )
except ( ValueError , TypeError ):
raise ValidationError ( "Quantity must be a valid number" )
if value <= 0 :
raise ValidationError ( "Quantity cannot be less than one" )
if value > 100 :
raise ValidationError ( "Quantity cannot be greater than 100" )
return value
class ProductOrderGoal ( Goal ):
product_name = Field ( "product to be ordered" , format_hint = "a string" )
customer_email = Field ( "customer email" , format_hint = "a string" )
quantity = Field ( "quantity of product" , format_hint = "an integer" , validator = quantity_validator )
En caso de que el cliente cambie de opinión, creemos otra clase secundaria Goal
llamada OrderCancelGoal
. Solicitaremos un motivo opcional para la cancelación del pedido en curso por parte del cliente. Al especificar que el campo es "(opcional)" en la descripción, el LLM sabrá que no es necesario para lograr el objetivo.
class OrderCancelGoal ( Goal ):
reason = Field ( "reason for order cancellation (optional)" , format_hint = "a string" )
Tenga en cuenta que los nombres de los objetos de campo, como product_name
se pasan directamente al indicador de LLM y, por lo tanto, forman parte de la tarea de ingeniería del indicador, al igual que cualquier otra cadena.
Básicamente, las clases que definimos son como formularios que debe completar el cliente, pero carecen de instrucciones. Agreguémoslos creando instancias de las clases como objetos.
product_order_goal = ProductOrderGoal (
label = "product_order" ,
goal = "to obtain information on an order to be made" ,
opener = "I see you are trying to order a product, how can I help you?" ,
out_of_scope = "Ask the user to contact sales team at [email protected]"
)
order_cancel_goal = OrderCancelGoal (
label = "cancel_current_order" ,
goal = "to obtain the reason for the cancellation" ,
opener = "I see you are trying to cancel the current order, how can I help you?" ,
out_of_scope = "Ask the user to contact the support team at [email protected]" ,
confirm = False
)
definimos
opener
predeterminado: algo que el asistente de IA usará sin información previa, El indicador confirm
determina si el asistente de IA solicitará confirmación una vez que tenga toda la información requerida definida utilizando los objetos Field
. Es True
por defecto. No necesitamos una confirmación para el objetivo de cancelación del pedido, ya que ya es en sí mismo un tipo de confirmación.
A continuación debemos conectar los objetivos.
product_order_goal . connect ( goal = order_cancel_goal ,
user_goal = "to cancel the current order" ,
hand_over = True ,
keep_messages = True )
El user_goal
es otra declaración "a ...". Sin hand_over=True
el agente de IA respondería con el opener
. Establecerlo en True
garantiza que la conversación fluya sin problemas. A veces es posible que desee una respuesta predeterminada, otras no.
keep_messages=True
significa que order_cancel_goal
recibirá el historial completo de la conversación con product_order_goal
; de lo contrario, se borrará. Nuevamente, a veces es posible que desee borrar el historial de conversaciones, como cuando se simulan diferentes personalidades de IA.
Consideremos también la posibilidad de un cliente realmente indeciso. También deberíamos darles la opción de "cancelar la cancelación".
order_cancel_goal . connect ( goal = product_order_goal ,
user_goal = "to continue with the order anyway" ,
hand_over = True ,
keep_messages = True )
En algún momento te habrás preguntado si puedes marcar un gol sin ningún objeto Field
. ¡Puede! Tal objetivo es un objetivo de enrutamiento definido únicamente por las conexiones que tiene. Esto es útil, por ejemplo, en un sistema de menú de correo de voz.
Quizás también sientas curiosidad por saber si puedes conectar un objetivo consigo mismo. ¡Puede! Esto es útil, por ejemplo, cuando se usa confirm=False
con el objeto que hereda Goal
, donde se requiere una entrada secuencial del usuario de cierta variedad.
También puede encadenar conexiones, por ejemplo goal.connect(...).connect(...).connect(...)
Finalmente, usemos GoalChain
para establecer el objetivo inicial y probar nuestro asistente de ventas de IA.
goal_chain = GoalChain ( product_order_goal )
Tenga en cuenta que cada objetivo puede usar una API LLM separada según lo habilitado por LiteLLM, y si tiene configuradas las variables de entorno requeridas, puede usar cualquier modelo de los proveedores de modelos admitidos.
El modelo predeterminado es "gpt-4-1106-preview"
, es decir:
product_order_goal = ProductOrderGoal (...
model = "gpt-4-1106-preview" ,
json_model = "gpt-4-1106-preview"
)
También puede pasar parámetros comunes de LiteLLM usando params
, por ejemplo:
product_order_goal = ProductOrderGoal (...
model = "gpt-4-1106-preview" ,
json_model = "gpt-4-1106-preview" ,
params = { "temperature" : 1.5 , "max_tokens" : 10 }
)
También puede usar params
para llamar a modelos locales usando VLLM.
Cuando utilice el modelo predeterminado "gpt-4-1106-preview"
, recuerde configurar la variable de entorno OPENAI_API_KEY
.
import os
os . environ [ "OPENAI_API_KEY" ] = "sk-ABC..."
Nota: El código hasta ahora está disponible de forma básica. Péguelo en un cuaderno de Jupyter, precedido por !pip install goalchain
para comenzar con el ejemplo en vivo a continuación.
Por lo general, es el usuario quien primero avisa al agente de IA, pero si este no es el caso, llamamos get_response
sin ningún argumento o usamos None
como argumento:
goal_chain . get_response ()
{'type': 'message',
'content': 'Great choice! Could you please provide me with your email address to proceed with the order?',
'goal': <__main__.ProductOrderGoal at 0x7f8c8b687110>}
GoalChain devuelve un dict
que contiene el tipo de respuesta (ya sea message
o data
), el contenido de la respuesta (en este momento solo nuestra respuesta predefinida) y el objeto actual que hereda Goal
.
Preguntemos a nuestro asistente de IA sobre una posible compra.
goal_chain . get_response ( "Hi, I'd like to buy a vacuum cleaner" )
{'type': 'message',
'content': 'Great! Could you please provide your email address so we can send the confirmation of your order?',
'goal': <__main__.ProductOrderGoal at 0x7ff0fb283090>}
El asistente de IA está trabajando para lograr su objetivo actual y recopilar la información necesaria para un pedido.
goal_chain . get_response ( "Sure, it is [email protected]" )
{'type': 'message',
'content': 'Thank you, John. Which model of vacuum cleaner would you like to order?',
'goal': <__main__.ProductOrderGoal at 0x7ff0fb283090>}
goal_chain . get_response ( "The 2000XL model" )
{'type': 'message',
'content': 'How many of the 2000XL model would you like to order?',
'goal': <__main__.ProductOrderGoal at 0x7ff0fb283090>}
Probemos si nuestro asistente de IA puede manejar una cancelación del pedido actual.
goal_chain . get_response ( "Actually I changed my mind, cancel this order" )
{'type': 'message',
'content': 'Of course, I can assist with that. Could you please tell me the reason for the cancellation?',
'goal': <__main__.OrderCancelGoal at 0x7ff0fb275650>}
Funcionó. Tenga en cuenta que el objetivo devuelto ahora es del tipo OrderCancelGoal
. Hemos cambiado de objetivos. Probemos también si podemos volver atrás.
goal_chain . get_response ( "Actually, yeah, I would like to buy the vacuum cleaner" )
{'type': 'message',
'content': 'Understood. How many of the 2000XL model would you like to order?',
'goal': <__main__.ProductOrderGoal at 0x7ff0fb283090>}
Volvemos al ProductOrderGoal
.
goal_chain . get_response ( "1 please" )
{'type': 'message',
'content': 'To confirm, you would like to order one 2000XL vacuum cleaner and the order will be sent to [email protected], is that correct?',
'goal': <__main__.ProductOrderGoal at 0x7ff0fb283090>}
El asistente de IA confirma nuestro pedido. Si no nos gustara este comportamiento, usaríamos confirm=False
.
Veamos cómo responde el asistente a una consulta fuera de alcance.
goal_chain . get_response ( "Is it a good vacuum cleaner? What do you think?" )
{'type': 'message',
'content': "For product reviews and additional information, I recommend contacting our sales team at [email protected]. They can help with your inquiries. Meanwhile, can you please confirm if you'd like to proceed with the order for one 2000XL vacuum cleaner to [email protected]?",
'goal': <__main__.ProductOrderGoal at 0x7ff0fb283090>}
El asistente de IA nos redirige a la bandeja de entrada del equipo de ventas como se definió anteriormente y reitera la confirmación.
Pero lancemos una bola curva...
goal_chain . get_response ( "Ok, I'd actually like to make that an order of 500" )
{'type': 'message',
'content': "Just to clarify, you'd like to order 500 units of the 2000XL vacuum cleaner, with the order confirmation sent to [email protected]. Is that correct?",
'goal': <__main__.ProductOrderGoal at 0x7ff0fb283090>}
goal_chain . get_response ( "Yes" )
{'type': 'message',
'content': 'I’m sorry, but I need to inform you that the quantity cannot be greater than 100 for an order. If you would like to proceed with an order within this limit, please let me know.',
'goal': <__main__.ProductOrderGoal at 0x7ff0fb283090>}
El validador que utilizamos ha proporcionado suficiente información al asistente de IA para justificar por qué no puede procesar esta cantidad a través del mensaje ValidationError
.
Tenga en cuenta que GoalChain solo valida las entradas una vez que se ha completado el Goal
por razones de rendimiento y eficiencia del token. Si desea validar las entradas sobre la marcha, tiene dos opciones:
Utilice un Goal
con un solo Field
y confirm=False
. Encadene estos objetivos en lugar de utilizar varios campos en un solo Goal
.
Utilice un mensaje suave, por ejemplo, quantity = Field("quantity of product (no more than 100)", format_hint="an integer")
. Este enfoque no es infalible, por lo que se recomienda utilizar un validador. Sin embargo, el usuario recibirá comentarios inmediatos.
Completemos el pedido.
goal_chain . get_response ( "Alright, I'll guess I'll just go with 1" )
{'type': 'message',
'content': 'To confirm, you would like to order one 2000XL vacuum cleaner and the order will be sent to [email protected], is that correct?',
'goal': <__main__.ProductOrderGoal at 0x7ff0fb283090>}
goal_chain . get_response ( "That's right" )
{'type': 'data',
'content': {'customer_email': '[email protected]',
'product_name': '2000XL',
'quantity': 1},
'goal': <__main__.ProductOrderGoal at 0x7ff0fb283090>}
El contenido devuelto es un diccionario analizado a partir de la salida del modo JSON del LLM. Las claves son los nombres de nuestras instancias de campo. Ya podemos utilizar los datos para realizar algún tipo de acción, como por ejemplo procesar el pedido de nuestra hipotética aspiradora 2000XL.
Tenga en cuenta que, en realidad, si estuviera creando un sistema de este tipo, necesitaría establecer un objetivo de búsqueda de productos dedicado para no permitir nombres de productos arbitrarios o sin sentido.
Enviemos nuestra confirmación de que el pedido ha sido procesado a través de simulate_response
. También usaremos rephrase = True
para reformular el resultado, que parecerá más natural en caso de que el cliente interactúe frecuentemente con el objetivo.
goal_chain . simulate_response ( f"Thank you for ordering from Acme. Your order will be dispatched in the next 1-3 business days." , rephrase = True )
{'type': 'message',
'content': 'We appreciate your purchase with Acme! Rest assured, your order will be on its way within the next 1 to 3 business days.',
'goal': <__main__.ProductOrderGoal at 0x7ff0fb283090>}
En este punto podemos finalizar la sesión o volver a conectarnos a un menú o a un objetivo de enrutamiento para obtener más información.
Si desea personalizar o contribuir a GoalChain, o informar cualquier problema, visite la página de GitHub.