Skip to content

[API Proposal]: FunctionInvocationContext.IterationState #7196

@verdie-g

Description

@verdie-g

Background and motivation

I have several use-cases where I would like to keep a state for the duration of a FunctionInvokingChatClient iteration.

Retries

FunctionInvokingChatClient.FunctionInvoker can be used to implement a retry mechanism but without an easy access to a state object, it's hard to keep track of how many retries were already performed.

Caching

One tool can return user data X and another tool data Y for the same user. Both tools need to fetch the same data which contains X and Y. X and Y are not returned by the same tool to avoid unnecessarily flood the context. Though, when both tools are called in the same iteration I'd like to fetch the data once. The iteration state could help with that.

API Proposal

namespace System.Collections.Generic;

public class FunctionInvocationContext
{
    public IDictionary<object,object?> IterationState { get; }
}

With FunctionInvokingChatClient.AllowConcurrentInvocation=true, we might need to deal with concurrent accesses. Should it be ConcurrentDictionary?

API Usage

In this example, tools can throw a custom AIFunctionRetryException to indicate to the LLM to retry the call. If too many retries are performed, a different message is sent to the LLM to try to stop the loop.

chatClient = new FunctionInvokingChatClient(chatClient)
{
    FunctionInvoker = async (ctx, ct) =>
    {
        try
        {
            return await ctx.Function.InvokeAsync(ctx.Arguments, ct);
        }
        catch (AIFunctionRetryException e)
        {
            string retryKey = $"retry.{ctx.Function.Name}";
            int retries;
            if (ctx.IterationState.TryGetValue(retryKey, out object? retriesObj))
            {
                retries = (int)retriesObj!;
            }
            else
            {
                retries = 1;
                ctx.IterationState[retryKey] = retries;
            }

            if (retries > 3)
            {
                return "Stop or something";
            }

            return $"""
                {e.Message}

                Fix the errors and try again.
                """;
        }
    },
};

Alternative Designs

DI Iteration Scope

Alternatively, if there was a DI scope created for the iteration, I could create a DI service by iteration. Not super familiar with DI scopes so I'm not sure that's possible.

AsyncLocal

The user could probably still create its own AsyncLocal but in my experience it's a feature that it beyond most C# developers.

Risks

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    api-suggestionEarly API idea and discussion, it is NOT ready for implementationarea-aiMicrosoft.Extensions.AI librariesuntriaged

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions