chore: import upstream snapshot with attribution
CodeQL / Analyze (csharp) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:21:23 +08:00
commit b957a53def
5423 changed files with 863745 additions and 0 deletions
@@ -0,0 +1,139 @@
# Restaurant - Demo Application
This sample provides a practical demonstration of how to leverage features from the [Semantic Kernel](https://learn.microsoft.com/en-us/semantic-kernel) to build a console application. Specifically, the application utilizes the [Business Schedule and Booking API](https://www.microsoft.com/en-us/microsoft-365/business/scheduling-and-booking-app) through Microsoft Graph to enable a Large Language Model (LLM) to book restaurant appointments efficiently. This guide will walk you through the necessary steps to integrate these technologies seamlessly.
## Prerequisites
- Python 3.10, 3.11, or 3.12.
- [Microsoft 365 Business License](https://www.microsoft.com/en-us/microsoft-365/business/compare-all-microsoft-365-business-products) to use [Business Schedule and Booking API](https://www.microsoft.com/en-us/microsoft-365/business/scheduling-and-booking-app).
- [Azure Entra Id](https://www.microsoft.com/en-us/security/business/identity-access/microsoft-entra-id) administrator account to register an application and set the necessary credentials and permissions.
### Function Calling Enabled Models
This sample uses function calling capable models and has been tested with the following models:
| Model type | Model name/id | Model version | Supported |
| --------------- | ------------------------- | ------------------: | --------- |
| Chat Completion | gpt-3.5-turbo | 0125 | ✅ |
| Chat Completion | gpt-3.5-turbo | 1106 | ✅ |
| Chat Completion | gpt-3.5-turbo-0613 | 0613 | ✅ |
| Chat Completion | gpt-3.5-turbo-0301 | 0301 | ❌ |
| Chat Completion | gpt-3.5-turbo-16k | 0613 | ✅ |
| Chat Completion | gpt-4 | 0613 | ✅ |
| Chat Completion | gpt-4-0613 | 0613 | ✅ |
| Chat Completion | gpt-4-0314 | 0314 | ❌ |
| Chat Completion | gpt-4-turbo | 2024-04-09 | ✅ |
| Chat Completion | gpt-4-turbo-2024-04-09 | 2024-04-09 | ✅ |
| Chat Completion | gpt-4-turbo-preview | 0125-preview | ✅ |
| Chat Completion | gpt-4-0125-preview | 0125-preview | ✅ |
| Chat Completion | gpt-4-vision-preview | 1106-vision-preview | ✅ |
| Chat Completion | gpt-4-1106-vision-preview | 1106-vision-preview | ✅ |
️ OpenAI Models older than 0613 version do not support function calling.
️ When using Azure OpenAI, ensure that the model name of your deployment matches any of the above supported models names.
## Configuring the sample
Please make sure either your environment variables or your .env file contains the following:
- "BOOKING_SAMPLE_CLIENT_ID"
- "BOOKING_SAMPLE_TENANT_ID"
- "BOOKING_SAMPLE_CLIENT_SECRET"
- "BOOKING_SAMPLE_BUSINESS_ID"
- "BOOKING_SAMPLE_SERVICE_ID"
If wanting to use the `.env` file, you must pass the `env_file_path` parameter with a valid path:
```python
booking_sample_settings = BookingSampleSettings(env_file_path=env_file_path)
```
This will tell Pydantic settings to also load the `.env` file instead of just trying to load environment variables.
### Create an App Registration in Azure Active Directory
1. Go to the [Azure Portal](https://portal.azure.com/).
2. Select the Azure Active Directory service.
3. Select App registrations and click on New registration.
4. Fill in the required fields and click on Register.
5. Copy the Application **(client) Id** for later use.
6. Save Directory **(tenant) Id** for later use..
7. Click on Certificates & secrets and create a new client secret. (Any name and expiration date will work)
8. Copy the **client secret** value for later use.
9. Click on API permissions and add the following permissions:
- Microsoft Graph
- Application permissions
- BookingsAppointment.ReadWrite.All
- Delegated permissions
- OpenId permissions
- offline_access
- profile
- openid
### Create Or Use a Booking Service and Business
1. Go to the [Bookings Homepage](https://outlook.office.com/bookings) website.
2. Create a new Booking Page and add a Service to the Booking (Skip if you don't ).
3. Access [Graph Explorer](https://developer.microsoft.com/en-us/graph/graph-explorer)
4. Run the following query to get the Booking Business Id:
```http
GET https://graph.microsoft.com/v1.0/solutions/bookingBusinesses
```
5. Copy the **Booking Business Id** for later use.
6. Run the following query and replace it with your **Booking Business Id** to get the Booking Service Id
```http
GET https://graph.microsoft.com/v1.0/solutions/bookingBusinesses/{bookingBusiness-id}/services
```
7. Copy the **Booking Service Id** for later use.
## Running the sample
Either run the sample in an IDE, like VSCode, or via the command line.
### Example of a conversation
> **User** > Please book a restaurant for me.
> **Assistant** > Sure, I can help you with that. Could you please provide me with the following information:
>
> 1. Name of the restaurant you would like to book.
> 2. Date and time of the reservation.
> 3. Number of people in your party.
> 4. Your name.
> 5. Your email address.
> 6. Your phone number.
>
> Once I have all this information, I will proceed with the booking.
> **User** > I would like to book a table at the restaurant "The French Laundry" on the 15th of March at 7:00 PM for 4 people. My name is John Doe, my email address is john@internet.com, and my phone number is 123-456-7890.
> **System** > Do you want to book a table at The French Laundry on 15/03/2023 19:00:00 for 4 people?
> **System** > Please confirm by typing 'yes' or 'no'.
> **User** > yes
> **Assistant** > Great! I have successfully booked a table for you at "The French Laundry" on the 15th of March at 7:00 PM for 4 people.
>
> Your reservation has been made under the name John Doe.
>
> If you have any further questions or need to make any changes to your reservation, please let me know.
> **User** > List my current bookings
> **Assistant** > You currently have one booking:
>
> - Restaurant: The French Laundry
> - Date and Time: 15th of March at 7:00 PM
> - Party Size: 4 people
>
> If you need any further assistance or if there's anything else I can help you with, please let me know.
> **User** > Cancel my booking
> **System** > `[Cancelling a reservation for 4 at The French Laundry on 2023-03-15 at 19:00:00]`
> **Assistant** > I have successfully canceled your booking at "The French Laundry" on the 15th of March at 7:00 PM for 4 people.
>
> If you have any other questions or need further assistance, please let me know.
@@ -0,0 +1,45 @@
# Copyright (c) Microsoft. All rights reserved.
from pydantic import SecretStr
from pydantic_settings import BaseSettings
class BookingSampleSettings(BaseSettings):
"""Restaurant Booking Sample settings
The settings are first loaded from environment variables with the prefix 'BOOKING_'. If the
environment variables are not found, the settings can be loaded from a .env file with the
encoding 'utf-8'. If the settings are not found in the .env file, the settings are ignored;
however, validation will fail alerting that the settings are missing.
Required settings for prefix 'BOOKING_' are:
- client_id = The App Registration Client ID (Env var BOOKING_CLIENT_ID)
- tenant_id = The App Registration Tenant ID (Env var BOOKING_TENANT_ID)
- client_secret = The App Registration Client Secret (Env var BOOKING_CLIENT_SECRET)
- business_id = The sample booking service ID (Env var BOOKING_BUSINESS_ID)
- service_id = The sample booking service ID (Env var BOOKING_SERVICE_ID)
For more information on these required settings, please see the sample's README.md file.
"""
env_file_path: str | None = None
client_id: str
tenant_id: str
client_secret: SecretStr
business_id: str
service_id: str
class Config:
env_prefix = "BOOKING_"
env_file = None
env_file_encoding = "utf-8"
extra = "ignore"
case_sensitive = False
@classmethod
def create(cls, **kwargs):
if kwargs.get("env_file_path"):
cls.Config.env_file = kwargs["env_file_path"]
else:
cls.Config.env_file = None
return cls(**kwargs)
@@ -0,0 +1,3 @@
# Copyright (c) Microsoft. All rights reserved.
# intentionally left empty
@@ -0,0 +1,140 @@
# Copyright (c) Microsoft. All rights reserved.
from datetime import datetime, timedelta
from typing import Annotated
from msgraph import GraphServiceClient
from msgraph.generated.models.booking_appointment import BookingAppointment
from msgraph.generated.models.booking_customer_information import BookingCustomerInformation
from msgraph.generated.models.date_time_time_zone import DateTimeTimeZone
from msgraph.generated.models.location import Location
from semantic_kernel.functions.kernel_function_decorator import kernel_function
class BookingsPlugin:
"""A plugin for booking tables at a restaurant."""
def __init__(
self,
graph_client: GraphServiceClient,
booking_business_id: str,
booking_service_id: str,
customer_timezone: str = "America/Chicago",
):
"""Initializes a new instance of the BookingsPlugin class.
Args:
graph_client (GraphServiceClient): The GraphServiceClient instance.
booking_business_id (str): The ID of the booking business.
service_id (str): The ID of the service.
customer_timezone (str, optional): The timezone of the customer. Defaults to "America/Chicago".
"""
self.graph_client = graph_client
self.booking_business_id = booking_business_id
self.booking_service_id = booking_service_id
self.customer_timezone = customer_timezone
@kernel_function(name="book_table", description="Book a table at a restaurant")
async def book_table(
self,
restaurant: Annotated[str, "The name of the restaurant"],
date_time: Annotated[str, "The time in UTC, formatted as an ISO datetime string, like 2024-09-15T19:00:00"],
party_size: Annotated[int, "The number of people in the party"],
customer_name: Annotated[str, "The name of the customer"],
customer_email: Annotated[str, "The email of the customer"],
customer_phone: Annotated[str, "The phone number of the customer"],
) -> Annotated[str, "The booking appointment ID"]:
"""Book a table at a restaurant.
Args:
restaurant (str): The name of the restaurant.
date_time (datetime): The time in UTC.
party_size (int): The number of people in the party.
customer_name (str): The name of the customer.
customer_email (str): The email of the customer.
customer_phone (str): The phone number of the customer.
Returns:
str: The status of the booking.
"""
print(f"System > Do you want to book a table at {restaurant} on {date_time} for {party_size} people?")
print("System > Please confirm by typing 'yes' or 'no'.")
confirmation = input("User:> ")
if confirmation.lower() != "yes":
return "Booking aborted by the user."
request_body = BookingAppointment(
odata_type="#microsoft.graph.bookingAppointment",
customer_time_zone=self.customer_timezone,
sms_notifications_enabled=False,
start_date_time=DateTimeTimeZone(
odata_type="#microsoft.graph.dateTimeTimeZone",
date_time=date_time,
time_zone="UTC",
),
end_date_time=DateTimeTimeZone(
odata_type="#microsoft.graph.dateTimeTimeZone",
date_time=(datetime.fromisoformat(date_time) + timedelta(hours=2)).isoformat(),
time_zone="UTC",
),
is_location_online=False,
opt_out_of_customer_email=False,
anonymous_join_web_url=None,
service_id=self.booking_service_id,
service_location=Location(
odata_type="#microsoft.graph.location",
display_name=restaurant,
),
maximum_attendees_count=party_size,
filled_attendees_count=party_size,
customers=[
BookingCustomerInformation(
odata_type="#microsoft.graph.bookingCustomerInformation",
name=customer_name,
email_address=customer_email,
phone=customer_phone,
time_zone=self.customer_timezone,
),
],
additional_data={
"price_type@odata_type": "#microsoft.graph.bookingPriceType",
"reminders@odata_type": "#Collection(microsoft.graph.bookingReminder)",
"customers@odata_type": "#Collection(microsoft.graph.bookingCustomerInformation)",
},
)
response = await self.graph_client.solutions.booking_businesses.by_booking_business_id(
self.booking_business_id
).appointments.post(request_body)
return f"Booking successful! Your reservation ID is {response.id}."
@kernel_function(name="list_revervations", description="List all reservations")
async def list_reservations(self) -> Annotated[str, "The list of reservations"]:
"""List the reservations for the booking business."""
appointments = await self.graph_client.solutions.booking_businesses.by_booking_business_id(
self.booking_business_id
).appointments.get()
return "\n".join([
f"{appointment.service_location.display_name} on {appointment.start_date_time.date_time} with id: {appointment.id}" # noqa: E501
for appointment in appointments.value
])
@kernel_function(name="cancel_reservation", description="Cancel a reservation")
async def cancel_reservation(
self,
reservation_id: Annotated[str, "The ID of the reservation"],
restaurant: Annotated[str, "The name of the restaurant"],
date: Annotated[str, "The date of the reservation"],
time: Annotated[str, "The time of the reservation"],
party_size: Annotated[int, "The number of people in the party"],
) -> Annotated[str, "The cancellation status of the reservation"]:
"""Cancel a reservation."""
print(f"System > [Cancelling a reservation for {party_size} at {restaurant} on {date} at {time}]")
_ = (
await self.graph_client.solutions.booking_businesses.by_booking_business_id(self.booking_business_id)
.appointments.by_booking_appointment_id(reservation_id)
.delete()
)
return "Cancellation successful!"
@@ -0,0 +1,109 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
from azure.identity import ClientSecretCredential
from booking_sample_settings import BookingSampleSettings
from bookings_plugin.bookings_plugin import BookingsPlugin
from msgraph import GraphServiceClient
from pydantic import ValidationError
from semantic_kernel.connectors.ai.chat_completion_client_base import ChatCompletionClientBase
from semantic_kernel.connectors.ai.open_ai.prompt_execution_settings.open_ai_prompt_execution_settings import (
OpenAIChatPromptExecutionSettings,
)
from semantic_kernel.connectors.ai.open_ai.services.open_ai_chat_completion import OpenAIChatCompletion
from semantic_kernel.contents.chat_history import ChatHistory
from semantic_kernel.exceptions.service_exceptions import ServiceInitializationError
from semantic_kernel.functions.kernel_arguments import KernelArguments
from semantic_kernel.kernel import Kernel
kernel = Kernel()
service_id = "open_ai"
ai_service = OpenAIChatCompletion(service_id=service_id, ai_model_id="gpt-3.5-turbo")
kernel.add_service(ai_service)
try:
booking_sample_settings = BookingSampleSettings()
except ValidationError as e:
raise ServiceInitializationError("Failed to initialize the booking sample settings.") from e
tenant_id = booking_sample_settings.tenant_id
client_id = booking_sample_settings.client_id
client_secret = booking_sample_settings.client_secret
client_secret_credential = ClientSecretCredential(tenant_id=tenant_id, client_id=client_id, client_secret=client_secret)
graph_client = GraphServiceClient(credentials=client_secret_credential, scopes=["https://graph.microsoft.com/.default"])
booking_business_id = booking_sample_settings.business_id
booking_service_id = booking_sample_settings.service_id
bookings_plugin = BookingsPlugin(
graph_client=graph_client,
booking_business_id=booking_business_id,
booking_service_id=booking_service_id,
)
kernel.add_plugin(bookings_plugin, "BookingsPlugin")
chat_function = kernel.add_function(
plugin_name="ChatBot",
function_name="Chat",
prompt="{{$chat_history}}{{$user_input}}",
template_format="semantic-kernel",
)
settings: OpenAIChatPromptExecutionSettings = kernel.get_prompt_execution_settings_from_service_id(
service_id, ChatCompletionClientBase
)
settings.max_tokens = 2000
settings.temperature = 0.1
settings.top_p = 0.8
settings.function_choice_behavior.Auto(filters={"exclude_plugin": ["ChatBot"]})
chat_history = ChatHistory(
system_message="When responding to the user's request to book a table, include the reservation ID."
)
async def chat() -> bool:
try:
user_input = input("User:> ")
except KeyboardInterrupt:
print("\n\nExiting chat...")
return False
except EOFError:
print("\n\nExiting chat...")
return False
if user_input == "exit":
print("\n\nExiting chat...")
return False
# Note the reservation returned contains an ID. That ID can be used to cancel the reservation,
# when the bookings API supports it.
answer = await kernel.invoke(
chat_function, KernelArguments(settings=settings, user_input=user_input, chat_history=chat_history)
)
chat_history.add_user_message(user_input)
chat_history.add_assistant_message(str(answer))
print(f"Assistant:> {answer}")
return True
async def main() -> None:
chatting = True
print(
"Welcome to your Restaurant Booking Assistant.\
\n Type 'exit' to exit.\
\n Please enter the following information to book a table: the restaurant, the date and time, \
\n the number of people, your name, phone, and email. You may ask me for help booking a table, \
\n listing reservations, or cancelling a reservation. When cancelling please provide the reservation ID."
)
while chatting:
chatting = await chat()
if __name__ == "__main__":
asyncio.run(main())