Skip to content

[BUG] Agent fails when using multiple tool calls (FunctionToolDefinition) #55417

@wmmihaa

Description

@wmmihaa

Library name and version

Azure.AI.Agents.Persistent 1.2.0-beta.8

Describe the bug

When running PersistentAgentsClient.Runs.CreateRunStreamingAsync the call fails if the agent requires more than one tool.

I cannot itterate over the toolOutputUpdate object (AsyncCollectionResult) as it seems to expect results from other tools. But How can I check if there are other tools are required?

Exception:

One or more errors occurred. (Expected tool outputs for call_ids ['call_vqQHEq4zogreMsiNSwx2rYLy', 'call_YSQu19hw6W5ZjmjEkeDyMgu1', 'call_WWqg4ER6YaSBxlGUjVpEoJG1', 'call_ZSxowT9OJbsXWt4nwSdnShbW', 'call_ZnRu2W1gI4meDqPet8vjQqHX', 'call_jRua3SQSbE8fNzFhykrEHExP'], got ['call_vqQHEq4zogreMsiNSwx2rYLy']
Status: 400 (Bad Request)

Content:
{
  "error": {
    "message": "Expected tool outputs for call_ids ['call_vqQHEq4zogreMsiNSwx2rYLy', 'call_YSQu19hw6W5ZjmjEkeDyMgu1', 'call_WWqg4ER6YaSBxlGUjVpEoJG1', 'call_ZSxowT9OJbsXWt4nwSdnShbW', 'call_ZnRu2W1gI4meDqPet8vjQqHX', 'call_jRua3SQSbE8fNzFhykrEHExP'], got ['call_vqQHEq4zogreMsiNSwx2rYLy']",
    "type": "invalid_request_error",
    "param": null,
    "code": null
  }
}

My Code:

// Create thread for communication
PersistentAgentThread thread = _client.Threads.CreateThread();

// Create message to thread
PersistentThreadMessage messageResponse = _client.Messages.CreateMessage(
    thread.Id,
    MessageRole.User,
    prompt);

// Run the Agent
AsyncCollectionResult<StreamingUpdate> streamingUpdate = _client.Runs.CreateRunStreamingAsync(
    threadId: thread.Id,
    agentId: _agent.Id,
    maxCompletionTokens: maxCompletionTokens,
    maxPromptTokens: maxPromptTokens,
    temperature: temperature,
    topP: topP
);

var result = new StringBuilder();
await foreach (StreamingUpdate update in streamingUpdate)
{
    await HandleStreamingUpdateAsync(update, client, result);
}

private async Task HandleStreamingUpdateAsync(StreamingUpdate update, ISingleClientProxy client, StringBuilder result)
{
    switch (update.UpdateKind)
    {
        case StreamingUpdateReason.RunRequiresAction:
            // The run requires an action from the application, such as a tool output submission.
            // This is where the application can handle the action.
            _logger.LogInformation("Run requires action.");
            RequiredActionUpdate requiredActionUpdate = (RequiredActionUpdate)update;
            await HandleActionAsync(requiredActionUpdate, client, result);
            break;

        case StreamingUpdateReason.MessageUpdated:
            // ...
            break;

        case StreamingUpdateReason.MessageCompleted:
            // ...
            break;

        case StreamingUpdateReason.RunCompleted:
            // ...
            break;

        case StreamingUpdateReason.RunFailed:
            // ...
            break;
    }
}

private async Task HandleActionAsync(RequiredActionUpdate requiredActionUpdate, ISingleClientProxy client, StringBuilder result)
{
    try
    {
        AsyncCollectionResult<StreamingUpdate> toolOutputUpdate = null;

        if (requiredActionUpdate.FunctionName == GreenEdgeDataHelper.GetModelData_ToolName)
        {
            JsonSerializerOptions options = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase };
            GetModelDataArgs args = JsonSerializer.Deserialize<GetModelDataArgs>(requiredActionUpdate.FunctionArguments, options) ?? throw new InvalidOperationException("Failed to parse JSON object.");
            
            var json = await _greenEdgeDataHelper.GetModelDataAsync(args.Query);
            
            toolOutputUpdate = _client.Runs.SubmitToolOutputsToStreamAsync(
                requiredActionUpdate.Value,
                new List<ToolOutput>([new ToolOutput(requiredActionUpdate.ToolCallId, json)])
            );
        }
        else if (requiredActionUpdate.FunctionName == GreenEdgeDataHelper.GetEmissionData_ToolName)
        {
            JsonSerializerOptions options = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase };
            GetModelDataArgs args = JsonSerializer.Deserialize<GetModelDataArgs>(requiredActionUpdate.FunctionArguments, options) ?? throw new InvalidOperationException("Failed to parse JSON object.");

            var json = await _greenEdgeDataHelper.GetCosmosDataAsync(args.Query);
            
            toolOutputUpdate = _client.Runs.SubmitToolOutputsToStreamAsync(
                requiredActionUpdate.Value,
                new List<ToolOutput>([new ToolOutput(requiredActionUpdate.ToolCallId, json)])
            );
        }
        else
        {
            _logger.LogWarning($"Unknown tool requested: {requiredActionUpdate.FunctionName}");
        }

        await foreach (StreamingUpdate toolUpdate in toolOutputUpdate) // This is where it fails
        {
            await HandleStreamingUpdateAsync(toolUpdate, client, result);
        }
    }
    catch (Exception ex)
    {

        throw;
    }
}

Thank you for any help

Expected behavior

To somehow check it all tools have been processed

Actual behavior

Today I'm just calling my backend services when I receive a StreamingUpdate and the UpdateKind is StreamingUpdateReason.RunRequiresAction

Reproduction Steps

See code

Environment

 dotnet --info
.NET SDK:
 Version:           10.0.100
 Commit:            b0f34d51fc
 Workload version:  10.0.100-manifests.355811b7
 MSBuild version:   18.0.2+b0f34d51f

Runtime Environment:
 OS Name:     Windows
 OS Version:  10.0.26200
 OS Platform: Windows
 RID:         win-arm64
 Base Path:   C:\Program Files\dotnet\sdk\10.0.100\

Metadata

Metadata

Assignees

No one assigned

    Labels

    AI AgentsClientThis issue is related to a non-management packageService AttentionWorkflow: This issue is responsible by Azure service team.customer-reportedIssues that are reported by GitHub users external to the Azure organization.needs-team-attentionWorkflow: This issue needs attention from Azure service team or SDK teamquestionThe issue doesn't require a change to the product in order to be resolved. Most issues start as that

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions