Lesson 8: Compare Step
This step can compare two or more hotels on these attributes: price,room type,amenities,distance.
Screen shots
The screenshots of the comparisons are as follows:




Prompt Explanation
- This prompt use a technique to ask LLM to maintain its mini states within the conversation. This will simplify tracking these states at code level. However, the trade-off is LLM may not 100% reliably observing all the rules.
- We have to prompt variables that are obtain from previous steps and use it as additional context:
{{ChosenHotels}}and{{AvailableHotels}} States 2 and 3are house keeping.State 4imposes some logics into LLM for this step.
Full step prompt
## State Machine Prompt for Hotel Selection and Comparison
### Variable
- ** `ChosenHotels` = {{ChosenHotels}}
- ** `AvailableHotels` = {{AvailableHotels}}
### Instructions
- ** If user wants to compare hotels, go to `State 1`
- ** If user chooses a feature, hotels are selected in `ChosenHotels`, to to `State 2`
### State 1: Present List of Hotels and Collect Selection
- **Examine Variable `ChosenHotels`**
- if already set, go to `State 2`
- **Responsibility:**
- Display a list of available hotels show with number bullets from `AvailableHotels`
- Request the user to select one or more hotels from the list for comparison.
- Collect and confirm the user's selection(s).
- Action Example: `Here are some hotels available for you, please choose one or more hotels by typing the name(s) or number(s) from the list`:
1. Hotel A
2. Hotel B
3. Hotel C
4. Hotel D
### State 2: Collect Hotel Feature Selection
- **Responsibility:**
- Once a hotel has been selected, prompt the user to provide ONLY one feature they want to compare for the selected hotels.
- Provide a list of features for the user to choose from:
- Action Example:
- `Great choice! Now, let's get more details. Please select one of the following features to compare for your hotel`:
1. `price`
2. `room type`
3. `amenities`
4. `distance`
- Feature Mapping, below is a map of what will be set the tool `generate_comparison` property `feature`
1. `price` ==> `price`
2. `room type` ==> `roomType`
3. `amenities` ==> `amenities`
4. `distance` ==> `distance`
### State 3: Confirm Selections and Offer Re-selection
- **Responsibility:**
- Confirm that both the hotel(s) and the selected feature have been gathered successfully.
- Provide the user with an option
- to proceed with the comparison
- or re-select a different set hotels for comparison
- or re-select a different feature for comparison
- or resume to hotel booking
- Action Example:
- Here is what you’ve selected:
- Hotel(s): [Selected Hotels]
- Feature: [Selected Feature]
- Would you like to proceed with this comparison or:
1. Re-select a different hotel set
2. Re-select a different feature
3. Proceed with the comparison
4. Exit comparison, resume hotel booking
### State 4: Generate Comparison
- **Responsibility:**
- If the user chooses to proceed to compare, call the tool `generate_comparison`, set property `hotels` to selected hotel and property `feature` to selected feature.
- If user chooses to re-select hotels, return to `State 1`.
- If user chooses to re-select a feature, go to `State 2`.
- If the user chose to exit comparison, resume or go back to the hotel booking, call the `resume_booking` tool
Code Explanation
- We provides two tools one to capture the comparison selection and generate the comparison chart, another to resume to the
PresentStepto finish the booking. - in
generate_comparison, a markdown formatted char is generated base on the comparison criteria. - We also read and save the internal step state.
const hotelAvailable = this.flow.getStepState(
PresentStep,
'hotelFound',
) as object[];
---
this.saveState({ chosen_hotels: finalHotels }, true);
- Last, we output the markdown result directly to the user without invoking a second LLM call by doing this:
const table = GenChart.getChart(finalHotels);
const msg = new AiMessageEx(this, table, { direct: true });
return { step: CompareStep, message: msg };
the extra payload direct: true is specifying the last message to be directly displayed to a user.
Full step code:
const ComparePrompt = Prompt.file('prompt/compare.md');
eexport class CompareStep extends Step {
constructor(flow: Flow, isActive?: boolean) {
super(CompareStep, flow, isActive);
}
public async run(message: string): Promise<MessageContent> {
return await super.run(message);
}
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 chosen_hotels = (this.getState('chosen_hotels') as []) ?? [];
const available_hotel = this.getState(`available_hotel`) ?? [];
const hotels = chosen_hotels.map((entry) => {
return { hotelName: entry['hotelName'] };
});
const prompt = Prompt.replace(ComparePrompt, {
ChosenHotels: JSON.stringify(hotels),
AvailableHotels: JSON.stringify(available_hotel),
});
return prompt;
}
public defineTool(): ToolType[] {
return [
{
name: 'generate_comparison',
description: 'Call to generate comparison',
schema: z.object({
hotels: z.array(z.string()).describe('Hotels name chosen'),
feature: z.string().describe('chosen feature'),
}),
},
{
name: 'resume_booking',
description: 'Resume to booking',
schema: z.object({
isResumed: z.boolean().describe('is resume booking'),
}),
},
];
}
public getTool(): string[] {
return ['generate_comparison', 'resume_booking'];
}
protected async generate_comparison(
tool: ToolCall,
): Promise<ToolResponseType> {
//perform a hotel search
let chosenHotels;
try {
chosenHotels = JSON.parse(tool.args?.hotels);
} catch (_e) {
chosenHotels = tool.args?.hotels;
}
const feature = tool.args?.feature;
// console.log(`feature: ${feature}`);
//find the full hotel doc from DB
const fetchHotels = (await PricingEngine.fetchHotels(
chosenHotels,
)) as object[];
//merge the price into the chosenHotels JSON
const hotelAvailable = this.flow.getStepState(
PresentStep,
'hotelFound',
) as object[];
let finalHotels = fetchHotels.map((doc) => {
for (const aHotel of hotelAvailable) {
if (aHotel['hotelName'] === doc['hotelName']) {
const myFeatures = {};
merge(myFeatures, { hotelName: doc['hotelName'] });
if (feature === 'amenities') {
merge(myFeatures, GenChart.flattenObject(doc['amenities']));
} else if (feature === 'roomType') {
merge(myFeatures, GenChart.transRoomType(doc['roomType']));
} else if (feature === 'distance') {
merge(myFeatures, { cityCenter: `${doc['cityCenter']} mi` });
merge(myFeatures, { airport: `${doc['airport']} mi` });
} else if (feature === 'price') {
const tree = this.flow.getStepState(ExploreStep, 'json');
const dates = tree['cDateArray'];
const prices = aHotel['prices'];
const jObject = GenChart.createJsonObject(dates, prices);
merge(myFeatures, jObject);
merge(myFeatures, {
total: GenChart.formatCurrency(aHotel['total']),
});
}
return {
...myFeatures,
};
}
}
});
if (feature === 'amenities' || feature === 'roomType') {
finalHotels = GenChart.transAmenities(finalHotels);
}
this.saveState({ chosen_hotels: finalHotels });
//produce a comparison chart
const table = GenChart.getChart(finalHotels);
const msg = new AiMessageEx(this, table, { direct: true });
return { step: CompareStep, message: msg };
}
protected async resume_booking(_tool: ToolCall): Promise<ToolResponseType> {
return PresentStep;
}
Wrapping up
You've made it to this lesson, it is packed with a lot of new information. let's conclude ...