picoflow.io

Lesson 7: Present Step

  • This step is responsible to present the list of hotels found to a user.
  • It can also accept instruction to change search criterial and execute another search
  • It can also transition to the comparison hotel step.

Screen Shot


explore step screen shot


Prompt Explanation

  1. We again see a {{HOTEL_FOUND_INFO}} prompt variable that will be filled in at runtime in this step. This is the list of hotels that are found by the ExploreStep
  2. We also need to properly prompt LLM to trigger three available tools: chosen_hotel, search_again and go_compare
Tip
  • You will discover this step does not have the logic to alter the search criteria for a re-searching. The logic resides in ExploreStep. How do we delegate the search criteria back to the previous step? Picoflow has some advanced features to enable inter-step communication you will see in further below.

  • The same applies to send the context window to the CompareStep


Full step prompt:

## Execution instruction ##
  - **Hotel founded JSON** 
    - {{HOTEL_FOUND_INFO}}
  
  - **Presenting Found Hotels**
    - Must use data from `Hotel founded JSON` section, present to user each item's `hotelName` , `total` in `Hotel founded JSON` list in numbered bullet form, and ask user to pick a number.
    - the `total` must be formatted in U.S. currency, and display "total price is: `total`"

  - **Subsequent Choices After Hotel Presentation**
    - While you are presenting the list of found hotels to be picked, tell user they can 
      a. book a hotel by typing name or number presented above.
        - if user choose to book hotel, call tool `chosen_hotel`
      b. re-run the entire search 
        - If user choose to re-run the search, immediately call tool `search_again`
        - Examples:
          - re-run search
          - search again
          - change room type to one bed/two beds/suite
          - add amenities tennis court
          - remove amenities indoor pool
          - change distance to airport to 10 miles.
          - remove distance to city center.
          - I want to see what criteria I have chosen
          - I want to re-enter search criteria
          - show preferences
      c. compare hotel features
        - If user choose to compare features of hotel, call tool `go_compare`
        - Examples:
          - compare hotel
          - compare hotel feature
          - compare <Hotel A> and <Hotel B>
          - compare all Hotels on list
          - compare <Hotel A> vs. <Hotel B>
          - compare <Hotel A> vs. <Hotel B>

Code Explanation

  1. The first obvious question is why we have a PresentStep? Why not put the same prompt in the ExploreStep . This suggestion is indeed workable and it was how we implemented in the beginning.
  2. However, in some LLM such as gemini-2.0-flash, sometimes it hallucinates especially after a user found a list of hotels, change search criterial to give a different list, but LLM use the original list from the context window.
  3. Separating the presentation into it's own step with his own memory will eliminate the hallucination problem. In order not to leave old memories from previous search, we reset the memory for this step by doing:
Tip
 protected onActivate() {
    this.eraseMemory();
 }
  1. Whenever a memory is reset, you also feed the first user message into the PresentStep
  public onCrossStep(_langMessage: MessageTypes): MessageTypes {
    return new HumanMessageExt(this, 'What hotels choice I have');
  }
  1. This step has three tools
  • chosen_hotel, this will simulate choosing a hotel from the list and make a reservation, then end the session.
  • search_again, this will pass the user's new search message and re-direct to the previous ExploreStep, despite the fact that both ExploreStep and PresentStep do not share the same memory because of deliberate isolation to reduce hallucination.
Tip
return { step: ExploreStep, message: this.getLastMessage() };
  • go_compare, this step use a different technique to pass the found hotel list to the CompareStep, when transition to it.
Tip
return {
     step: CompareStep,
     state: {
       available_hotel: strAvailableHotel,
     },
   };
Warning

Passing a user message(context) when transition to a new step is different than passing a backend information to a new step. This is illustrated in both tool handlers above.


Full step code:

const PresentPrompt = Prompt.file('prompt/present.md');
export class PresentStep extends Step {
  constructor(flow: Flow, isActive?: boolean) {
    super(PresentStep, flow, isActive);
  }

  protected onActivate(flag: boolean) {
    if (flag) {
      this.eraseMemory();
    }
  }

  public onCrossStep(_langMessage: MessageTypes): MessageTypes {
    return new HumanMessageExt(this, 'What hotels choice I have');
  }

  public getPrompt(): string {
    const hotelFoundInfo = this.getState('hotelFound') as SearchHotelEntry;

    const prompt = Prompt.replace(PresentPrompt, {
      HOTEL_FOUND_INFO: JSON.stringify(hotelFoundInfo),
    });

    return prompt;
  }

  public defineTool(): ToolType[] {
    return [
      {
        name: 'chosen_hotel',
        description: 'Capture user choice of hotel',
        schema: z.object({
          hotelName: z.string().describe('Hotel name chosen'),
        }),
      },
      {
        name: 'search_again',
        description: 'User request to re-run the search hotel again',
        schema: z.object({
          isSearch: z.boolean().describe('run the search'),
        }),
      },
      {
        name: 'go_compare',
        description: 'User request compare hotel',
        schema: z.object({
          isCompare: z.boolean().describe('go compare the hotels'),
        }),
      },
    ];
  }
  public getTool(): string[] {
    return ['chosen_hotel', 'search_again', 'go_compare'];
  }

  protected async chosen_hotel(tool: ToolCall): Promise<ToolResponseType> {
    this.saveState({ hotel: tool.args?.hotelName });
    const msg = `Tell user hotel is booked with confirmation #:${this.generateConfirmationNumber()}. Thank the user for choosing Hilton, you MUST NOT talk other things!`;
    return { step: EndStep, prompt: msg };
  }

  protected async search_again(_tool: ToolCall): Promise<ToolResponseType> {
    //forward last message to ExploreStep
    return { step: ExploreStep, message: this.getLastMessage() };
  }

  protected async go_compare(_tool: ToolCall): Promise<ToolResponseType> {
    const availableHotel = this.flow.getStepState(
      PresentStep,
      'hotelFound',
    ) as [];

    const strAvailableHotel = availableHotel.map((entry) => {
      return entry['hotelName'];
    }) as string[];

    return {
      step: CompareStep,
      state: {
        available_hotel: strAvailableHotel,
      },
    };
  }

  private generateConfirmationNumber(): number {
    return Math.floor(100000 + Math.random() * 900000);
  }
}

Wrapping up

Next, we will look at a CompareStep where a use can compare attributes of found hotel.