중요합니다
이 기능은 실험 단계에 있습니다. 이 단계의 기능은 개발 중이며 미리 보기 또는 릴리스 후보 단계로 넘어가기 전에 변경될 수 있습니다.
개요
이 샘플에서는 AgentGroupChat
사용하여 사용자가 제공한 콘텐츠를 검토하고 다시 쓰기 위해 작업하는 두 에이전트의 공동 작업을 조정하는 방법을 살펴봅니다. 각 에이전트에는 고유한 역할이 할당됩니다.
- 검토자: 작가에 대한 검토 및 방향을 제공합니다.
- 작성자: 검토자의 입력에 따라 사용자 콘텐츠를 업데이트합니다.
이 방법은 코딩 프로세스의 핵심 부분을 밝게 하기 위해 단계별로 세분화됩니다.
시작하기
기능 코딩을 계속하기 전에 개발 환경이 완전히 설정되고 구성되었는지 확인합니다.
먼저 콘솔 프로젝트를 만듭니다. 그런 다음 필요한 모든 종속성을 사용할 수 있도록 다음 패키지 참조를 포함합니다.
명령줄에서 패키지 종속성을 추가하려면 다음 명령을 사용합니다 dotnet
.
dotnet add package Azure.Identity
dotnet add package Microsoft.Extensions.Configuration
dotnet add package Microsoft.Extensions.Configuration.Binder
dotnet add package Microsoft.Extensions.Configuration.UserSecrets
dotnet add package Microsoft.Extensions.Configuration.EnvironmentVariables
dotnet add package Microsoft.SemanticKernel.Connectors.AzureOpenAI
dotnet add package Microsoft.SemanticKernel.Agents.Core --prerelease
Visual Studio에서 NuGet 패키지를 관리하는 경우
Include prerelease
확인합니다.
프로젝트 파일(.csproj
)에는 다음 PackageReference
정의가 포함되어야 합니다.
<ItemGroup>
<PackageReference Include="Azure.Identity" Version="<stable>" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="<stable>" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="<stable>" />
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="<stable>" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="<stable>" />
<PackageReference Include="Microsoft.SemanticKernel.Agents.Core" Version="<latest>" />
<PackageReference Include="Microsoft.SemanticKernel.Connectors.AzureOpenAI" Version="<latest>" />
</ItemGroup>
Agent Framework
실험적이며 경고 억제가 필요합니다. 프로젝트 파일.csproj
()의 속성으로 이 문제를 해결할 수 있습니다.
<PropertyGroup>
<NoWarn>$(NoWarn);CA2007;IDE1006;SKEXP0001;SKEXP0110;OPENAI001</NoWarn>
</PropertyGroup>
먼저 의미 체계 커널 Python 패키지를 설치합니다.
pip install semantic-kernel
다음으로 필요한 가져오기를 추가합니다.
import asyncio
import os
from semantic_kernel import Kernel
from semantic_kernel.agents import AgentGroupChat, ChatCompletionAgent
from semantic_kernel.agents.strategies import (
KernelFunctionSelectionStrategy,
KernelFunctionTerminationStrategy,
)
from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion
from semantic_kernel.contents import ChatHistoryTruncationReducer
from semantic_kernel.functions import KernelFunctionFromPrompt
현재 Java에서 기능을 사용할 수 없습니다.
구성 / 설정
이 샘플에서는 원격 서비스에 연결하기 위해 구성 설정이 필요합니다. OpenAI 또는 Azure OpenAI에 대한 설정을 정의해야 합니다.
# OpenAI
dotnet user-secrets set "OpenAISettings:ApiKey" "<api-key>"
dotnet user-secrets set "OpenAISettings:ChatModel" "gpt-4o"
# Azure OpenAI
dotnet user-secrets set "AzureOpenAISettings:ApiKey" "<api-key>" # Not required if using token-credential
dotnet user-secrets set "AzureOpenAISettings:Endpoint" "<model-endpoint>"
dotnet user-secrets set "AzureOpenAISettings:ChatModelDeployment" "gpt-4o"
다음 클래스는 모든 에이전트 예제에서 사용됩니다. 적절한 기능을 보장하려면 프로젝트에 포함해야 합니다. 이 클래스는 다음 예제의 기본 구성 요소 역할을 합니다.
using System.Reflection;
using Microsoft.Extensions.Configuration;
namespace AgentsSample;
public class Settings
{
private readonly IConfigurationRoot configRoot;
private AzureOpenAISettings azureOpenAI;
private OpenAISettings openAI;
public AzureOpenAISettings AzureOpenAI => this.azureOpenAI ??= this.GetSettings<Settings.AzureOpenAISettings>();
public OpenAISettings OpenAI => this.openAI ??= this.GetSettings<Settings.OpenAISettings>();
public class OpenAISettings
{
public string ChatModel { get; set; } = string.Empty;
public string ApiKey { get; set; } = string.Empty;
}
public class AzureOpenAISettings
{
public string ChatModelDeployment { get; set; } = string.Empty;
public string Endpoint { get; set; } = string.Empty;
public string ApiKey { get; set; } = string.Empty;
}
public TSettings GetSettings<TSettings>() =>
this.configRoot.GetRequiredSection(typeof(TSettings).Name).Get<TSettings>()!;
public Settings()
{
this.configRoot =
new ConfigurationBuilder()
.AddEnvironmentVariables()
.AddUserSecrets(Assembly.GetExecutingAssembly(), optional: true)
.Build();
}
}
샘플 코드를 실행하는 적절한 구성을 시작하는 가장 빠른 방법은 프로젝트의 루트(스크립트가 실행되는 위치)에 파일을 만드는 .env
것입니다. 샘플에서는 Azure OpenAI 또는 OpenAI 리소스를 사용할 수 있어야 합니다.
Azure OpenAI 또는 OpenAI에 대한 설정을 구성하려면 귀하의 .env
파일에서 다음 설정을 구성하십시오.
AZURE_OPENAI_API_KEY="..."
AZURE_OPENAI_ENDPOINT="https://<resource-name>.openai.azure.com/"
AZURE_OPENAI_CHAT_DEPLOYMENT_NAME="..."
AZURE_OPENAI_API_VERSION="..."
OPENAI_API_KEY="sk-..."
OPENAI_ORG_ID=""
OPENAI_CHAT_MODEL_ID=""
구성되면 각 AI 서비스 클래스는 필요한 변수를 선택하고 인스턴스화 중에 사용합니다.
현재 Java에서 기능을 사용할 수 없습니다.
코딩
이 샘플의 코딩 프로세스는 다음과 같습니다.
- 설치 - 설정 및 플러그 인 초기화
-
Agent
정의 - 두 개의ChatCompletionAgent
인스턴스(검토자 및 기록기)를 만듭니다. -
채팅 정의 -
AgentGroupChat
및 관련 전략을 만듭니다. - 채팅 루프 - 사용자/에이전트 상호 작용을 구동하는 루프를 작성합니다.
전체 예제 코드는 최종 섹션에 제공됩니다. 전체 구현은 해당 섹션을 참조하세요.
설치
ChatCompletionAgent
만들기 전에 구성 설정, 플러그 인 및 Kernel
초기화해야 합니다.
이전 구성 섹션에서 참조된 클래스를 인스턴스화 Settings
합니다.
Settings settings = new();
현재 Java에서 기능을 사용할 수 없습니다.
이제 Kernel
을(를) 사용하여 IChatCompletionService
인스턴스를 초기화합니다.
IKernelBuilder builder = Kernel.CreateBuilder();
builder.AddAzureOpenAIChatCompletion(
settings.AzureOpenAI.ChatModelDeployment,
settings.AzureOpenAI.Endpoint,
new AzureCliCredential());
Kernel kernel = builder.Build();
커널 개체를 초기화합니다.
kernel = Kernel()
현재 Java에서 기능을 사용할 수 없습니다.
또한 Kernel
통해 두 번째 인스턴스를 만들고 검토에서 업데이트된 콘텐츠를 클립보드에 배치할 수 있는 플러그 인을 추가해 보겠습니다.
Kernel toolKernel = kernel.Clone();
toolKernel.Plugins.AddFromType<ClipboardAccess>();
현재 Java에서 기능을 사용할 수 없습니다.
클립보드 플러그 인은 샘플의 일부로 정의될 수 있습니다.
private sealed class ClipboardAccess
{
[KernelFunction]
[Description("Copies the provided content to the clipboard.")]
public static void SetClipboard(string content)
{
if (string.IsNullOrWhiteSpace(content))
{
return;
}
using Process clipProcess = Process.Start(
new ProcessStartInfo
{
FileName = "clip",
RedirectStandardInput = true,
UseShellExecute = false,
});
clipProcess.StandardInput.Write(content);
clipProcess.StandardInput.Close();
}
}
현재 Java에서 기능을 사용할 수 없습니다.
에이전트 정의
const
전략에서 참조될 수 있도록 에이전트 이름을 AgentGroupChat
선언해 보겠습니다.
const string ReviewerName = "Reviewer";
const string WriterName = "Writer";
에이전트 이름을 "검토자" 및 "작성자"로 지정합니다.
REVIEWER_NAME = "Reviewer"
COPYWRITER_NAME = "Writer"
현재 Java에서 기능을 사용할 수 없습니다.
검토자 에이전트를 정의하는 것은 방법: 채팅 완료 에이전트에서 탐색한 패턴을 사용합니다.
여기서 검토자는 사용자 입력에 응답하고, 기록기 에이전트에 방향을 제공하고, 기록기 에이전트의 결과를 확인하는 역할을 부여합니다.
ChatCompletionAgent agentReviewer =
new()
{
Name = ReviewerName,
Instructions =
"""
Your responsibility is to review and identify how to improve user provided content.
If the user has providing input or direction for content already provided, specify how to address this input.
Never directly perform the correction or provide example.
Once the content has been updated in a subsequent response, you will review the content again until satisfactory.
Always copy satisfactory content to the clipboard using available tools and inform user.
RULES:
- Only identify suggestions that are specific and actionable.
- Verify previous suggestions have been addressed.
- Never repeat previous suggestions.
""",
Kernel = toolKernel,
Arguments =
new KernelArguments(
new AzureOpenAIPromptExecutionSettings()
{
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto()
})
};
agent_reviewer = ChatCompletionAgent(
kernel=kernel,
name=REVIEWER_NAME,
instructions="""
Your responsibility is to review and identify how to improve user provided content.
If the user has provided input or direction for content already provided, specify how to address this input.
Never directly perform the correction or provide an example.
Once the content has been updated in a subsequent response, review it again until it is satisfactory.
RULES:
- Only identify suggestions that are specific and actionable.
- Verify previous suggestions have been addressed.
- Never repeat previous suggestions.
""",
)
현재 Java에서 기능을 사용할 수 없습니다.
기록기 에이전트는 비슷하지만 플러그 인으로 구성되지 않으므로 실행 설정 사양이 필요하지 않습니다.
여기서 작성자에겐 단일 목적의 작업이 부여되고, 지시를 따르고 콘텐츠를 다시 작성합니다.
ChatCompletionAgent agentWriter =
new()
{
Name = WriterName,
Instructions =
"""
Your sole responsibility is to rewrite content according to review suggestions.
- Always apply all review direction.
- Always revise the content in its entirety without explanation.
- Never address the user.
""",
Kernel = kernel,
};
라이터 에이전트도 비슷합니다. 단일 목적의 작업을 부여받고 지시를 따라 콘텐츠를 다시 작성합니다.
agent_writer = ChatCompletionAgent(
kernel=kernel,
name=WRITER_NAME,
instructions="""
Your sole responsibility is to rewrite content according to review suggestions.
- Always apply all review directions.
- Always revise the content in its entirety without explanation.
- Never address the user.
""",
)
현재 Java에서 기능을 사용할 수 없습니다.
채팅 정의
AgentGroupChat
정의하려면 Agent
턴을 선택하고 채팅 루프를 종료할 시기를 결정하는 전략을 고려해야 합니다. 이러한 두 가지 고려 사항에 대해 커널 프롬프트 함수를 정의합니다.
Agent
선택을 추론하는 첫 번째 방법은 다음과 같습니다.
AgentGroupChat.CreatePromptFunctionForStrategy
사용하면 메시지 매개 변수를 HTML 인코딩을 방지할 수 있는 편리한 메커니즘이 제공됩니다.
KernelFunction selectionFunction =
AgentGroupChat.CreatePromptFunctionForStrategy(
$$$"""
Examine the provided RESPONSE and choose the next participant.
State only the name of the chosen participant without explanation.
Never choose the participant named in the RESPONSE.
Choose only from these participants:
- {{{ReviewerName}}}
- {{{WriterName}}}
Always follow these rules when choosing the next participant:
- If RESPONSE is user input, it is {{{ReviewerName}}}'s turn.
- If RESPONSE is by {{{ReviewerName}}}, it is {{{WriterName}}}'s turn.
- If RESPONSE is by {{{WriterName}}}, it is {{{ReviewerName}}}'s turn.
RESPONSE:
{{$lastmessage}}
""",
safeParameterNames: "lastmessage");
selection_function = KernelFunctionFromPrompt(
function_name="selection",
prompt=f"""
Examine the provided RESPONSE and choose the next participant.
State only the name of the chosen participant without explanation.
Never choose the participant named in the RESPONSE.
Choose only from these participants:
- {REVIEWER_NAME}
- {WRITER_NAME}
Rules:
- If RESPONSE is user input, it is {REVIEWER_NAME}'s turn.
- If RESPONSE is by {REVIEWER_NAME}, it is {WRITER_NAME}'s turn.
- If RESPONSE is by {WRITER_NAME}, it is {REVIEWER_NAME}'s turn.
RESPONSE:
{{{{$lastmessage}}}}
"""
)
현재 Java에서 기능을 사용할 수 없습니다.
두 번째는 채팅 루프를 종료할 시기를 평가합니다.
const string TerminationToken = "yes";
KernelFunction terminationFunction =
AgentGroupChat.CreatePromptFunctionForStrategy(
$$$"""
Examine the RESPONSE and determine whether the content has been deemed satisfactory.
If content is satisfactory, respond with a single word without explanation: {{{TerminationToken}}}.
If specific suggestions are being provided, it is not satisfactory.
If no correction is suggested, it is satisfactory.
RESPONSE:
{{$lastmessage}}
""",
safeParameterNames: "lastmessage");
termination_keyword = "yes"
termination_function = KernelFunctionFromPrompt(
function_name="termination",
prompt=f"""
Examine the RESPONSE and determine whether the content has been deemed satisfactory.
If the content is satisfactory, respond with a single word without explanation: {termination_keyword}.
If specific suggestions are being provided, it is not satisfactory.
If no correction is suggested, it is satisfactory.
RESPONSE:
{{{{$lastmessage}}}}
"""
)
현재 Java에서 기능을 사용할 수 없습니다.
이러한 두 전략 모두 최신 채팅 메시지에 대한 지식만 필요합니다. 이렇게 하면 토큰 사용량이 줄어들고 성능이 향상됩니다.
ChatHistoryTruncationReducer historyReducer = new(1);
history_reducer = ChatHistoryTruncationReducer(target_count=1)
현재 Java에서 기능을 사용할 수 없습니다.
마지막으로 우리는 AgentGroupChat
정의에 모든 것을 결합 할 준비가되어 있습니다.
만들기 AgentGroupChat
에는 다음 내용이 포함됩니다.
- 생성자에 두 에이전트를 모두 포함합니다.
- 이전에 정의한
KernelFunctionSelectionStrategy
및KernelFunction
인스턴스를 사용하여Kernel
를 정의합니다. - 이전에 정의한
KernelFunctionTerminationStrategy
및KernelFunction
인스턴스를 사용하여Kernel
를 정의합니다.
각 전략이 KernelFunction
결과를 구문 분석해야 한다는 점을 주목하십시오.
AgentGroupChat chat =
new(agentReviewer, agentWriter)
{
ExecutionSettings = new AgentGroupChatSettings
{
SelectionStrategy =
new KernelFunctionSelectionStrategy(selectionFunction, kernel)
{
// Always start with the editor agent.
InitialAgent = agentReviewer,
// Save tokens by only including the final response
HistoryReducer = historyReducer,
// The prompt variable name for the history argument.
HistoryVariableName = "lastmessage",
// Returns the entire result value as a string.
ResultParser = (result) => result.GetValue<string>() ?? agentReviewer.Name
},
TerminationStrategy =
new KernelFunctionTerminationStrategy(terminationFunction, kernel)
{
// Only evaluate for editor's response
Agents = [agentReviewer],
// Save tokens by only including the final response
HistoryReducer = historyReducer,
// The prompt variable name for the history argument.
HistoryVariableName = "lastmessage",
// Limit total number of turns
MaximumIterations = 12,
// Customer result parser to determine if the response is "yes"
ResultParser = (result) => result.GetValue<string>()?.Contains(TerminationToken, StringComparison.OrdinalIgnoreCase) ?? false
}
}
};
Console.WriteLine("Ready!");
만들기 AgentGroupChat
에는 다음 내용이 포함됩니다.
- 생성자에 두 에이전트를 모두 포함합니다.
- 이전에 정의한
KernelFunctionSelectionStrategy
및KernelFunction
인스턴스를 사용하여Kernel
를 정의합니다. - 이전에 정의한
KernelFunctionTerminationStrategy
및KernelFunction
인스턴스를 사용하여Kernel
를 정의합니다.
각 전략이 KernelFunction
결과를 구문 분석해야 한다는 점을 주목하십시오.
chat = AgentGroupChat(
agents=[agent_reviewer, agent_writer],
selection_strategy=KernelFunctionSelectionStrategy(
initial_agent=agent_reviewer,
function=selection_function,
kernel=kernel,
result_parser=lambda result: str(result.value[0]).strip() if result.value[0] is not None else WRITER_NAME,
history_variable_name="lastmessage",
history_reducer=history_reducer,
),
termination_strategy=KernelFunctionTerminationStrategy(
agents=[agent_reviewer],
function=termination_function,
kernel=kernel,
result_parser=lambda result: termination_keyword in str(result.value[0]).lower(),
history_variable_name="lastmessage",
maximum_iterations=10,
history_reducer=history_reducer,
),
)
lastmessage
history_variable_name
위에서 정의한 KernelFunctionSelectionStrategy
및 KernelFunctionTerminationStrategy
프롬프트에 해당합니다. 프롬프트를 렌더링할 때 마지막 메시지가 배치되는 위치입니다.
현재 Java에서 기능을 사용할 수 없습니다.
채팅 루프
마침내 사용자와 AgentGroupChat
간의 상호 작용을 조정할 수 있습니다. 먼저 빈 루프를 만듭니다.
참고: 다른 예제와 달리 외부 기록 또는 스레드 는 관리되지 않습니다.
AgentGroupChat
내부적으로 대화 기록을 관리합니다.
bool isComplete = false;
do
{
} while (!isComplete);
is_complete: bool = False
while not is_complete:
# operational logic
현재 Java에서 기능을 사용할 수 없습니다.
이제 이전 루프 내에서 사용자 입력을 캡처해 보겠습니다. 이 경우 다음과 같습니다.
- 빈 입력은 무시됩니다.
- 용어
EXIT
는 대화가 완료되었음을 나타냅니다. -
RESET
용어는AgentGroupChat
기록을 지웁니다. - 시작하는
@
모든 용어는 콘텐츠가 입력으로 제공되는 파일 경로로 처리됩니다. - 유효한 입력이
AgentGroupChat
메시지로 에 추가됩니다.
Console.WriteLine();
Console.Write("> ");
string input = Console.ReadLine();
if (string.IsNullOrWhiteSpace(input))
{
continue;
}
input = input.Trim();
if (input.Equals("EXIT", StringComparison.OrdinalIgnoreCase))
{
isComplete = true;
break;
}
if (input.Equals("RESET", StringComparison.OrdinalIgnoreCase))
{
await chat.ResetAsync();
Console.WriteLine("[Conversation has been reset]");
continue;
}
if (input.StartsWith("@", StringComparison.Ordinal) && input.Length > 1)
{
string filePath = input.Substring(1);
try
{
if (!File.Exists(filePath))
{
Console.WriteLine($"Unable to access file: {filePath}");
continue;
}
input = File.ReadAllText(filePath);
}
catch (Exception)
{
Console.WriteLine($"Unable to access file: {filePath}");
continue;
}
}
chat.AddChatMessage(new ChatMessageContent(AuthorRole.User, input));
이제 이전 루프 내에서 사용자 입력을 캡처해 보겠습니다. 이 경우 다음과 같습니다.
- 빈 입력은 무시됩니다.
-
exit
용어는 대화가 완료되었음을 나타냅니다. -
reset
용어는AgentGroupChat
역사를 지울 것입니다. -
@
시작하는 모든 용어는 콘텐츠가 입력으로 제공되는 파일 경로로 처리됩니다. - 유효한 입력이
AgentGroupChat
메시지로 에 추가됩니다.
while 루프 내의 작업 논리는 다음과 같습니다.
print()
user_input = input("User > ").strip()
if not user_input:
continue
if user_input.lower() == "exit":
is_complete = True
break
if user_input.lower() == "reset":
await chat.reset()
print("[Conversation has been reset]")
continue
# Try to grab files from the script's current directory
if user_input.startswith("@") and len(user_input) > 1:
file_name = user_input[1:]
script_dir = os.path.dirname(os.path.abspath(__file__))
file_path = os.path.join(script_dir, file_name)
try:
if not os.path.exists(file_path):
print(f"Unable to access file: {file_path}")
continue
with open(file_path, "r", encoding="utf-8") as file:
user_input = file.read()
except Exception:
print(f"Unable to access file: {file_path}")
continue
# Add the current user_input to the chat
await chat.add_chat_message(message=user_input)
현재 Java에서 기능을 사용할 수 없습니다.
사용자 입력에 대한 응답으로 Agent
협업을 시작하고 Agent
응답을 표시하려면 AgentGroupChat
를 호출하세요. 그러나 먼저 이전 호출로부터의 완료 상태를 초기화하십시오.
참고: 대화 루프가 중단되지 않도록 서비스 오류가 포착 및 표시되고 있습니다.
chat.IsComplete = false;
try
{
await foreach (ChatMessageContent response in chat.InvokeAsync())
{
Console.WriteLine();
Console.WriteLine($"{response.AuthorName.ToUpperInvariant()}:{Environment.NewLine}{response.Content}");
}
}
catch (HttpOperationException exception)
{
Console.WriteLine(exception.Message);
if (exception.InnerException != null)
{
Console.WriteLine(exception.InnerException.Message);
if (exception.InnerException.Data.Count > 0)
{
Console.WriteLine(JsonSerializer.Serialize(exception.InnerException.Data, new JsonSerializerOptions() { WriteIndented = true }));
}
}
}
try:
async for response in chat.invoke():
if response is None or not response.name:
continue
print()
print(f"# {response.name.upper()}:\n{response.content}")
except Exception as e:
print(f"Error during chat invocation: {e}")
# Reset the chat's complete flag for the new conversation round.
chat.is_complete = False
현재 Java에서 기능을 사용할 수 없습니다.
최종
모든 단계를 함께 가져오면 이 예제의 최종 코드가 있습니다. 전체 구현은 아래에 제공됩니다.
다음과 같은 제안된 입력을 사용해 보세요.
- 안녕
- {"message: "안녕 세상"}
- {"message": "안녕하세요 세계"}
- Semantic Kernel(SK)은 개발자가 NLP(자연어 처리) 및 기계 학습 모델을 포함하는 복잡한 AI 워크플로를 빌드하고 오케스트레이션할 수 있는 오픈 소스 SDK입니다. 의미 체계 검색, 텍스트 요약 및 대화 시스템과 같은 AI 기능을 애플리케이션에 통합하기 위한 유연한 플랫폼을 제공합니다. SK를 사용하면 다양한 AI 서비스와 모델을 쉽게 결합하고, 관계를 정의하고, 상호 작용을 오케스트레이션할 수 있습니다.
- 이 문장을 두 개의 단락으로 나누십시오.
- 감사합니다
- @.\WomensSuffrage.txt
- 괜찮긴 한데, 내 대학 교수에게도 괜찮을까?
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Text.Json;
using System.Threading.Tasks;
using Azure.Identity;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.Chat;
using Microsoft.SemanticKernel.Agents.History;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Connectors.AzureOpenAI;
namespace AgentsSample;
public static class Program
{
public static async Task Main()
{
// Load configuration from environment variables or user secrets.
Settings settings = new();
Console.WriteLine("Creating kernel...");
IKernelBuilder builder = Kernel.CreateBuilder();
builder.AddAzureOpenAIChatCompletion(
settings.AzureOpenAI.ChatModelDeployment,
settings.AzureOpenAI.Endpoint,
new AzureCliCredential());
Kernel kernel = builder.Build();
Kernel toolKernel = kernel.Clone();
toolKernel.Plugins.AddFromType<ClipboardAccess>();
Console.WriteLine("Defining agents...");
const string ReviewerName = "Reviewer";
const string WriterName = "Writer";
ChatCompletionAgent agentReviewer =
new()
{
Name = ReviewerName,
Instructions =
"""
Your responsibility is to review and identify how to improve user provided content.
If the user has providing input or direction for content already provided, specify how to address this input.
Never directly perform the correction or provide example.
Once the content has been updated in a subsequent response, you will review the content again until satisfactory.
Always copy satisfactory content to the clipboard using available tools and inform user.
RULES:
- Only identify suggestions that are specific and actionable.
- Verify previous suggestions have been addressed.
- Never repeat previous suggestions.
""",
Kernel = toolKernel,
Arguments = new KernelArguments(new AzureOpenAIPromptExecutionSettings() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() })
};
ChatCompletionAgent agentWriter =
new()
{
Name = WriterName,
Instructions =
"""
Your sole responsibility is to rewrite content according to review suggestions.
- Always apply all review direction.
- Always revise the content in its entirety without explanation.
- Never address the user.
""",
Kernel = kernel,
};
KernelFunction selectionFunction =
AgentGroupChat.CreatePromptFunctionForStrategy(
$$$"""
Examine the provided RESPONSE and choose the next participant.
State only the name of the chosen participant without explanation.
Never choose the participant named in the RESPONSE.
Choose only from these participants:
- {{{ReviewerName}}}
- {{{WriterName}}}
Always follow these rules when choosing the next participant:
- If RESPONSE is user input, it is {{{ReviewerName}}}'s turn.
- If RESPONSE is by {{{ReviewerName}}}, it is {{{WriterName}}}'s turn.
- If RESPONSE is by {{{WriterName}}}, it is {{{ReviewerName}}}'s turn.
RESPONSE:
{{$lastmessage}}
""",
safeParameterNames: "lastmessage");
const string TerminationToken = "yes";
KernelFunction terminationFunction =
AgentGroupChat.CreatePromptFunctionForStrategy(
$$$"""
Examine the RESPONSE and determine whether the content has been deemed satisfactory.
If content is satisfactory, respond with a single word without explanation: {{{TerminationToken}}}.
If specific suggestions are being provided, it is not satisfactory.
If no correction is suggested, it is satisfactory.
RESPONSE:
{{$lastmessage}}
""",
safeParameterNames: "lastmessage");
ChatHistoryTruncationReducer historyReducer = new(1);
AgentGroupChat chat =
new(agentReviewer, agentWriter)
{
ExecutionSettings = new AgentGroupChatSettings
{
SelectionStrategy =
new KernelFunctionSelectionStrategy(selectionFunction, kernel)
{
// Always start with the editor agent.
InitialAgent = agentReviewer,
// Save tokens by only including the final response
HistoryReducer = historyReducer,
// The prompt variable name for the history argument.
HistoryVariableName = "lastmessage",
// Returns the entire result value as a string.
ResultParser = (result) => result.GetValue<string>() ?? agentReviewer.Name
},
TerminationStrategy =
new KernelFunctionTerminationStrategy(terminationFunction, kernel)
{
// Only evaluate for editor's response
Agents = [agentReviewer],
// Save tokens by only including the final response
HistoryReducer = historyReducer,
// The prompt variable name for the history argument.
HistoryVariableName = "lastmessage",
// Limit total number of turns
MaximumIterations = 12,
// Customer result parser to determine if the response is "yes"
ResultParser = (result) => result.GetValue<string>()?.Contains(TerminationToken, StringComparison.OrdinalIgnoreCase) ?? false
}
}
};
Console.WriteLine("Ready!");
bool isComplete = false;
do
{
Console.WriteLine();
Console.Write("> ");
string input = Console.ReadLine();
if (string.IsNullOrWhiteSpace(input))
{
continue;
}
input = input.Trim();
if (input.Equals("EXIT", StringComparison.OrdinalIgnoreCase))
{
isComplete = true;
break;
}
if (input.Equals("RESET", StringComparison.OrdinalIgnoreCase))
{
await chat.ResetAsync();
Console.WriteLine("[Conversation has been reset]");
continue;
}
if (input.StartsWith("@", StringComparison.Ordinal) && input.Length > 1)
{
string filePath = input.Substring(1);
try
{
if (!File.Exists(filePath))
{
Console.WriteLine($"Unable to access file: {filePath}");
continue;
}
input = File.ReadAllText(filePath);
}
catch (Exception)
{
Console.WriteLine($"Unable to access file: {filePath}");
continue;
}
}
chat.AddChatMessage(new ChatMessageContent(AuthorRole.User, input));
chat.IsComplete = false;
try
{
await foreach (ChatMessageContent response in chat.InvokeAsync())
{
Console.WriteLine();
Console.WriteLine($"{response.AuthorName.ToUpperInvariant()}:{Environment.NewLine}{response.Content}");
}
}
catch (HttpOperationException exception)
{
Console.WriteLine(exception.Message);
if (exception.InnerException != null)
{
Console.WriteLine(exception.InnerException.Message);
if (exception.InnerException.Data.Count > 0)
{
Console.WriteLine(JsonSerializer.Serialize(exception.InnerException.Data, new JsonSerializerOptions() { WriteIndented = true }));
}
}
}
} while (!isComplete);
}
private sealed class ClipboardAccess
{
[KernelFunction]
[Description("Copies the provided content to the clipboard.")]
public static void SetClipboard(string content)
{
if (string.IsNullOrWhiteSpace(content))
{
return;
}
using Process clipProcess = Process.Start(
new ProcessStartInfo
{
FileName = "clip",
RedirectStandardInput = true,
UseShellExecute = false,
});
clipProcess.StandardInput.Write(content);
clipProcess.StandardInput.Close();
}
}
}
모든 단계를 함께 가져오면 이제 이 예제의 최종 코드가 있습니다. 전체 구현은 다음과 같습니다.
제안된 입력 중 하나를 사용해 볼 수 있습니다. 에이전트 채팅이 시작되면, 모든 에이전트는 검토자 에이전트가 카피라이터의 작업에 만족할 때까지 여러 번 메시지를 주고받습니다.
while
루프는 is_complete
플래그를 False
다시 설정하여 처음에 채팅이 완료된 것으로 간주되더라도 대화가 계속되도록 합니다.
- 로즈는 빨간색, 바이올레츠는 파란색입니다.
- Semantic Kernel(SK)은 개발자가 NLP(자연어 처리) 및 기계 학습 모델을 포함하는 복잡한 AI 워크플로를 빌드하고 오케스트레이션할 수 있는 오픈 소스 SDK입니다. 의미 체계 검색, 텍스트 요약 및 대화 시스템과 같은 AI 기능을 애플리케이션에 통합하기 위한 유연한 플랫폼을 제공합니다. SK를 사용하면 다양한 AI 서비스와 모델을 쉽게 결합하고, 관계를 정의하고, 상호 작용을 오케스트레이션할 수 있습니다.
- 이 문장을 두 단락으로 만드십시오.
- 감사합니다
- @WomensSuffrage.txt
- 좋지만, 내 대학 교수님에게 적합할까요?
팁 (조언)
@<file_path_to_file>
제공하여 모든 파일을 참조할 수 있습니다. "위에서 'WomensSuffrage' 텍스트를 참조하려면 여기에서 다운로드하고 현재 작업 디렉터리에 놓으세요." 그런 다음 @WomensSuffrage.txt
사용하여 참조할 수 있습니다.
import asyncio
import os
from semantic_kernel import Kernel
from semantic_kernel.agents import AgentGroupChat, ChatCompletionAgent
from semantic_kernel.agents.strategies import (
KernelFunctionSelectionStrategy,
KernelFunctionTerminationStrategy,
)
from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion
from semantic_kernel.contents import ChatHistoryTruncationReducer
from semantic_kernel.functions import KernelFunctionFromPrompt
"""
The following sample demonstrates how to create a simple,
agent group chat that utilizes a Reviewer Chat Completion
Agent along with a Writer Chat Completion Agent to
complete a user's task.
"""
# Define agent names
REVIEWER_NAME = "Reviewer"
WRITER_NAME = "Writer"
def create_kernel() -> Kernel:
"""Creates a Kernel instance with an Azure OpenAI ChatCompletion service."""
kernel = Kernel()
kernel.add_service(service=AzureChatCompletion())
return kernel
async def main():
# Create a single kernel instance for all agents.
kernel = create_kernel()
# Create ChatCompletionAgents using the same kernel.
agent_reviewer = ChatCompletionAgent(
kernel=kernel,
name=REVIEWER_NAME,
instructions="""
Your responsibility is to review and identify how to improve user provided content.
If the user has provided input or direction for content already provided, specify how to address this input.
Never directly perform the correction or provide an example.
Once the content has been updated in a subsequent response, review it again until it is satisfactory.
RULES:
- Only identify suggestions that are specific and actionable.
- Verify previous suggestions have been addressed.
- Never repeat previous suggestions.
""",
)
agent_writer = ChatCompletionAgent(
kernel=kernel,
name=WRITER_NAME,
instructions="""
Your sole responsibility is to rewrite content according to review suggestions.
- Always apply all review directions.
- Always revise the content in its entirety without explanation.
- Never address the user.
""",
)
# Define a selection function to determine which agent should take the next turn.
selection_function = KernelFunctionFromPrompt(
function_name="selection",
prompt=f"""
Examine the provided RESPONSE and choose the next participant.
State only the name of the chosen participant without explanation.
Never choose the participant named in the RESPONSE.
Choose only from these participants:
- {REVIEWER_NAME}
- {WRITER_NAME}
Rules:
- If RESPONSE is user input, it is {REVIEWER_NAME}'s turn.
- If RESPONSE is by {REVIEWER_NAME}, it is {WRITER_NAME}'s turn.
- If RESPONSE is by {WRITER_NAME}, it is {REVIEWER_NAME}'s turn.
RESPONSE:
{{{{$lastmessage}}}}
""",
)
# Define a termination function where the reviewer signals completion with "yes".
termination_keyword = "yes"
termination_function = KernelFunctionFromPrompt(
function_name="termination",
prompt=f"""
Examine the RESPONSE and determine whether the content has been deemed satisfactory.
If the content is satisfactory, respond with a single word without explanation: {termination_keyword}.
If specific suggestions are being provided, it is not satisfactory.
If no correction is suggested, it is satisfactory.
RESPONSE:
{{{{$lastmessage}}}}
""",
)
history_reducer = ChatHistoryTruncationReducer(target_count=5)
# Create the AgentGroupChat with selection and termination strategies.
chat = AgentGroupChat(
agents=[agent_reviewer, agent_writer],
selection_strategy=KernelFunctionSelectionStrategy(
initial_agent=agent_reviewer,
function=selection_function,
kernel=kernel,
result_parser=lambda result: str(result.value[0]).strip() if result.value[0] is not None else WRITER_NAME,
history_variable_name="lastmessage",
history_reducer=history_reducer,
),
termination_strategy=KernelFunctionTerminationStrategy(
agents=[agent_reviewer],
function=termination_function,
kernel=kernel,
result_parser=lambda result: termination_keyword in str(result.value[0]).lower(),
history_variable_name="lastmessage",
maximum_iterations=10,
history_reducer=history_reducer,
),
)
print(
"Ready! Type your input, or 'exit' to quit, 'reset' to restart the conversation. "
"You may pass in a file path using @<path_to_file>."
)
is_complete = False
while not is_complete:
print()
user_input = input("User > ").strip()
if not user_input:
continue
if user_input.lower() == "exit":
is_complete = True
break
if user_input.lower() == "reset":
await chat.reset()
print("[Conversation has been reset]")
continue
# Try to grab files from the script's current directory
if user_input.startswith("@") and len(user_input) > 1:
file_name = user_input[1:]
script_dir = os.path.dirname(os.path.abspath(__file__))
file_path = os.path.join(script_dir, file_name)
try:
if not os.path.exists(file_path):
print(f"Unable to access file: {file_path}")
continue
with open(file_path, "r", encoding="utf-8") as file:
user_input = file.read()
except Exception:
print(f"Unable to access file: {file_path}")
continue
# Add the current user_input to the chat
await chat.add_chat_message(message=user_input)
try:
async for response in chat.invoke():
if response is None or not response.name:
continue
print()
print(f"# {response.name.upper()}:\n{response.content}")
except Exception as e:
print(f"Error during chat invocation: {e}")
# Reset the chat's complete flag for the new conversation round.
chat.is_complete = False
if __name__ == "__main__":
asyncio.run(main())
위에 표시된 것처럼 리포지토리에서 전체 코드찾을 수 있습니다.
현재 Java에서 기능을 사용할 수 없습니다.