📋 Forms
Forms are Particular Tools to ask information to the user using the conversation provided!
How the Forms work
Imagine a scenario where you need to create a Order system for a pizzeria, using only the conversation of the user. The user must provide 3 informations:
- Type of pizza, must be a string in a restrictive set
- Phone number, must be 10 numbers long and restrictive to a specific dialling code
- Address, must be a valid address of "Milano"
How can i resolve this problem? Well, this type of information are very specific, needs validators (Phone numbers can be of different structure based on the country, the pizzeria has a well-defined list of pizza, you can deliver only in a certain area of the city) and can be provided in different orders (One user can tell the address before telling type of pizza!).
This is where the forms comes handy!
Implementation
class PizzaOrder(BaseModel): #(1)
pizza_type: str
phone: str
address: str
@form #(2)
class PizzaForm(CatForm): #(3)
description = "Pizza Order" #(4)
model_class = PizzaOrder #(5)
start_examples = [ #(6)
"order a pizza!",
"I want pizza"
]
stop_examples = [ #(7)
"stop pizza order",
"not hungry anymore",
]
ask_confirm = True #(8)
def submit(self, form_data): #(9)
# do the actual order here!
# Fake call
out = request.post("https://fakecallpizza/order",{
"pizza_type": form_data.pizza_type,
"phone": form_data.phone,
"address": form_data.address
})
if(out.status_code != 201):
raise Exception()
time = out.json()["estimatedTime"]
# return to conversation
return {
"output": f"Pizza order on its way: {form_data}. Estimated time: {time}"
}
- Pydantic class rappresenting the information you need to retrive
- Every class decorated with
@forms
is a Form. - Every Form must inhirit from
CatForm
. - Description of the Form
- Pydantic class name.
- Every form must have a list of start examples, so the llm can select the form properly. Is the same principal of tool's docstring
- Every form must have a list of stop examples, so the llm can stop properly the form during the conversation.
- Every form can ask the user the confirmation of the information the user provided.
- Every form must overload this method and the fuctionality is the same of tools: call database to collect the information, call the Order API, call another agent or llm etc..
Changing the "moves" of the form
Forms are implemented as FSM and you can change any move of the FSM by overloading the methods.
Here the diagram of the FSM: TODO
State-transition function
Each FSM has a State-Transition function that describes what is the next move to do based on the input we give. In this case the input is the User prompt and the def next(self)
method is State-Transition function.
The form has 4 states to be evaluate:
- INCOMPLETE
- WAIT_CONFIRM
- CLOSED
- COMPLETE
each state can execute 1 Phase:
- User Stop Confirmation Phase
- User Confirmation Phase
- Updating Phase
- Visualization Phase
- Submit Phase
You can change this state-transition by overloading the method def next(self)
and accessing the state by self._state
. The states are value from the CatFormState
enum.
User Stop Confirmation Phase
The user stop confirmation phase is where the form produce a prompt to ask the user his willingness to continue. You can change this phase by overloading the method def check_exit_intent(self)
User Confirmation Phase
The user confirmation phase is where the form produce a prompt to ask confirmation of the information provided by the user, if ask_confirm
is true. You can change this phase by overloading the method def confirm(self)
Updating Phase
The Updating phase is where the form execute the Extraction Phase, Sanitization Phase and Validation Phase. You can change this phase by overloading the method def update(self)
Extraction Phase
The extraction phase is where the form extract all possibile information from the user prompt. You can change this phase by overloading the method def extract(self)
Sanitization Phase
The sanitization phase is where the information are sinitize to take away unwanted values (null, None, '', ' ', etc...). You can change this phase by overloading the method def sanitize(self, model)
Validation Phase
The validation phase is where the form attempt to construct the model, so pydantic can use the validators implemented and check each field. You can change this phase by overloading the method def valdiate(self, model)
Visualization Phase
The visualization phase is where the form produce a message to the user displaying the model.
By default the cat present the forms like so
When there is invalid info that was retrived from the conversation, the cat tells you exacly who and what issue
You can change this phase by overloading the method def message(self)
:
#in the form you define
def message(self): #(1)
if self._state == CatFormState.CLOSED: #(2)
return {
"output": f"Form {type(self).__name__} closed"
}
missing_fields: List[str] = self._missing_fields #(3)
errors: List[str] = self._errors #(4)
out: str = f"""
the missing information are: {missing_fields}.
this are the invalid ones: {errors}
"""
if self._state == CatFormState.WAIT_CONFIRM:
out += "\n --> Confirm? Yes or no?"
return {
"output": out
}
- This method is useful to change the form visualization.
- Forms have states that can be checked.
- Forms can access the list of the missing fields
- Forms can access the list of the invalid fields and the error correlated
Final Phase: Submit
The Submit phase is where the form conclude the jorney by executing all defined instructions with the informations gatered from the conversation. The method have 2 params:
- self (you can access information about the form and the
StrayCat
instance) - form_data (the pydantic model defined)
And must return an Dict where the value of keyoutput
is a string and will be displayed in the chat.
If you need to use the form in the future conversation, you can retrive the model in the working memory by accessing the key form
Below an example: