GoalChain est un cadre simple mais efficace pour permettre des flux de conversation orientés vers des objectifs pour l'interaction humain-LLM et LLM-LLM.
pip install goalchain
Importons les classes Field
, ValidationError
, Goal
et GoalChain
, qui constituent la base du flux de conversation.
from goalchain import Field , ValidationError , Goal , GoalChain
Dans cet exemple, nous allons créer un assistant IA dont le but est de collecter des informations auprès d'un client sur la commande de produits souhaitée. Nous définissons les informations à collecter à l'aide des objets Field
dans ProductOrderGoal
, qui est un enfant de Goal
:
Nous définissons également un validateur pour la quantité (après conversion de type en int). ValidationError
est utilisé pour renvoyer les messages d'erreur à la conversation. Ces messages doivent être lisibles par l'homme.
format_hint
est un indice de type en langage naturel pour la sortie en mode JSON du 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 )
Si le client change d'avis, créons une autre classe enfant Goal
appelée OrderCancelGoal
. Nous demanderons au client un motif facultatif pour l'annulation de la commande en cours. En précisant que le champ est "(facultatif)" dans la description, le LLM saura qu'il n'est pas nécessaire pour atteindre l'objectif.
class OrderCancelGoal ( Goal ):
reason = Field ( "reason for order cancellation (optional)" , format_hint = "a string" )
Notez que les noms d'objet de champ, tels que product_name
sont transmis directement à l'invite LLM et font donc partie de la tâche d'ingénierie de l'invite, comme toutes les autres chaînes.
Essentiellement, les classes que nous avons définies sont comme des formulaires à remplir par le client, mais elles manquent d'instructions. Ajoutons-les en instanciant les classes en tant qu'objets.
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
)
Nous définissons
opener
par défaut - quelque chose que l'assistant IA utilisera sans aucune entrée préalable, L'indicateur confirm
détermine si l'assistant IA demandera une confirmation une fois qu'il aura toutes les informations requises définies à l'aide des objets Field
. C'est True
par défaut. Nous n'avons pas besoin de confirmation pour l'objectif d'annulation de commande, car il s'agit déjà en soi d'une sorte de confirmation.
Ensuite, nous devons relier les objectifs entre eux.
product_order_goal . connect ( goal = order_cancel_goal ,
user_goal = "to cancel the current order" ,
hand_over = True ,
keep_messages = True )
Le user_goal
est une autre instruction "to...". Sans hand_over=True
l'agent IA répondrait avec l' opener
en conserve. Le définir sur True
garantit le bon déroulement de la conversation. Parfois, vous souhaiterez peut-être une réponse standardisée, d’autres fois non.
keep_messages=True
signifie que order_cancel_goal
recevra l'historique complet de la conversation avec product_order_goal
, sinon il sera effacé. Encore une fois, il peut parfois être souhaitable d’effacer l’historique des conversations, par exemple lors de la simulation de différentes personnalités de l’IA.
Considérons également la possibilité d'un client vraiment indécis. Nous devrions également leur donner la possibilité « d’annuler l’annulation ».
order_cancel_goal . connect ( goal = product_order_goal ,
user_goal = "to continue with the order anyway" ,
hand_over = True ,
keep_messages = True )
À un moment donné, vous vous êtes peut-être demandé si vous pouviez vous fixer un objectif sans aucun objet Field
. Tu peux! Un tel objectif est un objectif de routage défini uniquement par les connexions dont il dispose. Ceci est utile par exemple dans un système de menus de messagerie vocale.
Vous pourriez également être curieux de savoir si vous pouvez relier un objectif à lui-même. Tu peux! Ceci est utile par exemple lors de l'utilisation confirm=False
avec l'objet Goal
-inheriting, où vous avez besoin d'une entrée utilisateur séquentielle d'une certaine variété.
Vous pouvez également enchaîner les connexions, par exemple goal.connect(...).connect(...).connect(...)
Enfin, utilisons GoalChain
pour définir l'objectif initial et tester notre assistant commercial IA !
goal_chain = GoalChain ( product_order_goal )
Notez que chaque objectif peut utiliser une API LLM distincte, comme activé par LiteLLM, et si les variables d'environnement requises sont définies, vous pouvez utiliser n'importe quel modèle des fournisseurs de modèles pris en charge.
Le modèle par défaut est "gpt-4-1106-preview"
, c'est-à-dire :
product_order_goal = ProductOrderGoal (...
model = "gpt-4-1106-preview" ,
json_model = "gpt-4-1106-preview"
)
Vous pouvez également transmettre des paramètres communs à LiteLLM à l'aide de params
, par exemple :
product_order_goal = ProductOrderGoal (...
model = "gpt-4-1106-preview" ,
json_model = "gpt-4-1106-preview" ,
params = { "temperature" : 1.5 , "max_tokens" : 10 }
)
Vous pouvez également utiliser params
pour appeler des modèles locaux à l'aide de VLLM.
Lorsque vous utilisez le modèle par défaut "gpt-4-1106-preview"
, n'oubliez pas de définir la variable d'environnement OPENAI_API_KEY
.
import os
os . environ [ "OPENAI_API_KEY" ] = "sk-ABC..."
Remarque : Le code jusqu'à présent est disponible sous forme d'essentiel. Collez-le dans un notebook Jupyter, précédé de !pip install goalchain
pour commencer avec l'exemple en direct ci-dessous.
Habituellement, c'est l'utilisateur qui demande l'agent IA en premier, mais si ce n'est pas le cas, nous appelons get_response
sans aucun argument, ou utilisons None
comme argument :
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 renvoie un dict
contenant le type de réponse (soit message
, soit data
), le contenu de la réponse (pour l'instant, juste notre réponse standardisée) et l'objet actuel héritant Goal
.
Interrogons notre assistant IA avec un achat potentiel.
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>}
L'assistant IA s'efforce d'atteindre son objectif actuel et de rassembler les informations requises pour une commande.
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>}
Testons si notre assistant IA peut gérer une annulation de la commande en cours.
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>}
Cela a fonctionné. Notez que l'objectif renvoyé est désormais de type OrderCancelGoal
. Nous avons changé d'objectif. Testons également si nous pouvons revenir en arrière.
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>}
Nous revenons au 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>}
L'assistant IA confirme notre commande. Si nous n’aimions pas ce comportement, nous utiliserions confirm=False
.
Voyons comment l'assistant répond à une requête hors de portée.
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>}
L'assistant IA nous redirige vers la boîte de réception de l'équipe commerciale telle que définie précédemment et réitère la confirmation.
Mais lançons une balle courbe...
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>}
Le validateur que nous utilisons a donné suffisamment d'informations à l'assistant IA pour justifier pourquoi il ne peut pas traiter cette quantité via le message ValidationError
.
Notez que GoalChain ne valide les entrées qu'une fois l' Goal
atteint pour des raisons d'efficacité et de performances des jetons. Si vous souhaitez valider les entrées au fur et à mesure, vous avez deux options :
Utilisez un Goal
avec un seul Field
et confirm=False
. Enchaînez ces objectifs au lieu d’utiliser plusieurs champs dans un seul Goal
.
Utilisez une invite logicielle, par exemple quantity = Field("quantity of product (no more than 100)", format_hint="an integer")
. Cette approche n’est pas infaillible, il est donc tout de même recommandé d’utiliser un validateur. L'utilisateur recevra cependant un retour immédiat.
Terminons la commande.
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>}
Le contenu renvoyé est un dictionnaire analysé à partir de la sortie du mode JSON du LLM. Les clés sont nos noms d'instances de champ. Nous pouvons désormais utiliser les données pour effectuer une sorte d’action, comme traiter la commande de notre hypothétique aspirateur 2000XL.
Notez qu'en réalité, si vous construisiez un tel système, vous devrez définir un objectif de recherche de produits dédié afin de ne pas autoriser les noms de produits arbitraires ou dénués de sens.
Envoyons notre confirmation que la commande a été traitée via simulate_response
. Nous utiliserons également rephrase = True
pour reformuler le résultat, ce qui semblera plus naturel au cas où le client interagit fréquemment avec l'objectif.
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>}
À ce stade, nous pouvons mettre fin à la session ou nous reconnecter à un menu ou à un objectif de routage pour une saisie plus approfondie.
Si vous souhaitez personnaliser ou contribuer à GoalChain, ou signaler des problèmes, visitez la page GitHub.