Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update ChatBot to work with dotnet-isolated and remove durable dependency #10

Merged
merged 30 commits into from
Feb 13, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
96abcc7
changes
aishwaryabh Jan 11, 2024
8d81d46
changes
aishwaryabh Jan 24, 2024
c1a164b
changes
aishwaryabh Jan 24, 2024
d64723c
changes using table
aishwaryabh Jan 26, 2024
cdb1bd7
more changes
aishwaryabh Jan 26, 2024
2605101
pulling from main
aishwaryabh Jan 29, 2024
41798b3
comments
aishwaryabh Jan 29, 2024
b044e52
adjusting spacing
aishwaryabh Jan 29, 2024
7239df9
Delete Functions.Worker.Extensions.OpenAI.Test
aishwaryabh Jan 29, 2024
165b410
updating version
aishwaryabh Jan 29, 2024
ed8734c
merging in result
aishwaryabh Jan 30, 2024
412ea13
using managed identity
aishwaryabh Jan 30, 2024
e7115ee
adding azure web jobs support
aishwaryabh Jan 31, 2024
29c748c
comments
aishwaryabh Jan 31, 2024
2db471a
updating config sections
aishwaryabh Feb 1, 2024
6ee62f4
changing default values for OpenAIConfigOptions
aishwaryabh Feb 1, 2024
1471bcb
addressing comments
aishwaryabh Feb 2, 2024
5615322
adding message
aishwaryabh Feb 2, 2024
409c291
forgot to make these classes internal
aishwaryabh Feb 2, 2024
4787c68
addressing comments
aishwaryabh Feb 12, 2024
e2e4be6
removing NRE
aishwaryabh Feb 12, 2024
c13a723
fixing chat bot service
aishwaryabh Feb 12, 2024
f017d44
addressing comments
aishwaryabh Feb 13, 2024
2842460
trying yaml change
aishwaryabh Feb 13, 2024
38b1d92
trying yaml change again
aishwaryabh Feb 13, 2024
6bf9541
addressing comments
aishwaryabh Feb 13, 2024
2671a19
yaml file changes
aishwaryabh Feb 13, 2024
2ec5209
trying yaml changes and bringing createdat back
aishwaryabh Feb 13, 2024
5973f8a
reverting build-release.yml file back
aishwaryabh Feb 13, 2024
1dffb38
fixing small bug for when chat bot state doesn't exist
aishwaryabh Feb 13, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public static async Task<CreateChatBotOutput> CreateChatBot(
{
var responseJson = new { chatId };

using (StreamReader reader = new StreamReader(req.Body))
using StreamReader reader = new StreamReader(req.Body);
{
aishwaryabh marked this conversation as resolved.
Show resolved Hide resolved
string request = await reader.ReadToEndAsync();

Expand All @@ -41,7 +41,6 @@ public static async Task<CreateChatBotOutput> CreateChatBot(
{
HttpResponse = new ObjectResult(responseJson) { StatusCode = 202 },
ChatBotCreateRequest = new ChatBotCreateRequest(chatId, createRequestBody.Instructions),

};
}
}
Expand Down
115 changes: 60 additions & 55 deletions src/WebJobs.Extensions.OpenAI/Agents/ChatBotService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,19 @@ public interface IChatBotService

public class DefaultChatBotService : IChatBotService
{
readonly TableClient tableClient;
readonly TableServiceClient tableServiceClient;
readonly OpenAIClient openAIClient;
readonly ILogger logger;

ChatBotRuntimeState? InitialState;
static readonly Dictionary<string, Func<ChatMessageEntity, ChatRequestMessage>> MessageFactories = new()
aishwaryabh marked this conversation as resolved.
Show resolved Hide resolved
{
{ ChatRole.User.ToString(), msg => new ChatRequestUserMessage(msg.Content) },
{ ChatRole.Assistant.ToString(), msg => new ChatRequestAssistantMessage(msg.Content) },
{ ChatRole.System.ToString(), msg => new ChatRequestSystemMessage(msg.Content) }
};

readonly TableClient tableClient;
readonly TableServiceClient tableServiceClient;
readonly OpenAIClient openAIClient;
readonly ILogger logger;
ChatBotRuntimeState? initialState;

public DefaultChatBotService(
OpenAIClient openAiClient,
IOptions<OpenAIConfigOptions> openAiConfigOptions,
Expand Down Expand Up @@ -82,11 +82,11 @@ void Initialize(ChatBotCreateRequest request)
request.Id,
request.Instructions ?? "(none)");

this.InitialState = new ChatBotRuntimeState
this.initialState = new ChatBotRuntimeState
{
ChatMessages = string.IsNullOrEmpty(request.Instructions) ?
new List<ChatMessageEntity>() :
new List<ChatMessageEntity>() { new ChatMessageEntity(request.Instructions, ChatRole.System.ToString()) },
new() :
new() { new ChatMessageEntity(request.Instructions, ChatRole.System.ToString()) },
aishwaryabh marked this conversation as resolved.
Show resolved Hide resolved
};
}

Expand All @@ -98,39 +98,51 @@ public async Task CreateChatBotAsync(ChatBotCreateRequest request, CancellationT
await this.tableClient.CreateIfNotExistsAsync();

// Check to see if the chat bot has already been initialized
Pageable<TableEntity> queryResultsFilter = this.tableClient.Query<TableEntity>(filter: $"PartitionKey eq '{request.Id}'");
AsyncPageable<TableEntity> queryResultsFilter = this.tableClient.QueryAsync<TableEntity>(filter: $"PartitionKey eq '{request.Id}'");

if (queryResultsFilter.Any())
{
// Create a batch of table transaction actions for deleting entities
List<TableTransactionAction> deleteBatch = new List<TableTransactionAction>();
// Create a batch of table transaction actions for deleting entities
List<TableTransactionAction> deleteBatch = new();

// Count of entities to delete to keep track of
int count = 0;

// Get all entities with the same partition key and add to batch for deletion
foreach (TableEntity entity in queryResultsFilter)
await foreach (TableEntity entity in queryResultsFilter)
{
// If the count is greater than or equal to 100, submit the transaction and clear the batch
if (count >= 100)
aishwaryabh marked this conversation as resolved.
Show resolved Hide resolved
{
this.logger.LogInformation("Deleting already existing entity with partition id {partitionKey} and row key {rowKey}", entity.PartitionKey, entity.RowKey);
deleteBatch.Add(new TableTransactionAction(TableTransactionActionType.Delete, entity));
this.logger.LogInformation("Deleting {Count} entities", count);
await this.tableClient.SubmitTransactionAsync(deleteBatch);
deleteBatch.Clear();
count = 0;
}
this.logger.LogInformation("Deleting already existing entity with partition id {partitionKey} and row key {rowKey}", entity.PartitionKey, entity.RowKey);
deleteBatch.Add(new TableTransactionAction(TableTransactionActionType.Delete, entity));
count += 1;
}

if (deleteBatch.Any())
{
await this.tableClient.SubmitTransactionAsync(deleteBatch);
}

// Create a batch of table transaction actions
List<TableTransactionAction> batch = new List<TableTransactionAction>();
List<TableTransactionAction> batch = new();

// Get chatbot runtime state
this.Initialize(request);

// Add first chat message entity to table
if (this.InitialState?.ChatMessages?.Count > 0)
if (this.initialState?.ChatMessages?.Count > 0)
{
ChatMessageEntity firstInstruction = this.InitialState.ChatMessages![0];
ChatMessageEntity firstInstruction = this.initialState.ChatMessages[0];
ChatMessageTableEntity chatMessageEntity = new ChatMessageTableEntity
{
RowKey = this.GetRowKey(1), // ChatMessage00001
PartitionKey = request.Id,
ChatMessage = firstInstruction.Content,
Role = firstInstruction.Role,
CreatedAt = DateTime.UtcNow,
};

batch.Add(new TableTransactionAction(TableTransactionActionType.Add, chatMessageEntity));
Expand All @@ -155,27 +167,26 @@ public async Task CreateChatBotAsync(ChatBotCreateRequest request, CancellationT
}

public async Task<ChatBotState> GetStateAsync(string id, DateTime after, CancellationToken cancellationToken)
{
{
DateTime afterUtc = after.ToUniversalTime();
this.logger.LogInformation(
"Reading state for chat bot entity '{Id}' and getting chat messages after {Timestamp}",
id,
after.ToString("o"));

string afterString = after.ToString("o");
afterUtc.ToString("o"));

// Check to see if any entity exists with partition id
AsyncPageable<TableEntity> itemsWithPartitionKey = this.tableClient.QueryAsync<TableEntity>(filter: $"PartitionKey eq '{id}'");

ChatBotStateEntity chatBotStateEntity = new();

List<ChatMessageEntity> filteredChatMessages = new List<ChatMessageEntity>();
List<ChatMessageEntity> filteredChatMessages = new();
await foreach (TableEntity chatMessage in itemsWithPartitionKey)
{
if (chatMessage.RowKey.StartsWith("ChatMessage"))
{
if (DateTimeOffset.Parse(chatMessage.Timestamp.ToString()).DateTime > after)
if (chatMessage.GetDateTime("CreatedAt") > afterUtc)
{
filteredChatMessages.Add(new ChatMessageEntity(chatMessage["ChatMessage"].ToString(), chatMessage["Role"].ToString()));
filteredChatMessages.Add(new ChatMessageEntity(chatMessage.GetString("ChatMessage"), chatMessage.GetString("Role")));
}
}

Expand All @@ -187,11 +198,11 @@ public async Task<ChatBotState> GetStateAsync(string id, DateTime after, Cancell
PartitionKey = chatMessage.PartitionKey,
ETag = chatMessage.ETag,
Timestamp = chatMessage.Timestamp,
CreatedAt = ((DateTimeOffset)chatMessage["CreatedAt"]).DateTime,
LastUpdatedAt = ((DateTimeOffset)chatMessage["LastUpdatedAt"]).DateTime,
TotalMessages = (int)chatMessage["TotalMessages"],
TotalTokens = (int)chatMessage["TotalTokens"],
Exists = (bool)chatMessage["Exists"],
CreatedAt = chatMessage.GetDateTime("CreatedAt").GetValueOrDefault(),
LastUpdatedAt = chatMessage.GetDateTime("LastUpdatedAt").GetValueOrDefault(),
TotalMessages = chatMessage.GetInt32("TotalMessages").GetValueOrDefault(),
TotalTokens = chatMessage.GetInt32("TotalTokens").GetValueOrDefault(),
Exists = chatMessage.GetBoolean("Exists").GetValueOrDefault(),
};
}
}
Expand Down Expand Up @@ -232,27 +243,18 @@ async Task PostAsync(ChatBotPostRequest request)
}

// Check to see if any entity exists with partition id
Pageable<TableEntity> itemsWithPartitionKey = this.tableClient.Query<TableEntity>(filter: $"PartitionKey eq '{request.Id}'");

// No entities exist at with the partition key
if (!itemsWithPartitionKey.Any())
{
this.logger.LogWarning("[{Id}] Ignoring message sent to chat bot that does not exist.", request.Id);
return;
}

this.logger.LogInformation("[{Id}] Received message: {Text}", request.Id, request.UserMessage);
AsyncPageable<TableEntity> itemsWithPartitionKey = this.tableClient.QueryAsync<TableEntity>(filter: $"PartitionKey eq '{request.Id}'");

// Deserialize the chat messages
List<ChatMessageEntity> chatMessageList = new List<ChatMessageEntity>();
List<ChatMessageEntity> chatMessageList = new();
ChatBotStateEntity chatBotStateEntity = new();

foreach (TableEntity chatMessage in itemsWithPartitionKey)
await foreach (TableEntity chatMessage in itemsWithPartitionKey)
{
// Add chat message to list
if (chatMessage.RowKey.StartsWith("ChatMessage"))
{
chatMessageList.Add(new ChatMessageEntity(chatMessage["ChatMessage"].ToString(), chatMessage["Role"].ToString()));
chatMessageList.Add(new ChatMessageEntity(chatMessage.GetString("ChatMessage"), chatMessage.GetString("Role")));
}

// Get chat bot state
Expand All @@ -263,11 +265,11 @@ async Task PostAsync(ChatBotPostRequest request)
RowKey = chatMessage.RowKey,
PartitionKey = chatMessage.PartitionKey,
ETag = chatMessage.ETag,
CreatedAt = DateTime.SpecifyKind(((DateTimeOffset)chatMessage["CreatedAt"]).DateTime, DateTimeKind.Utc),
LastUpdatedAt = DateTime.SpecifyKind(((DateTimeOffset)chatMessage["LastUpdatedAt"]).DateTime, DateTimeKind.Utc),
TotalMessages = (int)chatMessage["TotalMessages"],
TotalTokens = (int)chatMessage["TotalTokens"],
Exists = (bool)chatMessage["Exists"],
CreatedAt = DateTime.SpecifyKind(chatMessage.GetDateTime("CreatedAt").GetValueOrDefault(), DateTimeKind.Utc),
LastUpdatedAt = DateTime.SpecifyKind(chatMessage.GetDateTime("LastUpdatedAt").GetValueOrDefault(), DateTimeKind.Utc),
TotalMessages = chatMessage.GetInt32("TotalMessages").GetValueOrDefault(),
TotalTokens = chatMessage.GetInt32("TotalTokens").GetValueOrDefault(),
Exists = chatMessage.GetBoolean("Exists").GetValueOrDefault(),
};
}
}
Expand All @@ -279,19 +281,22 @@ async Task PostAsync(ChatBotPostRequest request)
return;
}

this.logger.LogInformation("[{Id}] Received message: {Text}", request.Id, request.UserMessage);

ChatMessageEntity chatMessageToSend = new ChatMessageEntity(request.UserMessage, ChatRole.User.ToString());
chatMessageList.Add(chatMessageToSend);

// Create a batch of table transaction actions
List<TableTransactionAction> batch = new List<TableTransactionAction>();
List<TableTransactionAction> batch = new();

// Add the user message as a new Chat message entity
ChatMessageTableEntity chatMessageEntity = new ChatMessageTableEntity
{
RowKey = this.GetRowKey(chatBotStateEntity.TotalMessages + 1), // Example: ChatMessage00012
PartitionKey = request.Id,
ChatMessage = request.UserMessage,
Role = ChatRole.User.ToString()
Role = ChatRole.User.ToString(),
CreatedAt = DateTime.UtcNow,
};

// Add the chat message to the batch
Expand Down Expand Up @@ -321,7 +326,8 @@ async Task PostAsync(ChatBotPostRequest request)
RowKey = this.GetRowKey(chatBotStateEntity.TotalMessages + 2), // Example: ChatMessage00013
PartitionKey = request.Id,
ChatMessage = replyMessage,
Role = ChatRole.Assistant.ToString()
Role = ChatRole.Assistant.ToString(),
CreatedAt = DateTime.UtcNow,
};

