-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Enhance API, logging, and Azure integration
- Integrated Azure Key Vault and initialized new Web API project - Refactored activities and updated configuration settings - Enhanced Azure AI Chat integration, updated DI, and improved chat ID usage - Improved API integration and configuration - Enhanced chat completion service handling and tests - Translated and restructured prompts; updated settings for better flexibility - Added logging in services and tests; updated package references - Adjusted log levels, refactored methods, and added necessary directives - Updated model handling in PromptExecutionSettings for better activity support
- Loading branch information
1 parent
e69e88e
commit 61fdd42
Showing
28 changed files
with
700 additions
and
38 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
using Azure.Identity; | ||
using Azure.Security.KeyVault.Secrets; | ||
|
||
namespace SemanticFlow.DemoWebApi; | ||
|
||
public class AzureKeyVaultHelper(string keyVaultUrl) | ||
{ | ||
private readonly SecretClient _secretClient = new(new Uri(keyVaultUrl), new DefaultAzureCredential()); | ||
|
||
public async Task<string> GetSecretAsync(string secretName) | ||
{ | ||
KeyVaultSecret secret = await _secretClient.GetSecretAsync(secretName); | ||
return secret.Value; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
using Microsoft.SemanticKernel; | ||
using OllamaApiFacade.Extensions; | ||
using SemanticFlow.DemoWebApi; | ||
using SemanticFlow.DemoWebApi.Workflow; | ||
using SemanticFlow.Extensions; | ||
using SemanticFlow.Services; | ||
|
||
var builder = WebApplication.CreateBuilder(args) | ||
.ConfigureAsLocalOllamaApi(); | ||
|
||
var configuration = builder.Configuration; | ||
var keyVaultName = configuration["KeyVault:Name"]; | ||
var keyVaultUrl = $"https://{keyVaultName}.vault.azure.net"; | ||
var azureKeyVaultHelper = new AzureKeyVaultHelper(keyVaultUrl); | ||
var azureOpenAiApiKey = await azureKeyVaultHelper.GetSecretAsync("AZURE-OPENAI-API-KEY"); | ||
var azureOpenAiEndpoint = configuration["AzureOpenAI:Endpoint"]; | ||
var azureOpenAiDeploymentNameGpt4 = configuration["AzureOpenAI:DeploymentNameGpt4"]; | ||
var azureOpenAiDeploymentNameGpt35 = configuration["AzureOpenAI:DeploymentNameGpt35"]; | ||
|
||
builder.Services.AddProxyForDebug().AddKernel() | ||
.AddAzureOpenAIChatCompletion(azureOpenAiDeploymentNameGpt4, azureOpenAiEndpoint, azureOpenAiApiKey, modelId: "gpt-4") | ||
.AddAzureOpenAIChatCompletion(azureOpenAiDeploymentNameGpt35, azureOpenAiEndpoint, azureOpenAiApiKey, modelId: "gpt-35-turbo"); | ||
|
||
builder.Services.AddKernelWorkflow() | ||
.StartWith<CustomerIdentificationActivity>() | ||
.Then<MenuSelectionActivity>() | ||
.Then<PaymentProcessingActivity>() | ||
.EndsWith<OrderConfirmationActivity>(); | ||
|
||
var app = builder.Build(); | ||
|
||
app.MapPostApiChat(async (chatRequest, chatCompletionService, httpContext, kernel) => | ||
{ | ||
|
||
// TODO: Noch in die Ollama API Facade doku aufnehmen: https://openwebui.com/f/gregorbiswanger/ollama_api_facade_metadata/ | ||
string chatId = chatRequest.ChatId ?? string.Empty; | ||
|
||
var workflowService = kernel.GetRequiredService<WorkflowService>(); | ||
var currentActivity = workflowService.GetCurrentActivity(chatId, kernel); | ||
|
||
var systemPrompt = currentActivity.SystemPrompt + " ### " + | ||
workflowService.WorkflowState.DataFrom(chatId).ToPromptString(); | ||
|
||
var chatHistory = chatRequest.ToChatHistory(systemPrompt); | ||
|
||
var chatCompletion = kernel.GetChatCompletionForActivity(currentActivity); | ||
var chatMessageContents = await chatCompletion.GetChatMessageContentsAsync(chatHistory, currentActivity.PromptExecutionSettings, kernel); | ||
await chatMessageContents.First().StreamToResponseAsync(httpContext.Response); | ||
}); | ||
|
||
app.Run(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
{ | ||
"$schema": "http://json.schemastore.org/launchsettings.json", | ||
"iisSettings": { | ||
"windowsAuthentication": false, | ||
"anonymousAuthentication": true, | ||
"iisExpress": { | ||
"applicationUrl": "http://localhost:62527", | ||
"sslPort": 0 | ||
} | ||
}, | ||
"profiles": { | ||
"http": { | ||
"commandName": "Project", | ||
"dotnetRunMessages": true, | ||
"launchBrowser": true, | ||
"launchUrl": "weatherforecast", | ||
"applicationUrl": "http://localhost:5171", | ||
"environmentVariables": { | ||
"ASPNETCORE_ENVIRONMENT": "Development" | ||
} | ||
}, | ||
"IIS Express": { | ||
"commandName": "IISExpress", | ||
"launchBrowser": true, | ||
"launchUrl": "weatherforecast", | ||
"environmentVariables": { | ||
"ASPNETCORE_ENVIRONMENT": "Development" | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
<Project Sdk="Microsoft.NET.Sdk.Web"> | ||
|
||
<PropertyGroup> | ||
<TargetFramework>net8.0</TargetFramework> | ||
<Nullable>enable</Nullable> | ||
<ImplicitUsings>enable</ImplicitUsings> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="Azure.Identity" Version="1.13.1" /> | ||
<PackageReference Include="Azure.Security.KeyVault.Secrets" Version="4.7.0" /> | ||
<PackageReference Include="Microsoft.SemanticKernel" Version="1.31.0" /> | ||
<PackageReference Include="OllamaApiFacade" Version="1.0.6" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<ProjectReference Include="..\src\SemanticFlow\SemanticFlow.csproj" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<None Update="Workflow\CustomerIdentificationActivity.SystemPrompt.txt"> | ||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> | ||
</None> | ||
<None Update="Workflow\OrderConfirmationActivity.SystemPrompt.txt"> | ||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> | ||
</None> | ||
<None Update="Workflow\PaymentProcessingActivity.SystemPrompt.txt"> | ||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> | ||
</None> | ||
<None Update="Workflow\MenuSelectionActivity.SystemPrompt.txt"> | ||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> | ||
</None> | ||
</ItemGroup> | ||
|
||
</Project> |
31 changes: 31 additions & 0 deletions
31
SemanticFlow.DemoWebApi/Workflow/CustomerIdentificationActivity.SystemPrompt.txt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
**You are Leonardo**, an AI-powered, friendly, and humorous assistant for the pizzeria *La Bella Pizza*. Your task is to guide the customer step-by-step through the process of collecting all necessary information for a successful pizza delivery. | ||
|
||
### **Your Objectives:** | ||
1. Collect the customer�s **full name**, ensuring it includes both first and last names. | ||
2. Gather the customer�s **complete address**, including street, house number, postal code, and city. | ||
3. Request the customer�s **phone number** for contact purposes. | ||
|
||
### **Important:** | ||
- Actively ensure all required information is complete. If any detail is missing (e.g., last name, street number), politely and specifically request it. | ||
- Avoid open-ended questions. Use direct prompts like, "Please provide your full name," and follow up if any part of the information is incomplete. | ||
- Lead the conversation with clear and structured instructions to guide the customer through the data collection process. | ||
- Repeat all collected data at the end and confirm its accuracy with the customer. | ||
- If the customer confirms the data, call the **CustomerDataApproved** function. | ||
|
||
### **Tone of Voice:** | ||
- Friendly, humorous, and lighthearted, but always professional. | ||
- Use a conversational style that keeps the customer engaged and ensures clarity. | ||
|
||
### **Examples of Dialogue:** | ||
- **Greeting:** "Ciao! I�m Leonardo from La Bella Pizza � here to make sure your pizza gets to you faster than you can say 'extra mozzarella!'" | ||
- **Asking for the full name:** | ||
- First attempt: "Let�s start with your full name � first and last, please. I need to know who I�m delivering this masterpiece to!" | ||
- If only the first name is provided: "Thanks, Gregor! And your last name, please? Just so I can put a proper name on this order." | ||
- **Asking for the address:** "Awesome! Now, where�s this delicious pizza headed? I�ll need your street, house number, postal code, and city � the full details." | ||
- **Asking for the phone number:** "Great! Last step � could I have your phone number? This is just in case we need to call about your order. Don�t worry, I won�t spam you!" | ||
- **Confirmation:** "Perfect! Let�s check: Your full name is [Name], the delivery address is [Address], and your phone number is [Phone Number]. Is everything correct?" | ||
|
||
### **Ensuring Completeness:** | ||
- Actively verify that both first and last names are collected: | ||
- If the last name is missing, say: "It looks like I only have your first name. Could you please provide your last name as well?" | ||
- Confirm each piece of information as you go and ask for clarifications if needed. |
35 changes: 35 additions & 0 deletions
35
SemanticFlow.DemoWebApi/Workflow/CustomerIdentificationActivity.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
using System.ComponentModel; | ||
using Microsoft.SemanticKernel; | ||
using Microsoft.SemanticKernel.Connectors.AzureOpenAI; | ||
using OllamaApiFacade.Extensions; | ||
using SemanticFlow.DemoWebApi.Workflow.DTOs; | ||
using SemanticFlow.Interfaces; | ||
using SemanticFlow.Services; | ||
|
||
namespace SemanticFlow.DemoWebApi.Workflow; | ||
|
||
public class CustomerIdentificationActivity(IHttpContextAccessor httpContextAccessor, | ||
WorkflowService workflowService, | ||
Kernel kernel) : IActivity | ||
{ | ||
public string SystemPrompt { get; set; } = File.ReadAllText("./Workflow/CustomerIdentificationActivity.SystemPrompt.txt"); | ||
|
||
public PromptExecutionSettings PromptExecutionSettings { get; set; } = new AzureOpenAIPromptExecutionSettings | ||
{ | ||
ModelId = "gpt-35-turbo", | ||
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto(), | ||
Temperature = 0.7, | ||
MaxTokens = 256 | ||
}; | ||
|
||
[KernelFunction] | ||
[Description("Confirms the customer's data for the pizza delivery.")] | ||
public string CustomerDataApproved(Customer customer) | ||
{ | ||
var chatId = httpContextAccessor.HttpContext?.GetChatRequest().ChatId; | ||
var nextActivity = workflowService.CompleteActivity(chatId, customer, kernel); | ||
|
||
return @$"{nextActivity.SystemPrompt} ### | ||
{workflowService.WorkflowState.DataFrom(chatId).ToPromptString()}"; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
using System.ComponentModel.DataAnnotations; | ||
|
||
namespace SemanticFlow.DemoWebApi.Workflow.DTOs; | ||
|
||
public class Customer | ||
{ | ||
[Required] | ||
[Display(Description = "The full name of the customer.")] | ||
public string FullName { get; set; } | ||
|
||
[Required] | ||
[Display(Description = "The full address of the customer including street, house number, postal code, and city.")] | ||
public string Address { get; set; } | ||
|
||
[Required] | ||
[Display(Description = "The customer's phone number for contact in case of issues.")] | ||
public string PhoneNumber { get; set; } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
namespace SemanticFlow.DemoWebApi.Workflow.DTOs; | ||
|
||
public class MenuItem | ||
{ | ||
public string Title { get; set; } | ||
Check warning on line 5 in SemanticFlow.DemoWebApi/Workflow/DTOs/MenuItem.cs GitHub Actions / build
|
||
public string Ingredients { get; set; } | ||
public double Price { get; set; } | ||
} |
18 changes: 18 additions & 0 deletions
18
SemanticFlow.DemoWebApi/Workflow/MenuSelectionActivity.SystemPrompt.txt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
**You are Leonardo, the assistant for La Bella Pizza.** | ||
The customer has already been successfully identified, and all necessary contact details are on file. | ||
Your sole task now is to take the customer's order. | ||
|
||
### **Your Objectives:** | ||
1. Record the customer�s desired items (dishes, drinks, etc.). | ||
2. Verify the availability of each requested item using the function **CheckMenuAvailability**. | ||
3. Summarize the entire order at the end and ask if that�s everything the customer would like. | ||
4. Once the customer confirms their order, call the **MenuSelectionApproved** function with the complete order details. | ||
|
||
### **Important Notes:** | ||
- Your only task is to accurately take and confirm the customer�s order. | ||
- If an item is unavailable, politely apologize and offer suitable alternatives. | ||
- Always repeat the order clearly at the end and ask for confirmation. | ||
- Always maintain the language and form of address (e.g., informal "you" or formal "you") used earlier in the conversation. Adapt to the customer�s preferred communication style as established in the dialogue so far. | ||
|
||
### **Example for Starting the Conversation:** | ||
"Alright, let�s get started with your order! What can I note down for you today?" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
using System.ComponentModel; | ||
using System.Text.Json; | ||
using Microsoft.SemanticKernel; | ||
using Microsoft.SemanticKernel.Connectors.AzureOpenAI; | ||
using OllamaApiFacade.Extensions; | ||
using SemanticFlow.DemoWebApi.Workflow.DTOs; | ||
using SemanticFlow.Interfaces; | ||
using SemanticFlow.Services; | ||
|
||
namespace SemanticFlow.DemoWebApi.Workflow; | ||
|
||
public class MenuSelectionActivity(IHttpContextAccessor httpContextAccessor, | ||
WorkflowService workflowService, | ||
Kernel kernel) : IActivity | ||
{ | ||
private static readonly List<MenuItem> MenuItems = | ||
[ | ||
new() { Title = "Margherita", Ingredients = "Tomato, Mozzarella, Basil", Price = 8.50 }, | ||
new() { Title = "Pepperoni", Ingredients = "Tomato, Mozzarella, Pepperoni", Price = 9.50 }, | ||
new() | ||
{ | ||
Title = "Quattro Stagioni", Ingredients = "Tomato, Mozzarella, Mushrooms, Ham, Artichokes, Olives", | ||
Price = 11.00 | ||
}, | ||
new() { Title = "Marinara", Ingredients = "Tomato, Garlic, Oregano", Price = 7.00 }, | ||
new() { Title = "Carbonara", Ingredients = "Mozzarella, Eggs, Bacon, Black Pepper", Price = 10.00 } | ||
]; | ||
|
||
public string SystemPrompt { get; set; } = File.ReadAllText("./Workflow/MenuSelectionActivity.SystemPrompt.txt"); | ||
public PromptExecutionSettings PromptExecutionSettings { get; set; } = new AzureOpenAIPromptExecutionSettings | ||
{ | ||
ModelId = "gpt-4", | ||
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto(), | ||
Temperature = 0.1, | ||
MaxTokens = 256 | ||
}; | ||
|
||
[KernelFunction, Description("Checks if a specific menu item is available in the restaurant's menu, including handling unknown words.")] | ||
[return: Description("Returns a confirmation message if the menu item is available, or a suggestion for alternatives if it is not.")] | ||
public string CheckMenuAvailability( | ||
[Description("The name of the menu item that the customer wants to order.")] string menu) | ||
{ | ||
var menuItem = MenuItems.FirstOrDefault(item => item.Title.Contains(menu, StringComparison.OrdinalIgnoreCase) || | ||
menu.Contains(item.Title, StringComparison.OrdinalIgnoreCase)); | ||
if (menuItem != null) | ||
{ | ||
var menuItemJson = JsonSerializer.Serialize(menuItem); | ||
|
||
return menuItemJson; | ||
} | ||
|
||
return "The requested menu item is not available. Please choose from our available options."; | ||
} | ||
|
||
[KernelFunction, Description("Finalizes the customer's order by confirming their complete menu selection.")] | ||
[return: Description("A confirmation message indicating that the customer's menu selection has been approved.")] | ||
public string MenuSelectionApproved( | ||
[Description("The full menu selection as confirmed by the customer, including all items they want to order.")] string fullMenuSelection) | ||
{ | ||
var chatId = httpContextAccessor.HttpContext?.GetChatRequest().ChatId; | ||
var nextActivity = workflowService.CompleteActivity(chatId, fullMenuSelection, kernel); | ||
|
||
return @$"{nextActivity.SystemPrompt} ### | ||
{workflowService.WorkflowState.DataFrom(chatId).ToPromptString()}"; | ||
} | ||
} |
30 changes: 30 additions & 0 deletions
30
SemanticFlow.DemoWebApi/Workflow/OrderConfirmationActivity.SystemPrompt.txt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
**You are Leonardo, the assistant for La Bella Pizza.** | ||
In this final step, you have the customer�s complete order and all relevant details (name, address, phone number, order, payment method, and total cost). | ||
Your task is to summarize this information and present it to the customer for confirmation. | ||
|
||
### **Your Objectives:** | ||
1. Clearly and completely summarize the following details for the customer: | ||
- The customer�s name. | ||
- The delivery address. | ||
- The phone number. | ||
- The detailed order (menu items). | ||
- The chosen payment method. | ||
- The total cost. | ||
2. Politely ask the customer if all the details are correct. | ||
3. If the customer confirms, thank them for their order and call the **OrderApproved** function. | ||
4. Pass all the data as a JSON string to the **OrderApproved** function. | ||
|
||
### **Important Notes:** | ||
- Your only task is to summarize the order and collect the customer�s confirmation. | ||
- If the customer wishes to make changes, kindly inform them that changes must be made in the earlier steps. | ||
- Be friendly, attentive, and thank the customer warmly at the end. | ||
- Always maintain the language and form of address (e.g., informal "you" or formal "you") used earlier in the conversation. Adapt to the customer�s preferred communication style as established in the dialogue so far. | ||
|
||
### **Example of How to Proceed:** | ||
- _"Before we proceed, here is your order summary: Your name is Max Mustermann, the delivery address is Hauptstra�e 123, 12345 Berlin. Your order includes one large pepperoni pizza and a cola. The chosen payment method is cash, and the total cost is �25.50."_ | ||
- Question: _"Is everything correct?"_ | ||
- If the customer confirms, call the **OrderApproved** function: | ||
_"Thank you for your order at La Bella Pizza! We�ll make sure your pizza gets to you quickly."_ | ||
|
||
### **Example Opening:** | ||
_"Hello! Let me summarize your order to make sure everything is perfect!"_ |
38 changes: 38 additions & 0 deletions
38
SemanticFlow.DemoWebApi/Workflow/OrderConfirmationActivity.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
using System.ComponentModel; | ||
using Microsoft.SemanticKernel; | ||
using Microsoft.SemanticKernel.Connectors.AzureOpenAI; | ||
using OllamaApiFacade.Extensions; | ||
using SemanticFlow.Interfaces; | ||
using SemanticFlow.Services; | ||
|
||
namespace SemanticFlow.DemoWebApi.Workflow; | ||
|
||
public class OrderConfirmationActivity(IHttpContextAccessor httpContextAccessor, | ||
WorkflowService workflowService, | ||
Kernel kernel) : IActivity | ||
{ | ||
public string SystemPrompt { get; set; } = | ||
File.ReadAllText("./Workflow/OrderConfirmationActivity.SystemPrompt.txt"); | ||
|
||
public PromptExecutionSettings PromptExecutionSettings { get; set; } = new AzureOpenAIPromptExecutionSettings | ||
{ | ||
ModelId = "gpt-35-turbo", | ||
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto(), | ||
Temperature = 0.1, | ||
MaxTokens = 256 | ||
}; | ||
|
||
[KernelFunction, Description("Finalizes the order by confirming all details and sending the complete order for processing.")] | ||
[return: Description("A confirmation message indicating that the customer's order has been approved and is being processed.")] | ||
public string OrderApproved( | ||
[Description("The complete order details as a JSON string, including the customer's name, address, phone number, order items, payment method, and total cost.")] string completeOrder) | ||
{ | ||
var chatId = httpContextAccessor.HttpContext?.GetChatRequest().ChatId; | ||
workflowService.CompleteActivity(chatId, completeOrder, kernel); | ||
|
||
Console.WriteLine("Incoming Order:" + workflowService.WorkflowState.DataFrom(chatId).ToPromptString()); | ||
|
||
return $"Vielen Dank für Ihre Bestellung bei La Bella Pizza! Wir kümmern uns darum, dass Ihre Pizza schnell bei Ihnen ist."; | ||
} | ||
|
||
} |
Oops, something went wrong.