mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
.NET: Require TODO finish reason and rename SubAgents to BackgroundAgents (#5902)
* Require TODO finish reason and rename SubAgents to BackgroundAgents * Address PR comments
This commit is contained in:
+9
-9
@@ -6,26 +6,26 @@ using Microsoft.Extensions.AI;
|
||||
namespace Harness.Shared.Console.ToolFormatters;
|
||||
|
||||
/// <summary>
|
||||
/// Formats <c>SubAgents_*</c> tool calls with human-readable details
|
||||
/// Formats <c>BackgroundAgents_*</c> tool calls with human-readable details
|
||||
/// for task start, continue, wait, and result retrieval operations.
|
||||
/// </summary>
|
||||
public sealed class SubAgentToolFormatter : ToolCallFormatter
|
||||
public sealed class BackgroundAgentToolFormatter : ToolCallFormatter
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override bool CanFormat(FunctionCallContent call) => call.Name.StartsWith("SubAgents_", StringComparison.Ordinal);
|
||||
public override bool CanFormat(FunctionCallContent call) => call.Name.StartsWith("BackgroundAgents_", StringComparison.Ordinal);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string? FormatDetail(FunctionCallContent call) => call.Name switch
|
||||
{
|
||||
"SubAgents_StartTask" => FormatStartSubTask(call),
|
||||
"SubAgents_WaitForFirstCompletion" => FormatIdList(call, "taskIds", "Wait for"),
|
||||
"SubAgents_GetTaskResults" => FormatSingleId(call, "taskId"),
|
||||
"SubAgents_ContinueTask" => FormatContinueTask(call),
|
||||
"SubAgents_ClearCompletedTask" => FormatSingleId(call, "taskId"),
|
||||
"BackgroundAgents_StartTask" => FormatStartBackgroundTask(call),
|
||||
"BackgroundAgents_WaitForFirstCompletion" => FormatIdList(call, "taskIds", "Wait for"),
|
||||
"BackgroundAgents_GetTaskResults" => FormatSingleId(call, "taskId"),
|
||||
"BackgroundAgents_ContinueTask" => FormatContinueTask(call),
|
||||
"BackgroundAgents_ClearCompletedTask" => FormatSingleId(call, "taskId"),
|
||||
_ => null,
|
||||
};
|
||||
|
||||
private static string? FormatStartSubTask(FunctionCallContent call)
|
||||
private static string? FormatStartBackgroundTask(FunctionCallContent call)
|
||||
{
|
||||
string? agentName = GetStringArgumentValue(call, "agentName");
|
||||
string? description = GetStringArgumentValue(call, "description");
|
||||
+45
-1
@@ -19,7 +19,7 @@ public sealed class TodoToolFormatter : ToolCallFormatter
|
||||
public override string? FormatDetail(FunctionCallContent call) => call.Name switch
|
||||
{
|
||||
"TodoList_Add" => FormatAddTodos(call),
|
||||
"TodoList_Complete" => FormatIdList(call, "ids", "Complete"),
|
||||
"TodoList_Complete" => FormatCompleteTodos(call),
|
||||
"TodoList_Remove" => FormatIdList(call, "ids", "Remove"),
|
||||
_ => null,
|
||||
};
|
||||
@@ -64,6 +64,50 @@ public sealed class TodoToolFormatter : ToolCallFormatter
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private static string? FormatCompleteTodos(FunctionCallContent call)
|
||||
{
|
||||
if (call.Arguments?.TryGetValue("items", out object? itemsObj) != true || itemsObj is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var entries = new List<(int Id, string? Reason)>();
|
||||
|
||||
if (itemsObj is JsonElement jsonArray && jsonArray.ValueKind == JsonValueKind.Array)
|
||||
{
|
||||
foreach (JsonElement item in jsonArray.EnumerateArray())
|
||||
{
|
||||
if (!item.TryGetProperty("id", out JsonElement idElement) || !idElement.TryGetInt32(out int id))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string? reason = item.TryGetProperty("reason", out JsonElement reasonElement)
|
||||
? reasonElement.GetString()
|
||||
: null;
|
||||
entries.Add((id, reason));
|
||||
}
|
||||
}
|
||||
|
||||
if (entries.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var sb = new StringBuilder();
|
||||
for (int i = 0; i < entries.Count; i++)
|
||||
{
|
||||
string connector = i < entries.Count - 1 ? "├─" : "└─";
|
||||
sb.Append($"\n {connector} Complete #{entries[i].Id}");
|
||||
if (!string.IsNullOrEmpty(entries[i].Reason))
|
||||
{
|
||||
sb.Append($" — {Truncate(entries[i].Reason!, 80)}");
|
||||
}
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private static string? FormatIdList(FunctionCallContent call, string paramName, string verb)
|
||||
{
|
||||
List<int>? ids = GetIntListArgumentValue(call, paramName);
|
||||
|
||||
+1
-1
@@ -56,7 +56,7 @@ public abstract class ToolCallFormatter
|
||||
[
|
||||
new TodoToolFormatter(),
|
||||
new ModeToolFormatter(),
|
||||
new SubAgentToolFormatter(),
|
||||
new BackgroundAgentToolFormatter(),
|
||||
new FileMemoryToolFormatter(),
|
||||
new WebSearchToolFormatter(),
|
||||
new FallbackToolFormatter(),
|
||||
|
||||
+14
-14
@@ -1,10 +1,10 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
// This sample demonstrates how to use the SubAgentsProvider to delegate work to sub-agents.
|
||||
// This sample demonstrates how to use the BackgroundAgentsProvider to delegate work to background agents.
|
||||
// A parent agent is given a list of stock tickers and instructed to find the closing price
|
||||
// for each ticker on December 31, 2025. It delegates the web searches to a sub-agent.
|
||||
// for each ticker on December 31, 2025. It delegates the web searches to a background agent.
|
||||
// The HarnessAgent provides built-in WebSearch (HostedWebSearchTool) so no manual web search
|
||||
// tool configuration is needed on the sub-agent.
|
||||
// tool configuration is needed on the background agent.
|
||||
//
|
||||
// Special commands:
|
||||
// /exit — End the session.
|
||||
@@ -26,7 +26,7 @@ var deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYME
|
||||
const int MaxContextWindowTokens = 1_050_000;
|
||||
const int MaxOutputTokens = 128_000;
|
||||
|
||||
// --- Sub-agent: Web Search Agent ---
|
||||
// --- Background agent: Web Search Agent ---
|
||||
// This agent uses the HarnessAgent's built-in HostedWebSearchTool to search the web.
|
||||
// Features not needed by this sub-agent are disabled.
|
||||
AIAgent webSearchAgent =
|
||||
@@ -55,26 +55,26 @@ AIAgent webSearchAgent =
|
||||
});
|
||||
|
||||
// --- Parent agent: Stock Price Researcher ---
|
||||
// This agent orchestrates the sub-agent to look up stock prices in parallel.
|
||||
// This agent orchestrates the background agent to look up stock prices in parallel.
|
||||
var parentInstructions =
|
||||
"""
|
||||
You are a stock price research assistant. You have access to a web search sub-agent that can look up information on the web.
|
||||
You are a stock price research assistant. You have access to a web search background agent that can look up information on the web.
|
||||
|
||||
When given a list of stock tickers, your job is to find the closing price for each ticker on December 31, 2025.
|
||||
|
||||
## Workflow
|
||||
|
||||
1. For each ticker, start a sub-task on the WebSearchAgent asking it to find the closing price on December 31, 2025.
|
||||
- Start all sub-tasks before waiting for any of them to complete, so they run concurrently.
|
||||
2. Wait for all sub-tasks to complete.
|
||||
3. Retrieve the results from each sub-task.
|
||||
1. For each ticker, start a background task on the WebSearchAgent asking it to find the closing price on December 31, 2025.
|
||||
- Start all background tasks before waiting for any of them to complete, so they run concurrently.
|
||||
2. Wait for all background tasks to complete.
|
||||
3. Retrieve the results from each background task.
|
||||
4. Present a summary table with the ticker symbol and closing price for each stock.
|
||||
5. Clear all completed tasks to free memory.
|
||||
|
||||
## Important
|
||||
|
||||
- Always delegate web searches to the WebSearchAgent sub-agent. Do not try to answer from memory.
|
||||
- If a sub-task fails or returns unclear results, continue the task with a more specific query.
|
||||
- Always delegate web searches to the WebSearchAgent background agent. Do not try to answer from memory.
|
||||
- If a background task fails or returns unclear results, continue the task with a more specific query.
|
||||
- Present results in a clean markdown table format.
|
||||
""";
|
||||
|
||||
@@ -94,7 +94,7 @@ AIAgent parentAgent =
|
||||
.AsHarnessAgent(MaxContextWindowTokens, MaxOutputTokens, new HarnessAgentOptions
|
||||
{
|
||||
Name = "StockPriceResearcher",
|
||||
Description = "An agent that researches stock prices using sub-agents.",
|
||||
Description = "An agent that researches stock prices using background agents.",
|
||||
DisableTodoProvider = true,
|
||||
DisableAgentModeProvider = true,
|
||||
DisableFileMemory = true, // If enabled, this would allow the agent to store memories as files in a directory associated with the current session
|
||||
@@ -103,7 +103,7 @@ AIAgent parentAgent =
|
||||
DisableWebSearch = true,
|
||||
AIContextProviders =
|
||||
[
|
||||
new SubAgentsProvider([webSearchAgent]),
|
||||
new BackgroundAgentsProvider([webSearchAgent]),
|
||||
],
|
||||
ChatOptions = new ChatOptions
|
||||
{
|
||||
+15
-15
@@ -1,24 +1,24 @@
|
||||
# Harness Step 02 — SubAgents (Stock Price Research)
|
||||
# Harness Step 02 — BackgroundAgents (Stock Price Research)
|
||||
|
||||
This sample demonstrates how to use the **SubAgentsProvider** to delegate work from a parent agent to sub-agents. Both agents use `HarnessAgent` for pre-configured function invocation, per-service-call persistence, and context-window compaction.
|
||||
This sample demonstrates how to use the **BackgroundAgentsProvider** to delegate work from a parent agent to background agents. Both agents use `HarnessAgent` for pre-configured function invocation, per-service-call persistence, and context-window compaction.
|
||||
|
||||
## What It Does
|
||||
|
||||
A parent agent receives a list of stock tickers and uses a web-search sub-agent to find the closing price for each ticker on December 31, 2025. The sub-tasks run concurrently, and results are presented in a summary table.
|
||||
A parent agent receives a list of stock tickers and uses a web-search background agent to find the closing price for each ticker on December 31, 2025. The background tasks run concurrently, and results are presented in a summary table.
|
||||
|
||||
### Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────┐
|
||||
│ StockPriceResearcher │
|
||||
│ (Parent Agent) │
|
||||
│ │
|
||||
│ SubAgentsProvider │
|
||||
│ ├─ SubAgents_StartTask │
|
||||
│ ├─ SubAgents_WaitFor... │
|
||||
│ ├─ SubAgents_GetTaskResults │
|
||||
│ └─ ... │
|
||||
└────────────┬────────────────────┘
|
||||
┌────────────────────────────────────────┐
|
||||
│ StockPriceResearcher │
|
||||
│ (Parent Agent) │
|
||||
│ │
|
||||
│ BackgroundAgentsProvider │
|
||||
│ ├─ BackgroundAgents_StartTask │
|
||||
│ ├─ BackgroundAgents_WaitFor... │
|
||||
│ ├─ BackgroundAgents_GetTaskResults │
|
||||
│ └─ ... │
|
||||
└────────────┬───────────────────────────┘
|
||||
│ delegates to
|
||||
▼
|
||||
┌─────────────────────────────────┐
|
||||
@@ -40,7 +40,7 @@ A parent agent receives a list of stock tickers and uses a web-search sub-agent
|
||||
## Running the Sample
|
||||
|
||||
```bash
|
||||
cd dotnet/samples/02-agents/Harness/Harness_Step02_Research_WithSubAgents
|
||||
cd dotnet/samples/02-agents/Harness/Harness_Step02_Research_WithBackgroundAgents
|
||||
dotnet run
|
||||
```
|
||||
|
||||
@@ -50,4 +50,4 @@ When prompted, enter a list of stock tickers such as:
|
||||
BAC, MSFT, BA
|
||||
```
|
||||
|
||||
The parent agent will delegate each ticker lookup to the web search sub-agent concurrently and present the results in a table.
|
||||
The parent agent will delegate each ticker lookup to the web search background agent concurrently and present the results in a table.
|
||||
@@ -7,5 +7,5 @@ Samples demonstrating the [Harness AIContextProviders](../../../src/Microsoft.Ag
|
||||
| Sample | Description |
|
||||
| --- | --- |
|
||||
| [Harness_Step01_Research](./Harness_Step01_Research/README.md) | Using a ChatClientAgent with TodoProvider and AgentModeProvider for research, showcasing planning mode and todo management |
|
||||
| [Harness_Step02_Research_WithSubAgents](./Harness_Step02_Research_WithSubAgents/README.md) | Using SubAgentsProvider to delegate stock price lookups to a web-search sub-agent concurrently |
|
||||
| [Harness_Step02_Research_WithBackgroundAgents](./Harness_Step02_Research_WithBackgroundAgents/README.md) | Using BackgroundAgentsProvider to delegate stock price lookups to a web-search background agent concurrently |
|
||||
| [Harness_Step03_DataProcessing](./Harness_Step03_DataProcessing/README.md) | Using FileAccessProvider to give an agent access to CSV data files for reading, analysis, and output generation |
|
||||
|
||||
Reference in New Issue
Block a user