Merge branch 'main' into feature-session-statebag

This commit is contained in:
westey
2026-02-12 19:57:40 +00:00
committed by GitHub
Unverified
553 changed files with 4442 additions and 1720 deletions
+7 -7
View File
@@ -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 -1
View File
@@ -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.
+1 -1
View File
@@ -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" />
+3 -3
View File
@@ -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)
});
@@ -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);
@@ -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);
}
@@ -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);
}
@@ -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);
}
}
@@ -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(
@@ -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);
}
}
}
@@ -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);
}
}
@@ -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
@@ -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);
}
}
}
@@ -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);
}
}
}
@@ -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
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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}"
+1 -1
View File
@@ -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).
+1 -1
View File
@@ -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
+5 -5
View File
@@ -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
+1 -1
View File
@@ -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
+2 -2
View File
@@ -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
```
+4 -4
View File
@@ -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():
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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).
+2 -2
View File
@@ -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
+2
View File
@@ -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())
+34
View File
@@ -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
```
@@ -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.")],
@@ -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.")],
@@ -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.")],
@@ -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."],
@@ -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.")],
@@ -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.")],
@@ -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.")],
@@ -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":
@@ -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":
@@ -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."""
@@ -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.
@@ -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
```
@@ -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",
@@ -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.")],
@@ -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