Skip to content

Commit 46e5d94

Browse files
authored
Merge pull request #49 from GarageGroup/feature/use-gpt-azure
Use Azure GPT
2 parents d45d676 + 6a10210 commit 46e5d94

24 files changed

+344
-139
lines changed

src/app/AzureFunc/Applicaton/Applicaton.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ internal static partial class Application
1414
{
1515
private const string DataverseSectionName = "Dataverse";
1616

17+
private const string GptApiSectionName = "GptApi";
18+
19+
private const string GptApiAzureSectionName = "Azure";
20+
21+
private const string IncidentCompleteSectionName = "IncidentComplete";
22+
1723
private static Dependency<HttpMessageHandler> UseHttpMessageHandlerStandard(string loggerCategoryName)
1824
=>
1925
PrimaryHandler.UseStandardSocketsHttpHandler()

src/app/AzureFunc/Applicaton/BotFlow/Flow.IncidentCreate.cs

Lines changed: 56 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,6 @@ namespace GarageGroup.Internal.Support;
99

1010
partial class Application
1111
{
12-
private const string GptApiSectionName = "GptApi";
13-
14-
private const string IncidentCompleteSectionName = "IncidentComplete";
15-
1612
private static IBotBuilder UseIncidentCreateFlow(this IBotBuilder botBuilder)
1713
=>
1814
Dependency.From(
@@ -53,37 +49,76 @@ private static SupportGptApiOption ResolveSupportGptApiOption(IServiceProvider s
5349
var gptApiSection = configuration.GetRequiredSection(GptApiSectionName);
5450
var incidentCompleteSection = gptApiSection.GetRequiredSection(IncidentCompleteSectionName);
5551

52+
var incidentComplete = new IncidentCompleteOption(
53+
chatMessages: new(
54+
new(
55+
role: "system",
56+
contentTemplate: incidentCompleteSection["SystemTemplate"].OrEmpty()),
57+
new(
58+
role: "user",
59+
contentTemplate: incidentCompleteSection["UserTemplate"].OrEmpty())))
60+
{
61+
MaxTokens = incidentCompleteSection.GetValue<int?>("MaxTokens"),
62+
Temperature = incidentCompleteSection.GetValue<decimal?>("Temperature")
63+
};
64+
65+
if (configuration.IsAzureGpt() is false)
66+
{
67+
return new(
68+
apiKey: gptApiSection["Key"].OrEmpty(),
69+
model: gptApiSection["Model"].OrEmpty(),
70+
incidentComplete: incidentComplete);
71+
}
72+
73+
var azureSection = gptApiSection.GetRequiredSection(GptApiAzureSectionName);
5674
return new(
57-
apiKey: gptApiSection["Key"].OrEmpty(),
58-
incidentComplete: new IncidentCompleteOption(
59-
model: incidentCompleteSection["Model"].OrEmpty())
60-
{
61-
MaxTokens = incidentCompleteSection.GetValue<int?>("MaxTokens"),
62-
Temperature = incidentCompleteSection.GetValue<decimal?>("Temperature"),
63-
ChatMessages = new(
64-
new(
65-
role: "system",
66-
contentTemplate: incidentCompleteSection["SystemTemplate"].OrEmpty()),
67-
new(
68-
role: "user",
69-
contentTemplate: incidentCompleteSection["UserTemplate"].OrEmpty()))
70-
});
75+
apiKey: azureSection["Key"].OrEmpty(),
76+
azureGpt: new(
77+
resourceName: azureSection["ResourceName"].OrEmpty(),
78+
deploymentId: azureSection["DeploymentId"].OrEmpty(),
79+
apiVersion: azureSection["ApiVersion"].OrEmpty()),
80+
incidentComplete: incidentComplete);
7181
}
7282

7383
private static FlatArray<KeyValuePair<string, string>> GetGptTraceData(this IConfiguration configuration)
7484
{
75-
var section = configuration.GetRequiredSection(GptApiSectionName).GetRequiredSection(IncidentCompleteSectionName);
85+
var gptApiSection = configuration.GetRequiredSection(GptApiSectionName);
7686
var traceData = new Dictionary<string, string>();
7787

88+
traceData.AppendSectionValues(gptApiSection);
89+
90+
if (configuration.IsAzureGpt())
91+
{
92+
traceData.AppendSectionValues(gptApiSection.GetSection(GptApiAzureSectionName));
93+
}
94+
95+
traceData.AppendSectionValues(gptApiSection.GetSection(IncidentCompleteSectionName));
96+
return traceData.ToFlatArray();
97+
}
98+
99+
private static bool IsAzureGpt(this IConfiguration configuration)
100+
=>
101+
string.IsNullOrEmpty(configuration[$"{GptApiSectionName}:Model"]);
102+
103+
private static void AppendSectionValues(this Dictionary<string, string> traceData, IConfigurationSection section)
104+
{
105+
if (section.Exists() is false)
106+
{
107+
return;
108+
}
109+
78110
foreach (var child in section.GetChildren())
79111
{
112+
if (string.Equals("key", child.Key, StringComparison.InvariantCultureIgnoreCase))
113+
{
114+
continue;
115+
}
116+
80117
if (string.IsNullOrEmpty(child.Value) is false)
81118
{
82119
traceData[child.Key.FromLowerCase()] = child.Value;
83120
}
84121
}
85-
86-
return traceData.ToFlatArray();
87122
}
88123

89124
private static string FromLowerCase(this string source)

src/app/AzureFunc/appsettings.json

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,15 @@
2828
},
2929
"GptApi": {
3030
"Key": "",
31+
"Model": "",
32+
"Version": "v2",
33+
"Azure": {
34+
"Key": "",
35+
"ResourceName": "ai-garage-gpt-dev-02",
36+
"DeploymentId": "garage-support-bot",
37+
"ApiVersion": "2023-07-01-preview"
38+
},
3139
"IncidentComplete": {
32-
"Version": "v1",
33-
"Model": "gpt-3.5-turbo",
3440
"MaxTokens": 200,
3541
"Temperature": 0,
3642
"SystemTemplate": "Ты выделяешь краткий заголовок без деталей (не больше 125 символов) из обращений к поддержке ИТ компании",

src/service/CrmContact/Test/Test.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
</ItemGroup>
1717

1818
<ItemGroup>
19-
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
19+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
2020
<PackageReference Include="Moq" Version="4.20.69" />
2121
<PackageReference Include="xunit" Version="2.6.1" />
2222
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3">

src/service/CrmCustomer/Test/Test.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
</ItemGroup>
1717

1818
<ItemGroup>
19-
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
19+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
2020
<PackageReference Include="Moq" Version="4.20.69" />
2121
<PackageReference Include="xunit" Version="2.6.1" />
2222
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3">

src/service/CrmIncident/Test/Test.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
</ItemGroup>
1717

1818
<ItemGroup>
19-
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
19+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
2020
<PackageReference Include="Moq" Version="4.20.69" />
2121
<PackageReference Include="xunit" Version="2.6.1" />
2222
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3">

src/service/CrmOwner/Test/Test.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
</ItemGroup>
1717

1818
<ItemGroup>
19-
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
19+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
2020
<PackageReference Include="Moq" Version="4.20.69" />
2121
<PackageReference Include="xunit" Version="2.6.1" />
2222
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3">

src/service/Gpt/Api/Api.csproj

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
<ItemGroup>
1818
<PackageReference Include="PrimeFuncPack.Core.FlatArray" Version="1.2.1" />
1919
<PackageReference Include="PrimeFuncPack.Dependency.Core" Version="2.0.2" />
20-
<PackageReference Include="PrimeFuncPack.Primitives.Strings" Version="2.0.2" />
2120
</ItemGroup>
2221

2322
</Project>

src/service/Gpt/Api/Api/Api.CompleteIncident.cs

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
using System;
22
using System.Net;
33
using System.Net.Http;
4+
using System.Net.Http.Headers;
5+
using System.Net.Http.Json;
46
using System.Net.Mime;
5-
using System.Text;
67
using System.Text.Json;
78
using System.Threading;
89
using System.Threading.Tasks;
@@ -35,32 +36,35 @@ private async ValueTask<Result<IncidentCompleteOut, Failure<IncidentCompleteFail
3536
var sourceMessage = input.Message.Trim();
3637
var jsonIn = new ChatGptJsonIn
3738
{
38-
Model = option.IncidentComplete.Model,
39+
Model = option.Model,
3940
MaxTokens = option.IncidentComplete.MaxTokens,
4041
Temperature = option.IncidentComplete.Temperature,
4142
Top = 1,
4243
Messages = option.IncidentComplete.ChatMessages.Map(CreateChatMessageJson)
4344
};
4445

45-
var json = JsonSerializer.Serialize(jsonIn);
46-
using var content = new StringContent(json, Encoding.UTF8, MediaTypeNames.Application.Json);
47-
4846
using var httpClient = CreateHttpClient();
47+
using var httpRequest = new HttpRequestMessage
48+
{
49+
Method = HttpMethod.Post,
50+
Content = JsonContent.Create(jsonIn, new MediaTypeHeaderValue(MediaTypeNames.Application.Json))
51+
};
4952

50-
using var httpResponse = await httpClient.PostAsync(OpenAiCompletionsUrl, content, cancellationToken).ConfigureAwait(false);
53+
using var httpResponse = await httpClient.SendAsync(httpRequest, cancellationToken).ConfigureAwait(false);
5154
var httpResponseText = await httpResponse.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
5255

5356
if (httpResponse.StatusCode is HttpStatusCode.TooManyRequests)
5457
{
55-
var errorMessage = ReadErrorMessage(httpResponseText);
56-
return Failure.Create(IncidentCompleteFailureCode.TooManyRequests, errorMessage);
58+
return Failure.Create(
59+
failureCode: IncidentCompleteFailureCode.TooManyRequests,
60+
failureMessage: ReadErrorMessage(httpResponseText));
5761
}
5862

5963
if (httpResponse.IsSuccessStatusCode is false)
6064
{
6165
return Failure.Create(
62-
IncidentCompleteFailureCode.Unknown,
63-
$"An unexpected http status code: {httpResponse.StatusCode}. Body: {httpResponseText}");
66+
failureCode: IncidentCompleteFailureCode.Unknown,
67+
failureMessage: $"An unexpected http status code: {httpResponse.StatusCode}. Body: '{httpResponseText}'");
6468
}
6569

6670
var jsonOut = JsonSerializer.Deserialize<ChatGptJsonOut>(httpResponseText);
@@ -69,15 +73,15 @@ private async ValueTask<Result<IncidentCompleteOut, Failure<IncidentCompleteFail
6973
{
7074
return Failure.Create(
7175
IncidentCompleteFailureCode.Unknown,
72-
$"GPT result choices are absent. Body: {httpResponseText}");
76+
$"GPT result choices are absent. Body: '{httpResponseText}'");
7377
}
7478

7579
var choice = jsonOut.Choices[0];
7680
if (string.Equals(choice.FinishReason, "stop", StringComparison.InvariantCultureIgnoreCase) is false)
7781
{
7882
return Failure.Create(
7983
IncidentCompleteFailureCode.Unknown,
80-
$"An unexpected GPT finish reason: {choice.FinishReason}. Body: {httpResponseText}");
84+
$"An unexpected GPT finish reason: '{choice.FinishReason}'. Body: '{httpResponseText}'");
8185
}
8286

