GoalChain é uma estrutura simples, mas eficaz para permitir fluxos de conversa orientados a objetivos para interação humano-LLM e LLM-LLM.
pip install goalchain
Vamos importar as classes Field
, ValidationError
, Goal
e GoalChain
, que são a base do fluxo da conversa.
from goalchain import Field , ValidationError , Goal , GoalChain
Neste exemplo criaremos um assistente de IA cujo objetivo é coletar informações de um cliente sobre o pedido do produto desejado. Definimos as informações a serem coletadas usando objetos Field
dentro de ProductOrderGoal
, que é filho de Goal
:
Também definimos um validador para a quantidade (após a conversão do tipo para um int). ValidationError
é usado para transmitir mensagens de erro de volta à conversa. Essas mensagens devem ser legíveis por humanos.
format_hint
é uma dica de tipo de linguagem natural para a saída do modo JSON do 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 )
Caso o cliente mude de ideia, vamos criar outra classe filha Goal
chamada OrderCancelGoal
. Solicitaremos um motivo opcional para o cancelamento do pedido em andamento pelo cliente. Ao especificar que o campo é "(opcional)" na descrição, o LLM saberá que não é necessário para atingir o objetivo.
class OrderCancelGoal ( Goal ):
reason = Field ( "reason for order cancellation (optional)" , format_hint = "a string" )
Observe que os nomes dos objetos de campo, como product_name
são passados diretamente para o prompt do LLM e, portanto, fazem parte da tarefa de engenharia de prompt, assim como todas as outras sequências.
Essencialmente as classes que definimos são como formulários a serem preenchidos pelo cliente, mas carecem de instruções. Vamos adicioná-los instanciando as classes 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
)
Nós definimos
opener
padrão - algo que o assistente de IA usará sem entrada prévia, O sinalizador confirm
determina se o assistente de IA solicitará confirmação assim que tiver todas as informações necessárias definidas usando os objetos Field
. É True
por padrão. Não necessitamos de confirmação para o objetivo de cancelamento do pedido, pois ela já é uma espécie de confirmação.
Em seguida, precisamos conectar os objetivos.
product_order_goal . connect ( goal = order_cancel_goal ,
user_goal = "to cancel the current order" ,
hand_over = True ,
keep_messages = True )
O user_goal
é outra instrução “to…”. Sem hand_over=True
o agente de IA responderia com o opener
de lata. Configurá-lo como True
garante que a conversa flua sem problemas. Às vezes você pode querer uma resposta automática, outras vezes não.
keep_messages=True
significa que order_cancel_goal
receberá o histórico completo da conversa com product_order_goal
, caso contrário, será apagado. Novamente, às vezes pode ser desejada uma limpeza do histórico de conversas, como ao simular diferentes personalidades de IA.
Consideremos também a possibilidade de um cliente realmente indeciso. Deveríamos também dar-lhes a opção de “cancelar o cancelamento”.
order_cancel_goal . connect ( goal = product_order_goal ,
user_goal = "to continue with the order anyway" ,
hand_over = True ,
keep_messages = True )
Em algum momento você deve ter se perguntado se é possível fazer uma meta sem nenhum objeto Field
. Você pode! Tal objetivo é um objetivo de roteamento definido apenas pelas conexões que possui. Isto é útil, por exemplo, num sistema de menu de correio de voz.
Você também pode estar curioso para saber se consegue conectar uma meta a si mesma. Você pode! Isso é útil, por exemplo, ao usar confirm=False
com o objeto de herança Goal
, onde você requer alguma variedade de entrada sequencial do usuário.
Você também pode encadear conexões, por exemplo goal.connect(...).connect(...).connect(...)
Finalmente, vamos usar GoalChain
para definir a meta inicial e testar nosso assistente de vendas de IA!
goal_chain = GoalChain ( product_order_goal )
Observe que cada meta pode usar uma API LLM separada, conforme habilitado pelo LiteLLM, e se você tiver as variáveis de ambiente necessárias definidas, poderá usar qualquer modelo dos provedores de modelos suportados.
O modelo padrão é "gpt-4-1106-preview"
, ou seja:
product_order_goal = ProductOrderGoal (...
model = "gpt-4-1106-preview" ,
json_model = "gpt-4-1106-preview"
)
Você também pode passar parâmetros comuns do LiteLLM usando params
, por exemplo:
product_order_goal = ProductOrderGoal (...
model = "gpt-4-1106-preview" ,
json_model = "gpt-4-1106-preview" ,
params = { "temperature" : 1.5 , "max_tokens" : 10 }
)
Você também pode usar params
para chamar modelos locais usando VLLM.
Ao usar o modelo padrão "gpt-4-1106-preview"
, lembre-se de definir a variável de ambiente OPENAI_API_KEY
.
import os
os . environ [ "OPENAI_API_KEY" ] = "sk-ABC..."
Nota: O código até agora está disponível como essência. Cole-o em um notebook Jupyter, precedido por !pip install goalchain
para começar com o exemplo ao vivo abaixo.
Normalmente é o usuário quem avisa primeiro o agente de IA, mas se este não for o caso, chamamos get_response
sem nenhum argumento ou 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 retorna um dict
contendo o tipo de resposta ( message
ou data
), o conteúdo da resposta (no momento apenas nossa resposta pronta) e o objeto atual que herda Goal
.
Vamos consultar nosso assistente de IA sobre uma compra potencial.
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>}
O assistente de IA está trabalhando para atingir seu objetivo atual e coletar as informações necessárias para um 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>}
Vamos testar se nosso assistente de IA pode lidar com o cancelamento do pedido atual.
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>}
Funcionou. Observe que a meta retornada agora é do tipo OrderCancelGoal
. Trocamos objetivos. Vamos também testar se podemos voltar 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>}
Estamos de volta ao 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>}
O assistente de IA confirma nosso pedido. Se não gostássemos desse comportamento, usaríamos confirm=False
.
Vamos ver como o assistente responde a uma consulta fora do escopo.
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>}
O assistente de IA nos redireciona para a caixa de entrada da equipe de vendas conforme definido anteriormente e reitera a confirmação.
Mas vamos lançar uma 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>}
O validador que usamos forneceu informações suficientes ao assistente de IA para justificar porque ele não pode processar essa quantidade por meio da mensagem ValidationError
.
Observe que o GoalChain só valida as entradas depois que a Goal
é concluída por motivos de eficiência e desempenho do token. Se quiser validar as entradas conforme você avança, você tem duas opções:
Use uma Goal
com apenas um Field
e confirm=False
. Encadeie essas metas em vez de usar vários campos em uma única Goal
.
Use um prompt virtual, por exemplo, quantity = Field("quantity of product (no more than 100)", format_hint="an integer")
. Esta abordagem não é infalível, por isso ainda é recomendado o uso de um validador. O usuário receberá feedback imediato, no entanto.
Vamos completar o 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>}
O conteúdo retornado é um dicionário analisado a partir da saída do modo JSON do LLM. As chaves são nossos nomes de instância de campo. Agora podemos usar os dados para realizar algum tipo de ação, como processar o pedido do nosso hipotético aspirador 2000XL.
Observe que, na realidade, se você estivesse construindo tal sistema, precisaria estabelecer uma meta dedicada de pesquisa de produtos para não permitir nomes de produtos arbitrários ou sem sentido.
Vamos enviar nossa confirmação de que o pedido foi processado via simulate_response
. Também usaremos rephrase = True
para reformular a saída, o que parecerá mais natural caso o cliente interaja frequentemente com a meta.
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>}
Neste ponto, podemos encerrar a sessão ou conectar-nos novamente a um menu ou objetivo de roteamento para obter mais informações.
Se você quiser personalizar ou contribuir com o GoalChain, ou relatar qualquer problema, visite a página do GitHub.