diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/Models/ChatMessageExtensionsTests.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/Models/ChatMessageExtensionsTests.cs index 290781c70..5bcad5382 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/Models/ChatMessageExtensionsTests.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/Models/ChatMessageExtensionsTests.cs @@ -6,6 +6,19 @@ namespace Microsoft.Teams.AI.Tests.AITests.Models { public class ChatMessageExtensionsTests { + [Fact] + public void Test_InvalidRole_ToAzureSdkChatMessage() + { + // Arrange + var chatMessage = new ChatMessage(new AI.Models.ChatRole("InvalidRole")); + + // Act + var ex = Assert.Throws(() => chatMessage.ToChatRequestMessage()); + + // Assert + Assert.Equal($"Invalid chat message role: InvalidRole", ex.Message); + } + [Fact] public void Test_UserRole_ToAzureSdkChatMessage() { @@ -13,6 +26,7 @@ public void Test_UserRole_ToAzureSdkChatMessage() var chatMessage = new ChatMessage(AI.Models.ChatRole.User) { Content = "test-content", + Name = "author" }; // Act @@ -22,6 +36,7 @@ public void Test_UserRole_ToAzureSdkChatMessage() Assert.Equal(Azure.AI.OpenAI.ChatRole.User, result.Role); Assert.Equal(typeof(ChatRequestUserMessage), result.GetType()); Assert.Equal("test-content", ((ChatRequestUserMessage)result).Content); + Assert.Equal("author", ((ChatRequestUserMessage)result).Name); } [Fact] @@ -67,6 +82,7 @@ public void Test_SystemRole_ToAzureSdkChatMessage() var chatMessage = new ChatMessage(AI.Models.ChatRole.System) { Content = "test-content", + Name = "author" }; // Act @@ -76,6 +92,7 @@ public void Test_SystemRole_ToAzureSdkChatMessage() Assert.Equal(Azure.AI.OpenAI.ChatRole.System, result.Role); Assert.Equal(typeof(ChatRequestSystemMessage), result.GetType()); Assert.Equal("test-content", ((ChatRequestSystemMessage)result).Content); + Assert.Equal("author", ((ChatRequestSystemMessage)result).Name); } [Fact] diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Models/ChatMessage.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Models/ChatMessage.cs index 02430640e..83ad5d365 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Models/ChatMessage.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Models/ChatMessage.cs @@ -13,7 +13,7 @@ public class ChatMessage /// /// The text associated with this message payload. /// - public string? Content { get; set; } + public string Content { get; set; } = string.Empty; /// /// The name of the author of this message. `name` is required if role is `function`, and it should be the name of the @@ -53,7 +53,7 @@ public class FunctionCall /// /// The name of the function to call. /// - public string Name { get; set; } + public string Name { get; set; } = string.Empty; /// /// The arguments to call the function with, as generated by the model in JSON format. @@ -61,7 +61,7 @@ public class FunctionCall /// not defined by your function schema. Validate the arguments in your code before calling /// your function. /// - public string Arguments { get; set; } + public string Arguments { get; set; } = string.Empty; /// /// Creates an instance of `FunctionCall`. diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Models/ChatMessageExtensions.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Models/ChatMessageExtensions.cs index 55796a89b..7c8595591 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Models/ChatMessageExtensions.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Models/ChatMessageExtensions.cs @@ -1,5 +1,6 @@ using Azure.AI.OpenAI; using Microsoft.Teams.AI.Exceptions; +using Microsoft.Teams.AI.Utilities; namespace Microsoft.Teams.AI.AI.Models { @@ -15,23 +16,32 @@ internal static class ChatMessageExtensions /// An . public static ChatRequestMessage ToChatRequestMessage(this ChatMessage chatMessage) { + Verify.NotNull(chatMessage.Content); + Verify.NotNull(chatMessage.Role); + ChatRole role = chatMessage.Role; ChatRequestMessage? message = null; if (role == ChatRole.User) { - message = new ChatRequestUserMessage(chatMessage.Content); + ChatRequestUserMessage userMessage = new(chatMessage.Content); + + if (chatMessage.Name != null) + { + userMessage.Name = chatMessage.Name; + } + + message = userMessage; } if (role == ChatRole.Assistant) { - Azure.AI.OpenAI.FunctionCall functionCall = new(chatMessage.FunctionCall?.Name, chatMessage.FunctionCall?.Arguments); + ChatRequestAssistantMessage assistantMessage = new(chatMessage.Content); - ChatRequestAssistantMessage assistantMessage = new(chatMessage.Content) + if (chatMessage.FunctionCall != null) { - FunctionCall = functionCall, - Name = chatMessage.Name, - }; + assistantMessage.FunctionCall = new(chatMessage.FunctionCall.Name ?? "", chatMessage.FunctionCall.Arguments ?? ""); + } if (chatMessage.ToolCalls != null) { @@ -41,22 +51,34 @@ public static ChatRequestMessage ToChatRequestMessage(this ChatMessage chatMessa } } + if (chatMessage.Name != null) + { + assistantMessage.Name = chatMessage.Name; + } + message = assistantMessage; } if (role == ChatRole.System) { - message = new ChatRequestSystemMessage(chatMessage.Content); + ChatRequestSystemMessage systemMessage = new(chatMessage.Content); + + if (chatMessage.Name != null) + { + systemMessage.Name = chatMessage.Name; + } + + message = systemMessage; } if (role == ChatRole.Function) { - message = new ChatRequestFunctionMessage(chatMessage.Name, chatMessage.Content); + message = new ChatRequestFunctionMessage(chatMessage.Name ?? "", chatMessage.Content); } if (role == ChatRole.Tool) { - message = new ChatRequestToolMessage(chatMessage.Content, chatMessage.ToolCallId); + message = new ChatRequestToolMessage(chatMessage.Content, chatMessage.ToolCallId ?? ""); } if (message == null) diff --git a/dotnet/samples/04.ai.a.teamsChefBot/KernelMemoryDataSource.cs b/dotnet/samples/04.ai.a.teamsChefBot/KernelMemoryDataSource.cs new file mode 100644 index 000000000..13e7fca8d --- /dev/null +++ b/dotnet/samples/04.ai.a.teamsChefBot/KernelMemoryDataSource.cs @@ -0,0 +1,125 @@ +using Microsoft.Bot.Builder; +using Microsoft.KernelMemory; +using Microsoft.Teams.AI.AI.DataSources; +using Microsoft.Teams.AI.AI.Prompts.Sections; +using Microsoft.Teams.AI.AI.Tokenizers; +using Microsoft.Teams.AI.State; +using System.Text; + +namespace TeamsChefBot +{ + /// + /// The class connects the Kernel Memory library data source to the bot. + /// Kernel Memory is a library that allows you to index and query any data using LLM and natural language, + /// tracking sources and showing citations (https://github.com/microsoft/kernel-memory). + /// + public class KernelMemoryDataSource : IDataSource + { + private readonly IKernelMemory _kernelMemory; + private readonly Task? _ingestTask; + + public KernelMemoryDataSource(string name, IKernelMemory memoryInstance) + { + ArgumentNullException.ThrowIfNull(memoryInstance); + + this._kernelMemory = memoryInstance; + this.Name = name; + + if (memoryInstance.GetDocumentStatusAsync("doc-1").Result?.Completed != true) + { + // Ingest documents on construction + this._ingestTask = this.IngestAsync(); + } + } + + public string Name { get; } + + /// + /// Loads documents from the 'files' folder into Kernel Memory's in-memory vector database. + /// + /// + private async Task IngestAsync() + { + Console.WriteLine("Loading documents from the 'files' folder into Kernel Memory's in-memory vector database"); + + var importTasks = new List(); + string[] Documents = Directory.GetFiles("files"); + + int i = 0; + foreach (string doc in Documents) + { + importTasks.Add(this._kernelMemory.ImportDocumentAsync(doc, documentId: $"doc-{i}")); + i++; + } + + await Task.WhenAll(importTasks); + } + + public async Task> RenderDataAsync(ITurnContext context, IMemory memory, ITokenizer tokenizer, int maxTokens, CancellationToken cancellationToken = default) + { + if (this._ingestTask?.IsCompleted == false) + { + // Wait for ingestion to complete + await _ingestTask; + } + + string? ask = memory.GetValue("temp.input") as string; + + if (ask == null) + { + return new RenderedPromptSection(string.Empty, 0); + } + + // Query index for all relevant documents + SearchResult result = await this._kernelMemory.SearchAsync(ask); + + if (result.NoResult) + { + Console.WriteLine("No results when querying Kernel Memory found"); + return new RenderedPromptSection(string.Empty, 0); + } + + List citations = result.Results; + + // Add documents until you run out of tokens + int length = 0; + StringBuilder output = new(); + string connector = ""; + bool maxTokensReached = false; + foreach (Citation citation in citations) + { + // Start a new doc + StringBuilder doc = new(); + doc.Append($"{connector}\n"); + length += tokenizer.Encode($"{connector}\n").Count; + // Add ending tag count to token count + length += tokenizer.Encode("\n").Count; + + foreach (var partition in citation.Partitions) + { + // Add the partition to the doc + int partitionLength = tokenizer.Encode(partition.Text).Count; + int remainingTokens = maxTokens - (length + partitionLength); + if (remainingTokens < 0) + { + maxTokensReached = true; + break; + } + length += partitionLength; + doc.Append($"{partition.Text}\n"); + } + + doc.Append("\n"); + output.Append(doc.ToString()); + connector = "\n\n"; + + if (maxTokensReached) + { + break; + } + } + + return new RenderedPromptSection(output.ToString(), length, length > maxTokens); + } + } +} diff --git a/dotnet/samples/04.ai.a.teamsChefBot/Program.cs b/dotnet/samples/04.ai.a.teamsChefBot/Program.cs index 78edfc5bc..d883e3baf 100644 --- a/dotnet/samples/04.ai.a.teamsChefBot/Program.cs +++ b/dotnet/samples/04.ai.a.teamsChefBot/Program.cs @@ -7,6 +7,7 @@ using Microsoft.Teams.AI.State; using Microsoft.Teams.AI; using TeamsChefBot; +using Microsoft.KernelMemory; var builder = WebApplication.CreateBuilder(args); @@ -35,6 +36,7 @@ // Create AI Model if (!string.IsNullOrEmpty(config.OpenAI?.ApiKey)) { + // Create OpenAI Model builder.Services.AddSingleton(sp => new( new OpenAIModelOptions(config.OpenAI.ApiKey, "gpt-3.5-turbo") { @@ -42,9 +44,19 @@ }, sp.GetService() )); + + // Create Kernel Memory Serverless instance using OpenAI embeddings API + builder.Services.AddSingleton((sp) => + { + return new KernelMemoryBuilder() + .WithOpenAIDefaults(config.OpenAI.ApiKey) + .WithSimpleFileStorage() + .Build(); + }); } else if (!string.IsNullOrEmpty(config.Azure?.OpenAIApiKey) && !string.IsNullOrEmpty(config.Azure.OpenAIEndpoint)) { + // Create Azure OpenAI Model builder.Services.AddSingleton(sp => new( new AzureOpenAIModelOptions( config.Azure.OpenAIApiKey, @@ -56,6 +68,25 @@ }, sp.GetService() )); + + // Create Kernel Memory Serverless instance using AzureOpenAI embeddings API + builder.Services.AddSingleton((sp) => + { + AzureOpenAIConfig azureConfig = new() + { + Auth = AzureOpenAIConfig.AuthTypes.APIKey, + APIKey = config.Azure.OpenAIApiKey, + Endpoint = config.Azure.OpenAIEndpoint, + APIType = AzureOpenAIConfig.APITypes.EmbeddingGeneration, + Deployment = "text-embedding-ada-002" // Update this to the deployment you want to use + }; + + return new KernelMemoryBuilder() + .WithAzureOpenAITextEmbeddingGeneration(azureConfig) + .WithAzureOpenAITextGeneration(azureConfig) + .WithSimpleFileStorage() + .Build(); + }); } else { @@ -74,6 +105,9 @@ PromptFolder = "./Prompts" }); + KernelMemoryDataSource dataSource = new("teams-ai", sp.GetService()!); + prompts.AddDataSource("teams-ai", dataSource); + // Create ActionPlanner ActionPlanner planner = new( options: new( diff --git a/dotnet/samples/04.ai.a.teamsChefBot/Prompts/Chat/config.json b/dotnet/samples/04.ai.a.teamsChefBot/Prompts/Chat/config.json index b4b01c792..ad403f98b 100644 --- a/dotnet/samples/04.ai.a.teamsChefBot/Prompts/Chat/config.json +++ b/dotnet/samples/04.ai.a.teamsChefBot/Prompts/Chat/config.json @@ -7,7 +7,7 @@ "completion_type": "chat", "include_history": true, "include_input": true, - "max_input_tokens": 2800, + "max_input_tokens": 2000, "max_tokens": 1000, "temperature": 0.2, "top_p": 0.0, @@ -16,6 +16,9 @@ "stop_sequences": [] }, "augmentation": { - "augmentation_type": "none" + "augmentation_type": "none", + "data_sources": { + "teams-ai": 900 + } } } \ No newline at end of file diff --git a/dotnet/samples/04.ai.a.teamsChefBot/TeamsChefBot.csproj b/dotnet/samples/04.ai.a.teamsChefBot/TeamsChefBot.csproj index b675f7c28..f218fc3cd 100644 --- a/dotnet/samples/04.ai.a.teamsChefBot/TeamsChefBot.csproj +++ b/dotnet/samples/04.ai.a.teamsChefBot/TeamsChefBot.csproj @@ -14,6 +14,8 @@ + + diff --git a/dotnet/samples/04.ai.a.teamsChefBot/files/README.md b/dotnet/samples/04.ai.a.teamsChefBot/files/README.md new file mode 100644 index 000000000..14e02e05e --- /dev/null +++ b/dotnet/samples/04.ai.a.teamsChefBot/files/README.md @@ -0,0 +1 @@ +Each document in this folder is a markdown file that was scraped from the Teams AI Github repository. This knowledge base will be used by the Teams Chef bot to answer questions about the Teams AI library. \ No newline at end of file diff --git a/dotnet/samples/04.ai.a.teamsChefBot/files/action-planner.txt b/dotnet/samples/04.ai.a.teamsChefBot/files/action-planner.txt new file mode 100644 index 000000000..85583c58b --- /dev/null +++ b/dotnet/samples/04.ai.a.teamsChefBot/files/action-planner.txt @@ -0,0 +1,28 @@ +# Action Planner + +The Action Planner is a powerful planner that uses an LLM to generate plans. It can trigger parameterized actions and send text-based responses to the user. It supports the following advanced features: + +### [Prompt Management](./PROMPTS.md) + +The Action Planner has a built-in prompt management system that supports creating prompt templates as folders in the file system. A prompt template is the prompt text along with all the configurations for completion with the LLM model. Dynamic prompts also support template variables and functions. + +#### [Data Sources](./DATA-SOURCES.md) +Use data sources to augment prompts even further and facilitate better responses. + +### [Augmentations](./AUGMENTATIONS.md) +Augmentations virtually eliminate the need for prompt engineering. Prompts +can be configured to use a named augmentation which will be automatically appended to the outgoing +prompt. Augmentations let the developer specify whether they want to support multi-step plans (sequence), +or create an AutoGPT style agent (monologue). + +### Validations +Validators are used to validate the response returned by the LLM and can guarantee +that the parameters passed to an action match a supplied schema. The validator used is automatically +selected based on the augmentation being used. Validators also prevent hallucinated action names, +making it impossible for the LLM to trigger an action that doesn't exist. + +### Repair +The Action Planner will automatically attempt to repair invalid responses returned by the +LLM using a feedback loop. When a validation fails, the ActionPlanner sends the error back to the +model, along with an instruction asking it to fix its mistake. This feedback technique leads to a +dramatic reduction in the number of invalid responses returned by the model. \ No newline at end of file diff --git a/dotnet/samples/04.ai.a.teamsChefBot/files/actions.txt b/dotnet/samples/04.ai.a.teamsChefBot/files/actions.txt new file mode 100644 index 000000000..7551dcaca --- /dev/null +++ b/dotnet/samples/04.ai.a.teamsChefBot/files/actions.txt @@ -0,0 +1,62 @@ +# Action + +An action is an atomic function that is registered to the AI System. It is a fundamental building block of a plan. + +Here's an example of what an action creating a new list would look like in code. Call this action `createList`: + +### C# + +[List Bot sample](https://github.com/microsoft/teams-ai/blob/a20f8715d3fe81e11c330853e3930e22abe298af/dotnet/samples/04.ai.d.chainedActions.listBot/ListBotActions.cs#L15) +```C# +[Action("createList")] +public bool CreateList([ActionTurnState] ListState turnState, [ActionParameters] Dictionary parameters) +{ + ArgumentNullException.ThrowIfNull(turnState); + ArgumentNullException.ThrowIfNull(parameters); + + string listName = GetParameterString(parameters, "list"); + + EnsureListExists(turnState, listName); + + // Continues execution of next command in the plan. + return ""; +} +``` + +> Adding the `Action` attribute marks the method as an action. To register it to the AI System you have pass the instance object containing this method to the `AI.ImportActions(instance)` method. Alternatively, you can use the `AI.RegisterAction(name, handler)` to register a single action. + +### JS + +[List Bot sample](https://github.com/microsoft/teams-ai/blob/0fca2ed09d327ecdc682f2b15eb342a552733f5e/js/samples/04.ai.d.chainedActions.listBot/src/index.ts#L153) + +```typescript +app.ai.action("createList", async (context: TurnContext, state: ApplicationTurnState, parameters: ListAndItems) => { + // Ex. create a list with name "Grocery Shopping". + ensureListExists(state, parameters.list); + + // Continues exectuion of next command in the plan. + return true; +}); +``` + +> The `action` method registers the action named `createList` with corresponding callback function. + + +## Default Actions + +The user can register custom actions or override default actions in the system. Below is a list of default actions present: + +| Action | Called when | +| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `___UnknownAction___` | An unknown action is predicted by the planner. | +| `___FlaggedInput___` | The input is flagged by the moderator. | +| `___FlaggedOutput___` | The output is flagged by the moderator. | +| `___HttpError___` | The planner encounters an HTTP response with status code >= `400` | +| `__TooManySteps__` | The planner task either executed too many steps or timed out. | +| `___PlanReady___` | The plan has been predicted by the planner and it has passed moderation. This can be overriden to mutate the plan before execution. | +| `___DO___` | The AI system is executing a plan with the DO command. Overriding this action will change how _all_ DO commands are handled. | +| `___SAY___` | The AI system is executing a plan with the SAY command. Overriding this action will change how _all_ SAY commands are handled. By default this will send the `response` message back to the user. | + +> Detailed description of each action can be found in the codebase. + +Note that `___DO___` and `___SAY___`, despite being called commands, are actually specialized actions. This means that a plan is really a sequence of actions. diff --git a/dotnet/samples/04.ai.a.teamsChefBot/files/ai-system.txt b/dotnet/samples/04.ai.a.teamsChefBot/files/ai-system.txt new file mode 100644 index 000000000..ab29432a5 --- /dev/null +++ b/dotnet/samples/04.ai.a.teamsChefBot/files/ai-system.txt @@ -0,0 +1,7 @@ +# The AI System + +The AI system is responsible for moderating input and output, generating plans, and executing them. It can be used free standing or routed to by the Application object. This system is encapsulated in the `AI` class. It is made of three components, the moderator, the planner, and actions. + +1. [Moderator](./MODERATOR.md) +2. [Planner](./PLANNER.md) +3. [Actions](./ACTIONS.md) \ No newline at end of file diff --git a/dotnet/samples/04.ai.a.teamsChefBot/files/application.txt b/dotnet/samples/04.ai.a.teamsChefBot/files/application.txt new file mode 100644 index 000000000..90fc4ef82 --- /dev/null +++ b/dotnet/samples/04.ai.a.teamsChefBot/files/application.txt @@ -0,0 +1,69 @@ +# The `Application` class + +The `Application` class encapsulates all the business logic for the application and comprises of two major components, the _Activity Handler System_ and the _AI System_. + + +## The Activity Handler System + +The activity handler system is the primary way to implement bot or message extension application logic. It is a set of methods and configurations that allows you to register callbacks (known as route handlers), which will trigger based on the incoming activity. These can be in the form of a message, message reaction, or virtually any interaction within the Teams app. + +Here's an example of registering a route handler that will run when the the user sends *"/login"* to the bot: + +**JS** +```js +// Listen for user to say '/login'. +app.message('/login', async (context: TurnContext, state: TurnState) => { + await context.sendActivity(`Starting sign in flow.`); + // start signin flow +}); +``` + +**C#** +```cs +// Listen for user to say '/login'. +app.OnMessage("/login", async (ITurnContext turnContext, TurnState turnState, CancellationToken cancellationToken) => +{ + await turnContext.SendActivityAsync("Starting sign in flow.", cancellationToken: cancellationToken); + // start signin flow +}); +``` +> The `message` and `OnMessage` methods are referred to as activity or *route registration* method. +> The `turnContext` and `turnState` parameters are present in every route handler. To learn more about them see [TURNS](TURNS.md). + +The `Application` groups the route registration methods based on the specific feature groups: + + +| **Feature** | **Description** | +| ----------------- | ---------------------------------------------------------------- | +| Task Modules | Task module related activities like `task/fetch`. | +| Message Extension | Message extension activities like `composeExtension/query`. | +| Meetings | Meeting activites like `application/vnd.microsoft.meetingStart`. | +| AdaptiveCards | Adaptive card activities like `adaptiveCard/action`. | +| General | Generic activites like `message`. | + +> To see all the route registration methods supported, see the migration docs ([JS](https://github.com/microsoft/teams-ai/blob/main/getting-started/MIGRATION/JS.md#activity-handler-methods) | [C#](https://github.com/microsoft/teams-ai/blob/main/getting-started/MIGRATION/DOTNET.md#activity-handler-methods)). + +In general, the activity handler system is all that is needed to have a functional bot or message extension. + +## The AI System +The AI System is an optional component used to plug in LLM powered experiences like user intent mapping, chaining...etc. It is configured once when orchestrating the application class. To learn more about it see [The AI System](./AI-SYSTEM.md). + +## The Routing Logic + +When an incoming activity reaches the server, the bot adapter handles the necessary authentication and creates a turn context object that encapsulates the activity details. Then the `Application`'s main method (`run()` in Javscript. `OnTurnAsync()` in C#) is called. Its logic can be broken down into these eight steps. + +1. If configured in the application options, pulses of the `Typing` activity are sent to the user. +2. If configured in the application options, the @mention is removed from the incoming message activity. +3. The turn state is loaded using the configured turn state factory. +4. If user authentication is configured, then attempt to sign the user in. If the user is already signed in, retrieve the access token and continue to step 5. Otherwise, start the sign in flow and end the current turn. +5. The `beforeTurn` activity handler is executed. If it returns false, save turn state to storage and end the turn. +6. All routes are iterated over and if a selector function is triggered, then the corresponding route handler is executed. +7. If no route is triggered, the incoming activity is a message, and an AI System is configured, then it is invoked by calling the `AI.run()` method. +8. The `AfterTurnAsync` activity handler is executed. If it returns true, save turn state to storage. + + +> Note: _End the turn_ means that the main method has terminated execution and so the application has completed processing the incoming activity. + +> Note: To learn about what a *turn* is, see [TURNS](TURNS.md). + +![the routing logic](../assets/routing-logic.png) diff --git a/dotnet/samples/04.ai.a.teamsChefBot/files/augmentations.txt b/dotnet/samples/04.ai.a.teamsChefBot/files/augmentations.txt new file mode 100644 index 000000000..c37675980 --- /dev/null +++ b/dotnet/samples/04.ai.a.teamsChefBot/files/augmentations.txt @@ -0,0 +1,193 @@ +# Augmentations + +Augmentations virtually eliminate the need for prompt engineering. Prompts +can be configured to use a named augmentation which will be automatically appended to the outgoing +prompt. Augmentations let the developer specify whether they want to support multi-step plans (sequence), +or create an AutoGPT style agent (monologue). + +It is recommended to read the [AI System](./AI-SYSTEM.md) and [Action Planner](./ACTION-PLANNER.md) guides if you are not familiar with plans and actions. + +## Sequence Augmentation + +This augmentation allows the model to return a sequence of actions to perform. It does this by appending instructions to the prompt text during runtime. These instructions guide the model to generate a plan object that uses actions defined in the `actions.json` file from the prompt template folder. + +Here's an example of the `actions.json` file from the [Light Bot](https://github.com/microsoft/teams-ai/blob/77339da9e3e03bfd7f629fc796cfebdcd2891afb/js/samples/04.ai.c.actionMapping.lightBot/src/prompts/sequence/actions.json) sample: + +```json +[ + { + "name": "LightsOn", + "description": "Turns on the lights" + }, + { + "name": "LightsOff", + "description": "Turns off the lights" + }, + { + "name": "Pause", + "description": "Delays for a period of time", + "parameters": { + "type": "object", + "properties": { + "time": { + "type": "number", + "description": "The amount of time to delay in milliseconds" + } + }, + "required": [ + "time" + ] + } + } +] +``` + +It defines three actions, `LightsOn`, `LightsOff` and `Pause`. The `Pause` action requires the `time` parameter, while the other two don't. + +These actions are then appended to the prompt text during runtime. This is text added to end of the prompt text: + +```txt +actions: + LightsOn: + description: Turns on the lights + LightsOff: + description: Turns off the lights + Pause: + description: Delays for a period of time + parameters: + time: + type: number + description: The amount of time to delay in milliseconds + +Use the actions above to create a plan in the following JSON format: + +{ + "type": "plan", + "commands": [ + { + "type": "DO", + "action": "", + "parameters": { + "": "" + } + }, + { + "type": "SAY", + "response": "" + } + ] +} +``` +> Note: When the prompt is rendered, the above text is compressed to reduce token usage. + +The first section lists the actions in yaml structure. The second section tells the model to return a plan object of the following schema. + +### Configuring your prompt + +There are two steps to use sequence augmentation in your prompt: + +1. Update the prompt's `config.json` by adding the `augmentation` property. +```diff +{ + "schema": 1.1, + "description": "", + "type": "", + "completion": {}, ++ "augmentation": { ++ "augmentation_type": "sequence" ++ } +} +``` +2. Create an `actions.json` file in the prompt folder with a list of actions. For example: +```json +[ + { + "name": "LightsOn", + "description": "Turns on the lights" + }, + { + "name": "LightsOff", + "description": "Turns off the lights" + }, +] +``` + +To learn more about the action object schema see the corresponding typescript interface [ChatCompletionAction](https://github.com/microsoft/teams-ai/blob/0fca2ed09d327ecdc682f2b15eb342a552733f5e/js/packages/teams-ai/src/models/ChatCompletionAction.ts#L14). + + +## Monologue Augmentation + +This augmentation adds support for an inner monologue to the prompt. The monologue helps the LLM perform chain-of-thought reasoning across multiple turns of conversation. It does this by appending instructions to the prompt text during runtime. It tells the model to explicitly show it's thought, reasoning and plan in response to the user's message, then predict the next action to execute. If looping is configured, then the predicted action can guide the model to predict the next action by returning the instruction as a string in the action handler callback. The loop will terminate as soon as the model predicts a *SAY* action, which sends the response back to the user. + +Using the `actions.json` example from abovce, the instructions appended to the prompt look like the text below. + +These actions are then used and appended to the prompt text in runtime. This is text added to end of the prompt text: + +```txt +actions: + LightsOn: + description: Turns on the lights + LightsOff: + description: Turns off the lights + Pause: + description: Delays for a period of time + parameters: + time: + type: number + description: The amount of time to delay in milliseconds + +Return a JSON object with your thoughts and the next action to perform. +Only respond with the JSON format below and base your plan on the actions above. +If you're not sure what to do, you can always say something by returning a SAY action. +If you're told your JSON response has errors, do your best to fix them. + +Response Format: + +{ + "thoughts": { + "thought": "", + "reasoning": "", + "plan": "- short bulleted\\n- list that conveys\\n- long-term plan" + }, + "action": { + "name": "", + "parameters": { + "": "" + } + } +} +``` + +> Note: When the prompt is rendered, the above text is compressed to reduce token usage. + +The first section lists the actions in yaml structure. The second section tells the model to return a plan object of the following schema. + +### Configuring your prompt + +To use monologue augmentation in your prompt there are two steps. + +1. Update the prompt's `config.json` by adding the `augmentation` property. +```diff +{ + "schema": 1.1, + "description": "", + "type": "", + "completion": {}, ++ "augmentation": { ++ "augmentation_type": "monologue" ++ } +} +``` +2. Create an `actions.json` file in the prompt folder with a list of actions. For example: +```json +[ + { + "name": "LightsOn", + "description": "Turns on the lights" + }, + { + "name": "LightsOff", + "description": "Turns off the lights" + }, +] +``` diff --git a/dotnet/samples/04.ai.a.teamsChefBot/files/c#-migration.txt b/dotnet/samples/04.ai.a.teamsChefBot/files/c#-migration.txt new file mode 100644 index 000000000..26d9e3ad9 --- /dev/null +++ b/dotnet/samples/04.ai.a.teamsChefBot/files/c#-migration.txt @@ -0,0 +1,244 @@ +# Migrating from the BotFramework SDK (C#) + +_**Navigation**_ +- [00.OVERVIEW](./README.md) +- [01.JS](./JS.md) +- [**02.DOTNET**](./DOTNET.md) +___ + +If you have a bot built using the C# BF SDK, the following will help you update your bot to the Teams AI library. + +## New Project or Migrate existing app + +Since the library builds on top of the BF SDK, much of the bot logic can be directly carried over to the Teams AI app. If you want to start with a new project, set up the Echo bot sample in the [quick start](../.QUICKSTART.md) guide and jump directly to [step 2](#2-replace-the-activity-handler-implementations-with-specific-route-handlers). + +If you want to migrate your existing app start with [step 1](#1-replace-the-activityhandler-with-the-application-object). + +## 1. Replace the ActivityHandler with the Application object + +To understand how to replace the `ActivityHandler` with the `Application` object, let's look at the Echo bot sample from the BF SDK and the Teams AI library. + +**BF SDK [Echo bot](https://github.com/microsoft/BotBuilder-Samples/tree/main/samples/csharp_dotnetcore/02.echo-bot)** + +[`Startup.cs`](https://github.com/microsoft/BotBuilder-Samples/blob/main/samples/csharp_dotnetcore/02.echo-bot/Startup.cs) + +```cs +// Create the bot as a transient. In this case the ASP Controller is expecting an IBot. +services.AddTransient(); +``` + +[`EchoBot.cs`](https://github.com/microsoft/BotBuilder-Samples/blob/main/samples/csharp_dotnetcore/02.echo-bot/Bots/EchoBot.cs) + +```cs +public class EchoBot : ActivityHandler +{ + protected override async Task OnMessageActivityAsync(ITurnContext turnContext, CancellationToken cancellationToken) + { + var replyText = $"Echo: {turnContext.Activity.Text}"; + await turnContext.SendActivityAsync(MessageFactory.Text(replyText, replyText), cancellationToken); + } +} +``` + +> Note that `Echobot` derives from the `ActivityHandler` class. + +**Teams AI library [Echo bot](https://github.com/microsoft/teams-ai/tree/main/dotnet/samples/01.messaging.echoBot)** + +[`Program.cs`](https://github.com/microsoft/teams-ai/blob/main/dotnet/samples/01.messaging.echoBot/Program.cs) + +```cs +// Create the storage to persist turn state +builder.Services.AddSingleton(); + +// Create the bot as a transient. In this case the ASP Controller is expecting an IBot. +builder.Services.AddTransient(sp => +{ + IStorage storage = sp.GetService(); + ApplicationOptions applicationOptions = new() + { + Storage = storage, + TurnStateFactory = () => + { + return new AppState(); + } + }; + + Application app = new(applicationOptions); + + // Listen for user to say "/reset" and then delete conversation state + app.OnMessage("/reset", ActivityHandlers.ResetMessageHandler); + + // Listen for ANY message to be received. MUST BE AFTER ANY OTHER MESSAGE HANDLERS + app.OnActivity(ActivityTypes.Message, ActivityHandlers.MessageHandler); + + return app; +}); +``` + +[`ActivityHandlers.cs`](https://github.com/microsoft/teams-ai/blob/main/dotnet/samples/01.messaging.echoBot/ActivityHandlers.cs) + +```cs + /// + /// Defines the activity handlers. + /// + public static class ActivityHandlers + { + /// + /// Handles "/reset" message. + /// + public static RouteHandler ResetMessageHandler = async (ITurnContext turnContext, AppState turnState, CancellationToken cancellationToken) => + { + turnState.DeleteConversationState(); + await turnContext.SendActivityAsync("Ok I've deleted the current conversation state", cancellationToken: cancellationToken); + }; + + /// + /// Handles messages except "/reset". + /// + public static RouteHandler MessageHandler = async (ITurnContext turnContext, AppState turnState, CancellationToken cancellationToken) => + { + int count = turnState.Conversation.MessageCount; + + // Increment count state. + turnState.Conversation.MessageCount = ++count; + + await turnContext.SendActivityAsync($"[{count}] you said: {turnContext.Activity.Text}", cancellationToken: cancellationToken); + }; + } +``` + +#### Optional ApplicationBuilder Class + +You may also use the `ApplicationBuilder` class to build your `Application`. This option provides greater readability and separates the management of the various configuration options (e.g., storage, turn state, AI options, etc). + +```cs +//// Constructor initialization method +// Application app = new() +// { +// storage +// }; + +// Build pattern method +var applicationBuilder = new ApplicationBuilder() + .WithStorage(storage); + +// Create Application +Application app = applicationBuilder.Build(); +``` + +## 2. Replace the activity handler implementations with specific route registration method. + +The `EchoBot` class derives from the `ActivityHandler` class. Each method in the class corresponds to a specific route registration method in the `Application` object. Here's a simple example: + +Given the `EchoBot` implementation: + +```cs +public class EchoBot : ActivityHandler +{ + protected override async Task OnMessageActivityAsync(ITurnContext turnContext, CancellationToken cancellationToken) + { + var replyText = $"Echo: {turnContext.Activity.Text}"; + await turnContext.SendActivityAsync(MessageFactory.Text(replyText, replyText), cancellationToken); + } +} +``` + +This is how a route should be added to the `Application` object: + +```cs +app.OnActivity(ActivityTypes.Message, async (ITurnContext turnContext, TurnState turnState, CancellationToken cancellationToken) => +{ + var replyText = $"Echo: {turnContext.Activity.Text}"; + await turnContext.SendActivityAsync(MessageFactory.Text(replyText, replyText), cancellationToken); +}); +``` + +> The `OnActivity` method allows you to register a possible route for the incomming activity. For each method in the `ActivityHandler` or `TeamsActivityHandler` class, there is an equivalent route registration method. + +If your bot derives from `ActivityHandler` or the `TeamsActivityHandler` refer to the following table to see which method maps to which `Application` route registration method. + +## Activity Handler Methods + +If your bot derives from the `TeamsActivityHandler` refer to the following table to see which method maps to which `Application` route registration method. + +#### Invoke Activities + +| `TeamsActivityHandler` method | `Application` route registration method | +| ------------------------------------------------------------ | ----------------------------------------------------------------------------------------------- | +| `OnTeamsO365ConnectorCardActionAsync` | `OnO365ConnectorCardAction` (usage: `app.OnO365ConnectorCardAction(...)`) | +| `OnTeamsFileConsentAsync` | Either `OnFileConsentAccept` or `OnFileConsentDecline` | +| `OnTeamsConfigFetchAsync` | `OnConfigFetch` | +| `OnTeamsConfigSubmitAsync` | `OnConfigSubmit` | +| `OnTeamsTaskModuleFetchAsync` | `TaskModules.OnFetch` (usage: `app.TaskModules.Fetch(...)`) | +| `OnTeamsTaskModuleSubmitAsync` | `TaskModules.OnSubmit` | +| `OnTeamsConfigSubmitAsync` | `MessageExtensions.OnQueryLink` (usage: `app.MessageExtensions.OnQueryLink(...)`) | +| `OnTeamsAnonymousAppBasedLinkQueryAsync` | `MessageExtensions.OnAnonymousQueryLink` | +| `OnTeamsMessagingExtensionQueryAsync` | `MessageExtensions.OnQuery` | +| `OnTeamsMessagingExtensionSelectItemAsync` | `MessageExtensions.OnSelectItem` | +| `OnTeamsMessagingExtensionSubmitActionDispatchAsync` | `MessageExtensions.OnSubmitAction` | +| `OnTeamsMessagingExtensionFetchTaskAsync` | `MessageExtensions.OnFetchTask` | +| `OnTeamsMessagingExtensionConfigurationQuerySettingUrlAsync` | `MessageExtensions.OnQueryUrlSetting` | +| `OnTeamsMessagingExtensionConfigurationSettingAsync` | `MessageExtensions.OnConfigureSettings` | +| `OnTeamsMessagingExtensionCardButtonClickedAsync` | `MessageExtensions.OnCardButtonClicked` | +| `OnTeamsSigninVerifyStateAsync` | N/A (you should use the built-in user authentication feature instead of handling this manually) | + +#### Conversation Update Activities + +These are the following methods from the `TeamsActivityHandler`: + +- `onTeamsChannelCreatedAsync` +- `onTeamsChannelDeletedAsync` +- `onTeamsChannelRenamedAsync` +- `onTeamsTeamArchivedAsync` +- `onTeamsTeamDeletedAsync` +- `onTeamsTeamHardDeletedAsync` +- `onTeamsChannelRestoredAsync` +- `onTeamsTeamRenamedAsync` +- `onTeamsTeamRestoredAsync` +- `onTeamsTeamUnarchivedAsync` + +These activities can be handled using the `Application.OnConversationUpdate` method. + +For example in the `TeamsActivityHandler`: + +```cs +protected virtual Task OnTeamsChannelCreatedAsync(ChannelInfo channelInfo, TeamInfo teamInfo, ITurnContext turnContext, CancellationToken cancellationToken) +{ + // Handle channel created activity +} +``` + +The `Application` equivalent: + +```cs +app.OnConversationUpdate(ConversationUpdateEvents.ChannelCreated, async (ITurnContext turnContext, TurnState turnState, CancellationToken cancellationToken) => +{ + // Handle channel created activity +}); +``` + +> Note that the first parameter `event` specifies which conversation update event to handle. + +#### Message Activites + +| `TeamsActivityHandler` method | `Application` route registration method | +| -------------------------------- | -------------------------------------------------------- | +| `OnMessage` | `OnMessage` (usage: `app.OnMessage(...)`) | +| `OnTeamsMessageEditAsync` | `OnMessageEdit` | +| `OnTeamsMessageUndeletedAsync` | `OnMessageUndelete` | +| `OnTeamsMessageSoftDeleteAsync` | `OnMessageDelete` | +| `OnMessageReactionActivityAsync` | `OnMessageReactionsAdded` or `OnMessageReactionsRemoved` | +| `OnTeamsReadRecieptAsync` | `OnTeamsReadReceipt` | + +#### Meeting Activities + +| `TeamsActivityHandler` method | `Application` route registration method | +| -------------------------------------- | ------------------------------------------------------- | +| `OnTeamsMeetingStartAsync` | `Meetings.OnStart` (usage: `app.Meetings.OnStart(...)`) | +| `OnTeamsMeetingEndAsync` | `Meetings.OnEnd` | +| `OnTeamsMeetingParticipantsJoinAsync` | `Meetings.OnParticipantsJoin` | +| `OnTeamsMeetingParticipantsLeaveAsync` | `Meetings.OnParticipantsLeave` | + +#### Other Activities + +If there are activities for which there isn't a corresponding route registration method, you can use the generic route registration method `Application.OnActivity` and specify a custom selector function given the activity object as input. diff --git a/dotnet/samples/04.ai.a.teamsChefBot/files/concepts.txt b/dotnet/samples/04.ai.a.teamsChefBot/files/concepts.txt new file mode 100644 index 000000000..3aaaf6f09 --- /dev/null +++ b/dotnet/samples/04.ai.a.teamsChefBot/files/concepts.txt @@ -0,0 +1,33 @@ +# Concepts + +Here you will find short guides on features available in the library. + + + +**General Concepts** + +| Name | Description | +| --------------------------------------- | ------------------------------------------------- | +| [Turn Context and Turn State](TURNS.md) | Explains what the turn context and turn state is. | +| [The Application class](APPLICATION.md) | What the `Application` class is and how it works. | +| [User Authentication](USER-AUTH.md) | Describes user authentication features out of the box | + + +
+ +**The AI System Concepts** +| Name | Description | +| --------------------------------------- | ------------------------------------------------- | +| [The AI System](AI-SYSTEM.md) | Describes the AI System. | +| [Planner](PLANNER.md) | Planner and plans. | +| [Moderator](MODERATOR.md) | The moderator. | +| [Actions](ACTIONS.md) | The action in the AI system. | + +
+ +**Action Planner Concepts** +| Name | Description | +| --------------------------------------- | ------------------------------------------------- | +| [The Action Planner](ACTION-PLANNER.md) | Describes the Action Planner. | +| [Prompt Management](PROMPTS.md) | Prompts, prompt templates, and creating prompt templates. | +| [Augmentations](AUGMENTATIONS.md) | Monologue and sequence augmentations. | diff --git a/dotnet/samples/04.ai.a.teamsChefBot/files/data-sources.txt b/dotnet/samples/04.ai.a.teamsChefBot/files/data-sources.txt new file mode 100644 index 000000000..b63f8f15d --- /dev/null +++ b/dotnet/samples/04.ai.a.teamsChefBot/files/data-sources.txt @@ -0,0 +1,47 @@ +# Data Sources +Data sources allow the injection of relevant information from external sources into prompts, such as vector databases or cognitive search. A vector data source makes it easy to add [RAG](https://en.wikipedia.org/wiki/Prompt_engineering#Retrieval-augmented_generation) to any prompt, allowing for better and more accurate replies from the bot. + +Within each Action Planner’s prompt management system, a list of data sources can be registered. For each data source, a max number of tokens to use is specified, via `maxTokens`. + +## Customize a Data Source +1. Construct a class that implements our `DataSource` base class. + +2. Register the data source with your prompt management system through the `addDataSource` function. + +3. To augment a specific prompt, you can specify the name(s) of the data sources within the prompt's `config.json` file. + +Our simplest example (primarily for testing) is `TextDataSource`, which adds a static block of text to a prompt. + +Our most complex example is the `VectraDataSource` in the Chef Bot sample, which uses an external library called Vectra. + +### Customized Example of VectraDataSource +Here is an example of the configuration for the +[Chef Bot sample](https://github.com/microsoft/teams-ai/tree/main/js/samples/04.ai.a.teamsChefBot): + +**JS** +```js +// Inside VectraDataSource.ts +export class VectraDataSource implements DataSource +``` + +```js +// Inside of index.ts +planner.prompts.addDataSource( + new VectraDataSource({ + name: 'teams-ai', + apiKey: process.env.OPENAI_KEY!, + azureApiKey: process.env.AZURE_OPENAI_KEY!, + azureEndpoint: process.env.AZURE_OPENAI_ENDPOINT!, + indexFolder: path.join(__dirname, '../index') + }) +); +``` +Inside the prompt's config.json. Here, `teams-ai` denotes the name of the VectraDataSource, and 1200 is `maxTokens`. +```json +"augmentation": { + "augmentation_type": "none", + "data_sources": { + "teams-ai": 1200 + } +} +``` \ No newline at end of file diff --git a/dotnet/samples/04.ai.a.teamsChefBot/files/getting started.txt b/dotnet/samples/04.ai.a.teamsChefBot/files/getting started.txt new file mode 100644 index 000000000..795e6d138 --- /dev/null +++ b/dotnet/samples/04.ai.a.teamsChefBot/files/getting started.txt @@ -0,0 +1,31 @@ +# Getting Started + +_**Navigation**_ +- [**00.OVERVIEW**](./README.md) +- [01.QUICKSTART](./QUICKSTART.md) +- [02.SAMPLES](./SAMPLES.md) +___ + +### Get started with the Teams AI Library + +The first step is to get a basic bot running E2E through the [Quickstart](./QUICKSTART.md) guide to build Echo Bot, which echoes back any message sent to it. This simple bot helps to familiarize yourself with the Teams AI Library and ensures your system is set up correctly to move onto the AI powered samples. + +If you would rather dive into an AI sample first, check out the fully conversational Teams Chef Bot sample on the [samples](./SAMPLES.md) page. + +There are numerous samples to try showcasing the different capabilities of the Teams AI Library. + +### Migration + +If you have a bot built using the BotFramework SDK and want to migrate to the Teams AI library, see [Migration](./MIGRATION/README.md). + +### Concepts + +To dive deeper into the library learning more about its AI components and concepts, see [Concepts](./CONCEPTS/README.md). + +### Useful links + +- [Microsoft Learn Docs](https://learn.microsoft.com/en-us/microsoftteams/platform/bots/how-to/teams%20conversational%20ai/teams-conversation-ai-overview) +- [C# samples folder](https://github.com/microsoft/teams-ai/tree/main/dotnet/samples) +- [JS samples folder](https://github.com/microsoft/teams-ai/tree/main/js/samples) +- [@microsoft/teams-ai package on npm](https://www.npmjs.com/package/@microsoft/teams-ai) +- [Microsoft.Teams.AI on nuget.org](https://www.nuget.org/packages/Microsoft.Teams.AI) diff --git a/dotnet/samples/04.ai.a.teamsChefBot/files/github.txt b/dotnet/samples/04.ai.a.teamsChefBot/files/github.txt new file mode 100644 index 000000000..a0ab142a0 --- /dev/null +++ b/dotnet/samples/04.ai.a.teamsChefBot/files/github.txt @@ -0,0 +1,19 @@ +# Teams AI Library + +Welcome to the Teams AI Library .NET package! + +This SDK is specifically designed to assist you in creating bots capable of interacting with Teams and Microsoft 365 applications. It is constructed using the [Bot Framework SDK](https://github.com/microsoft/botbuilder-dotnet) as its foundation, simplifying the process of developing bots that interact with Teams' artificial intelligence capabilities. See the [Teams AI repo README.md](https://github.com/microsoft/teams-ai), for general information, and JavaScript support is available via the [js](https://github.com/microsoft/teams-ai/tree/main/js) folder. + +Requirements: + +* [.NET 6.0 SDK](https://dotnet.microsoft.com/download/dotnet/6.0) +* (Optional) [Azure OpenAI](https://azure.microsoft.com/en-us/products/ai-services/openai-service) resource or an account with [OpenAI](https://platform.openai.com/) + + +## Getting Started + +To get started, take a look at the [getting started docs](https://github.com/microsoft/teams-ai/blob/main/getting-started/README.md). + +## Migration + +If you're migrating an existing project, switching to add on the Teams AI Library layer is quick and simple. See the [migration guide](https://github.com/microsoft/teams-ai/blob/main/getting-started/MIGRATION/DOTNET.md). diff --git a/dotnet/samples/04.ai.a.teamsChefBot/files/js-migration.txt b/dotnet/samples/04.ai.a.teamsChefBot/files/js-migration.txt new file mode 100644 index 000000000..445da387c --- /dev/null +++ b/dotnet/samples/04.ai.a.teamsChefBot/files/js-migration.txt @@ -0,0 +1,162 @@ +# Migrating from the BotFramework SDK (Javascript) + +_**Navigation**_ +- [00.OVERVIEW](./README.md) +- [**01.JS**](./JS.md) +- [02.DOTNET](./DOTNET.md) +___ + +If you have a bot built using the JS BotFramework SDK, the following will help you update your bot to the Teams AI library. + +## New Project or Migrate existing app + +Since the library builds on top of the BF SDK, much of the bot logic can be directly carried over to the Teams AI app. If you want to start with a new project, set up the Echo bot sample in the [quick start](../.QUICKSTART.md) guide and jump directly to [step 2](#2-replace-the-activity-handler-implementations-with-specific-route-handlers). + +If you want to migrate your existing app start with [step 1](#1-replace-the-activityhandler-with-the-application-object). + +## 1. Replace the ActivityHandler with the Application object + +Replace `ActivityHandler` with `Application`. + +```diff ++ import { Application, TurnState } from "@microsoft/teams-ai"; + +- const app = BotActivityHandler(); + +// Define storage and application ++ const storage = new MemoryStorage(); ++ const app = new Application({ + storage +}); +``` + +### Optional ApplicationBuilder Class + +You may also use the `ApplicationBuilder` class to build your `Application`. This option provides greater readability and separates the management of the various configuration options (e.g., storage, turn state, AI options, etc). + +```js +//// Constructor initialization method +// const app = new Application() +// { +// storage +// }; + +// Build pattern method +const app = new ApplicationBuilder() + .withStorage(storage) + .build(); // this internally calls the Application constructor +``` + +## 2. Replace the activity handler implementations with specific route registration methods + +The `BotActivityHandler` class derives from the `ActivityHandler` class. Each method in the class corresponds to a specific route registration method (`handler`) in the `Application` object. Here's a simple example: + +Given the `BotActivityHandler` implementation: + +```js +class BotActivityHandler extends ActivityHandler { + constructor() { + this.onMessage(async (context, next) => { + const replyText = `Echo: ${ context.activity.text }`; + await context.sendActivity(MessageFactory.text(replyText, replyText)); + await next(); + }); + } +} +``` + +This is how a route should be added to the `Application` object: + +```js +app.activity(ActivityTypes.Message, async(context: TurnContext, state: TurnState) => { + const replyText = `Echo: ${ context.activity.text }`; + await context.sendActivity(replyText); +}); +``` + +> The `activity` method is refered as a *route registration method*. For each method in the `ActivityHandler` or `TeamsActivityHandler` class, there is an equivalent route registration method. + +Your existing BF app will probably have different activity handlers implemented. To migrate that over with Teams AI route registration methods see the following. + +## Activity Handler Methods + +If your bot derives from the `TeamsActivityHandler` refer to the following table to see which method maps to which `Application` route registration method. + +### Invoke Activities + +| `TeamsActivityHandler` method | `Application` route registration method | +| ----------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | +| `handleTeamsO365ConnectorCardAction` | `O365ConnectorCardAction` (usage: `app.O365ConnectorCardAction(...)`) | +| `handleTeamsFileConsent` | Either `fileConsentAccept` or `fileConsentDecline` | +| `handleTeamsTaskModuleFetch` | `taskModules.fetch` (usage: `app.taskModules.Fetch(...)`) | +| `handleTeamsTaskModuleSubmit` | `taskModules.submit` | +| `handleTeamsConfigFetch` | `taskModules.configFetch` | +| `handleTeamsConfigSubmit` | `taskModules.configSubmit` | +| `handleTeamsAppBasedLinkQuery` | `messageExtensions.queryLink` (usage: `app.MessageExtensions.queryLink(...)`) | +| `handleTeamsAnonymousAppBasedLinkQuery` | `messageExtensions.anonymousQueryLink` | +| `handleTeamsMessagingExtensionQuery` | `messageExtensions.query` | +| `handlehandleTeamsMessageExtensionSelectItem` | `messageExtensions.selectItem` | +| `handleTeamsMessagingExtensionSubmitActionDispatch` | `messageExtensions.submitAction` | +| `handleTeamsMessagingExtensionFetchTask` | `messageExtensions.fetchTask` | +| `handleTeamsMessagingExtensionConfigurationQuerySettingUrl` | `messageExtensions.queryUrlSetting` | +| `handleTeamsMessagingExtensionConfigurationSetting` | `messageExtensions.configureSettings` | +| `handleTeamsMessagingExtensionCardButtonClicked` | `messageExtensions.handleOnButtonClicked` | +| `handleTeamsSigninVerifyState` | N/A (you should use the built-in user authentication feature instead of handling this manually) | +| `handleTeamsSigninTokenExchange` | N/A (you should use the built-in user authentication feature instead of handling this manually) | + +### Conversation Update Activities + +These are the following methods from the `TeamsActivityHandler`: + +- `onTeamsChannelCreated` +- `onTeamsChannelDeleted` +- `onTeamsChannelRenamed` +- `onTeamsTeamArchived` +- `onTeamsTeamDeleted` +- `onTeamsTeamHardDeleted` +- `onTeamsChannelRestored` +- `onTeamsTeamRenamed` +- `onTeamsTeamRestored` +- `onTeamsTeamUnarchived` + +These activities can be handled using the `Application.conversationUpdate` method. + +For example in the `TeamsActivityHandler`: + +```js +protected async onTeamsChannelCreated(context: TurnContext): Promise { + // handle teams channel creation. +} +``` + +The `Application` equivalent: + +```js +app.conversationUpdate('channelCreated', (context: TurnContext, state: TurnState) => { + // handle teams channel creation. +}) +``` + +> Note that the first parameter `event` specifies which conversation update event to handle. It only accepts specific values that can be found through your IDE's intellisense. + +### Message Activites + +| `TeamsActivityHandler` method | `Application` route registration method | +| --------------------------------------------------------------------------- | -------------------------------------------------------------------------- | +| `OnMessage` | `message` | +| `OnTeamsMessageUndelete`, `OnTeamsMessageEdit` & `OnTeamsMessageSoftDelete` | `messageEventUpdate` , the first parameter `event` specifies the activity. | +| `OnMessageReactionActivity` | `messageReactions` | +| `OnTeamsReadReciept` | `teamsReadReceipt` | + +### Meeting Activities + +| `TeamsActivityHandler` method | `Application` route registration method | +| --------------------------------- | ---------------------------- | +| `OnTeamsMeetingStart` | `meetings.start` | +| `OnTeamsMeetingEnd` | `meetings.end` | +| `onTeamsMeetingParticipantsJoin` | `meetings.participantsJoin` | +| `onTeamsMeetingParticipantsLeave` | `meetings.participantsLeave` | + +### Other Activities + +If there are activities for which there isn't a corresponding route registration method, you can use the generic route registration method `Application.activity` and specify a custom selector function given the activity object as input. diff --git a/dotnet/samples/04.ai.a.teamsChefBot/files/migration.txt b/dotnet/samples/04.ai.a.teamsChefBot/files/migration.txt new file mode 100644 index 000000000..b7818b029 --- /dev/null +++ b/dotnet/samples/04.ai.a.teamsChefBot/files/migration.txt @@ -0,0 +1,29 @@ +# Migration + +_**Navigation**_ +- [**00.OVERVIEW**](./README.md) +- [01.JS](./JS.md) +- [02.DOTNET](./DOTNET.md) +___ + +### Why you should migrate to the Teams AI library + +Is your Teams App currently built using the [BotFramework (BF) SDK](https://github.com/microsoft/botframework-sdk)? If so you should migrate it to the Teams AI library. + +Here are a few reasons why: + +1. Take advantage of the advanced AI system to build complex LLM-powered Teams applications. +2. User authentication is built right into the library, simplifying the set up process. +3. The library builds on top of fundamental BF SDK tools & concepts, so your existing knowledge is transferrable. +4. The library will continue to support latest tools & APIs in the LLM space. + +### Difference between the Teams AI library and BF SDK + +This library provides the `Application` object which replaces the traditional `ActivityHandler` object. It supports a simpler fluent style of authoring bots versus the inheritance based approach used by the `ActivityHandler` class. The `Application` object has built-in support for calling into the library's AI system which can be used to create bots that leverage Large Language Models (LLM) and other AI capabilities. It also has built-in support for configuring user authentication to access user data from third-party services. + +### Guides + +Here are the guides on how to migrate from the BotFramework SDK: + +1. [JS Migration](JS.md) +2. [C# Migration](DOTNET.md) diff --git a/dotnet/samples/04.ai.a.teamsChefBot/files/moderator.txt b/dotnet/samples/04.ai.a.teamsChefBot/files/moderator.txt new file mode 100644 index 000000000..b2e3940c6 --- /dev/null +++ b/dotnet/samples/04.ai.a.teamsChefBot/files/moderator.txt @@ -0,0 +1,68 @@ +# Moderator + +The `Moderator` is responsible for reviewing the input prompt and approving the AI generated plans. It is configured when orchestrating the `Application` class. + +The AI system is such that developers can create their own moderator class by simply implementing the moderator interface. The library has a few native moderators that can be used out of the box: + +| Name | Description | +| -------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | +| [OpenAIModerator](#openai-moderator) | Wrapper around OpenAI's [Moderation API](https://platform.openai.com/docs/api-reference/moderations). | +| [AzureContentSafetyModerator](#azure-content-safety-moderator) | Wrapper around [Azure Content Safety](https://learn.microsoft.com/en-us/azure/ai-services/content-safety/overview) API | + +## OpenAI Moderator + +Here's an example of configuring the `OpenAIModerator`: + +**JS** +```js +const moderator = new OpenAIModerator({ + apiKey: process.env.OPENAI_KEY!, + moderate: 'both' +}); + +const app = new Application({ + storage, + ai: { + planner, + moderator + } +}); +``` + +**C#** +```cs +OpenAIModeratorOptions moderatorOptions = new(config.OpenAI.ApiKey, ModerationType.Both); +IModerator moderator = new OpenAIModerator(moderatorOptions); + +AIOptions aIOptions = new(planner) +{ + Moderator = moderator +}; + +var app = new ApplicationBuilder() +.WithStorage(storage) +.WithAIOptions(aIOptions) +.Build(); +``` +> This snippet is taken from the [Twenty Questions bot] sample. +> Note for C# application, the moderator should be registered to the Web app's service collection as a singleton. + +## Azure Content Safety Moderator + +Here's an example of configuring the `AzureContentSafetyModerator`: + +**JS** +```js +const moderator = new AzureContentSafetyModerator({ + apiKey: process.env.AZURE_CONTENT_SAFETY_KEY, + endpoint: process.env.AZURE_CONTENT_SAFETY_ENDPOINT, + apiVersion: '2023-04-30-preview', + moderate: 'both' +}); +``` + +**C#** +```cs +AzureContentSafetyModeratorOptions moderatorOptions = new(config.Azure.ContentSafetyApiKey, config.Azure.ContentSafetyEndpoint, ModerationType.Both); +IModerator moderator = new AzureContentSafetyModerator(moderatorOptions); +``` \ No newline at end of file diff --git a/dotnet/samples/04.ai.a.teamsChefBot/files/planner.txt b/dotnet/samples/04.ai.a.teamsChefBot/files/planner.txt new file mode 100644 index 000000000..00e766a7d --- /dev/null +++ b/dotnet/samples/04.ai.a.teamsChefBot/files/planner.txt @@ -0,0 +1,85 @@ +# Planner + +The planner receives the user's ask and returns a plan on how to accomplish the request. The user's ask is in the form of a prompt or prompt template. It does this by using AI to mix and match atomic functions (called _actions_) registered to the AI system so that it can recombine them into a series of steps that complete a goal. + +This is a powerful concept because it allows you to create actions that can be used in ways that you as a developer may not have thought of. + +For instance, If you have a task with `Summarize` & `SendEmail` actions, the planner could combine them to create workflows like "Rewrite the following report into a short paragraph and email it to johndoe@email.com" without you explicitly having to write code for those scenarios. + +The planner is an extensible part of the AI system. This means that a custom planner can be created for your specific needs. Out of the box, the Teams AI library supports the following planners. + + +| Planner | Description | C# | JS/TS | Python | +| ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------- | --- | ----- | ------ | +| [ActionPlanner](./ACTION-PLANNER.md) | Powerful planner that uses LLMs to generate plans. It has a built-in prompt management, LLM modularity, amongst other features. | ✅ | ✅ | ❌ | +| AssistantsPlanner | A planner that uses OpenAI's Assistants APIs to generate plans. | ✅ | ✅ | ❌ | + +### Plan + +A plan is the entity that is generated by the planner. It is a JSON object of the following shape: + +```json +{ + "type": "plan", + "commands": [ + { + "type": "DO", + "action": "", + "parameters": { + "": "" + } + }, + { + "type": "SAY", + "response": "" + } + ] +} +``` + +A plan consists of two types of commands and their entities: + +- **SAY**: Sends a message to the user. + - _response_: The string message to send. +- **DO**: AI system will execute a specific _action_, passing in the generated parameters. + - _action_: A lambda function registered to the AI system + - _parameters_: A dictionary passed to the action. + +The JSON object string is returned by the LLM and deserialized into an object. + +#### Example + +Here's an example of a plan for the following ask: + +User: +`Create a grocery shopping list and add bananas to it.` + +Plan: + +```json +{ + "type": "plan", + "commands": [ + { + "type": "DO", + "action": "createList", + "entities": { + "name": "Grocery Shopping" + } + }, + { + "type": "DO", + "action": "addItem", + "entities": { + "name": "Bananas" + } + }, + { + "type": "SAY", + "response": "Created a grocery shopping list and added a banana to it." + } + ] +} +``` + +This plan is executed in sequential order. So first the list will be created and then an item will be added to it. Finally, the `response` message will be sent to the user. diff --git a/dotnet/samples/04.ai.a.teamsChefBot/files/prompts.txt b/dotnet/samples/04.ai.a.teamsChefBot/files/prompts.txt new file mode 100644 index 000000000..efdad7742 --- /dev/null +++ b/dotnet/samples/04.ai.a.teamsChefBot/files/prompts.txt @@ -0,0 +1,192 @@ +# Prompts + +Prompts play a crucial role in communicating and directing the behavior of Large Language Models (LLMs) AI. +They serve as inputs or queries that users can provide to elicit specific responses from a model. + +Here's a prompt that asks the LLM for name suggestions: + +_Input:_ + +``` +Give me 3 name suggestions for my pet golden retriever. +``` + +_Response:_ + +``` +Some possible name suggestions for a pet golden retriever are: + +- Bailey +- Sunny +- Cooper +``` + +# Prompt Template + +Prompt templates are a simple and powerful way to +define and compose AI functions **using plain text**. +You can use it to create natural language prompts, generate responses, extract +information, **invoke other prompts,** or perform any other task that can be +expressed with text. + +The language supports two basic features that allow you to include +variables and call functions. + +**Simple Example:** + +Here's an example of a prompt template: + +``` +Give me 3 name suggestions for my pet {{ $petName }}. +``` + +`$petName` is a variable that is populated on runtime when the template is rendered. + +## Prompt Template Language + +You don't need to write any code or import any external libraries, just use the +double curly braces {{...}} to embed expressions in your prompts. +Teams AI will parse your template and execute the logic behind it. +This way, you can easily integrate AI into your apps with minimal effort and +maximum flexibility. + +### Variables + +To include a variable value in your text, use the `{{$variableName}}` syntax. For example, if you have a variable called name that holds the user's name, you can write: + +`Hello {{$name}}, nice to meet you!` + +This will produce a greeting with the user's name. + +Spaces are ignored, so if you find it more readable, you can also write: + +`Hello {{ $name }}, nice to meet you!` + +Here's how to define variables in code: + +**C#** + +In an *action* or *route handler* where the turn state object is available: +```cs +state.Temp.Post = "Lorem Ipsum..." +``` + +The usage in the prompt: +``` +This is the user's post: {{ $post }} +``` + +> Note: The `turnState.Temp.Post = ...` updates a dictionary with the `post` key under the hood from the [AI Message Extension sample](https://github.com/microsoft/teams-ai/blob/a20f8715d3fe81e11c330853e3930e22abe298af/dotnet/samples/04.ai.b.messageExtensions.gptME/ActivityHandlers.cs#L156). + +**Javascript** + +```typescript +app.beforeTurn((context, state) => { + state.temp.post = "Lorem Ipsum..."; +}); +``` + +The usage in the prompt: +``` +This is the user's post: {{ $post }} +``` + +You can simply add to the `state.temp` object, and it will be accessible from the prompt template on runtime. Note that the safest place to do that would be in the `beforeTurn` activity because it will execute before any activity handler or action. + + +**Default Variables** + +The following are variables accessible in the prompt template without having to manually configure them. These are pre-defined in the turn state and populated by the library. Users can override them by changing it in the turn state. + +| Variable name | Description | +| ------------- | ---------------------------------------------- | +| `input` | Input passed from the user to the AI Library. | +| `lastOutput` | Output returned from the last executed action. | + +### Function calls + +To call an external function and embed the result in your text, use the `{{ functionName }}` syntax. For example, if you have a function called `diceRoll` that returns a random number between 1 and 6, you can write: + +`The dice roll has landed on: {{ diceRoll }}` + +**C#** + +In the `Application` class, + +```cs +prompts.AddFunction("diceRoll", async (context, memory, functions, tokenizer, args) => +{ + int diceRoll = // random number between 1 and 6 + return diceRoll; +}); +``` + +**Javascript** + +```typescript +prompts.addFunction('diceRoll', async (context, state, functions, tokenizer, args) => { + let diceRoll = // random number between 1 and 6 + return diceRoll; +}); +``` + +# Creating Prompt Templates + +Each prompt template is a folder with two files, `skprompt.txt` and `config.json`. The folder name is the prompt template's name which can be referred to in your code. The `skprompt.txt` file contains the prompt's text, which can contain natural language or prompt template syntax as defined in the previous section. The `config.json` file specifies the prompt completion configuration. + +Here's an example of a prompt template from the [Twenty Questions](https://github.com/microsoft/teams-ai/blob/c5ec11842b808e48cd214b3cb52da84e5811da33/js/samples/04.e.twentyQuestions) sample. + +*skprompt.txt* +``` +You are the AI in a game of 20 questions. +The goal of the game is for the Human to guess a secret within 20 questions. +The AI should answer questions about the secret. +The AI should assume that every message from the Human is a question about the secret. + +GuessCount: {{$conversation.guessCount}} +RemainingGuesses: {{$conversation.remainingGuesses}} +Secret: {{$conversation.secretWord}} + +Answer the human's question but do not mention the secret word. +``` + +*config.json* +```json +{ + "schema": 1.1, + "description": "A bot that plays a game of 20 questions", + "type": "completion", + "completion": { + "completion_type": "chat", + "include_history": false, + "include_input": true, + "max_input_tokens": 2000, + "max_tokens": 256, + "temperature": 0.7, + "top_p": 0.0, + "presence_penalty": 0.6, + "frequency_penalty": 0.0 + } +} +``` + +> Note that the configuration properties in the file do not include all the possible configurations. To learn more about the description of each configuration and all the supported configurations see the [`PromptTemplatConfig`](https://github.com/microsoft/teams-ai/blob/2d43f5ca5b3bf27844f760663641741cae4a3243/js/packages/teams-ai/src/prompts/PromptTemplate.ts#L46C18-L46C39) Typescript interface. + +These files can be found under the `src/prompts/chat/` folder. So, this prompt template's name is `chat`. Then, to plug these files in the Action Planner, the prompt manager has to be created with the folder path specified and then passed into the Action Planner constructor: + +**C#** +```cs +PromptManager prompts = new PromptManager(new PromptManagerOptions(){ + PromptFolder = "./prompts" +}); +``` + +The file path is relative to the source of file in which the `PromptManager` is created. In this case the `Program.cs` was in the same folder as the `prompts` folder. + +**Javascript** +```ts +const prompts = new PromptManager({ + promptsFolder: path.join(__dirname, '../src/prompts') +}); +``` + diff --git a/dotnet/samples/04.ai.a.teamsChefBot/files/quickstart.txt b/dotnet/samples/04.ai.a.teamsChefBot/files/quickstart.txt new file mode 100644 index 000000000..24dd1d130 --- /dev/null +++ b/dotnet/samples/04.ai.a.teamsChefBot/files/quickstart.txt @@ -0,0 +1,153 @@ +# Quickstart + +_**Navigation**_ +- [00.OVERVIEW](./README.md) +- [**01.QUICKSTART**](./QUICKSTART.md) +- [02.SAMPLES](./SAMPLES.md) +___ + +In this quickstart we will show you how to get the Echo Bot up and running. The Echo Bot echoes back messages sent to it while keeping track of the number of messages sent by the user. + + +* [C# Quickstart](#c-quickstart) +* [Javascript Quickstart](#javascript) + + +## C# Quickstart + +This guide will show you have the set up the Echo Bot using the C# library. + +### Prerequisites + +To get started, ensure that you have the following tools: + +| Install | For using... | +| ------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [Visual Studio](https://visualstudio.microsoft.com/downloads/) (17.7.0 or greater) | C# build environments. Use the latest version. | +| [Teams Toolkit](https://learn.microsoft.com/en-us/microsoftteams/platform/toolkit/toolkit-v4/teams-toolkit-fundamentals-vs?pivots=visual-studio-v17-7) | Microsoft Visual Studio extension that creates a project scaffolding for your app. Use the latest version. | +| [Git](https://git-scm.com/downloads) | Git is a version control system that helps you manage different versions of code within a repository. | +| [Microsoft Teams](https://www.microsoft.com/microsoft-teams/download-app) | Microsoft Teams to collaborate with everyone you work with through apps for chat, meetings, and call-all in one place. | +| [Microsoft Edge](https://www.microsoft.com/edge) (recommended) or [Google Chrome](https://www.google.com/chrome/) | A browser with developer tools. | +| [Microsoft 365 developer account](/microsoftteams/platform/concepts/build-and-test/prepare-your-o365-tenant) | Access to Teams account with the appropriate permissions to install an app and [enable custom Teams apps and turn on custom app uploading](../../../concepts/build-and-test/prepare-your-o365-tenant.md#enable-custom-teams-apps-and-turn-on-custom-app-uploading). | + +
+ +### Build and run the sample app + +1. Clone the teams-ai repository + + ```cmd + git clone https://github.com/microsoft/teams-ai.git + ``` + +2. Open **Visual Studio** and select `Open a project or a solution`. + +3. Navigate to the `teams-ai/dotnet/samples/01.messaging.echoBot` folder and open the `Echobot.sln` file. + +4. In the debug dropdown menu, select *Dev Tunnels > Create A Tunnel* (Tunnel type: `Persistent` & Access: `Public`) or select an existing public dev tunnel. Ensure that the dev tunnel is selected. + + ![create a tunnel](https://learn.microsoft.com/en-us/microsoftteams/platform/assets/images/bots/dotnet-ai-library-dev-tunnel.png) + + + +5. Right-click your project and select *Teams Toolkit > Prepare Teams App Dependencies* + + ![prepare teams app dependencies](https://learn.microsoft.com/en-us/microsoftteams/platform/assets/images/bots/dotnet-ai-library-prepare-teams-app.png) + + > Note: If you are running into errors in this step, ensure that you have correctly configured the dev tunnels in step 4. + +6. If prompted, sign in with a Microsoft 365 account for the Teams organization you want to install the app to. + + > If you do not have permission to upload custom apps (sideloading), Teams Toolkit will recommend creating and using a [Microsoft 365 Developer Program](https://developer.microsoft.com/microsoft-365/dev-program) account - a free program to get your own dev environment sandbox that includes Teams. + +7. Press F5, or select the `Debug > Start Debugging` menu in Visual Studio. If step 3 was completed correctly then this should launch Teams on the browser. + +8. You should be prompted to sideload a bot into teams. Click the `Add` button to load the app in Teams. + + ![add echobot](./assets/quickstart-echobot-add.png) + +9. This should redirect you to a chat window with the bot. + + ![demo-image](./assets/quickstart-echobot-demo.png) + + +## Javascript + +This guide will show you have the set up the Echo Bot using the JS library. + +### Prerequisite + +| Install | For using... | +| ----------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [Visual Studio Code](https://code.visualstudio.com/download) | Typescript build environments. Use the latest version. | +| [Teams Toolkit](https://marketplace.visualstudio.com/items?itemName=TeamsDevApp.ms-teams-vscode-extension) (5.3.x or greater) | Microsoft Visual Studio Code extension that creates a project scaffolding for your app. Use the latest version. | +| [Git](https://git-scm.com/downloads) | Git is a version control system that helps you manage different versions of code within a repository. | +| [Node.js](https://nodejs.org/en) (16 or 18) | Microsoft Teams to collaborate with everyone you work with through apps for chat, meetings, and call-all in one place. | +| [Microsoft Teams](https://www.microsoft.com/microsoft-teams/download-app) | Microsoft Teams to collaborate with everyone you work with through apps for chat, meetings, and call-all in one place. | +| [Microsoft Edge](https://www.microsoft.com/edge) (recommended) or [Google Chrome](https://www.google.com/chrome/) | A browser with developer tools. | +| [Microsoft 365 developer account](/microsoftteams/platform/concepts/build-and-test/prepare-your-o365-tenant) | Access to Teams account with the appropriate permissions to install an app and [enable custom Teams apps and turn on custom app uploading](../../../concepts/build-and-test/prepare-your-o365-tenant.md#enable-custom-teams-apps-and-turn-on-custom-app-uploading). || +[Yarn](https://yarnpkg.com/) (1.22.x or greater) | Node.js package manager used to install dependencies and build samples. | + +### Build and run the sample app + +1. Clone the repository. + ```cmd + git clone https://github.com/microsoft/teams-ai.git + ``` + +2. Go to **Visual Studio Code**. + +3. Select `File > Open Folder`. + +4. Go to the location where you cloned teams-ai repo and select the `teams-ai` folder. + +5. Click `Select Folder`. + + ![:::image type="content" source="../../../assets/images/bots/ai-library-dot-net-select-folder.png" alt-text="Screenshot shows the teams-ai folder and the Select Folder option.":::](https://learn.microsoft.com/en-us/microsoftteams/platform/assets/images/bots/ai-library-dot-net-select-folder.png) + +6. Select `View > Terminal`. A terminal window opens. + +7. In the terminal window, run the following command to go to the js folder: + + ``` + cd ./js/ + ``` + +8. Run the following command to install dependencies: + + ```terminal + yarn install + ``` + +9. Run the following command to build project and samples: + + ```terminal + yarn build + ``` + +10. After the dependencies are installed and project is built, select `File > Open Folder`. + +11. Go to `teams-ai > js > samples > 01.messaging.a.echoBot` and click `Select Folder`. This open the echo bot sample folder in vscode. + +12. From the left pane, select `Teams Toolkit`. + +13. Under `ACCOUNTS`, sign in to the following: + + * **Microsoft 365 account** + + +14. To debug your app, press the **F5** key. + + A browser tab opens a Teams web client requesting to add the bot to your tenant. + +15. Select **Add**. + + ![add-image](./assets/quickstart-echobot-add.png) + + A chat window opens. + +16. In the message compose area, send a message to invoke the bot. + + ![demo-image](./assets/quickstart-echobot-demo.png) + +The bot will echo back what the user sends it while keeping track of the number of messages sent by the user. diff --git a/dotnet/samples/04.ai.a.teamsChefBot/files/samples.txt b/dotnet/samples/04.ai.a.teamsChefBot/files/samples.txt new file mode 100644 index 000000000..16a3e2977 --- /dev/null +++ b/dotnet/samples/04.ai.a.teamsChefBot/files/samples.txt @@ -0,0 +1,77 @@ +# Samples + +_**Navigation**_ +- [00.OVERVIEW](./README.md) +- [01.QUICKSTART](./QUICKSTART.md) +- [**02.SAMPLES**](./SAMPLES.md) +___ + + +After completing the quickstart guide, the next step is to try out the samples. + +Samples are E2E teams apps that are easy to set up locally. There are various samples to showcase the different features supported. + +The following is a list of all the samples we support organized into four categories. If you are new to the library it is recommended to start with the basic samples. + +When you are ready to dive into the AI Samples, try the fully conversational Teams Chef Bot sample that illustrates how to use Retrieval Augmentation Generation to ground the AI model’s answers in custom documents and datasets. + +- [Samples](#samples) + - [Basic Samples](#basic-samples) + - [AI Samples](#ai-samples) + - [User Authentication Samples](#user-authentication-samples) + - [Advanced Samples](#advanced-samples) + +## Basic Samples + +These samples showcase basic functionalities to build message extensions and conversational bots. + +| Name | Description | Languages Supported | +| ---------------------------------- | ------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| *Echo bot* | Bot that echos back the users message. | [JS](https://github.com/microsoft/teams-ai/tree/main/js/samples/01.messaging.a.echoBot), [C#](https://github.com/microsoft/teams-ai/tree/main/dotnet/samples/01.messaging.echoBot) | +| *Search Command Message Extension* | Message Extension to search NPM for a specific package and return the result as an Adaptive Card. | [JS](https://github.com/microsoft/teams-ai/tree/main/js/samples/02.messageExtensions.a.searchCommand), [C#](https://github.com/microsoft/teams-ai/tree/main/dotnet/samples/02.messageExtensions.a.searchCommand) | +| *Type-Ahead Bot* | Bot that shows how to incorporate Adaptive Cards into the coversational flow. | [JS](https://github.com/microsoft/teams-ai/tree/main/js/samples/03.adaptiveCards.a.typeAheadBot), [C#](https://github.com/microsoft/teams-ai/tree/main/dotnet/samples/03.adaptiveCards.a.typeAheadBot) | + +## AI Samples + +These samples showcase the AI features supported by the library. It builds on the basics of implementing conversational bots and message extensions. + +| Name | Description | Languages Supported | +| ---------------------- | ------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| *Teams Chef Bot* | Bot that helps the user build Teams apps by answering queries against external data source. | [JS](https://github.com/microsoft/teams-ai/tree/main/js/samples/04.ai.a.teamsChefBot), [C#](https://github.com/microsoft/teams-ai/tree/main/dotnet/samples/04.ai.a.teamsChefBot) | +| *AI Message Extension* | Message Extension that leverages GPT models to help users generate and update posts. | [JS](https://github.com/microsoft/teams-ai/tree/main/js/samples/04.ai.b.messageExtensions.AI-ME), [C#](https://github.com/microsoft/teams-ai/tree/main/dotnet/samples/04.ai.b.messageExtensions.gptME) | +| *Light Bot* | Bot that can switch the light on or off. It uses AI to map users message to predefined actions (or skills) | [JS](https://github.com/microsoft/teams-ai/tree/main/js/samples/04.ai.c.actionMapping.lightBot), [C#](https://github.com/microsoft/teams-ai/tree/main/dotnet/samples/04.ai.c.actionMapping.lightBot) | +| *List Bot* | Bot that helps the user maintain task lists. It can add, remove, update, and search lists and tasks. | [JS](https://github.com/microsoft/teams-ai/tree/main/js/samples/04.ai.d.chainedActions.listBot), [C#](https://github.com/microsoft/teams-ai/tree/main/dotnet/samples/04.ai.d.chainedActions.listBot) | +| *DevOps Bot* | Bot that helps the user perform DevOps actions such as create, update, triage and summarize work items. | [JS](https://github.com/microsoft/teams-ai/tree/main/js/samples/04.ai.e.chainedActions.devOpsBot), [C#](https://github.com/microsoft/teams-ai/tree/main/dotnet/samples/04.ai.e.chainedActions.devOpsBot) | +| *Card Master Bot* | Bot with AI vision support that is able to generate Adaptive Cards from uploaded images by using GPT vision models. | [JS](https://github.com/microsoft/teams-ai/tree/main/js/samples/04.ai.f.vision.cardMaster) | +| *Twenty Questions Bot* | Bot that plays a game of twenty questions with the user. | [JS](https://github.com/microsoft/teams-ai/tree/main/js/samples/04.e.twentyQuestions), [C#](https://github.com/microsoft/teams-ai/tree/main/dotnet/samples/04.e.twentyQuestions) | +| *Chat Moderation Bot* | Bot that shows how to incorporate content safety control when using AI features. | [JS](https://github.com/microsoft/teams-ai/tree/main/js/samples/05.chatModeration) | +| *Math Tutor Bot* | Bot that is an expert in math. It uses OpenAI's Assisstants API. | [JS](https://github.com/microsoft/teams-ai/tree/main/js/samples/06.assistants.a.mathBot), [C#](https://github.com/microsoft/teams-ai/tree/main/dotnet/samples/06.assistants.a.mathBot) | +| *Food Ordering Bot* | Bot that can take a food order for a fictional restaurant called The Pub. | [JS](https://github.com/microsoft/teams-ai/tree/main/js/samples/06.assistants.b.orderBot), [C#](https://github.com/microsoft/teams-ai/tree/main/dotnet/samples/06.assistants.b.orderBot) | + +## User Authentication Samples + +Being able to access user specific data from other third party services is a cruicial capability of any Teams app. These samples showcase the different ways to authenticate a user to third party services such as Microsoft Graph. + +There are two approaches to user authentication: `OAuth` and `TeamsSSO`. + +The `OAuth` approach requires creating an OAuth connection in the Azure Bot service. It uses the Bot Framework's token service to handle the OAuth2.0 flow on behalf of the bot server. + +The `TeamsSSO` approach implements the OAuth 2.0 protocol within the bot web server itself. It gives you more flexibility on how to configure Azure Active Directory (AAD), like using a client certificate. There is no need to create an OAuth connection in Azure Bot service. + +Both of these approaches can be used to achieve the same functionality, such as using the SSO flow to authenticate the user. + +| Name | Description | Languages Supported | +| ---------------------------- | ----------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| *OAuth Bot* | User authentication in a conversational bot with the `OAuth` apporach. | [JS](https://github.com/microsoft/teams-ai/tree/main/js/samples/06.auth.oauth.bot), [C#](https://github.com/microsoft/teams-ai/tree/main/dotnet/samples/06.auth.oauth.bot) | +| *OAuth Message Extension* | User authentication in a message extension with the `OAuth` approach. | [JS](https://github.com/microsoft/teams-ai/tree/main/js/samples/06.auth.oauth.messageExtension), [C#](https://github.com/microsoft/teams-ai/tree/main/dotnet/samples/06.auth.oauth.messageExtension) | +| *OAuth Adaptive Card Bot* | User authentication in a conversational bot using ACv2 cards (Adaptive Cards v2) with the `OAuth` approach. | [JS](https://github.com/microsoft/teams-ai/tree/main/js/samples/06.auth.oauth.adaptiveCard) | +| *TeamsSSO Bot* | User authentication in a conversational bot using the `TeamsSSO` approach. | [JS](https://github.com/microsoft/teams-ai/tree/main/js/samples/06.auth.teamsSSO.bot), [C#](https://github.com/microsoft/teams-ai/tree/main/dotnet/samples/06.auth.teamsSSO.bot) | +| *TeamsSSO Message Extension* | User authentication in a message extension with the `TeamsSSO` approach. | [JS](https://github.com/microsoft/teams-ai/tree/main/js/samples/06.auth.teamsSSO.messageExtension), [C#](https://github.com/microsoft/teams-ai/tree/main/dotnet/samples/06.auth.teamsSSO.messageExtension) | + +## Advanced Samples + +These samples a combination of advanced features such as AI, user authentication, basic conversational bot and message extension capabilities, resulting in their complexity. + +| Name | Description | Languages Supported | +| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- | +| *Who Bot* | Bot that can tell you who your manager is, when's your next meeting...etc. It will authenticate that user, use AI to map user intents to actions in which it will call Graph to get specific user data. | [JS](https://github.com/microsoft/teams-ai/tree/main/js/samples/07.whoBot) | diff --git a/dotnet/samples/04.ai.a.teamsChefBot/files/turns.txt b/dotnet/samples/04.ai.a.teamsChefBot/files/turns.txt new file mode 100644 index 000000000..842400cd7 --- /dev/null +++ b/dotnet/samples/04.ai.a.teamsChefBot/files/turns.txt @@ -0,0 +1,105 @@ +# Turn Context and Turn State + +In a conversation, people often speak one-at-a-time, taking turns speaking. With a bot, it generally reacts to user input. Within the Teams AI Library, a turn consists of the user's incoming activity to the bot and any activity the bot sends back to the user as an immediate response. You can think of a _turn_ as the processing associated with the bot receiving a given activity. + +In each turn the _turn context_ and the _turn state_ are configured to manage conversational data. + +### Turn Context + +The turn context object provides information about the activity such as the sender and receiver, the channel, and other data needed to process the activity. + +The turn context is one of the most important abstractions in the SDK. Not only does it carry the inbound activity to all the middleware components and the application logic but it also provides the mechanism whereby the components and the bot logic can send outbound activities. + +#### Example + +The turn context object is accessible from the activity handler or an action. Here's how to use it send a message back to the user in an activity handler: + +##### C# + +```cs +app.OnActivity(ActivityTypes.Message, async (ITurnContext turnContext, TurnState turnState, CancellationToken cancellationToken) => +{ + // Extract user's message + string message = turnContext.Activity.Text; + await turnContext.sendActivity(`You said: ${message}`); +}) +``` + +##### JS/TS + +```ts +app.activity(ActivityTypes.Message, async (context: TurnContext, state: ApplicationTurnState) => { + // Extract user's message + let message = context.activity.text; + await context.sendActivity(`You said: ${message}`); +}); +``` + +##### Python + +```python +@app.activity("message") +async def on_message(context: TurnContext, state: TurnState): + # Extract user's message + message = context.activity.text + await context.send_activity(f"You said: {message}") + return True +``` + +### Turn State + +The turn state object stores cookie-like data for the current turn. Just like the turn context, it is carried through the entire application logic, including the activity handlers and the AI System. Unlike the turn context, the turn state is not fixed and is meant to be configured to each application-specific use case. It is common for apps to have conversation state, user state, and temp (temporary) state, but as a developer you can add or remove state objects to fit your needs. + +It is used to store information like the user's message, the conversation history, and any custom data configured by the application code. + +#### Example + +This is how a bot can keep track of the number of messages send by the user using the turn state: + +##### C# + +```cs +app.OnActivity(ActivityTypes.Message, async (ITurnContext turnContext, AppState turnState, CancellationToken cancellationToken) => +{ + int count = turnState.Conversation.MessageCount; + // Increment count state. + turnState.Conversation.MessageCount = ++count; + + // Send a message back to the user.... +}); +``` + + +##### JS/TS + +```ts +app.activity(ActivityTypes.Message, async (context: TurnContext, state: ApplicationTurnState) => { + let count = state.conversation.value.count ?? 0; + // Increment count state + state.conversation.value.count += 1; + + // Send a message back to the user.... +}); +``` + +##### Python + +```python +@app.activity("message") +async def on_message(context: TurnContext, state: AppTurnState): + count = state.conversation.count + # Increment count state + state.conversation.count += 1 + + # Send a message back to the user.... + return True +``` + +### Appendix + +
+What happens when the user sends a message to the bot? +
+ +When a message is sent by the user it is routed to the bots `HTTP POST` endpoint `/api/messages`, which +starts the routing process. \ No newline at end of file diff --git a/getting-started/CONCEPTS/DATA-SOURCES.md b/getting-started/CONCEPTS/DATA-SOURCES.md index b63f8f15d..0f24a1cdc 100644 --- a/getting-started/CONCEPTS/DATA-SOURCES.md +++ b/getting-started/CONCEPTS/DATA-SOURCES.md @@ -15,10 +15,11 @@ Our simplest example (primarily for testing) is `TextDataSource`, which adds a s Our most complex example is the `VectraDataSource` in the Chef Bot sample, which uses an external library called Vectra. ### Customized Example of VectraDataSource + +#### Javascript Here is an example of the configuration for the [Chef Bot sample](https://github.com/microsoft/teams-ai/tree/main/js/samples/04.ai.a.teamsChefBot): -**JS** ```js // Inside VectraDataSource.ts export class VectraDataSource implements DataSource @@ -44,4 +45,31 @@ Inside the prompt's config.json. Here, `teams-ai` denotes the name of the Vectra "teams-ai": 1200 } } -``` \ No newline at end of file +``` + +#### C# +Here is an example of the configuration for the +[Chef Bot sample](https://github.com/microsoft/teams-ai/tree/main/dotnet/samples/04.ai.a.teamsChefBot): + +```cs +// Inside KernelMemoryDataSource.cs +public class KernelMemoryDataSource : IDataSource +``` + +```cs +// Inside of Program.cs +KernelMemoryDataSource dataSource = new("teams-ai", sp.GetService()!); +prompts.AddDataSource("teams-ai", dataSource); +``` + +Inside the prompt's `config.json`. Here, `teams-ai` denotes the name of the `KernelMemoryDataSource`, and 900 is the `maxTokens`. +```json +"augmentation": { + "augmentation_type": "none", + "data_sources": { + "teams-ai": 900 + } +} +``` + +> The [`Kernel Memory`](https://github.com/microsoft/kernel-memory) library provides tools for indexing and querying data. The Chef Bot uses Kernel Memory as an example of how to integrate Retrieval Augmentation (RAG) into the AI library.