선언적 워크플로를 사용하면 프로그래밍 코드를 작성하는 대신 YAML 구성 파일을 사용하여 워크플로 논리를 정의할 수 있습니다. 이 방법을 사용하면 워크플로를 팀 전체에서 더 쉽게 읽고 수정하고 공유할 수 있습니다.
Overview
선언적 워크플로를 사용하면 워크플로를 구현하는 방법보다는 워크플로에서 수행해야 하는 작업을 설명합니다. 프레임워크는 기본 실행을 처리하여 YAML 정의를 실행 가능한 워크플로 그래프로 변환합니다.
주요 이점:
- 읽을 수 있는 형식: YAML 구문은 개발자가 아닌 경우에도 이해하기 쉽습니다.
- 이식 가능: 코드 변경 없이 워크플로 정의를 공유, 버전 관리 및 수정할 수 있습니다.
- 빠른 반복: 구성 파일을 편집하여 워크플로 동작 수정
- 일관된 구조: 미리 정의된 작업 유형은 워크플로가 모범 사례를 따르도록 합니다.
선언적 워크플로와 프로그래밍 방식 워크플로를 사용해야 하는 경우
| Scenario | 권장되는 접근 방식 |
|---|---|
| 표준 오케스트레이션 패턴 | 선언적 |
| 자주 변경되는 워크플로 | 선언적 |
| 비 개발자는 워크플로를 수정해야 합니다. | 선언적 |
| 복잡한 사용자 지정 논리 | 프로그래매틱 |
| 최대 유연성 및 제어 | 프로그래매틱 |
| 기존 Python 코드와 통합 | 프로그래매틱 |
기본 YAML 구조체
YAML 구조는 C# 구현과 Python 구현 간에 약간 다릅니다. 자세한 내용은 아래의 언어별 섹션을 참조하세요.
작업 형식
선언적 워크플로는 변수 관리, 제어 흐름, 에이전트 및 도구 호출, HTTP 및 MCP 통합, 휴먼 인더 루프 및 대화 제어를 포함하는 광범위한 작업 종류를 지원합니다. 전체 언어별 참조는 아래의 각 영역에 나타납니다. 두 언어의 한눈에 볼 수 있는 가용성 매트릭스는 이 문서의 맨 아래에 있는 작업 빠른 참조를 참조 하세요.
C# YAML 구조체
C# 선언적 워크플로는 트리거 기반 구조를 사용합니다.
#
# Workflow description as a comment
#
kind: Workflow
trigger:
kind: OnConversationStart
id: my_workflow
actions:
- kind: ActionType
id: unique_action_id
displayName: Human readable name
# Action-specific properties
구조 요소
| 요소 | 필수 | 설명 |
|---|---|---|
kind |
Yes |
Workflow이어야 합니다. |
trigger.kind |
Yes | 트리거 유형(일반적으로 OnConversationStart) |
trigger.id |
Yes | 워크플로의 고유 식별자 |
trigger.actions |
Yes | 실행할 작업 목록 |
Python YAML 구조체
Python 선언적 워크플로는 선택적 입력과 함께 이름 기반 구조를 사용합니다.
name: my-workflow
description: A brief description of what this workflow does
inputs:
parameterName:
type: string
description: Description of the parameter
actions:
- kind: ActionType
id: unique_action_id
displayName: Human readable name
# Action-specific properties
구조 요소
| 요소 | 필수 | 설명 |
|---|---|---|
name |
Yes | 워크플로의 고유 식별자 |
description |
No | 사람이 읽을 수 있는 설명 |
inputs |
No | 워크플로에서 허용하는 입력 매개 변수 |
actions |
Yes | 실행할 작업 목록 |
필수 조건
시작하기 전에 다음 사항을 확인하세요.
- .NET 8.0 이상
- 배포된 에이전트가 하나 이상 있는 Microsoft Foundry 프로젝트
- 설치된 NuGet 패키지는 다음과 같습니다.
dotnet add package Microsoft.Agents.AI.Workflows.Declarative.Foundry --prerelease
- 이 문서의 예제는 사용자 비밀 및 환경 변수에서 구성을 로드하며, 이를 위해 다음 NuGet 패키지가 필요합니다.
dotnet add package Microsoft.Extensions.Configuration.EnvironmentVariables
dotnet add package Microsoft.Extensions.Configuration.UserSecrets
- 워크플로에 MCP 도구 호출 작업을 추가하려는 경우 다음 NuGet 패키지도 설치합니다.
dotnet add package Microsoft.Agents.AI.Workflows.Declarative.Mcp --prerelease
- YAML 구문에 대한 기본 숙지
- 워크플로 개념 이해
첫 번째 선언적 워크플로
입력에 따라 사용자를 맞이하는 간단한 워크플로를 만들어 보겠습니다.
1단계: YAML 파일 만들기
다음과 같은 파일을 만듭니다 greeting-workflow.yaml.
#
# This workflow demonstrates a simple greeting based on user input.
# The user's message is captured via System.LastMessage.
#
# Example input:
# Alice
#
kind: Workflow
trigger:
kind: OnConversationStart
id: greeting_workflow
actions:
# Capture the user's input from the last message
- kind: SetVariable
id: capture_name
displayName: Capture user name
variable: Local.userName
value: =System.LastMessage.Text
# Set a greeting prefix
- kind: SetVariable
id: set_greeting
displayName: Set greeting prefix
variable: Local.greeting
value: Hello
# Build the full message using an expression
- kind: SetVariable
id: build_message
displayName: Build greeting message
variable: Local.message
value: =Concat(Local.greeting, ", ", Local.userName, "!")
# Send the greeting to the user
- kind: SendActivity
id: send_greeting
displayName: Send greeting to user
activity: =Local.message
2단계: 에이전트 공급자 구성
워크플로를 실행하는 C# 콘솔 애플리케이션을 만듭니다. 먼저 Foundry에 연결하는 에이전트 공급자를 구성합니다.
using Azure.Identity;
using Microsoft.Agents.AI.Workflows;
using Microsoft.Agents.AI.Workflows.Declarative;
using Microsoft.Extensions.Configuration;
// Load configuration (endpoint should be set in user secrets or environment variables)
IConfiguration configuration = new ConfigurationBuilder()
.AddUserSecrets<Program>()
.AddEnvironmentVariables()
.Build();
string foundryEndpoint = configuration["FOUNDRY_PROJECT_ENDPOINT"]
?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT not configured");
// Create the agent provider that connects to Foundry
// WARNING: DefaultAzureCredential is convenient for development but requires
// careful consideration in production environments.
AzureAgentProvider agentProvider = new(
new Uri(foundryEndpoint),
new DefaultAzureCredential());
3단계: 워크플로 빌드 및 실행
// Define workflow options with the agent provider
DeclarativeWorkflowOptions options = new(agentProvider)
{
Configuration = configuration,
// LoggerFactory = loggerFactory, // Optional: Enable logging
// ConversationId = conversationId, // Optional: Continue existing conversation
};
// Build the workflow from the YAML file
string workflowPath = Path.Combine(AppContext.BaseDirectory, "greeting-workflow.yaml");
Workflow workflow = DeclarativeWorkflowBuilder.Build<string>(workflowPath, options);
Console.WriteLine($"Loaded workflow from: {workflowPath}");
Console.WriteLine(new string('-', 40));
// Create a checkpoint manager (in-memory for this example)
CheckpointManager checkpointManager = CheckpointManager.CreateInMemory();
// Execute the workflow with input
string input = "Alice";
StreamingRun run = await InProcessExecution.RunStreamingAsync(
workflow,
input,
checkpointManager);
// Process workflow events
await foreach (WorkflowEvent workflowEvent in run.WatchStreamAsync())
{
switch (workflowEvent)
{
case AgentResponseEvent responseEvent:
Console.WriteLine($"Response: {responseEvent.Response.Text}");
break;
case WorkflowErrorEvent errorEvent:
Console.WriteLine($"Error: {errorEvent.Data}");
break;
}
}
Console.WriteLine("Workflow completed!");
예상 출력
Loaded workflow from: C:\path\to\greeting-workflow.yaml
----------------------------------------
Response: Hello, Alice!
Workflow completed!
핵심 개념
변수 네임스페이스
C#의 선언적 워크플로는 이름 표시 변수를 사용하여 상태를 구성합니다.
| 네임스페이스 | 설명 | Example |
|---|---|---|
Local.* |
워크플로에 로컬 변수 | Local.message |
System.* |
시스템 제공 값 |
System.ConversationId, System.LastMessage |
비고
C# 선언적 워크플로는 Workflow.Inputs 또는 Workflow.Outputs 네임스페이스를 사용하지 않습니다. 입력은 System.LastMessage을 통해 수신되고, 출력은 SendActivity 작동을 통해 전송됩니다.
시스템 변수
| Variable | 설명 |
|---|---|
System.ConversationId |
현재 대화 식별자 |
System.LastMessage |
최신 사용자 메시지 |
System.LastMessage.Text |
마지막 메시지의 텍스트 내용 |
식 언어
접두사로 = 지정된 값은 PowerFx 식 언어를 사용하여 식으로 평가됩니다.
# Literal value (no evaluation)
value: Hello
# Expression (evaluated at runtime)
value: =Concat("Hello, ", Local.userName)
# Access last message text
value: =System.LastMessage.Text
일반적인 함수는 다음과 같습니다.
-
Concat(str1, str2, ...)- 문자열 연결 -
If(condition, trueValue, falseValue)-조건식 -
IsBlank(value)- 값이 비어 있는지 확인 -
Upper(text)/Lower(text)- 대/소문자 변환 -
Find(searchText, withinText)- 문자열 내에서 텍스트 찾기 -
MessageText(message)- 메시지 개체에서 텍스트 추출 -
UserMessage(text)- 텍스트에서 사용자 메시지 만들기 -
AgentMessage(text)- 텍스트에서 에이전트 메시지 만들기
구성 옵션
클래스는 DeclarativeWorkflowOptions 워크플로 실행을 위한 구성을 제공합니다.
DeclarativeWorkflowOptions options = new(agentProvider)
{
// Application configuration for variable substitution
Configuration = configuration,
// Continue an existing conversation (optional)
ConversationId = "existing-conversation-id",
// Enable logging (optional)
LoggerFactory = loggerFactory,
// MCP tool handler for InvokeMcpTool actions (optional)
McpToolHandler = mcpToolHandler,
// HTTP request handler for HttpRequestAction actions (optional)
HttpRequestHandler = new DefaultHttpRequestHandler(),
// PowerFx expression limits (optional)
MaximumCallDepth = 50,
MaximumExpressionLength = 10000,
// Telemetry configuration (optional)
ConfigureTelemetry = opts => { /* configure telemetry */ },
TelemetryActivitySource = activitySource,
};
에이전트 공급자 설정
워크플로의 AzureAgentProvider가 Foundry 에이전트에 연결됩니다.
using Azure.Identity;
using Microsoft.Agents.AI.Workflows.Declarative;
// Create the agent provider with Azure credentials
AzureAgentProvider agentProvider = new(
new Uri("https://your-project.api.azureml.ms"),
new DefaultAzureCredential())
{
// Optional: Define functions that agents can automatically invoke
Functions = [
AIFunctionFactory.Create(myPlugin.GetData),
AIFunctionFactory.Create(myPlugin.ProcessItem),
],
// Optional: Allow concurrent function invocation
AllowConcurrentInvocation = true,
// Optional: Allow multiple tool calls per response
AllowMultipleToolCalls = true,
};
워크플로 실행
워크플로를 실행하고 이벤트를 처리하는 데 사용합니다 InProcessExecution . 다음은 완료된 응답을 표시하고 입력 요청을 개별적으로 처리하는 예제입니다. 또한 동일한 응답에 대한 스트리밍 업데이트 또는 활동 알림도 표시하지 않습니다.
using Microsoft.Agents.AI.Workflows;
using Microsoft.Agents.AI.Workflows.Checkpointing;
// Create checkpoint manager (choose in-memory or file-based)
CheckpointManager checkpointManager = CheckpointManager.CreateInMemory();
// Or persist to disk:
// var checkpointFolder = Directory.CreateDirectory("./checkpoints");
// var checkpointManager = CheckpointManager.CreateJson(
// new FileSystemJsonCheckpointStore(checkpointFolder));
// Start workflow execution
StreamingRun run = await InProcessExecution.RunStreamingAsync(
workflow,
input,
checkpointManager);
// Process events as they occur
await foreach (WorkflowEvent workflowEvent in run.WatchStreamAsync())
{
switch (workflowEvent)
{
case AgentResponseEvent response:
Console.WriteLine($"Agent: {response.Response.Text}");
break;
case RequestInfoEvent request:
// Handle external input requests (human-in-the-loop)
var userInput = await GetUserInputAsync(request);
await run.SendResponseAsync(request.Request.CreateResponse(userInput));
break;
case SuperStepCompletedEvent checkpoint:
// Checkpoint created - can resume from here if needed
var checkpointInfo = checkpoint.CompletionInfo?.Checkpoint;
break;
case WorkflowErrorEvent error:
Console.WriteLine($"Error: {error.Data}");
break;
}
}
표시할 응답 이벤트 선택
워크플로 이벤트 스트림에는 동일한 작업의 여러 보기가 포함됩니다. 예를 들어 텍스트 SendActivity 작업은 다음을 생성합니다.
| Event | Purpose |
|---|---|
MessageActivityEvent |
메시지 텍스트가 포함된 활동 알림입니다. |
AgentResponseUpdateEvent |
스트리밍 수신자를 위한 텍스트를 포함하는 AgentResponseUpdate. |
AgentResponseEvent |
완료된 AgentResponse워크플로 출력으로 사용할 수 있습니다. |
이러한 이벤트는 별도의 메시지 3개 또는 작업 실행 3개를 나타내지 않습니다. 각 이벤트의 텍스트를 인쇄하면 동일한 메시지가 반복적으로 표시됩니다.
SendActivity 대신 Question를 선택하는 AgentResponseUpdateEvent 또는 AgentResponseEvent YAML 설정은 없습니다. C# 이벤트 처리기가 표시하는 내용을 선택합니다.
- 완료된 응답의 경우 위의 예제와 같이 처리
AgentResponseEvent합니다. 다른 워크플로 이벤트를 즉시 수신하는 데 계속 사용할RunStreamingAsync수 있습니다. - 스트리밍 텍스트의 경우
AgentResponseUpdateEvent를 처리합니다. 메시지가 이미 스트리밍된 경우 완료된 응답의 텍스트를 다시 추가하지 마세요. 의 경우SendActivity업데이트 및 완료된 응답은 응답 ID와 메시지 ID를 공유합니다. 스트리밍된 메시지와 완료된 메시지의 상관 관계를 지정하는 경우 실행기 ID 및 메시지 ID를 추적합니다. - 워크플로가 외부 입력을 받을 수 있도록 독립적으로 처리
RequestInfoEvent합니다. 응답 텍스트를 필터링해도 요청에 응답할 필요가 없습니다.
workflow.AsAIAgent()를 사용할 때 includeWorkflowOutputsInResponse 인수는 워크플로 출력이 에이전트 응답에 포함되는 방식을 제어합니다. YAML 작업이 내보내는 이벤트는 변경되지 않으며 스트리밍 업데이트를 사용하지 않도록 설정하기 위한 스위치가 아닙니다.
검사점에서 다시 시작하기
워크플로는 오류 허용을 위해 체크포인트부터 재개할 수 있습니다.
// Save checkpoint info when workflow yields
CheckpointInfo? lastCheckpoint = null;
await foreach (WorkflowEvent workflowEvent in run.WatchStreamAsync())
{
if (workflowEvent is SuperStepCompletedEvent checkpointEvent)
{
lastCheckpoint = checkpointEvent.CompletionInfo?.Checkpoint;
}
}
// Later: Resume from the saved checkpoint
if (lastCheckpoint is not null)
{
// Recreate the workflow (can be on a different machine)
Workflow workflow = DeclarativeWorkflowBuilder.Build<string>(workflowPath, options);
StreamingRun resumedRun = await InProcessExecution.ResumeStreamingAsync(
workflow,
lastCheckpoint,
checkpointManager);
// Continue processing events...
}
AOT 및 트림-공격적 체크포인트
Native AOT(dotnet publish -p:PublishAot=true)로 게시하거나 System.Text.Json의 리플렉션 대체 기능(<JsonSerializerIsReflectionEnabledByDefault>false</JsonSerializerIsReflectionEnabledByDefault>)을 사용하지 않도록 설정하면, 체크포인트를 커밋하거나 리하이드레이션할 때 기본 CheckpointManager.CreateJson(store) 호출이 실패합니다.
선언적 워크플로 패키지는 검사점 파이프라인을 통해 흐르는 모든 선언적 패키지 유형을 포함하는 원본 생성 JsonSerializerOptions 인스턴스 DeclarativeWorkflowJsonOptions.Default를 제공합니다.
CheckpointManager.CreateJson에 두 번째 인수로 전달합니다:
using Microsoft.Agents.AI.Workflows.Checkpointing;
using Microsoft.Agents.AI.Workflows.Declarative;
// AOT-safe: type info is resolved via the source-generated JsonSerializerContext,
// so no runtime reflection is required.
CheckpointManager checkpointManager = CheckpointManager.CreateJson(
store,
DeclarativeWorkflowJsonOptions.Default);
비고
전달 DeclarativeWorkflowJsonOptions.Default 은 AOT가 아닌 환경에서도 안전하게 사용할 수 있습니다 . 리플렉션 사용 앱에 대한 CheckpointManager.CreateJson(store) 드롭인 업그레이드로, 동작이 변경되지 않습니다. 나중에 AOT 또는 트리밍을 사용하여 게시하는 경우 동일한 코드가 계속 작동되도록 무조건 채택합니다.
DeclarativeWorkflowJsonOptions 가 표시됩니다 [Experimental("MAAI001")]. 호출 사이트 또는 프로젝트 파일에서 진단을 표시하지 않습니다.
<PropertyGroup>
<NoWarn>$(NoWarn);MAAI001</NoWarn>
</PropertyGroup>
사용자 정의 형식 등록
워크플로 입력, 사용자 지정 ActionExecutorResult.Result 페이로드 또는 원시 형식이 아닌 승인 요청 인수가 사용자 정의 타입인 경우 Default을 복제한 다음 자체 소스 생성 리졸버를 추가하세요:
// Compose: declarative-package types + your app's source-gen context.
JsonSerializerOptions options = new(DeclarativeWorkflowJsonOptions.Default);
options.TypeInfoResolverChain.Add(MyAppJsonContext.Default);
options.MakeReadOnly();
CheckpointManager checkpointManager = CheckpointManager.CreateJson(store, options);
여기서 MyAppJsonContext는 앱의 타입에 대해 정의하는 JsonSerializerContext입니다:
[JsonSourceGenerationOptions(JsonSerializerDefaults.Web)]
[JsonSerializable(typeof(MyWorkflowInput))]
[JsonSerializable(typeof(MyCustomResult))]
internal sealed partial class MyAppJsonContext : JsonSerializerContext;
Tip
YAML 워크플로, AzureCliCredential 기반 에이전트, 그리고 관찰 가능한 "옵션을 제거해 실패를 확인" 모드를 포함한 엔드투엔드 실행 가능 예제는 AotCheckpointing의 dotnet/samples/03-workflows/Declarative/AotCheckpointing을 참조하세요. 이 샘플의 .csproj은(는) JsonSerializerIsReflectionEnabledByDefault=false을(를) 전체 AOT 게시 없이도 AOT 오류 모드를 재현하도록 설정합니다.
작업 참조
작업은 선언적 워크플로의 구성 요소입니다. 각 작업은 특정 작업을 수행하고 작업은 YAML 파일에 나타나는 순서대로 순차적으로 실행됩니다.
작업 구조
모든 작업은 공통 속성을 공유합니다.
- kind: ActionType # Required: The type of action
id: unique_id # Optional: Unique identifier for referencing
displayName: Name # Optional: Human-readable name for logging
# Action-specific properties...
변수 관리 작업
변수 설정
변수를 지정된 값으로 설정합니다.
- kind: SetVariable
id: set_greeting
displayName: Set greeting message
variable: Local.greeting
value: Hello World
다음 표현식과 함께:
- kind: SetVariable
variable: Local.fullName
value: =Concat(Local.firstName, " ", Local.lastName)
속성:
| 속성 | 필수 | 설명 |
|---|---|---|
variable |
Yes | 변수 경로(예: Local.name, Workflow.Outputs.result) |
value |
Yes | 설정할 값(리터럴 또는 식) |
다중 변수 설정
단일 작업에서 여러 변수를 설정합니다.
- kind: SetMultipleVariables
id: initialize_vars
displayName: Initialize variables
variables:
Local.counter: 0
Local.status: pending
Local.message: =Concat("Processing order ", Local.orderId)
속성:
| 속성 | 필수 | 설명 |
|---|---|---|
variables |
Yes | 값에 대한 변수 경로 매핑 |
SetTextVariable
텍스트 변수를 지정된 문자열 값으로 설정합니다.
- kind: SetTextVariable
id: set_text
displayName: Set text content
variable: Local.description
value: This is a text description
속성:
| 속성 | 필수 | 설명 |
|---|---|---|
variable |
Yes | 텍스트 값의 변수 경로 |
value |
Yes | 설정할 텍스트 값 |
변수초기화
변수의 값을 지웁니다.
- kind: ResetVariable
id: clear_counter
variable: Local.counter
속성:
| 속성 | 필수 | 설명 |
|---|---|---|
variable |
Yes | 재설정할 변수 경로 |
모든 변수 지우기
현재 컨텍스트의 모든 변수를 다시 설정합니다.
- kind: ClearAllVariables
id: clear_all
displayName: Clear all workflow variables
ParseValue
데이터를 추출하거나 사용 가능한 형식으로 변환합니다.
- kind: ParseValue
id: parse_json
displayName: Parse JSON response
source: =Local.rawResponse
variable: Local.parsedData
속성:
| 속성 | 필수 | 설명 |
|---|---|---|
source |
Yes | 구문 분석할 값을 반환하는 표현 |
variable |
Yes | 분석된 결과를 저장할 변수 경로 |
EditTableV2
구조화된 테이블 형식으로 데이터를 수정합니다.
- kind: EditTableV2
id: update_table
displayName: Update configuration table
table: Local.configTable
operation: update
row:
key: =Local.settingName
value: =Local.settingValue
속성:
| 속성 | 필수 | 설명 |
|---|---|---|
table |
Yes | 테이블에 대한 가변 경로 |
operation |
Yes | 작업 유형(추가, 업데이트, 삭제) |
row |
Yes | 작업에 대한 행 데이터 |
제어 흐름 작업
만약에
조건에 따라 조건부로 작업을 실행합니다.
- kind: If
id: check_age
displayName: Check user age
condition: =Local.age >= 18
then:
- kind: SendActivity
activity:
text: "Welcome, adult user!"
else:
- kind: SendActivity
activity:
text: "Welcome, young user!"
속성:
| 속성 | 필수 | 설명 |
|---|---|---|
condition |
Yes | true/false로 평가되는 식 |
then |
Yes | 조건이 true인 경우 실행할 작업 |
else |
No | 조건이 false인 경우 실행할 작업 |
ConditionGroup
switch/case 문과 같은 여러 조건을 평가합니다.
- kind: ConditionGroup
id: route_by_category
displayName: Route based on category
conditions:
- condition: =Local.category = "electronics"
id: electronics_branch
actions:
- kind: SetVariable
variable: Local.department
value: Electronics Team
- condition: =Local.category = "clothing"
id: clothing_branch
actions:
- kind: SetVariable
variable: Local.department
value: Clothing Team
elseActions:
- kind: SetVariable
variable: Local.department
value: General Support
속성:
| 속성 | 필수 | 설명 |
|---|---|---|
conditions |
Yes | 조건/작업 쌍 목록(먼저 일치하는 항목이 우선) |
elseActions |
No | 일치하는 조건이 없는 경우의 작업 |
각각
컬렉션의 요소를 반복 처리합니다.
- kind: Foreach
id: process_items
displayName: Process each item
source: =Local.items
itemName: item
indexName: index
actions:
- kind: SendActivity
activity:
text: '=Concat("Processing item ", index, ": ", item)'
속성:
| 속성 | 필수 | 설명 |
|---|---|---|
source |
Yes | 컬렉션을 반환하는 식 |
itemName |
No | 현재 항목의 변수 이름(기본값: item) |
indexName |
No | 현재 인덱스 변수 이름(기본값: index) |
actions |
Yes | 각 항목에 대해 실행할 작업 |
BreakLoop
현재 루프를 즉시 종료합니다.
- kind: Foreach
source: =Local.items
actions:
- kind: If
condition: =item = "stop"
then:
- kind: BreakLoop
- kind: SendActivity
activity:
text: =item
ContinueLoop
루프의 다음 반복으로 건너뜁니다.
- kind: Foreach
source: =Local.numbers
actions:
- kind: If
condition: =item < 0
then:
- kind: ContinueLoop
- kind: SendActivity
activity:
text: '=Concat("Positive number: ", item)'
GotoAction
ID별로 특정 작업으로 이동합니다.
- kind: SetVariable
id: start_label
variable: Local.attempts
value: =Local.attempts + 1
- kind: SendActivity
activity:
text: =Concat("Attempt ", Local.attempts)
- kind: If
condition: =And(Local.attempts < 3, Not(Local.success))
then:
- kind: GotoAction
actionId: start_label
속성:
| 속성 | 필수 | 설명 |
|---|---|---|
actionId |
Yes | 다음으로 이동할 작업의 ID: |
출력 작업
SendActivity
사용자에게 메시지를 보냅니다.
텍스트 작업은 활동 알림 외에도 스트리밍 업데이트와 완료된 응답을 모두 생성합니다. 동일한 텍스트를 두 번 이상 표시하지 않도록 표시할 응답 이벤트 선택을 참조하세요.
- kind: SendActivity
id: send_welcome
displayName: Send welcome message
activity:
text: "Welcome to our service!"
다음 표현식과 함께:
- kind: SendActivity
activity:
text: =Concat("Hello, ", Local.userName, "! How can I help you today?")
속성:
| 속성 | 필수 | 설명 |
|---|---|---|
activity |
Yes | 전송할 작업 |
activity.text |
Yes | 메시지 텍스트(리터럴 또는 식) |
메시지 보내기
템플릿이 있는 문자 메시지를 사용자에게 보냅니다. 텍스트를 보내야 하고 전체 활동 개체 셰이프가 필요하지 않은 경우에 사용합니다 SendMessage .
- kind: SendMessage
id: show_repo_summary
message: "Fetched repo: visibility={Local.RepoInfo.visibility}, description={Local.RepoInfo.description}"
속성:
| 속성 | 필수 | 설명 |
|---|---|---|
message |
Yes | 보낼 메시지 텍스트입니다. 자리 표시자를 사용하여 {Variable.Path} 워크플로 상태 값을 포함합니다. |
비고
C# 선언적 워크플로 구현에서 지원합니다.SendMessage
SendActivity를 Python 워크플로에 사용하세요.
에이전트 호출 작업
InvokeAzureAgent
Foundry 에이전트를 호출합니다.
기본 호출:
- kind: InvokeAzureAgent
id: call_assistant
displayName: Call assistant agent
agent:
name: AssistantAgent
conversationId: =System.ConversationId
입출력 구성을 사용하여:
- kind: InvokeAzureAgent
id: call_analyst
displayName: Call analyst agent
agent:
name: AnalystAgent
conversationId: =System.ConversationId
input:
messages: =Local.userMessage
arguments:
topic: =Local.topic
output:
responseObject: Local.AnalystResult
messages: Local.AnalystMessages
autoSend: true
외부 루프 사용(조건이 충족될 때까지 계속):
- kind: InvokeAzureAgent
id: support_agent
agent:
name: SupportAgent
input:
externalLoop:
when: =Not(Local.IsResolved)
output:
responseObject: Local.SupportResult
속성:
| 속성 | 필수 | 설명 |
|---|---|---|
agent.name |
Yes | 등록된 에이전트의 이름 |
conversationId |
No | 대화 컨텍스트 식별자 |
input.messages |
No | 에이전트에 보낼 메시지 |
input.arguments |
No | 에이전트에 대한 추가 인수 |
input.externalLoop.when |
No | 에이전트 루프를 계속하기 위한 조건 |
output.responseObject |
No | 에이전트 응답을 저장하는 경로 |
output.messages |
No | 대화 메시지를 저장하는 경로 |
output.autoSend |
No | 사용자에게 자동으로 응답 보내기 |
도구 및 HTTP 작업
InvokeFunctionTool
AI 에이전트를 거치지 않고 워크플로에서 직접 함수 도구를 호출합니다.
- kind: InvokeFunctionTool
id: invoke_get_data
displayName: Get data from function
functionName: GetUserData
conversationId: =System.ConversationId
requireApproval: true
arguments:
userId: =Local.userId
output:
autoSend: true
result: Local.UserData
messages: Local.FunctionMessages
속성:
| 속성 | 필수 | 설명 |
|---|---|---|
functionName |
Yes | 호출할 함수의 이름 |
conversationId |
No | 대화 컨텍스트 식별자 |
requireApproval |
No | 실행하기 전에 사용자 승인이 필요한지 여부 |
arguments |
No | 함수에 전달할 인수 |
output.result |
No | 함수 결과를 저장하는 경로 |
output.messages |
No | 함수 메시지를 저장하는 경로 |
output.autoSend |
No | 사용자에게 자동으로 결과 보내기 |
InvokeFunctionTool에 대한 C# 설치:
함수는 외부 입력을 WorkflowRunner 통해 등록하거나 처리해야 합니다.
// Define functions that can be invoked
AIFunction[] functions = [
AIFunctionFactory.Create(myPlugin.GetUserData),
AIFunctionFactory.Create(myPlugin.ProcessOrder),
];
// Create workflow runner with functions
WorkflowRunner runner = new(functions) { UseJsonCheckpoints = true };
await runner.ExecuteAsync(workflowFactory.CreateWorkflow, input);
InvokeMcpTool
MCP(모델 컨텍스트 프로토콜) 서버에서 도구를 호출합니다.
- kind: InvokeMcpTool
id: invoke_docs_search
displayName: Search documentation
serverUrl: https://learn.microsoft.com/api/mcp
serverLabel: microsoft_docs
toolName: microsoft_docs_search
conversationId: =System.ConversationId
requireApproval: false
headers:
X-Custom-Header: custom-value
arguments:
query: =Local.SearchQuery
output:
autoSend: true
result: Local.SearchResults
호스트된 시나리오에 대한 연결 이름 사용:
- kind: InvokeMcpTool
id: invoke_hosted_mcp
serverUrl: https://mcp.ai.azure.com
toolName: my_tool
# Connection name is used in hosted scenarios to connect to a ProjectConnectionId in Foundry.
# Note: This feature is not fully supported yet.
connection:
name: my-foundry-connection
output:
result: Local.ToolResult
속성:
| 속성 | 필수 | 설명 |
|---|---|---|
serverUrl |
Yes | MCP 서버의 URL |
serverLabel |
No | 서버에 대한 사람이 읽을 수 있는 레이블 |
toolName |
Yes | 호출할 도구의 이름 |
conversationId |
No | 대화 컨텍스트 식별자 |
requireApproval |
No | 사용자 승인 필요 여부 |
arguments |
No | 도구에 전달할 인수 |
headers |
No | 요청에 대한 사용자 지정 HTTP 헤더 |
connection.name |
No | 호스트된 시나리오에 대한 명명된 연결(Foundry의 ProjectConnectionId에 연결됨, 아직 완전히 지원되지 않음) |
output.result |
No | 도구 결과를 저장하는 경로 |
output.messages |
No | 결과 메시지를 저장할 경로 |
output.autoSend |
No | 사용자에게 자동으로 결과 보내기 |
InvokeMcpTool에 대한 C# 설정:
McpToolHandler을(를) 워크플로 팩토리에서 구성하십시오.
using Azure.Core;
using Azure.Identity;
using Microsoft.Agents.AI.Workflows.Declarative;
// Create MCP tool handler with authentication callback
DefaultAzureCredential credential = new();
DefaultMcpToolHandler mcpToolHandler = new(
httpClientProvider: async (serverUrl, cancellationToken) =>
{
if (serverUrl.StartsWith("https://mcp.ai.azure.com", StringComparison.OrdinalIgnoreCase))
{
// Acquire token for Azure MCP server
AccessToken token = await credential.GetTokenAsync(
new TokenRequestContext(["https://mcp.ai.azure.com/.default"]),
cancellationToken);
HttpClient httpClient = new();
httpClient.DefaultRequestHeaders.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token.Token);
return httpClient;
}
// Return null for servers that don't require authentication
return null;
});
// Configure workflow factory with MCP handler
WorkflowFactory workflowFactory = new("workflow.yaml", foundryEndpoint)
{
McpToolHandler = mcpToolHandler
};
HttpRequestAction
구성된 IHttpRequestHandler을 통해 HTTP 요청을 보냅니다. 성공적인 JSON 응답은 할당 전에 구문 분석됩니다. 2xx가 아닌 응답은 작업에 실패합니다.
- kind: HttpRequestAction
id: fetch_repo_info
method: GET
url: "https://api.github.com/repos/Microsoft/agent-framework"
headers:
Accept: application/vnd.github+json
User-Agent: agent-framework
queryParameters:
per_page: 10
response: Local.RepoInfo
responseHeaders: Local.RepoHeaders
속성:
| 속성 | 필수 | 설명 |
|---|---|---|
url |
Yes | 절대 요청 URL |
method |
No | HTTP 메서드; 기본값: GET |
headers |
No | 요청 헤더 |
queryParameters |
No | URL에 추가된 쿼리 매개 변수 |
body |
No | 요청 본문; kind: json, raw 또는 none를 사용하십시오. |
requestTimeoutInMilliseconds |
No | 요청당 시간 제한 |
conversationId |
No | 대화에 성공적인 응답 본문을 추가합니다. |
response |
No | 구문 분석된 응답 본문을 저장하는 경로 |
responseHeaders |
No | 응답 헤더를 저장하는 경로 |
HttpRequestAction에 대한 C# 설정:
HttpRequestHandler을 설정합니다 워크플로를 빌드할 때. 재시도 또는 URL 허용 목록이 필요할 때 사용자 지정 처리기를 사용합니다.
DeclarativeWorkflowOptions options = new(agentProvider)
{
HttpRequestHandler = new DefaultHttpRequestHandler(),
};
Workflow workflow = DeclarativeWorkflowBuilder.Build<string>("workflow.yaml", options);
인간 참여형 작업
Question
사용자에게 질문을 하고 응답을 저장합니다.
직접 워크플로 실행의 경우 질문의 RequestInfoEvent.를 처리합니다. 해당 ExternalInputRequest.AgentResponse에는 프롬프트가 포함되며, AgentResponse 속성은 페이로드이지 별도의 AgentResponseEvent가 아닙니다. 요청을 통해 ExternalInputResponse를 반환하여 워크플로를 재개합니다. 아래의 autoSend 설정은 채택된 답변이 대화에 추가되는 방식을 제어하며, 어떤 응답 이벤트가 생성되는지를 제어하는 것은 아닙니다.
C# 및 Python SDK는 질문 프롬프트에 다른 필드 이름을 사용합니다. 이 예제에서는 가장 작은 공유 동작을 사용합니다. 즉, 텍스트를 요청하고 워크플로 상태에 답변을 저장합니다.
- kind: Question
id: ask_name
displayName: Ask for user name
property: Local.userName
prompt:
kind: Message
text:
- "What is your name?"
entity:
kind: StringPrebuiltEntity
속성:
| 속성 | 필수 | 설명 |
|---|---|---|
prompt |
Yes | 질문으로 보낼 활동 템플릿입니다. 메시지 프롬프트에 대한 목록과 함께 text 사용합니다kind: Message. |
property |
Yes | 추출된 응답을 저장하는 경로입니다. |
entity |
Yes | 응답을 구문 분석하고 유효성을 검사하는 데 사용되는 엔터티 형식입니다. 자유 텍스트 답변에는 StringPrebuiltEntity를 사용합니다. |
alwaysPrompt |
No | 대상 속성에 이미 값이 있는 경우에도 질문을 강제로 합니다. |
skipQuestionMode |
No | 대상 속성에 이미 값이 있는 경우 프롬프트를 건너뛸지 여부를 제어합니다. 지원되는 값은 AlwaysSkipIfVariableHasValue, SkipOnFirstExecutionIfVariableHasValue및 AlwaysAsk. |
repeatCount |
No | 저장하기 전에 유효하지 않거나 인식할 수 없는 응답의 최대 수입니다 defaultValue. |
defaultValue |
No | 유효한 응답 없이 repeatCount에 도달했을 때 저장할 값입니다. |
defaultValueResponse |
No | 기본값을 사용할 때 보낼 메시지 활동 템플릿입니다. |
invalidPrompt |
No | 응답 텍스트가 구성된 엔터티와 일치하지 않을 때 보낼 메시지 활동 템플릿입니다. |
unrecognizedPrompt |
No | 응답에 메시지가 포함되지 않은 경우 보낼 메시지 활동 템플릿입니다. |
autoSend |
No | 워크플로 대화 및 업데이트에 유효한 답변을 추가합니다 System.LastMessage. 기본값은 true입니다. |
외부입력요청
외부 시스템 또는 프로세스에서 입력을 요청합니다.
- kind: RequestExternalInput
id: request_approval
displayName: Request manager approval
prompt:
text: "Please provide approval for this request."
variable: Local.approvalResult
default: "pending"
속성:
| 속성 | 필수 | 설명 |
|---|---|---|
prompt.text |
Yes | 필수 입력에 대한 설명 |
variable |
Yes | 입력을 저장할 경로 |
default |
No | 기본값 |
워크플로 제어 작업
EndWorkflow
워크플로 실행을 종료합니다.
- kind: EndWorkflow
id: finish
displayName: End workflow
대화 종료
현재 대화를 종료합니다.
- kind: EndConversation
id: end_chat
displayName: End conversation
대화생성
새 대화 컨텍스트를 만듭니다.
- kind: CreateConversation
id: create_new_conv
displayName: Create new conversation
conversationId: Local.NewConversationId
속성:
| 속성 | 필수 | 설명 |
|---|---|---|
conversationId |
Yes | 새 대화 ID를 저장하는 경로 |
대화 작업(C#에만 해당)
대화 메시지 추가
대화 스레드에 메시지를 추가합니다.
- kind: AddConversationMessage
id: add_system_message
displayName: Add system context
conversationId: =System.ConversationId
message:
role: system
content: =Local.contextInfo
속성:
| 속성 | 필수 | 설명 |
|---|---|---|
conversationId |
Yes | 대상 대화 식별자 |
message |
Yes | 추가할 메시지 |
message.role |
Yes | 메시지 역할(시스템, 사용자, 도우미) |
message.content |
Yes | 메시지 콘텐츠 |
대화 메시지 복사
한 대화에서 다른 대화로 메시지를 복사합니다.
- kind: CopyConversationMessages
id: copy_context
displayName: Copy conversation context
sourceConversationId: =Local.SourceConversation
targetConversationId: =System.ConversationId
limit: 10
속성:
| 속성 | 필수 | 설명 |
|---|---|---|
sourceConversationId |
Yes | 원본 대화 식별자 |
targetConversationId |
Yes | 대상 대화 식별자 |
limit |
No | 복사할 최대 메시지 수 |
RetrieveConversationMessage
대화에서 특정 메시지를 검색합니다.
- kind: RetrieveConversationMessage
id: get_message
displayName: Get specific message
conversationId: =System.ConversationId
messageId: =Local.targetMessageId
variable: Local.retrievedMessage
속성:
| 속성 | 필수 | 설명 |
|---|---|---|
conversationId |
Yes | 대화 식별자 |
messageId |
Yes | 검색할 메시지 식별자 |
variable |
Yes | 검색된 메시지를 저장할 경로 |
RetrieveConversationMessages
대화에서 여러 메시지를 검색합니다.
- kind: RetrieveConversationMessages
id: get_history
displayName: Get conversation history
conversationId: =System.ConversationId
limit: 20
newestFirst: true
variable: Local.conversationHistory
속성:
| 속성 | 필수 | 설명 |
|---|---|---|
conversationId |
Yes | 대화 식별자 |
limit |
No | 검색할 최대 메시지(기본값: 20) |
newestFirst |
No | 내림차순으로 반환 |
after |
No | 페이지를 매길 커서 |
before |
No | 페이지를 매길 커서 |
variable |
Yes | 검색된 메시지를 저장하는 경로 |
작업에 대한 빠른 참조
| 조치 | Category | C# | Python | 설명 |
|---|---|---|---|---|
SetVariable |
Variable | ✅ | ✅ | 단일 변수 설정 |
SetMultipleVariables |
Variable | ✅ | ✅ | 여러 변수 설정 |
SetTextVariable |
Variable | ✅ | ✅ | 텍스트 변수 설정 |
ResetVariable |
Variable | ✅ | ✅ | 변수 지우기 |
ClearAllVariables |
Variable | ✅ | ✅ | 모든 변수 지우기 |
ParseValue |
Variable | ✅ | ✅ | 데이터 구문 분석/변환 |
EditTableV2 |
Variable | ✅ | ✅ | 테이블 데이터 수정 |
If |
제어 흐름 | ✅ | ✅ | 조건부 분기 |
ConditionGroup |
제어 흐름 | ✅ | ✅ | 다중 분기 스위치 |
Foreach |
제어 흐름 | ✅ | ✅ | 컬렉션 반복 |
BreakLoop |
제어 흐름 | ✅ | ✅ | 현재 루프 종료 |
ContinueLoop |
제어 흐름 | ✅ | ✅ | 다음 반복으로 건너뛰기 |
GotoAction |
제어 흐름 | ✅ | ✅ | ID별 작업으로 이동 |
SendActivity |
출력 | ✅ | ✅ | 사용자에게 메시지 보내기 |
SendMessage |
출력 | ✅ | ❌ | 서식 파일로 된 문자 메시지 보내기 |
InvokeAzureAgent |
대리인 | ✅ | ✅ | Azure AI 에이전트 호출 |
InvokeFunctionTool |
Tool | ✅ | ✅ | 함수 직접 호출 |
InvokeMcpTool |
Tool | ✅ | ✅ | MCP 서버 도구 호출 |
HttpRequestAction |
HTTP | ✅ | ✅ | HTTP 엔드포인트 호출 |
Question |
휴먼 인 더 루프 | ✅ | ✅ | 사용자에게 질문하기 |
RequestExternalInput |
휴먼 인 더 루프 | ✅ | ✅ | 외부 입력 요청 |
EndWorkflow |
워크플로 제어 | ✅ | ✅ | 워크플로 종료 |
EndConversation |
워크플로 제어 | ✅ | ✅ | 대화 종료 |
CreateConversation |
워크플로 제어 | ✅ | ✅ | 새 대화 만들기 |
AddConversationMessage |
대화 | ✅ | ❌ | 스레드에 메시지 추가 |
CopyConversationMessages |
대화 | ✅ | ❌ | 메시지 복사 |
RetrieveConversationMessage |
대화 | ✅ | ❌ | 단일 메시지 가져오기 |
RetrieveConversationMessages |
대화 | ✅ | ❌ | 여러 메시지 가져오기 |
고급 패턴
다중 에이전트 오케스트레이션
순차 에이전트 파이프라인
작업을 여러 에이전트를 순서대로 통과시킵니다.
#
# Sequential agent pipeline for content creation
#
kind: Workflow
trigger:
kind: OnConversationStart
id: content_workflow
actions:
# First agent: Research
- kind: InvokeAzureAgent
id: invoke_researcher
displayName: Research phase
conversationId: =System.ConversationId
agent:
name: ResearcherAgent
# Second agent: Write draft
- kind: InvokeAzureAgent
id: invoke_writer
displayName: Writing phase
conversationId: =System.ConversationId
agent:
name: WriterAgent
# Third agent: Edit
- kind: InvokeAzureAgent
id: invoke_editor
displayName: Editing phase
conversationId: =System.ConversationId
agent:
name: EditorAgent
C# 설정:
using Azure.AI.Projects;
using Azure.AI.Projects.OpenAI;
using Azure.Identity;
// Ensure agents exist in Foundry
AIProjectClient aiProjectClient = new(foundryEndpoint, new DefaultAzureCredential());
await aiProjectClient.CreateAgentAsync(
agentName: "ResearcherAgent",
agentDefinition: new DeclarativeAgentDefinition(modelName)
{
Instructions = "You are a research specialist..."
},
agentDescription: "Research agent for content pipeline");
// Create and run workflow
WorkflowFactory workflowFactory = new("content-pipeline.yaml", foundryEndpoint);
WorkflowRunner runner = new();
await runner.ExecuteAsync(workflowFactory.CreateWorkflow, "Create content about AI");
조건부 에이전트 라우팅
조건에 따라 요청을 다른 에이전트로 라우팅합니다.
#
# Route to specialized support agents based on category
#
kind: Workflow
trigger:
kind: OnConversationStart
id: support_router
actions:
# Capture category from user input or set via another action
- kind: SetVariable
id: set_category
variable: Local.category
value: =System.LastMessage.Text
- kind: ConditionGroup
id: route_request
displayName: Route to appropriate agent
conditions:
- condition: =Local.category = "billing"
id: billing_route
actions:
- kind: InvokeAzureAgent
id: billing_agent
agent:
name: BillingAgent
conversationId: =System.ConversationId
- condition: =Local.category = "technical"
id: technical_route
actions:
- kind: InvokeAzureAgent
id: technical_agent
agent:
name: TechnicalAgent
conversationId: =System.ConversationId
elseActions:
- kind: InvokeAzureAgent
id: general_agent
agent:
name: GeneralAgent
conversationId: =System.ConversationId
도구 통합 패턴
InvokeFunctionTool을 사용하여 데이터 미리 가져오기
에이전트를 호출하기 전에 데이터를 가져옵니다.
#
# Pre-fetch menu data before agent interaction
#
kind: Workflow
trigger:
kind: OnConversationStart
id: menu_workflow
actions:
# Pre-fetch today's specials
- kind: InvokeFunctionTool
id: get_specials
functionName: GetSpecials
requireApproval: true
output:
autoSend: true
result: Local.Specials
# Agent uses pre-fetched data
- kind: InvokeAzureAgent
id: menu_agent
conversationId: =System.ConversationId
agent:
name: MenuAgent
input:
messages: '=UserMessage("Describe today''s specials: " & Local.Specials)'
MCP 도구 통합
MCP를 사용하여 외부 서버 호출:
#
# Search documentation using MCP
#
kind: Workflow
trigger:
kind: OnConversationStart
id: docs_search
actions:
- kind: SetVariable
variable: Local.SearchQuery
value: =System.LastMessage.Text
# Search Microsoft Learn
- kind: InvokeMcpTool
id: search_docs
serverUrl: https://learn.microsoft.com/api/mcp
toolName: microsoft_docs_search
conversationId: =System.ConversationId
arguments:
query: =Local.SearchQuery
output:
result: Local.SearchResults
autoSend: true
# Summarize results with agent
- kind: InvokeAzureAgent
id: summarize
agent:
name: SummaryAgent
conversationId: =System.ConversationId
input:
messages: =UserMessage("Summarize these search results")
필수 조건
시작하기 전에 다음 사항을 확인하세요.
- Python 3.10 - 3.13(PowerFx 호환성으로 인해 Python 3.14가 아직 지원되지 않음)
- 설치된 에이전트 프레임워크 선언적 패키지:
pip install agent-framework-declarative --pre
이 패키지는 기본 agent-framework-core를 자동으로 불러온다.
- YAML 구문에 대한 기본 숙지
- 워크플로 개념 이해
첫 번째 선언적 워크플로
이름으로 사용자를 맞이하는 간단한 워크플로를 만들어 보겠습니다.
1단계: YAML 파일 만들기
다음과 같은 파일을 만듭니다 greeting-workflow.yaml.
name: greeting-workflow
description: A simple workflow that greets the user
inputs:
name:
type: string
description: The name of the person to greet
actions:
# Set a greeting prefix
- kind: SetVariable
id: set_greeting
displayName: Set greeting prefix
variable: Local.greeting
value: Hello
# Build the full message using an expression
- kind: SetVariable
id: build_message
displayName: Build greeting message
variable: Local.message
value: =Concat(Local.greeting, ", ", Workflow.Inputs.name, "!")
# Send the greeting to the user
- kind: SendActivity
id: send_greeting
displayName: Send greeting to user
activity:
text: =Local.message
# Store the result in outputs
- kind: SetVariable
id: set_output
displayName: Store result in outputs
variable: Workflow.Outputs.greeting
value: =Local.message
2단계: 워크플로 로드 및 실행
워크플로를 실행하는 Python 파일을 만듭니다.
import asyncio
from pathlib import Path
from agent_framework.declarative import WorkflowFactory
async def main() -> None:
"""Run the greeting workflow."""
# Create a workflow factory
factory = WorkflowFactory()
# Load the workflow from YAML
workflow_path = Path(__file__).parent / "greeting-workflow.yaml"
workflow = factory.create_workflow_from_yaml_path(workflow_path)
print(f"Loaded workflow: {workflow.name}")
print("-" * 40)
# Run with a name input
result = await workflow.run({"name": "Alice"})
for output in result.get_outputs():
print(f"Output: {output}")
for output in result.get_intermediate_outputs():
print(f"Intermediate: {output}")
if __name__ == "__main__":
asyncio.run(main())
예상 출력
Loaded workflow: greeting-workflow
----------------------------------------
Output: Hello, Alice!
핵심 개념
변수 네임스페이스
선언적 워크플로는 이름 간격 변수를 사용하여 상태를 구성합니다.
| 네임스페이스 | 설명 | Example |
|---|---|---|
Local.* |
워크플로에 로컬 변수 | Local.message |
Workflow.Inputs.* |
입력 매개 변수 | Workflow.Inputs.name |
Workflow.Outputs.* |
출력 값 | Workflow.Outputs.result |
System.* |
시스템 제공 값 | System.ConversationId |
개체 특성 경로
Python 상태 경로가 중첩된 값을 트래버스할 때 개체 특성과 사전 키를 구분합니다. 사전 키는 정확한 문자열 값을 유지하지만 개체 특성 세그먼트는 일치 [A-Za-z][A-Za-z0-9_]*해야 합니다.
예를 들어 Local.profile.display_name 공용 개체 특성을 읽을 수 있습니다.
Local.profile._private
Local.profile.display-name 개체 특성에 액세스하지 않고 조회 기본값을 반환하지 않습니다.
Local.bag가 사전이면 _private는 여전히 해당 Local.bag._private 키를 읽습니다. UUID, 하이픈을 넣은 이름 및 기타 불규칙한 문자열도 유효한 사전 키로 유지됩니다.
개체 지원 상태를 마이그레이션할 때 지원되는 패턴과 일치하는 공용 특성을 노출하거나 불규칙한 이름을 사전 키로 저장합니다.
식 언어
접두사로 = 지정된 값은 식으로 평가됩니다.
# Literal value (no evaluation)
value: Hello
# Expression (evaluated at runtime)
value: =Concat("Hello, ", Workflow.Inputs.name)
일반적인 함수는 다음과 같습니다.
-
Concat(str1, str2, ...)- 문자열 연결 -
If(condition, trueValue, falseValue)-조건식 -
IsBlank(value)- 값이 비어 있는지 확인
PowerFx 상태 순회 제한
Python은 상태를 기록하거나 스냅샷을 생성하기 전에, 그리고 값을 PowerFx 기호로 변환하기 전에 선언적 상태의 유효성을 검사합니다. 순환 구조는 잘라내는 대신 거부됩니다.
각 순회는 다음과 같은 고정된 제한을 사용합니다.
- 루트 값이 깊이 0인 최대 깊이는 64입니다.
- 컨테이너와 매핑 키를 포함해 최대 10,000개의 값을 탐색합니다.
- 최대 집계 크기는 1,048,576개 문자열 문자 및 이진 바이트입니다.
반복 참조 및 별칭은 트래버스될 때마다 다시 계산됩니다. 상태 쓰기나 스냅샷 중 또는 PowerFx 변환 중에 순환 또는 한도 초과가 ValueError를 발생시킵니다. 이러한 순회 제한은 PowerFx 식 실행 또는 애플리케이션 정의 Python 복사 또는 변환 후크를 바인딩하지 않습니다.
작업 형식
선언적 워크플로는 다음과 같은 다양한 작업 유형을 지원합니다.
| Category | 활동 |
|---|---|
| 변수 관리 |
SetVariable, SetMultipleVariables, ResetVariable |
| 제어 흐름 |
If, ConditionGroup, Foreach, BreakLoop, ContinueLoopGotoAction |
| 출력 | SendActivity |
| 에이전트 호출 | InvokeAzureAgent |
| 도구 호출 |
InvokeFunctionTool, InvokeMcpTool |
| HTTP | HttpRequestAction |
| 휴먼 인 더 루프 |
Question, RequestExternalInput |
| 워크플로 제어 |
EndWorkflow, EndConversation, CreateConversation |
작업 참조
작업은 선언적 워크플로의 구성 요소입니다. 각 작업은 특정 작업을 수행하고 작업은 YAML 파일에 나타나는 순서대로 순차적으로 실행됩니다.
작업 구조
모든 작업은 공통 속성을 공유합니다.
- kind: ActionType # Required: The type of action
id: unique_id # Optional: Unique identifier for referencing
displayName: Name # Optional: Human-readable name for logging
# Action-specific properties...
변수 관리 작업
변수 설정
변수를 지정된 값으로 설정합니다.
- kind: SetVariable
id: set_greeting
displayName: Set greeting message
variable: Local.greeting
value: Hello World
다음 표현식과 함께:
- kind: SetVariable
variable: Local.fullName
value: =Concat(Workflow.Inputs.firstName, " ", Workflow.Inputs.lastName)
속성:
| 속성 | 필수 | 설명 |
|---|---|---|
variable |
Yes | 변수 경로(예: Local.name, Workflow.Outputs.result) |
value |
Yes | 설정할 값(리터럴 또는 식) |
비고
Python은 SetValue 대상 속성 대신 path 사용하는 variable 작업 종류도 지원합니다.
SetVariable(와(과) variable), SetValue(와(과) path)는 동일한 결과를 달성합니다. 다음은 그 예입니다.
- kind: SetValue
id: set_greeting
path: Local.greeting
value: Hello World
다중 변수 설정
단일 작업에서 여러 변수를 설정합니다.
- kind: SetMultipleVariables
id: initialize_vars
displayName: Initialize variables
variables:
Local.counter: 0
Local.status: pending
Local.message: =Concat("Processing order ", Workflow.Inputs.orderId)
속성:
| 속성 | 필수 | 설명 |
|---|---|---|
variables |
Yes | 값에 대한 변수 경로 매핑 |
변수초기화
변수의 값을 지웁니다.
- kind: ResetVariable
id: clear_counter
variable: Local.counter
속성:
| 속성 | 필수 | 설명 |
|---|---|---|
variable |
Yes | 재설정할 변수 경로 |
제어 흐름 작업
만약에
조건에 따라 조건부로 작업을 실행합니다.
- kind: If
id: check_age
displayName: Check user age
condition: =Workflow.Inputs.age >= 18
then:
- kind: SendActivity
activity:
text: "Welcome, adult user!"
else:
- kind: SendActivity
activity:
text: "Welcome, young user!"
중첩 조건:
- kind: If
condition: =Workflow.Inputs.role = "admin"
then:
- kind: SendActivity
activity:
text: "Admin access granted"
else:
- kind: If
condition: =Workflow.Inputs.role = "user"
then:
- kind: SendActivity
activity:
text: "User access granted"
else:
- kind: SendActivity
activity:
text: "Access denied"
속성:
| 속성 | 필수 | 설명 |
|---|---|---|
condition |
Yes | true/false로 평가되는 식 |
then |
Yes | 조건이 true인 경우 실행할 작업 |
else |
No | 조건이 false인 경우 실행할 작업 |
ConditionGroup
switch/case 문과 같은 여러 조건을 평가합니다.
- kind: ConditionGroup
id: route_by_category
displayName: Route based on category
conditions:
- condition: =Workflow.Inputs.category = "electronics"
id: electronics_branch
actions:
- kind: SetVariable
variable: Local.department
value: Electronics Team
- condition: =Workflow.Inputs.category = "clothing"
id: clothing_branch
actions:
- kind: SetVariable
variable: Local.department
value: Clothing Team
- condition: =Workflow.Inputs.category = "food"
id: food_branch
actions:
- kind: SetVariable
variable: Local.department
value: Food Team
elseActions:
- kind: SetVariable
variable: Local.department
value: General Support
속성:
| 속성 | 필수 | 설명 |
|---|---|---|
conditions |
Yes | 조건/작업 쌍 목록(먼저 일치하는 항목이 우선) |
elseActions |
No | 일치하는 조건이 없는 경우의 작업 |
각각
컬렉션의 요소를 반복 처리합니다.
- kind: Foreach
id: process_items
displayName: Process each item
source: =Workflow.Inputs.items
itemName: item
indexName: index
actions:
- kind: SendActivity
activity:
text: '=Concat("Processing item ", index, ": ", item)'
속성:
| 속성 | 필수 | 설명 |
|---|---|---|
source |
Yes | 컬렉션을 반환하는 식 |
itemName |
No | 현재 항목의 변수 이름(기본값: item) |
indexName |
No | 현재 인덱스 변수 이름(기본값: index) |
actions |
Yes | 각 항목에 대해 실행할 작업 |
BreakLoop
현재 루프를 즉시 종료합니다.
- kind: Foreach
source: =Workflow.Inputs.items
actions:
- kind: If
condition: =item = "stop"
then:
- kind: BreakLoop
- kind: SendActivity
activity:
text: =item
ContinueLoop
루프의 다음 반복으로 건너뜁니다.
- kind: Foreach
source: =Workflow.Inputs.numbers
actions:
- kind: If
condition: =item < 0
then:
- kind: ContinueLoop
- kind: SendActivity
activity:
text: '=Concat("Positive number: ", item)'
GotoAction
ID별로 특정 작업으로 이동합니다.
- kind: SetVariable
id: start_label
variable: Local.attempts
value: =Local.attempts + 1
- kind: SendActivity
activity:
text: =Concat("Attempt ", Local.attempts)
- kind: If
condition: =And(Local.attempts < 3, Not(Local.success))
then:
- kind: GotoAction
actionId: start_label
속성:
| 속성 | 필수 | 설명 |
|---|---|---|
actionId |
Yes | 다음으로 이동할 작업의 ID: |
출력 작업
SendActivity
사용자에게 메시지를 보냅니다.
- kind: SendActivity
id: send_welcome
displayName: Send welcome message
activity:
text: "Welcome to our service!"
다음 표현식과 함께:
- kind: SendActivity
activity:
text: =Concat("Hello, ", Workflow.Inputs.name, "! How can I help you today?")
속성:
| 속성 | 필수 | 설명 |
|---|---|---|
activity |
Yes | 전송할 작업 |
activity.text |
Yes | 메시지 텍스트(리터럴 또는 식) |
Python에서는 =로 시작하는 작성된 텍스트가 평가되어 데이터로 출력됩니다. 표현식 결과는 {Variable.Path} 보간을 위해 다시 처리되지 않습니다.
Hello, {Local.name}!와 같이 템플릿을 직접 작성하거나, ="Hello, " & Local.name & "!"와 같이 표현식에서 전체 문자열을 작성하세요.
에이전트 호출 작업
InvokeAzureAgent
Azure AI 에이전트를 호출합니다.
기본 호출:
- kind: InvokeAzureAgent
id: call_assistant
displayName: Call assistant agent
agent:
name: AssistantAgent
conversationId: =System.ConversationId
입출력 구성을 사용하여:
- kind: InvokeAzureAgent
id: call_analyst
displayName: Call analyst agent
agent:
name: AnalystAgent
conversationId: =System.ConversationId
input:
messages: =Local.userMessage
arguments:
topic: =Workflow.Inputs.topic
output:
responseObject: Local.AnalystResult
messages: Local.AnalystMessages
autoSend: true
외부 루프 사용(조건이 충족될 때까지 계속):
- kind: InvokeAzureAgent
id: support_agent
agent:
name: SupportAgent
input:
externalLoop:
when: =Not(Local.IsResolved)
output:
responseObject: Local.SupportResult
속성:
| 속성 | 필수 | 설명 |
|---|---|---|
agent.name |
Yes | 등록된 에이전트의 이름 |
conversationId |
No | 대화 컨텍스트 식별자 |
input.messages |
No | 에이전트에 보낼 메시지 |
input.arguments |
No | 에이전트 입력 텍스트를 평가하고 추가할 인수 |
input.externalLoop.when |
No | 에이전트 루프를 계속하기 위한 조건 |
output.responseObject |
No | 에이전트 응답을 저장하는 경로 |
output.messages |
No | 대화 메시지를 저장하는 경로 |
output.autoSend |
No | 워크플로 출력에 응답을 자동으로 보냅니다. 부울 값 또는 = 식을 받으며, 기본값은 true입니다. |
Python에서는 InvokeAzureAgent 구성된 각 input.arguments 값을 평가하고 비어 있지 않은 인수를 key: value 줄로 서식 지정합니다.
input.messages를 설정하면 평가된 메시지 텍스트가 인수 줄 뒤에 옵니다.
input.arguments에 하나 이상의 인수가 포함되어 있고 input.messages를 생략하면, Python은 인수 텍스트만 사용하며 System.LastMessage.Text, Workflow.Inputs, Local.input 또는 Local.userInput의 대체 값을 추가하지 않습니다.
Python은 재개된 외부 루프 차례를 포함해 각 호출 전에 현재 워크플로 상태와 output.autoSend를 대조하여 평가합니다. 결과가 false인 경우 자동 워크플로 출력만 숨겨집니다. 에이전트는 계속 실행되며, 구성된 응답 출력과 메시지 출력은 계속 저장됩니다.
도구 및 HTTP 작업
InvokeFunctionTool
AI 에이전트를 거치지 않고 워크플로에서 직접 등록된 Python 함수를 호출합니다.
- kind: InvokeFunctionTool
id: invoke_weather
displayName: Get weather data
functionName: get_weather
arguments:
location: =Local.location
unit: =Local.unit
output:
result: Local.weatherInfo
messages: Local.weatherToolCallItems
autoSend: true
속성:
| 속성 | 필수 | 설명 |
|---|---|---|
functionName |
Yes | 호출할 등록된 함수의 이름 |
arguments |
No | 함수에 전달할 인수 |
output.result |
No | 함수 결과를 저장할 경로 |
output.messages |
No | 함수 메시지를 저장하는 경로 |
output.autoSend |
No | 결과를 워크플로 출력으로 자동으로 보냅니다. 부울 값 또는 = 식을 받으며, 기본값은 true입니다. |
Python 도구가 output.autoSend 실행되기 직전에 현재 워크플로 상태에 대해 평가됩니다. 결과가 false인 경우 자동 워크플로 출력만 억제되며, 도구는 계속 실행되고 구성된 결과 출력과 메시지 출력은 여전히 저장됩니다. 거부된 승인 요청은 표현식을 평가하지 않고 완료됩니다.
InvokeFunctionTool에 대한 Python 설정:
함수는 WorkflowFactory 와(과) register_tool를 사용하여 등록해야 합니다.
from agent_framework.declarative import WorkflowFactory
# Define your functions
def get_weather(location: str, unit: str = "F") -> dict:
"""Get weather information for a location."""
# Your implementation here
return {"location": location, "temp": 72, "unit": unit}
def format_message(template: str, data: dict) -> str:
"""Format a message template with data."""
return template.format(**data)
# Register functions with the factory
factory = (
WorkflowFactory()
.register_tool("get_weather", get_weather)
.register_tool("format_message", format_message)
)
# Load and run the workflow
workflow = factory.create_workflow_from_yaml_path("workflow.yaml")
result = await workflow.run({"location": "Seattle", "unit": "F"})
InvokeMcpTool
구성된 MCPToolHandler을 통해 MCP 서버에서 도구를 호출합니다.
- kind: InvokeMcpTool
id: search_docs
serverUrl: https://learn.microsoft.com/api/mcp
serverLabel: microsoft_docs
toolName: microsoft_docs_search
arguments:
query: =Local.searchQuery
output:
result: Local.searchResults
messages: Local.toolMessage
autoSend: true
속성:
| 속성 | 필수 | 설명 |
|---|---|---|
serverUrl |
Yes | MCP 서버 URL |
toolName |
Yes | MCP 서버의 도구 이름 |
serverLabel |
No | 사람이 읽을 수 있는 서버 레이블 |
arguments |
No | 도구에 전달된 인수 |
headers |
No | 요청 헤더; 빈 값 건너뛰기 |
connection.name |
No | 사용자 지정 처리기에 대한 명명된 연결 |
conversationId |
No | 대화에 성공적인 도구 출력 추가 |
requireApproval |
No | 도구를 호출하기 전에 승인을 요청합니다. |
output.result |
No | 구문 분석된 도구 출력을 저장하는 경로 |
output.messages |
No | 도구 메시지를 저장할 경로 |
output.autoSend |
No | 워크플로 결과에 도구 출력을 내보낸다. 기본값: true |
requireApproval이 true인 경우, Python 런타임은 평가된 작업 헤더를 승인 요청에 바인딩합니다. 요청에는 헤더 이름이 포함되지만 헤더 값은 포함되지 않습니다. 헤더 변경, 자격 증명 회전 또는 복원된 레거시 요청에 확인 가능한 바인딩 상태가 없는 경우 워크플로는 새 요청 ID로 대체 승인 요청을 내보내고 도구를 호출하지 않습니다. 별도의 확인 키가 포함되어 있으므로 워크플로 검사점 스토리지를 보호합니다. 사용자 지정 헨들러는 작업의 headers 외부에서 확인되는 자격 증명에 대해 여전히 책임이 있습니다.
InvokeMcpTool의 Python 설정:
MCP 도구 처리기를 WorkflowFactory에 전달합니다. 인증, 관리되는 연결 또는 URL 허용 목록이 필요한 경우 사용자 지정 처리기를 사용합니다.
from agent_framework.declarative import DefaultMCPToolHandler, WorkflowFactory
factory = WorkflowFactory(mcp_tool_handler=DefaultMCPToolHandler())
workflow = factory.create_workflow_from_yaml_path("workflow.yaml")
client_provider 없이 DefaultMCPToolHandler는 크기가 제한된 캐시를 통해 MCP 세션을 재사용합니다.
client_provider를 사용하면 공급자가 동일한 클라이언트 또는 tools/list를 반환하는 경우에도 None를 포함한 모든 호출마다 별도의 MCP 도구와 세션을 생성하고 종료합니다. 처리기는 내부적으로 만든 대체 HTTP 클라이언트를 닫지만 호출자 제공 httpx.AsyncClient 인스턴스는 호출자 소유로 유지됩니다.
공급자 지원 호출은 서버 세션 상태를 유지하지 않습니다. 워크플로에 MCP 세션 연속성이 필요한 경우 명시적 인증 및 수명 계약을 사용하여 사용자 지정 MCPToolHandler 을 구현합니다.
HttpRequestAction
구성된 HttpRequestHandler을 통해 HTTP 요청을 보냅니다. 정상적인 JSON 응답은 할당되기 전에 파싱됩니다. 2xx가 아닌 응답이 작업에 실패합니다. Python 오류에는 요청 URL 및 상태 코드가 포함되지만 응답 본문은 생략됩니다.
- kind: HttpRequestAction
id: fetch_repo_info
method: GET
url: =Concat("https://api.github.com/repos/", Local.repoName)
headers:
Accept: application/vnd.github+json
User-Agent: agent-framework
queryParameters:
per_page: 10
response: Local.repoInfo
responseHeaders: Local.repoHeaders
속성:
| 속성 | 필수 | 설명 |
|---|---|---|
url |
Yes | 절대 요청 URL |
method |
No | HTTP 메서드; 기본값: GET |
headers |
No | 요청 헤더 |
queryParameters |
No | URL에 추가된 쿼리 매개 변수 |
body |
No | 요청 본문; kind: json, raw 또는 none를 사용하십시오. |
requestTimeoutInMilliseconds |
No | 요청당 시간 제한 |
connection.name |
No | 사용자 지정 처리기에 대한 명명된 연결 |
conversationId |
No | 대화에 성공적인 응답 본문을 추가합니다. |
response |
No | 구문 분석된 응답 본문을 저장하는 경로 |
responseHeaders |
No | 응답 헤더를 저장하는 경로 |
Python 설정: HttpRequestAction을 위한
WorkflowFactory에 HTTP 요청 처리기를 전달합니다. 인증, 재시도 또는 URL 허용 목록이 필요한 경우 사용자 지정 처리기를 사용합니다.
from agent_framework.declarative import DefaultHttpRequestHandler, WorkflowFactory
factory = WorkflowFactory(http_request_handler=DefaultHttpRequestHandler())
workflow = factory.create_workflow_from_yaml_path("workflow.yaml")
기본 처리기는 절대 HTTP 또는 HTTPS URL이 아닌 URL을 거부합니다. 기존 쿼리 순서와 바이트를 유지하면서 URL을 정규화하고 기존 쿼리 후에 추가 queryParameters 합니다.
client_provider는 정규화된 HttpRequestInfo 스냅샷을 받습니다. 스냅샷에는 info.url에 조합된 URL이 있고 비어 있는 info.query_parameters가 있으며, 원본 요청 정보는 수정되지 않습니다. 선택한 클라이언트의 기본값은 계속 적용됩니다. 리디렉션 동작은 해당 클라이언트의 follow_redirects 설정에 의해 제어되며 공급자는 리디렉션 홉에 대해 다시 호출되지 않습니다.
기본 처리기는 내부적으로 소유한 HTTP 클라이언트를 다시 사용하지만 응답 쿠키는 유지하지 않습니다. 워크플로에 인증, 세션 연속성 또는 로드 밸런서 선호도를 위해 쿠키가 필요한 경우, 구성된 httpx.AsyncClient을(를) client=을(를) 통해 전달하거나 client_provider=에서 이를 반환합니다. 쿠키를 포함하는 클라이언트는 하나의 인증된 주체에만 한정하고, 애플리케이션에서 호출자가 제공한 클라이언트는 닫으세요. 명시적 Cookie 요청 헤더 및 응답 Set-Cookie 헤더는 계속 사용할 수 있습니다.
인간 참여형 작업
Question
사용자에게 질문을 하고 응답을 저장합니다.
C# 및 Python SDK는 질문 프롬프트에 다른 필드 이름을 사용합니다. 이 섹션에서는 텍스트를 요청하고 워크플로 상태에 답변을 저장하는 가장 작은 공유 동작을 사용합니다.
- kind: Question
id: ask_name
displayName: Ask for user name
question:
text: "What is your name?"
property: Local.userName
속성:
| 속성 | 필수 | 설명 |
|---|---|---|
question |
Yes | 물어봐야 할 질문입니다. 중첩된 폼이나 압축 폼의 문자열 값에 사용합니다 question.text . |
variable |
Yes | 응답을 저장할 경로입니다. C# 대상 필드 이름과 정렬하려는 경우 대체 최상위 필드 이름으로 사용합니다 property . |
default |
No | 호출자가 적용할 요청 메타데이터에 포함된 기본값입니다. |
choices |
No | 메타데이터를 요청하는 데 추가된 선택 항목 목록입니다. 각 옵션은 문자열이거나 label 필드와 선택적 value 필드가 있는 객체일 수 있습니다. |
allowFreeText |
No | 호출자가 구성된 choices외부의 텍스트를 허용할 수 있는지 여부를 나타냅니다. 기본값은 true입니다. |
Python 호환성을 위해 대체 필드 이름도 허용합니다. 대신 최상위 수준을 textquestiondefaultValuedefault사용합니다.
- kind: Question
id: ask_color
text: "Choose a color:"
property: Local.color
choices:
- value: red
label: Red
- blue
allowFreeText: false
외부입력요청
외부 시스템 또는 프로세스에서 입력을 요청합니다.
- kind: RequestExternalInput
id: request_approval
displayName: Request manager approval
prompt:
text: "Please provide approval for this request."
variable: Local.approvalResult
default: "pending"
속성:
| 속성 | 필수 | 설명 |
|---|---|---|
prompt.text |
Yes | 필수 입력에 대한 설명 |
variable |
Yes | 입력을 저장할 경로 |
default |
No | 기본값 |
워크플로 제어 작업
EndWorkflow
워크플로 실행을 종료합니다.
- kind: EndWorkflow
id: finish
displayName: End workflow
대화 종료
현재 대화를 종료합니다.
- kind: EndConversation
id: end_chat
displayName: End conversation
대화생성
새 대화 컨텍스트를 만듭니다.
- kind: CreateConversation
id: create_new_conv
displayName: Create new conversation
conversationId: Local.NewConversationId
속성:
| 속성 | 필수 | 설명 |
|---|---|---|
conversationId |
Yes | 새 대화 ID를 저장하는 경로 |
작업에 대한 빠른 참조
| 조치 | Category | 설명 |
|---|---|---|
SetVariable |
Variable | 단일 변수 설정 |
SetMultipleVariables |
Variable | 여러 변수 설정 |
ResetVariable |
Variable | 변수 지우기 |
If |
제어 흐름 | 조건부 분기 |
ConditionGroup |
제어 흐름 | 다중 분기 스위치 |
Foreach |
제어 흐름 | 컬렉션 반복 |
BreakLoop |
제어 흐름 | 현재 루프 종료 |
ContinueLoop |
제어 흐름 | 다음 반복으로 건너뛰기 |
GotoAction |
제어 흐름 | ID별 작업으로 이동 |
SendActivity |
출력 | 사용자에게 메시지 보내기 |
InvokeAzureAgent |
대리인 | Azure AI 에이전트 호출 |
InvokeFunctionTool |
Tool | 등록된 함수 호출 |
InvokeMcpTool |
Tool | MCP 서버 도구 호출 |
HttpRequestAction |
HTTP | HTTP 엔드포인트 호출 |
Question |
휴먼 인 더 루프 | 사용자에게 질문하기 |
RequestExternalInput |
휴먼 인 더 루프 | 외부 입력 요청 |
EndWorkflow |
워크플로 제어 | 워크플로 종료 |
EndConversation |
워크플로 제어 | 대화 종료 |
CreateConversation |
워크플로 제어 | 새 대화 만들기 |
식 구문
선언적 워크플로는 PowerFx와 유사한 식 언어를 사용하여 상태를 관리하고 동적 값을 계산합니다. 접두사로 = 지정된 값은 런타임에 식으로 평가됩니다.
변수 네임스페이스 세부 정보
| 네임스페이스 | 설명 | Access |
|---|---|---|
Local.* |
워크플로-지역 변수 | 읽기/쓰기 |
Workflow.Inputs.* |
워크플로에 전달된 입력 매개 변수 | 읽기 전용 |
Workflow.Outputs.* |
워크플로에서 반환된 값 | 읽기/쓰기 |
System.* |
시스템 제공 값 | 읽기 전용 |
Agent.* |
에이전트 호출 결과 | 읽기 전용 |
시스템 변수
| Variable | 설명 |
|---|---|
System.ConversationId |
현재 대화 식별자 |
System.LastMessage |
최신 메시지 |
System.Timestamp |
현재 타임스탬프 |
에이전트 변수
에이전트를 호출한 후 출력 변수를 통해 응답 데이터에 액세스합니다.
actions:
- kind: InvokeAzureAgent
id: call_assistant
agent:
name: MyAgent
output:
responseObject: Local.AgentResult
# Access agent response
- kind: SendActivity
activity:
text: =Local.AgentResult.text
리터럴 값과 식 값 비교
# Literal string (stored as-is)
value: Hello World
# Expression (evaluated at runtime)
value: =Concat("Hello ", Workflow.Inputs.name)
# Literal number
value: 42
# Expression returning a number
value: =Workflow.Inputs.quantity * 2
문자열 작업
Concat
여러 문자열을 연결합니다.
value: =Concat("Hello, ", Workflow.Inputs.name, "!")
# Result: "Hello, Alice!" (if Workflow.Inputs.name is "Alice")
value: =Concat(Local.firstName, " ", Local.lastName)
# Result: "John Doe" (if firstName is "John" and lastName is "Doe")
IsBlank
값이 비어 있는지 또는 정의되지 않은지 확인합니다.
condition: =IsBlank(Workflow.Inputs.optionalParam)
# Returns true if the parameter is not provided
value: =If(IsBlank(Workflow.Inputs.name), "Guest", Workflow.Inputs.name)
# Returns "Guest" if name is blank, otherwise returns the name
조건식
If 함수
조건에 따라 다른 값을 반환합니다.
value: =If(Workflow.Inputs.age < 18, "minor", "adult")
value: =If(Local.count > 0, "Items found", "No items")
# Nested conditions
value: =If(Workflow.Inputs.role = "admin", "Full access", If(Workflow.Inputs.role = "user", "Limited access", "No access"))
비교 연산자
| 오퍼레이터 | 설명 | Example |
|---|---|---|
= |
다음과 같음 | =Workflow.Inputs.status = "active" |
<> |
같지 않음 | =Workflow.Inputs.status <> "deleted" |
< |
미만 | =Workflow.Inputs.age < 18 |
> |
보다 크다 | =Workflow.Inputs.count > 0 |
<= |
작거나 같음 | =Workflow.Inputs.score <= 100 |
>= |
크거나 같음 | =Workflow.Inputs.quantity >= 1 |
부울 함수
# Or - returns true if any condition is true
condition: =Or(Workflow.Inputs.role = "admin", Workflow.Inputs.role = "moderator")
# And - returns true if all conditions are true
condition: =And(Workflow.Inputs.age >= 18, Workflow.Inputs.hasConsent)
# Not - negates a condition
condition: =Not(IsBlank(Workflow.Inputs.email))
수학 연산
# Addition
value: =Workflow.Inputs.price + Workflow.Inputs.tax
# Subtraction
value: =Workflow.Inputs.total - Workflow.Inputs.discount
# Multiplication
value: =Workflow.Inputs.quantity * Workflow.Inputs.unitPrice
# Division
value: =Workflow.Inputs.total / Workflow.Inputs.count
실제 수식 예제
사용자 분류
name: categorize-user
inputs:
age:
type: integer
description: User's age
actions:
- kind: SetVariable
variable: Local.age
value: =Workflow.Inputs.age
- kind: SetVariable
variable: Local.category
value: =If(Local.age < 13, "child", If(Local.age < 20, "teenager", If(Local.age < 65, "adult", "senior")))
- kind: SendActivity
activity:
text: '=Concat("You are categorized as: ", Local.category)'
- kind: SetVariable
variable: Workflow.Outputs.category
value: =Local.category
조건부 인사말
name: smart-greeting
inputs:
name:
type: string
description: User's name (optional)
timeOfDay:
type: string
description: morning, afternoon, or evening
actions:
# Set the greeting based on time of day
- kind: SetVariable
variable: Local.timeGreeting
value: =If(Workflow.Inputs.timeOfDay = "morning", "Good morning", If(Workflow.Inputs.timeOfDay = "afternoon", "Good afternoon", "Good evening"))
# Handle optional name
- kind: SetVariable
variable: Local.userName
value: =If(IsBlank(Workflow.Inputs.name), "friend", Workflow.Inputs.name)
# Build the full greeting
- kind: SetVariable
variable: Local.fullGreeting
value: =Concat(Local.timeGreeting, ", ", Local.userName, "!")
- kind: SendActivity
activity:
text: =Local.fullGreeting
입력 유효성 검사
name: validate-order
inputs:
quantity:
type: integer
description: Number of items to order
email:
type: string
description: Customer email
actions:
# Check if inputs are valid
- kind: SetVariable
variable: Local.isValidQuantity
value: =And(Workflow.Inputs.quantity > 0, Workflow.Inputs.quantity <= 100)
- kind: SetVariable
variable: Local.hasEmail
value: =Not(IsBlank(Workflow.Inputs.email))
- kind: SetVariable
variable: Local.isValid
value: =And(Local.isValidQuantity, Local.hasEmail)
- kind: If
condition: =Local.isValid
then:
- kind: SendActivity
activity:
text: "Order validated successfully!"
else:
- kind: SendActivity
activity:
text: =If(Not(Local.isValidQuantity), "Invalid quantity (must be 1-100)", "Email is required")
고급 패턴
워크플로가 복잡해짐에 따라 다단계 프로세스, 에이전트 조정 및 대화형 시나리오를 처리하는 패턴이 필요합니다.
다중 에이전트 오케스트레이션
순차 에이전트 파이프라인
작업을 여러 에이전트를 순서대로 거치게 하며, 각 에이전트는 이전 에이전트의 출력을 기반으로 작업을 수행합니다.
사용 사례: 다양한 전문가가 연구, 쓰기 및 편집을 처리하는 콘텐츠 만들기 파이프라인입니다.
name: content-pipeline
description: Sequential agent pipeline for content creation
kind: Workflow
trigger:
kind: OnConversationStart
id: content_workflow
actions:
# First agent: Research and analyze
- kind: InvokeAzureAgent
id: invoke_researcher
displayName: Research phase
conversationId: =System.ConversationId
agent:
name: ResearcherAgent
# Second agent: Write draft based on research
- kind: InvokeAzureAgent
id: invoke_writer
displayName: Writing phase
conversationId: =System.ConversationId
agent:
name: WriterAgent
# Third agent: Edit and polish
- kind: InvokeAzureAgent
id: invoke_editor
displayName: Editing phase
conversationId: =System.ConversationId
agent:
name: EditorAgent
Python 설정:
from agent_framework.declarative import WorkflowFactory
# Create factory and register agents
factory = WorkflowFactory()
factory.register_agent("ResearcherAgent", researcher_agent)
factory.register_agent("WriterAgent", writer_agent)
factory.register_agent("EditorAgent", editor_agent)
# Load and run
workflow = factory.create_workflow_from_yaml_path("content-pipeline.yaml")
result = await workflow.run({"topic": "AI in healthcare"})
조건부 에이전트 라우팅
입력 또는 중간 결과에 따라 요청을 다른 에이전트로 라우팅합니다.
사용 사례: 문제 유형에 따라 특수 에이전트로 라우팅되는 시스템을 지원합니다.
name: support-router
description: Route to specialized support agents
inputs:
category:
type: string
description: Support category (billing, technical, general)
actions:
- kind: ConditionGroup
id: route_request
displayName: Route to appropriate agent
conditions:
- condition: =Workflow.Inputs.category = "billing"
id: billing_route
actions:
- kind: InvokeAzureAgent
id: billing_agent
agent:
name: BillingAgent
conversationId: =System.ConversationId
- condition: =Workflow.Inputs.category = "technical"
id: technical_route
actions:
- kind: InvokeAzureAgent
id: technical_agent
agent:
name: TechnicalAgent
conversationId: =System.ConversationId
elseActions:
- kind: InvokeAzureAgent
id: general_agent
agent:
name: GeneralAgent
conversationId: =System.ConversationId
외부 루프가 있는 에이전트
해결되는 문제와 같은 조건이 충족될 때까지 에이전트 상호 작용을 계속합니다.
사용 사례: 사용자의 문제가 해결될 때까지 계속되는 대화를 지원합니다.
name: support-conversation
description: Continue support until resolved
actions:
- kind: SetVariable
variable: Local.IsResolved
value: false
- kind: InvokeAzureAgent
id: support_agent
displayName: Support agent with external loop
agent:
name: SupportAgent
conversationId: =System.ConversationId
input:
externalLoop:
when: =Not(Local.IsResolved)
output:
responseObject: Local.SupportResult
- kind: SendActivity
activity:
text: "Thank you for contacting support. Your issue has been resolved."
루프 컨트롤 패턴
반복 에이전트 대화
제어된 반복을 사용하여 에이전트 간에 앞뒤로 대화를 만듭니다.
사용 사례: 학생-교사 시나리오, 토론 시뮬레이션 또는 반복적인 구체화.
name: student-teacher
description: Iterative learning conversation between student and teacher
kind: Workflow
trigger:
kind: OnConversationStart
id: learning_session
actions:
# Initialize turn counter
- kind: SetVariable
id: init_counter
variable: Local.TurnCount
value: 0
- kind: SendActivity
id: start_message
activity:
text: '=Concat("Starting session for: ", Workflow.Inputs.problem)'
# Student attempts solution (loop entry point)
- kind: SendActivity
id: student_label
activity:
text: "\n[Student]:"
- kind: InvokeAzureAgent
id: student_attempt
conversationId: =System.ConversationId
agent:
name: StudentAgent
# Teacher reviews
- kind: SendActivity
id: teacher_label
activity:
text: "\n[Teacher]:"
- kind: InvokeAzureAgent
id: teacher_review
conversationId: =System.ConversationId
agent:
name: TeacherAgent
output:
messages: Local.TeacherResponse
# Increment counter
- kind: SetVariable
id: increment
variable: Local.TurnCount
value: =Local.TurnCount + 1
# Check completion conditions
- kind: ConditionGroup
id: check_completion
conditions:
# Success: Teacher congratulated student
- condition: =Not(IsBlank(Find("congratulations", Local.TeacherResponse)))
id: success_check
actions:
- kind: SendActivity
activity:
text: "Session complete - student succeeded!"
- kind: SetVariable
variable: Workflow.Outputs.result
value: success
# Continue: Under turn limit
- condition: =Local.TurnCount < 4
id: continue_check
actions:
- kind: GotoAction
actionId: student_label
elseActions:
# Timeout: Reached turn limit
- kind: SendActivity
activity:
text: "Session ended - turn limit reached."
- kind: SetVariable
variable: Workflow.Outputs.result
value: timeout
카운터 기반 루프
변수 및 GotoAction을 사용하여 기존 계산 루프를 구현합니다.
name: counter-loop
description: Process items with a counter
actions:
- kind: SetVariable
variable: Local.counter
value: 0
- kind: SetVariable
variable: Local.maxIterations
value: 5
# Loop start
- kind: SetVariable
id: loop_start
variable: Local.counter
value: =Local.counter + 1
- kind: SendActivity
activity:
text: =Concat("Processing iteration ", Local.counter)
# Your processing logic here
- kind: SetVariable
variable: Local.result
value: =Concat("Result from iteration ", Local.counter)
# Check if should continue
- kind: If
condition: =Local.counter < Local.maxIterations
then:
- kind: GotoAction
actionId: loop_start
else:
- kind: SendActivity
activity:
text: "Loop complete!"
BreakLoop을 사용하여 조기 종료
BreakLoop을 사용하여 조건이 충족되면 반복을 일찍 종료합니다.
name: search-workflow
description: Search through items and stop when found
actions:
- kind: SetVariable
variable: Local.found
value: false
- kind: Foreach
source: =Workflow.Inputs.items
itemName: currentItem
actions:
# Check if this is the item we're looking for
- kind: If
condition: =currentItem.id = Workflow.Inputs.targetId
then:
- kind: SetVariable
variable: Local.found
value: true
- kind: SetVariable
variable: Local.result
value: =currentItem
- kind: BreakLoop
- kind: SendActivity
activity:
text: '=Concat("Checked item: ", currentItem.name)'
- kind: If
condition: =Local.found
then:
- kind: SendActivity
activity:
text: '=Concat("Found: ", Local.result.name)'
else:
- kind: SendActivity
activity:
text: "Item not found"
휴먼 인 더 루프 패턴
대화형 설문 조사
사용자로부터 여러 정보를 수집합니다.
name: customer-survey
description: Interactive customer feedback survey
actions:
- kind: SendActivity
activity:
text: "Welcome to our customer feedback survey!"
# Collect name
- kind: Question
id: ask_name
question:
text: "What is your name?"
variable: Local.userName
default: "Anonymous"
- kind: SendActivity
activity:
text: =Concat("Nice to meet you, ", Local.userName, "!")
# Collect rating
- kind: Question
id: ask_rating
question:
text: "How would you rate our service? (1-5)"
variable: Local.rating
default: "3"
# Respond based on rating
- kind: If
condition: =Local.rating >= 4
then:
- kind: SendActivity
activity:
text: "Thank you for the positive feedback!"
else:
- kind: Question
id: ask_improvement
question:
text: "What could we improve?"
variable: Local.feedback
# Collect additional feedback
- kind: RequestExternalInput
id: additional_comments
prompt:
text: "Any additional comments? (optional)"
variable: Local.comments
default: ""
# Summary
- kind: SendActivity
activity:
text: =Concat("Thank you, ", Local.userName, "! Your feedback has been recorded.")
- kind: SetVariable
variable: Workflow.Outputs.survey
value:
name: =Local.userName
rating: =Local.rating
feedback: =Local.feedback
comments: =Local.comments
승인 워크플로
작업을 진행하기 전에 승인을 요청합니다.
name: approval-workflow
description: Request approval before processing
inputs:
requestType:
type: string
description: Type of request
amount:
type: number
description: Request amount
actions:
- kind: SendActivity
activity:
text: =Concat("Processing ", Workflow.Inputs.requestType, " request for $", Workflow.Inputs.amount)
# Check if approval is needed
- kind: If
condition: =Workflow.Inputs.amount > 1000
then:
- kind: SendActivity
activity:
text: "This request requires manager approval."
- kind: Question
id: get_approval
question:
text: =Concat("Do you approve this ", Workflow.Inputs.requestType, " request for $", Workflow.Inputs.amount, "? (yes/no)")
variable: Local.approved
- kind: If
condition: =Local.approved = "yes"
then:
- kind: SendActivity
activity:
text: "Request approved. Processing..."
- kind: SetVariable
variable: Workflow.Outputs.status
value: approved
else:
- kind: SendActivity
activity:
text: "Request denied."
- kind: SetVariable
variable: Workflow.Outputs.status
value: denied
else:
- kind: SendActivity
activity:
text: "Request auto-approved (under threshold)."
- kind: SetVariable
variable: Workflow.Outputs.status
value: auto_approved
복합 오케스트레이션
지원 티켓 워크플로
에이전트 라우팅, 조건부 논리 및 대화 관리와 같은 여러 패턴을 결합하는 포괄적인 예제입니다.
name: support-ticket-workflow
description: Complete support ticket handling with escalation
kind: Workflow
trigger:
kind: OnConversationStart
id: support_workflow
actions:
# Initial self-service agent
- kind: InvokeAzureAgent
id: self_service
displayName: Self-service agent
agent:
name: SelfServiceAgent
conversationId: =System.ConversationId
input:
externalLoop:
when: =Not(Local.ServiceResult.IsResolved)
output:
responseObject: Local.ServiceResult
# Check if resolved by self-service
- kind: If
condition: =Local.ServiceResult.IsResolved
then:
- kind: SendActivity
activity:
text: "Issue resolved through self-service."
- kind: SetVariable
variable: Workflow.Outputs.resolution
value: self_service
- kind: EndWorkflow
id: end_resolved
# Create support ticket
- kind: SendActivity
activity:
text: "Creating support ticket..."
- kind: SetVariable
variable: Local.TicketId
value: =Concat("TKT-", System.ConversationId)
# Route to appropriate team
- kind: ConditionGroup
id: route_ticket
conditions:
- condition: =Local.ServiceResult.Category = "technical"
id: technical_route
actions:
- kind: InvokeAzureAgent
id: technical_support
agent:
name: TechnicalSupportAgent
conversationId: =System.ConversationId
output:
responseObject: Local.TechResult
- condition: =Local.ServiceResult.Category = "billing"
id: billing_route
actions:
- kind: InvokeAzureAgent
id: billing_support
agent:
name: BillingSupportAgent
conversationId: =System.ConversationId
output:
responseObject: Local.BillingResult
elseActions:
# Escalate to human
- kind: SendActivity
activity:
text: "Escalating to human support..."
- kind: SetVariable
variable: Workflow.Outputs.resolution
value: escalated
- kind: SendActivity
activity:
text: =Concat("Ticket ", Local.TicketId, " has been processed.")
모범 사례
명명 규칙
작업 및 변수에 대해 명확하고 설명적인 이름을 사용합니다.
# Good
- kind: SetVariable
id: calculate_total_price
variable: Local.orderTotal
# Avoid
- kind: SetVariable
id: sv1
variable: Local.x
대규모 워크플로 구성
주석을 사용하여 복잡한 워크플로를 논리 섹션으로 나누기:
actions:
# === INITIALIZATION ===
- kind: SetVariable
id: init_status
variable: Local.status
value: started
# === DATA COLLECTION ===
- kind: Question
id: collect_name
# ...
# === PROCESSING ===
- kind: InvokeAzureAgent
id: process_request
# ...
# === OUTPUT ===
- kind: SendActivity
id: send_result
# ...
오류 처리
조건부 검사를 사용하여 잠재적인 문제를 처리합니다.
actions:
- kind: SetVariable
variable: Local.hasError
value: false
- kind: InvokeAzureAgent
id: call_agent
agent:
name: ProcessingAgent
output:
responseObject: Local.AgentResult
- kind: If
condition: =IsBlank(Local.AgentResult)
then:
- kind: SetVariable
variable: Local.hasError
value: true
- kind: SendActivity
activity:
text: "An error occurred during processing."
else:
- kind: SendActivity
activity:
text: =Local.AgentResult.message
테스트 전략
- 간단한 시작: 복잡성을 추가하기 전에 기본 흐름 테스트
- 기본값 사용: 입력에 대한 합리적인 기본값 제공
- 로깅 추가: 개발 중에 디버깅에 SendActivity 사용
- 테스트 에지 사례: 누락되거나 잘못된 입력으로 동작 확인
# Debug logging example
- kind: SendActivity
id: debug_log
activity:
text: '=Concat("[DEBUG] Current state: counter=", Local.counter, ", status=", Local.status)'
다음 단계
-
C# 선언적 워크플로 샘플 - 다음을 비롯한 전체 작업 예제를 살펴봅니다.
- StudentTeacher - 반복 학습을 사용하여 다중 에이전트 대화
- InvokeMcpTool - MCP 서버 도구 통합
- InvokeFunctionTool - 워크플로에서 직접 함수 호출
- FunctionTools - 함수 도구를 사용하는 에이전트
- ToolApproval - 도구 실행에 대한 사용자 승인
- CustomerSupport - 복잡한 지원 티켓 워크플로
- DeepResearch - 여러 에이전트가 있는 연구 워크플로
- Python 선언적 워크플로 샘플 - 전체 작업 예제 살펴보기
비고
이 기능에 대한 지원은 곧 제공될 예정입니다. 최신 상태는 에이전트 프레임워크 Go 리포지토리 를 참조하세요.