Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable AIFunctionFactory to resolve parameters from an IServiceProvider #5956

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

stephentoub
Copy link
Member

@stephentoub stephentoub commented Feb 22, 2025

In a discussion last week, @eiriktsarpalis suggested that sooner or later we'll want to support AIFunctionFactory being able to populate parameters from DI. The second commit here is how I think we could do that. (Ignore the first commit, it's just #5954.) What do we think? Is this something we'd want to proceed with, or if not, are we comfortable with this as a future solution, in which case this demonstrates it's possible without breakage?

Effectively, it enables someone to write:

AIFunctionFactory.Create(([FromServiceProvider] WhateverType wt = null) =>
{
    ...
});

ala what you can do with MVC and SK and others. When constructing a FunctionInvokingChatClient, it'll hold onto whatever IServiceProvider was used to construct it, and then feed that into AIFunction.InvokeAsync via an AIFunctionArguments type that AIFunctionFactory type checks for.

cc: @eiriktsarpalis, @SteveSandersonMS, @halter73

Microsoft Reviewers: Open in CodeFlow

@dotnet-comment-bot
Copy link
Collaborator

‼️ Found issues ‼️

Project Coverage Type Expected Actual
Microsoft.Extensions.AI Line 89 88.27 🔻

🎉 Good job! The coverage increased 🎉
Update MinCodeCoverage in the project files.

Project Expected Actual
Microsoft.Extensions.AI.Abstractions 82 83
Microsoft.Gen.MetadataExtractor 57 70

Full code coverage report: https://dev.azure.com/dnceng-public/public/_build/results?buildId=960014&view=codecoverage-tab

@stephentoub stephentoub force-pushed the fromserviceproviderattribute branch 2 times, most recently from 170d874 to 31a6da0 Compare February 25, 2025 15:17
@stephentoub stephentoub force-pushed the fromserviceproviderattribute branch from 31a6da0 to 0f43eed Compare February 26, 2025 02:34
{
if (fspAttr.ServiceKey is object serviceKey)
if ((arguments as AIFunctionArguments)?.ServiceProvider is IServiceProvider services)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The need to use a magic dictionary type makes me sad, but at the same time I can't think of a better solution that addresses our current dependency constraints.

Making it optional does seem like a pit of failure for library authors whose test coverage doesn't include DI scenaria. Should we consider requiring that input dictionaries are always of this type regardless?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One possibility I considered is using a dependency inversion scheme where the InvokeAsync method accepts a Func<Type, object> either directly or through an "AIFunctionInvocationContext" type that contains it as a property.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

consider requiring that input dictionaries are always of this type regardless?

I'd rather not, both because it limits what input types are possible but also because it makes the IServiceProvider a prominent thing that's part of the base abstraction, when it's not really.

Making it optional does seem like a pit of failure for library authors whose test coverage doesn't include DI scenaria. Should we consider requiring that input dictionaries are always of this type regardless?

I wouldn't expect this to be an issue for most library authors. What are these libraries doing? The only component that would need to instantiate this type to flow additional data through would be something that's invoking arbitrary AIFunction instances, ala FunctionInvokingChatClient.

I agree it's not ideal. I just don't have a better answer that keeps AIFunction.InvokeAsync simple and clean while still allowing for other ambient context to be passed through. If we want to change InvokeAsync now, before it's stable, options would include changing the type of the argument, adding an additional dictionary argument for ambient context in addition to named arguments, adding some kind of callback argument that the AIFunction could use to retrieve the value for an argument, e.g. Func<string, object?>? argumentFactory (where we establish a search order between the dictionary and the callback), etc. I'm not sure any of those is better, though I'm open to arguments (no pun intended).

@stephentoub stephentoub force-pushed the fromserviceproviderattribute branch from 0f43eed to c9b580a Compare February 26, 2025 16:57
@stephentoub stephentoub force-pushed the fromserviceproviderattribute branch from c9b580a to 08c4d4b Compare March 1, 2025 04:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants