From 717c8b52f4605070cf46ced0a82dd9de8af8c25e Mon Sep 17 00:00:00 2001 From: manvkaur <67894494+manvkaur@users.noreply.github.com> Date: Thu, 12 Sep 2024 16:50:37 +0100 Subject: [PATCH 01/10] fix issue with tabel storage identity --- .../annotation/assistant/AssistantPost.java | 25 ++++++++--- .../annotation/assistant/AssistantQuery.java | 29 ++++++++++--- .../assistant/csharp-ooproc/AssistantApis.cs | 11 +++-- samples/chat/csharp-ooproc/ChatBot.cs | 11 +++-- samples/chat/javascript/src/app.js | 15 +++++-- .../powershell/GetChatState/function.json | 4 +- .../powershell/PostUserResponse/function.json | 4 +- samples/chat/typescript/src/functions/app.ts | 14 +++++-- src/Directory.Build.props | 2 +- .../Assistants/AssistantPostInputAttribute.cs | 10 +++++ .../AssistantQueryInputAttribute.cs | 10 +++++ .../Assistants/AssistantBindingConverter.cs | 8 +--- .../Assistants/AssistantPostAttribute.cs | 10 +++++ .../Assistants/AssistantQueryAttribute.cs | 10 +++++ .../Assistants/AssistantService.cs | 41 +++++++++++++------ 15 files changed, 154 insertions(+), 50 deletions(-) diff --git a/java-library/src/main/java/com/microsoft/azure/functions/openai/annotation/assistant/AssistantPost.java b/java-library/src/main/java/com/microsoft/azure/functions/openai/annotation/assistant/AssistantPost.java index 768b127f..57b456d4 100644 --- a/java-library/src/main/java/com/microsoft/azure/functions/openai/annotation/assistant/AssistantPost.java +++ b/java-library/src/main/java/com/microsoft/azure/functions/openai/annotation/assistant/AssistantPost.java @@ -13,7 +13,6 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; - /** *

* Assistant post input attribute which is used to update the assistant. @@ -30,14 +29,14 @@ * The variable name used in function.json. * * @return The variable name used in function.json. - */ + */ String name(); - + /** * The ID of the Assistant to query. * * @return The ID of the Assistant to query. - */ + */ String id(); /** @@ -48,12 +47,26 @@ */ String model(); - /** * The user message that user has entered for assistant to respond to. * * @return The user message that user has entered for assistant to respond to. - */ + */ String userMessage(); + /** + * The configuration section name for the table settings for assistant chat + * storage. + * + * @return The configuration section name for the table settings for assistant + * chat storage. + */ + String chatStorageConnectionSetting(); + + /** + * The table collection name for assistant chat storage. + * + * @return the table collection name for assistant chat storage.. + */ + String collectionName(); } diff --git a/java-library/src/main/java/com/microsoft/azure/functions/openai/annotation/assistant/AssistantQuery.java b/java-library/src/main/java/com/microsoft/azure/functions/openai/annotation/assistant/AssistantQuery.java index cb4831dc..07a41799 100644 --- a/java-library/src/main/java/com/microsoft/azure/functions/openai/annotation/assistant/AssistantQuery.java +++ b/java-library/src/main/java/com/microsoft/azure/functions/openai/annotation/assistant/AssistantQuery.java @@ -12,10 +12,11 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; - + /** *

- * Assistant query input attribute which is used query the Assistant to get current state. + * Assistant query input attribute which is used query the Assistant to get + * current state. *

* * @since 1.0.0 @@ -31,7 +32,7 @@ * @return The variable name used in function.json. */ String name(); - + /** * The ID of the Assistant to query. * @@ -41,10 +42,26 @@ /** * The timestamp of the earliest message in the chat history to fetch. - * The timestamp should be in ISO 8601 format - for example, 2023-08-01T00:00:00Z. + * The timestamp should be in ISO 8601 format - for example, + * 2023-08-01T00:00:00Z. * * @return The timestamp of the earliest message in the chat history to fetch. */ String timestampUtc(); - - } + + /** + * The configuration section name for the table settings for assistant chat + * storage. + * + * @return The configuration section name for the table settings for assistant + * chat storage. + */ + String chatStorageConnectionSetting(); + + /** + * The table collection name for assistant chat storage. + * + * @return the table collection name for assistant chat storage.. + */ + String collectionName(); +} diff --git a/samples/assistant/csharp-ooproc/AssistantApis.cs b/samples/assistant/csharp-ooproc/AssistantApis.cs index 60cdd9ac..99b3c500 100644 --- a/samples/assistant/csharp-ooproc/AssistantApis.cs +++ b/samples/assistant/csharp-ooproc/AssistantApis.cs @@ -13,6 +13,9 @@ namespace AssistantSample; /// static class AssistantApis { + const string CHAT_STORAGE_CONNECTION_SETTING = "AzureWebJobsStorage"; + const string COLLECTION_NAME = "SampleChatState"; + /// /// HTTP PUT function that creates a new assistant chat bot with the specified ID. /// @@ -37,8 +40,8 @@ public static async Task CreateAssistant( HttpResponse = new ObjectResult(new { assistantId }) { StatusCode = 202 }, ChatBotCreateRequest = new AssistantCreateRequest(assistantId, instructions) { - ChatStorageConnectionSetting = "AzureWebJobsStorage", - CollectionName = "SampleChatState", + ChatStorageConnectionSetting = CHAT_STORAGE_CONNECTION_SETTING, + CollectionName = COLLECTION_NAME, }, }; } @@ -59,7 +62,7 @@ public class CreateChatBotOutput public static async Task PostUserQuery( [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "assistants/{assistantId}")] HttpRequestData req, string assistantId, - [AssistantPostInput("{assistantId}", "{Query.message}", Model = "%CHAT_MODEL_DEPLOYMENT_NAME%")] AssistantState state) + [AssistantPostInput("{assistantId}", "{Query.message}", Model = "%CHAT_MODEL_DEPLOYMENT_NAME%", ChatStorageConnectionSetting = CHAT_STORAGE_CONNECTION_SETTING, CollectionName = COLLECTION_NAME)] AssistantState state) { return new OkObjectResult(state.RecentMessages.LastOrDefault()?.Content ?? "No response returned."); } @@ -71,7 +74,7 @@ public static async Task PostUserQuery( public static async Task GetChatState( [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "assistants/{assistantId}")] HttpRequestData req, string assistantId, - [AssistantQueryInput("{assistantId}", TimestampUtc = "{Query.timestampUTC}")] AssistantState state) + [AssistantQueryInput("{assistantId}", TimestampUtc = "{Query.timestampUTC}", ChatStorageConnectionSetting = CHAT_STORAGE_CONNECTION_SETTING, CollectionName = COLLECTION_NAME)] AssistantState state) { return new OkObjectResult(state); } diff --git a/samples/chat/csharp-ooproc/ChatBot.cs b/samples/chat/csharp-ooproc/ChatBot.cs index 73025983..cd0db61d 100644 --- a/samples/chat/csharp-ooproc/ChatBot.cs +++ b/samples/chat/csharp-ooproc/ChatBot.cs @@ -12,6 +12,9 @@ namespace ChatBot; /// public static class ChatBot { + const string CHAT_STORAGE_CONNECTION_SETTING = "AzureWebJobsStorage"; + const string COLLECTION_NAME = "SampleChatState"; + public class CreateRequest { [JsonPropertyName("instructions")] @@ -44,8 +47,8 @@ public static async Task CreateChatBot( HttpResponse = new ObjectResult(responseJson) { StatusCode = 201 }, ChatBotCreateRequest = new AssistantCreateRequest(chatId, createRequestBody?.Instructions) { - ChatStorageConnectionSetting = "AzureWebJobsStorage", - CollectionName = "SampleChatState" + ChatStorageConnectionSetting = CHAT_STORAGE_CONNECTION_SETTING, + CollectionName = COLLECTION_NAME }, }; } @@ -63,7 +66,7 @@ public class CreateChatBotOutput public static async Task PostUserResponse( [HttpTrigger(AuthorizationLevel.Function, "post", Route = "chats/{chatId}")] HttpRequestData req, string chatId, - [AssistantPostInput("{chatId}", "{Query.message}", Model = "%CHAT_MODEL_DEPLOYMENT_NAME%")] AssistantState state) + [AssistantPostInput("{chatId}", "{Query.message}", Model = "%CHAT_MODEL_DEPLOYMENT_NAME%", ChatStorageConnectionSetting = CHAT_STORAGE_CONNECTION_SETTING, CollectionName = COLLECTION_NAME)] AssistantState state) { return new OkObjectResult(state.RecentMessages.LastOrDefault()?.Content ?? "No response returned."); } @@ -72,7 +75,7 @@ public static async Task PostUserResponse( public static async Task GetChatState( [HttpTrigger(AuthorizationLevel.Function, "get", Route = "chats/{chatId}")] HttpRequestData req, string chatId, - [AssistantQueryInput("{chatId}", TimestampUtc = "{Query.timestampUTC}")] AssistantState state, + [AssistantQueryInput("{chatId}", TimestampUtc = "{Query.timestampUTC}", ChatStorageConnectionSetting = CHAT_STORAGE_CONNECTION_SETTING, CollectionName = COLLECTION_NAME)] AssistantState state, FunctionContext context) { return new OkObjectResult(state); diff --git a/samples/chat/javascript/src/app.js b/samples/chat/javascript/src/app.js index 6c82941c..ae799f71 100644 --- a/samples/chat/javascript/src/app.js +++ b/samples/chat/javascript/src/app.js @@ -3,6 +3,9 @@ const { app, input, output } = require("@azure/functions"); +const CHAT_STORAGE_CONNECTION_SETTING = "AzureWebJobsStorage"; +const COLLECTION_NAME = "SampleChatState"; + const chatBotCreateOutput = output.generic({ type: 'assistantCreate' }) @@ -18,8 +21,8 @@ app.http('CreateChatBot', { const createRequest = { id: chatID, instructions: inputJson.instructions, - chatStorageConnectionSetting: "AzureWebJobsStorage", - collectionName: "SampleChatState" + chatStorageConnectionSetting: CHAT_STORAGE_CONNECTION_SETTING, + collectionName: COLLECTION_NAME } context.extraOutputs.set(chatBotCreateOutput, createRequest) return { status: 202, jsonBody: { chatId: chatID } } @@ -30,7 +33,9 @@ app.http('CreateChatBot', { const assistantQueryInput = input.generic({ type: 'assistantQuery', id: '{chatId}', - timestampUtc: '{Query.timestampUTC}' + timestampUtc: '{Query.timestampUTC}', + chatStorageConnectionSetting: CHAT_STORAGE_CONNECTION_SETTING, + collectionName: COLLECTION_NAME }) app.http('GetChatState', { methods: ['GET'], @@ -48,7 +53,9 @@ const assistantPostInput = input.generic({ type: 'assistantPost', id: '{chatID}', model: '%CHAT_MODEL_DEPLOYMENT_NAME%', - userMessage: '{Query.message}' + userMessage: '{Query.message}', + chatStorageConnectionSetting: CHAT_STORAGE_CONNECTION_SETTING, + collectionName: COLLECTION_NAME }) app.http('PostUserResponse', { methods: ['POST'], diff --git a/samples/chat/powershell/GetChatState/function.json b/samples/chat/powershell/GetChatState/function.json index 8a51d7da..52da792d 100644 --- a/samples/chat/powershell/GetChatState/function.json +++ b/samples/chat/powershell/GetChatState/function.json @@ -20,7 +20,9 @@ "direction": "in", "name": "ChatBotState", "id": "{chatId}", - "timeStampUtc": "{Query.timestampUTC}" + "timeStampUtc": "{Query.timestampUTC}", + "chatStorageConnectionSetting": "AzureWebJobsStorage", + "collectionName": "SampleChatState" } ] } \ No newline at end of file diff --git a/samples/chat/powershell/PostUserResponse/function.json b/samples/chat/powershell/PostUserResponse/function.json index f560e806..3a9c871c 100644 --- a/samples/chat/powershell/PostUserResponse/function.json +++ b/samples/chat/powershell/PostUserResponse/function.json @@ -21,7 +21,9 @@ "name": "ChatBotState", "id": "{chatId}", "model": "%CHAT_MODEL_DEPLOYMENT_NAME%", - "userMessage": "{Query.message}" + "userMessage": "{Query.message}", + "chatStorageConnectionSetting": "AzureWebJobsStorage", + "collectionName": "SampleChatState" } ] } \ No newline at end of file diff --git a/samples/chat/typescript/src/functions/app.ts b/samples/chat/typescript/src/functions/app.ts index 78fad462..62c495aa 100644 --- a/samples/chat/typescript/src/functions/app.ts +++ b/samples/chat/typescript/src/functions/app.ts @@ -3,6 +3,8 @@ import { HttpRequest, InvocationContext, app, input, output } from "@azure/functions"; +const CHAT_STORAGE_CONNECTION_SETTING = "AzureWebJobsStorage"; +const COLLECTION_NAME = "SampleChatState"; const chatBotCreateOutput = output.generic({ type: 'assistantCreate' @@ -19,8 +21,8 @@ app.http('CreateChatBot', { const createRequest = { id: chatID, instructions: inputJson.instructions, - chatStorageConnectionSetting: "AzureWebJobsStorage", - collectionName: "SampleChatState" + chatStorageConnectionSetting: CHAT_STORAGE_CONNECTION_SETTING, + collectionName: COLLECTION_NAME } context.extraOutputs.set(chatBotCreateOutput, createRequest) return { status: 202, jsonBody: { chatId: chatID } } @@ -31,7 +33,9 @@ app.http('CreateChatBot', { const assistantQueryInput = input.generic({ type: 'assistantQuery', id: '{chatId}', - timestampUtc: '{Query.timestampUTC}' + timestampUtc: '{Query.timestampUTC}', + chatStorageConnectionSetting: CHAT_STORAGE_CONNECTION_SETTING, + collectionName: COLLECTION_NAME }) app.http('GetChatState', { methods: ['GET'], @@ -49,7 +53,9 @@ const assistantPostInput = input.generic({ type: 'assistantPost', id: '{chatID}', model: '%CHAT_MODEL_DEPLOYMENT_NAME%', - userMessage: '{Query.message}' + userMessage: '{Query.message}', + chatStorageConnectionSetting: CHAT_STORAGE_CONNECTION_SETTING, + collectionName: COLLECTION_NAME }) app.http('PostUserResponse', { methods: ['POST'], diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 9fbbeeb4..ae985155 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -23,7 +23,7 @@ 0 - 17 + 18 0 $(MajorVersion).$(MinorVersion).$(PatchVersion) alpha diff --git a/src/Functions.Worker.Extensions.OpenAI/Assistants/AssistantPostInputAttribute.cs b/src/Functions.Worker.Extensions.OpenAI/Assistants/AssistantPostInputAttribute.cs index 3753ddb4..8104c8d6 100644 --- a/src/Functions.Worker.Extensions.OpenAI/Assistants/AssistantPostInputAttribute.cs +++ b/src/Functions.Worker.Extensions.OpenAI/Assistants/AssistantPostInputAttribute.cs @@ -33,4 +33,14 @@ public AssistantPostInputAttribute(string id, string UserMessage) /// Gets user message that user has entered for assistant to respond to. /// public string UserMessage { get; } + + /// + /// Configuration section name for the table settings for chat storage. + /// + public string? ChatStorageConnectionSetting { get; set; } + + /// + /// Table collection name for chat storage. + /// + public string CollectionName { get; set; } = "SampleChatState"; } diff --git a/src/Functions.Worker.Extensions.OpenAI/Assistants/AssistantQueryInputAttribute.cs b/src/Functions.Worker.Extensions.OpenAI/Assistants/AssistantQueryInputAttribute.cs index 74128d6e..b3711431 100644 --- a/src/Functions.Worker.Extensions.OpenAI/Assistants/AssistantQueryInputAttribute.cs +++ b/src/Functions.Worker.Extensions.OpenAI/Assistants/AssistantQueryInputAttribute.cs @@ -25,4 +25,14 @@ public AssistantQueryInputAttribute(string id) /// The timestamp should be in ISO 8601 format - for example, 2023-08-01T00:00:00Z. /// public string TimestampUtc { get; set; } = string.Empty; + + /// + /// Configuration section name for the table settings for chat storage. + /// + public string? ChatStorageConnectionSetting { get; set; } + + /// + /// Table collection name for chat storage. + /// + public string CollectionName { get; set; } = "SampleChatState"; } diff --git a/src/WebJobs.Extensions.OpenAI/Assistants/AssistantBindingConverter.cs b/src/WebJobs.Extensions.OpenAI/Assistants/AssistantBindingConverter.cs index 05f634a5..8ec12b63 100644 --- a/src/WebJobs.Extensions.OpenAI/Assistants/AssistantBindingConverter.cs +++ b/src/WebJobs.Extensions.OpenAI/Assistants/AssistantBindingConverter.cs @@ -33,13 +33,7 @@ public Task ConvertAsync( AssistantQueryAttribute input, CancellationToken cancellationToken) { - string timestampString = Uri.UnescapeDataString(input.TimestampUtc); - if (!DateTime.TryParse(timestampString, out DateTime timestamp)) - { - throw new ArgumentException($"Invalid timestamp '{timestampString}'"); - } - - return this.assistantService.GetStateAsync(input.Id, timestamp, cancellationToken); + return this.assistantService.GetStateAsync(input, cancellationToken); } async Task IAsyncConverter.ConvertAsync( diff --git a/src/WebJobs.Extensions.OpenAI/Assistants/AssistantPostAttribute.cs b/src/WebJobs.Extensions.OpenAI/Assistants/AssistantPostAttribute.cs index 6acc8e41..db6d3cd6 100644 --- a/src/WebJobs.Extensions.OpenAI/Assistants/AssistantPostAttribute.cs +++ b/src/WebJobs.Extensions.OpenAI/Assistants/AssistantPostAttribute.cs @@ -35,4 +35,14 @@ public AssistantPostAttribute(string id, string userMessage) /// [AutoResolve] public string UserMessage { get; } + + /// + /// Configuration section name for the table settings for chat storage. + /// + public string? ChatStorageConnectionSetting { get; set; } + + /// + /// Table collection name for chat storage. + /// + public string CollectionName { get; set; } = "SampleChatState"; } \ No newline at end of file diff --git a/src/WebJobs.Extensions.OpenAI/Assistants/AssistantQueryAttribute.cs b/src/WebJobs.Extensions.OpenAI/Assistants/AssistantQueryAttribute.cs index 28c0d3ff..ede24c9b 100644 --- a/src/WebJobs.Extensions.OpenAI/Assistants/AssistantQueryAttribute.cs +++ b/src/WebJobs.Extensions.OpenAI/Assistants/AssistantQueryAttribute.cs @@ -26,4 +26,14 @@ public AssistantQueryAttribute(string id) /// [AutoResolve] public string TimestampUtc { get; set; } = string.Empty; + + /// + /// Configuration section name for the table settings for chat storage. + /// + public string? ChatStorageConnectionSetting { get; set; } + + /// + /// Table collection name for chat storage. + /// + public string CollectionName { get; set; } = "SampleChatState"; } \ No newline at end of file diff --git a/src/WebJobs.Extensions.OpenAI/Assistants/AssistantService.cs b/src/WebJobs.Extensions.OpenAI/Assistants/AssistantService.cs index 3c783459..6071f157 100644 --- a/src/WebJobs.Extensions.OpenAI/Assistants/AssistantService.cs +++ b/src/WebJobs.Extensions.OpenAI/Assistants/AssistantService.cs @@ -14,7 +14,7 @@ namespace Microsoft.Azure.WebJobs.Extensions.OpenAI.Assistants; public interface IAssistantService { Task CreateAssistantAsync(AssistantCreateRequest request, CancellationToken cancellationToken); - Task GetStateAsync(string id, DateTime since, CancellationToken cancellationToken); + Task GetStateAsync(AssistantQueryAttribute assistantQuery, CancellationToken cancellationToken); Task PostMessageAsync(AssistantPostAttribute attribute, CancellationToken cancellationToken); } @@ -63,7 +63,7 @@ public async Task CreateAssistantAsync(AssistantCreateRequest request, Cancellat request.Id, request.Instructions ?? "(none)"); - this.CreateTableClient(request); + this.CreateTableClient(request.ChatStorageConnectionSetting, request.CollectionName); if (this.tableClient is null) { @@ -136,14 +136,26 @@ async Task DeleteBatch() await this.tableClient.SubmitTransactionAsync(batch); } - public async Task GetStateAsync(string id, DateTime after, CancellationToken cancellationToken) + public async Task GetStateAsync(AssistantQueryAttribute assistantQuery, CancellationToken cancellationToken) { - DateTime afterUtc = after.ToUniversalTime(); + string id = assistantQuery.Id; + string timestampString = Uri.UnescapeDataString(assistantQuery.TimestampUtc); + if (!DateTime.TryParse(timestampString, out DateTime timestamp)) + { + throw new ArgumentException($"Invalid timestamp '{timestampString}'"); + } + + DateTime afterUtc = timestamp.ToUniversalTime(); this.logger.LogInformation( "Reading state for assistant entity '{Id}' and getting chat messages after {Timestamp}", id, afterUtc.ToString("o")); + if (this.tableClient is null) + { + this.CreateTableClient(assistantQuery.ChatStorageConnectionSetting, assistantQuery.CollectionName); + } + InternalChatState? chatState = await this.LoadChatStateAsync(id, cancellationToken); if (chatState is null) { @@ -176,7 +188,7 @@ public async Task PostMessageAsync(AssistantPostAttribute attrib DateTime timeFilter = DateTime.UtcNow; if (string.IsNullOrEmpty(attribute.Id)) { - throw new ArgumentException("The assistant ID must be specified.", nameof(attribute)); + throw new ArgumentException("The assistant Id must be specified.", nameof(attribute)); } if (string.IsNullOrEmpty(attribute.UserMessage)) @@ -186,7 +198,12 @@ public async Task PostMessageAsync(AssistantPostAttribute attrib if (this.tableClient is null) { - throw new ArgumentException("The assistant must be initialized first using CreateAssistantAsync", nameof(this.tableClient)); + this.CreateTableClient(attribute.ChatStorageConnectionSetting, attribute.CollectionName); + } + + if (this.tableClient is null) + { + throw new ArgumentNullException(nameof(this.tableClient)); } this.logger.LogInformation("Posting message to assistant entity '{Id}'", attribute.Id); @@ -389,7 +406,7 @@ public async Task PostMessageAsync(AssistantPostAttribute attrib { if (this.tableClient is null) { - throw new ArgumentException("The assistant must be initialized first using CreateAssistantAsync", nameof(this.tableClient)); + throw new ArgumentNullException(nameof(this.tableClient)); } // Check to see if any entity exists with partition id @@ -451,9 +468,9 @@ static IEnumerable ToOpenAIChatRequestMessages(IEnumerable(connectionStringName); @@ -489,7 +506,7 @@ void CreateTableClient(AssistantCreateRequest request) this.tableServiceClient = new TableServiceClient(connectionString); } - this.logger.LogInformation("Using {CollectionName} for table storage collection name", request.CollectionName); - this.tableClient = this.tableServiceClient.GetTableClient(request.CollectionName); + this.logger.LogInformation("Using {CollectionName} for table storage collection name", collectionName); + this.tableClient = this.tableServiceClient.GetTableClient(collectionName); } } \ No newline at end of file From dcf4e45721b4f584c57d178c50edb169119654a2 Mon Sep 17 00:00:00 2001 From: manvkaur <67894494+manvkaur@users.noreply.github.com> Date: Mon, 23 Sep 2024 11:22:32 +0100 Subject: [PATCH 02/10] update java library for table client post and query --- java-library/pom.xml | 2 +- .../annotation/assistant/AssistantPost.java | 25 ++++++++++++++++--- .../annotation/assistant/AssistantQuery.java | 25 ++++++++++++++++--- .../embeddings/EmbeddingsContext.java | 2 +- .../Assistants/AssistantService.cs | 13 ++++++---- 5 files changed, 52 insertions(+), 15 deletions(-) diff --git a/java-library/pom.xml b/java-library/pom.xml index e1a8d766..c8951db5 100644 --- a/java-library/pom.xml +++ b/java-library/pom.xml @@ -90,7 +90,7 @@ com.azure azure-ai-openai - 1.0.0-beta.10 + 1.0.0-beta.11 compile diff --git a/java-library/src/main/java/com/microsoft/azure/functions/openai/annotation/assistant/AssistantPost.java b/java-library/src/main/java/com/microsoft/azure/functions/openai/annotation/assistant/AssistantPost.java index 57b456d4..4850bccc 100644 --- a/java-library/src/main/java/com/microsoft/azure/functions/openai/annotation/assistant/AssistantPost.java +++ b/java-library/src/main/java/com/microsoft/azure/functions/openai/annotation/assistant/AssistantPost.java @@ -25,6 +25,22 @@ @CustomBinding(direction = "in", name = "", type = "assistantPost") public @interface AssistantPost { + /** + * The default storage account setting for the table storage account. + * This constant is used to specify the connection string for the table storage + * account + * where chat data will be stored. + */ + String DEFAULT_CHATSTORAGE = "AzureWebJobsStorage"; + + /** + * The default collection name for the table storage account. + * This constant is used to specify the collection name for the table storage + * account + * where chat data will be stored. + */ + String DEFAULT_COLLECTION = "SampleChatState"; + /** * The variable name used in function.json. * @@ -59,14 +75,15 @@ * storage. * * @return The configuration section name for the table settings for assistant - * chat storage. + * chat storage. By default, it returns {@code DEFAULT_CHATSTORAGE}. */ - String chatStorageConnectionSetting(); + String chatStorageConnectionSetting() default DEFAULT_CHATSTORAGE; /** * The table collection name for assistant chat storage. * - * @return the table collection name for assistant chat storage.. + * @return the table collection name for assistant chat storage.By default, it + * returns {@code DEFAULT_COLLECTION}. */ - String collectionName(); + String collectionName() default DEFAULT_COLLECTION; } diff --git a/java-library/src/main/java/com/microsoft/azure/functions/openai/annotation/assistant/AssistantQuery.java b/java-library/src/main/java/com/microsoft/azure/functions/openai/annotation/assistant/AssistantQuery.java index 07a41799..76cf262a 100644 --- a/java-library/src/main/java/com/microsoft/azure/functions/openai/annotation/assistant/AssistantQuery.java +++ b/java-library/src/main/java/com/microsoft/azure/functions/openai/annotation/assistant/AssistantQuery.java @@ -26,6 +26,22 @@ @CustomBinding(direction = "in", name = "", type = "assistantQuery") public @interface AssistantQuery { + /** + * The default storage account setting for the table storage account. + * This constant is used to specify the connection string for the table storage + * account + * where chat data will be stored. + */ + String DEFAULT_CHATSTORAGE = "AzureWebJobsStorage"; + + /** + * The default collection name for the table storage account. + * This constant is used to specify the collection name for the table storage + * account + * where chat data will be stored. + */ + String DEFAULT_COLLECTION = "SampleChatState"; + /** * The variable name used in function.json. * @@ -54,14 +70,15 @@ * storage. * * @return The configuration section name for the table settings for assistant - * chat storage. + * chat storage. By default, it returns {@code DEFAULT_CHATSTORAGE}. */ - String chatStorageConnectionSetting(); + String chatStorageConnectionSetting() default DEFAULT_CHATSTORAGE; /** * The table collection name for assistant chat storage. * - * @return the table collection name for assistant chat storage.. + * @return the table collection name for assistant chat storage.By default, it + * returns {@code DEFAULT_COLLECTION}. */ - String collectionName(); + String collectionName() default DEFAULT_COLLECTION; } diff --git a/java-library/src/main/java/com/microsoft/azure/functions/openai/annotation/embeddings/EmbeddingsContext.java b/java-library/src/main/java/com/microsoft/azure/functions/openai/annotation/embeddings/EmbeddingsContext.java index 8908b33b..1627b380 100644 --- a/java-library/src/main/java/com/microsoft/azure/functions/openai/annotation/embeddings/EmbeddingsContext.java +++ b/java-library/src/main/java/com/microsoft/azure/functions/openai/annotation/embeddings/EmbeddingsContext.java @@ -13,7 +13,7 @@ public class EmbeddingsContext { private EmbeddingsOptions request; private Embeddings response; - private int count; + private int count = 0; public EmbeddingsOptions getRequest() { return request; diff --git a/src/WebJobs.Extensions.OpenAI/Assistants/AssistantService.cs b/src/WebJobs.Extensions.OpenAI/Assistants/AssistantService.cs index 6071f157..503b2078 100644 --- a/src/WebJobs.Extensions.OpenAI/Assistants/AssistantService.cs +++ b/src/WebJobs.Extensions.OpenAI/Assistants/AssistantService.cs @@ -63,11 +63,14 @@ public async Task CreateAssistantAsync(AssistantCreateRequest request, Cancellat request.Id, request.Instructions ?? "(none)"); - this.CreateTableClient(request.ChatStorageConnectionSetting, request.CollectionName); + if (this.tableClient is null) + { + this.CreateTableClient(request.ChatStorageConnectionSetting, request.CollectionName); + } if (this.tableClient is null) { - throw new ArgumentNullException(nameof(this.tableClient)); + throw new InvalidOperationException($"{nameof(this.tableClient)} is null."); } // Create the table if it doesn't exist @@ -142,7 +145,7 @@ public async Task GetStateAsync(AssistantQueryAttribute assistan string timestampString = Uri.UnescapeDataString(assistantQuery.TimestampUtc); if (!DateTime.TryParse(timestampString, out DateTime timestamp)) { - throw new ArgumentException($"Invalid timestamp '{timestampString}'"); + throw new ArgumentException($"Invalid timestamp '{assistantQuery.TimestampUtc}'"); } DateTime afterUtc = timestamp.ToUniversalTime(); @@ -203,7 +206,7 @@ public async Task PostMessageAsync(AssistantPostAttribute attrib if (this.tableClient is null) { - throw new ArgumentNullException(nameof(this.tableClient)); + throw new InvalidOperationException($"{nameof(this.tableClient)} is null."); } this.logger.LogInformation("Posting message to assistant entity '{Id}'", attribute.Id); @@ -406,7 +409,7 @@ public async Task PostMessageAsync(AssistantPostAttribute attrib { if (this.tableClient is null) { - throw new ArgumentNullException(nameof(this.tableClient)); + throw new InvalidOperationException($"{nameof(this.tableClient)} is null."); } // Check to see if any entity exists with partition id From 7c6730fba5038fbcb850fa794f004a214c5e7861 Mon Sep 17 00:00:00 2001 From: manvkaur <67894494+manvkaur@users.noreply.github.com> Date: Mon, 23 Sep 2024 11:23:14 +0100 Subject: [PATCH 03/10] add null checks --- samples/assistant/csharp-ooproc/TodoManager.cs | 13 +++---------- .../Embeddings/EmbeddingsStoreConverter.cs | 5 +++-- .../Search/SearchableDocumentJsonConverter.cs | 6 +++--- 3 files changed, 9 insertions(+), 15 deletions(-) diff --git a/samples/assistant/csharp-ooproc/TodoManager.cs b/samples/assistant/csharp-ooproc/TodoManager.cs index c267925b..53d572e2 100644 --- a/samples/assistant/csharp-ooproc/TodoManager.cs +++ b/samples/assistant/csharp-ooproc/TodoManager.cs @@ -67,22 +67,15 @@ class CosmosDbTodoManager : ITodoManager public CosmosDbTodoManager(ILoggerFactory loggerFactory, CosmosClient cosmosClient) { - if (loggerFactory is null) - { - throw new ArgumentNullException(nameof(loggerFactory)); - } - - if (cosmosClient is null) - { - throw new ArgumentNullException(nameof(cosmosClient)); - } + ArgumentNullException.ThrowIfNull(loggerFactory, nameof(loggerFactory)); + ArgumentNullException.ThrowIfNull(cosmosClient, nameof(cosmosClient)); string? CosmosDatabaseName = Environment.GetEnvironmentVariable("CosmosDatabaseName"); string? CosmosContainerName = Environment.GetEnvironmentVariable("CosmosContainerName"); if (string.IsNullOrEmpty(CosmosDatabaseName) || string.IsNullOrEmpty(CosmosContainerName)) { - throw new ArgumentNullException("CosmosDatabaseName and CosmosContainerName must be set as environment variables or in local.settings.json"); + throw new InvalidOperationException("CosmosDatabaseName and CosmosContainerName must be set as environment variables or in local.settings.json"); } this.logger = loggerFactory.CreateLogger(); diff --git a/src/WebJobs.Extensions.OpenAI/Embeddings/EmbeddingsStoreConverter.cs b/src/WebJobs.Extensions.OpenAI/Embeddings/EmbeddingsStoreConverter.cs index ee76230a..22fcafb7 100644 --- a/src/WebJobs.Extensions.OpenAI/Embeddings/EmbeddingsStoreConverter.cs +++ b/src/WebJobs.Extensions.OpenAI/Embeddings/EmbeddingsStoreConverter.cs @@ -49,8 +49,9 @@ public Task> ConvertAsync(EmbeddingsStoreAtt internal SearchableDocument ToSearchableDocument(string? json) { this.logger.LogDebug("Creating searchable document from JSON string: {Text}", json); - SearchableDocument document = JsonSerializer.Deserialize(json, options); - return document ?? throw new ArgumentException("Invalid search request."); + SearchableDocument document = JsonSerializer.Deserialize(json ?? throw new ArgumentNullException(nameof(json)), options) + ?? throw new ArgumentException("Invalid search request."); + return document; } sealed class SemanticDocumentCollector : IAsyncCollector diff --git a/src/WebJobs.Extensions.OpenAI/Search/SearchableDocumentJsonConverter.cs b/src/WebJobs.Extensions.OpenAI/Search/SearchableDocumentJsonConverter.cs index 2526a6c0..0bbf9b55 100644 --- a/src/WebJobs.Extensions.OpenAI/Search/SearchableDocumentJsonConverter.cs +++ b/src/WebJobs.Extensions.OpenAI/Search/SearchableDocumentJsonConverter.cs @@ -49,18 +49,18 @@ public override SearchableDocument Read(ref Utf8JsonReader reader, Type typeToCo { if (connectionInfoItem.NameEquals("connectionName"u8)) { - connectionName = connectionInfoItem.Value.GetString(); + connectionName = connectionInfoItem.Value.GetString() ?? string.Empty; } if (connectionInfoItem.NameEquals("collectionName"u8)) { - collectionName = connectionInfoItem.Value.GetString(); + collectionName = connectionInfoItem.Value.GetString() ?? string.Empty; } } } if (item.NameEquals("title"u8)) { - title = item.Value.GetString(); + title = item.Value.GetString() ?? string.Empty; } } SearchableDocument searchableDocument = new SearchableDocument(title) From 1de690448a3ac749a8d5e1bec31523dbfc4bd596 Mon Sep 17 00:00:00 2001 From: manvkaur <67894494+manvkaur@users.noreply.github.com> Date: Mon, 23 Sep 2024 11:27:02 +0100 Subject: [PATCH 04/10] add null checks for IngestEmail --- .../rag-aisearch/csharp-ooproc/FilePrompt.cs | 19 ++++++++++++++----- .../rag-cosmosdb/csharp-ooproc/FilePrompt.cs | 19 ++++++++++++++----- .../csharp-ooproc/EmailPromptDemo.cs | 19 ++++++++++++++----- 3 files changed, 42 insertions(+), 15 deletions(-) diff --git a/samples/rag-aisearch/csharp-ooproc/FilePrompt.cs b/samples/rag-aisearch/csharp-ooproc/FilePrompt.cs index 56fcabde..7cda09af 100644 --- a/samples/rag-aisearch/csharp-ooproc/FilePrompt.cs +++ b/samples/rag-aisearch/csharp-ooproc/FilePrompt.cs @@ -30,24 +30,33 @@ public class SemanticSearchRequest public static async Task IngestFile( [HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequestData req) { + ArgumentNullException.ThrowIfNull(req); + using StreamReader reader = new(req.Body); string request = await reader.ReadToEndAsync(); + if (string.IsNullOrWhiteSpace(request)) + { + throw new ArgumentException("Request body is empty."); + } + EmbeddingsRequest? requestBody = JsonSerializer.Deserialize(request); - if (requestBody == null || requestBody.Url == null) + if (string.IsNullOrWhiteSpace(requestBody?.Url)) { throw new ArgumentException("Invalid request body. Make sure that you pass in {\"Url\": value } as the request body."); } - Uri uri = new(requestBody.Url); - string filename = Path.GetFileName(uri.AbsolutePath); + if (!Uri.TryCreate(requestBody.Url, UriKind.Absolute, out Uri? uri)) + { + throw new ArgumentException("Invalid Url format."); + } - IActionResult result = new OkObjectResult(new { status = HttpStatusCode.OK }); + string filename = Path.GetFileName(uri.AbsolutePath); return new EmbeddingsStoreOutputResponse { - HttpResponse = result, + HttpResponse = new OkObjectResult(new { status = HttpStatusCode.OK }), SearchableDocument = new SearchableDocument(filename) }; } diff --git a/samples/rag-cosmosdb/csharp-ooproc/FilePrompt.cs b/samples/rag-cosmosdb/csharp-ooproc/FilePrompt.cs index ed0ba589..87587626 100644 --- a/samples/rag-cosmosdb/csharp-ooproc/FilePrompt.cs +++ b/samples/rag-cosmosdb/csharp-ooproc/FilePrompt.cs @@ -30,24 +30,33 @@ public class SemanticSearchRequest public static async Task IngestFile( [HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequestData req) { + ArgumentNullException.ThrowIfNull(req); + using StreamReader reader = new(req.Body); string request = await reader.ReadToEndAsync(); + if (string.IsNullOrWhiteSpace(request)) + { + throw new ArgumentException("Request body is empty."); + } + EmbeddingsRequest? requestBody = JsonSerializer.Deserialize(request); - if (requestBody == null || requestBody.Url == null) + if (string.IsNullOrWhiteSpace(requestBody?.Url)) { throw new ArgumentException("Invalid request body. Make sure that you pass in {\"Url\": value } as the request body."); } - Uri uri = new(requestBody.Url); - string filename = Path.GetFileName(uri.AbsolutePath); + if (!Uri.TryCreate(requestBody.Url, UriKind.Absolute, out Uri? uri)) + { + throw new ArgumentException("Invalid Url format."); + } - IActionResult result = new OkObjectResult(new { status = HttpStatusCode.OK }); + string filename = Path.GetFileName(uri.AbsolutePath); return new EmbeddingsStoreOutputResponse { - HttpResponse = result, + HttpResponse = new OkObjectResult(new { status = HttpStatusCode.OK }), SearchableDocument = new SearchableDocument(filename) }; } diff --git a/samples/rag-kusto/csharp-ooproc/EmailPromptDemo.cs b/samples/rag-kusto/csharp-ooproc/EmailPromptDemo.cs index b9f1ec6a..972ad200 100644 --- a/samples/rag-kusto/csharp-ooproc/EmailPromptDemo.cs +++ b/samples/rag-kusto/csharp-ooproc/EmailPromptDemo.cs @@ -30,24 +30,33 @@ public class SemanticSearchRequest public async Task IngestEmail( [HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequestData req) { + ArgumentNullException.ThrowIfNull(req); + using StreamReader reader = new(req.Body); string request = await reader.ReadToEndAsync(); + if (string.IsNullOrWhiteSpace(request)) + { + throw new ArgumentException("Request body is empty."); + } + EmbeddingsRequest? requestBody = JsonSerializer.Deserialize(request); - if (requestBody == null || requestBody.Url == null) + if (string.IsNullOrWhiteSpace(requestBody?.Url)) { throw new ArgumentException("Invalid request body. Make sure that you pass in {\"Url\": value } as the request body."); } - Uri uri = new(requestBody.Url); - string filename = Path.GetFileName(uri.AbsolutePath); + if (!Uri.TryCreate(requestBody.Url, UriKind.Absolute, out Uri? uri)) + { + throw new ArgumentException("Invalid Url format."); + } - IActionResult result = new OkObjectResult(new { status = HttpStatusCode.OK }); + string filename = Path.GetFileName(uri.AbsolutePath); return new EmbeddingsStoreOutputResponse { - HttpResponse = result, + HttpResponse = new OkObjectResult(new { status = HttpStatusCode.OK }), SearchableDocument = new SearchableDocument(filename) }; } From af06e5d47c906a22a2103631800d9c3c282abc5b Mon Sep 17 00:00:00 2001 From: manvkaur <67894494+manvkaur@users.noreply.github.com> Date: Mon, 23 Sep 2024 11:28:51 +0100 Subject: [PATCH 05/10] update packages --- .../WebJobs.Extensions.OpenAI.csproj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/WebJobs.Extensions.OpenAI/WebJobs.Extensions.OpenAI.csproj b/src/WebJobs.Extensions.OpenAI/WebJobs.Extensions.OpenAI.csproj index 522080b3..65ecfa87 100644 --- a/src/WebJobs.Extensions.OpenAI/WebJobs.Extensions.OpenAI.csproj +++ b/src/WebJobs.Extensions.OpenAI/WebJobs.Extensions.OpenAI.csproj @@ -6,12 +6,12 @@ - - + + - + - + From aa411823ba297c2bf024c175d37d7d532435f934 Mon Sep 17 00:00:00 2001 From: manvkaur <67894494+manvkaur@users.noreply.github.com> Date: Mon, 23 Sep 2024 16:00:01 +0100 Subject: [PATCH 06/10] update worker core package --- .../Functions.Worker.Extensions.OpenAI.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Functions.Worker.Extensions.OpenAI/Functions.Worker.Extensions.OpenAI.csproj b/src/Functions.Worker.Extensions.OpenAI/Functions.Worker.Extensions.OpenAI.csproj index eb465162..ee76b15d 100644 --- a/src/Functions.Worker.Extensions.OpenAI/Functions.Worker.Extensions.OpenAI.csproj +++ b/src/Functions.Worker.Extensions.OpenAI/Functions.Worker.Extensions.OpenAI.csproj @@ -8,7 +8,7 @@ - + From 7863d9a6401f514dcce9b6b99c864b6e3aaf078a Mon Sep 17 00:00:00 2001 From: manvkaur <67894494+manvkaur@users.noreply.github.com> Date: Mon, 23 Sep 2024 16:57:11 +0100 Subject: [PATCH 07/10] update assistant samples for js,ts and ps --- .../javascript/src/functions/assistantApis.js | 15 +++++++++++---- .../assistant/powershell/CreateAssistant/run.ps1 | 4 ++-- .../powershell/GetChatState/function.json | 4 +++- .../powershell/PostUserQuery/function.json | 4 +++- .../typescript/src/functions/assistantApis.ts | 14 ++++++++++---- samples/chat/powershell/CreateChatBot/run.ps1 | 4 ++-- 6 files changed, 31 insertions(+), 14 deletions(-) diff --git a/samples/assistant/javascript/src/functions/assistantApis.js b/samples/assistant/javascript/src/functions/assistantApis.js index 469cae21..b64289e3 100644 --- a/samples/assistant/javascript/src/functions/assistantApis.js +++ b/samples/assistant/javascript/src/functions/assistantApis.js @@ -3,6 +3,9 @@ const { app, input, output } = require("@azure/functions"); +const CHAT_STORAGE_CONNECTION_SETTING = "AzureWebJobsStorage"; +const COLLECTION_NAME = "SampleChatState"; + const chatBotCreateOutput = output.generic({ type: 'assistantCreate' }) @@ -21,8 +24,8 @@ app.http('CreateAssistant', { const createRequest = { id: assistantId, instructions: instructions, - chatStorageConnectionSetting: "AzureWebJobsStorage", - collectionName: "SampleChatState" + chatStorageConnectionSetting: CHAT_STORAGE_CONNECTION_SETTING, + collectionName: COLLECTION_NAME } context.extraOutputs.set(chatBotCreateOutput, createRequest) return { status: 202, jsonBody: { assistantId: assistantId } } @@ -34,7 +37,9 @@ const assistantPostInput = input.generic({ type: 'assistantPost', id: '{assistantId}', model: '%CHAT_MODEL_DEPLOYMENT_NAME%', - userMessage: '{Query.message}' + userMessage: '{Query.message}', + chatStorageConnectionSetting: CHAT_STORAGE_CONNECTION_SETTING, + collectionName: COLLECTION_NAME }) app.http('PostUserResponse', { methods: ['POST'], @@ -58,7 +63,9 @@ app.http('PostUserResponse', { const chatBotQueryInput = input.generic({ type: 'assistantQuery', id: '{assistantId}', - timestampUtc: '{Query.timestampUTC}' + timestampUtc: '{Query.timestampUTC}', + chatStorageConnectionSetting: CHAT_STORAGE_CONNECTION_SETTING, + collectionName: COLLECTION_NAME }) app.http('GetChatState', { methods: ['GET'], diff --git a/samples/assistant/powershell/CreateAssistant/run.ps1 b/samples/assistant/powershell/CreateAssistant/run.ps1 index d61679a9..08fdb14d 100644 --- a/samples/assistant/powershell/CreateAssistant/run.ps1 +++ b/samples/assistant/powershell/CreateAssistant/run.ps1 @@ -9,8 +9,8 @@ $instructions += "\nAsk for clarification if a user request is ambiguous." $create_request = @{ "id" = $assistantId - "instructions" = $instructions, - "chatStorageConnectionSetting" = "AzureWebJobsStorage", + "instructions" = $instructions + "chatStorageConnectionSetting" = "AzureWebJobsStorage" "collectionName" = "SampleChatState" } diff --git a/samples/assistant/powershell/GetChatState/function.json b/samples/assistant/powershell/GetChatState/function.json index 56e22792..dc5a00a4 100644 --- a/samples/assistant/powershell/GetChatState/function.json +++ b/samples/assistant/powershell/GetChatState/function.json @@ -21,7 +21,9 @@ "direction": "in", "dataType": "string", "id": "{assistantId}", - "timestampUtc": "{Query.timestampUTC}" + "timestampUtc": "{Query.timestampUTC}", + "chatStorageConnectionSetting": "AzureWebJobsStorage", + "collectionName": "SampleChatState" } ] } \ No newline at end of file diff --git a/samples/assistant/powershell/PostUserQuery/function.json b/samples/assistant/powershell/PostUserQuery/function.json index c6958834..1be27abf 100644 --- a/samples/assistant/powershell/PostUserQuery/function.json +++ b/samples/assistant/powershell/PostUserQuery/function.json @@ -22,7 +22,9 @@ "dataType": "string", "id": "{assistantId}", "userMessage": "{Query.message}", - "model": "%CHAT_MODEL_DEPLOYMENT_NAME%" + "model": "%CHAT_MODEL_DEPLOYMENT_NAME%", + "chatStorageConnectionSetting": "AzureWebJobsStorage", + "collectionName": "SampleChatState" } ] } \ No newline at end of file diff --git a/samples/assistant/typescript/src/functions/assistantApis.ts b/samples/assistant/typescript/src/functions/assistantApis.ts index 27d2963c..81f27d4e 100644 --- a/samples/assistant/typescript/src/functions/assistantApis.ts +++ b/samples/assistant/typescript/src/functions/assistantApis.ts @@ -3,6 +3,8 @@ import { HttpRequest, InvocationContext, app, input, output } from "@azure/functions" +const CHAT_STORAGE_CONNECTION_SETTING = "AzureWebJobsStorage"; +const COLLECTION_NAME = "SampleChatState"; const chatBotCreateOutput = output.generic({ type: 'assistantCreate' @@ -22,8 +24,8 @@ app.http('CreateAssistant', { const createRequest = { id: assistantId, instructions: instructions, - chatStorageConnectionSetting: "AzureWebJobsStorage", - collectionName: "SampleChatState" + chatStorageConnectionSetting: CHAT_STORAGE_CONNECTION_SETTING, + collectionName: COLLECTION_NAME } context.extraOutputs.set(chatBotCreateOutput, createRequest) return { status: 202, jsonBody: { assistantId: assistantId } } @@ -35,7 +37,9 @@ const assistantPostInput = input.generic({ type: 'assistantPost', id: '{assistantId}', model: '%CHAT_MODEL_DEPLOYMENT_NAME%', - userMessage: '{Query.message}' + userMessage: '{Query.message}', + chatStorageConnectionSetting: CHAT_STORAGE_CONNECTION_SETTING, + collectionName: COLLECTION_NAME }) app.http('PostUserResponse', { methods: ['POST'], @@ -59,7 +63,9 @@ app.http('PostUserResponse', { const chatBotQueryInput = input.generic({ type: 'assistantQuery', id: '{assistantId}', - timestampUtc: '{Query.timestampUTC}' + timestampUtc: '{Query.timestampUTC}', + chatStorageConnectionSetting: CHAT_STORAGE_CONNECTION_SETTING, + collectionName: COLLECTION_NAME }) app.http('GetChatState', { methods: ['GET'], diff --git a/samples/chat/powershell/CreateChatBot/run.ps1 b/samples/chat/powershell/CreateChatBot/run.ps1 index e669a730..6406004d 100644 --- a/samples/chat/powershell/CreateChatBot/run.ps1 +++ b/samples/chat/powershell/CreateChatBot/run.ps1 @@ -8,8 +8,8 @@ Write-Host "Creating chat $chatID from input parameters $($inputJson)" $createRequest = @{ id = $chatID - instructions = $inputJson.Instructions, - chatStorageConnectionSetting = "AzureWebJobsStorage", + instructions = $inputJson.Instructions + chatStorageConnectionSetting = "AzureWebJobsStorage" collectionName = "SampleChatState" } From 826f5efe7760ac7cd3ecf4c2443ec16ceb150668 Mon Sep 17 00:00:00 2001 From: manvkaur <67894494+manvkaur@users.noreply.github.com> Date: Thu, 26 Sep 2024 15:33:13 +0100 Subject: [PATCH 08/10] resolve Chris' comments --- .../assistant/AssistantCreateRequest.java | 2 +- .../annotation/assistant/AssistantPost.java | 2 +- .../annotation/assistant/AssistantQuery.java | 2 +- samples/assistant/README.md | 49 +++++++---------- .../assistant/csharp-ooproc/AssistantApis.cs | 12 ++--- .../javascript/src/functions/assistantApis.js | 2 +- .../powershell/CreateAssistant/run.ps1 | 2 +- .../powershell/GetChatState/function.json | 2 +- .../powershell/PostUserQuery/function.json | 2 +- samples/assistant/python/assistant_apis.py | 2 +- .../typescript/src/functions/assistantApis.ts | 2 +- samples/chat/csharp-ooproc/ChatBot.cs | 12 ++--- samples/chat/javascript/src/app.js | 2 +- samples/chat/powershell/CreateChatBot/run.ps1 | 2 +- .../powershell/GetChatState/function.json | 2 +- .../powershell/PostUserResponse/function.json | 2 +- samples/chat/python/function_app.py | 2 +- samples/chat/typescript/src/functions/app.ts | 2 +- .../Assistants/AssistantCreateRequest.cs | 2 +- .../Assistants/AssistantPostInputAttribute.cs | 2 +- .../AssistantQueryInputAttribute.cs | 2 +- .../Assistants/AssistantCreateAttribute.cs | 2 +- .../Assistants/AssistantPostAttribute.cs | 2 +- .../Assistants/AssistantQueryAttribute.cs | 2 +- .../Assistants/AssistantService.cs | 52 ++++++------------- .../Embeddings/EmbeddingsStoreConverter.cs | 6 ++- 26 files changed, 72 insertions(+), 101 deletions(-) diff --git a/java-library/src/main/java/com/microsoft/azure/functions/openai/annotation/assistant/AssistantCreateRequest.java b/java-library/src/main/java/com/microsoft/azure/functions/openai/annotation/assistant/AssistantCreateRequest.java index 7e621f63..f9f0c6ef 100644 --- a/java-library/src/main/java/com/microsoft/azure/functions/openai/annotation/assistant/AssistantCreateRequest.java +++ b/java-library/src/main/java/com/microsoft/azure/functions/openai/annotation/assistant/AssistantCreateRequest.java @@ -14,7 +14,7 @@ public class AssistantCreateRequest { private String id; private String instructions = "You are a helpful assistant."; private String chatStorageConnectionSetting; - private String collectionName = "SampleChatState"; + private String collectionName = "ChatState"; public AssistantCreateRequest(String id) { this.id = id; diff --git a/java-library/src/main/java/com/microsoft/azure/functions/openai/annotation/assistant/AssistantPost.java b/java-library/src/main/java/com/microsoft/azure/functions/openai/annotation/assistant/AssistantPost.java index 4850bccc..67a3cc3f 100644 --- a/java-library/src/main/java/com/microsoft/azure/functions/openai/annotation/assistant/AssistantPost.java +++ b/java-library/src/main/java/com/microsoft/azure/functions/openai/annotation/assistant/AssistantPost.java @@ -39,7 +39,7 @@ * account * where chat data will be stored. */ - String DEFAULT_COLLECTION = "SampleChatState"; + String DEFAULT_COLLECTION = "ChatState"; /** * The variable name used in function.json. diff --git a/java-library/src/main/java/com/microsoft/azure/functions/openai/annotation/assistant/AssistantQuery.java b/java-library/src/main/java/com/microsoft/azure/functions/openai/annotation/assistant/AssistantQuery.java index 76cf262a..caf7c2a6 100644 --- a/java-library/src/main/java/com/microsoft/azure/functions/openai/annotation/assistant/AssistantQuery.java +++ b/java-library/src/main/java/com/microsoft/azure/functions/openai/annotation/assistant/AssistantQuery.java @@ -40,7 +40,7 @@ * account * where chat data will be stored. */ - String DEFAULT_COLLECTION = "SampleChatState"; + String DEFAULT_COLLECTION = "ChatState"; /** * The variable name used in function.json. diff --git a/samples/assistant/README.md b/samples/assistant/README.md index eda3965b..b4d0e541 100644 --- a/samples/assistant/README.md +++ b/samples/assistant/README.md @@ -26,27 +26,6 @@ This OpenAI extension internally uses the [function calling](https://platform.op * 0301 is the default and oldest model version for gpt-3.5 but it doesn't support this feature. * Model version 1106 has known issue with duplicate function calls in the OpenAI extension, check the repo issues for progress as the extension team works on it. -### Chat Storage Configuration - -If you are using a different table storage than `AzureWebJobsStorage` for chat storage, follow these steps: - -1. **Managed Identity - Assign Permissions**: - * Assign the user or function app's managed identity the role of `Storage Table Data Contributor`. - -1. **Configure Table Service URI**: - * Set the `tableServiceUri` in the configuration as follows: - - ```json - "__tableServiceUri": "tableServiceUri" - ``` - - * Replace `CONNECTION_NAME_PREFIX` with the appropriate prefix. - -1. **Update Function Code**: - * Supply the `ConnectionNamePrefix` to `ChatStorageConnectionSetting` in the function code. This will replace the default value of `AzureWebJobsStorage`. - -For additional details on using identity-based connections, refer to the [Azure Functions reference documentation](https://learn.microsoft.com/azure/azure-functions/functions-reference?#common-properties-for-identity-based-connections). - ## Defining skills You can define a skill by creating a function that uses the `AssistantSkillTrigger` binding. The following example shows a skill that adds a todo item to a database: @@ -205,18 +184,26 @@ Additionally, if you want to run the sample with Cosmos DB, then you must also d * Install the [Azure Cosmos DB Emulator](https://docs.microsoft.com/azure/cosmos-db/local-emulator), or get a connection string to a real Azure Cosmos DB resource. * Update the `CosmosDbConnectionString` setting in the `local.settings.json` file and configure it with the connection string to your Cosmos DB resource (local or Azure). -Also note that the storage of chat history is done via table storage. You may configure the `host.json` file within the project to be as follows: +### Chat Storage Configuration -```json -"extensions": { - "openai": { - "storageConnectionName": "AzureWebJobsStorage", - "collectionName": "SampleChatState" - } -} -``` +If you are using a different table storage than `AzureWebJobsStorage` for chat storage, follow these steps: + +1. **Managed Identity - Assign Permissions**: + * Assign the user or function app's managed identity the role of `Storage Table Data Contributor`. + +1. **Configure Table Service URI**: + * Set the `tableServiceUri` in the configuration as follows: -`StorageConnectionName` is the name of connection string of a storage account and `CollectionName` is the name of the table that would hold the chat state and messages. + ```json + "__tableServiceUri": "tableServiceUri" + ``` + + * Replace `CONNECTION_NAME_PREFIX` with the appropriate prefix. + +1. **Update Function Code**: + * Supply the `ConnectionNamePrefix` to `ChatStorageConnectionSetting` in the function code. This will replace the default value of `AzureWebJobsStorage`. + +For additional details on using identity-based connections, refer to the [Azure Functions reference documentation](https://learn.microsoft.com/azure/azure-functions/functions-reference?#common-properties-for-identity-based-connections). ## Running the sample diff --git a/samples/assistant/csharp-ooproc/AssistantApis.cs b/samples/assistant/csharp-ooproc/AssistantApis.cs index 99b3c500..c1f4c16d 100644 --- a/samples/assistant/csharp-ooproc/AssistantApis.cs +++ b/samples/assistant/csharp-ooproc/AssistantApis.cs @@ -13,8 +13,8 @@ namespace AssistantSample; /// static class AssistantApis { - const string CHAT_STORAGE_CONNECTION_SETTING = "AzureWebJobsStorage"; - const string COLLECTION_NAME = "SampleChatState"; + const string DefaultChatStorageConnectionSetting = "AzureWebJobsStorage"; + const string DefaultCollectionName = "ChatState"; /// /// HTTP PUT function that creates a new assistant chat bot with the specified ID. @@ -40,8 +40,8 @@ public static async Task CreateAssistant( HttpResponse = new ObjectResult(new { assistantId }) { StatusCode = 202 }, ChatBotCreateRequest = new AssistantCreateRequest(assistantId, instructions) { - ChatStorageConnectionSetting = CHAT_STORAGE_CONNECTION_SETTING, - CollectionName = COLLECTION_NAME, + ChatStorageConnectionSetting = DefaultChatStorageConnectionSetting, + CollectionName = DefaultCollectionName, }, }; } @@ -62,7 +62,7 @@ public class CreateChatBotOutput public static async Task PostUserQuery( [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "assistants/{assistantId}")] HttpRequestData req, string assistantId, - [AssistantPostInput("{assistantId}", "{Query.message}", Model = "%CHAT_MODEL_DEPLOYMENT_NAME%", ChatStorageConnectionSetting = CHAT_STORAGE_CONNECTION_SETTING, CollectionName = COLLECTION_NAME)] AssistantState state) + [AssistantPostInput("{assistantId}", "{Query.message}", Model = "%CHAT_MODEL_DEPLOYMENT_NAME%", ChatStorageConnectionSetting = DefaultChatStorageConnectionSetting, CollectionName = CollectionName)] AssistantState state) { return new OkObjectResult(state.RecentMessages.LastOrDefault()?.Content ?? "No response returned."); } @@ -74,7 +74,7 @@ public static async Task PostUserQuery( public static async Task GetChatState( [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "assistants/{assistantId}")] HttpRequestData req, string assistantId, - [AssistantQueryInput("{assistantId}", TimestampUtc = "{Query.timestampUTC}", ChatStorageConnectionSetting = CHAT_STORAGE_CONNECTION_SETTING, CollectionName = COLLECTION_NAME)] AssistantState state) + [AssistantQueryInput("{assistantId}", TimestampUtc = "{Query.timestampUTC}", ChatStorageConnectionSetting = DefaultChatStorageConnectionSetting, CollectionName = DefaultCollectionName)] AssistantState state) { return new OkObjectResult(state); } diff --git a/samples/assistant/javascript/src/functions/assistantApis.js b/samples/assistant/javascript/src/functions/assistantApis.js index b64289e3..a8eaced4 100644 --- a/samples/assistant/javascript/src/functions/assistantApis.js +++ b/samples/assistant/javascript/src/functions/assistantApis.js @@ -4,7 +4,7 @@ const { app, input, output } = require("@azure/functions"); const CHAT_STORAGE_CONNECTION_SETTING = "AzureWebJobsStorage"; -const COLLECTION_NAME = "SampleChatState"; +const COLLECTION_NAME = "ChatState"; const chatBotCreateOutput = output.generic({ type: 'assistantCreate' diff --git a/samples/assistant/powershell/CreateAssistant/run.ps1 b/samples/assistant/powershell/CreateAssistant/run.ps1 index 08fdb14d..c18d0d0c 100644 --- a/samples/assistant/powershell/CreateAssistant/run.ps1 +++ b/samples/assistant/powershell/CreateAssistant/run.ps1 @@ -11,7 +11,7 @@ $create_request = @{ "id" = $assistantId "instructions" = $instructions "chatStorageConnectionSetting" = "AzureWebJobsStorage" - "collectionName" = "SampleChatState" + "collectionName" = "ChatState" } Push-OutputBinding -Name Requests -Value (ConvertTo-Json $create_request) diff --git a/samples/assistant/powershell/GetChatState/function.json b/samples/assistant/powershell/GetChatState/function.json index dc5a00a4..0aae0b70 100644 --- a/samples/assistant/powershell/GetChatState/function.json +++ b/samples/assistant/powershell/GetChatState/function.json @@ -23,7 +23,7 @@ "id": "{assistantId}", "timestampUtc": "{Query.timestampUTC}", "chatStorageConnectionSetting": "AzureWebJobsStorage", - "collectionName": "SampleChatState" + "collectionName": "ChatState" } ] } \ No newline at end of file diff --git a/samples/assistant/powershell/PostUserQuery/function.json b/samples/assistant/powershell/PostUserQuery/function.json index 1be27abf..024cd24f 100644 --- a/samples/assistant/powershell/PostUserQuery/function.json +++ b/samples/assistant/powershell/PostUserQuery/function.json @@ -24,7 +24,7 @@ "userMessage": "{Query.message}", "model": "%CHAT_MODEL_DEPLOYMENT_NAME%", "chatStorageConnectionSetting": "AzureWebJobsStorage", - "collectionName": "SampleChatState" + "collectionName": "ChatState" } ] } \ No newline at end of file diff --git a/samples/assistant/python/assistant_apis.py b/samples/assistant/python/assistant_apis.py index 86b28335..9202c690 100644 --- a/samples/assistant/python/assistant_apis.py +++ b/samples/assistant/python/assistant_apis.py @@ -17,7 +17,7 @@ def create_assistant(req: func.HttpRequest, requests: func.Out[str]) -> func.Htt "id": assistantId, "instructions": instructions, "chatStorageConnectionSection": "AzureWebJobsStorage", - "collectionName": "SampleChatState" + "collectionName": "ChatState" } requests.set(json.dumps(create_request)) response_json = {"assistantId": assistantId} diff --git a/samples/assistant/typescript/src/functions/assistantApis.ts b/samples/assistant/typescript/src/functions/assistantApis.ts index 81f27d4e..35bdd59c 100644 --- a/samples/assistant/typescript/src/functions/assistantApis.ts +++ b/samples/assistant/typescript/src/functions/assistantApis.ts @@ -4,7 +4,7 @@ import { HttpRequest, InvocationContext, app, input, output } from "@azure/functions" const CHAT_STORAGE_CONNECTION_SETTING = "AzureWebJobsStorage"; -const COLLECTION_NAME = "SampleChatState"; +const COLLECTION_NAME = "ChatState"; const chatBotCreateOutput = output.generic({ type: 'assistantCreate' diff --git a/samples/chat/csharp-ooproc/ChatBot.cs b/samples/chat/csharp-ooproc/ChatBot.cs index cd0db61d..88dd3975 100644 --- a/samples/chat/csharp-ooproc/ChatBot.cs +++ b/samples/chat/csharp-ooproc/ChatBot.cs @@ -12,8 +12,8 @@ namespace ChatBot; /// public static class ChatBot { - const string CHAT_STORAGE_CONNECTION_SETTING = "AzureWebJobsStorage"; - const string COLLECTION_NAME = "SampleChatState"; + const string DefaultChatStorageConnectionSetting = "AzureWebJobsStorage"; + const string DefaultCollectionName = "ChatState"; public class CreateRequest { @@ -47,8 +47,8 @@ public static async Task CreateChatBot( HttpResponse = new ObjectResult(responseJson) { StatusCode = 201 }, ChatBotCreateRequest = new AssistantCreateRequest(chatId, createRequestBody?.Instructions) { - ChatStorageConnectionSetting = CHAT_STORAGE_CONNECTION_SETTING, - CollectionName = COLLECTION_NAME + ChatStorageConnectionSetting = DefaultChatStorageConnectionSetting, + CollectionName = DefaultChatStorageConnectionSetting }, }; } @@ -66,7 +66,7 @@ public class CreateChatBotOutput public static async Task PostUserResponse( [HttpTrigger(AuthorizationLevel.Function, "post", Route = "chats/{chatId}")] HttpRequestData req, string chatId, - [AssistantPostInput("{chatId}", "{Query.message}", Model = "%CHAT_MODEL_DEPLOYMENT_NAME%", ChatStorageConnectionSetting = CHAT_STORAGE_CONNECTION_SETTING, CollectionName = COLLECTION_NAME)] AssistantState state) + [AssistantPostInput("{chatId}", "{Query.message}", Model = "%CHAT_MODEL_DEPLOYMENT_NAME%", ChatStorageConnectionSetting = DefaultChatStorageConnectionSetting, CollectionName = DefaultCollectionName)] AssistantState state) { return new OkObjectResult(state.RecentMessages.LastOrDefault()?.Content ?? "No response returned."); } @@ -75,7 +75,7 @@ public static async Task PostUserResponse( public static async Task GetChatState( [HttpTrigger(AuthorizationLevel.Function, "get", Route = "chats/{chatId}")] HttpRequestData req, string chatId, - [AssistantQueryInput("{chatId}", TimestampUtc = "{Query.timestampUTC}", ChatStorageConnectionSetting = CHAT_STORAGE_CONNECTION_SETTING, CollectionName = COLLECTION_NAME)] AssistantState state, + [AssistantQueryInput("{chatId}", TimestampUtc = "{Query.timestampUTC}", ChatStorageConnectionSetting = DefaultChatStorageConnectionSetting, CollectionName = DefaultCollectionName)] AssistantState state, FunctionContext context) { return new OkObjectResult(state); diff --git a/samples/chat/javascript/src/app.js b/samples/chat/javascript/src/app.js index ae799f71..f0a86ddd 100644 --- a/samples/chat/javascript/src/app.js +++ b/samples/chat/javascript/src/app.js @@ -4,7 +4,7 @@ const { app, input, output } = require("@azure/functions"); const CHAT_STORAGE_CONNECTION_SETTING = "AzureWebJobsStorage"; -const COLLECTION_NAME = "SampleChatState"; +const COLLECTION_NAME = "ChatState"; const chatBotCreateOutput = output.generic({ type: 'assistantCreate' diff --git a/samples/chat/powershell/CreateChatBot/run.ps1 b/samples/chat/powershell/CreateChatBot/run.ps1 index 6406004d..eacd061c 100644 --- a/samples/chat/powershell/CreateChatBot/run.ps1 +++ b/samples/chat/powershell/CreateChatBot/run.ps1 @@ -10,7 +10,7 @@ $createRequest = @{ id = $chatID instructions = $inputJson.Instructions chatStorageConnectionSetting = "AzureWebJobsStorage" - collectionName = "SampleChatState" + collectionName = "ChatState" } Push-OutputBinding -Name ChatBotCreate -Value $createRequest diff --git a/samples/chat/powershell/GetChatState/function.json b/samples/chat/powershell/GetChatState/function.json index 52da792d..d919a1b9 100644 --- a/samples/chat/powershell/GetChatState/function.json +++ b/samples/chat/powershell/GetChatState/function.json @@ -22,7 +22,7 @@ "id": "{chatId}", "timeStampUtc": "{Query.timestampUTC}", "chatStorageConnectionSetting": "AzureWebJobsStorage", - "collectionName": "SampleChatState" + "collectionName": "ChatState" } ] } \ No newline at end of file diff --git a/samples/chat/powershell/PostUserResponse/function.json b/samples/chat/powershell/PostUserResponse/function.json index 3a9c871c..16142e20 100644 --- a/samples/chat/powershell/PostUserResponse/function.json +++ b/samples/chat/powershell/PostUserResponse/function.json @@ -23,7 +23,7 @@ "model": "%CHAT_MODEL_DEPLOYMENT_NAME%", "userMessage": "{Query.message}", "chatStorageConnectionSetting": "AzureWebJobsStorage", - "collectionName": "SampleChatState" + "collectionName": "ChatState" } ] } \ No newline at end of file diff --git a/samples/chat/python/function_app.py b/samples/chat/python/function_app.py index a366e1b6..eb23ab2a 100644 --- a/samples/chat/python/function_app.py +++ b/samples/chat/python/function_app.py @@ -17,7 +17,7 @@ def create_chat_bot(req: func.HttpRequest, requests: func.Out[str]) -> func.Http "id": chatId, "instructions": input_json.get("instructions"), "chatStorageConnectionSection": "AzureWebJobsStorage", - "collectionName": "SampleChatState" + "collectionName": "ChatState" } requests.set(json.dumps(create_request)) response_json = {"chatId": chatId} diff --git a/samples/chat/typescript/src/functions/app.ts b/samples/chat/typescript/src/functions/app.ts index 62c495aa..84a12075 100644 --- a/samples/chat/typescript/src/functions/app.ts +++ b/samples/chat/typescript/src/functions/app.ts @@ -4,7 +4,7 @@ import { HttpRequest, InvocationContext, app, input, output } from "@azure/functions"; const CHAT_STORAGE_CONNECTION_SETTING = "AzureWebJobsStorage"; -const COLLECTION_NAME = "SampleChatState"; +const COLLECTION_NAME = "ChatState"; const chatBotCreateOutput = output.generic({ type: 'assistantCreate' diff --git a/src/Functions.Worker.Extensions.OpenAI/Assistants/AssistantCreateRequest.cs b/src/Functions.Worker.Extensions.OpenAI/Assistants/AssistantCreateRequest.cs index fdb5991a..243d8bfd 100644 --- a/src/Functions.Worker.Extensions.OpenAI/Assistants/AssistantCreateRequest.cs +++ b/src/Functions.Worker.Extensions.OpenAI/Assistants/AssistantCreateRequest.cs @@ -47,5 +47,5 @@ public AssistantCreateRequest(string id, string? instructions) /// /// Table collection name for chat storage. /// - public string CollectionName { get; set; } = "SampleChatState"; + public string CollectionName { get; set; } = "ChatState"; } diff --git a/src/Functions.Worker.Extensions.OpenAI/Assistants/AssistantPostInputAttribute.cs b/src/Functions.Worker.Extensions.OpenAI/Assistants/AssistantPostInputAttribute.cs index 8104c8d6..fda594e7 100644 --- a/src/Functions.Worker.Extensions.OpenAI/Assistants/AssistantPostInputAttribute.cs +++ b/src/Functions.Worker.Extensions.OpenAI/Assistants/AssistantPostInputAttribute.cs @@ -42,5 +42,5 @@ public AssistantPostInputAttribute(string id, string UserMessage) /// /// Table collection name for chat storage. /// - public string CollectionName { get; set; } = "SampleChatState"; + public string CollectionName { get; set; } = "ChatState"; } diff --git a/src/Functions.Worker.Extensions.OpenAI/Assistants/AssistantQueryInputAttribute.cs b/src/Functions.Worker.Extensions.OpenAI/Assistants/AssistantQueryInputAttribute.cs index b3711431..029baf14 100644 --- a/src/Functions.Worker.Extensions.OpenAI/Assistants/AssistantQueryInputAttribute.cs +++ b/src/Functions.Worker.Extensions.OpenAI/Assistants/AssistantQueryInputAttribute.cs @@ -34,5 +34,5 @@ public AssistantQueryInputAttribute(string id) /// /// Table collection name for chat storage. /// - public string CollectionName { get; set; } = "SampleChatState"; + public string CollectionName { get; set; } = "ChatState"; } diff --git a/src/WebJobs.Extensions.OpenAI/Assistants/AssistantCreateAttribute.cs b/src/WebJobs.Extensions.OpenAI/Assistants/AssistantCreateAttribute.cs index fdcbffb1..2a9dcc5e 100644 --- a/src/WebJobs.Extensions.OpenAI/Assistants/AssistantCreateAttribute.cs +++ b/src/WebJobs.Extensions.OpenAI/Assistants/AssistantCreateAttribute.cs @@ -68,5 +68,5 @@ public AssistantCreateRequest(string id, string? instructions) /// /// Table collection name for chat storage. /// - public string CollectionName { get; set; } = "SampleChatState"; + public string CollectionName { get; set; } = "ChatState"; } \ No newline at end of file diff --git a/src/WebJobs.Extensions.OpenAI/Assistants/AssistantPostAttribute.cs b/src/WebJobs.Extensions.OpenAI/Assistants/AssistantPostAttribute.cs index db6d3cd6..e024e7be 100644 --- a/src/WebJobs.Extensions.OpenAI/Assistants/AssistantPostAttribute.cs +++ b/src/WebJobs.Extensions.OpenAI/Assistants/AssistantPostAttribute.cs @@ -44,5 +44,5 @@ public AssistantPostAttribute(string id, string userMessage) /// /// Table collection name for chat storage. /// - public string CollectionName { get; set; } = "SampleChatState"; + public string CollectionName { get; set; } = "ChatState"; } \ No newline at end of file diff --git a/src/WebJobs.Extensions.OpenAI/Assistants/AssistantQueryAttribute.cs b/src/WebJobs.Extensions.OpenAI/Assistants/AssistantQueryAttribute.cs index ede24c9b..466eb97c 100644 --- a/src/WebJobs.Extensions.OpenAI/Assistants/AssistantQueryAttribute.cs +++ b/src/WebJobs.Extensions.OpenAI/Assistants/AssistantQueryAttribute.cs @@ -35,5 +35,5 @@ public AssistantQueryAttribute(string id) /// /// Table collection name for chat storage. /// - public string CollectionName { get; set; } = "SampleChatState"; + public string CollectionName { get; set; } = "ChatState"; } \ No newline at end of file diff --git a/src/WebJobs.Extensions.OpenAI/Assistants/AssistantService.cs b/src/WebJobs.Extensions.OpenAI/Assistants/AssistantService.cs index 503b2078..ba50dff4 100644 --- a/src/WebJobs.Extensions.OpenAI/Assistants/AssistantService.cs +++ b/src/WebJobs.Extensions.OpenAI/Assistants/AssistantService.cs @@ -63,15 +63,7 @@ public async Task CreateAssistantAsync(AssistantCreateRequest request, Cancellat request.Id, request.Instructions ?? "(none)"); - if (this.tableClient is null) - { - this.CreateTableClient(request.ChatStorageConnectionSetting, request.CollectionName); - } - - if (this.tableClient is null) - { - throw new InvalidOperationException($"{nameof(this.tableClient)} is null."); - } + this.tableClient = this.GetOrCreateTableClient(request.ChatStorageConnectionSetting, request.CollectionName); // Create the table if it doesn't exist await this.tableClient.CreateIfNotExistsAsync(); @@ -154,12 +146,9 @@ public async Task GetStateAsync(AssistantQueryAttribute assistan id, afterUtc.ToString("o")); - if (this.tableClient is null) - { - this.CreateTableClient(assistantQuery.ChatStorageConnectionSetting, assistantQuery.CollectionName); - } + this.tableClient = this.GetOrCreateTableClient(assistantQuery.ChatStorageConnectionSetting, assistantQuery.CollectionName); - InternalChatState? chatState = await this.LoadChatStateAsync(id, cancellationToken); + InternalChatState? chatState = await this.LoadChatStateAsync(id, this.tableClient, cancellationToken); if (chatState is null) { this.logger.LogWarning("No assistant exists with ID = '{Id}'", id); @@ -191,7 +180,7 @@ public async Task PostMessageAsync(AssistantPostAttribute attrib DateTime timeFilter = DateTime.UtcNow; if (string.IsNullOrEmpty(attribute.Id)) { - throw new ArgumentException("The assistant Id must be specified.", nameof(attribute)); + throw new ArgumentException("The assistant ID must be specified.", nameof(attribute)); } if (string.IsNullOrEmpty(attribute.UserMessage)) @@ -199,19 +188,11 @@ public async Task PostMessageAsync(AssistantPostAttribute attrib throw new ArgumentException("The assistant must have a user message", nameof(attribute)); } - if (this.tableClient is null) - { - this.CreateTableClient(attribute.ChatStorageConnectionSetting, attribute.CollectionName); - } - - if (this.tableClient is null) - { - throw new InvalidOperationException($"{nameof(this.tableClient)} is null."); - } - this.logger.LogInformation("Posting message to assistant entity '{Id}'", attribute.Id); - InternalChatState? chatState = await this.LoadChatStateAsync(attribute.Id, cancellationToken); + this.tableClient = this.GetOrCreateTableClient(attribute.ChatStorageConnectionSetting, attribute.CollectionName); + + InternalChatState? chatState = await this.LoadChatStateAsync(attribute.Id, this.tableClient, cancellationToken); // Check if assistant has been deactivated if (chatState is null || !chatState.Metadata.Exists) @@ -405,15 +386,10 @@ public async Task PostMessageAsync(AssistantPostAttribute attrib return state; } - async Task LoadChatStateAsync(string id, CancellationToken cancellationToken) + async Task LoadChatStateAsync(string id, TableClient tableClient, CancellationToken cancellationToken) { - if (this.tableClient is null) - { - throw new InvalidOperationException($"{nameof(this.tableClient)} is null."); - } - // Check to see if any entity exists with partition id - AsyncPageable itemsWithPartitionKey = this.tableClient.QueryAsync( + AsyncPageable itemsWithPartitionKey = tableClient.QueryAsync( filter: $"PartitionKey eq '{id}'", cancellationToken: cancellationToken); @@ -471,8 +447,13 @@ static IEnumerable ToOpenAIChatRequestMessages(IEnumerable(connectionStringName); this.logger.LogInformation("using connection string for table service client"); @@ -510,6 +490,6 @@ void CreateTableClient(string? chatStorageConnectionSetting, string? collectionN } this.logger.LogInformation("Using {CollectionName} for table storage collection name", collectionName); - this.tableClient = this.tableServiceClient.GetTableClient(collectionName); + return this.tableServiceClient.GetTableClient(collectionName); } } \ No newline at end of file diff --git a/src/WebJobs.Extensions.OpenAI/Embeddings/EmbeddingsStoreConverter.cs b/src/WebJobs.Extensions.OpenAI/Embeddings/EmbeddingsStoreConverter.cs index 22fcafb7..f51285d5 100644 --- a/src/WebJobs.Extensions.OpenAI/Embeddings/EmbeddingsStoreConverter.cs +++ b/src/WebJobs.Extensions.OpenAI/Embeddings/EmbeddingsStoreConverter.cs @@ -48,8 +48,12 @@ public Task> ConvertAsync(EmbeddingsStoreAtt // Called by the host when processing binding requests from out-of-process workers. internal SearchableDocument ToSearchableDocument(string? json) { + if (json is null) + { + throw new ArgumentNullException(nameof(json)); + } this.logger.LogDebug("Creating searchable document from JSON string: {Text}", json); - SearchableDocument document = JsonSerializer.Deserialize(json ?? throw new ArgumentNullException(nameof(json)), options) + SearchableDocument document = JsonSerializer.Deserialize(json, options) ?? throw new ArgumentException("Invalid search request."); return document; } From e365273580ff06818601c99928a4237e3568bd05 Mon Sep 17 00:00:00 2001 From: manvkaur <67894494+manvkaur@users.noreply.github.com> Date: Fri, 27 Sep 2024 15:46:40 +0100 Subject: [PATCH 09/10] resolve Aishwarya's comments --- samples/assistant/csharp-ooproc/AssistantApis.cs | 2 +- samples/chat/csharp-ooproc/ChatBot.cs | 2 +- .../Assistants/AssistantPostAttribute.cs | 1 + .../Assistants/AssistantQueryAttribute.cs | 1 + 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/samples/assistant/csharp-ooproc/AssistantApis.cs b/samples/assistant/csharp-ooproc/AssistantApis.cs index c1f4c16d..a55df046 100644 --- a/samples/assistant/csharp-ooproc/AssistantApis.cs +++ b/samples/assistant/csharp-ooproc/AssistantApis.cs @@ -62,7 +62,7 @@ public class CreateChatBotOutput public static async Task PostUserQuery( [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "assistants/{assistantId}")] HttpRequestData req, string assistantId, - [AssistantPostInput("{assistantId}", "{Query.message}", Model = "%CHAT_MODEL_DEPLOYMENT_NAME%", ChatStorageConnectionSetting = DefaultChatStorageConnectionSetting, CollectionName = CollectionName)] AssistantState state) + [AssistantPostInput("{assistantId}", "{Query.message}", Model = "%CHAT_MODEL_DEPLOYMENT_NAME%", ChatStorageConnectionSetting = DefaultChatStorageConnectionSetting, CollectionName = DefaultCollectionName)] AssistantState state) { return new OkObjectResult(state.RecentMessages.LastOrDefault()?.Content ?? "No response returned."); } diff --git a/samples/chat/csharp-ooproc/ChatBot.cs b/samples/chat/csharp-ooproc/ChatBot.cs index 88dd3975..2a94b4b4 100644 --- a/samples/chat/csharp-ooproc/ChatBot.cs +++ b/samples/chat/csharp-ooproc/ChatBot.cs @@ -48,7 +48,7 @@ public static async Task CreateChatBot( ChatBotCreateRequest = new AssistantCreateRequest(chatId, createRequestBody?.Instructions) { ChatStorageConnectionSetting = DefaultChatStorageConnectionSetting, - CollectionName = DefaultChatStorageConnectionSetting + CollectionName = DefaultCollectionName }, }; } diff --git a/src/WebJobs.Extensions.OpenAI/Assistants/AssistantPostAttribute.cs b/src/WebJobs.Extensions.OpenAI/Assistants/AssistantPostAttribute.cs index e024e7be..56ed35fa 100644 --- a/src/WebJobs.Extensions.OpenAI/Assistants/AssistantPostAttribute.cs +++ b/src/WebJobs.Extensions.OpenAI/Assistants/AssistantPostAttribute.cs @@ -44,5 +44,6 @@ public AssistantPostAttribute(string id, string userMessage) /// /// Table collection name for chat storage. /// + [AutoResolve] public string CollectionName { get; set; } = "ChatState"; } \ No newline at end of file diff --git a/src/WebJobs.Extensions.OpenAI/Assistants/AssistantQueryAttribute.cs b/src/WebJobs.Extensions.OpenAI/Assistants/AssistantQueryAttribute.cs index 466eb97c..9d3a8e31 100644 --- a/src/WebJobs.Extensions.OpenAI/Assistants/AssistantQueryAttribute.cs +++ b/src/WebJobs.Extensions.OpenAI/Assistants/AssistantQueryAttribute.cs @@ -35,5 +35,6 @@ public AssistantQueryAttribute(string id) /// /// Table collection name for chat storage. /// + [AutoResolve] public string CollectionName { get; set; } = "ChatState"; } \ No newline at end of file From 346568d0821fd477ab1298d8493f47f65891dca0 Mon Sep 17 00:00:00 2001 From: manvkaur <67894494+manvkaur@users.noreply.github.com> Date: Mon, 30 Sep 2024 12:41:12 +0100 Subject: [PATCH 10/10] resolve Chris's review comments --- .../Assistants/AssistantService.cs | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/WebJobs.Extensions.OpenAI/Assistants/AssistantService.cs b/src/WebJobs.Extensions.OpenAI/Assistants/AssistantService.cs index ba50dff4..e31e8a3b 100644 --- a/src/WebJobs.Extensions.OpenAI/Assistants/AssistantService.cs +++ b/src/WebJobs.Extensions.OpenAI/Assistants/AssistantService.cs @@ -63,13 +63,13 @@ public async Task CreateAssistantAsync(AssistantCreateRequest request, Cancellat request.Id, request.Instructions ?? "(none)"); - this.tableClient = this.GetOrCreateTableClient(request.ChatStorageConnectionSetting, request.CollectionName); + TableClient tableClient = this.GetOrCreateTableClient(request.ChatStorageConnectionSetting, request.CollectionName); // Create the table if it doesn't exist - await this.tableClient.CreateIfNotExistsAsync(); + await tableClient.CreateIfNotExistsAsync(); // Check to see if the assistant has already been initialized - AsyncPageable queryResultsFilter = this.tableClient.QueryAsync( + AsyncPageable queryResultsFilter = tableClient.QueryAsync( filter: $"PartitionKey eq '{request.Id}'", cancellationToken: cancellationToken); @@ -85,7 +85,7 @@ async Task DeleteBatch() "Deleting {Count} record(s) for assistant '{Id}'.", deleteBatch.Count, request.Id); - await this.tableClient.SubmitTransactionAsync(deleteBatch); + await tableClient.SubmitTransactionAsync(deleteBatch); deleteBatch.Clear(); } } @@ -128,7 +128,7 @@ async Task DeleteBatch() batch.Add(new TableTransactionAction(TableTransactionActionType.Add, assistantStateEntity)); // Add the batch of table transaction actions to the table - await this.tableClient.SubmitTransactionAsync(batch); + await tableClient.SubmitTransactionAsync(batch); } public async Task GetStateAsync(AssistantQueryAttribute assistantQuery, CancellationToken cancellationToken) @@ -146,9 +146,9 @@ public async Task GetStateAsync(AssistantQueryAttribute assistan id, afterUtc.ToString("o")); - this.tableClient = this.GetOrCreateTableClient(assistantQuery.ChatStorageConnectionSetting, assistantQuery.CollectionName); + TableClient tableClient = this.GetOrCreateTableClient(assistantQuery.ChatStorageConnectionSetting, assistantQuery.CollectionName); - InternalChatState? chatState = await this.LoadChatStateAsync(id, this.tableClient, cancellationToken); + InternalChatState? chatState = await this.LoadChatStateAsync(id, tableClient, cancellationToken); if (chatState is null) { this.logger.LogWarning("No assistant exists with ID = '{Id}'", id); @@ -190,9 +190,9 @@ public async Task PostMessageAsync(AssistantPostAttribute attrib this.logger.LogInformation("Posting message to assistant entity '{Id}'", attribute.Id); - this.tableClient = this.GetOrCreateTableClient(attribute.ChatStorageConnectionSetting, attribute.CollectionName); + TableClient tableClient = this.GetOrCreateTableClient(attribute.ChatStorageConnectionSetting, attribute.CollectionName); - InternalChatState? chatState = await this.LoadChatStateAsync(attribute.Id, this.tableClient, cancellationToken); + InternalChatState? chatState = await this.LoadChatStateAsync(attribute.Id, tableClient, cancellationToken); // Check if assistant has been deactivated if (chatState is null || !chatState.Metadata.Exists) @@ -361,7 +361,7 @@ public async Task PostMessageAsync(AssistantPostAttribute attrib batch.Add(new TableTransactionAction(TableTransactionActionType.UpdateMerge, chatState.Metadata)); // Add the batch of table transaction actions to the table - await this.tableClient.SubmitTransactionAsync(batch, cancellationToken); + await tableClient.SubmitTransactionAsync(batch, cancellationToken); // return the latest assistant message in the chat state List filteredChatMessages = chatState.Messages @@ -490,6 +490,8 @@ TableClient GetOrCreateTableClient(string? chatStorageConnectionSetting, string? } this.logger.LogInformation("Using {CollectionName} for table storage collection name", collectionName); - return this.tableServiceClient.GetTableClient(collectionName); + this.tableClient = this.tableServiceClient.GetTableClient(collectionName); + + return this.tableClient; } } \ No newline at end of file