diff --git a/CHANGELOG.md b/CHANGELOG.md
index 41702adf..7c687d73 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,6 +15,7 @@ Starting v0.1.0 for Microsoft.Azure.WebJobs.Extensions.OpenAI.AzureAISearch, it
- Added dotnet-isolated support for Embeddings
- Added support for multiple search providers.
+- Added dotnet-isolated support for Kusto and Azure AI Search RAG scenarios.
### Breaking changes
diff --git a/OpenAI-Extension.sln b/OpenAI-Extension.sln
index c96c923d..ebd879b9 100644
--- a/OpenAI-Extension.sln
+++ b/OpenAI-Extension.sln
@@ -47,12 +47,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "assistant", "assistant", "{
samples\assistant\demo.http = samples\assistant\demo.http
EndProjectSection
EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "rag", "rag", "{4CD51D81-D334-4FBC-A82B-8E2408913B7D}"
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "rag-kusto", "rag-kusto", "{4CD51D81-D334-4FBC-A82B-8E2408913B7D}"
ProjectSection(SolutionItems) = preProject
- samples\rag\csharp-inproc\demo.http = samples\rag\csharp-inproc\demo.http
+ samples\rag-kusto\csharp-ooproc\demo.http = samples\rag-kusto\csharp-ooproc\demo.http
EndProjectSection
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SemanticSearchEmbeddings", "samples\rag\csharp-inproc\SemanticSearchEmbeddings.csproj", "{BD2064F5-6F56-45D4-A2E9-5C276FDAF4B1}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SemanticSearchEmbeddings", "samples\rag-kusto\csharp-ooproc\SemanticSearchEmbeddings.csproj", "{3E9B9A7F-A98D-4CDD-9528-6D3077CAA4DA}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AssistantSample", "samples\assistant\csharp-ooproc\AssistantSample\AssistantSample.csproj", "{8573FE76-9920-4A95-9D29-911FD6B0AB25}"
EndProject
@@ -67,7 +67,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "rag-aisearch", "rag-aisearc
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Embeddings", "samples\embeddings\csharp-ooproc\Embeddings\Embeddings.csproj", "{C9CCD98A-B4E5-4E8E-9163-F86AF2DBB645}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SemanticAISearchEmbeddings", "samples\rag-aisearch\csharp-inproc\SemanticAISearchEmbeddings.csproj", "{21EE2832-FDEB-4D5A-BD72-61BDDF86DC2B}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SemanticAISearchEmbeddings", "samples\rag-aisearch\csharp-ooproc\SemanticAISearchEmbeddings.csproj", "{21EE2832-FDEB-4D5A-BD72-61BDDF86DC2B}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Functions.Worker.Extensions.OpenAI.Kusto", "src\Functions.Worker.Extensions.OpenAI.Kusto\Functions.Worker.Extensions.OpenAI.Kusto.csproj", "{F413D075-38DA-4357-AB60-E6E157018827}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Functions.Worker.Extensions.OpenAI.AzureAISearch", "src\Functions.Worker.Extensions.OpenAI.AzureAISearch\Functions.Worker.Extensions.OpenAI.AzureAISearch.csproj", "{CAB7034D-FBA9-4A7D-B50B-E9029DFD0DEF}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -95,10 +99,10 @@ Global
{6701A54D-E00F-46E8-9344-B1547805459E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6701A54D-E00F-46E8-9344-B1547805459E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6701A54D-E00F-46E8-9344-B1547805459E}.Release|Any CPU.Build.0 = Release|Any CPU
- {BD2064F5-6F56-45D4-A2E9-5C276FDAF4B1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {BD2064F5-6F56-45D4-A2E9-5C276FDAF4B1}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {BD2064F5-6F56-45D4-A2E9-5C276FDAF4B1}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {BD2064F5-6F56-45D4-A2E9-5C276FDAF4B1}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3E9B9A7F-A98D-4CDD-9528-6D3077CAA4DA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3E9B9A7F-A98D-4CDD-9528-6D3077CAA4DA}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3E9B9A7F-A98D-4CDD-9528-6D3077CAA4DA}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3E9B9A7F-A98D-4CDD-9528-6D3077CAA4DA}.Release|Any CPU.Build.0 = Release|Any CPU
{8573FE76-9920-4A95-9D29-911FD6B0AB25}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8573FE76-9920-4A95-9D29-911FD6B0AB25}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8573FE76-9920-4A95-9D29-911FD6B0AB25}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -119,6 +123,14 @@ Global
{21EE2832-FDEB-4D5A-BD72-61BDDF86DC2B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{21EE2832-FDEB-4D5A-BD72-61BDDF86DC2B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{21EE2832-FDEB-4D5A-BD72-61BDDF86DC2B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F413D075-38DA-4357-AB60-E6E157018827}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F413D075-38DA-4357-AB60-E6E157018827}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F413D075-38DA-4357-AB60-E6E157018827}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F413D075-38DA-4357-AB60-E6E157018827}.Release|Any CPU.Build.0 = Release|Any CPU
+ {CAB7034D-FBA9-4A7D-B50B-E9029DFD0DEF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {CAB7034D-FBA9-4A7D-B50B-E9029DFD0DEF}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {CAB7034D-FBA9-4A7D-B50B-E9029DFD0DEF}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {CAB7034D-FBA9-4A7D-B50B-E9029DFD0DEF}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -134,13 +146,15 @@ Global
{6701A54D-E00F-46E8-9344-B1547805459E} = {46888A9D-8058-499C-9F2F-07800FF686EB}
{C5578BE5-AAC7-4616-9A35-41CA5903882A} = {865C9FB5-1043-47BE-B650-25A2B42030A3}
{4CD51D81-D334-4FBC-A82B-8E2408913B7D} = {865C9FB5-1043-47BE-B650-25A2B42030A3}
- {BD2064F5-6F56-45D4-A2E9-5C276FDAF4B1} = {4CD51D81-D334-4FBC-A82B-8E2408913B7D}
+ {3E9B9A7F-A98D-4CDD-9528-6D3077CAA4DA} = {4CD51D81-D334-4FBC-A82B-8E2408913B7D}
{8573FE76-9920-4A95-9D29-911FD6B0AB25} = {C5578BE5-AAC7-4616-9A35-41CA5903882A}
{32196912-898F-428E-94EF-4C2333367977} = {7A80B95C-AD57-4057-AED0-48629B75874F}
{346BE57C-341F-4EF4-A81A-BAC9D2F5B938} = {7A80B95C-AD57-4057-AED0-48629B75874F}
{3E836929-3691-4DD1-AB8D-F3E0C3A42B5F} = {865C9FB5-1043-47BE-B650-25A2B42030A3}
{C9CCD98A-B4E5-4E8E-9163-F86AF2DBB645} = {4B55F637-B067-43B4-B6FC-65171654315C}
{21EE2832-FDEB-4D5A-BD72-61BDDF86DC2B} = {3E836929-3691-4DD1-AB8D-F3E0C3A42B5F}
+ {F413D075-38DA-4357-AB60-E6E157018827} = {7A80B95C-AD57-4057-AED0-48629B75874F}
+ {CAB7034D-FBA9-4A7D-B50B-E9029DFD0DEF} = {7A80B95C-AD57-4057-AED0-48629B75874F}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {05A7A679-3A53-45B5-AE93-4313655E127D}
diff --git a/README.md b/README.md
index 93e7bbff..307b5667 100644
--- a/README.md
+++ b/README.md
@@ -19,6 +19,8 @@ https://www.nuget.org/packages/Microsoft.Azure.Functions.Worker.Extensions.OpenA
)
[](https://www.nuget.org/packages/Microsoft.Azure.WebJobs.Extensions.OpenAI.Kusto)
[](https://www.nuget.org/packages/Microsoft.Azure.WebJobs.Extensions.OpenAI.AzureAISearch)
+[](https://www.nuget.org/packages/Microsoft.Azure.Functions.Worker.Extensions.OpenAI.Kusto)
+[](https://www.nuget.org/packages/Microsoft.Azure.Functions.Worker.Extensions.OpenAI.AzureAISearch)
## Requirements
@@ -269,36 +271,60 @@ The semantic search feature allows you to import documents into a vector databas
More may be added over time.
-#### [C# document storage example](./samples/rag-aisearch/csharp-inproc/FilePrompt.cs)
+#### [C# document storage example](./samples/rag-aisearch/csharp-ooproc/FilePrompt.cs)
This HTTP trigger function takes a path to a local file as input, generates embeddings for the file, and stores the result into an Azure AI Search Index.
```csharp
-public record EmbeddingsRequest(string FilePath);
+public class EmbeddingsRequest
+{
+ [JsonPropertyName("FilePath")]
+ public string? FilePath { get; set; }
+}
-[FunctionName("IngestFile")]
-public static async Task IngestFile(
- [HttpTrigger(AuthorizationLevel.Function, "post")] EmbeddingsRequest req,
- [Embeddings("{FilePath}", InputType.FilePath, Model = "%EMBEDDING_MODEL_DEPLOYMENT_NAME%")] EmbeddingsContext embeddings,
- [SemanticSearch("AISearchEndpoint", "openai-index", CredentialSettingName = "SearchAPIKey", ChatModel = "%CHAT_MODEL_DEPLOYMENT_NAME%", EmbeddingsModel = "%EMBEDDING_MODEL_DEPLOYMENT_NAME%")] IAsyncCollector output)
+[Function("IngestFile")]
+public static async Task IngestFile(
+ [HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequestData req,
+ [EmbeddingsInput("{FilePath}", InputType.FilePath, Model = "%EMBEDDING_MODEL_DEPLOYMENT_NAME%")] EmbeddingsContext embeddings)
{
- string title = Path.GetFileNameWithoutExtension(req.FilePath);
- await output.AddAsync(new SearchableDocument(title, embeddings));
- return new OkObjectResult(new { status = "success", title, chunks = embeddings.Count });
+ using StreamReader reader = new(req.Body);
+ string request = await reader.ReadToEndAsync();
+
+ EmbeddingsRequest? requestBody = JsonSerializer.Deserialize(request);
+
+ if (requestBody == null)
+ {
+ throw new ArgumentException("Invalid request body. Make sure that you pass in {\"filePath\": value } as the request body.");
+ }
+
+ string title = Path.GetFileNameWithoutExtension(requestBody.FilePath);
+
+ HttpResponseData response = req.CreateResponse(HttpStatusCode.OK);
+ await response.WriteAsJsonAsync(new { status = "success", title, chunks = embeddings.Count });
+
+ return new SemanticSearchOutputResponse
+ {
+ HttpResponse = response,
+ SearchableDocument = new SearchableDocument(title, embeddings)
+ };
}
```
-#### [C# document query example](./samples/rag-aisearch/csharp-inproc/FilePrompt.cs)
+#### [C# document query example](./samples/rag-aisearch/csharp-ooproc/FilePrompt.cs)
This HTTP trigger function takes a query prompt as input, pulls in semantically similar document chunks into a prompt, and then sends the combined prompt to OpenAI. The results are then made available to the function, which simply returns that chat response to the caller.
```csharp
-public record SemanticSearchRequest(string Prompt);
+public class SemanticSearchRequest
+{
+ [JsonPropertyName("Prompt")]
+ public string? Prompt { get; set; }
+}
-[FunctionName("PromptFile")]
+[Function("PromptFile")]
public static IActionResult PromptFile(
[HttpTrigger(AuthorizationLevel.Function, "post")] SemanticSearchRequest unused,
- [SemanticSearch("AISearchEndpoint", "openai-index", CredentialSettingName = "SearchAPIKey", Query = "{Prompt}", ChatModel = "%CHAT_MODEL_DEPLOYMENT_NAME%", EmbeddingsModel = "%EMBEDDING_MODEL_DEPLOYMENT_NAME%")] SemanticSearchContext result)
+ [SemanticSearchInput("AISearchEndpoint", "openai-index", CredentialSettingName = "SearchAPIKey", Query = "{Prompt}", ChatModel = "%CHAT_MODEL_DEPLOYMENT_NAME%", EmbeddingsModel = "%EMBEDDING_MODEL_DEPLOYMENT_NAME%")] SemanticSearchContext result)
{
return new ContentResult { Content = result.Response, ContentType = "text/plain" };
}
@@ -331,7 +357,7 @@ All samples in this project rely on default model selection, which assumes the m
```csharp
// "gpt-35-turbo" is the name of an Azure OpenAI deployment
-[FunctionName(nameof(WhoIs))]
+[Function(nameof(WhoIs))]
public static string WhoIs(
[HttpTrigger(AuthorizationLevel.Function, Route = "whois/{name}")] HttpRequest req,
[TextCompletion("Who is {name}?", Model = "gpt-35-turbo")] TextCompletionResponse response)
@@ -344,7 +370,7 @@ public static string WhoIs(
public record SemanticSearchRequest(string Prompt);
// "my-gpt-4" and "my-ada-2" are the names of Azure OpenAI deployments corresponding to gpt-4 and text-embedding-3-small models, respectively
-[FunctionName("PromptEmail")]
+[Function("PromptEmail")]
public static IActionResult PromptEmail(
[HttpTrigger(AuthorizationLevel.Function, "post")] SemanticSearchRequest unused,
[SemanticSearch("KustoConnectionString", "Documents", Query = "{Prompt}", ChatModel = "my-gpt-4", EmbeddingsModel = "my-ada-2")] SemanticSearchContext result)
diff --git a/build/build-pr-ci.yml b/build/build-pr-ci.yml
index 10ce5722..3dbbb304 100644
--- a/build/build-pr-ci.yml
+++ b/build/build-pr-ci.yml
@@ -60,17 +60,21 @@ steps:
displayName: Create new directory
- script: |
- dotnet build $(System.DefaultWorkingDirectory)/src/WebJobs.Extensions.OpenAI/WebJobs.Extensions.OpenAI.csproj --configuration $(config) -p:WebJobsVersion=$(fakeWebJobsPackageVersion) -p:Version=$(fakeWebJobsPackageVersion)
+ dotnet build $(System.DefaultWorkingDirectory)/src/WebJobs.Extensions.OpenAI/WebJobs.Extensions.OpenAI.csproj --configuration $(config) -p:WebJobsVersion=$(fakeWebJobsPackageVersion) -p:Version=$(fakeWebJobsPackageVersion) -p:AzureAISearchVersion=$(fakeWebJobsPackageVersion) -p:KustoVersion=$(KustoVersion)
+ dotnet build $(System.DefaultWorkingDirectory)/src/WebJobs.Extensions.OpenAI.Kusto/WebJobs.Extensions.OpenAI.Kusto.csproj --configuration $(config) -p:Version=$(fakeWebJobsPackageVersion) -p:AzureAISearchVersion=$(fakeWebJobsPackageVersion) -p:KustoVersion=$(KustoVersion)
+ dotnet build $(System.DefaultWorkingDirectory)/src/WebJobs.Extensions.OpenAI.AzureAISearch/WebJobs.Extensions.OpenAI.AzureAISearch.csproj --configuration $(config) -p:Version=$(fakeWebJobsPackageVersion) -p:AzureAISearchVersion=$(fakeWebJobsPackageVersion) -p:KustoVersion=$(KustoVersion)
sudo cp "$(System.DefaultWorkingDirectory)/src/WebJobs.Extensions.OpenAI/bin/Release/Microsoft.Azure.WebJobs.Extensions.OpenAI.$(fakeWebJobsPackageVersion).nupkg" ~/NuGetPackagesLocal
+ sudo cp "$(System.DefaultWorkingDirectory)/src/WebJobs.Extensions.OpenAI.AzureAISearch/bin/Release/Microsoft.Azure.WebJobs.Extensions.OpenAI.AzureAISearch.$(fakeWebJobsPackageVersion).nupkg" ~/NuGetPackagesLocal
+ sudo cp "$(System.DefaultWorkingDirectory)/src/WebJobs.Extensions.OpenAI.Kusto/bin/Release/Microsoft.Azure.WebJobs.Extensions.OpenAI.Kusto.$(fakeWebJobsPackageVersion).nupkg" ~/NuGetPackagesLocal
sleep 10s
-
- dotnet nuget add source ~/NuGetPackagesLocal
- dotnet nuget list source
+
+ dotnet nuget add source ~/NuGetPackagesLocal --configfile ~/.nuget/NuGet/NuGet.Config
+ dotnet nuget list source --configfile ~/.nuget/NuGet/NuGet.Config
sleep 10s
- dotnet build --configuration $(config) /p:WebJobsVersion=$(fakeWebJobsPackageVersion)
+ dotnet build --configuration $(config) /p:WebJobsVersion=$(fakeWebJobsPackageVersion) /p:Version=$(fakeWebJobsPackageVersion) /p:AzureAISearchVersion=$(fakeWebJobsPackageVersion) /p:KustoVersion=$(fakeWebJobsPackageVersion) --configfile ~/.nuget/NuGet/NuGet.Config
displayName: Create/Copy NuGet Package to local directory and Build
- script: dotnet test --configuration $(config) --no-build --verbosity normal
diff --git a/samples/rag-aisearch/csharp-inproc/FilePrompt.cs b/samples/rag-aisearch/csharp-inproc/FilePrompt.cs
deleted file mode 100644
index 270e2e09..00000000
--- a/samples/rag-aisearch/csharp-inproc/FilePrompt.cs
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT License.
-
-using System.IO;
-using System.Threading.Tasks;
-using Microsoft.AspNetCore.Mvc;
-using Microsoft.Azure.WebJobs;
-using Microsoft.Azure.WebJobs.Extensions.Http;
-using Microsoft.Azure.WebJobs.Extensions.OpenAI.Embeddings;
-using Microsoft.Azure.WebJobs.Extensions.OpenAI.Search;
-
-namespace SemanticAISearchEmbeddings;
-
-public static class FilePrompt
-{
- public record EmbeddingsRequest(string FilePath);
- public record SemanticSearchRequest(string Prompt);
-
- // REVIEW: There are several assumptions about how the Embeddings binding and the SemanticSearch bindings
- // work together. We should consider creating a higher-level of abstraction for this.
- [FunctionName("IngestFile")]
- public static async Task IngestFile(
- [HttpTrigger(AuthorizationLevel.Function, "post")] EmbeddingsRequest req,
- [Embeddings("{FilePath}", InputType.FilePath, Model = "%EMBEDDING_MODEL_DEPLOYMENT_NAME%")] EmbeddingsContext embeddings,
- [SemanticSearch("AISearchEndpoint", "openai-index", CredentialSettingName = "SearchAPIKey", ChatModel = "%CHAT_MODEL_DEPLOYMENT_NAME%", EmbeddingsModel = "%EMBEDDING_MODEL_DEPLOYMENT_NAME%")] IAsyncCollector output)
- {
- string title = Path.GetFileNameWithoutExtension(req.FilePath);
- await output.AddAsync(new SearchableDocument(title, embeddings));
- return new OkObjectResult(new { status = "success", title, chunks = embeddings.Count });
- }
-
- [FunctionName("PromptFile")]
- public static IActionResult PromptFile(
- [HttpTrigger(AuthorizationLevel.Function, "post")] SemanticSearchRequest unused,
- [SemanticSearch("AISearchEndpoint", "openai-index", CredentialSettingName = "SearchAPIKey", Query = "{Prompt}", ChatModel = "%CHAT_MODEL_DEPLOYMENT_NAME%", EmbeddingsModel = "%EMBEDDING_MODEL_DEPLOYMENT_NAME%")] SemanticSearchContext result)
- {
- return new ContentResult { Content = result.Response, ContentType = "text/plain" };
- }
-}
diff --git a/samples/rag-aisearch/csharp-inproc/SemanticAISearchEmbeddings.csproj b/samples/rag-aisearch/csharp-inproc/SemanticAISearchEmbeddings.csproj
deleted file mode 100644
index b9403523..00000000
--- a/samples/rag-aisearch/csharp-inproc/SemanticAISearchEmbeddings.csproj
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
- net6.0
- v4
-
-
-
-
-
-
-
-
-
-
- PreserveNewest
-
-
- PreserveNewest
- Never
-
-
-
diff --git a/samples/rag-aisearch/csharp-ooproc/FilePrompt.cs b/samples/rag-aisearch/csharp-ooproc/FilePrompt.cs
new file mode 100644
index 00000000..4a4fba68
--- /dev/null
+++ b/samples/rag-aisearch/csharp-ooproc/FilePrompt.cs
@@ -0,0 +1,73 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Net;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Azure.Functions.Worker;
+using Microsoft.Azure.Functions.Worker.Extensions.OpenAI.Embeddings;
+using Microsoft.Azure.Functions.Worker.Extensions.OpenAI.Search;
+using Microsoft.Azure.Functions.Worker.Http;
+
+namespace SemanticAISearchEmbeddings;
+
+public static class FilePrompt
+{
+ public class EmbeddingsRequest
+ {
+ [JsonPropertyName("FilePath")]
+ public string? FilePath { get; set; }
+ }
+
+ public class SemanticSearchRequest
+ {
+ [JsonPropertyName("Prompt")]
+ public string? Prompt { get; set; }
+ }
+
+ // REVIEW: There are several assumptions about how the Embeddings binding and the SemanticSearch bindings
+ // work together. We should consider creating a higher-level of abstraction for this.
+ [Function("IngestFile")]
+ public static async Task IngestFile(
+ [HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequestData req,
+ [EmbeddingsInput("{FilePath}", InputType.FilePath, Model = "%EMBEDDING_MODEL_DEPLOYMENT_NAME%")] EmbeddingsContext embeddings)
+ {
+ using StreamReader reader = new(req.Body);
+ string request = await reader.ReadToEndAsync();
+
+ EmbeddingsRequest? requestBody = JsonSerializer.Deserialize(request);
+
+ if (requestBody == null)
+ {
+ throw new ArgumentException("Invalid request body. Make sure that you pass in {\"filePath\": value } as the request body.");
+ }
+
+ string title = Path.GetFileNameWithoutExtension(requestBody.FilePath);
+
+ HttpResponseData response = req.CreateResponse(HttpStatusCode.OK);
+ await response.WriteAsJsonAsync(new { status = "success", title, chunks = embeddings.Count });
+
+ return new SemanticSearchOutputResponse
+ {
+ HttpResponse = response,
+ SearchableDocument = new SearchableDocument(title, embeddings)
+ };
+ }
+
+ public class SemanticSearchOutputResponse
+ {
+ [SemanticSearchOutput("AISearchEndpoint", "openai-index", CredentialSettingName = "SearchAPIKey", EmbeddingsModel = "%EMBEDDING_MODEL_DEPLOYMENT_NAME%")]
+ public SearchableDocument SearchableDocument { get; set; }
+
+ public HttpResponseData? HttpResponse { get; set; }
+ }
+
+ [Function("PromptFile")]
+ public static IActionResult PromptFile(
+ [HttpTrigger(AuthorizationLevel.Function, "post")] SemanticSearchRequest unused,
+ [SemanticSearchInput("AISearchEndpoint", "openai-index", CredentialSettingName = "SearchAPIKey", Query = "{Prompt}", ChatModel = "%CHAT_MODEL_DEPLOYMENT_NAME%", EmbeddingsModel = "%EMBEDDING_MODEL_DEPLOYMENT_NAME%")] SemanticSearchContext result)
+ {
+ return new ContentResult { Content = result.Response, ContentType = "text/plain" };
+ }
+}
diff --git a/samples/rag-aisearch/csharp-ooproc/Program.cs b/samples/rag-aisearch/csharp-ooproc/Program.cs
new file mode 100644
index 00000000..107dbf94
--- /dev/null
+++ b/samples/rag-aisearch/csharp-ooproc/Program.cs
@@ -0,0 +1,17 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using Microsoft.Azure.Functions.Worker;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+
+var host = new HostBuilder()
+ .ConfigureFunctionsWebApplication()
+ .ConfigureServices(services =>
+ {
+ services.AddApplicationInsightsTelemetryWorkerService();
+ services.ConfigureFunctionsApplicationInsights();
+ })
+ .Build();
+
+host.Run();
diff --git a/samples/rag-aisearch/csharp-ooproc/SemanticAISearchEmbeddings.csproj b/samples/rag-aisearch/csharp-ooproc/SemanticAISearchEmbeddings.csproj
new file mode 100644
index 00000000..27f18bf8
--- /dev/null
+++ b/samples/rag-aisearch/csharp-ooproc/SemanticAISearchEmbeddings.csproj
@@ -0,0 +1,33 @@
+
+
+ net6.0
+ v4
+ Exe
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+ Never
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/rag-aisearch/csharp-inproc/host.json b/samples/rag-aisearch/csharp-ooproc/host.json
similarity index 100%
rename from samples/rag-aisearch/csharp-inproc/host.json
rename to samples/rag-aisearch/csharp-ooproc/host.json
diff --git a/samples/rag-aisearch/csharp-inproc/local.settings.json b/samples/rag-aisearch/csharp-ooproc/local.settings.json
similarity index 88%
rename from samples/rag-aisearch/csharp-inproc/local.settings.json
rename to samples/rag-aisearch/csharp-ooproc/local.settings.json
index 1ac539c4..35808050 100644
--- a/samples/rag-aisearch/csharp-inproc/local.settings.json
+++ b/samples/rag-aisearch/csharp-ooproc/local.settings.json
@@ -2,7 +2,7 @@
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
- "FUNCTIONS_WORKER_RUNTIME": "dotnet",
+ "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
"SearchAPIKey": "",
"AISearchEndpoint": "https://.search.windows.net",
"AZURE_OPENAI_ENDPOINT": "https://.openai.azure.com/",
diff --git a/samples/rag/README.md b/samples/rag-kusto/README.md
similarity index 97%
rename from samples/rag/README.md
rename to samples/rag-kusto/README.md
index bb0d7b80..6c6ef4f6 100644
--- a/samples/rag/README.md
+++ b/samples/rag-kusto/README.md
@@ -4,7 +4,7 @@
The sample is available in the following language stacks:
-* [C# on the in process worker](csharp-inproc/)
+* [C# on the out of process worker](csharp-ooproc/)
Please refer to the [root README](../../README.md#requirements) for common prerequisites that apply to all samples.
diff --git a/samples/rag-kusto/csharp-ooproc/EmailPromptDemo.cs b/samples/rag-kusto/csharp-ooproc/EmailPromptDemo.cs
new file mode 100644
index 00000000..3552e712
--- /dev/null
+++ b/samples/rag-kusto/csharp-ooproc/EmailPromptDemo.cs
@@ -0,0 +1,73 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Net;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Azure.Functions.Worker;
+using Microsoft.Azure.Functions.Worker.Extensions.OpenAI.Embeddings;
+using Microsoft.Azure.Functions.Worker.Extensions.OpenAI.Search;
+using Microsoft.Azure.Functions.Worker.Http;
+
+namespace SemanticSearchEmbeddings;
+
+public class EmailPromptDemo
+{
+ public class EmbeddingsRequest
+ {
+ [JsonPropertyName("FilePath")]
+ public string? FilePath { get; set; }
+ }
+
+ public class SemanticSearchRequest
+ {
+ [JsonPropertyName("Prompt")]
+ public string? Prompt { get; set; }
+ }
+
+ // REVIEW: There are several assumptions about how the Embeddings binding and the SemanticSearch bindings
+ // work together. We should consider creating a higher-level of abstraction for this.
+ [Function("IngestEmail")]
+ public async Task IngestEmail(
+ [HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequestData req,
+ [EmbeddingsInput("{FilePath}", InputType.FilePath, Model = "%EMBEDDING_MODEL_DEPLOYMENT_NAME%")] EmbeddingsContext embeddings)
+ {
+ using StreamReader reader = new(req.Body);
+ string request = await reader.ReadToEndAsync();
+
+ EmbeddingsRequest? requestBody = JsonSerializer.Deserialize(request);
+
+ if (requestBody == null)
+ {
+ throw new ArgumentException("Invalid request body. Make sure that you pass in {\"filePath\": value } as the request body.");
+ }
+
+ string title = Path.GetFileNameWithoutExtension(requestBody.FilePath);
+
+ HttpResponseData response = req.CreateResponse(HttpStatusCode.OK);
+ await response.WriteAsJsonAsync(new { status = "success", title, chunks = embeddings.Count });
+
+ return new SemanticSearchOutputResponse
+ {
+ HttpResponse = response,
+ SearchableDocument = new SearchableDocument(title, embeddings)
+ };
+ }
+
+ public class SemanticSearchOutputResponse
+ {
+ [SemanticSearchOutput("KustoConnectionString", "Documents", EmbeddingsModel = "%EMBEDDING_MODEL_DEPLOYMENT_NAME%")]
+ public SearchableDocument SearchableDocument { get; set; }
+
+ public HttpResponseData? HttpResponse { get; set; }
+ }
+
+ [Function("PromptEmail")]
+ public IActionResult PromptEmail(
+ [HttpTrigger(AuthorizationLevel.Function, "post")] SemanticSearchRequest unused,
+ [SemanticSearchInput("KustoConnectionString", "Documents", Query = "{Prompt}", ChatModel = "%CHAT_MODEL_DEPLOYMENT_NAME%", EmbeddingsModel = "%EMBEDDING_MODEL_DEPLOYMENT_NAME%")] SemanticSearchContext result)
+ {
+ return new ContentResult { Content = result.Response, ContentType = "text/plain" };
+ }
+}
diff --git a/samples/rag-kusto/csharp-ooproc/Program.cs b/samples/rag-kusto/csharp-ooproc/Program.cs
new file mode 100644
index 00000000..107dbf94
--- /dev/null
+++ b/samples/rag-kusto/csharp-ooproc/Program.cs
@@ -0,0 +1,17 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using Microsoft.Azure.Functions.Worker;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+
+var host = new HostBuilder()
+ .ConfigureFunctionsWebApplication()
+ .ConfigureServices(services =>
+ {
+ services.AddApplicationInsightsTelemetryWorkerService();
+ services.ConfigureFunctionsApplicationInsights();
+ })
+ .Build();
+
+host.Run();
diff --git a/samples/rag/csharp-inproc/Properties/launchSettings.json b/samples/rag-kusto/csharp-ooproc/Properties/launchSettings.json
similarity index 100%
rename from samples/rag/csharp-inproc/Properties/launchSettings.json
rename to samples/rag-kusto/csharp-ooproc/Properties/launchSettings.json
diff --git a/samples/rag-kusto/csharp-ooproc/SemanticSearchEmbeddings.csproj b/samples/rag-kusto/csharp-ooproc/SemanticSearchEmbeddings.csproj
new file mode 100644
index 00000000..04bd099c
--- /dev/null
+++ b/samples/rag-kusto/csharp-ooproc/SemanticSearchEmbeddings.csproj
@@ -0,0 +1,33 @@
+
+
+ net6.0
+ v4
+ Exe
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+ Never
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/rag/csharp-inproc/demo.http b/samples/rag-kusto/csharp-ooproc/demo.http
similarity index 100%
rename from samples/rag/csharp-inproc/demo.http
rename to samples/rag-kusto/csharp-ooproc/demo.http
diff --git a/samples/rag/csharp-inproc/host.json b/samples/rag-kusto/csharp-ooproc/host.json
similarity index 100%
rename from samples/rag/csharp-inproc/host.json
rename to samples/rag-kusto/csharp-ooproc/host.json
diff --git a/samples/rag/csharp-inproc/local.settings.json b/samples/rag-kusto/csharp-ooproc/local.settings.json
similarity index 88%
rename from samples/rag/csharp-inproc/local.settings.json
rename to samples/rag-kusto/csharp-ooproc/local.settings.json
index ae7afa23..58ab015f 100644
--- a/samples/rag/csharp-inproc/local.settings.json
+++ b/samples/rag-kusto/csharp-ooproc/local.settings.json
@@ -2,7 +2,7 @@
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
- "FUNCTIONS_WORKER_RUNTIME": "dotnet",
+ "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
"KustoConnectionString": "Data Source=https://YOUR-CLUSTER-HOSTNAME;Initial Catalog=YOUR-DATABASE-NAME;Fed=True",
"AZURE_OPENAI_ENDPOINT": "https://.openai.azure.com/",
"CHAT_MODEL_DEPLOYMENT_NAME": "gpt-3.5-turbo",
diff --git a/samples/rag/csharp-inproc/EmailPromptDemo.cs b/samples/rag/csharp-inproc/EmailPromptDemo.cs
deleted file mode 100644
index 697fa0aa..00000000
--- a/samples/rag/csharp-inproc/EmailPromptDemo.cs
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT License.
-
-using System.IO;
-using System.Threading.Tasks;
-using Microsoft.AspNetCore.Mvc;
-using Microsoft.Azure.WebJobs;
-using Microsoft.Azure.WebJobs.Extensions.Http;
-using Microsoft.Azure.WebJobs.Extensions.OpenAI.Embeddings;
-using Microsoft.Azure.WebJobs.Extensions.OpenAI.Search;
-
-namespace SemanticSearchEmbeddings;
-
-public static class EmailPromptDemo
-{
- public record EmbeddingsRequest(string FilePath);
- public record SemanticSearchRequest(string Prompt);
-
- // REVIEW: There are several assumptions about how the Embeddings binding and the SemanticSearch bindings
- // work together. We should consider creating a higher-level of abstraction for this.
- [FunctionName("IngestEmail")]
- public static async Task IngestEmail(
- [HttpTrigger(AuthorizationLevel.Function, "post")] EmbeddingsRequest req,
- [Embeddings("{FilePath}", InputType.FilePath, Model = "%EMBEDDING_MODEL_DEPLOYMENT_NAME%")] EmbeddingsContext embeddings,
- [SemanticSearch("KustoConnectionString", "Documents", ChatModel = "%CHAT_MODEL_DEPLOYMENT_NAME%", EmbeddingsModel = "%EMBEDDING_MODEL_DEPLOYMENT_NAME%")] IAsyncCollector output)
- {
- string title = Path.GetFileNameWithoutExtension(req.FilePath);
- await output.AddAsync(new SearchableDocument(title, embeddings));
- return new OkObjectResult(new { status = "success", title, chunks = embeddings.Count });
- }
-
- [FunctionName("PromptEmail")]
- public static IActionResult PromptEmail(
- [HttpTrigger(AuthorizationLevel.Function, "post")] SemanticSearchRequest unused,
- [SemanticSearch("KustoConnectionString", "Documents", Query = "{Prompt}", ChatModel = "%CHAT_MODEL_DEPLOYMENT_NAME%", EmbeddingsModel = "%EMBEDDING_MODEL_DEPLOYMENT_NAME%")] SemanticSearchContext result)
- {
- return new ContentResult { Content = result.Response, ContentType = "text/plain" };
- }
-}
diff --git a/samples/rag/csharp-inproc/SemanticSearchEmbeddings.csproj b/samples/rag/csharp-inproc/SemanticSearchEmbeddings.csproj
deleted file mode 100644
index bbd6b82b..00000000
--- a/samples/rag/csharp-inproc/SemanticSearchEmbeddings.csproj
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
- net6.0
- v4
-
-
-
-
-
-
-
-
-
-
- PreserveNewest
-
-
- PreserveNewest
- Never
-
-
-
diff --git a/src/Directory.Build.props b/src/Directory.Build.props
index 76d26407..ef2a7c3c 100644
--- a/src/Directory.Build.props
+++ b/src/Directory.Build.props
@@ -31,6 +31,10 @@
$(VersionPrefix).0
$(VersionPrefix).$(FileVersionRevision)
+
+ 0.13.0-alpha
+
+ 0.1.0-alpha
diff --git a/src/Functions.Worker.Extensions.OpenAI.AzureAISearch/Functions.Worker.Extensions.OpenAI.AzureAISearch.csproj b/src/Functions.Worker.Extensions.OpenAI.AzureAISearch/Functions.Worker.Extensions.OpenAI.AzureAISearch.csproj
new file mode 100644
index 00000000..44663c06
--- /dev/null
+++ b/src/Functions.Worker.Extensions.OpenAI.AzureAISearch/Functions.Worker.Extensions.OpenAI.AzureAISearch.csproj
@@ -0,0 +1,22 @@
+
+
+
+ netstandard2.1
+ Microsoft Azure Functions .NET Isolated Worker Extension for OpenAI Azure AI Search
+ $(AzureAISearchVersion)
+
+
+
+
+
+
+
+
+ <_Parameter1>Microsoft.Azure.WebJobs.Extensions.OpenAI.AzureAISearch
+ <_Parameter2>$(AzureAISearchVersion)
+ <_Parameter3>true
+ <_Parameter3_IsLiteral>true
+
+
+
+
diff --git a/src/Functions.Worker.Extensions.OpenAI.Kusto/Functions.Worker.Extensions.OpenAI.Kusto.csproj b/src/Functions.Worker.Extensions.OpenAI.Kusto/Functions.Worker.Extensions.OpenAI.Kusto.csproj
new file mode 100644
index 00000000..e4a59d53
--- /dev/null
+++ b/src/Functions.Worker.Extensions.OpenAI.Kusto/Functions.Worker.Extensions.OpenAI.Kusto.csproj
@@ -0,0 +1,22 @@
+
+
+
+ netstandard2.1
+ Microsoft Azure Functions .NET Isolated Worker Extension for OpenAI Kusto
+ $(KustoVersion)
+
+
+
+
+
+
+
+
+ <_Parameter1>Microsoft.Azure.WebJobs.Extensions.OpenAI.Kusto
+ <_Parameter2>$(KustoVersion)
+ <_Parameter3>true
+ <_Parameter3_IsLiteral>true
+
+
+
+
diff --git a/src/Functions.Worker.Extensions.OpenAI/Assistants/ChatCompletionJsonConverter.cs b/src/Functions.Worker.Extensions.OpenAI/Assistants/ChatCompletionJsonConverter.cs
new file mode 100644
index 00000000..a73974fa
--- /dev/null
+++ b/src/Functions.Worker.Extensions.OpenAI/Assistants/ChatCompletionJsonConverter.cs
@@ -0,0 +1,23 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using Azure.AI.OpenAI;
+using System.ClientModel.Primitives;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace Microsoft.Azure.Functions.Worker.Extensions.OpenAI.Assistants;
+public class ChatCompletionsJsonConverter : JsonConverter
+{
+ static readonly ModelReaderWriterOptions modelReaderWriterOptions = new("J");
+ public override ChatCompletions Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ using JsonDocument jsonDocument = JsonDocument.ParseValue(ref reader);
+ return ModelReaderWriter.Read(BinaryData.FromString(jsonDocument.RootElement.GetRawText()))!;
+ }
+
+ public override void Write(Utf8JsonWriter writer, ChatCompletions value, JsonSerializerOptions options)
+ {
+ ((IJsonModel)value).Write(writer, modelReaderWriterOptions);
+ }
+}
\ No newline at end of file
diff --git a/src/Functions.Worker.Extensions.OpenAI/Search/ConnectionInfo.cs b/src/Functions.Worker.Extensions.OpenAI/Search/ConnectionInfo.cs
new file mode 100644
index 00000000..ae709409
--- /dev/null
+++ b/src/Functions.Worker.Extensions.OpenAI/Search/ConnectionInfo.cs
@@ -0,0 +1,46 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+namespace Microsoft.Azure.Functions.Worker.Extensions.OpenAI.Search;
+
+///
+/// Connection info object.
+///
+public class ConnectionInfo
+{
+ ///
+ /// Connection info containing connection name, collection name, and credentials.
+ ///
+ ///
+ /// The name of an app setting or environment variable which contains a connection string value.
+ ///
+ ///
+ /// The name of the collection or table to search or store.
+ ///
+ ///
+ /// Credentials for authenticating with the search provider.
+ ///
+ public ConnectionInfo(string connectionName, string collectionName, string? credentials)
+ {
+ this.ConnectionName = connectionName;
+ this.CollectionName = collectionName;
+ this.Credentials = credentials;
+ }
+
+ ///
+ /// Gets or sets the name of an app setting or environment variable which contains a connection string value.
+ ///
+ public string ConnectionName { get; set; }
+
+ ///
+ /// The name of the collection or table to search.
+ ///
+ public string CollectionName { get; set; }
+
+ ///
+ /// The name of the app setting or environment variable containing the required credentials
+ /// for authenticating with the search provider. See the documentation for the search provider
+ /// extension to know what format the underlying credential value requires.
+ ///
+ public string? Credentials { get; set; }
+}
diff --git a/src/Functions.Worker.Extensions.OpenAI/Search/SearchableDocument.cs b/src/Functions.Worker.Extensions.OpenAI/Search/SearchableDocument.cs
new file mode 100644
index 00000000..e7902ce9
--- /dev/null
+++ b/src/Functions.Worker.Extensions.OpenAI/Search/SearchableDocument.cs
@@ -0,0 +1,42 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using Microsoft.Azure.Functions.Worker.Extensions.OpenAI.Embeddings;
+
+namespace Microsoft.Azure.Functions.Worker.Extensions.OpenAI.Search;
+
+///
+/// Searchable document which contains the title and embeddings context.
+///
+public class SearchableDocument
+{
+ ///
+ /// Searchable document containing the title and embeddings context.
+ ///
+ ///
+ /// Title of the searchable document.
+ ///
+ ///
+ /// The embeddings context associated with the searchable document.
+ ///
+ public SearchableDocument(string title, EmbeddingsContext embeddingsContext)
+ {
+ this.Title = title;
+ this.EmbeddingsContext = embeddingsContext;
+ }
+
+ ///
+ /// Connection info for the searchable document.
+ ///
+ public ConnectionInfo? ConnectionInfo { get; set; }
+
+ ///
+ /// Title of the searchable document.
+ ///
+ public string Title { get; }
+
+ ///
+ /// Embeddings context that contains embedings request and response from OpenAI for searchable document.
+ ///
+ public EmbeddingsContext EmbeddingsContext { get; }
+}
diff --git a/src/Functions.Worker.Extensions.OpenAI/Search/SearchableDocumentJsonConverter.cs b/src/Functions.Worker.Extensions.OpenAI/Search/SearchableDocumentJsonConverter.cs
new file mode 100644
index 00000000..3e1116b1
--- /dev/null
+++ b/src/Functions.Worker.Extensions.OpenAI/Search/SearchableDocumentJsonConverter.cs
@@ -0,0 +1,70 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.ClientModel.Primitives;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using OpenAISDK = Azure.AI.OpenAI;
+
+namespace Microsoft.Azure.Functions.Worker.Extensions.OpenAI.Search;
+
+class SearchableDocumentJsonConverter : JsonConverter
+{
+ static readonly ModelReaderWriterOptions modelReaderWriterOptions = new("J");
+ public override SearchableDocument Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override void Write(Utf8JsonWriter writer, SearchableDocument value, JsonSerializerOptions options)
+ {
+ writer.WriteStartObject();
+
+ writer.WritePropertyName("embeddingsContext"u8);
+ writer.WriteStartObject();
+
+ writer.WritePropertyName("request"u8);
+ ((IJsonModel)value.EmbeddingsContext.Request).Write(writer, modelReaderWriterOptions);
+
+ writer.WritePropertyName("response"u8);
+ ((IJsonModel)value.EmbeddingsContext.Response).Write(writer, modelReaderWriterOptions);
+
+ writer.WritePropertyName("count"u8);
+ writer.WriteNumberValue(value.EmbeddingsContext.Count);
+ writer.WriteEndObject();
+
+ writer.WritePropertyName("connectionInfo"u8);
+ writer.WriteStartObject();
+ writer.WritePropertyName("connectionName"u8);
+
+ if (value.ConnectionInfo == null)
+ {
+ writer.WriteNullValue();
+ }
+ else
+ {
+ writer.WriteStringValue(value.ConnectionInfo.ConnectionName);
+ }
+
+ writer.WritePropertyName("collectionName"u8);
+
+ if (value.ConnectionInfo == null)
+ {
+ writer.WriteNullValue();
+ }
+ else if (value.ConnectionInfo.CollectionName == null)
+ {
+ writer.WriteNullValue();
+ }
+ else
+ {
+ writer.WriteStringValue(value.ConnectionInfo.CollectionName);
+ }
+ writer.WriteEndObject();
+
+
+ writer.WritePropertyName("title");
+ writer.WriteStringValue(value.Title);
+ writer.WriteEndObject();
+ }
+}
diff --git a/src/Functions.Worker.Extensions.OpenAI/Search/SemanticSearchContext.cs b/src/Functions.Worker.Extensions.OpenAI/Search/SemanticSearchContext.cs
new file mode 100644
index 00000000..1258442d
--- /dev/null
+++ b/src/Functions.Worker.Extensions.OpenAI/Search/SemanticSearchContext.cs
@@ -0,0 +1,41 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using Azure.AI.OpenAI;
+using Microsoft.Azure.Functions.Worker.Extensions.OpenAI.Embeddings;
+
+namespace Microsoft.Azure.Functions.Worker.Extensions.OpenAI.Search;
+
+///
+/// Input binding target for semantic search.
+///
+public class SemanticSearchContext
+{
+ ///
+ /// Input binding target for the .
+ ///
+ /// The embeddings context associated with the semantic search.
+ /// The chat response from the large language model.
+ public SemanticSearchContext(EmbeddingsContext Embeddings, ChatCompletions Chat)
+ {
+ this.Embeddings = Embeddings;
+ this.Chat = Chat;
+
+ }
+
+ ///
+ /// Gets the embeddings context associated with the semantic search.
+ ///
+ public EmbeddingsContext Embeddings { get; }
+
+ ///
+ /// Gets the chat response from the large language model.
+ ///
+ public ChatCompletions Chat { get; }
+
+
+ ///
+ /// Gets the latest response message from the OpenAI Chat API.
+ ///
+ public string Response => this.Chat.Choices.Last().Message.Content;
+ }
diff --git a/src/Functions.Worker.Extensions.OpenAI/Search/SemanticSearchInputAttribute.cs b/src/Functions.Worker.Extensions.OpenAI/Search/SemanticSearchInputAttribute.cs
new file mode 100644
index 00000000..fac171aa
--- /dev/null
+++ b/src/Functions.Worker.Extensions.OpenAI/Search/SemanticSearchInputAttribute.cs
@@ -0,0 +1,111 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using Microsoft.Azure.Functions.Worker.Extensions.Abstractions;
+
+namespace Microsoft.Azure.Functions.Worker.Extensions.OpenAI.Search;
+
+///
+/// Binding attribute for semantic search (input bindings).
+///
+public class SemanticSearchInputAttribute : InputBindingAttribute
+{
+ ///
+ /// Initializes a new instance of the class with the specified connection
+ /// and collection names.
+ ///
+ ///
+ /// The name of an app setting or environment variable which contains a connection string value.
+ ///
+ /// The name of the collection or table to search or store.
+ ///
+ /// Thrown if either or are null.
+ ///
+ public SemanticSearchInputAttribute(string connectionName, string collection)
+ {
+ this.ConnectionName = connectionName ?? throw new ArgumentNullException(nameof(connectionName));
+ this.Collection = collection ?? throw new ArgumentNullException(nameof(collection));
+ }
+
+ ///
+ /// Gets or sets the name of an app setting or environment variable which contains a connection string value.
+ ///
+ ///
+ /// This property supports binding expressions.
+ ///
+ public string ConnectionName { get; set; }
+
+ ///
+ /// The name of the collection or table to search.
+ ///
+ ///
+ /// This property supports binding expressions.
+ ///
+ public string Collection { get; set; }
+
+ ///
+ /// The name of the app setting or environment variable containing the required credentials
+ /// for authenticating with the search provider. See the documentation for the search provider
+ /// extension to know what format the underlying credential value requires.
+ ///
+ ///
+ /// This property supports binding expressions.
+ ///
+ public string? CredentialSettingName { get; set; }
+
+ ///
+ /// Gets or sets the semantic query text to use for searching.
+ /// This property is only used for the semantic search input binding.
+ ///
+ ///
+ /// This property supports binding expressions.
+ ///
+ public string? Query { get; set; }
+
+ ///
+ /// Gets or sets the ID of the model to use for embeddings.
+ /// The default value is "text-embedding-3-small".
+ ///
+ ///
+ /// This property supports binding expressions.
+ ///
+ public string EmbeddingsModel { get; set; } = OpenAIModels.DefaultEmbeddingsModel;
+
+ ///
+ /// Gets or sets the name of the Large Language Model to invoke for chat responses.
+ /// The default value is "gpt-3.5-turbo".
+ ///
+ ///
+ /// This property supports binding expressions.
+ ///
+ public string ChatModel { get; set; } = OpenAIModels.DefaultChatModel;
+
+ ///
+ /// Gets or sets the system prompt to use for prompting the large language model.
+ ///
+ ///
+ ///
+ /// The system prompt will be appended with knowledge that is fetched as a result of the .
+ /// The combined prompt will then be sent to the OpenAI Chat API.
+ ///
+ /// This property supports binding expressions.
+ ///
+ ///
+ public string SystemPrompt { get; set; } = """
+ You are a helpful assistant. You are responding to requests from a user about internal emails and documents.
+ You can and should refer to the internal documents to help respond to requests. If a user makes a request that's
+ not covered by the internal emails and documents, explain that you don't know the answer or that you don't have
+ access to the information.
+
+ The following is a list of documents that you can refer to when answering questions. The documents are in the format
+ [filename]: [text] and are separated by newlines. If you answer a question by referencing any of the documents,
+ please cite the document in your answer. For example, if you answer a question by referencing info.txt,
+ you should add "Reference: info.txt" to the end of your answer on a separate line.
+
+ """;
+
+ ///
+ /// Gets or sets the number of knowledge items to inject into the .
+ ///
+ public int MaxKnowledgeCount { get; set; } = 1;
+}
diff --git a/src/Functions.Worker.Extensions.OpenAI/Search/SemanticSearchOutputAttribute.cs b/src/Functions.Worker.Extensions.OpenAI/Search/SemanticSearchOutputAttribute.cs
new file mode 100644
index 00000000..b00f140c
--- /dev/null
+++ b/src/Functions.Worker.Extensions.OpenAI/Search/SemanticSearchOutputAttribute.cs
@@ -0,0 +1,64 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using Microsoft.Azure.Functions.Worker.Extensions.Abstractions;
+
+namespace Microsoft.Azure.Functions.Worker.Extensions.OpenAI.Search;
+
+///
+/// Binding attribute for semantic document storage (output bindings).
+///
+public class SemanticSearchOutputAttribute : OutputBindingAttribute
+{
+ ///
+ /// Initializes a new instance of the class with the specified connection
+ /// and collection names.
+ ///
+ ///
+ /// The name of an app setting or environment variable which contains a connection string value.
+ ///
+ /// The name of the collection or table to search or store.
+ ///
+ /// Thrown if either or are null.
+ ///
+ public SemanticSearchOutputAttribute(string connectionName, string collection)
+ {
+ this.ConnectionName = connectionName ?? throw new ArgumentNullException(nameof(connectionName));
+ this.Collection = collection ?? throw new ArgumentNullException(nameof(collection));
+ }
+
+ ///
+ /// Gets or sets the name of an app setting or environment variable which contains a connection string value.
+ ///
+ ///
+ /// This property supports binding expressions.
+ ///
+ public string ConnectionName { get; set; }
+
+ ///
+ /// The name of the collection or table to search.
+ ///
+ ///
+ /// This property supports binding expressions.
+ ///
+ public string Collection { get; set; }
+
+ ///
+ /// The name of the app setting or environment variable containing the required credentials
+ /// for authenticating with the search provider. See the documentation for the search provider
+ /// extension to know what format the underlying credential value requires.
+ ///
+ ///
+ /// This property supports binding expressions.
+ ///
+ public string? CredentialSettingName { get; set; }
+
+ ///
+ /// Gets or sets the ID of the model to use for embeddings.
+ /// The default value is "text-embedding-3-small".
+ ///
+ ///
+ /// This property supports binding expressions.
+ ///
+ public string EmbeddingsModel { get; set; } = OpenAIModels.DefaultEmbeddingsModel;
+}
diff --git a/src/Functions.Worker.Extensions.OpenAI/Startup.cs b/src/Functions.Worker.Extensions.OpenAI/Startup.cs
index a1d1ba90..8bbd524b 100644
--- a/src/Functions.Worker.Extensions.OpenAI/Startup.cs
+++ b/src/Functions.Worker.Extensions.OpenAI/Startup.cs
@@ -4,7 +4,9 @@
using System.Text.Json;
using Microsoft.Azure.Functions.Worker.Core;
using Microsoft.Azure.Functions.Worker.Extensions.OpenAI;
+using Microsoft.Azure.Functions.Worker.Extensions.OpenAI.Assistants;
using Microsoft.Azure.Functions.Worker.Extensions.OpenAI.Embeddings;
+using Microsoft.Azure.Functions.Worker.Extensions.OpenAI.Search;
using Microsoft.Extensions.DependencyInjection;
[assembly: WorkerExtensionStartup(typeof(Startup))]
@@ -22,6 +24,8 @@ public override void Configure(IFunctionsWorkerApplicationBuilder applicationBui
{
jsonSerializerOptions.Converters.Add(new EmbeddingsJsonConverter());
jsonSerializerOptions.Converters.Add(new EmbeddingsOptionsJsonConverter());
+ jsonSerializerOptions.Converters.Add(new SearchableDocumentJsonConverter());
+ jsonSerializerOptions.Converters.Add(new ChatCompletionsJsonConverter());
});
}
}
diff --git a/src/WebJobs.Extensions.OpenAI.AzureAISearch/WebJobs.Extensions.OpenAI.AzureAISearch.csproj b/src/WebJobs.Extensions.OpenAI.AzureAISearch/WebJobs.Extensions.OpenAI.AzureAISearch.csproj
index 00ad2361..1cab077a 100644
--- a/src/WebJobs.Extensions.OpenAI.AzureAISearch/WebJobs.Extensions.OpenAI.AzureAISearch.csproj
+++ b/src/WebJobs.Extensions.OpenAI.AzureAISearch/WebJobs.Extensions.OpenAI.AzureAISearch.csproj
@@ -1,27 +1,15 @@
+
+ Microsoft Azure WebJobs SDK Extension for OpenAI - Azure AI Search Package.
+ $(AzureAISearchVersion)
+
+
+
+
+
-
- Microsoft Azure WebJobs SDK Extension for OpenAI - Azure AI Search Package.
-
-
-
- 0
- 1
- 0
- $(MajorVersion).$(MinorVersion).$(PatchVersion)
- alpha
- $(MajorVersion).$(MinorVersion).0.0
- $(VersionPrefix).0
-
- $(VersionPrefix).$(FileVersionRevision)
-
-
-
-
-
-
-
-
-
+
+
+
diff --git a/src/WebJobs.Extensions.OpenAI.Kusto/WebJobs.Extensions.OpenAI.Kusto.csproj b/src/WebJobs.Extensions.OpenAI.Kusto/WebJobs.Extensions.OpenAI.Kusto.csproj
index 86e0b579..ce29a34f 100644
--- a/src/WebJobs.Extensions.OpenAI.Kusto/WebJobs.Extensions.OpenAI.Kusto.csproj
+++ b/src/WebJobs.Extensions.OpenAI.Kusto/WebJobs.Extensions.OpenAI.Kusto.csproj
@@ -1,27 +1,16 @@
-
-
- Microsoft Azure WebJobs SDK Extension for OpenAI - Kusto Search Package.
-
-
-
- 0
- 13
- 0
- $(MajorVersion).$(MinorVersion).$(PatchVersion)
- alpha
- $(MajorVersion).$(MinorVersion).0.0
- $(VersionPrefix).0
-
- $(VersionPrefix).$(FileVersionRevision)
-
-
-
-
-
-
-
-
-
-
+
+
+ Microsoft Azure WebJobs SDK Extension for OpenAI - Kusto Search Package.
+ $(KustoVersion)
+
+
+
+
+
+
+
+
+
+
diff --git a/src/WebJobs.Extensions.OpenAI/Assistants/ChatCompletionsJsonConverter.cs b/src/WebJobs.Extensions.OpenAI/Assistants/ChatCompletionsJsonConverter.cs
new file mode 100644
index 00000000..f8ad8c8c
--- /dev/null
+++ b/src/WebJobs.Extensions.OpenAI/Assistants/ChatCompletionsJsonConverter.cs
@@ -0,0 +1,22 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.ClientModel.Primitives;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using Azure.AI.OpenAI;
+
+namespace Microsoft.Azure.WebJobs.Extensions.OpenAI.Assistants;
+class ChatCompletionsJsonConverter : JsonConverter
+{
+ static readonly ModelReaderWriterOptions modelReaderWriterOptions = new("J");
+ public override ChatCompletions Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override void Write(Utf8JsonWriter writer, ChatCompletions value, JsonSerializerOptions options)
+ {
+ ((IJsonModel)value).Write(writer, modelReaderWriterOptions);
+ }
+}
diff --git a/src/WebJobs.Extensions.OpenAI/Embeddings/EmbeddingsJsonConverter.cs b/src/WebJobs.Extensions.OpenAI/Embeddings/EmbeddingsContextConverter.cs
similarity index 94%
rename from src/WebJobs.Extensions.OpenAI/Embeddings/EmbeddingsJsonConverter.cs
rename to src/WebJobs.Extensions.OpenAI/Embeddings/EmbeddingsContextConverter.cs
index 51a12948..301712fc 100644
--- a/src/WebJobs.Extensions.OpenAI/Embeddings/EmbeddingsJsonConverter.cs
+++ b/src/WebJobs.Extensions.OpenAI/Embeddings/EmbeddingsContextConverter.cs
@@ -11,7 +11,7 @@ namespace Microsoft.Azure.WebJobs.Extensions.OpenAI.Embeddings;
///
/// Embeddings JSON converter needed to serialize the EmbeddingsContext object.
///
-class EmbeddingsJsonConverter : JsonConverter
+class EmbeddingsContextConverter : JsonConverter
{
static readonly ModelReaderWriterOptions modelReaderWriterOptions = new("J");
public override EmbeddingsContext Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
diff --git a/src/WebJobs.Extensions.OpenAI/Embeddings/EmbeddingsConverter.cs b/src/WebJobs.Extensions.OpenAI/Embeddings/EmbeddingsConverter.cs
index dc42374b..70c44951 100644
--- a/src/WebJobs.Extensions.OpenAI/Embeddings/EmbeddingsConverter.cs
+++ b/src/WebJobs.Extensions.OpenAI/Embeddings/EmbeddingsConverter.cs
@@ -6,7 +6,6 @@
using Microsoft.Extensions.Logging;
using OpenAISDK = Azure.AI.OpenAI;
-
namespace Microsoft.Azure.WebJobs.Extensions.OpenAI.Embeddings;
class EmbeddingsConverter :
@@ -18,7 +17,7 @@ class EmbeddingsConverter :
// Note: we need this converter as Azure.AI.OpenAI does not support System.Text.Json serialization since their constructors are internal
static readonly JsonSerializerOptions options = new()
{
- Converters = { new EmbeddingsJsonConverter() }
+ Converters = { new EmbeddingsContextConverter() }
};
public EmbeddingsConverter(OpenAISDK.OpenAIClient openAIClient, ILoggerFactory loggerFactory)
diff --git a/src/WebJobs.Extensions.OpenAI/Embeddings/EmbeddingsOptionsJsonConverter.cs b/src/WebJobs.Extensions.OpenAI/Embeddings/EmbeddingsOptionsJsonConverter.cs
new file mode 100644
index 00000000..e1705ac5
--- /dev/null
+++ b/src/WebJobs.Extensions.OpenAI/Embeddings/EmbeddingsOptionsJsonConverter.cs
@@ -0,0 +1,22 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.ClientModel.Primitives;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using Azure.AI.OpenAI;
+
+namespace Microsoft.Azure.WebJobs.Extensions.OpenAI.Embeddings;
+class EmbeddingsOptionsJsonConverter : JsonConverter
+{
+ static readonly ModelReaderWriterOptions modelReaderWriterOptions = new("J");
+ public override EmbeddingsOptions Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override void Write(Utf8JsonWriter writer, EmbeddingsOptions value, JsonSerializerOptions options)
+ {
+ ((IJsonModel)value).Write(writer, modelReaderWriterOptions);
+ }
+}
diff --git a/src/WebJobs.Extensions.OpenAI/OpenAIExtension.cs b/src/WebJobs.Extensions.OpenAI/OpenAIExtension.cs
index 42688349..44ed6b99 100644
--- a/src/WebJobs.Extensions.OpenAI/OpenAIExtension.cs
+++ b/src/WebJobs.Extensions.OpenAI/OpenAIExtension.cs
@@ -53,8 +53,10 @@ void IExtensionConfigProvider.Initialize(ExtensionConfigContext context)
// Semantic search input binding support
var semanticSearchRule = context.AddBindingRule();
semanticSearchRule.BindToInput(this.semanticSearchConverter);
+ semanticSearchRule.BindToInput(this.semanticSearchConverter);
// TODO: Add string binding support to enable binding in non-.NET languages.
semanticSearchRule.BindToCollector(this.semanticSearchConverter);
+ context.AddConverter(this.semanticSearchConverter.ToSearchableDocument);
// Assistant support
var chatBotCreateRule = context.AddBindingRule();
diff --git a/src/WebJobs.Extensions.OpenAI/Search/SearchableDocumentJsonConverter.cs b/src/WebJobs.Extensions.OpenAI/Search/SearchableDocumentJsonConverter.cs
new file mode 100644
index 00000000..f127eae5
--- /dev/null
+++ b/src/WebJobs.Extensions.OpenAI/Search/SearchableDocumentJsonConverter.cs
@@ -0,0 +1,134 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.ClientModel.Primitives;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using Microsoft.Azure.WebJobs.Extensions.OpenAI.Embeddings;
+using OpenAISDK = Azure.AI.OpenAI;
+
+namespace Microsoft.Azure.WebJobs.Extensions.OpenAI.Search;
+class SearchableDocumentJsonConverter : JsonConverter
+{
+ static readonly ModelReaderWriterOptions modelReaderWriterOptions = new("J");
+ public override SearchableDocument Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ using JsonDocument jsonDocument = JsonDocument.ParseValue(ref reader);
+
+ // Properties for SearchableDocument
+ OpenAISDK.EmbeddingsOptions embeddingsOptions = null;
+ OpenAISDK.Embeddings embeddings = null;
+ int count;
+ string title = null;
+ string connectionName = null;
+ string collectionName = null;
+ string credentials = null;
+
+ foreach (JsonProperty item in jsonDocument.RootElement.EnumerateObject())
+ {
+ if (item.NameEquals("embeddingsContext"u8))
+ {
+ foreach (JsonProperty embeddingContextItem in item.Value.EnumerateObject())
+ {
+ if (embeddingContextItem.NameEquals("request"u8))
+ {
+ embeddingsOptions = ModelReaderWriter.Read(BinaryData.FromString(embeddingContextItem.Value.GetRawText()))!;
+ }
+ if (embeddingContextItem.NameEquals("response"u8))
+ {
+ embeddings = ModelReaderWriter.Read(BinaryData.FromString(embeddingContextItem.Value.GetRawText()))!;
+ }
+ if (embeddingContextItem.NameEquals("count"u8))
+ {
+ count = embeddingContextItem.Value.GetInt32();
+ }
+ }
+ }
+ if (item.NameEquals("connectionInfo"u8))
+ {
+ foreach (JsonProperty connectionInfoItem in item.Value.EnumerateObject())
+ {
+ if (connectionInfoItem.NameEquals("connectionName"u8))
+ {
+ connectionName = connectionInfoItem.Value.GetString();
+ }
+ if (connectionInfoItem.NameEquals("collectionName"u8))
+ {
+ collectionName = connectionInfoItem.Value.GetString();
+ }
+ if (connectionInfoItem.NameEquals("credentials"u8))
+ {
+ credentials = connectionInfoItem.Value.GetString();
+ }
+ }
+ }
+
+ if (item.NameEquals("title"u8))
+ {
+ title = item.Value.GetString();
+ }
+ }
+ SearchableDocument searchableDocument = new SearchableDocument(title, new EmbeddingsContext(embeddingsOptions, embeddings));
+ searchableDocument.ConnectionInfo = new ConnectionInfo(connectionName, collectionName, credentials);
+ return searchableDocument;
+ }
+
+ public override void Write(Utf8JsonWriter writer, SearchableDocument value, JsonSerializerOptions options)
+ {
+ writer.WriteStartObject();
+
+ writer.WritePropertyName("embeddingsContext"u8);
+ writer.WriteStartObject();
+
+ writer.WritePropertyName("request"u8);
+ ((IJsonModel)value.Embeddings.Request).Write(writer, modelReaderWriterOptions);
+
+ writer.WritePropertyName("response"u8);
+ ((IJsonModel)value.Embeddings.Response).Write(writer, modelReaderWriterOptions);
+
+ writer.WritePropertyName("count"u8);
+ writer.WriteNumberValue(value.Embeddings.Count);
+ writer.WriteEndObject();
+
+ writer.WritePropertyName("connectionInfo"u8);
+ writer.WriteStartObject();
+ writer.WritePropertyName("connectionName"u8);
+
+ if (value.ConnectionInfo == null)
+ {
+ writer.WriteNullValue();
+ }
+ else
+ {
+ writer.WriteStringValue(value.ConnectionInfo.ConnectionName);
+ }
+
+ writer.WritePropertyName("collectionName"u8);
+
+ if (value.ConnectionInfo == null)
+ {
+ writer.WriteNullValue();
+ }
+ else
+ {
+ writer.WriteStringValue(value.ConnectionInfo.CollectionName);
+ }
+
+ writer.WritePropertyName("credentials"u8);
+
+ if (value.ConnectionInfo == null)
+ {
+ writer.WriteNullValue();
+ }
+ else
+ {
+ writer.WriteStringValue(value.ConnectionInfo.Credentials);
+ }
+ writer.WriteEndObject();
+
+
+ writer.WritePropertyName("title");
+ writer.WriteStringValue(value.Title);
+ writer.WriteEndObject();
+ }
+}
diff --git a/src/WebJobs.Extensions.OpenAI/Search/SemanticSearchContext.cs b/src/WebJobs.Extensions.OpenAI/Search/SemanticSearchContext.cs
new file mode 100644
index 00000000..4935cdc3
--- /dev/null
+++ b/src/WebJobs.Extensions.OpenAI/Search/SemanticSearchContext.cs
@@ -0,0 +1,41 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using Azure.AI.OpenAI;
+using Microsoft.Azure.WebJobs.Extensions.OpenAI.Embeddings;
+
+namespace Microsoft.Azure.WebJobs.Extensions.OpenAI.Search;
+
+///
+/// Input binding target for the .
+///
+public class SemanticSearchContext
+{
+ ///
+ /// Input binding target for the .
+ ///
+ /// The embeddings context associated with the semantic search.
+ /// The chat response from the large language model.
+ public SemanticSearchContext(EmbeddingsContext Embeddings, ChatCompletions Chat)
+ {
+ this.Embeddings = Embeddings;
+ this.Chat = Chat;
+
+ }
+
+ ///
+ /// Embeddings context that contains embedings request and response from OpenAI for searchable document.
+ ///
+ public EmbeddingsContext Embeddings { get; }
+
+ ///
+ /// Chat response from the chat completions request.
+ ///
+ public ChatCompletions Chat { get; }
+
+
+ ///
+ /// Gets the latest response message from the OpenAI Chat API.
+ ///
+ public string Response => this.Chat.Choices.Last().Message.Content;
+}
diff --git a/src/WebJobs.Extensions.OpenAI/Search/SemanticSearchConverter.cs b/src/WebJobs.Extensions.OpenAI/Search/SemanticSearchConverter.cs
index 12bc4b9a..246587ef 100644
--- a/src/WebJobs.Extensions.OpenAI/Search/SemanticSearchConverter.cs
+++ b/src/WebJobs.Extensions.OpenAI/Search/SemanticSearchConverter.cs
@@ -1,8 +1,10 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
-using System.Text;
using Azure;
+using System.Text;
+using System.Text.Json;
+using Microsoft.Azure.WebJobs.Extensions.OpenAI.Assistants;
using Microsoft.Azure.WebJobs.Extensions.OpenAI.Embeddings;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
@@ -10,27 +12,24 @@
namespace Microsoft.Azure.WebJobs.Extensions.OpenAI.Search;
-///
-/// Input binding target for the .
-///
-/// The embeddings context associated with the semantic search.
-/// The chat response from the large language model.
-public record SemanticSearchContext(EmbeddingsContext Embeddings, Response Chat)
-{
- ///
- /// Gets the latest response message from the OpenAI Chat API.
- ///
- public string Response => this.Chat.Value.Choices.Last().Message.Content;
-}
-
class SemanticSearchConverter :
IAsyncConverter,
- IAsyncConverter>
+ IAsyncConverter>,
+ IAsyncConverter
{
readonly OpenAISDK.OpenAIClient openAIClient;
readonly ILogger logger;
readonly ISearchProvider? searchProvider;
+ static readonly JsonSerializerOptions options = new()
+ {
+ Converters = {
+ new SearchableDocumentJsonConverter(),
+ new EmbeddingsContextConverter(),
+ new EmbeddingsOptionsJsonConverter(),
+ new ChatCompletionsJsonConverter()}
+ };
+
public SemanticSearchConverter(
OpenAISDK.OpenAIClient openAIClient,
ILoggerFactory loggerFactory,
@@ -60,7 +59,7 @@ public Task> ConvertAsync(
return Task.FromResult(collector);
}
- async Task IAsyncConverter.ConvertAsync(
+ async Task ConvertHelperAsync(
SemanticSearchAttribute attribute,
CancellationToken cancellationToken)
{
@@ -126,6 +125,27 @@ async Task IAsyncConverter IAsyncConverter.ConvertAsync(
+ SemanticSearchAttribute attribute,
+ CancellationToken cancellationToken)
+ {
+ return await this.ConvertHelperAsync(attribute, cancellationToken);
+ }
+
+ // Called by the host when processing binding requests from out-of-process workers.
+ 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 assistant post request");
+ }
+
+ async Task IAsyncConverter.ConvertAsync(SemanticSearchAttribute input, CancellationToken cancellationToken)
+ {
+ SemanticSearchContext semanticSearchContext = await this.ConvertHelperAsync(input, cancellationToken);
+ return JsonSerializer.Serialize(semanticSearchContext, options);
+ }
+
sealed class SemanticDocumentCollector : IAsyncCollector
{
readonly SemanticSearchAttribute attribute;
@@ -139,7 +159,7 @@ public SemanticDocumentCollector(SemanticSearchAttribute attribute, ISearchProvi
public Task AddAsync(SearchableDocument item, CancellationToken cancellationToken = default)
{
- if (item.ConnectionInfo == null)
+ if (item.ConnectionInfo == null || item.ConnectionInfo.CollectionName == null || item.ConnectionInfo.CollectionName == null)
{
item.ConnectionInfo = new ConnectionInfo(this.attribute.ConnectionName, this.attribute.Collection, this.attribute.CredentialSettingName);
}
diff --git a/src/WebJobs.Extensions.OpenAI/WebJobs.Extensions.OpenAI.csproj b/src/WebJobs.Extensions.OpenAI/WebJobs.Extensions.OpenAI.csproj
index 24f777ad..daac9cc5 100644
--- a/src/WebJobs.Extensions.OpenAI/WebJobs.Extensions.OpenAI.csproj
+++ b/src/WebJobs.Extensions.OpenAI/WebJobs.Extensions.OpenAI.csproj
@@ -8,8 +8,8 @@
-
+