Skip to content

Commit e455117

Browse files
authored
[Azure.AI.Projects] Add support for Inference (#46972)
Add Inference
1 parent dbfbd35 commit e455117

14 files changed

+289
-113
lines changed

eng/Packages.Data.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@
129129
<PackageReference Update="Azure.Storage.Queues" Version="12.19.1" />
130130
<PackageReference Update="Azure.Storage.Files.Shares" Version="12.19.1" />
131131
<PackageReference Update="Azure.AI.OpenAI" Version="2.0.0" />
132+
<PackageReference Update="Azure.AI.Inference" Version="1.0.0-beta.2" />
132133
<PackageReference Update="Azure.ResourceManager" Version="1.13.0" />
133134
<PackageReference Update="Azure.ResourceManager.AppConfiguration" Version="1.3.2" />
134135
<PackageReference Update="Azure.ResourceManager.ApplicationInsights" Version="1.0.0" />

sdk/ai/Azure.AI.Projects/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
# Azure.AI.Client client library for .NET
1+
# Azure AI Projects client library for .NET
22

3-
The Azure AI Assistants client library for .NET is an adaptation of OpenAI's REST APIs that provides an idiomatic interface
3+
TODO: [Update README] The Azure AI Assistants client library for .NET is an adaptation of OpenAI's REST APIs that provides an idiomatic interface
44
and rich integration with the rest of the Azure SDK ecosystem. It will connect to Azure AI resources endpoint.
55

66
Use this library to:
@@ -20,7 +20,7 @@ To use Assistants capabilities, you'll need to use an Azure AI resource, you mus
2020
Install the client library for .NET with [NuGet](https://www.nuget.org/ ):
2121

2222
```dotnetcli
23-
dotnet add package Azure.AI.Project --prerelease
23+
dotnet add package Azure.AI.Projects --prerelease
2424
```
2525

2626
### Authenticate the client

sdk/ai/Azure.AI.Projects/api/Azure.AI.Projects.netstandard2.0.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -457,7 +457,9 @@ public AIProjectClient(System.Uri endpoint, string subscriptionId, string resour
457457
public AIProjectClient(System.Uri endpoint, string subscriptionId, string resourceGroupName, string projectName, Azure.Core.TokenCredential credential, Azure.AI.Projects.AIProjectClientOptions options) { }
458458
public virtual Azure.Core.Pipeline.HttpPipeline Pipeline { get { throw null; } }
459459
public virtual Azure.AI.Projects.AgentsClient GetAgentsClient(string apiVersion = "2024-07-01-preview") { throw null; }
460+
public virtual Azure.AI.Inference.ChatCompletionsClient GetChatCompletionsClient() { throw null; }
460461
public virtual Azure.AI.Projects.ConnectionsClient GetConnectionsClient(string apiVersion = "2024-07-01-preview") { throw null; }
462+
public virtual Azure.AI.Inference.EmbeddingsClient GetEmbeddingsClient() { throw null; }
461463
public virtual Azure.AI.Projects.EvaluationsClient GetEvaluationsClient(string apiVersion = "2024-07-01-preview") { throw null; }
462464
}
463465
public partial class AIProjectClientOptions : Azure.Core.ClientOptions

sdk/ai/Azure.AI.Projects/src/Azure.AI.Projects.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
<ItemGroup>
1616
<PackageReference Include="Azure.Core" />
1717
<PackageReference Include="System.Text.Json" />
18+
<PackageReference Include="Azure.AI.Inference" />
1819
</ItemGroup>
1920

2021
<!-- Shared source from Azure.Core -->

sdk/ai/Azure.AI.Projects/src/Custom/AIProjectClient.cs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the MIT License.
33

44
using System;
5+
using Azure.AI.Inference;
56
using Azure.Core;
67

78
namespace Azure.AI.Projects
@@ -36,5 +37,49 @@ public AIProjectClient(string connectionString, TokenCredential credential, AIPr
3637
options)
3738
{
3839
}
40+
41+
private ChatCompletionsClient _chatCompletionsClient;
42+
private EmbeddingsClient _embeddingsClient;
43+
44+
/// <summary> Initializes a new instance of Inference's ChatCompletionsClient. </summary>
45+
public virtual ChatCompletionsClient GetChatCompletionsClient()
46+
{
47+
return _chatCompletionsClient ??= InitializeInferenceClient((endpoint, credential) =>
48+
new ChatCompletionsClient(endpoint, credential, new AzureAIInferenceClientOptions()));
49+
}
50+
51+
/// <summary> Initializes a new instance of Inference's EmbeddingsClient. </summary>
52+
public virtual EmbeddingsClient GetEmbeddingsClient()
53+
{
54+
return _embeddingsClient ??= InitializeInferenceClient((endpoint, credential) =>
55+
new EmbeddingsClient(endpoint, credential, new AzureAIInferenceClientOptions()));
56+
}
57+
58+
/// <summary> Initializes a new instance of Inference client. </summary>
59+
private T InitializeInferenceClient<T>(Func<Uri, AzureKeyCredential, T> clientFactory)
60+
{
61+
var connectionsClient = GetConnectionsClient();
62+
ConnectionsListSecretsResponse connectionSecret = connectionsClient.GetDefaultConnection(ConnectionType.Serverless, true);
63+
64+
if (connectionSecret.Properties is ConnectionPropertiesApiKeyAuth apiKeyAuthProperties)
65+
{
66+
if (string.IsNullOrWhiteSpace(apiKeyAuthProperties.Target))
67+
{
68+
throw new ArgumentException("The API key authentication target URI is missing or invalid.");
69+
}
70+
71+
if (!Uri.TryCreate(apiKeyAuthProperties.Target, UriKind.Absolute, out var endpoint))
72+
{
73+
throw new UriFormatException("Invalid URI format in API key authentication target.");
74+
}
75+
76+
var credential = new AzureKeyCredential(apiKeyAuthProperties.Credentials.Key);
77+
return clientFactory(endpoint, credential);
78+
}
79+
else
80+
{
81+
throw new ArgumentException("Cannot connect with Inference! Ensure valid ConnectionPropertiesApiKeyAuth.");
82+
}
83+
}
3984
}
4085
}