8387
return new IncidentCompleteOut

src/service/Gpt/Api/Api/SupportGptApi.cs

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,13 @@ namespace GarageGroup.Internal.Support;
55

66
internal sealed partial class SupportGptApi : ISupportGptApi
77
{
8-
private const string OpenAiBaseAddressUrl = "https://api.openai.com/";
8+
private const string OpenAiUrl = "https://api.openai.com/v1/chat/completions";
99

10-
private const string OpenAiCompletionsUrl = "/v1/chat/completions";
10+
private const string AzureAiUrlTemplate
11+
=
12+
"https://{0}.openai.azure.com/openai/deployments/{1}/chat/completions?api-version={2}";
13+
14+
private const string AzureAiApiKeyHeaderName = "api-key";
1115

1216
private readonly HttpMessageHandler httpMessageHandler;
1317

@@ -21,12 +25,20 @@ internal SupportGptApi(HttpMessageHandler httpMessageHandler, SupportGptApiOptio
2125

2226
private HttpClient CreateHttpClient()
2327
{
24-
var httpClient = new HttpClient(httpMessageHandler, false)
28+
var httpClient = new HttpClient(httpMessageHandler, false);
29+
var azure = option.AzureGpt;
30+
31+
if (azure is null)
2532
{
26-
BaseAddress = new(OpenAiBaseAddressUrl)
27-
};
33+
httpClient.BaseAddress = new(OpenAiUrl);
34+
httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {option.ApiKey}");
35+
}
36+
else
37+
{
38+
httpClient.BaseAddress = new(string.Format(AzureAiUrlTemplate, azure.ResourceName, azure.DeploymentId, azure.ApiVersion));
39+
httpClient.DefaultRequestHeaders.Add(AzureAiApiKeyHeaderName, option.ApiKey);
40+
}
2841

29-
httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {option.ApiKey}");
3042
return httpClient;
3143
}
3244

0 commit comments

Comments
 (0)