Skip to content

Commit f512f54

Browse files
authored
Add the C# function deployment to the readme. (Azure#48121)
* The draft of changes * Recird the tests and add fuction deployment instructions. * Remove unrelated changes * Remove non needed line
1 parent 14d8d34 commit f512f54

File tree

3 files changed

+108
-5
lines changed

3 files changed

+108
-5
lines changed

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

Lines changed: 102 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -541,7 +541,7 @@ await foreach (StreamingUpdate streamingUpdate in client.CreateRunStreamingAsync
541541

542542
#### Azure function call
543543

544-
We also can use Azure Function from inside the agent. In the example below we are calling function "foo", which responds "Bar". In this example we create `AzureFunctionToolDefinition` object, with the function name, description, input and output queues, followed by function parameters.
544+
We also can use Azure Function from inside the agent. In the example below we are calling function "foo", which responds "Bar". In this example we create `AzureFunctionToolDefinition` object, with the function name, description, input and output queues, followed by function parameters. See below for the instructions on function deployment.
545545
```C# Snippet:AzureFunctionsDefineFunctionTools
546546
AzureFunctionToolDefinition azureFnTool = new(
547547
name: "foo",
@@ -616,6 +616,107 @@ while (runResponse.Value.Status == RunStatus.Queued
616616
|| runResponse.Value.Status == RunStatus.RequiresAction);
617617
```
618618

619+
To make a function call we need to create and deploy the Azure function. In the code snippet below, we have an example of function on C# which can be used by the code above.
620+
621+
```C#
622+
namespace FunctionProj
623+
{
624+
public class Response
625+
{
626+
public required string Value { get; set; }
627+
public required string CorrelationId { get; set; }
628+
}
629+
630+
public class Arguments
631+
{
632+
public required string OutputQueueUri { get; set; }
633+
public required string CorrelationId { get; set; }
634+
}
635+
636+
public class Foo
637+
{
638+
private readonly ILogger<Foo> _logger;
639+
640+
public Foo(ILogger<Foo> logger)
641+
{
642+
_logger = logger;
643+
}
644+
645+
[Function("Foo")]
646+
public void Run([QueueTrigger("azure-function-foo-input")] Arguments input, FunctionContext executionContext)
647+
{
648+
var logger = executionContext.GetLogger("Foo");
649+
logger.LogInformation("C# Queue function processed a request.");
650+
651+
// We have to provide the Managed identity for function resource
652+
// and allow this identity a Queue Data Contributor role on the storage account.
653+
var cred = new DefaultAzureCredential();
654+
var queueClient = new QueueClient(new Uri(input.OutputQueueUri), cred,
655+
new QueueClientOptions { MessageEncoding = QueueMessageEncoding.Base64 });
656+
657+
var response = new Response
658+
{
659+
Value = "Bar",
660+
// Important! Correlation ID must match the input correlation ID.
661+
CorrelationId = input.CorrelationId
662+
};
663+
664+
var jsonResponse = JsonSerializer.Serialize(response);
665+
queueClient.SendMessage(jsonResponse);
666+
}
667+
}
668+
}
669+
```
670+
671+
In this code we define function input and output class: `Arguments` and `Response` respectively. These two data classes will be serialized in JSON. It is important that these both contain field `CorrelationId`, which is the same between input and output.
672+
673+
In our example the function will be stored in the storage account, created with the AI hub. For that we need to allow key access to that storage. In Azure portal go to Storage account > Settings > Configuration and set "Allow storage account key access" to Enabled. If it is not done, the error will be displayed "The remote server returned an error: (403) Forbidden." To create the function resource that will host our function, install azure-cli python package and run the next command:
674+
675+
```shell
676+
pip install -U azure-cli
677+
az login
678+
az functionapp create --resource-group your-resource-group --consumption-plan-location region --runtime dotnet-isolated --functions-version 4 --name function_name --storage-account storage_account_already_present_in_resource_group --app-insights existing_or_new_application_insights_name
679+
```
680+
681+
This function writes data to the output queue and hence needs to be authenticated to Azure, so we will need to assign the function system identity and provide it `Storage Queue Data Contributor`. To do that in Azure portal select the function, located in `your-resource-group` resource group and in Settings>Identity, switch it on and click Save. After that assign the `Storage Queue Data Contributor` permission on storage account used by our function (`storage_account_already_present_in_resource_group` in the script above) for just assigned System Managed identity.
682+
683+
Now we will create the function itself. Install [.NET](https://dotnet.microsoft.com/download) and [Core Tools](https://go.microsoft.com/fwlink/?linkid=2174087) and create the function project using next commands.
684+
```
685+
func init FunctionProj --worker-runtime dotnet-isolated --target-framework net8.0
686+
cd FunctionProj
687+
func new --name foo --template "HTTP trigger" --authlevel "anonymous"
688+
dotnet add package Azure.Identity
689+
dotnet add package Microsoft.Azure.Functions.Worker.Extensions.Storage.Queues --prerelease
690+
```
691+
692+
**Note:** There is a "Azure Queue Storage trigger", however the attempt to use it results in error for now.
693+
We have created a project, containing HTTP-triggered azure function with the logic in `Foo.cs` file. As far as we need to trigger Azure function by a new message in the queue, we will replace the content of a Foo.cs by the C# sample code above.
694+
To deploy the function run the command from dotnet project folder:
695+
696+
```
697+
func azure functionapp publish function_name
698+
```
699+
700+
In the `storage_account_already_present_in_resource_group` select the `Queue service` and create two queues: `azure-function-foo-input` and `azure-function-tool-output`. Note that the same queues are used in our sample. To check that the function is working, place the next message into the `azure-function-foo-input` and replace `storage_account_already_present_in_resource_group` by the actual resource group name, or just copy the output queue address.
701+
```json
702+
{
703+
"OutputQueueUri": "https://storage_account_already_present_in_resource_group.queue.core.windows.net/azure-function-tool-output",
704+
"CorrelationId": "42"
705+
}
706+
```
707+
708+
Next, we will monitor the output queue or the message. You should receive the next message.
709+
```json
710+
{
711+
"Value": "Bar",
712+
"CorrelationId": "42"
713+
}
714+
```
715+
Please note that the input `CorrelationId` is the same as output.
716+
*Hint:* Place multiple messages to input queue and keep second internet browser window with the output queue open and hit the refresh button on the portal user interface, so that you will not miss the message. If the message instead went to `azure-function-foo-input-poison` queue, the function completed with error, please check your setup.
717+
After we have tested the function and made sure it works, please make sure that the Azure AI Project have the next roles for the storage account: `Storage Account Contributor`, `Storage Blob Data Contributor`, `Storage File Data Privileged Contributor`, `Storage Queue Data Contributor` and `Storage Table Data Contributor`. Now the function is ready to be used by the agent.
718+
719+
619720
#### Create Agent With OpenAPI
620721

621722
OpenAPI specifications describe REST operations against a specific endpoint. Agents SDK can read an OpenAPI spec, create a function from it, and call that function against the REST endpoint without additional client-side execution.

sdk/ai/Azure.AI.Projects/assets.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22
"AssetsRepo": "Azure/azure-sdk-assets",
33
"AssetsRepoPrefixPath": "net",
44
"TagPrefix": "net/ai/Azure.AI.Projects",
5-
"Tag": "net/ai/Azure.AI.Projects_d15b826ff9"
5+
"Tag": "net/ai/Azure.AI.Projects_c34fc3243d"
66
}

sdk/ai/Azure.AI.Projects/tests/AgentClientTests.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -973,9 +973,10 @@ public async Task TestIncludeFileSearchContent(bool useStream, bool includeConte
973973
}
974974

975975
[RecordedTest]
976-
[Ignore("Azure function call is not supported in all regions yet.")]
977976
public async Task TestAzureFunctionCall()
978977
{
978+
// Note: This test was recorded in westus region as for now
979+
// 2025-02-05 it is not supported in test region (East US 2)
979980
AzureFunctionToolDefinition azureFnTool = new(
980981
name: "foo",
981982
description: "Get answers from the foo bot.",
@@ -1032,9 +1033,10 @@ public async Task TestAzureFunctionCall()
10321033
await WaitForRun(client, run);
10331034
PageableList<ThreadMessage> afterRunMessages = await client.GetMessagesAsync(thread.Id);
10341035

1036+
Assert.Greater(afterRunMessages.Count(), 1);
1037+
bool foundResponse = false;
10351038
foreach (ThreadMessage msg in afterRunMessages)
10361039
{
1037-
bool foundResponse = false;
10381040
foreach (MessageContent contentItem in msg.ContentItems)
10391041
{
10401042
if (contentItem is MessageTextContent textItem)
@@ -1046,8 +1048,8 @@ public async Task TestAzureFunctionCall()
10461048
}
10471049
}
10481050
}
1049-
Assert.True(foundResponse);
10501051
}
1052+
Assert.True(foundResponse);
10511053
}
10521054

10531055
[RecordedTest]

0 commit comments

Comments
 (0)