// Add the reply from assistant chat message to the batch
Expand All @@ -335,7 +341,6 @@ async Task PostAsync(ChatBotPostRequest request)
chatBotStateEntity.LastUpdatedAt = DateTime.UtcNow;
chatBotStateEntity.TotalMessages += 2;


// Update the chat bot state entity
batch.Add(new TableTransactionAction(TableTransactionActionType.UpdateMerge, chatBotStateEntity));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,22 @@
/// <summary>
/// Partition key.
/// </summary>
public string PartitionKey { get; set; }

Check warning on line 17 in src/WebJobs.Extensions.OpenAI/Models/ChatMessageTableEntity.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'PartitionKey' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

/// <summary>
/// Row key.
/// </summary>
public string RowKey { get; set; }

Check warning on line 22 in src/WebJobs.Extensions.OpenAI/Models/ChatMessageTableEntity.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'RowKey' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

/// <summary>
/// Chat message that will be stored in the table.
/// </summary>
public string ChatMessage { get; set; }

Check warning on line 27 in src/WebJobs.Extensions.OpenAI/Models/ChatMessageTableEntity.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'ChatMessage' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

/// <summary>
/// Role of who sent message.
/// </summary>
public string Role { get; set; }

Check warning on line 32 in src/WebJobs.Extensions.OpenAI/Models/ChatMessageTableEntity.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'Role' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

// <summary>
/// Gets timestamp of table entity.
Expand All @@ -41,4 +41,9 @@
/// </summary>
public ETag ETag { get; set; }

// <summary>
/// Gets when chat message was created.
/// </summary>
public DateTime CreatedAt { get; set; }
cgillum marked this conversation as resolved.
Show resolved Hide resolved

}
Loading