mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Merge branch 'main' into feature-session-statebag
This commit is contained in:
@@ -53,7 +53,7 @@ Still have questions? Join our [weekly office hours](./COMMUNITY.md#public-commu
|
||||
### ✨ **Highlights**
|
||||
|
||||
- **Graph-based Workflows**: Connect agents and deterministic functions using data flows with streaming, checkpointing, human-in-the-loop, and time-travel capabilities
|
||||
- [Python workflows](./python/samples/getting_started/workflows/) | [.NET workflows](./dotnet/samples/GettingStarted/Workflows/)
|
||||
- [Python workflows](./python/samples/03-workflows/) | [.NET workflows](./dotnet/samples/GettingStarted/Workflows/)
|
||||
- **AF Labs**: Experimental packages for cutting-edge features including benchmarking, reinforcement learning, and research initiatives
|
||||
- [Labs directory](./python/packages/lab/)
|
||||
- **DevUI**: Interactive developer UI for agent development, testing, and debugging workflows
|
||||
@@ -73,11 +73,11 @@ Still have questions? Join our [weekly office hours](./COMMUNITY.md#public-commu
|
||||
- **Python and C#/.NET Support**: Full framework support for both Python and C#/.NET implementations with consistent APIs
|
||||
- [Python packages](./python/packages/) | [.NET source](./dotnet/src/)
|
||||
- **Observability**: Built-in OpenTelemetry integration for distributed tracing, monitoring, and debugging
|
||||
- [Python observability](./python/samples/getting_started/observability/) | [.NET telemetry](./dotnet/samples/GettingStarted/AgentOpenTelemetry/)
|
||||
- [Python observability](./python/samples/02-agents/observability/) | [.NET telemetry](./dotnet/samples/GettingStarted/AgentOpenTelemetry/)
|
||||
- **Multiple Agent Provider Support**: Support for various LLM providers with more being added continuously
|
||||
- [Python examples](./python/samples/getting_started/agents/) | [.NET examples](./dotnet/samples/GettingStarted/AgentProviders/)
|
||||
- [Python examples](./python/samples/02-agents/providers/) | [.NET examples](./dotnet/samples/GettingStarted/AgentProviders/)
|
||||
- **Middleware**: Flexible middleware system for request/response processing, exception handling, and custom pipelines
|
||||
- [Python middleware](./python/samples/getting_started/middleware/) | [.NET middleware](./dotnet/samples/GettingStarted/Agents/Agent_Step14_Middleware/)
|
||||
- [Python middleware](./python/samples/02-agents/middleware/) | [.NET middleware](./dotnet/samples/GettingStarted/Agents/Agent_Step14_Middleware/)
|
||||
|
||||
### 💬 **We want your feedback!**
|
||||
|
||||
@@ -159,9 +159,9 @@ Console.WriteLine(await agent.RunAsync("Write a haiku about Microsoft Agent Fram
|
||||
|
||||
### Python
|
||||
|
||||
- [Getting Started with Agents](./python/samples/getting_started/agents): basic agent creation and tool usage
|
||||
- [Chat Client Examples](./python/samples/getting_started/chat_client): direct chat client usage patterns
|
||||
- [Getting Started with Workflows](./python/samples/getting_started/workflows): basic workflow creation and integration with agents
|
||||
- [Getting Started with Agents](./python/samples/01-get-started): progressive tutorial from hello-world to hosting
|
||||
- [Agent Concepts](./python/samples/02-agents): deep-dive samples by topic (tools, middleware, providers, etc.)
|
||||
- [Getting Started with Workflows](./python/samples/03-workflows): workflow creation and integration with agents
|
||||
|
||||
### .NET
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
# Declarative Agents
|
||||
|
||||
This folder contains sample agent definitions that can be run using the declarative agent support, for python see the [declarative agent python sample folder](../python/samples/getting_started/declarative/).
|
||||
This folder contains sample agent definitions that can be run using the declarative agent support, for python see the [declarative agent python sample folder](../python/samples/02-agents/declarative/).
|
||||
|
||||
@@ -126,4 +126,4 @@ response = await client.get_response(
|
||||
|
||||
Chosen option: **"Option 2: TypedDict with Generic Type Parameters"**, because it provides full type safety, excellent IDE support with autocompletion, and allows users to extend provider-specific options for their use cases. Extended this Generic to ChatAgents in order to also properly type the options used in agent construction and run methods.
|
||||
|
||||
See [typed_options.py](../../python/samples/concepts/typed_options.py) for a complete example demonstrating the usage of typed options with custom extensions.
|
||||
See [typed_options.py](../../python/samples/02-agents/typed_options.py) for a complete example demonstrating the usage of typed options with custom extensions.
|
||||
|
||||
@@ -89,7 +89,7 @@
|
||||
<PackageVersion Include="Microsoft.SemanticKernel.Agents.AzureAI" Version="1.67.0-preview" />
|
||||
<PackageVersion Include="Microsoft.SemanticKernel.Plugins.OpenApi" Version="1.67.0" />
|
||||
<!-- Agent SDKs -->
|
||||
<PackageVersion Include="GitHub.Copilot.SDK" Version="0.1.18" />
|
||||
<PackageVersion Include="GitHub.Copilot.SDK" Version="0.1.23" />
|
||||
<PackageVersion Include="Microsoft.Agents.CopilotStudio.Client" Version="1.3.171-beta" />
|
||||
<!-- M365 Agents SDK -->
|
||||
<PackageVersion Include="AdaptiveCards" Version="3.1.0" />
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
<PropertyGroup>
|
||||
<!-- Central version prefix - applies to all nuget packages. -->
|
||||
<VersionPrefix>1.0.0</VersionPrefix>
|
||||
<PackageVersion Condition="'$(VersionSuffix)' != ''">$(VersionPrefix)-$(VersionSuffix).260209.1</PackageVersion>
|
||||
<PackageVersion Condition="'$(VersionSuffix)' == ''">$(VersionPrefix)-preview.260209.1</PackageVersion>
|
||||
<GitTag>1.0.0-preview.260209.1</GitTag>
|
||||
<PackageVersion Condition="'$(VersionSuffix)' != ''">$(VersionPrefix)-$(VersionSuffix).260212.1</PackageVersion>
|
||||
<PackageVersion Condition="'$(VersionSuffix)' == ''">$(VersionPrefix)-preview.260212.1</PackageVersion>
|
||||
<GitTag>1.0.0-preview.260212.1</GitTag>
|
||||
|
||||
<Configurations>Debug;Release;Publish</Configurations>
|
||||
<IsPackable>true</IsPackable>
|
||||
|
||||
@@ -147,21 +147,7 @@ public sealed class GitHubCopilotAgent : AIAgent, IAsyncDisposable
|
||||
|
||||
// Create or resume a session with streaming enabled
|
||||
SessionConfig sessionConfig = this._sessionConfig != null
|
||||
? new SessionConfig
|
||||
{
|
||||
Model = this._sessionConfig.Model,
|
||||
Tools = this._sessionConfig.Tools,
|
||||
SystemMessage = this._sessionConfig.SystemMessage,
|
||||
AvailableTools = this._sessionConfig.AvailableTools,
|
||||
ExcludedTools = this._sessionConfig.ExcludedTools,
|
||||
Provider = this._sessionConfig.Provider,
|
||||
OnPermissionRequest = this._sessionConfig.OnPermissionRequest,
|
||||
McpServers = this._sessionConfig.McpServers,
|
||||
CustomAgents = this._sessionConfig.CustomAgents,
|
||||
SkillDirectories = this._sessionConfig.SkillDirectories,
|
||||
DisabledSkills = this._sessionConfig.DisabledSkills,
|
||||
Streaming = true
|
||||
}
|
||||
? CopySessionConfig(this._sessionConfig)
|
||||
: new SessionConfig { Streaming = true };
|
||||
|
||||
CopilotSession copilotSession;
|
||||
@@ -283,16 +269,64 @@ public sealed class GitHubCopilotAgent : AIAgent, IAsyncDisposable
|
||||
}
|
||||
|
||||
private ResumeSessionConfig CreateResumeConfig()
|
||||
{
|
||||
return CopyResumeSessionConfig(this._sessionConfig);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies all supported properties from a source <see cref="SessionConfig"/> into a new instance
|
||||
/// with <see cref="SessionConfig.Streaming"/> set to <c>true</c>.
|
||||
/// </summary>
|
||||
internal static SessionConfig CopySessionConfig(SessionConfig source)
|
||||
{
|
||||
return new SessionConfig
|
||||
{
|
||||
Model = source.Model,
|
||||
ReasoningEffort = source.ReasoningEffort,
|
||||
Tools = source.Tools,
|
||||
SystemMessage = source.SystemMessage,
|
||||
AvailableTools = source.AvailableTools,
|
||||
ExcludedTools = source.ExcludedTools,
|
||||
Provider = source.Provider,
|
||||
OnPermissionRequest = source.OnPermissionRequest,
|
||||
OnUserInputRequest = source.OnUserInputRequest,
|
||||
Hooks = source.Hooks,
|
||||
WorkingDirectory = source.WorkingDirectory,
|
||||
ConfigDir = source.ConfigDir,
|
||||
McpServers = source.McpServers,
|
||||
CustomAgents = source.CustomAgents,
|
||||
SkillDirectories = source.SkillDirectories,
|
||||
DisabledSkills = source.DisabledSkills,
|
||||
InfiniteSessions = source.InfiniteSessions,
|
||||
Streaming = true
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies all supported properties from a source <see cref="SessionConfig"/> into a new
|
||||
/// <see cref="ResumeSessionConfig"/> with <see cref="ResumeSessionConfig.Streaming"/> set to <c>true</c>.
|
||||
/// </summary>
|
||||
internal static ResumeSessionConfig CopyResumeSessionConfig(SessionConfig? source)
|
||||
{
|
||||
return new ResumeSessionConfig
|
||||
{
|
||||
Tools = this._sessionConfig?.Tools,
|
||||
Provider = this._sessionConfig?.Provider,
|
||||
OnPermissionRequest = this._sessionConfig?.OnPermissionRequest,
|
||||
McpServers = this._sessionConfig?.McpServers,
|
||||
CustomAgents = this._sessionConfig?.CustomAgents,
|
||||
SkillDirectories = this._sessionConfig?.SkillDirectories,
|
||||
DisabledSkills = this._sessionConfig?.DisabledSkills,
|
||||
Model = source?.Model,
|
||||
ReasoningEffort = source?.ReasoningEffort,
|
||||
Tools = source?.Tools,
|
||||
SystemMessage = source?.SystemMessage,
|
||||
AvailableTools = source?.AvailableTools,
|
||||
ExcludedTools = source?.ExcludedTools,
|
||||
Provider = source?.Provider,
|
||||
OnPermissionRequest = source?.OnPermissionRequest,
|
||||
OnUserInputRequest = source?.OnUserInputRequest,
|
||||
Hooks = source?.Hooks,
|
||||
WorkingDirectory = source?.WorkingDirectory,
|
||||
ConfigDir = source?.ConfigDir,
|
||||
McpServers = source?.McpServers,
|
||||
CustomAgents = source?.CustomAgents,
|
||||
SkillDirectories = source?.SkillDirectories,
|
||||
DisabledSkills = source?.DisabledSkills,
|
||||
InfiniteSessions = source?.InfiniteSessions,
|
||||
Streaming = true
|
||||
};
|
||||
}
|
||||
@@ -427,9 +461,8 @@ public sealed class GitHubCopilotAgent : AIAgent, IAsyncDisposable
|
||||
string tempFilePath = await dataContent.SaveToAsync(tempDir, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
attachments ??= [];
|
||||
attachments.Add(new UserMessageDataAttachmentsItem
|
||||
attachments.Add(new UserMessageDataAttachmentsItemFile
|
||||
{
|
||||
Type = UserMessageDataAttachmentsItemType.File,
|
||||
Path = tempFilePath,
|
||||
DisplayName = Path.GetFileName(tempFilePath)
|
||||
});
|
||||
|
||||
+4
@@ -17,6 +17,10 @@
|
||||
<ProjectReference Include="..\Microsoft.Agents.AI.Abstractions\Microsoft.Agents.AI.Abstractions.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<InternalsVisibleTo Include="Microsoft.Agents.AI.GitHub.Copilot.UnitTests" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="GitHub.Copilot.SDK" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -65,7 +65,7 @@ namespace Microsoft.Agents.AI.Workflows.Declarative.CodeGen
|
||||
this._values = [evaluatedValue];
|
||||
}
|
||||
|
||||
await this.ResetAsync(context, null, cancellationToken).ConfigureAwait(false);
|
||||
await this.ResetAsync(context, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return default;
|
||||
}
|
||||
@@ -84,9 +84,19 @@ namespace Microsoft.Agents.AI.Workflows.Declarative.CodeGen
|
||||
AssignVariable(this.Index, "this._index", tightFormat: true);
|
||||
}
|
||||
|
||||
this.Write("\n\n this._index++;\n }\n }\n\n public async ValueTask ResetAsy" +
|
||||
"nc(IWorkflowContext context, object? _, CancellationToken cancellationToken)\n " +
|
||||
" {");
|
||||
this.Write(@"
|
||||
|
||||
this._index++;
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask CompleteAsync(IWorkflowContext context, object? _, CancellationToken cancellationToken)
|
||||
{
|
||||
await this.ResetAsync(context, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async ValueTask ResetAsync(IWorkflowContext context, CancellationToken cancellationToken)
|
||||
{");
|
||||
|
||||
AssignVariable(this.Value, "UnassignedValue.Instance", tightFormat: true);
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ internal sealed class <#= this.Name #>Executor(FormulaSession session) : ActionE
|
||||
this._values = [evaluatedValue];
|
||||
}
|
||||
|
||||
await this.ResetAsync(context, null, cancellationToken).ConfigureAwait(false);
|
||||
await this.ResetAsync(context, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return default;
|
||||
}
|
||||
@@ -59,7 +59,12 @@ internal sealed class <#= this.Name #>Executor(FormulaSession session) : ActionE
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask ResetAsync(IWorkflowContext context, object? _, CancellationToken cancellationToken)
|
||||
public async ValueTask CompleteAsync(IWorkflowContext context, object? _, CancellationToken cancellationToken)
|
||||
{
|
||||
await this.ResetAsync(context, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async ValueTask ResetAsync(IWorkflowContext context, CancellationToken cancellationToken)
|
||||
{<#
|
||||
AssignVariable(this.Value, "UnassignedValue.Instance", tightFormat: true);
|
||||
|
||||
|
||||
+1
-1
@@ -201,7 +201,7 @@ internal sealed class WorkflowActionVisitor : DialogActionVisitor
|
||||
{
|
||||
// Transition to end of inner actions
|
||||
string endActionsId = ForeachExecutor.Steps.End(action.Id);
|
||||
this.ContinueWith(new DelegateActionExecutor(endActionsId, this._workflowState, action.ResetAsync), action.Id);
|
||||
this.ContinueWith(new DelegateActionExecutor(endActionsId, this._workflowState, action.CompleteAsync), action.Id);
|
||||
// Transition to select the next item
|
||||
this._workflowModel.AddLink(endActionsId, loopId);
|
||||
}
|
||||
|
||||
+1
-1
@@ -163,7 +163,7 @@ internal sealed class WorkflowTemplateVisitor : DialogActionVisitor
|
||||
{
|
||||
// Transition to end of inner actions
|
||||
string endActionsId = ForeachExecutor.Steps.End(action.Id); // Loop continuation
|
||||
this.ContinueWith(new EmptyTemplate(endActionsId, this._rootId, $"{action.Id.FormatName()}.{nameof(ForeachExecutor.ResetAsync)}"), action.Id);
|
||||
this.ContinueWith(new EmptyTemplate(endActionsId, this._rootId, $"{action.Id.FormatName()}.{nameof(ForeachExecutor.CompleteAsync)}"), action.Id);
|
||||
// Transition to select the next item
|
||||
this._workflowModel.AddLink(endActionsId, loopId);
|
||||
}
|
||||
|
||||
+18
-19
@@ -37,27 +37,21 @@ internal sealed class ForeachExecutor : DeclarativeActionExecutor<Foreach>
|
||||
|
||||
protected override async ValueTask<object?> ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
Throw.IfNull(this.Model.Items, $"{nameof(this.Model)}.{nameof(this.Model.Items)}");
|
||||
|
||||
this._index = 0;
|
||||
|
||||
if (this.Model.Items is null)
|
||||
EvaluationResult<DataValue> expressionResult = this.Evaluator.GetValue(this.Model.Items);
|
||||
if (expressionResult.Value is TableDataValue tableValue)
|
||||
{
|
||||
this._values = [];
|
||||
this.HasValue = false;
|
||||
this._values = [.. tableValue.Values.Select(value => value.Properties.Values.First().ToFormula())];
|
||||
}
|
||||
else
|
||||
{
|
||||
EvaluationResult<DataValue> expressionResult = this.Evaluator.GetValue(this.Model.Items);
|
||||
if (expressionResult.Value is TableDataValue tableValue)
|
||||
{
|
||||
this._values = [.. tableValue.Values.Select(value => value.Properties.Values.First().ToFormula())];
|
||||
}
|
||||
else
|
||||
{
|
||||
this._values = [expressionResult.Value.ToFormula()];
|
||||
}
|
||||
this._values = [expressionResult.Value.ToFormula()];
|
||||
}
|
||||
|
||||
await this.ResetAsync(context, null, cancellationToken).ConfigureAwait(false);
|
||||
await this.ResetStateAsync(context, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return default;
|
||||
}
|
||||
@@ -79,19 +73,24 @@ internal sealed class ForeachExecutor : DeclarativeActionExecutor<Foreach>
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask ResetAsync(IWorkflowContext context, object? _, CancellationToken cancellationToken)
|
||||
public async ValueTask CompleteAsync(IWorkflowContext context, object? _, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
await context.QueueStateResetAsync(Throw.IfNull(this.Model.Value), cancellationToken).ConfigureAwait(false);
|
||||
if (this.Model.Index is not null)
|
||||
{
|
||||
await context.QueueStateResetAsync(this.Model.Index, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
await this.ResetStateAsync(context, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
await context.RaiseCompletionEventAsync(this.Model, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ResetStateAsync(IWorkflowContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
await context.QueueStateResetAsync(Throw.IfNull(this.Model.Value), cancellationToken).ConfigureAwait(false);
|
||||
if (this.Model.Index is not null)
|
||||
{
|
||||
await context.QueueStateResetAsync(this.Model.Index, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,4 +100,125 @@ public sealed class GitHubCopilotAgentTests
|
||||
Assert.NotNull(agent);
|
||||
Assert.NotNull(agent.Id);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CopySessionConfig_CopiesAllProperties()
|
||||
{
|
||||
// Arrange
|
||||
List<AIFunction> tools = [AIFunctionFactory.Create(() => "test", "TestFunc", "Test function")];
|
||||
var hooks = new SessionHooks();
|
||||
var infiniteSessions = new InfiniteSessionConfig();
|
||||
var systemMessage = new SystemMessageConfig { Mode = SystemMessageMode.Append, Content = "Be helpful" };
|
||||
PermissionHandler permissionHandler = (_, _) => Task.FromResult(new PermissionRequestResult());
|
||||
UserInputHandler userInputHandler = (_, _) => Task.FromResult(new UserInputResponse { Answer = "input" });
|
||||
var mcpServers = new Dictionary<string, object> { ["server1"] = new McpLocalServerConfig() };
|
||||
|
||||
var source = new SessionConfig
|
||||
{
|
||||
Model = "gpt-4o",
|
||||
ReasoningEffort = "high",
|
||||
Tools = tools,
|
||||
SystemMessage = systemMessage,
|
||||
AvailableTools = ["tool1", "tool2"],
|
||||
ExcludedTools = ["tool3"],
|
||||
WorkingDirectory = "/workspace",
|
||||
ConfigDir = "/config",
|
||||
Hooks = hooks,
|
||||
InfiniteSessions = infiniteSessions,
|
||||
OnPermissionRequest = permissionHandler,
|
||||
OnUserInputRequest = userInputHandler,
|
||||
McpServers = mcpServers,
|
||||
DisabledSkills = ["skill1"],
|
||||
};
|
||||
|
||||
// Act
|
||||
SessionConfig result = GitHubCopilotAgent.CopySessionConfig(source);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("gpt-4o", result.Model);
|
||||
Assert.Equal("high", result.ReasoningEffort);
|
||||
Assert.Same(tools, result.Tools);
|
||||
Assert.Same(systemMessage, result.SystemMessage);
|
||||
Assert.Equal(new List<string> { "tool1", "tool2" }, result.AvailableTools);
|
||||
Assert.Equal(new List<string> { "tool3" }, result.ExcludedTools);
|
||||
Assert.Equal("/workspace", result.WorkingDirectory);
|
||||
Assert.Equal("/config", result.ConfigDir);
|
||||
Assert.Same(hooks, result.Hooks);
|
||||
Assert.Same(infiniteSessions, result.InfiniteSessions);
|
||||
Assert.Same(permissionHandler, result.OnPermissionRequest);
|
||||
Assert.Same(userInputHandler, result.OnUserInputRequest);
|
||||
Assert.Same(mcpServers, result.McpServers);
|
||||
Assert.Equal(new List<string> { "skill1" }, result.DisabledSkills);
|
||||
Assert.True(result.Streaming);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CopyResumeSessionConfig_CopiesAllProperties()
|
||||
{
|
||||
// Arrange
|
||||
List<AIFunction> tools = [AIFunctionFactory.Create(() => "test", "TestFunc", "Test function")];
|
||||
var hooks = new SessionHooks();
|
||||
var infiniteSessions = new InfiniteSessionConfig();
|
||||
var systemMessage = new SystemMessageConfig { Mode = SystemMessageMode.Append, Content = "Be helpful" };
|
||||
PermissionHandler permissionHandler = (_, _) => Task.FromResult(new PermissionRequestResult());
|
||||
UserInputHandler userInputHandler = (_, _) => Task.FromResult(new UserInputResponse { Answer = "input" });
|
||||
var mcpServers = new Dictionary<string, object> { ["server1"] = new McpLocalServerConfig() };
|
||||
|
||||
var source = new SessionConfig
|
||||
{
|
||||
Model = "gpt-4o",
|
||||
ReasoningEffort = "high",
|
||||
Tools = tools,
|
||||
SystemMessage = systemMessage,
|
||||
AvailableTools = ["tool1", "tool2"],
|
||||
ExcludedTools = ["tool3"],
|
||||
WorkingDirectory = "/workspace",
|
||||
ConfigDir = "/config",
|
||||
Hooks = hooks,
|
||||
InfiniteSessions = infiniteSessions,
|
||||
OnPermissionRequest = permissionHandler,
|
||||
OnUserInputRequest = userInputHandler,
|
||||
McpServers = mcpServers,
|
||||
DisabledSkills = ["skill1"],
|
||||
};
|
||||
|
||||
// Act
|
||||
ResumeSessionConfig result = GitHubCopilotAgent.CopyResumeSessionConfig(source);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("gpt-4o", result.Model);
|
||||
Assert.Equal("high", result.ReasoningEffort);
|
||||
Assert.Same(tools, result.Tools);
|
||||
Assert.Same(systemMessage, result.SystemMessage);
|
||||
Assert.Equal(new List<string> { "tool1", "tool2" }, result.AvailableTools);
|
||||
Assert.Equal(new List<string> { "tool3" }, result.ExcludedTools);
|
||||
Assert.Equal("/workspace", result.WorkingDirectory);
|
||||
Assert.Equal("/config", result.ConfigDir);
|
||||
Assert.Same(hooks, result.Hooks);
|
||||
Assert.Same(infiniteSessions, result.InfiniteSessions);
|
||||
Assert.Same(permissionHandler, result.OnPermissionRequest);
|
||||
Assert.Same(userInputHandler, result.OnUserInputRequest);
|
||||
Assert.Same(mcpServers, result.McpServers);
|
||||
Assert.Equal(new List<string> { "skill1" }, result.DisabledSkills);
|
||||
Assert.True(result.Streaming);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CopyResumeSessionConfig_WithNullSource_ReturnsDefaults()
|
||||
{
|
||||
// Act
|
||||
ResumeSessionConfig result = GitHubCopilotAgent.CopyResumeSessionConfig(null);
|
||||
|
||||
// Assert
|
||||
Assert.Null(result.Model);
|
||||
Assert.Null(result.ReasoningEffort);
|
||||
Assert.Null(result.Tools);
|
||||
Assert.Null(result.SystemMessage);
|
||||
Assert.Null(result.OnPermissionRequest);
|
||||
Assert.Null(result.OnUserInputRequest);
|
||||
Assert.Null(result.Hooks);
|
||||
Assert.Null(result.WorkingDirectory);
|
||||
Assert.Null(result.ConfigDir);
|
||||
Assert.True(result.Streaming);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -54,7 +54,7 @@ public class ForeachTemplateTest(ITestOutputHelper output) : WorkflowActionTempl
|
||||
AssertGeneratedCode<ActionExecutor>(template.Id, workflowCode);
|
||||
AssertAgentProvider(template.UseAgentProvider, workflowCode);
|
||||
AssertGeneratedMethod(nameof(ForeachExecutor.TakeNextAsync), workflowCode);
|
||||
AssertGeneratedMethod(nameof(ForeachExecutor.ResetAsync), workflowCode);
|
||||
AssertGeneratedMethod(nameof(ForeachExecutor.CompleteAsync), workflowCode);
|
||||
}
|
||||
|
||||
private Foreach CreateModel(
|
||||
|
||||
+14
-11
@@ -52,7 +52,7 @@ public sealed class DeclarativeWorkflowTest(ITestOutputHelper output) : Workflow
|
||||
{
|
||||
await this.RunWorkflowAsync("LoopBreak.yaml");
|
||||
this.AssertExecutionCount(expectedCount: 6);
|
||||
this.AssertExecuted("foreach_loop");
|
||||
this.AssertExecuted("foreach_loop", isDiscrete: false);
|
||||
this.AssertExecuted("break_loop_now");
|
||||
this.AssertExecuted("end_all");
|
||||
this.AssertNotExecuted("set_variable_inner");
|
||||
@@ -64,7 +64,7 @@ public sealed class DeclarativeWorkflowTest(ITestOutputHelper output) : Workflow
|
||||
{
|
||||
await this.RunWorkflowAsync("LoopContinue.yaml");
|
||||
this.AssertExecutionCount(expectedCount: 22);
|
||||
this.AssertExecuted("foreach_loop");
|
||||
this.AssertExecuted("foreach_loop", isDiscrete: false);
|
||||
this.AssertExecuted("continue_loop_now");
|
||||
this.AssertExecuted("end_all");
|
||||
this.AssertNotExecuted("set_variable_inner");
|
||||
@@ -103,7 +103,7 @@ public sealed class DeclarativeWorkflowTest(ITestOutputHelper output) : Workflow
|
||||
this.AssertExecuted("conditionGroup_test");
|
||||
if (input % 2 == 0)
|
||||
{
|
||||
this.AssertExecuted("conditionItem_even", isScope: true);
|
||||
this.AssertExecuted("conditionItem_even", isAction: false);
|
||||
this.AssertExecuted("sendActivity_even");
|
||||
this.AssertNotExecuted("conditionItem_odd");
|
||||
this.AssertNotExecuted("sendActivity_odd");
|
||||
@@ -111,7 +111,7 @@ public sealed class DeclarativeWorkflowTest(ITestOutputHelper output) : Workflow
|
||||
}
|
||||
else
|
||||
{
|
||||
this.AssertExecuted("conditionItem_odd", isScope: true);
|
||||
this.AssertExecuted("conditionItem_odd", isAction: false);
|
||||
this.AssertExecuted("sendActivity_odd");
|
||||
this.AssertNotExecuted("conditionItem_even");
|
||||
this.AssertNotExecuted("sendActivity_even");
|
||||
@@ -131,13 +131,13 @@ public sealed class DeclarativeWorkflowTest(ITestOutputHelper output) : Workflow
|
||||
this.AssertExecuted("conditionGroup_test");
|
||||
if (input % 2 == 0)
|
||||
{
|
||||
this.AssertExecuted("sendActivity_else", isScope: true);
|
||||
this.AssertExecuted("sendActivity_else", isAction: false);
|
||||
this.AssertNotExecuted("conditionItem_odd");
|
||||
this.AssertNotExecuted("sendActivity_odd");
|
||||
}
|
||||
else
|
||||
{
|
||||
this.AssertExecuted("conditionItem_odd", isScope: true);
|
||||
this.AssertExecuted("conditionItem_odd", isAction: false);
|
||||
this.AssertExecuted("sendActivity_odd");
|
||||
this.AssertNotExecuted("sendActivity_else");
|
||||
}
|
||||
@@ -152,7 +152,7 @@ public sealed class DeclarativeWorkflowTest(ITestOutputHelper output) : Workflow
|
||||
await this.RunWorkflowAsync("ConditionFallThrough.yaml", input);
|
||||
this.AssertExecutionCount(expectedActions);
|
||||
this.AssertExecuted("setVariable_test");
|
||||
this.AssertExecuted("conditionGroup_test", isScope: true);
|
||||
this.AssertExecuted("conditionGroup_test", isAction: false);
|
||||
if (input % 2 == 0)
|
||||
{
|
||||
this.AssertNotExecuted("conditionItem_odd");
|
||||
@@ -160,7 +160,7 @@ public sealed class DeclarativeWorkflowTest(ITestOutputHelper output) : Workflow
|
||||
}
|
||||
else
|
||||
{
|
||||
this.AssertExecuted("conditionItem_odd", isScope: true);
|
||||
this.AssertExecuted("conditionItem_odd", isAction: false);
|
||||
this.AssertExecuted("sendActivity_odd");
|
||||
this.AssertMessage("ODD");
|
||||
}
|
||||
@@ -307,14 +307,17 @@ public sealed class DeclarativeWorkflowTest(ITestOutputHelper output) : Workflow
|
||||
Assert.DoesNotContain(this.WorkflowEvents.OfType<ExecutorCompletedEvent>(), e => e.ExecutorId == executorId);
|
||||
}
|
||||
|
||||
private void AssertExecuted(string executorId, bool isScope = false)
|
||||
private void AssertExecuted(string executorId, bool isAction = true, bool isDiscrete = true)
|
||||
{
|
||||
Assert.Contains(this.WorkflowEvents.OfType<ExecutorInvokedEvent>(), e => e.ExecutorId == executorId);
|
||||
Assert.Contains(this.WorkflowEvents.OfType<ExecutorCompletedEvent>(), e => e.ExecutorId == executorId);
|
||||
if (!isScope)
|
||||
if (isAction)
|
||||
{
|
||||
Assert.Contains(this.WorkflowEvents.OfType<DeclarativeActionInvokedEvent>(), e => e.ActionId == executorId);
|
||||
Assert.Contains(this.WorkflowEvents.OfType<DeclarativeActionCompletedEvent>(), e => e.ActionId == executorId);
|
||||
if (isDiscrete)
|
||||
{
|
||||
Assert.Contains(this.WorkflowEvents.OfType<DeclarativeActionCompletedEvent>(), e => e.ActionId == executorId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+320
@@ -0,0 +1,320 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Agents.AI.Workflows.Declarative.ObjectModel;
|
||||
using Microsoft.Agents.ObjectModel;
|
||||
using Microsoft.PowerFx.Types;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Microsoft.Agents.AI.Workflows.Declarative.UnitTests.ObjectModel;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for <see cref="ForeachExecutor"/>.
|
||||
/// </summary>
|
||||
public sealed class ForeachExecutorTest(ITestOutputHelper output) : WorkflowActionExecutorTest(output)
|
||||
{
|
||||
[Fact]
|
||||
public void ForeachThrowsWhenModelInvalid() =>
|
||||
// Arrange, Act & Assert
|
||||
Assert.Throws<DeclarativeModelException>(() => new ForeachExecutor(new Foreach(), this.State));
|
||||
|
||||
[Fact]
|
||||
public void ForeachNamingConvention()
|
||||
{
|
||||
// Arrange
|
||||
string testId = this.CreateActionId().Value;
|
||||
|
||||
// Act
|
||||
string startStep = ForeachExecutor.Steps.Start(testId);
|
||||
string nextStep = ForeachExecutor.Steps.Next(testId);
|
||||
string endStep = ForeachExecutor.Steps.End(testId);
|
||||
|
||||
// Assert
|
||||
Assert.Equal($"{testId}_{nameof(ForeachExecutor.Steps.Start)}", startStep);
|
||||
Assert.Equal($"{testId}_{nameof(ForeachExecutor.Steps.Next)}", nextStep);
|
||||
Assert.Equal($"{testId}_{nameof(ForeachExecutor.Steps.End)}", endStep);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ForeachInvokedWithSingleValueAsync()
|
||||
{
|
||||
// Arrange
|
||||
this.SetVariableState("CurrentValue");
|
||||
|
||||
// Act & Assert
|
||||
await this.ExecuteTestAsync(
|
||||
displayName: nameof(ForeachInvokedWithSingleValueAsync),
|
||||
items: ValueExpression.Literal(new NumberDataValue(42)),
|
||||
valueName: "CurrentValue",
|
||||
indexName: null);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ForeachInvokedWithTableValueAsync()
|
||||
{
|
||||
// Arrange
|
||||
this.SetVariableState("CurrentValue");
|
||||
|
||||
// Act & Assert
|
||||
await this.ExecuteTestAsync(
|
||||
displayName: nameof(ForeachInvokedWithTableValueAsync),
|
||||
items: ValueExpression.Literal(DataValue.EmptyTable),
|
||||
valueName: "CurrentValue",
|
||||
indexName: null);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ForeachInvokedWithIndexAsync()
|
||||
{
|
||||
// Arrange
|
||||
this.SetVariableState("CurrentValue", "CurrentIndex");
|
||||
TableDataValue tableValue = DataValue.TableFromRecords(
|
||||
DataValue.RecordFromFields(new KeyValuePair<string, DataValue>("item", new NumberDataValue(1))),
|
||||
DataValue.RecordFromFields(new KeyValuePair<string, DataValue>("item", new NumberDataValue(2))),
|
||||
DataValue.RecordFromFields(new KeyValuePair<string, DataValue>("item", new NumberDataValue(3))));
|
||||
|
||||
// Act & Assert
|
||||
await this.ExecuteTestAsync(
|
||||
displayName: nameof(ForeachInvokedWithIndexAsync),
|
||||
items: ValueExpression.Literal(tableValue),
|
||||
valueName: "CurrentValue",
|
||||
indexName: "CurrentIndex");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ForeachInvokedWithExpressionAsync()
|
||||
{
|
||||
// Arrange
|
||||
this.SetVariableState("CurrentValue");
|
||||
this.State.Set("SourceArray", FormulaValue.NewTable(RecordType.Empty()));
|
||||
|
||||
// Act & Assert
|
||||
await this.ExecuteTestAsync(
|
||||
displayName: nameof(ForeachInvokedWithExpressionAsync),
|
||||
items: ValueExpression.Variable(PropertyPath.TopicVariable("SourceArray")),
|
||||
valueName: "CurrentValue",
|
||||
indexName: null);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ForeachTakeNextAsync()
|
||||
{
|
||||
// Arrange
|
||||
this.SetVariableState("CurrentValue");
|
||||
this.State.Set(
|
||||
"SourceArray",
|
||||
FormulaValue.NewTable(
|
||||
RecordType.Empty(),
|
||||
FormulaValue.NewRecordFromFields(new NamedValue("value", FormulaValue.New(10))),
|
||||
FormulaValue.NewRecordFromFields(new NamedValue("value", FormulaValue.New(20))),
|
||||
FormulaValue.NewRecordFromFields(new NamedValue("value", FormulaValue.New(30)))));
|
||||
|
||||
// Act & Assert
|
||||
await this.TakeNextTestAsync(
|
||||
displayName: nameof(ForeachTakeNextAsync),
|
||||
items: ValueExpression.Variable(PropertyPath.TopicVariable("SourceArray")),
|
||||
valueName: "CurrentValue",
|
||||
indexName: null);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ForeachTakeNextWithIndexAsync()
|
||||
{
|
||||
// Arrange
|
||||
this.SetVariableState("CurrentValue", "CurrentIndex");
|
||||
this.State.Set(
|
||||
"SourceArray",
|
||||
FormulaValue.NewTable(
|
||||
RecordType.Empty(),
|
||||
FormulaValue.NewRecordFromFields(new NamedValue("value", FormulaValue.New(10))),
|
||||
FormulaValue.NewRecordFromFields(new NamedValue("value", FormulaValue.New(20))),
|
||||
FormulaValue.NewRecordFromFields(new NamedValue("value", FormulaValue.New(30)))));
|
||||
|
||||
// Act & Assert
|
||||
await this.TakeNextTestAsync(
|
||||
displayName: nameof(ForeachTakeNextWithIndexAsync),
|
||||
items: ValueExpression.Variable(PropertyPath.TopicVariable("SourceArray")),
|
||||
valueName: "CurrentValue",
|
||||
indexName: "CurrentIndex");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ForeachTakeLastAsync()
|
||||
{
|
||||
// Arrange
|
||||
this.SetVariableState("CurrentValue");
|
||||
this.State.Set(
|
||||
"SourceArray",
|
||||
FormulaValue.NewTable(
|
||||
RecordType.Empty(),
|
||||
FormulaValue.NewRecordFromFields(new NamedValue("value", FormulaValue.New(10)))));
|
||||
|
||||
// Act & Assert
|
||||
await this.TakeNextTestAsync(
|
||||
displayName: nameof(ForeachTakeLastAsync),
|
||||
items: ValueExpression.Variable(PropertyPath.TopicVariable("SourceArray")),
|
||||
valueName: "CurrentValue",
|
||||
indexName: null);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ForeachTakeNextWhenDoneAsync()
|
||||
{
|
||||
// Arrange
|
||||
this.SetVariableState("CurrentValue");
|
||||
|
||||
// Act & Assert
|
||||
await this.TakeNextTestAsync(
|
||||
displayName: nameof(ForeachTakeNextWhenDoneAsync),
|
||||
items: ValueExpression.Literal(DataValue.EmptyTable),
|
||||
valueName: "CurrentValue",
|
||||
indexName: null,
|
||||
expectValue: false);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ForeachCompletedWithoutIndexAsync()
|
||||
{
|
||||
// Arrange
|
||||
this.SetVariableState("CurrentValue");
|
||||
|
||||
// Act & Assert
|
||||
await this.CompletedTestAsync(
|
||||
displayName: nameof(ForeachCompletedWithoutIndexAsync),
|
||||
valueName: "CurrentValue",
|
||||
indexName: null);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ForeachCompletedWithIndexAsync()
|
||||
{
|
||||
// Arrange
|
||||
this.SetVariableState("CurrentValue", "CurrentIndex");
|
||||
|
||||
// Act & Assert
|
||||
await this.CompletedTestAsync(
|
||||
displayName: nameof(ForeachCompletedWithIndexAsync),
|
||||
valueName: "CurrentValue",
|
||||
indexName: "CurrentIndex");
|
||||
}
|
||||
|
||||
private void SetVariableState(string valueName, string? indexName = null, FormulaValue? valueState = null)
|
||||
{
|
||||
this.State.Set(valueName, valueState ?? FormulaValue.New("something"));
|
||||
if (indexName is not null)
|
||||
{
|
||||
this.State.Set(indexName, FormulaValue.New(33));
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ExecuteTestAsync(
|
||||
string displayName,
|
||||
ValueExpression items,
|
||||
string valueName,
|
||||
string? indexName,
|
||||
bool expectValue = false)
|
||||
{
|
||||
// Arrange
|
||||
Foreach model = this.CreateModel(displayName, items, valueName, indexName);
|
||||
ForeachExecutor action = new(model, this.State);
|
||||
|
||||
// Act
|
||||
WorkflowEvent[] events = await this.ExecuteAsync(action, isDiscrete: false);
|
||||
|
||||
// Assert
|
||||
VerifyModel(model, action);
|
||||
VerifyInvocationEvent(events);
|
||||
|
||||
// IsDiscreteAction should be false for Foreach
|
||||
Assert.Equal(
|
||||
false,
|
||||
action.GetType().BaseType?
|
||||
.GetProperty("IsDiscreteAction", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)?
|
||||
.GetValue(action));
|
||||
|
||||
// Verify HasValue state after execution
|
||||
Assert.Equal(expectValue, action.HasValue);
|
||||
|
||||
// Verify value was reset at the end
|
||||
this.VerifyUndefined(valueName);
|
||||
|
||||
// Verify index was reset at the end if it was used
|
||||
if (indexName is not null)
|
||||
{
|
||||
this.VerifyUndefined(indexName);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task TakeNextTestAsync(
|
||||
string displayName,
|
||||
ValueExpression items,
|
||||
string valueName,
|
||||
string? indexName,
|
||||
bool expectValue = true)
|
||||
{
|
||||
// Arrange
|
||||
Foreach model = this.CreateModel(displayName, items, valueName, indexName);
|
||||
ForeachExecutor action = new(model, this.State);
|
||||
|
||||
// Act
|
||||
await this.ExecuteAsync(action, ForeachExecutor.Steps.Next(action.Id), action.TakeNextAsync);
|
||||
|
||||
// Assert
|
||||
VerifyModel(model, action);
|
||||
|
||||
// Verify HasValue state after execution
|
||||
Assert.Equal(expectValue, action.HasValue);
|
||||
}
|
||||
|
||||
private async Task CompletedTestAsync(
|
||||
string displayName,
|
||||
string valueName,
|
||||
string? indexName)
|
||||
{
|
||||
// Arrange
|
||||
Foreach model = this.CreateModel(displayName, ValueExpression.Literal(DataValue.EmptyTable), valueName, indexName);
|
||||
ForeachExecutor action = new(model, this.State);
|
||||
|
||||
// Act
|
||||
WorkflowEvent[] events = await this.ExecuteAsync(ForeachExecutor.Steps.End(action.Id), action.CompleteAsync);
|
||||
|
||||
// Assert
|
||||
VerifyModel(model, action);
|
||||
VerifyCompletionEvent(events);
|
||||
|
||||
// Verify HasValue state after completion
|
||||
Assert.False(action.HasValue);
|
||||
|
||||
// Verify value was reset at the end
|
||||
this.VerifyUndefined(valueName);
|
||||
|
||||
// Verify index was reset at the end if it was used
|
||||
if (indexName is not null)
|
||||
{
|
||||
this.VerifyUndefined(indexName);
|
||||
}
|
||||
}
|
||||
|
||||
private Foreach CreateModel(
|
||||
string displayName,
|
||||
ValueExpression items,
|
||||
string valueName,
|
||||
string? indexName)
|
||||
{
|
||||
Foreach.Builder actionBuilder = new()
|
||||
{
|
||||
Id = this.CreateActionId(),
|
||||
DisplayName = this.FormatDisplayName(displayName),
|
||||
Items = items,
|
||||
Value = PropertyPath.Create(FormatVariablePath(valueName)),
|
||||
};
|
||||
|
||||
if (indexName is not null)
|
||||
{
|
||||
actionBuilder.Index = PropertyPath.Create(FormatVariablePath(indexName));
|
||||
}
|
||||
|
||||
return AssignParent<Foreach>(actionBuilder);
|
||||
}
|
||||
}
|
||||
+34
-6
@@ -6,6 +6,7 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Agents.AI.Workflows.Declarative.Extensions;
|
||||
using Microsoft.Agents.AI.Workflows.Declarative.Interpreter;
|
||||
using Microsoft.Agents.AI.Workflows.Declarative.Kit;
|
||||
using Microsoft.Agents.AI.Workflows.Declarative.PowerFx;
|
||||
using Microsoft.Agents.ObjectModel;
|
||||
using Microsoft.PowerFx.Types;
|
||||
@@ -25,17 +26,37 @@ public abstract class WorkflowActionExecutorTest(ITestOutputHelper output) : Wor
|
||||
|
||||
protected string FormatDisplayName(string name) => $"{this.GetType().Name}_{name}";
|
||||
|
||||
internal async Task<WorkflowEvent[]> ExecuteAsync(DeclarativeActionExecutor executor)
|
||||
internal Task<WorkflowEvent[]> ExecuteAsync(string actionId, DelegateAction<ActionExecutorResult> executorAction) =>
|
||||
this.ExecuteAsync(new DelegateActionExecutor(actionId, this.State, executorAction), isDiscrete: false);
|
||||
|
||||
internal Task<WorkflowEvent[]> ExecuteAsync(Executor executor, string actionId, DelegateAction<ActionExecutorResult> executorAction) =>
|
||||
this.ExecuteAsync([executor, new DelegateActionExecutor(actionId, this.State, executorAction)], isDiscrete: false);
|
||||
|
||||
internal Task<WorkflowEvent[]> ExecuteAsync(Executor executor, bool isDiscrete = true) =>
|
||||
this.ExecuteAsync([executor], isDiscrete);
|
||||
|
||||
internal async Task<WorkflowEvent[]> ExecuteAsync(Executor[] executors, bool isDiscrete)
|
||||
{
|
||||
this.State.Bind();
|
||||
|
||||
TestWorkflowExecutor workflowExecutor = new();
|
||||
WorkflowBuilder workflowBuilder = new(workflowExecutor);
|
||||
workflowBuilder.AddEdge(workflowExecutor, executor);
|
||||
Executor prevExecutor = workflowExecutor;
|
||||
foreach (Executor executor in executors)
|
||||
{
|
||||
workflowBuilder.AddEdge(prevExecutor, executor);
|
||||
prevExecutor = executor;
|
||||
}
|
||||
|
||||
await using StreamingRun run = await InProcessExecution.StreamAsync(workflowBuilder.Build(), this.State);
|
||||
WorkflowEvent[] events = await run.WatchStreamAsync().ToArrayAsync();
|
||||
Assert.Contains(events, e => e is DeclarativeActionInvokedEvent);
|
||||
Assert.Contains(events, e => e is DeclarativeActionCompletedEvent);
|
||||
|
||||
if (isDiscrete)
|
||||
{
|
||||
VerifyInvocationEvent(events);
|
||||
VerifyCompletionEvent(events);
|
||||
}
|
||||
|
||||
ExecutorFailedEvent[] failureEvents = events.OfType<ExecutorFailedEvent>().ToArray();
|
||||
switch (failureEvents.Length)
|
||||
{
|
||||
@@ -48,6 +69,7 @@ public abstract class WorkflowActionExecutorTest(ITestOutputHelper output) : Wor
|
||||
throw aggregateException;
|
||||
|
||||
}
|
||||
|
||||
return events;
|
||||
}
|
||||
|
||||
@@ -57,15 +79,21 @@ public abstract class WorkflowActionExecutorTest(ITestOutputHelper output) : Wor
|
||||
Assert.Equal(model, action.Model);
|
||||
}
|
||||
|
||||
protected static void VerifyInvocationEvent(WorkflowEvent[] events) =>
|
||||
Assert.Contains(events, e => e is DeclarativeActionInvokedEvent);
|
||||
|
||||
protected static void VerifyCompletionEvent(WorkflowEvent[] events) =>
|
||||
Assert.Contains(events, e => e is DeclarativeActionCompletedEvent);
|
||||
|
||||
protected void VerifyState(string variableName, FormulaValue expectedValue) => this.VerifyState(variableName, WorkflowFormulaState.DefaultScopeName, expectedValue);
|
||||
|
||||
internal void VerifyState(string variableName, string scopeName, FormulaValue expectedValue)
|
||||
protected void VerifyState(string variableName, string scopeName, FormulaValue expectedValue)
|
||||
{
|
||||
FormulaValue actualValue = this.State.Get(variableName, scopeName);
|
||||
Assert.Equal(expectedValue.Format(), actualValue.Format());
|
||||
}
|
||||
|
||||
internal void VerifyUndefined(string variableName, string? scopeName = null) =>
|
||||
protected void VerifyUndefined(string variableName, string? scopeName = null) =>
|
||||
Assert.IsType<BlankValue>(this.State.Get(variableName, scopeName));
|
||||
|
||||
protected static TAction AssignParent<TAction>(DialogAction.Builder actionBuilder) where TAction : DialogAction
|
||||
|
||||
+10
-5
@@ -1,4 +1,4 @@
|
||||
// ------------------------------------------------------------------------------
|
||||
// ------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// </auto-generated>
|
||||
@@ -81,7 +81,7 @@ public static class WorkflowProvider
|
||||
this._values = [evaluatedValue];
|
||||
}
|
||||
|
||||
await this.ResetAsync(context, null, cancellationToken).ConfigureAwait(false);
|
||||
await this.ResetAsync(context, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return default;
|
||||
}
|
||||
@@ -99,7 +99,12 @@ public static class WorkflowProvider
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask ResetAsync(IWorkflowContext context, object? _, CancellationToken cancellationToken)
|
||||
public async ValueTask CompleteAsync(IWorkflowContext context, object? _, CancellationToken cancellationToken)
|
||||
{
|
||||
await this.ResetAsync(context, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async ValueTask ResetAsync(IWorkflowContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
await context.QueueStateUpdateAsync(key: "LoopValue", value: UnassignedValue.Instance, scopeName: "Local").ConfigureAwait(false);
|
||||
await context.QueueStateUpdateAsync(key: "LoopIndex", value: UnassignedValue.Instance, scopeName: "Local").ConfigureAwait(false);
|
||||
@@ -160,7 +165,7 @@ public static class WorkflowProvider
|
||||
SetVariableInnerExecutor setVariableInner = new(myWorkflowRoot.Session);
|
||||
SendActivityInnerExecutor sendActivityInner = new(myWorkflowRoot.Session);
|
||||
DelegateExecutor endAll = new(id: "end_all", myWorkflowRoot.Session);
|
||||
DelegateExecutor foreachLoopEnd = new(id: "foreach_loop_End", myWorkflowRoot.Session, foreachLoop.ResetAsync);
|
||||
DelegateExecutor foreachLoopEnd = new(id: "foreach_loop_End", myWorkflowRoot.Session, foreachLoop.CompleteAsync);
|
||||
|
||||
// Define the workflow builder
|
||||
WorkflowBuilder builder = new(myWorkflowRoot);
|
||||
@@ -182,4 +187,4 @@ public static class WorkflowProvider
|
||||
// Build the workflow
|
||||
return builder.Build(validateOrphans: false);
|
||||
}
|
||||
}
|
||||
}
|
||||
+10
-5
@@ -1,4 +1,4 @@
|
||||
// ------------------------------------------------------------------------------
|
||||
// ------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// </auto-generated>
|
||||
@@ -81,7 +81,7 @@ public static class WorkflowProvider
|
||||
this._values = [evaluatedValue];
|
||||
}
|
||||
|
||||
await this.ResetAsync(context, null, cancellationToken).ConfigureAwait(false);
|
||||
await this.ResetAsync(context, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return default;
|
||||
}
|
||||
@@ -99,7 +99,12 @@ public static class WorkflowProvider
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask ResetAsync(IWorkflowContext context, object? _, CancellationToken cancellationToken)
|
||||
public async ValueTask CompleteAsync(IWorkflowContext context, object? _, CancellationToken cancellationToken)
|
||||
{
|
||||
await this.ResetAsync(context, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async ValueTask ResetAsync(IWorkflowContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
await context.QueueStateUpdateAsync(key: "LoopValue", value: UnassignedValue.Instance, scopeName: "Local").ConfigureAwait(false);
|
||||
await context.QueueStateUpdateAsync(key: "LoopIndex", value: UnassignedValue.Instance, scopeName: "Local").ConfigureAwait(false);
|
||||
@@ -160,7 +165,7 @@ public static class WorkflowProvider
|
||||
SetVariableInnerExecutor setVariableInner = new(myWorkflowRoot.Session);
|
||||
SendActivityInnerExecutor sendActivityInner = new(myWorkflowRoot.Session);
|
||||
DelegateExecutor endAll = new(id: "end_all", myWorkflowRoot.Session);
|
||||
DelegateExecutor foreachLoopEnd = new(id: "foreach_loop_End", myWorkflowRoot.Session, foreachLoop.ResetAsync);
|
||||
DelegateExecutor foreachLoopEnd = new(id: "foreach_loop_End", myWorkflowRoot.Session, foreachLoop.CompleteAsync);
|
||||
|
||||
// Define the workflow builder
|
||||
WorkflowBuilder builder = new(myWorkflowRoot);
|
||||
@@ -182,4 +187,4 @@ public static class WorkflowProvider
|
||||
// Build the workflow
|
||||
return builder.Build(validateOrphans: false);
|
||||
}
|
||||
}
|
||||
}
|
||||
+10
-5
@@ -1,4 +1,4 @@
|
||||
// ------------------------------------------------------------------------------
|
||||
// ------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// </auto-generated>
|
||||
@@ -81,7 +81,7 @@ public static class WorkflowProvider
|
||||
this._values = [evaluatedValue];
|
||||
}
|
||||
|
||||
await this.ResetAsync(context, null, cancellationToken).ConfigureAwait(false);
|
||||
await this.ResetAsync(context, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return default;
|
||||
}
|
||||
@@ -99,7 +99,12 @@ public static class WorkflowProvider
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask ResetAsync(IWorkflowContext context, object? _, CancellationToken cancellationToken)
|
||||
public async ValueTask CompleteAsync(IWorkflowContext context, object? _, CancellationToken cancellationToken)
|
||||
{
|
||||
await this.ResetAsync(context, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async ValueTask ResetAsync(IWorkflowContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
await context.QueueStateUpdateAsync(key: "LoopValue", value: UnassignedValue.Instance, scopeName: "Local").ConfigureAwait(false);
|
||||
await context.QueueStateUpdateAsync(key: "LoopIndex", value: UnassignedValue.Instance, scopeName: "Local").ConfigureAwait(false);
|
||||
@@ -158,7 +163,7 @@ public static class WorkflowProvider
|
||||
SetVariableInnerExecutor setVariableInner = new(myWorkflowRoot.Session);
|
||||
SendActivityInnerExecutor sendActivityInner = new(myWorkflowRoot.Session);
|
||||
DelegateExecutor endAll = new(id: "end_all", myWorkflowRoot.Session);
|
||||
DelegateExecutor foreachLoopEnd = new(id: "foreach_loop_End", myWorkflowRoot.Session, foreachLoop.ResetAsync);
|
||||
DelegateExecutor foreachLoopEnd = new(id: "foreach_loop_End", myWorkflowRoot.Session, foreachLoop.CompleteAsync);
|
||||
|
||||
// Define the workflow builder
|
||||
WorkflowBuilder builder = new(myWorkflowRoot);
|
||||
@@ -178,4 +183,4 @@ public static class WorkflowProvider
|
||||
// Build the workflow
|
||||
return builder.Build(validateOrphans: false);
|
||||
}
|
||||
}
|
||||
}
|
||||
+6
-6
@@ -70,7 +70,7 @@ client = AzureOpenAIChatClient(
|
||||
)
|
||||
```
|
||||
|
||||
See the following [setup guide](https://github.com/microsoft/agent-framework/tree/main/python/samples/getting_started) for more information.
|
||||
See the following [setup guide](samples/01-get-started) for more information.
|
||||
|
||||
## 2. Create a Simple Agent
|
||||
|
||||
@@ -181,7 +181,7 @@ if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
You can explore additional agent samples [here](https://github.com/microsoft/agent-framework/tree/main/python/samples/getting_started/agents).
|
||||
You can explore additional agent samples [here](samples/02-agents).
|
||||
|
||||
## 5. Multi-Agent Orchestration
|
||||
|
||||
@@ -233,14 +233,14 @@ if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
For more advanced orchestration patterns including Sequential, Concurrent, Group Chat, Handoff, and Magentic orchestrations, see the [orchestration samples](samples/getting_started/orchestrations).
|
||||
For more advanced orchestration patterns including Sequential, Concurrent, Group Chat, Handoff, and Magentic orchestrations, see the [orchestration samples](samples/02-agents/orchestrations).
|
||||
|
||||
## More Examples & Samples
|
||||
|
||||
- [Getting Started with Agents](https://github.com/microsoft/agent-framework/tree/main/python/samples/getting_started/agents): Basic agent creation and tool usage
|
||||
- [Chat Client Examples](https://github.com/microsoft/agent-framework/tree/main/python/samples/getting_started/chat_client): Direct chat client usage patterns
|
||||
- [Getting Started with Agents](samples/02-agents): Basic agent creation and tool usage
|
||||
- [Chat Client Examples](samples/02-agents/chat_client): Direct chat client usage patterns
|
||||
- [Azure AI Integration](https://github.com/microsoft/agent-framework/tree/main/python/packages/azure-ai): Azure AI integration
|
||||
- [Workflow Samples](https://github.com/microsoft/agent-framework/tree/main/python/samples/getting_started/workflows): Advanced multi-agent patterns
|
||||
- [Workflow Samples](samples/03-workflows): Advanced multi-agent patterns
|
||||
|
||||
## Agent Framework Documentation
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ The A2A agent integration enables communication with remote A2A-compliant agents
|
||||
|
||||
### Basic Usage Example
|
||||
|
||||
See the [A2A agent examples](https://github.com/microsoft/agent-framework/tree/main/python/samples/getting_started/agents/a2a/) which demonstrate:
|
||||
See the [A2A agent examples](../../samples/04-hosting/a2a/) which demonstrate:
|
||||
|
||||
- Connecting to remote A2A agents
|
||||
- Sending messages and receiving responses
|
||||
|
||||
@@ -12,7 +12,7 @@ The Anthropic integration enables communication with the Anthropic API, allowing
|
||||
|
||||
### Basic Usage Example
|
||||
|
||||
See the [Anthropic agent examples](https://github.com/microsoft/agent-framework/tree/main/python/samples/getting_started/agents/anthropic/) which demonstrate:
|
||||
See the [Anthropic agent examples](../../samples/02-agents/providers/anthropic/) which demonstrate:
|
||||
|
||||
- Connecting to a Anthropic endpoint with an agent
|
||||
- Streaming and non-streaming responses
|
||||
|
||||
@@ -15,7 +15,7 @@ The Azure AI Search integration provides context providers for RAG (Retrieval Au
|
||||
|
||||
### Basic Usage Example
|
||||
|
||||
See the [Azure AI Search context provider examples](https://github.com/microsoft/agent-framework/tree/main/python/samples/getting_started/agents/azure_ai/) which demonstrate:
|
||||
See the [Azure AI Search context provider examples](../../samples/02-agents/providers/azure_ai/) which demonstrate:
|
||||
|
||||
- Semantic search with hybrid (vector + keyword) queries
|
||||
- Agentic mode with Knowledge Bases for complex multi-hop reasoning
|
||||
|
||||
@@ -300,7 +300,7 @@ def _get_sample_path_from_marker(request: pytest.FixtureRequest) -> tuple[Path |
|
||||
|
||||
sample_name = marker.args[0]
|
||||
repo_root = _resolve_repo_root()
|
||||
sample_path = repo_root / "samples" / "getting_started" / "azure_functions" / sample_name
|
||||
sample_path = repo_root / "samples" / "04-hosting" / "azure_functions" / sample_name
|
||||
|
||||
if not sample_path.exists():
|
||||
return None, f"Sample directory does not exist: {sample_path}"
|
||||
|
||||
@@ -124,4 +124,4 @@ async def chatkit_endpoint(request: Request):
|
||||
return Response(content=result.json, media_type="application/json") # type: ignore[union-attr]
|
||||
```
|
||||
|
||||
For a complete end-to-end example with a full frontend, see the [weather agent sample](../../samples/demos/chatkit-integration/README.md).
|
||||
For a complete end-to-end example with a full frontend, see the [weather agent sample](../../samples/05-end-to-end/chatkit-integration/README.md).
|
||||
|
||||
@@ -89,7 +89,7 @@ The package uses MSAL (Microsoft Authentication Library) for authentication with
|
||||
|
||||
### Examples
|
||||
|
||||
For more comprehensive examples, see the [Copilot Studio examples](https://github.com/microsoft/agent-framework/tree/main/python/samples/getting_started/agents/copilotstudio/) which demonstrate:
|
||||
For more comprehensive examples, see the [Copilot Studio examples](../../samples/02-agents/providers/copilotstudio/) which demonstrate:
|
||||
|
||||
- Basic non-streaming and streaming execution
|
||||
- Explicit settings and manual token acquisition
|
||||
|
||||
@@ -53,7 +53,7 @@ client = AzureOpenAIChatClient(
|
||||
)
|
||||
```
|
||||
|
||||
See the following [setup guide](https://github.com/microsoft/agent-framework/tree/main/python/samples/getting_started) for more information.
|
||||
See the following [setup guide](../../samples/01-get-started) for more information.
|
||||
|
||||
## 2. Create a Simple Agent
|
||||
|
||||
@@ -161,7 +161,7 @@ async def main():
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
You can explore additional agent samples [here](https://github.com/microsoft/agent-framework/tree/main/python/samples/getting_started/agents).
|
||||
You can explore additional agent samples [here](../../samples/02-agents).
|
||||
|
||||
## 5. Multi-Agent Orchestration
|
||||
|
||||
@@ -213,12 +213,12 @@ if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
**Note**: Sequential, Concurrent, Group Chat, Handoff, and Magentic orchestrations are available. See examples in [orchestration samples](../../samples/getting_started/orchestrations).
|
||||
**Note**: Sequential, Concurrent, Group Chat, Handoff, and Magentic orchestrations are available. See examples in [orchestration samples](../../samples/02-agents/orchestrations).
|
||||
|
||||
## More Examples & Samples
|
||||
|
||||
- [Getting Started with Agents](https://github.com/microsoft/agent-framework/tree/main/python/samples/getting_started/agents): Basic agent creation and tool usage
|
||||
- [Chat Client Examples](https://github.com/microsoft/agent-framework/tree/main/python/samples/getting_started/chat_client): Direct chat client usage patterns
|
||||
- [Getting Started with Agents](../../samples/02-agents): Basic agent creation and tool usage
|
||||
- [Chat Client Examples](../../samples/02-agents/chat_client): Direct chat client usage patterns
|
||||
- [Azure AI Integration](https://github.com/microsoft/agent-framework/tree/main/python/packages/azure-ai): Azure AI integration
|
||||
- [.NET Workflows Samples](https://github.com/microsoft/agent-framework/tree/main/dotnet/samples/GettingStarted/Workflows): Advanced multi-agent patterns (.NET)
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"""Tests to ensure PowerFx evaluation supports all expressions used in declarative YAML workflows.
|
||||
|
||||
This test suite validates that all PowerFx expressions found in the sample YAML workflows
|
||||
under samples/getting_started/workflows/declarative/ work correctly with our implementation.
|
||||
under samples/03-workflows/declarative/ work correctly with our implementation.
|
||||
|
||||
Coverage includes:
|
||||
- Built-in PowerFx functions: Concat, If, IsBlank, Not, Or, Upper, Find
|
||||
|
||||
@@ -373,7 +373,7 @@ This restricts developer APIs (reload, deployment, entity details) and requires
|
||||
|
||||
## Examples
|
||||
|
||||
See working implementations in `python/samples/getting_started/devui/`
|
||||
See working implementations in `python/samples/02-agents/devui/`
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ AZURE_OPENAI_CHAT_DEPLOYMENT_NAME="your-deployment-name"
|
||||
**Option A: In-Memory Mode (Recommended for quick testing)**
|
||||
|
||||
```bash
|
||||
cd samples/getting_started/devui
|
||||
cd samples/02-agents/devui
|
||||
python in_memory_mode.py
|
||||
```
|
||||
|
||||
@@ -54,7 +54,7 @@ This runs a simple example with predefined agents and opens your browser automat
|
||||
**Option B: Directory-Based Discovery**
|
||||
|
||||
```bash
|
||||
cd samples/getting_started/devui
|
||||
cd samples/02-agents/devui
|
||||
devui
|
||||
```
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
All DevUI samples are now located at:
|
||||
|
||||
```
|
||||
python/samples/getting_started/devui/
|
||||
python/samples/02-agents/devui/
|
||||
```
|
||||
|
||||
## Available Samples
|
||||
@@ -22,17 +22,17 @@ python/samples/getting_started/devui/
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
cd ../../samples/getting_started/devui
|
||||
cd ../../samples/02-agents/devui
|
||||
python in_memory_mode.py
|
||||
```
|
||||
|
||||
Or for directory discovery:
|
||||
|
||||
```bash
|
||||
cd ../../samples/getting_started/devui
|
||||
cd ../../samples/02-agents/devui
|
||||
devui
|
||||
```
|
||||
|
||||
## Learn More
|
||||
|
||||
See the [DevUI samples README](../../../samples/getting_started/devui/README.md) for detailed documentation.
|
||||
See the [DevUI samples README](../../../samples/02-agents/devui/README.md) for detailed documentation.
|
||||
|
||||
@@ -28,8 +28,8 @@ def start_server() -> tuple[str, Any]:
|
||||
"""Start server with samples directory."""
|
||||
# Get samples directory - updated path after samples were moved
|
||||
current_dir = Path(__file__).parent
|
||||
# Samples are now in python/samples/getting_started/devui
|
||||
samples_dir = current_dir.parent.parent.parent / "samples" / "getting_started" / "devui"
|
||||
# Samples are now in python/samples/02-agents/devui
|
||||
samples_dir = current_dir.parent.parent.parent / "samples" / "02-agents" / "devui"
|
||||
|
||||
if not samples_dir.exists():
|
||||
raise RuntimeError(f"Samples directory not found: {samples_dir}")
|
||||
|
||||
@@ -413,8 +413,8 @@ def executor_failed_event() -> WorkflowEvent[WorkflowErrorDetails]:
|
||||
def test_entities_dir() -> str:
|
||||
"""Use the samples directory which has proper entity structure."""
|
||||
current_dir = Path(__file__).parent
|
||||
# Navigate to python/samples/getting_started/devui
|
||||
samples_dir = current_dir.parent.parent.parent.parent / "samples" / "getting_started" / "devui"
|
||||
# Navigate to python/samples/02-agents/devui
|
||||
samples_dir = current_dir.parent.parent.parent.parent / "samples" / "02-agents" / "devui"
|
||||
return str(samples_dir.resolve())
|
||||
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ def devui_server() -> Generator[str, None, None]:
|
||||
"""
|
||||
# Get samples directory
|
||||
current_dir = Path(__file__).parent
|
||||
samples_dir = current_dir.parent.parent.parent / "samples" / "getting_started" / "devui"
|
||||
samples_dir = current_dir.parent.parent.parent / "samples" / "02-agents" / "devui"
|
||||
|
||||
if not samples_dir.exists():
|
||||
pytest.skip(f"Samples directory not found: {samples_dir}")
|
||||
|
||||
@@ -382,7 +382,7 @@ def worker_process(
|
||||
pytest.fail("Test class must have @pytest.mark.sample() marker")
|
||||
|
||||
sample_name: str = cast(str, sample_marker.args[0]) # type: ignore[union-attr]
|
||||
sample_path: Path = Path(__file__).parents[4] / "samples" / "getting_started" / "durabletask" / sample_name
|
||||
sample_path: Path = Path(__file__).parents[4] / "samples" / "04-hosting" / "durabletask" / sample_name
|
||||
worker_file: Path = sample_path / "worker.py"
|
||||
|
||||
if not worker_file.exists():
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@ import pytest
|
||||
import redis.asyncio as aioredis
|
||||
|
||||
# Add sample directory to path to import RedisStreamResponseHandler
|
||||
SAMPLE_DIR = Path(__file__).parents[4] / "samples" / "getting_started" / "durabletask" / "03_single_agent_streaming"
|
||||
SAMPLE_DIR = Path(__file__).parents[4] / "samples" / "04-hosting" / "durabletask" / "03_single_agent_streaming"
|
||||
sys.path.insert(0, str(SAMPLE_DIR))
|
||||
|
||||
from redis_stream_response_handler import RedisStreamResponseHandler # type: ignore[reportMissingImports] # noqa: E402
|
||||
|
||||
@@ -12,7 +12,7 @@ The Mem0 context provider enables persistent memory capabilities for your agents
|
||||
|
||||
### Basic Usage Example
|
||||
|
||||
See the [Mem0 basic example](https://github.com/microsoft/agent-framework/tree/main/python/samples/getting_started/context_providers/mem0/mem0_basic.py) which demonstrates:
|
||||
See the [Mem0 basic example](../../samples/02-agents/context_providers/mem0/mem0_basic.py) which demonstrates:
|
||||
|
||||
- Setting up an agent with Mem0 context provider
|
||||
- Teaching the agent user preferences
|
||||
|
||||
@@ -10,4 +10,4 @@ and see the [README](https://github.com/microsoft/agent-framework/tree/main/pyth
|
||||
|
||||
# Run samples with the Ollama Conector
|
||||
|
||||
You can find samples how to run the connector under the [Getting_started] (./getting_started/README.md) folder
|
||||
You can find samples how to run the connector in the [Ollama provider samples](../../samples/02-agents/providers/ollama).
|
||||
|
||||
@@ -14,7 +14,7 @@ The `RedisProvider` enables persistent context & memory capabilities for your ag
|
||||
|
||||
#### Basic Usage Examples
|
||||
|
||||
Review the set of [getting started examples](../../samples/getting_started/context_providers/redis/README.md) for using the Redis context provider.
|
||||
Review the set of [getting started examples](../../samples/02-agents/context_providers/redis/README.md) for using the Redis context provider.
|
||||
|
||||
### Redis Chat Message Store
|
||||
|
||||
@@ -30,7 +30,7 @@ The `RedisChatMessageStore` provides persistent conversation storage using Redis
|
||||
|
||||
#### Basic Usage Examples
|
||||
|
||||
See the complete [Redis chat message store examples](../../samples/getting_started/threads/redis_chat_message_store_thread.py) including:
|
||||
See the complete [Redis chat message store examples](../../samples/02-agents/conversations/redis_chat_message_store_thread.py) including:
|
||||
- User session management
|
||||
- Conversation persistence across restarts
|
||||
- Thread serialization and deserialization
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
"**/autogen-migration/**",
|
||||
"**/semantic-kernel-migration/**",
|
||||
"**/demos/**",
|
||||
"**/_to_delete/**",
|
||||
"**/05-end-to-end/**",
|
||||
"**/agent_with_foundry_tracing.py",
|
||||
"**/azure_responses_client_with_foundry.py"
|
||||
],
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
"""
|
||||
Hello Agent — Simplest possible agent
|
||||
|
||||
This sample creates a minimal agent using AzureOpenAIResponsesClient via an
|
||||
Azure AI Foundry project endpoint, and runs it in both non-streaming and streaming modes.
|
||||
|
||||
Environment variables:
|
||||
AZURE_AI_PROJECT_ENDPOINT — Your Azure AI Foundry project endpoint
|
||||
AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME — Model deployment name (e.g. gpt-4o)
|
||||
"""
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
# <create_agent>
|
||||
credential = AzureCliCredential()
|
||||
client = AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME"],
|
||||
credential=credential,
|
||||
)
|
||||
|
||||
agent = client.as_agent(
|
||||
name="HelloAgent",
|
||||
instructions="You are a friendly assistant. Keep your answers brief.",
|
||||
)
|
||||
# </create_agent>
|
||||
|
||||
# <run_agent>
|
||||
# Non-streaming: get the complete response at once
|
||||
result = await agent.run("What is the capital of France?")
|
||||
print(f"Agent: {result}")
|
||||
# </run_agent>
|
||||
|
||||
# <run_agent_streaming>
|
||||
# Streaming: receive tokens as they are generated
|
||||
print("Agent (streaming): ", end="", flush=True)
|
||||
async for chunk in agent.run("Tell me a one-sentence fun fact.", stream=True):
|
||||
if chunk.text:
|
||||
print(chunk.text, end="", flush=True)
|
||||
print()
|
||||
# </run_agent_streaming>
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,61 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
from random import randint
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import tool
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from azure.identity import AzureCliCredential
|
||||
from pydantic import Field
|
||||
|
||||
"""
|
||||
Add Tools — Give your agent a function tool
|
||||
|
||||
This sample shows how to define a function tool with the @tool decorator
|
||||
and wire it into an agent so the model can call it.
|
||||
|
||||
Environment variables:
|
||||
AZURE_AI_PROJECT_ENDPOINT — Your Azure AI Foundry project endpoint
|
||||
AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME — Model deployment name (e.g. gpt-4o)
|
||||
"""
|
||||
|
||||
|
||||
# <define_tool>
|
||||
# NOTE: approval_mode="never_require" is for sample brevity.
|
||||
# Use "always_require" in production for user confirmation before tool execution.
|
||||
@tool(approval_mode="never_require")
|
||||
def get_weather(
|
||||
location: Annotated[str, Field(description="The location to get the weather for.")],
|
||||
) -> str:
|
||||
"""Get the weather for a given location."""
|
||||
conditions = ["sunny", "cloudy", "rainy", "stormy"]
|
||||
return f"The weather in {location} is {conditions[randint(0, 3)]} with a high of {randint(10, 30)}°C."
|
||||
# </define_tool>
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
credential = AzureCliCredential()
|
||||
client = AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME"],
|
||||
credential=credential,
|
||||
)
|
||||
|
||||
# <create_agent_with_tools>
|
||||
agent = client.as_agent(
|
||||
name="WeatherAgent",
|
||||
instructions="You are a helpful weather agent. Use the get_weather tool to answer questions.",
|
||||
tools=get_weather,
|
||||
)
|
||||
# </create_agent_with_tools>
|
||||
|
||||
# <run_agent>
|
||||
result = await agent.run("What's the weather like in Seattle?")
|
||||
print(f"Agent: {result}")
|
||||
# </run_agent>
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,51 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
"""
|
||||
Multi-Turn Conversations — Use AgentThread to maintain context
|
||||
|
||||
This sample shows how to keep conversation history across multiple calls
|
||||
by reusing the same thread object.
|
||||
|
||||
Environment variables:
|
||||
AZURE_AI_PROJECT_ENDPOINT — Your Azure AI Foundry project endpoint
|
||||
AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME — Model deployment name (e.g. gpt-4o)
|
||||
"""
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
# <create_agent>
|
||||
credential = AzureCliCredential()
|
||||
client = AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME"],
|
||||
credential=credential,
|
||||
)
|
||||
|
||||
agent = client.as_agent(
|
||||
name="ConversationAgent",
|
||||
instructions="You are a friendly assistant. Keep your answers brief.",
|
||||
)
|
||||
# </create_agent>
|
||||
|
||||
# <multi_turn>
|
||||
# Create a thread to maintain conversation history
|
||||
thread = agent.get_new_thread()
|
||||
|
||||
# First turn
|
||||
result = await agent.run("My name is Alice and I love hiking.", thread=thread)
|
||||
print(f"Agent: {result}\n")
|
||||
|
||||
# Second turn — the agent should remember the user's name and hobby
|
||||
result = await agent.run("What do you remember about me?", thread=thread)
|
||||
print(f"Agent: {result}")
|
||||
# </multi_turn>
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,91 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
from collections.abc import MutableSequence
|
||||
from typing import Any
|
||||
|
||||
from agent_framework import Context, ContextProvider, Message
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
"""
|
||||
Agent Memory with Context Providers
|
||||
|
||||
Context providers let you inject dynamic instructions and context into each
|
||||
agent invocation. This sample defines a simple provider that tracks the user's
|
||||
name and enriches every request with personalization instructions.
|
||||
|
||||
Environment variables:
|
||||
AZURE_AI_PROJECT_ENDPOINT — Your Azure AI Foundry project endpoint
|
||||
AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME — Model deployment name (e.g. gpt-4o)
|
||||
"""
|
||||
|
||||
|
||||
# <context_provider>
|
||||
class UserNameProvider(ContextProvider):
|
||||
"""A simple context provider that remembers the user's name."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.user_name: str | None = None
|
||||
|
||||
async def invoking(self, messages: Message | MutableSequence[Message], **kwargs: Any) -> Context:
|
||||
"""Called before each agent invocation — add extra instructions."""
|
||||
if self.user_name:
|
||||
return Context(instructions=f"The user's name is {self.user_name}. Always address them by name.")
|
||||
return Context(instructions="You don't know the user's name yet. Ask for it politely.")
|
||||
|
||||
async def invoked(
|
||||
self,
|
||||
request_messages: Message | list[Message] | None = None,
|
||||
response_messages: "Message | list[Message] | None" = None,
|
||||
invoke_exception: Exception | None = None,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""Called after each agent invocation — extract information."""
|
||||
msgs = [request_messages] if isinstance(request_messages, Message) else list(request_messages or [])
|
||||
for msg in msgs:
|
||||
text = msg.text if hasattr(msg, "text") else ""
|
||||
if isinstance(text, str) and "my name is" in text.lower():
|
||||
# Simple extraction — production code should use structured extraction
|
||||
self.user_name = text.lower().split("my name is")[-1].strip().split()[0].capitalize()
|
||||
# </context_provider>
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
# <create_agent>
|
||||
credential = AzureCliCredential()
|
||||
client = AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME"],
|
||||
credential=credential,
|
||||
)
|
||||
|
||||
memory = UserNameProvider()
|
||||
|
||||
agent = client.as_agent(
|
||||
name="MemoryAgent",
|
||||
instructions="You are a friendly assistant.",
|
||||
context_provider=memory,
|
||||
)
|
||||
# </create_agent>
|
||||
|
||||
thread = agent.get_new_thread()
|
||||
|
||||
# The provider doesn't know the user yet — it will ask for a name
|
||||
result = await agent.run("Hello! What's the square root of 9?", thread=thread)
|
||||
print(f"Agent: {result}\n")
|
||||
|
||||
# Now provide the name — the provider extracts and stores it
|
||||
result = await agent.run("My name is Alice", thread=thread)
|
||||
print(f"Agent: {result}\n")
|
||||
|
||||
# Subsequent calls are personalized
|
||||
result = await agent.run("What is 2 + 2?", thread=thread)
|
||||
print(f"Agent: {result}\n")
|
||||
|
||||
print(f"[Memory] Stored user name: {memory.user_name}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,72 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
|
||||
from agent_framework import (
|
||||
Executor,
|
||||
WorkflowBuilder,
|
||||
WorkflowContext,
|
||||
executor,
|
||||
handler,
|
||||
)
|
||||
from typing_extensions import Never
|
||||
|
||||
"""
|
||||
First Workflow — Chain executors with edges
|
||||
|
||||
This sample builds a minimal workflow with two steps:
|
||||
1. Convert text to uppercase (class-based executor)
|
||||
2. Reverse the text (function-based executor)
|
||||
|
||||
No external services are required.
|
||||
"""
|
||||
|
||||
|
||||
# <create_workflow>
|
||||
# Step 1: A class-based executor that converts text to uppercase
|
||||
class UpperCase(Executor):
|
||||
def __init__(self, id: str):
|
||||
super().__init__(id=id)
|
||||
|
||||
@handler
|
||||
async def to_upper_case(self, text: str, ctx: WorkflowContext[str]) -> None:
|
||||
"""Convert input to uppercase and forward to the next node."""
|
||||
await ctx.send_message(text.upper())
|
||||
|
||||
|
||||
# Step 2: A function-based executor that reverses the string and yields output
|
||||
@executor(id="reverse_text")
|
||||
async def reverse_text(text: str, ctx: WorkflowContext[Never, str]) -> None:
|
||||
"""Reverse the string and yield the final workflow output."""
|
||||
await ctx.yield_output(text[::-1])
|
||||
|
||||
|
||||
def create_workflow():
|
||||
"""Build the workflow: UpperCase → reverse_text."""
|
||||
upper = UpperCase(id="upper_case")
|
||||
return (
|
||||
WorkflowBuilder(start_executor=upper)
|
||||
.add_edge(upper, reverse_text)
|
||||
.build()
|
||||
)
|
||||
# </create_workflow>
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
# <run_workflow>
|
||||
workflow = create_workflow()
|
||||
|
||||
events = await workflow.run("hello world")
|
||||
print(f"Output: {events.get_outputs()}")
|
||||
print(f"Final state: {events.get_final_state()}")
|
||||
# </run_workflow>
|
||||
|
||||
"""
|
||||
Expected output:
|
||||
Output: ['DLROW OLLEH']
|
||||
Final state: WorkflowRunState.IDLE
|
||||
"""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,60 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
"""
|
||||
Host Your Agent — Minimal A2A hosting stub
|
||||
|
||||
This sample shows the pattern for exposing an agent via the Agent-to-Agent
|
||||
(A2A) protocol. It creates the agent and demonstrates how to wrap it with
|
||||
the A2A hosting layer.
|
||||
|
||||
Prerequisites:
|
||||
pip install agent-framework[a2a] --pre
|
||||
|
||||
Environment variables:
|
||||
AZURE_AI_PROJECT_ENDPOINT — Your Azure AI Foundry project endpoint
|
||||
AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME — Model deployment name (e.g. gpt-4o)
|
||||
|
||||
To run a full A2A server, see samples/04-hosting/a2a/ for a complete example.
|
||||
"""
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
# <create_agent>
|
||||
credential = AzureCliCredential()
|
||||
client = AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME"],
|
||||
credential=credential,
|
||||
)
|
||||
|
||||
agent = client.as_agent(
|
||||
name="HostedAgent",
|
||||
instructions="You are a helpful assistant exposed via A2A.",
|
||||
)
|
||||
# </create_agent>
|
||||
|
||||
# <host_agent>
|
||||
# The A2A hosting integration wraps your agent behind an HTTP endpoint.
|
||||
# Import is gated so this sample can run without the a2a extra installed.
|
||||
try:
|
||||
from agent_framework.a2a import A2AAgent # noqa: F401
|
||||
|
||||
print("A2A support is available.")
|
||||
print("See samples/04-hosting/a2a/ for a runnable A2A server example.")
|
||||
except ImportError:
|
||||
print("Install a2a extras: pip install agent-framework[a2a] --pre")
|
||||
|
||||
# Quick smoke-test: run the agent locally to verify it works
|
||||
result = await agent.run("Hello! What can you do?")
|
||||
print(f"Agent: {result}")
|
||||
# </host_agent>
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,34 @@
|
||||
# Get Started with Agent Framework for Python
|
||||
|
||||
This folder contains a progressive set of samples that introduce the core
|
||||
concepts of **Agent Framework** one step at a time.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
```bash
|
||||
pip install agent-framework --pre
|
||||
```
|
||||
|
||||
Set the required environment variables:
|
||||
|
||||
```bash
|
||||
export OPENAI_API_KEY="sk-..."
|
||||
export OPENAI_RESPONSES_MODEL_ID="gpt-4o" # optional, defaults to gpt-4o
|
||||
```
|
||||
|
||||
## Samples
|
||||
|
||||
| # | File | What you'll learn |
|
||||
|---|------|-------------------|
|
||||
| 1 | [01_hello_agent.py](01_hello_agent.py) | Create your first agent and run it (streaming and non-streaming). |
|
||||
| 2 | [02_add_tools.py](02_add_tools.py) | Define a function tool with `@tool` and attach it to an agent. |
|
||||
| 3 | [03_multi_turn.py](03_multi_turn.py) | Keep conversation history across turns with `AgentThread`. |
|
||||
| 4 | [04_memory.py](04_memory.py) | Add dynamic context with a custom `ContextProvider`. |
|
||||
| 5 | [05_first_workflow.py](05_first_workflow.py) | Chain executors into a workflow with edges. |
|
||||
| 6 | [06_host_your_agent.py](06_host_your_agent.py) | Prepare your agent for A2A hosting. |
|
||||
|
||||
Run any sample with:
|
||||
|
||||
```bash
|
||||
python 01_hello_agent.py
|
||||
```
|
||||
+1
-1
@@ -17,7 +17,7 @@ Shows function calling capabilities with custom business logic.
|
||||
"""
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/getting_started/tools/function_tool_with_approval.py and samples/getting_started/tools/function_tool_with_approval_and_threads.py.
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_threads.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def get_weather(
|
||||
location: Annotated[str, Field(description="The location to get the weather for.")],
|
||||
+1
-1
@@ -17,7 +17,7 @@ Shows function calling capabilities and automatic assistant creation.
|
||||
"""
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/getting_started/tools/function_tool_with_approval.py and samples/getting_started/tools/function_tool_with_approval_and_threads.py.
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_threads.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def get_weather(
|
||||
location: Annotated[str, Field(description="The location to get the weather for.")],
|
||||
+1
-1
@@ -17,7 +17,7 @@ Shows function calling capabilities with custom business logic.
|
||||
"""
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/getting_started/tools/function_tool_with_approval.py and samples/getting_started/tools/function_tool_with_approval_and_threads.py.
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_threads.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def get_weather(
|
||||
location: Annotated[str, Field(description="The location to get the weather for.")],
|
||||
+1
-1
@@ -17,7 +17,7 @@ Shows function calling capabilities with custom business logic.
|
||||
"""
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/getting_started/tools/function_tool_with_approval.py and samples/getting_started/tools/function_tool_with_approval_and_threads.py.
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_threads.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def get_weather(
|
||||
location: Annotated[str, "The location to get the weather for."],
|
||||
+1
-1
@@ -17,7 +17,7 @@ Shows function calling capabilities and automatic assistant creation.
|
||||
"""
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/getting_started/tools/function_tool_with_approval.py and samples/getting_started/tools/function_tool_with_approval_and_threads.py.
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_threads.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def get_weather(
|
||||
location: Annotated[str, Field(description="The location to get the weather for.")],
|
||||
+1
-1
@@ -17,7 +17,7 @@ Shows function calling capabilities with custom business logic.
|
||||
"""
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/getting_started/tools/function_tool_with_approval.py and samples/getting_started/tools/function_tool_with_approval_and_threads.py.
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_threads.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def get_weather(
|
||||
location: Annotated[str, Field(description="The location to get the weather for.")],
|
||||
+1
-1
@@ -17,7 +17,7 @@ Shows function calling capabilities with custom business logic.
|
||||
"""
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/getting_started/tools/function_tool_with_approval.py and samples/getting_started/tools/function_tool_with_approval_and_threads.py.
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_threads.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def get_weather(
|
||||
location: Annotated[str, Field(description="The location to get the weather for.")],
|
||||
+1
-1
@@ -9,7 +9,7 @@ from agent_framework.mem0 import Mem0Provider
|
||||
from azure.identity.aio import AzureCliCredential
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/getting_started/tools/function_tool_with_approval.py and samples/getting_started/tools/function_tool_with_approval_and_threads.py.
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_threads.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def retrieve_company_report(company_code: str, detailed: bool) -> str:
|
||||
if company_code != "CNTS":
|
||||
+1
-1
@@ -10,7 +10,7 @@ from azure.identity.aio import AzureCliCredential
|
||||
from mem0 import AsyncMemory
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/getting_started/tools/function_tool_with_approval.py and samples/getting_started/tools/function_tool_with_approval_and_threads.py.
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_threads.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def retrieve_company_report(company_code: str, detailed: bool) -> str:
|
||||
if company_code != "CNTS":
|
||||
+1
-1
@@ -9,7 +9,7 @@ from agent_framework.mem0 import Mem0Provider
|
||||
from azure.identity.aio import AzureCliCredential
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/getting_started/tools/function_tool_with_approval.py and samples/getting_started/tools/function_tool_with_approval_and_threads.py.
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_threads.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def get_user_preferences(user_id: str) -> str:
|
||||
"""Mock function to get user preferences."""
|
||||
+1
-1
@@ -37,7 +37,7 @@ from redisvl.extensions.cache.embeddings import EmbeddingsCache
|
||||
from redisvl.utils.vectorize import OpenAITextVectorizer
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/getting_started/tools/function_tool_with_approval.py and samples/getting_started/tools/function_tool_with_approval_and_threads.py.
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_threads.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def search_flights(origin_airport_code: str, destination_airport_code: str, detailed: bool = False) -> str:
|
||||
"""Simulated flight-search tool to demonstrate tool memory.
|
||||
+2
-2
@@ -21,7 +21,7 @@ DevUI is a sample application that provides:
|
||||
Run a single sample directly. This demonstrates how to wrap agents and workflows programmatically without needing a directory structure:
|
||||
|
||||
```bash
|
||||
cd python/samples/getting_started/devui
|
||||
cd python/samples/02-agents/devui
|
||||
python in_memory_mode.py
|
||||
```
|
||||
|
||||
@@ -32,7 +32,7 @@ This opens your browser at http://localhost:8090 with pre-configured agents and
|
||||
Launch DevUI to discover all samples in this folder:
|
||||
|
||||
```bash
|
||||
cd python/samples/getting_started/devui
|
||||
cd python/samples/02-agents/devui
|
||||
devui
|
||||
```
|
||||
|
||||
+1
-1
@@ -50,7 +50,7 @@ def analyze_content(
|
||||
return f"Analyzing content for: {query}"
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/getting_started/tools/function_tool_with_approval.py and samples/getting_started/tools/function_tool_with_approval_and_threads.py.
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_threads.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def summarize_document(
|
||||
length: Annotated[str, "Desired summary length: 'brief', 'medium', or 'detailed'"] = "medium",
|
||||
+1
-1
@@ -14,7 +14,7 @@ from azure.identity.aio import AzureCliCredential
|
||||
from pydantic import Field
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/getting_started/tools/function_tool_with_approval.py and samples/getting_started/tools/function_tool_with_approval_and_threads.py.
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_threads.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def get_weather(
|
||||
location: Annotated[str, Field(description="The location to get the weather for.")],
|
||||
+1
-1
@@ -16,7 +16,7 @@ from agent_framework.devui import serve
|
||||
from typing_extensions import Never
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/getting_started/tools/function_tool_with_approval.py and samples/getting_started/tools/function_tool_with_approval_and_threads.py.
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_threads.py.
|
||||
@tool(approval_mode="never_require")
|
||||
# Tool functions for the agent
|
||||
@tool(approval_mode="never_require")
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user