This repo has been moved to Microsoft official repository, visit the Azure/azure-functions-openai-extension to learn more or make contributions.
This is an experimental project that adds support for OpenAI LLM (GPT-3.5-turbo, GPT-4) bindings in Azure Functions. It is not currently endorsed or supported by Microsoft.
This extension depends on the Betalgo.OpenAI by Betalgo.
The following NuGet packages are available as part of this project.
- .NET 6 SDK or greater (Visual Studio 2022 recommended)
- Azure Functions Core Tools v4.x
- An OpenAI account and an API key saved into a
OPENAI_API_KEY
environment variable, OR an Azure OpenAI resource withAZURE_OPENAI_KEY
andAZURE_OPENAI_ENDPOINT
set. Learn more in .env readme.
The following features are currently available. More features will be slowly added over time.
The textCompletion
input binding can be used to invoke the OpenAI Text Completions API and return the results to the function.
The examples below define "who is" HTTP-triggered functions with a hardcoded "who is {name}?"
prompt, where {name}
is the substituted with the value in the HTTP request path. The OpenAI input binding invokes the OpenAI GPT endpoint to surface the answer to the prompt to the function, which then returns the result text as the response content.
[FunctionName(nameof(WhoIs))]
public static string WhoIs(
[HttpTrigger(AuthorizationLevel.Function, Route = "whois/{name}")] HttpRequest req,
[TextCompletion("Who is {name}?")] CompletionCreateResponse response)
{
return response.Choices[0].Text;
}
import { app, input } from "@azure/functions";
// This OpenAI completion input requires a {name} binding value.
const openAICompletionInput = input.generic({
prompt: 'Who is {name}?',
maxTokens: '100',
type: 'textCompletion'
})
app.http('whois', {
methods: ['GET'],
route: 'whois/{name}',
authLevel: 'function',
extraInputs: [openAICompletionInput],
handler: async (_request, context) => {
var response: any = context.extraInputs.get(openAICompletionInput)
return { body: response.choices[0].text.trim() }
}
});
You can run the above function locally using the Azure Functions Core Tools and sending an HTTP request, similar to the following:
GET http://localhost:7127/api/whois/pikachu
The result that comes back will include the response from the GPT language model:
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Date: Tue, 28 Mar 2023 18:25:40 GMT
Server: Kestrel
Transfer-Encoding: chunked
Pikachu is a fictional creature from the Pok�mon franchise. It is a yellow
mouse-like creature with powerful electrical abilities and a mischievous
personality. Pikachu is one of the most iconic and recognizable characters
from the franchise, and is featured in numerous video games, anime series,
movies, and other media.
You can find more instructions for running the samples in the corresponding project directories. The goal is to have samples for all languages supported by Azure Functions.
Chat completions are useful for building AI-powered chat bots. This extension adds a built-in OpenAI::ChatBotEntity
function that's powered by the Durable Functions extension to implement a long-running chat bot entity.
There are three bindings you can use to interact with the chat bot:
- The
chatBotCreate
output binding creates a new chat bot with a specified system prompt. - The
chatBotPost
output binding sends a message to the chat bot and saves the response in its internal state. - The
chatBotQuery
input binding fetches the chat bot history and passes it to the function.
You can find samples in multiple languages with instructions in the chat samples directory.
Assistants build on top of the chat bot functionality to provide chat bots with custom skills defined as functions. This internally uses the function calling feature of OpenAIs GPT models to select which functions to invoke and when.
You can define functions that can be triggered by chat bots by using the assistantSkillTrigger
trigger binding.
These functions are invoked by the extension when a chat bot signals that it would like to invoke a function in response to a user prompt.
The name of the function, the description provided by the trigger, and the parameter name are all hints that the underlying language model use to determine when and how to invoke an assistant function.
public class AssistantSkills
{
readonly ITodoManager todoManager;
// This constructor is called by the Azure Functions runtime's dependency injection container.
public AssistantSkills(ITodoManager todoManager)
{
this.todoManager = todoManager ?? throw new ArgumentNullException(nameof(todoManager));
}
// Called by the assistant to create new todo tasks.
[FunctionName(nameof(AddTodo))]
public Task AddTodo([AssistantSkillTrigger("Create a new todo task")] string taskDescription)
{
string todoId = Guid.NewGuid().ToString()[..6];
return this.todoManager.AddTodoAsync(new TodoItem(todoId, taskDescription));
}
// Called by the assistant to fetch the list of previously created todo tasks.
[FunctionName(nameof(GetTodos))]
public Task<IReadOnlyList<TodoItem>> GetTodos(
[AssistantSkillTrigger("Fetch the list of previously created todo tasks")] object inputIgnored)
{
return this.todoManager.GetTodosAsync();
}
}
You can find samples with instructions in the assistant samples directory.
OpenAI's text embeddings measure the relatedness of text strings. Embeddings are commonly used for:
- Search (where results are ranked by relevance to a query string)
- Clustering (where text strings are grouped by similarity)
- Recommendations (where items with related text strings are recommended)
- Anomaly detection (where outliers with little relatedness are identified)
- Diversity measurement (where similarity distributions are analyzed)
- Classification (where text strings are classified by their most similar label)
Processing of the source text files typically involves chunking the text into smaller pieces, such as sentences or paragraphs, and then making an OpenAI call to produce embeddings for each chunk independently. Finally, the embeddings need to be stored in a database or other data store for later use.
[FunctionName(nameof(GenerateEmbeddings_Http_Request))]
public static void GenerateEmbeddings_Http_Request(
[HttpTrigger(AuthorizationLevel.Function, "post", Route = "embeddings")] EmbeddingsRequest req,
[Embeddings("{RawText}", InputType.RawText)] EmbeddingCreateResponse embeddingsResponse,
ILogger logger)
{
logger.LogInformation(
"Received {count} embedding(s) for input text containing {length} characters.",
embeddingsResponse.Data.Count,
req.RawText.Length);
// TODO: Store the embeddings into a database or other storage.
}
The semantic search feature allows you to import documents into a vector database using an output binding and query the documents in that database using an input binding. For example, you can have a function that imports documents into a vector database and another function that issues queries to OpenAI using content stored in the vector database as context (also known as the Retrieval Augmented Generation, or RAG technique).
The supported list of vector databases is extensible, and more can be added by authoring a specially crafted NuGet package. Currently supported vector databases include:
More may be added over time.
This HTTP trigger function takes a path to a local file as input, generates embeddings for the file, and stores the result into Azure Data Explorer (a.k.a. Kusto).
public record EmbeddingsRequest(string FilePath);
[FunctionName("IngestEmail")]
public static async Task<IActionResult> IngestEmail_Better(
[HttpTrigger(AuthorizationLevel.Function, "post")] EmbeddingsRequest req,
[Embeddings("{FilePath}", InputType.FilePath)] EmbeddingsContext embeddings,
[SemanticSearch("KustoConnectionString", "Documents")] IAsyncCollector<SearchableDocument> output)
{
string title = Path.GetFileNameWithoutExtension(req.FilePath);
await output.AddAsync(new SearchableDocument(title, embeddings));
return new OkObjectResult(new { status = "success", title, chunks = embeddings.Count });
}
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.
public record SemanticSearchRequest(string Prompt);
[FunctionName("PromptEmail")]
public static IActionResult PromptEmail(
[HttpTrigger(AuthorizationLevel.Function, "post")] SemanticSearchRequest unused,
[SemanticSearch("KustoConnectionString", "Documents", Query = "{Prompt}")] SemanticSearchContext result)
{
return new ContentResult { Content = result.Response, ContentType = "text/plain" };
}
The responses from the above function will be based on relevant document snippets which were previously uploaded to the vector database. For example, assuming you uploaded internal emails discussing a new feature of Azure Functions that supports OpenAI, you could issue a query similar to the following:
POST http://localhost:7127/api/PromptEmail
Content-Type: application/json
{
"Prompt": "Was a decision made to officially release an OpenAI binding for Azure Functions?"
}
And you might get a response that looks like the following (actual results may vary):
HTTP/1.1 200 OK
Content-Length: 454
Content-Type: text/plain
There is no clear decision made on whether to officially release an OpenAI binding for Azure Functions as per the email "Thoughts on Functions+AI conversation" sent by Bilal. However, he suggests that the team should figure out if they are able to free developers from needing to know the details of AI/LLM APIs by sufficiently/elegantly designing bindings to let them do the "main work" they need to do. Reference: Thoughts on Functions+AI conversation.
As of v0.3.0, this extension also supports using OpenAI models deployed to the Azure OpenAI service. To use this feature, you must have an Azure OpenAI resource provisioned in your Azure subscription. You can find more information about how to provision an Azure OpenAI resource here.
To use Azure OpenAI with these bindings, you must set the following environment variables:
AZURE_OPENAI_KEY
- The API key for your Azure OpenAI resource.AZURE_OPENAI_ENDPOINT
- The endpoint for your Azure OpenAI resource - e.g.,https://***.openai.azure.com/
.
IMPORTANT: Azure OpenAI requires you to specify a deployment when making API calls instead of a model. The deployment is a specific instance of a model that you have deployed to your Azure OpenAI resource. In order to make code more portable across OpenAI and Azure OpenAI, the bindings in this extension use the Model
and EmbeddingsModel
to refer to either the OpenAI model or the Azure OpenAI deployment ID, depending on whether you're using OpenAI or Azure OpenAI.
All samples in this project rely on default model selection, which assumes the models are named after the OpenAI models. If you want to use an Azure OpenAI deployment, you'll want to configure the Model
and EmbeddingsModel
properties explicitly in your binding configuration. Here are a couple examples:
// "my-gpt-4" is the name of an Azure OpenAI deployment
[FunctionName(nameof(WhoIs))]
public static string WhoIs(
[HttpTrigger(AuthorizationLevel.Function, Route = "whois/{name}")] HttpRequest req,
[TextCompletion("Who is {name}?", Model = "my-gpt-4")] CompletionCreateResponse response)
{
return response.Choices[0].Text;
}
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-ada-002 models, respectively
[FunctionName("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)
{
return new ContentResult { Content = result.Response, ContentType = "text/plain" };
}