Skip to content

Commit

Permalink
fix twenty questions bot
Browse files Browse the repository at this point in the history
  • Loading branch information
singhk97 committed Nov 29, 2023
1 parent 1975c0d commit a3e267b
Show file tree
Hide file tree
Showing 7 changed files with 117 additions and 182 deletions.
24 changes: 15 additions & 9 deletions dotnet/samples/04.e.twentyQuestions/GameBotHandlers.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
using Microsoft.Bot.Builder;
using Microsoft.Teams.AI;
using Microsoft.Teams.AI.AI.Planners;
using Microsoft.Teams.AI.AI.Prompts;

namespace TwentyQuestions
{
public class GameBotHandlers
{
private readonly Application<GameState, GameStateManager> _app;
private readonly ActionPlanner<GameState> _actionPlanner;

public GameBotHandlers(Application<GameState, GameStateManager> app)
public GameBotHandlers(ActionPlanner<GameState> actionPlanner)
{
this._app = app;
this._actionPlanner = actionPlanner;
}

public async Task OnMessageActivityAsync(ITurnContext turnContext, GameState turnState, CancellationToken cancellationToken)
Expand All @@ -19,7 +21,7 @@ public async Task OnMessageActivityAsync(ITurnContext turnContext, GameState tur
if (string.Equals("/quit", input, StringComparison.OrdinalIgnoreCase))
{
// Quit Game
turnState.ConversationStateEntry?.Delete();
turnState.DeleteConversationState();
await turnContext.SendActivityAsync(ResponseBuilder.QuitGame(secretWord), cancellationToken: cancellationToken);
}
else if (string.IsNullOrEmpty(secretWord))
Expand Down Expand Up @@ -82,13 +84,17 @@ private async Task<string> GetHint(ITurnContext turnContext, GameState turnState
// Set input for prompt
turnState.Temp!.Input = turnContext.Activity.Text;

PromptTemplate template = _actionPlanner.Options.Prompts.GetPrompt("hint");
// Set prompt variables
_app.AI.Prompts.Variables.Add("guessCount", turnState.Conversation!.GuessCount.ToString());
_app.AI.Prompts.Variables.Add("remainingGuesses", turnState.Conversation!.RemainingGuesses.ToString());
_app.AI.Prompts.Variables.Add("secretWord", turnState.Conversation!.SecretWord!);
PromptResponse response = await _actionPlanner.CompletePromptAsync(turnContext, turnState, template, null, cancellationToken);

string hint = await _app.AI.CompletePromptAsync(turnContext, turnState, "Hint", null, cancellationToken);
return hint ?? throw new Exception("The request to OpenAI was rate limited. Please try again later.");
if (response.Status == PromptResponseStatus.Success && response.Message != null)
{
// Prompt completed successfully
return response.Message.Content!;
}

throw new Exception($"An error occured when trying to make a call to the AI service: {response.Error}");
}
}
}
45 changes: 40 additions & 5 deletions dotnet/samples/04.e.twentyQuestions/GameState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,53 @@ namespace TwentyQuestions
/// <summary>
/// Extend the turn state by configuring custom strongly typed state classes.
/// </summary>
public class GameState : TurnState<ConversationState, StateBase, TempState>
public class GameState : TurnState
{
public GameState()
{
ScopeDefaults[CONVERSATION_SCOPE] = new ConversationState();
}

/// <summary>
/// Stores all the conversation-related state.
/// </summary>
public new ConversationState Conversation
{
get
{
TurnStateEntry? scope = GetScope(CONVERSATION_SCOPE);

if (scope == null)
{
throw new ArgumentException("TurnState hasn't been loaded. Call LoadStateAsync() first.");
}

return (ConversationState)scope.Value!;
}
set
{
TurnStateEntry? scope = GetScope(CONVERSATION_SCOPE);

if (scope == null)
{
throw new ArgumentException("TurnState hasn't been loaded. Call LoadStateAsync() first.");
}

scope.Replace(value!);
}
}
}

/// <summary>
/// This class adds custom properties to the turn state which will be accessible in the activity handler methods.
/// </summary>
public class ConversationState : StateBase
public class ConversationState : Record
{
private const string _secretWordKey = "secretWordKey";
private const string _guessCountKey = "guessCountKey";
private const string _remainingGuessesKey = "remainingGuessesKey";
// The keys can be referenced in the prompt using the ${{conversation.<key>}} syntax.
// For example, ${{conversation.secretWord}}
private const string _secretWordKey = "secretWord";
private const string _guessCountKey = "guessCount";
private const string _remainingGuessesKey = "remainingGuesses";

public string? SecretWord
{
Expand Down
9 changes: 0 additions & 9 deletions dotnet/samples/04.e.twentyQuestions/GameStateManager.cs

This file was deleted.

140 changes: 41 additions & 99 deletions dotnet/samples/04.e.twentyQuestions/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@
using Microsoft.Bot.Connector.Authentication;
using Microsoft.Bot.Schema;
using Microsoft.Teams.AI;
using Microsoft.Teams.AI.AI;
using Microsoft.Teams.AI.AI.Moderator;
using Microsoft.Teams.AI.AI.Planner;
using Microsoft.Teams.AI.AI.Prompt;
using Microsoft.Teams.AI.AI.Models;
using Microsoft.Teams.AI.AI.Planners;
using Microsoft.Teams.AI.AI.Prompts;
using TwentyQuestions;

var builder = WebApplication.CreateBuilder(args);
Expand Down Expand Up @@ -34,112 +33,57 @@
// Create singleton instances for bot application
builder.Services.AddSingleton<IStorage, MemoryStorage>();

#region Use Azure OpenAI and Azure Content Safety
// Following code is for using Azure OpenAI and Azure Content Safety
if (config.Azure == null
|| string.IsNullOrEmpty(config.Azure.OpenAIApiKey)
|| string.IsNullOrEmpty(config.Azure.OpenAIEndpoint)
|| string.IsNullOrEmpty(config.Azure.ContentSafetyApiKey)
|| string.IsNullOrEmpty(config.Azure.ContentSafetyEndpoint))
OpenAIModel? model = null;

if (!string.IsNullOrEmpty(config.OpenAI?.ApiKey))
{
throw new Exception("Missing Azure configuration.");
model = new(new OpenAIModelOptions(config.OpenAI.ApiKey, "gpt-3.5-turbo"));
}
builder.Services.AddSingleton<AzureOpenAIPlannerOptions>(_ => new(config.Azure.OpenAIApiKey, "text-davinci-003", config.Azure.OpenAIEndpoint));
builder.Services.AddSingleton<AzureContentSafetyModeratorOptions>(_ => new(config.Azure.ContentSafetyApiKey, config.Azure.ContentSafetyEndpoint, ModerationType.Both));

// Create the bot as a transient. In this case the ASP Controller is expecting an IBot.
builder.Services.AddTransient<IBot>(sp =>
else if (!string.IsNullOrEmpty(config.Azure?.OpenAIApiKey) && !string.IsNullOrEmpty(config.Azure.OpenAIEndpoint))
{
// Create loggers
ILoggerFactory loggerFactory = sp.GetService<ILoggerFactory>()!;

// Create AzureOpenAIPlanner
IPlanner<GameState> planner = new AzureOpenAIPlanner<GameState>(
sp.GetService<AzureOpenAIPlannerOptions>()!,
loggerFactory);

// Create AzureContentSafetyModerator
IModerator<GameState> moderator = new AzureContentSafetyModerator<GameState>(sp.GetService<AzureContentSafetyModeratorOptions>()!);

// Setup Application
AIHistoryOptions aiHistoryOptions = new()
{
AssistantHistoryType = AssistantHistoryType.Text
};
AIOptions<GameState> aiOptions = new(
planner: planner,
promptManager: new PromptManager<GameState>("./Prompts"),
moderator: moderator,
prompt: "Chat",
history: aiHistoryOptions);
var applicationBuilder = new ApplicationBuilder<GameState, GameStateManager>()
.WithAIOptions(aiOptions)
.WithLoggerFactory(loggerFactory)
.WithTurnStateManager(new GameStateManager());

// Set storage options
IStorage? storage = sp.GetService<IStorage>();
if (storage != null)
{
applicationBuilder.WithStorage(storage);
}

// Create Application
Application<GameState, GameStateManager> app = applicationBuilder.Build();

GameBotHandlers handlers = new(app);

// register turn and activity handlers
app.OnActivity(ActivityTypes.Message, handlers.OnMessageActivityAsync);

return app;
});
#endregion
model = new(new AzureOpenAIModelOptions(
config.Azure.OpenAIApiKey,
"gpt-35-turbo",
config.Azure.OpenAIEndpoint
));
}

#region Use OpenAI
/** // Use OpenAI
if (config.OpenAI == null || string.IsNullOrEmpty(config.OpenAI.ApiKey))
if (model == null)
{
throw new Exception("Missing OpenAI configuration.");
throw new Exception("please configure settings for either OpenAI or Azure");
}
builder.Services.AddSingleton<OpenAIPlannerOptions>(_ => new(config.OpenAI.ApiKey, "text-davinci-003"));
builder.Services.AddSingleton<OpenAIModeratorOptions>(_ => new(config.OpenAI.ApiKey, ModerationType.Both));

// Create the bot as a transient. In this case the ASP Controller is expecting an IBot.
builder.Services.AddTransient<IBot>(sp =>
{
// Create loggers
ILoggerFactory loggerFactory = sp.GetService<ILoggerFactory>()!;

// Get HttpClient
HttpClient moderatorHttpClient = sp.GetService<IHttpClientFactory>()!.CreateClient("WebClient");
// Create OpenAIPlanner
IPlanner<GameState> planner = new OpenAIPlanner<GameState>(
sp.GetService<OpenAIPlannerOptions>()!,
loggerFactory);
// Create OpenAIModerator
IModerator<GameState> moderator = new OpenAIModerator<GameState>(
sp.GetService<OpenAIModeratorOptions>()!,
loggerFactory,
moderatorHttpClient);
// Setup Application
AIHistoryOptions aiHistoryOptions = new()
// Create Prompt Manager
PromptManager prompts = new(new()
{
AssistantHistoryType = AssistantHistoryType.Text
};
AIOptions<GameState> aiOptions = new(
planner: planner,
promptManager: new PromptManager<GameState>("./Prompts"),
moderator: moderator,
prompt: "Chat",
history: aiHistoryOptions);
var applicationBuilder = new ApplicationBuilder<GameState, GameStateManager>()
.WithAIOptions(aiOptions)
PromptFolder = "./Prompts"
});

// Create ActionPlanner
ActionPlanner<GameState> planner = new(
options: new(
model: model,
prompts: prompts,
defaultPrompt: async (context, state, planner) =>
{
PromptTemplate template = prompts.GetPrompt("sequence");
return await Task.FromResult(template);
}
)
{ LogRepairs = true },
loggerFactory: loggerFactory
);

var applicationBuilder = new ApplicationBuilder<GameState>()
.WithAIOptions(new(planner))
.WithLoggerFactory(loggerFactory)
.WithTurnStateManager(new GameStateManager());
.WithTurnStateFactory(() => new GameState());

// Set storage options
IStorage? storage = sp.GetService<IStorage>();
Expand All @@ -149,17 +93,15 @@
}

// Create Application
Application<GameState, GameStateManager> app = applicationBuilder.Build();
Application<GameState> app = applicationBuilder.Build();

GameBotHandlers handlers = new(app);
GameBotHandlers handlers = new(planner);

// register turn and activity handlers
app.OnActivity(ActivityTypes.Message, handlers.OnMessageActivityAsync);

return app;
});
**/
#endregion

var app = builder.Build();

Expand Down
27 changes: 14 additions & 13 deletions dotnet/samples/04.e.twentyQuestions/Prompts/Hint/config.json
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
{
"schema": 1,
"description": "A bot that plays a game of 20 questions",
"type": "completion",
"completion": {
"max_tokens": 256,
"temperature": 0.7,
"top_p": 0.0,
"presence_penalty": 0.6,
"frequency_penalty": 0.0
},
"default_backends": [
"text-davinci-003"
]
"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
}
}
15 changes: 6 additions & 9 deletions dotnet/samples/04.e.twentyQuestions/Prompts/Hint/skprompt.txt
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
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.
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: {{$guessCount}}
RemainingGuesses: {{$remainingGuesses}}
Secret: {{$secretWord}}
GuessCount: {{$conversation.guessCount}}
RemainingGuesses: {{$conversation.remainingGuesses}}
Secret: {{$conversation.secretWord}}

Answer the humans question but do not mention the secret word.

Human: {{$input}}
AI:
Answer the humans question but do not mention the secret word.
Loading

0 comments on commit a3e267b

Please sign in to comment.