sdk/ai/Azure.AI.Projects/src/Custom/Connection/ConnectionsClient.cs

Lines changed: 144 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
#nullable disable
55

66
using System;
7+
using System.Threading.Tasks;
8+
using System.Threading;
79
using Azure.Core;
810
using Azure.Core.Pipeline;
911

@@ -73,11 +75,152 @@ public ConnectionsClient(Uri endpoint, string subscriptionId, string resourceGro
7375
ClientDiagnostics = new ClientDiagnostics(options, true);
7476
_tokenCredential = credential;
7577
_pipeline = HttpPipelineBuilder.Build(options, Array.Empty<HttpPipelinePolicy>(), new HttpPipelinePolicy[] { new BearerTokenAuthenticationPolicy(_tokenCredential, AuthorizationScopes) }, new ResponseClassifier());
76-
_endpoint = endpoint;
78+
_endpoint = new Uri("https://management.azure.com");
7779
_subscriptionId = subscriptionId;
7880
_resourceGroupName = resourceGroupName;
7981
_projectName = projectName;
8082
_apiVersion = options.Version;
8183
}
84+
85+
/// <summary> Initializes a new instance of ConnectionsClient. </summary>
86+
/// <param name="clientDiagnostics"> The handler for diagnostic messaging in the client. </param>
87+
/// <param name="pipeline"> The HTTP pipeline for sending and receiving REST requests and responses. </param>
88+
/// <param name="tokenCredential"> The token credential to copy. </param>
89+
/// <param name="endpoint"> The Azure AI Studio project endpoint, in the form `https://&lt;azure-region&gt;.api.azureml.ms` or `https://&lt;private-link-guid&gt;.&lt;azure-region&gt;.api.azureml.ms`, where &lt;azure-region&gt; is the Azure region where the project is deployed (e.g. westus) and &lt;private-link-guid&gt; is the GUID of the Enterprise private link. </param>
90+
/// <param name="subscriptionId"> The Azure subscription ID. </param>
91+
/// <param name="resourceGroupName"> The name of the Azure Resource Group. </param>
92+
/// <param name="projectName"> The Azure AI Studio project name. </param>
93+
/// <param name="apiVersion"> The API version to use for this operation. </param>
94+
internal ConnectionsClient(ClientDiagnostics clientDiagnostics, HttpPipeline pipeline, TokenCredential tokenCredential, Uri endpoint, string subscriptionId, string resourceGroupName, string projectName, string apiVersion)
95+
{
96+
ClientDiagnostics = clientDiagnostics;
97+
_pipeline = pipeline;
98+
_tokenCredential = tokenCredential;
99+
_endpoint = new Uri("https://management.azure.com");
100+
_subscriptionId = subscriptionId;
101+
_resourceGroupName = resourceGroupName;
102+
_projectName = projectName;
103+
_apiVersion = apiVersion;
104+
}
105+
106+
/// <summary> List the details of all the connections (not including their credentials). </summary>
107+
/// <param name="category"> Category of the workspace connection. </param>
108+
/// <param name="withCredential"></param>
109+
/// <param name="includeAll"> Indicates whether to list datastores. Service default: do not list datastores. </param>
110+
/// <param name="target"> Target of the workspace connection. </param>
111+
/// <param name="cancellationToken"> The cancellation token to use. </param>
112+
internal virtual async Task<Response<ConnectionsListSecretsResponse>> GetDefaultConnectionAsync(ConnectionType category, bool? withCredential = null, bool? includeAll = null, string target = null, CancellationToken cancellationToken = default)
113+
{
114+
ConnectionsListResponse connections = await GetConnectionsAsync(category, includeAll, target, cancellationToken).ConfigureAwait(false);
115+
116+
if (connections?.Value == null || connections.Value.Count == 0)
117+
{
118+
throw new InvalidOperationException("No connections found for the specified parameters.");
119+
}
120+
121+
var secret = connections.Value[0];
122+
return withCredential.GetValueOrDefault()
123+
? await GetSecretsAsync(secret.Name, "ignored").ConfigureAwait(false)
124+
: await GetConnectionAsync(secret.Name).ConfigureAwait(false);
125+
}
126+
127+
/// <summary> Get the details of a single connection. </summary>
128+
/// <param name="category"> Category of the workspace connection. </param>
129+
/// <param name="withCredential"></param>
130+
/// <param name="includeAll"> Indicates whether to list datastores. Service default: do not list datastores. </param>
131+
/// <param name="target"> Target of the workspace connection. </param>
132+
/// <param name="cancellationToken"> The cancellation token to use. </param>
133+
internal virtual Response<ConnectionsListSecretsResponse> GetDefaultConnection(ConnectionType category, bool? withCredential = null, bool? includeAll = null, string target = null, CancellationToken cancellationToken = default)
134+
{
135+
ConnectionsListResponse connections = GetConnections(category, includeAll, target, cancellationToken);
136+
137+
if (connections?.Value == null || connections.Value.Count == 0)
138+
{
139+
throw new InvalidOperationException("No connections found for the specified parameters.");
140+
}
141+
142+
var secret = connections.Value[0];
143+
return withCredential.GetValueOrDefault()
144+
? GetSecrets(secret.Name, "ignored")
145+
: GetConnection(secret.Name);
146+
}
147+
148+
// CUSTOM: Fixed the request URI by removing "/agents/v1.0"
149+
internal HttpMessage CreateGetConnectionsRequest(string category, bool? includeAll, string target, RequestContext context)
150+
{
151+
var message = _pipeline.CreateMessage(context, ResponseClassifier200);
152+
var request = message.Request;
153+
request.Method = RequestMethod.Get;
154+
var uri = new RawRequestUriBuilder();
155+
uri.Reset(_endpoint);
156+
uri.AppendRaw("/subscriptions/", false);
157+
uri.AppendRaw(_subscriptionId, true);
158+
uri.AppendRaw("/resourceGroups/", false);
159+
uri.AppendRaw(_resourceGroupName, true);
160+
uri.AppendRaw("/providers/Microsoft.MachineLearningServices/workspaces/", false);
161+
uri.AppendRaw(_projectName, true);
162+
uri.AppendPath("/connections", false);
163+
uri.AppendQuery("api-version", _apiVersion, true);
164+
if (category != null)
165+
{
166+
uri.AppendQuery("category", category, true);
167+
}
168+
if (includeAll != null)
169+
{
170+
uri.AppendQuery("includeAll", includeAll.Value, true);
171+
}
172+
if (target != null)
173+
{
174+
uri.AppendQuery("target", target, true);
175+
}
176+
request.Uri = uri;
177+
request.Headers.Add("Accept", "application/json");
178+
return message;
179+
}
180+
181+
internal HttpMessage CreateGetConnectionRequest(string connectionName, RequestContext context)
182+
{
183+
var message = _pipeline.CreateMessage(context, ResponseClassifier200);
184+
var request = message.Request;
185+
request.Method = RequestMethod.Get;
186+
var uri = new RawRequestUriBuilder();
187+
uri.Reset(_endpoint);
188+
uri.AppendRaw("/subscriptions/", false);
189+
uri.AppendRaw(_subscriptionId, true);
190+
uri.AppendRaw("/resourceGroups/", false);
191+
uri.AppendRaw(_resourceGroupName, true);
192+
uri.AppendRaw("/providers/Microsoft.MachineLearningServices/workspaces/", false);
193+
uri.AppendRaw(_projectName, true);
194+
uri.AppendPath("/connections/", false);
195+
uri.AppendPath(connectionName, true);
196+
uri.AppendQuery("api-version", _apiVersion, true);
197+
request.Uri = uri;
198+
request.Headers.Add("Accept", "application/json");
199+
return message;
200+
}
201+
202+
internal HttpMessage CreateGetSecretsRequest(string connectionName, RequestContent content, RequestContext context)
203+
{
204+
var message = _pipeline.CreateMessage(context, ResponseClassifier200);
205+
var request = message.Request;
206+
request.Method = RequestMethod.Post;
207+
var uri = new RawRequestUriBuilder();
208+
uri.Reset(_endpoint);
209+
uri.AppendRaw("/subscriptions/", false);
210+
uri.AppendRaw(_subscriptionId, true);
211+
uri.AppendRaw("/resourceGroups/", false);
212+
uri.AppendRaw(_resourceGroupName, true);
213+
uri.AppendRaw("/providers/Microsoft.MachineLearningServices/workspaces/", false);
214+
uri.AppendRaw(_projectName, true);
215+
uri.AppendPath("/connections/", false);
216+
uri.AppendPath(connectionName, true);
217+
uri.AppendPath("/listsecrets", false);
218+
uri.AppendQuery("api-version", _apiVersion, true);
219+
request.Uri = uri;
220+
request.Headers.Add("Accept", "application/json");
221+
request.Headers.Add("Content-Type", "application/json");
222+
request.Content = content;
223+
return message;
224+
}
82225
}
83226
}

0 commit comments

Comments
 (0)