Repository navigation
Some issues related to support for an Agent declarative format #10237
Copy link
Copy link
Closed
Labels
.NETIssue or Pull requests regarding .NET codeIssue or Pull requests regarding .NET codeBuildFeatures planned for next Build conferenceFeatures planned for next Build conferenceagentsexperimentalAssociated with an experimental featureAssociated with an experimental featurequestionFurther information is requestedFurther information is requestedsk team issueA tag to denote issues that where created by the Semantic Kernel team (i.e., not the community)A tag to denote issues that where created by the Semantic Kernel team (i.e., not the community)staleIssue is stale because it has been open for a while and has no activityIssue is stale because it has been open for a while and has no activity
Description
1. Use KernelFunction return value
The default KernelFunctionTerminationStrategy.ResultParser is:
public Func<FunctionResult, bool> ResultParser { get; init; } = (_) => true;
If the KernelFunction returns false then this will override this return value and cause termination.
Why doesn't the default implementation change the function result?
2. Pass Agent and ChatHistory in KernelArguments
Consider this code
protected sealed override async Task<bool> ShouldAgentTerminateAsync(Agent agent, IReadOnlyList<ChatMessageContent> history, CancellationToken cancellationToken = default)
{
history = await history.ReduceAsync(this.HistoryReducer, cancellationToken).ConfigureAwait(false);
KernelArguments originalArguments = this.Arguments ?? [];
KernelArguments arguments =
new(originalArguments, originalArguments.ExecutionSettings?.ToDictionary(kvp => kvp.Key, kvp => kvp.Value))
{
{ this.AgentVariableName, agent.Name ?? agent.Id },
{ this.HistoryVariableName, ChatMessageForPrompt.Format(history, this.EvaluateNameOnly) },
};
this.Logger.LogKernelFunctionTerminationStrategyInvokingFunction(nameof(ShouldAgentTerminateAsync), this.Function.PluginName, this.Function.Name);
FunctionResult result = await this.Function.InvokeAsync(this.Kernel, arguments, cancellationToken).ConfigureAwait(false);
this.Logger.LogKernelFunctionTerminationStrategyInvokedFunction(nameof(ShouldAgentTerminateAsync), this.Function.PluginName, this.Function.Name, result.ValueType);
return this.ResultParser.Invoke(result);
}It would be more convenient to:
- Pass
Agentinstance rather than the name or id - Pass the
ChatHistoryrather than a string (which cannot be parsed back into a ChatHistory - Allow for
KernelFunctionswhich has a signature which takes typedAgentandChatHistoryparameters
3. Should we have a standard way to Invoke an Agent
When we support declarative agents the follow will look something like this
- Load the
Agentinstance from a file which contains a declarative definition of anAgent - Invoke the
Agentinstance optionally using a provided input and other arguments - Capture the result which should contain everything needed to invoke the
Agentinstance again and maintain context
Here's some psuedo code:
Kernel kernel = ...
string agentYaml = EmbeddedResource.Read("MyAgent.yaml");
AgentFactory agentFactory = new AggregatorAgentFactory(
new ChatCompletionFactory(),
new OpenAIAssistantAgentFactory(),
new XXXAgentFactory());
Agent agent = kernel.LoadAgentFromYaml(agentYaml); // What return type should we use to standardise?
// Should we have a unified pattern here?
ChatHistory chatHistory = new();
chatHistory.AddUserMessage(input);
await foreach (ChatMessageContent content in agent.InvokeAsync(chatHistory))
{
chatHistory.Add(content);
}At the moment the code to "Invoke" a ChatCompletionAgent versus a OpenAIAssistantAgent is different. Should we have an abstraction in the Agent framework that allows us to standardise?
Metadata
Metadata
Assignees
Labels
.NETIssue or Pull requests regarding .NET codeIssue or Pull requests regarding .NET codeBuildFeatures planned for next Build conferenceFeatures planned for next Build conferenceagentsexperimentalAssociated with an experimental featureAssociated with an experimental featurequestionFurther information is requestedFurther information is requestedsk team issueA tag to denote issues that where created by the Semantic Kernel team (i.e., not the community)A tag to denote issues that where created by the Semantic Kernel team (i.e., not the community)staleIssue is stale because it has been open for a while and has no activityIssue is stale because it has been open for a while and has no activity
Type
Projects
- StatusShow more project fieldsNo status
Activity
[-]Why does the default KernelFunctionTerminationStrategy.ResultParser causes termination?[/-][+].Net: Why does the default KernelFunctionTerminationStrategy.ResultParser causes termination?[/+][-].Net: Why does the default KernelFunctionTerminationStrategy.ResultParser causes termination?[/-][+]Changes to KernelFunctionTerminationStrategy.ResultParser [/+]crickman commentedon Jan 21, 2025
For the initial question (#1), I believe the original thinking was to avoid infinite chat when a developer failed to provide parser. One option is that the default implementation could be
nulland then raise aKernelExceptionif not defined. If I remember correctly, at the time you were keen to avoidnulldefaults.In response to your question: Why doesn't the default implementation change the function result?
The termination function could provide any result and isn't bound to forcing the model to respond in any particular fashion. (For example, I personally like using structured output and then parsing the JSON result.) Since there is insufficient visibility for the base implementation to evaluate the result, the default posture is currently to avoid a non-terminating chat (with the most conservative approach - always terminate).
As we're having this discussion, I do kind've like the approach of not having any default implemenation and raising an actionable error when no-result parser is defined. Thoughts?
[-]Changes to KernelFunctionTerminationStrategy.ResultParser [/-][+]Some issues related to support for an Agent declarative format[/+]markwallace-microsoft commentedon Jan 22, 2025
Going back a little.
TerminationStrategyhas the following abstract methodprotected abstract Task<bool> ShouldAgentTerminateAsync(Agent agent, IReadOnlyList<ChatMessageContent> history, CancellationToken cancellationToken);.So if I am creating a
KernelFunctionto implement aTerminationStrategyI think we want to support two cases:It is option #2 which I am looking at.
Note: We should consider changing the
TerminationStrategyabstract method toprotected abstract Task<bool> ShouldAgentTerminateAsync(Agent agent, ChatHistory chatHistory, CancellationToken cancellationToken);.ChatHistoryis already a list of messages but has the added advantage of providing extensibility e.g. we could add aMetadataproperty in the future if needed.crickman commentedon Jan 22, 2025
KernelFunctionTerminationStrategy was originally designed for use with a prompt function only. The
Functionparameter is declared asKernelFunctionsince that is the only public type available.crickman commentedon Feb 13, 2025
Given our trajectory w.r.t. the current agent-chat and autogen alignment, we'll be addressing multi-agent interaction in an entirely new context. For declarative agents, I'd suggest to exclude the current
AgentChatandAgentGroupChatfrom consideration.crickman commentedon Feb 13, 2025
Also, it may be best to break this into separate issues that each address one aspect, as the resolution for different aspects may vary. I do appreciate the dialog we've been able to have up to this point, however.
github-actions commentedon Apr 23, 2025
This issue was closed because it has been inactive for 14 days since being marked as stale.