Skip to content

Commit b62cafc

Browse files
Merge pull request Azure#81 from Azure/assistant-dalle
Add DALLE function
2 parents f47b2d6 + bdf01fc commit b62cafc

17 files changed

Lines changed: 160 additions & 33 deletions

gen-ai/Assistants/bot-in-a-box/README.md

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -63,17 +63,7 @@ azd up
6363
```
6464
You will be prompted for a subscription, region and model information. Keep regional model availability when proceeding.
6565
66-
3. Go to the Azure OpenAI Studio and create an Assistant with the tools you want to use. Alternatively, you can also use the API.
67-
68-
> Important: currently, this application only supports Code Interpreter. Other tools will be implemented in the near future.
69-
70-
![Assistant Creation](./readme_assets/assistant-creation.png)
71-
72-
4. Add your newly created Assistant's ID in the AOAI_ASSISTANT_ID environment variable.
73-
74-
![Add Assistant ID to environment](./readme_assets/assistant-id-variable.png)
75-
76-
5. Test on Web Chat - go to your Azure Bot resource on the Azure portal and look for the Web Chat feature on the left side menu.
66+
3. Test on Web Chat - go to your Azure Bot resource on the Azure portal and look for the Web Chat feature on the left side menu.
7767
7868
![Test Web Chat](./readme_assets/assistant-test.png)
7969
@@ -106,4 +96,13 @@ To deploy a Web Chat version of your app:
10696
- Add the secret to your App Service's environment variables, under the key DIRECT_LINE_SECRET;
10797
- Your bot will be available at https://APP_NAME.azurewebsites.net.
10898
109-
Please note that doing so will make your bot public, unless you implement authentication / SSO.
99+
Please note that doing so will make your bot public, unless you implement authentication / SSO.
100+
101+
### Creating custom functions
102+
103+
To update function calling behavior or create your own functions, follow the steps below.
104+
105+
- Go to [./src/Tools](./src/Tools/)
106+
- Create or update a JSON file with the function specification. You may also copy files from [./src/ToolsSamples](./src/ToolsSamples/) into [./src/Tools](./src/Tools/)
107+
- Go to `_Tools.cs` and create or update the method with the same name as the function's "name" field. **Use only lowercase characters and underscores for names**.
108+
- Redeploy with `azd up`. The Assistant definition will be updated automatically.

gen-ai/Assistants/bot-in-a-box/infra/main.bicep

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ param cosmosName string = ''
2121
param speechName string = ''
2222

2323
param storageName string = ''
24-
param deployDalle3 bool = false
24+
param deployDalle3 bool
2525
param deploySpeech bool
2626
@description('Deploy Speech service?')
2727

@@ -114,6 +114,7 @@ module m_app 'modules/appservice.bicep' = {
114114
openaiEndpoint: m_openai.outputs.openaiEndpoint
115115
openaiGPTModel: m_openai.outputs.openaiGPTModel
116116
openaiEmbeddingsModel: m_openai.outputs.openaiEmbeddingsModel
117+
openaiDalleModel: m_openai.outputs.openaiDalleModel
117118
speechName: deploySpeech ? m_speech.outputs.speechName : ''
118119
speechEndpoint: deploySpeech ? m_speech.outputs.speechEndpoint : ''
119120
cosmosEndpoint: m_cosmos.outputs.cosmosEndpoint

gen-ai/Assistants/bot-in-a-box/infra/modules/appservice.bicep

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ param sku string = 'S1'
77
param tags object = {}
88
param openaiGPTModel string
99
param openaiEmbeddingsModel string
10+
param openaiDalleModel string
1011
param speechName string
1112
var speechNames = !empty(speechName) ? [speechName] : []
1213

@@ -87,6 +88,10 @@ resource appService 'Microsoft.Web/sites@2022-09-01' = {
8788
name: 'AOAI_ASSISTANT_ID'
8889
value: 'YOUR_ASSISTANT_ID'
8990
}
91+
{
92+
name: 'AOAI_DALLE_DEPLOYMENT'
93+
value: openaiDalleModel
94+
}
9095
{
9196
name: 'SPEECH_API_ENDPOINT'
9297
value: speechEndpoint

gen-ai/Assistants/bot-in-a-box/infra/modules/openai.bicep

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ resource adaEmbeddingsdeployment 'Microsoft.CognitiveServices/accounts/deploymen
6464

6565
resource dalle3deployment 'Microsoft.CognitiveServices/accounts/deployments@2023-05-01' = if (deployDalle3) {
6666
parent: openai
67-
name: 'dall-e-3'
67+
name: 'Dalle3'
6868
properties: {
6969
model: {
7070
format: 'OpenAI'
@@ -98,3 +98,4 @@ output openaiName string = openai.name
9898
output openaiEndpoint string = openai.properties.endpoint
9999
output openaiGPTModel string = gpt4deployment.name
100100
output openaiEmbeddingsModel string = adaEmbeddingsdeployment.name
101+
output openaiDalleModel string = deployDalle3 ? dalle3deployment.name : ''

gen-ai/Assistants/bot-in-a-box/src/AssistantBot.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<Project Sdk="Microsoft.NET.Sdk.Web">
22
<PropertyGroup>
3-
<TargetFramework>net7.0</TargetFramework>
3+
<TargetFramework>net8.0</TargetFramework>
44
<LangVersion>latest</LangVersion>
55
<UserSecretsId>73c2a03b-aae7-4833-b3a8-c01744ad2b7e</UserSecretsId>
66
</PropertyGroup>

gen-ai/Assistants/bot-in-a-box/src/Bots/AssistantBot.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ public override async Task<List<string>> ProcessMessage(ConversationData convers
116116
{
117117
if (run.Status == "requires_action")
118118
{
119-
var tools = new Tools(conversationData, turnContext);
119+
var tools = new Tools(conversationData, turnContext, _aoaiClient);
120120
var submitData = await tools.RunRequestedTools(run);
121121
await _aoaiClient.SubmitToolOutputs(conversationData.ThreadId, run.Id, submitData);
122122
}

gen-ai/Assistants/bot-in-a-box/src/Models/AOAIModels.cs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
using System.Collections.Generic;
44
using System.Text.Json.Serialization;
5+
using Azure.AI.OpenAI;
56

67
namespace Models
78
{
@@ -139,5 +140,45 @@ public class ThreadRun
139140
[JsonPropertyName("required_action")]
140141
public RequiredAction RequiredAction { get; set; }
141142
}
143+
public class ImageGenerationInput
144+
{
145+
[JsonPropertyName("prompt")]
146+
public string Prompt { get; set; }
147+
148+
[JsonPropertyName("size")]
149+
public string Size { get; set; }
150+
151+
[JsonPropertyName("n")]
152+
public int N { get; set; }
153+
}
154+
public class ImageGenerationOutput
155+
{
156+
[JsonPropertyName("id")]
157+
public string Id { get; set; }
158+
159+
[JsonPropertyName("status")]
160+
public string Status { get; set; }
161+
}
162+
public class ImageGenerationStatusResponse
163+
{
164+
[JsonPropertyName("result")]
165+
public ImageGenerationResult Result { get; set; }
166+
167+
[JsonPropertyName("status")]
168+
public string Status { get; set; }
169+
}
170+
public class ImageGenerationResult
171+
{
172+
[JsonPropertyName("data")]
173+
public List<GeneratedImage> Data { get; set; }
174+
}
175+
public class GeneratedImage
176+
{
177+
[JsonPropertyName("url")]
178+
public string Url { get; set; }
179+
180+
[JsonPropertyName("revised_prompt")]
181+
public string RevisedPrompt { get; set; }
182+
}
142183

143184
}

gen-ai/Assistants/bot-in-a-box/src/Services/AOAIClient.cs

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
using System.Text;
66
using System.Text.Json;
77
using System.Threading.Tasks;
8-
using Microsoft.Azure.Cosmos.Core;
8+
using Microsoft.IdentityModel.Tokens;
99
using Models;
1010

1111
namespace Services
@@ -14,11 +14,13 @@ public class AOAIClient
1414
{
1515
private readonly HttpClient _httpClient;
1616
private readonly string _accessKey;
17-
public AOAIClient(HttpClient httpClient, Uri uriBase, string apiKey)
17+
private readonly string _dalleDeployment;
18+
public AOAIClient(HttpClient httpClient, Uri uriBase, string apiKey, string dalleDeployment)
1819
{
1920
httpClient.BaseAddress = uriBase;
2021
_httpClient = httpClient;
2122
_accessKey = apiKey;
23+
_dalleDeployment = dalleDeployment;
2224
}
2325

2426
public async Task<List<Assistant>> ListAssistants()
@@ -74,10 +76,30 @@ public async Task<HttpResponseMessage> GetFile(string fileId)
7476
var result = await SendRequest($"/files/{fileId}/content", HttpMethod.Get);
7577
return result;
7678
}
77-
78-
private async Task<HttpResponseMessage> SendRequest(string path, HttpMethod method, StringContent body = null)
79+
public async Task<ImageGenerationResult> GenerateImages(ImageGenerationInput input)
80+
{
81+
if (_dalleDeployment.IsNullOrEmpty())
82+
return await GenerateImagesV2(input);
83+
else
84+
return await GenerateImagesV3(_dalleDeployment, input);
85+
}
86+
public async Task<ImageGenerationResult> GenerateImagesV2(ImageGenerationInput input)
87+
{
88+
var output = await JsonRequest<ImageGenerationOutput>($"/images/generations:submit", HttpMethod.Post, new StringContent(JsonSerializer.Serialize(input), Encoding.UTF8, "application/json"), "2023-06-01-preview");
89+
var response = await JsonRequest<ImageGenerationStatusResponse>($"/operations/images/{output.Id}", HttpMethod.Get, apiVersion: "2023-06-01-preview");
90+
while (response.Status != "succeeded") {
91+
response = await JsonRequest<ImageGenerationStatusResponse>($"/operations/images/{output.Id}", HttpMethod.Get, apiVersion: "2023-06-01-preview");
92+
System.Threading.Thread.Sleep(5000);
93+
}
94+
return response.Result;
95+
}
96+
public async Task<ImageGenerationResult> GenerateImagesV3(string deploymentId, ImageGenerationInput input)
97+
{
98+
return await JsonRequest<ImageGenerationResult>($"/deployments/{deploymentId}/images/generations", HttpMethod.Post, new StringContent(JsonSerializer.Serialize(input), Encoding.UTF8, "application/json"), "2023-12-01-preview");
99+
}
100+
private async Task<HttpResponseMessage> SendRequest(string path, HttpMethod method, StringContent body = null, string apiVersion = "2024-02-15-preview")
79101
{
80-
var url = "/openai" + path + "?api-version=2024-02-15-preview";
102+
var url = "/openai" + path + "?api-version=" + apiVersion;
81103

82104
var request = new HttpRequestMessage(method, url)
83105
{
@@ -91,9 +113,9 @@ private async Task<HttpResponseMessage> SendRequest(string path, HttpMethod meth
91113
}
92114

93115

94-
private async Task<T> JsonRequest<T>(string path, HttpMethod method, StringContent body = null)
116+
private async Task<T> JsonRequest<T>(string path, HttpMethod method, StringContent body = null, string apiVersion = "2024-02-15-preview")
95117
{
96-
var response = await SendRequest(path, method, body);
118+
var response = await SendRequest(path, method, body, apiVersion);
97119
var responseContent = await response.Content.ReadAsStringAsync();
98120
if (!response.IsSuccessStatusCode)
99121
throw new Exception(responseContent);
@@ -102,9 +124,9 @@ private async Task<T> JsonRequest<T>(string path, HttpMethod method, StringConte
102124
return content;
103125
}
104126

105-
private async Task<HttpResponseMessage> SendRequest(string path, HttpMethod method, MultipartFormDataContent body)
127+
private async Task<HttpResponseMessage> SendRequest(string path, HttpMethod method, MultipartFormDataContent body, string apiVersion = "2024-02-15-preview")
106128
{
107-
var url = "/openai" + path + "?api-version=2024-02-15-preview";
129+
var url = "/openai" + path + "?api-version=" + apiVersion;
108130

109131
var request = new HttpRequestMessage(method, url)
110132
{
@@ -118,9 +140,9 @@ private async Task<HttpResponseMessage> SendRequest(string path, HttpMethod meth
118140
}
119141

120142

121-
private async Task<T> JsonRequest<T>(string path, HttpMethod method, MultipartFormDataContent body)
143+
private async Task<T> JsonRequest<T>(string path, HttpMethod method, MultipartFormDataContent body, string apiVersion = "2024-02-15-preview")
122144
{
123-
var response = await SendRequest(path, method, body);
145+
var response = await SendRequest(path, method, body, apiVersion);
124146

125147
var responseContent = await response.Content.ReadAsStringAsync();
126148
if (!response.IsSuccessStatusCode)

gen-ai/Assistants/bot-in-a-box/src/Startup.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
// Licensed under the MIT License.
33

44
using System;
5-
using Azure;
6-
using Azure.AI.OpenAI;
75
using Azure.Identity;
86
using Microsoft.AspNetCore.Builder;
97
using Microsoft.AspNetCore.Hosting;
@@ -73,7 +71,7 @@ public void ConfigureServices(IServiceCollection services)
7371
// Create the Conversation state passing in the storage layer.
7472
var conversationState = new ConversationState(storage);
7573
services.AddSingleton(conversationState);
76-
services.AddSingleton(new AOAIClient(new System.Net.Http.HttpClient(), new Uri(configuration.GetValue<string>("AOAI_API_ENDPOINT")), configuration.GetValue<string>("AOAI_API_KEY")));
74+
services.AddSingleton(new AOAIClient(new System.Net.Http.HttpClient(), new Uri(configuration.GetValue<string>("AOAI_API_ENDPOINT")), configuration.GetValue<string>("AOAI_API_KEY"), configuration.GetValue<string>("AOAI_DALLE_DEPLOYMENT")));
7775
services.AddHttpClient();
7876
if (!configuration.GetValue<string>("SPEECH_API_ENDPOINT").IsNullOrEmpty())
7977
services.AddSingleton(new SpeechService(new System.Net.Http.HttpClient(), configuration.GetValue<string>("SPEECH_API_ENDPOINT"), configuration.GetValue<string>("SPEECH_API_KEY")));

gen-ai/Assistants/bot-in-a-box/src/Tools/_Tools.cs

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
using System;
21
using System.Collections.Generic;
32
using System.Net.Http;
43
using System.Text.Encodings.Web;
@@ -9,20 +8,24 @@
98
using Microsoft.Bot.Schema;
109
using Microsoft.BotBuilderSamples;
1110
using Models;
11+
using Services;
1212

1313
public class Tools
1414
{
1515
private HttpClient client = new HttpClient();
16+
private AOAIClient _aoaiClient;
1617
private ITurnContext _turnContext;
1718
private ConversationData _conversationData;
1819

1920
public Tools(
2021
ConversationData conversationData,
21-
ITurnContext<IMessageActivity> turnContext
22+
ITurnContext<IMessageActivity> turnContext,
23+
AOAIClient aoaiClient
2224
)
2325
{
2426
_conversationData = conversationData;
2527
_turnContext = turnContext;
28+
_aoaiClient = aoaiClient;
2629
}
2730

2831
public async Task<ToolOutputData> RunRequestedTools(ThreadRun run)
@@ -35,7 +38,7 @@ public async Task<ToolOutputData> RunRequestedTools(ThreadRun run)
3538
{
3639
var method = typeof(Tools).GetMethod(toolcall.Function.Name);
3740
var arguments = JsonSerializer.Deserialize<Dictionary<string, object>>(toolcall.Function.Arguments);
38-
string output = await (Task<string>)method.Invoke(this, new object[]{arguments});
41+
string output = await (Task<string>)method.Invoke(this, new object[] { arguments });
3942
var toolOutput = new ToolOutput
4043
{
4144
ToolCallId = toolcall.Id,
@@ -94,6 +97,36 @@ public async Task<string> wikipedia_get_article(Dictionary<string, object> argum
9497
return await response.Content.ReadAsStringAsync();
9598
else
9699
return $"FAILED TO FETCH DATA FROM API. STATUS CODE {response.StatusCode}";
100+
}
101+
public async Task<string> dalle_generate_images(Dictionary<string, object> arguments)
102+
{
103+
var numImages = ((JsonElement)arguments["num_images"]).GetInt32();
104+
var prompt = arguments["prompt"].ToString();
105+
await _turnContext.SendActivityAsync($"Generating {numImages} images with the description \"{prompt}\"...");
106+
var imageGenerationOutput = await _aoaiClient.GenerateImages(
107+
new ImageGenerationInput()
108+
{
109+
Prompt = prompt,
110+
Size = "1024x1024",
111+
N = numImages
112+
});
113+
114+
List<object> images = new();
115+
foreach (GeneratedImage img in imageGenerationOutput.Data)
116+
images.Add(new { type = "Image", url = img.Url });
117+
object adaptiveCardJson = new
118+
{
119+
type = "AdaptiveCard",
120+
version = "1.0",
121+
body = images
122+
};
97123

124+
var adaptiveCardAttachment = new Microsoft.Bot.Schema.Attachment()
125+
{
126+
ContentType = "application/vnd.microsoft.card.adaptive",
127+
Content = adaptiveCardJson,
128+
};
129+
await _turnContext.SendActivityAsync(MessageFactory.Attachment(adaptiveCardAttachment));
130+
return "Images were generated successfully and already sent to user. No need to embed or reference them into your next message.";
98131
}
99132
}

0 commit comments

Comments
 (0)