diff --git a/demos/module-04/demo04/AvailableRoom.cs b/demos/module-04/demo04/AvailableRoom.cs new file mode 100644 index 0000000..3c3df57 --- /dev/null +++ b/demos/module-04/demo04/AvailableRoom.cs @@ -0,0 +1,15 @@ +namespace modulerag; + +public static partial class HotelBookingFunctions +{ + public class AvailableRoom + { + public int RoomId { get; set; } + public string RoomType { get; set; } + public decimal PricePerNight { get; set; } + public string HotelName { get; set; } + public int NumberOfAdultsAllowedInRoom { get; set; } + public string Amenities { get; set; } + public string City { get; set; } + } +} diff --git a/demos/module-04/demo04/ChatWithAgent.cs b/demos/module-04/demo04/ChatWithAgent.cs index dcfb9ab..69e338e 100644 --- a/demos/module-04/demo04/ChatWithAgent.cs +++ b/demos/module-04/demo04/ChatWithAgent.cs @@ -1,14 +1,11 @@ -using Microsoft.Extensions.Configuration; -using Microsoft.SemanticKernel; -using Microsoft.SemanticKernel.Agents; -using Microsoft.SemanticKernel.ChatCompletion; -using Microsoft.SemanticKernel.Connectors.OpenAI; +using Microsoft.Agents.AI; +using Microsoft.Extensions.AI; namespace modulerag; -internal class ChatWithAgent +internal class ChatWithAgent(IChatClient chatClient) { - public async Task LetAgentFindRideAndHotel(IConfiguration config) + public async Task LetAgentFindRideAndHotel() { var question = """ @@ -16,18 +13,19 @@ public async Task LetAgentFindRideAndHotel(IConfiguration config) """; Console.WriteLine("******** Create the ride agent ***********"); - var rideAgent = CreateTransportationAgent(config); - rideAgent.Kernel.ImportPluginFromType(); + var rideAgent = CreateTransportationAgent(); Console.WriteLine("******** Create the hotel agent ***********"); - var hotelAgent = HotelBookingAgent.CreateChatCompletionAgent(config); - hotelAgent.Kernel.ImportPluginFromType(); + var hotelAgent = HotelBookingAgent.CreateChatCompletionAgent(chatClient); // create the chat history that starts the agent thread var history = new ChatHistory(); history.AddMessage(AuthorRole.User, question); - AgentThread thread = new ChatHistoryAgentThread(history); + // TODO how to deal with multiple agents? AgentSession is created for a specific agent. + // Sessions are agent/service-specific. + // Reusing a session with a different agent configuration or provider can lead to invalid context. + AgentSession thread = new ChatHistoryAgentThread(history); await RunUntilGoalReached(hotelAgent, thread); Console.WriteLine("******** hotel agent done ***********"); @@ -36,68 +34,56 @@ public async Task LetAgentFindRideAndHotel(IConfiguration config) Console.WriteLine("******** Done ***********"); } - private async Task RunUntilGoalReached(ChatCompletionAgent agent, AgentThread thread) + private async Task RunUntilGoalReached(AIAgent agent, AgentSession session) { - var agentResponse = agent.InvokeAsync("", thread); + var agentResponse = await agent.RunAsync("", session); - await PrintResult(agentResponse); - while (!await IsGoalReached(agentResponse)) + PrintResult(agentResponse); + while (!IsGoalReached(agentResponse)) { var input = Console.ReadLine(); - agentResponse = agent.InvokeAsync(input, thread); + agentResponse = await agent.RunAsync(input, session); - await PrintResult(agentResponse); + PrintResult(agentResponse); } } - public async Task LetAgentFindRide(IConfiguration config) + public async Task LetAgentFindRide() { var question = """ - I stay at the WestIn Seattle and the venue is the Seattle Kraken stadium. + I stay at the Westin Seattle and the venue is the Seattle Kraken stadium. the Concert starts at 7:30 pm and is November 20th this year. """; Console.WriteLine("******** Create the agent ***********"); - var transportationAgent = CreateTransportationAgent(config); - transportationAgent.Kernel.ImportPluginFromType(); + var transportationAgent = CreateTransportationAgent(); + Console.WriteLine("******** Start the agent ***********"); + var session = await transportationAgent.CreateSessionAsync(); - var thread = new ChatHistoryAgentThread(); - var agentresult = transportationAgent.InvokeAsync(question, thread); + var agentResponse = await transportationAgent.RunAsync(question, session); Console.WriteLine("******** RESPONSE 1 ***********"); - await PrintResult(agentresult); + PrintResult(agentResponse); - while (!await IsGoalReached(agentresult)) + while (!IsGoalReached(agentResponse)) { var input = Console.ReadLine(); - agentresult = transportationAgent.InvokeAsync(input, thread); + agentResponse = await transportationAgent.RunAsync(input, session); Console.WriteLine("******** RESPONSE ***********"); - await PrintResult(agentresult); + PrintResult(agentResponse); } Console.WriteLine("******** Terminating, goal reached ***********"); } - private async Task IsGoalReached(IAsyncEnumerable> agentResponse) - { - await foreach(var item in agentResponse) - { - if(item.Message.Content.Contains("[** GOAL REACHED **]")) - { - return true; - } - } - return false; - } + private bool IsGoalReached(AgentResponse agentResponse) => agentResponse.Text.Contains("[** GOAL REACHED **]"); - private ChatCompletionAgent CreateTransportationAgent(IConfiguration config) + private AIAgent CreateTransportationAgent() { - var kernel = CreateKernel(config); - var instructions = """ You are an expert in finding transportation options from a given hotel location to the concert location. You will try to get the best options available for an affordable price. Make sure the customer will be there at least 30 minutes before the concert starts at the venue. @@ -108,44 +94,23 @@ You are not allowed to make a booking without user confirmation! After you successfully booked the ride you will respond with [** GOAL REACHED **] in your message. """; - ChatCompletionAgent agent = new() - { - Name = "TransportationAgent", - Instructions = instructions, - Description = "An agent that finds transportation options from hotel to concert location", - Kernel = kernel, - Arguments = new KernelArguments(new OpenAIPromptExecutionSettings() - { - FunctionChoiceBehavior = FunctionChoiceBehavior.Auto(), - }), - }; - - return agent; + return chatClient.AsAIAgent( + name: "TransportationAgent", + description: "An agent that finds transportation options for the user from their hotel to the concert venue.", + instructions: instructions, + tools: [AIFunctionFactory.Create(RideInformationSystemService.GetAvailableRides), + AIFunctionFactory.Create(RideInformationSystemService.BookARide)] + ); } - private static async Task PrintResult(IAsyncEnumerable> agentResponse) + private static void PrintResult(AgentResponse agentResponse) { - await - foreach (var item in agentResponse) + foreach (var message in agentResponse.Messages) { - Console.WriteLine($"Thread: {item.Thread.Id}"); - Console.WriteLine($"Thread data: {item.Thread}"); - Console.WriteLine($"Author: {item.Message.AuthorName}"); - Console.WriteLine($"Message:{item.Message}"); + //Console.WriteLine($"Thread: {session.Id}"); + //Console.WriteLine($"Thread data: {message.}"); + Console.WriteLine($"Author: {message.AuthorName}"); + Console.WriteLine($"Message:{message.Text}"); } } - - private static Kernel CreateKernel(IConfiguration config) - { - var model = config["OpenAI:Model"]; - var endpoint = config["OpenAI:EndPoint"]; - var token = config["OpenAI:ApiKey"]; - - var kernelBuilder = Kernel - .CreateBuilder() - .AddOpenAIChatCompletion(model, new Uri(endpoint), token); - - var kernel = kernelBuilder.Build(); - return kernel; - } } \ No newline at end of file diff --git a/demos/module-04/demo04/HotelBookingAgent.cs b/demos/module-04/demo04/HotelBookingAgent.cs index d5f9eed..b66eb55 100644 --- a/demos/module-04/demo04/HotelBookingAgent.cs +++ b/demos/module-04/demo04/HotelBookingAgent.cs @@ -1,20 +1,15 @@ -using Microsoft.Extensions.Configuration; -using Microsoft.SemanticKernel; -using Microsoft.SemanticKernel.Agents; -using Microsoft.SemanticKernel.Connectors.OpenAI; +using Microsoft.Agents.AI; +using Microsoft.Extensions.AI; namespace modulerag; internal class HotelBookingAgent { - public static ChatCompletionAgent CreateChatCompletionAgent(IConfiguration config) + public static AIAgent CreateChatCompletionAgent(IChatClient chatClient) { - Kernel kernel = CreateKernel(config); - ChatCompletionAgent hotelReservationAgent = - new() - { - Name = "HotelReservationAgent", - Instructions = """ + return chatClient.AsAIAgent( + name: "HotelReservationAgent", + instructions: """ You are an expert in finding hotel rooms close to music concert locations. You provide some options what you have found and ask for approval before you make the booking. You always suggest 3 options with different price ranges. @@ -23,25 +18,10 @@ You are not allowed to make a booking without user confirmation! After you succesfully booked the ride you will respond with [** GOAL REACHED **] in your message. """, - Description = "An agent that finds and books a hotel room close to the concert location", - Kernel = kernel, - Arguments = new KernelArguments(new OpenAIPromptExecutionSettings() - { - FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() - }) - }; - return hotelReservationAgent; - } - - private static Kernel CreateKernel(IConfiguration config) - { - var model = config["OpenAI:Model"]; - var endpoint = config["OpenAI:EndPoint"]; - var token = config["OpenAI:ApiKey"]; - var kernelBuilder = Kernel - .CreateBuilder() - .AddOpenAIChatCompletion(model, new Uri(endpoint), token); - var kernel = kernelBuilder.Build(); - return kernel; + description: "An agent that finds and books a hotel room close to the concert location", + tools: [AIFunctionFactory.Create(HotelBookingFunctions.SelectRoomPreference), + AIFunctionFactory.Create(HotelBookingFunctions.BookSelectedRoom), + AIFunctionFactory.Create(HotelBookingFunctions.GetApprovalForBooking)] + ); } } diff --git a/demos/module-04/demo04/HotelBookingFunctions.cs b/demos/module-04/demo04/HotelBookingFunctions.cs index 5a72197..cb923a4 100644 --- a/demos/module-04/demo04/HotelBookingFunctions.cs +++ b/demos/module-04/demo04/HotelBookingFunctions.cs @@ -3,11 +3,11 @@ namespace modulerag; -public class HotelBookingFunctions +public static partial class HotelBookingFunctions { - [KernelFunction, Description("Returns available rooms in a city for a date")] + [Description("Returns available rooms in a city for a date")] [return: Description("The available room information, containing Type, Price per night, name of the hotel, number of people per room, amenities and location")] - public AvailableRoom[] SelectRoomPreference( + public static AvailableRoom[] SelectRoomPreference( [Description("City where you need an hotel")] string city, [Description("Date the hotel night is required")] @@ -21,9 +21,9 @@ public AvailableRoom[] SelectRoomPreference( .ToArray(); } - [KernelFunction, Description("Books a room previously selected from available rooms")] + [Description("Books a room previously selected from available rooms")] [return: Description("Indication if the booking was successful")] - public bool BookSelectedRoom( + public static bool BookSelectedRoom( [Description("id of the room to be booked")] int id) { @@ -34,9 +34,9 @@ public bool BookSelectedRoom( return selectedRoom != null; } - [KernelFunction, Description("Provides approval from the user for making a suggested booking based on the room id")] + [Description("Provides approval from the user for making a suggested booking based on the room id")] [return: Description("Indication if it is allowed to make the booking")] - public bool GetApprovalForBooking( + public static bool GetApprovalForBooking( [Description("id of the room to be booked")] int id) { @@ -47,18 +47,7 @@ public bool GetApprovalForBooking( return consent.Equals("yes", StringComparison.OrdinalIgnoreCase); } - public class AvailableRoom - { - public int RoomId { get; set; } - public string RoomType { get; set; } - public decimal PricePerNight { get; set; } - public string HotelName { get; set; } - public int NumberOfAdultsAllowedInRoom { get; set; } - public string Amenities { get; set; } - public string City { get; set; } - } - - private readonly AvailableRoom[] availableRooms = + private static readonly AvailableRoom[] availableRooms = [ // --- 50 room options below --- new AvailableRoom { RoomId = 304, RoomType = "Standard", PricePerNight = 110.00m, HotelName = "Harbor View", NumberOfAdultsAllowedInRoom = 2, Amenities = "Free WiFi, TV", City = "San Francisco" }, diff --git a/demos/module-04/demo04/Program.cs b/demos/module-04/demo04/Program.cs index 7a42501..1d11e26 100644 --- a/demos/module-04/demo04/Program.cs +++ b/demos/module-04/demo04/Program.cs @@ -1,5 +1,7 @@ -using Microsoft.Extensions.Configuration; -using Microsoft.SemanticKernel; +using System.ClientModel; +using Microsoft.Extensions.AI; +using Microsoft.Extensions.Configuration; +using OpenAI; using modulerag; var builder = new ConfigurationBuilder(); @@ -9,5 +11,18 @@ IConfiguration config = builder.Build(); -//await new ChatWithAgent().LetAgentFindRide(config); -await new ChatWithAgent().LetAgentFindRideAndHotel(config); \ No newline at end of file +var model = config["OpenAI:Model"] ?? throw new InvalidOperationException("Missing OpenAI model setting."); +var endpoint = config["OpenAI:Endpoint"] ?? config["OpenAI:EndPoint"] ?? + throw new InvalidOperationException("Missing OpenAI endpoint setting."); +var apiKey = config["OpenAI:ApiKey"] ?? throw new InvalidOperationException("Missing OpenAI API key."); + +// Create OpenAI client with custom endpoint +var openAIClient = new OpenAIClient(new ApiKeyCredential(apiKey), new OpenAIClientOptions +{ + Endpoint = new Uri(endpoint) +}); + +var chatClient = openAIClient.GetChatClient(model).AsIChatClient(); + +//await new ChatWithAgent(chatClient).LetAgentFindRide(); +await new ChatWithAgent(chatClient).LetAgentFindRideAndHotel(); \ No newline at end of file diff --git a/demos/module-04/demo04/Ride.cs b/demos/module-04/demo04/Ride.cs new file mode 100644 index 0000000..f2f06c3 --- /dev/null +++ b/demos/module-04/demo04/Ride.cs @@ -0,0 +1,3 @@ +namespace modulerag; + +public record Ride(int RideId, string RideType, decimal Price, string ServiceName, string City); diff --git a/demos/module-04/demo04/RideInformationSystemService.cs b/demos/module-04/demo04/RideInformationSystemService.cs index 1399800..0381b61 100644 --- a/demos/module-04/demo04/RideInformationSystemService.cs +++ b/demos/module-04/demo04/RideInformationSystemService.cs @@ -1,26 +1,25 @@ -using Microsoft.SemanticKernel; -using System.ComponentModel; +using System.ComponentModel; namespace modulerag; -internal class RideInformationSystemService +internal static class RideInformationSystemService { - [KernelFunction("get_available_rides"), - Description("Get available rides in a city for a given date")] - public Ride[] GetAvailableRides([Description("City where you need the ride")] string city, [Description("Date the ride is required")] DateTime bookingDate) + [Description("Get available rides in a city for a given date")] + public static Ride[] GetAvailableRides( + [Description("City where you need the ride")] string city, + [Description("Date the ride is required")] DateTime bookingDate) { Console.ForegroundColor = ConsoleColor.Cyan; Console.WriteLine($"Searching for Rides for City: {city} and Date: {bookingDate}"); - Console.ResetColor(); + Console.ResetColor(); return availableRides .Where(r => r.City.Equals(city, StringComparison.OrdinalIgnoreCase)) .ToArray(); } - - [KernelFunction("book_a_ride"), - Description("makes it possible to book a selected ride")] + + [Description("makes it possible to book a selected ride")] [return: Description("returns true if the booking was successfull")] - public bool BookARide(int rideID) + public static bool BookARide(int rideID) { Console.ForegroundColor = ConsoleColor.Cyan; Console.WriteLine($"Booked the ride with id: {rideID}"); @@ -28,31 +27,21 @@ public bool BookARide(int rideID) return true; } - private readonly Ride[] availableRides = + private static readonly Ride[] availableRides = [ - new Ride { RideId = 1, RideType = "Taxi", Price = 25.00m, ServiceName = "City Cabs", City = "New York" }, - new Ride { RideId = 2, RideType = "Ride Share", Price = 20.00m, ServiceName = "Uber", City = "New York" }, - new Ride { RideId = 3, RideType = "Limousine", Price = 100.00m, ServiceName = "Luxury Rides", City = "Los Angeles" }, - new Ride { RideId = 4, RideType = "Taxi", Price = 30.00m, ServiceName = "LA Cabs", City = "Los Angeles" }, - new Ride { RideId = 5, RideType = "Ride Share", Price = 22.00m, ServiceName = "Lyft", City = "Chicago" }, - new Ride { RideId = 6, RideType = "Taxi", Price = 28.00m, ServiceName = "Chicago Taxis", City = "Chicago" }, - new Ride { RideId = 7, RideType = "Shuttle", Price = 15.00m, ServiceName = "City Shuttle", City = "Miami" }, - new Ride { RideId = 8, RideType = "Ride Share", Price = 18.00m, ServiceName = "Uber", City = "Miami" }, - new Ride { RideId = 9, RideType = "Taxi", Price = 27.00m, ServiceName = "Miami Cabs", City = "Miami" }, - new Ride { RideId = 10, RideType = "Limousine", Price = 120.00m, ServiceName = "Elite Rides", City = "New York" }, - new Ride { RideId = 11, RideType = "Taxi", Price = 26.00m, ServiceName = "Downtown Taxis", City = "San Francisco" }, - new Ride { RideId = 12, RideType = "Ride Share", Price = 21.00m, ServiceName = "Lyft", City = "San Francisco" }, - new Ride { RideId = 13, RideType = "Shuttle", Price = 16.00m, ServiceName = "Airport Shuttle", City = "San Francisco" }, - new Ride { RideId = 14, RideType = "Taxi", Price = 29.00m, ServiceName = "City Cabs", City = "Seattle" }, + new Ride(RideId: 1, RideType: "Taxi", Price: 25.00m, ServiceName: "City Cabs", City: "New York"), + new Ride(RideId: 2, RideType: "Ride Share", Price: 20.00m, ServiceName: "Uber", City: "New York"), + new Ride(RideId: 3, RideType: "Limousine", Price: 100.00m, ServiceName: "Luxury Rides", City: "Los Angeles"), + new Ride(RideId: 4, RideType: "Taxi", Price: 30.00m, ServiceName: "LA Cabs", City: "Los Angeles"), + new Ride(RideId: 5, RideType: "Ride Share", Price: 22.00m, ServiceName: "Lyft", City: "Chicago"), + new Ride(RideId: 6, RideType: "Taxi", Price: 28.00m, ServiceName: "Chicago Taxis", City: "Chicago"), + new Ride(RideId: 7, RideType: "Shuttle", Price: 15.00m, ServiceName: "City Shuttle", City: "Miami"), + new Ride(RideId: 8, RideType: "Ride Share", Price: 18.00m, ServiceName: "Uber", City: "Miami"), + new Ride(RideId: 9, RideType: "Taxi", Price: 27.00m, ServiceName: "Miami Cabs", City: "Miami"), + new Ride(RideId: 10, RideType: "Limousine", Price: 120.00m, ServiceName: "Elite Rides", City: "New York"), + new Ride(RideId: 11, RideType: "Taxi", Price: 26.00m, ServiceName: "Downtown Taxis", City: "San Francisco"), + new Ride(RideId: 12, RideType: "Ride Share", Price: 21.00m, ServiceName: "Lyft", City: "San Francisco"), + new Ride(RideId: 13, RideType: "Shuttle", Price: 16.00m, ServiceName: "Airport Shuttle", City: "San Francisco"), + new Ride(RideId: 14, RideType: "Taxi", Price: 29.00m, ServiceName: "City Cabs", City: "Seattle"), ]; } - -public class Ride -{ - public int RideId { get; set; } - public string RideType { get; set; } - public decimal Price { get; set; } - public string ServiceName { get; set; } - public string City { get; set; } - -} diff --git a/demos/module-04/demo04/demo-04.csproj b/demos/module-04/demo04/demo-04.csproj index dcf32d1..d9a40dc 100644 --- a/demos/module-04/demo04/demo-04.csproj +++ b/demos/module-04/demo04/demo-04.csproj @@ -2,7 +2,7 @@ Exe - net9.0 + net10.0 modulerag enable enable @@ -10,24 +10,12 @@ - - - - - - + + + - - - - - - - - - - +