mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Merge branch 'main' into feature-harness
This commit is contained in:
@@ -5,16 +5,16 @@
|
||||
// This is provided for demonstration purposes only.
|
||||
|
||||
using System.Diagnostics;
|
||||
using System.Text.Json;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Extensions.AI;
|
||||
|
||||
/// <summary>
|
||||
/// Executes file-based skill scripts as local subprocesses.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This runner uses the script's absolute path, converts the arguments
|
||||
/// to CLI flags, and returns captured output. It is intended for
|
||||
/// demonstration purposes only.
|
||||
/// This runner uses the script's absolute path and converts the arguments
|
||||
/// to CLI arguments. When the LLM sends a JSON array, each element is used
|
||||
/// as a positional argument. It is intended for demonstration purposes only.
|
||||
/// </remarks>
|
||||
internal static class SubprocessScriptRunner
|
||||
{
|
||||
@@ -24,7 +24,8 @@ internal static class SubprocessScriptRunner
|
||||
public static async Task<object?> RunAsync(
|
||||
AgentFileSkill skill,
|
||||
AgentFileSkillScript script,
|
||||
AIFunctionArguments arguments,
|
||||
JsonElement? arguments,
|
||||
IServiceProvider? serviceProvider,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (!File.Exists(script.FullPath))
|
||||
@@ -61,24 +62,27 @@ internal static class SubprocessScriptRunner
|
||||
startInfo.FileName = script.FullPath;
|
||||
}
|
||||
|
||||
if (arguments is not null)
|
||||
if (arguments is { ValueKind: JsonValueKind.Array } json)
|
||||
{
|
||||
foreach (var (key, value) in arguments)
|
||||
// Positional CLI arguments
|
||||
foreach (var element in json.EnumerateArray())
|
||||
{
|
||||
if (value is bool boolValue)
|
||||
if (element.ValueKind != JsonValueKind.String)
|
||||
{
|
||||
if (boolValue)
|
||||
{
|
||||
startInfo.ArgumentList.Add(NormalizeKey(key));
|
||||
}
|
||||
}
|
||||
else if (value is not null)
|
||||
{
|
||||
startInfo.ArgumentList.Add(NormalizeKey(key));
|
||||
startInfo.ArgumentList.Add(value.ToString()!);
|
||||
throw new InvalidOperationException(
|
||||
$"File-based skill scripts only accept string CLI arguments but received a JSON element of kind '{element.ValueKind}'. " +
|
||||
"All array elements must be JSON strings.");
|
||||
}
|
||||
|
||||
startInfo.ArgumentList.Add(element.GetString()!);
|
||||
}
|
||||
}
|
||||
else if (arguments is not null && arguments.Value.ValueKind != JsonValueKind.Null && arguments.Value.ValueKind != JsonValueKind.Undefined)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Expected a JSON array of CLI arguments but received {arguments.Value.ValueKind}. " +
|
||||
"File-based skill scripts expect positional arguments as a JSON array of strings.");
|
||||
}
|
||||
|
||||
Process? process = null;
|
||||
try
|
||||
@@ -128,10 +132,4 @@ internal static class SubprocessScriptRunner
|
||||
process?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Normalizes a parameter key to a consistent --flag format.
|
||||
/// Models may return keys with or without leading dashes (e.g., "value" vs "--value").
|
||||
/// </summary>
|
||||
private static string NormalizeKey(string key) => "--" + key.TrimStart('-');
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFrameworks>net10.0</TargetFrameworks>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<InjectIsExternalInitOnLegacy>true</InjectIsExternalInitOnLegacy>
|
||||
<InjectSharedFoundryAgents>true</InjectSharedFoundryAgents>
|
||||
<InjectSharedWorkflowsExecution>true</InjectSharedWorkflowsExecution>
|
||||
<InjectSharedWorkflowsSettings>true</InjectSharedWorkflowsSettings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Workflows.Declarative\Microsoft.Agents.AI.Workflows.Declarative.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Workflows.Declarative.Foundry\Microsoft.Agents.AI.Workflows.Declarative.Foundry.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="InvokeHttpRequest.yaml">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,76 @@
|
||||
#
|
||||
# This workflow demonstrates using HttpRequestAction to call a REST API directly
|
||||
# from the workflow without going through an AI agent first.
|
||||
#
|
||||
# HttpRequestAction allows workflows to:
|
||||
# - Fetch data from external HTTP endpoints
|
||||
# - Store the parsed response in workflow variables for later use
|
||||
# - Add the response body to the conversation so a downstream agent can
|
||||
# answer questions based on it
|
||||
#
|
||||
# This sample fetches public metadata for the dotnet/runtime repository from
|
||||
# the GitHub REST API (no authentication required) and uses an agent to
|
||||
# answer follow-up questions about it.
|
||||
#
|
||||
# Example input:
|
||||
# How many subscribers does the repository have?
|
||||
#
|
||||
kind: Workflow
|
||||
trigger:
|
||||
|
||||
kind: OnConversationStart
|
||||
id: workflow_invoke_http_request_demo
|
||||
actions:
|
||||
|
||||
# Capture the original user message for input to the follow-up agent.
|
||||
- kind: SetVariable
|
||||
id: set_user_message
|
||||
variable: Local.InputMessage
|
||||
value: =System.LastMessage
|
||||
|
||||
# Set the repository org/name used to form the request URL.
|
||||
- kind: SetVariable
|
||||
id: set_repo_name
|
||||
variable: Local.RepoName
|
||||
value: microsoft/agent-framework
|
||||
|
||||
# Invoke the GitHub repo API. The response body is parsed into Local.RepoInfo
|
||||
# and also added to the conversation (via conversationId) so the agent below
|
||||
# can answer questions based on it.
|
||||
- kind: HttpRequestAction
|
||||
id: fetch_repo_info
|
||||
conversationId: =System.ConversationId
|
||||
method: GET
|
||||
url: =Concatenate("https://api.github.com/repos/", Local.RepoName)
|
||||
headers:
|
||||
Accept: application/vnd.github+json
|
||||
User-Agent: agent-framework-sample
|
||||
response: Local.RepoInfo
|
||||
|
||||
# Display a confirmation message showing key fields from the parsed response.
|
||||
- kind: SendMessage
|
||||
id: show_repo_summary
|
||||
message: "Fetched repo: visibility={Local.RepoInfo.visibility}, description={Local.RepoInfo.description}"
|
||||
|
||||
# Use the agent to summarize the repo using the conversation context.
|
||||
- kind: InvokeAzureAgent
|
||||
id: summarize_repo
|
||||
conversationId: =System.ConversationId
|
||||
agent:
|
||||
name: GitHubRepoInfoAgent
|
||||
input:
|
||||
messages: =UserMessage("Please provide a brief summary of this GitHub repository based on the data already in the conversation.")
|
||||
output:
|
||||
autoSend: true
|
||||
messages: Local.AgentResponse
|
||||
|
||||
# Allow the user to ask follow-up questions about the repo in a loop.
|
||||
- kind: InvokeAzureAgent
|
||||
id: invoke_followup
|
||||
conversationId: =System.ConversationId
|
||||
agent:
|
||||
name: GitHubRepoInfoAgent
|
||||
input:
|
||||
messages: =Local.InputMessage
|
||||
externalLoop:
|
||||
when: =Upper(System.LastMessage.Text) <> "EXIT"
|
||||
@@ -0,0 +1,95 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Azure.AI.Projects;
|
||||
using Azure.AI.Projects.Agents;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Agents.AI.Workflows.Declarative;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Shared.Foundry;
|
||||
using Shared.Workflows;
|
||||
|
||||
namespace Demo.Workflows.Declarative.InvokeHttpRequest;
|
||||
|
||||
/// <summary>
|
||||
/// Demonstrates a workflow that uses HttpRequestAction to call a REST API
|
||||
/// directly from the workflow.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// The HttpRequestAction allows workflows to issue HTTP requests and:
|
||||
/// </para>
|
||||
/// <list type="bullet">
|
||||
/// <item>Fetch data from external REST endpoints</item>
|
||||
/// <item>Store the parsed response in workflow variables</item>
|
||||
/// <item>Add the response body to the conversation so an agent can answer
|
||||
/// questions based on it</item>
|
||||
/// </list>
|
||||
/// <para>
|
||||
/// This sample fetches public metadata for the dotnet/runtime repository from
|
||||
/// the GitHub REST API (no authentication required) and uses a Foundry agent
|
||||
/// to answer follow-up questions about it. Type "EXIT" to end the conversation.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// See the README.md file in the parent folder (../README.md) for detailed
|
||||
/// information about the configuration required to run this sample.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
internal sealed class Program
|
||||
{
|
||||
public static async Task Main(string[] args)
|
||||
{
|
||||
// Initialize configuration
|
||||
IConfiguration configuration = Application.InitializeConfig();
|
||||
Uri foundryEndpoint = new(configuration.GetValue(Application.Settings.FoundryEndpoint));
|
||||
|
||||
// Ensure sample agent exists in Foundry. The agent has no tools - it answers
|
||||
// questions about the GitHub repository using only the JSON data that the
|
||||
// HttpRequestAction adds to the conversation.
|
||||
await CreateAgentAsync(foundryEndpoint, configuration);
|
||||
|
||||
// Get input from command line or console
|
||||
string workflowInput = Application.GetInput(args);
|
||||
|
||||
// The default HttpRequestHandler is sufficient for this sample because the
|
||||
// GitHub REST endpoint used here does not require authentication. For
|
||||
// authenticated endpoints, supply a custom Func<HttpRequestInfo, ..., HttpClient?>
|
||||
// to DefaultHttpRequestHandler so each request can be routed through a
|
||||
// pre-configured (cached) HttpClient with the appropriate credentials.
|
||||
await using DefaultHttpRequestHandler httpRequestHandler = new();
|
||||
|
||||
// Create the workflow factory with the HTTP request handler
|
||||
WorkflowFactory workflowFactory = new("InvokeHttpRequest.yaml", foundryEndpoint)
|
||||
{
|
||||
HttpRequestHandler = httpRequestHandler
|
||||
};
|
||||
|
||||
// Execute the workflow
|
||||
WorkflowRunner runner = new() { UseJsonCheckpoints = true };
|
||||
await runner.ExecuteAsync(workflowFactory.CreateWorkflow, workflowInput);
|
||||
}
|
||||
|
||||
private static async Task CreateAgentAsync(Uri foundryEndpoint, IConfiguration configuration)
|
||||
{
|
||||
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
|
||||
AIProjectClient aiProjectClient = new(foundryEndpoint, new DefaultAzureCredential());
|
||||
|
||||
await aiProjectClient.CreateAgentAsync(
|
||||
agentName: "GitHubRepoInfoAgent",
|
||||
agentDefinition: DefineAgent(configuration),
|
||||
agentDescription: "Answers questions about a GitHub repository using HTTP response data in the conversation");
|
||||
}
|
||||
|
||||
private static DeclarativeAgentDefinition DefineAgent(IConfiguration configuration)
|
||||
{
|
||||
return new DeclarativeAgentDefinition(configuration.GetValue(Application.Settings.FoundryModel))
|
||||
{
|
||||
Instructions =
|
||||
"""
|
||||
Answer the user's questions about the GitHub repository using only the
|
||||
JSON data already present in the conversation history.
|
||||
If the answer is not contained in the conversation, say so plainly
|
||||
rather than guessing. Be concise and helpful.
|
||||
"""
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user