Skip to content

📋 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:

  1. Type of pizza, must be a string in a restrictive set
  2. Phone number, must be 10 numbers long and restrictive to a specific dialling code
  3. 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}"
        }
  1. Pydantic class rappresenting the information you need to retrive
  2. Every class decorated with @forms is a Form.
  3. Every Form must inhirit from CatForm.
  4. Description of the Form
  5. Pydantic class name.
  6. 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
  7. Every form must have a list of stop examples, so the llm can stop properly the form during the conversation.
  8. Every form can ask the user the confirmation of the information the user provided.
  9. 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:

  1. INCOMPLETE
  2. WAIT_CONFIRM
  3. CLOSED
  4. 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 display form

When there is invalid info that was retrived from the conversation, the cat tells you exacly who and what issue display invalid info

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
        }
  1. This method is useful to change the form visualization.
  2. Forms have states that can be checked.
  3. Forms can access the list of the missing fields
  4. 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 key output 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:

    @hook  
    def before_cat_sends_message(message, cat):
        form_data = cat.working_memory["form"]