操作說明:使用代理程式群組聊天協調代理程式共同作業 (實驗性)
警告
語意核心代理程式架構是實驗性的,仍在開發中,而且可能會變更。
概觀
在此範例中,我們將探索如何使用 代理程式群組聊天 來協調兩個不同代理程式的定序,以檢閱和重寫使用者提供的內容。 每個代理程式都會獲指派不同的角色:
- 檢閱者:檢閱並提供寫入器的方向。
- 寫入器:根據檢 閱者 輸入更新用戶內容。
此方法會逐步細分,以高光撰寫程序的關鍵部分。
快速入門
繼續進行功能程式代碼撰寫之前,請確定您的開發環境已完全設定和設定。
從建立 主控台 項目開始。 然後,請包含下列套件參考,以確保所有必要的相依性都可供使用。
若要從命令行新增套件相依性, 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>
import asyncio
import os
import copy
import pyperclip # Install via pip
from semantic_kernel.agents import AgentGroupChat, ChatCompletionAgent
from semantic_kernel.agents.strategies.selection.kernel_function_selection_strategy import (
KernelFunctionSelectionStrategy,
)
from semantic_kernel.agents.strategies.termination.kernel_function_termination_strategy import (
KernelFunctionTerminationStrategy,
)
from semantic_kernel.connectors.ai.open_ai.services.azure_chat_completion import AzureChatCompletion
from semantic_kernel.contents.chat_message_content import ChatMessageContent
from semantic_kernel.contents.utils.author_role import AuthorRole
from semantic_kernel.functions.kernel_function_decorator import kernel_function
from semantic_kernel.functions.kernel_function_from_prompt import KernelFunctionFromPrompt
from semantic_kernel.kernel import Kernel
代理程式目前無法在Java中使用。
組態
此範例需要組態設定,才能連線到遠端服務。 您必須定義 Open AI 或 Azure Open AI 的設定。
# Open AI
dotnet user-secrets set "OpenAISettings:ApiKey" "<api-key>"
dotnet user-secrets set "OpenAISettings:ChatModel" "gpt-4o"
# Azure Open AI
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"
下列類別用於所有 Agent 範例中。 請務必將它包含在專案中,以確保適當的功能。 這個類別可作為後續範例的基礎元件。
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
檔案(執行腳本的位置)。
在 .env
Azure OpenAI 或 OpenAI 的檔案中設定下列設定:
AZURE_OPENAI_API_KEY="..."
AZURE_OPENAI_ENDPOINT="https://..."
AZURE_OPENAI_CHAT_DEPLOYMENT_NAME="..."
AZURE_OPENAI_API_VERSION="..."
OPENAI_API_KEY="sk-..."
OPENAI_ORG_ID=""
OPENAI_CHAT_MODEL_ID=""
設定之後,個別的 AI 服務類別會挑選必要的變數,並在具現化期間使用這些變數。
代理程式目前無法在Java中使用。
撰寫程式碼
這個範例的編碼程式牽涉到:
- 安裝程式 - 初始化設定和外掛程式。
- 代理程式 定義 - 建立兩 個聊天完成代理程序 實例(檢 閱者和 寫入器)。
- 聊天 定義 - 建立 代理程式群組聊天 和相關策略。
- 聊天迴圈 - 撰寫可驅動使用者/代理程式互動的迴圈。
完整範例程式代碼會在 Final 區段中提供。 如需完整的實作,請參閱該區段。
設定
建立任何 聊天完成代理程式之前,必須初始化組態設定、外掛程式和 核心 。
代理程式目前無法在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>();
tool_kernel = copy.deepcopy(kernel)
tool_kernel.add_plugin(ClipboardAccess(), plugin_name="clipboard")
代理程式目前無法在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();
}
}
注意:我們正利用名為 pyperclip 的 Python 套件。 請安裝使用 pip。
class ClipboardAccess:
@kernel_function
def set_clipboard(content: str):
if not content.strip():
return
pyperclip.copy(content)
代理程式目前無法在Java中使用。
代理程式定義
讓我們將代理程式名稱宣告為 const
,以便在代理程式群組聊天策略中參考這些名稱:
const string ReviewerName = "Reviewer";
const string WriterName = "Writer";
REVIEWER_NAME = "Reviewer"
WRITER_NAME = "Writer"
代理程式目前無法在Java中使用。
定義檢閱者代理程式會使用操作說明:聊天完成代理程式中探索的模式。
在這裡,檢閱者會提供回應使用者輸入的角色、提供寫入器代理程式的方向,以及驗證寫入器代理程序的結果。
ChatCompletionAgent agentReviewer =
new()
{
Name = ReviewerName,
Instructions =
"""
Your responsiblity 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(
service_id=REVIEWER_NAME,
kernel=_create_kernel_with_chat_completion(REVIEWER_NAME),
name=REVIEWER_NAME,
instructions="""
Your responsiblity 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.
""",
)
代理程式目前無法在Java中使用。
寫入器代理程式很類似,但不需要執行設定的規格,因為它未使用外掛程式進行設定。
在這裡, 寫入器 會提供單一用途的工作,請遵循指示並重寫內容。
ChatCompletionAgent agentWriter =
new()
{
Name = WriterName,
Instructions =
"""
Your sole responsiblity 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(
service_id=COPYWRITER_NAME,
kernel=_create_kernel_with_chat_completion(COPYWRITER_NAME),
name=COPYWRITER_NAME,
instructions="""
Your sole responsiblity 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.
""",
)
代理程式目前無法在Java中使用。
聊天定義
定義代理程式群組聊天需要考慮選取代理程式回合的策略,並判斷何時結束聊天迴圈。 針對這兩項考慮,我們將定義 核心提示函式。
代理程式選取的第一個原因:
使用 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"""
Determine which participant takes the next turn in a conversation based on the the most recent participant.
State only the name of the participant to take the next turn.
No participant should take more than one turn in a row.
Choose only from these participants:
- {REVIEWER_NAME}
- {COPYWRITER_NAME}
Always follow these rules when selecting the next participant:
- After user input, it is {COPYWRITER_NAME}'s turn.
- After {COPYWRITER_NAME} replies, it is {REVIEWER_NAME}'s turn.
- After {REVIEWER_NAME} provides feedback, it is {COPYWRITER_NAME}'s turn.
History:
{{{{$history}}}}
""",
)
代理程式目前無法在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 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:
{{{{$history}}}}
""",
)
代理程式目前無法在Java中使用。
這 兩個策略 只需要瞭解最新的 聊天 訊息。 這可減少令牌使用量,並協助改善效能:
ChatHistoryTruncationReducer historyReducer = new(1);
**ChatHistoryReducer is coming soon to Python.**
代理程式目前無法在Java中使用。
最後,我們已準備好將代理程式群組聊天定義中的一切整合在一起。
建立 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_writer, agent_reviewer],
selection_strategy=KernelFunctionSelectionStrategy(
function=selection_function,
kernel=_create_kernel_with_chat_completion("selection"),
result_parser=lambda result: str(result.value[0]) if result.value is not None else COPYWRITER_NAME,
agent_variable_name="agents",
history_variable_name="history",
),
termination_strategy=KernelFunctionTerminationStrategy(
agents=[agent_reviewer],
function=termination_function,
kernel=_create_kernel_with_chat_completion("termination"),
result_parser=lambda result: TERMINATION_KEYWORD in str(result.value[0]).lower(),
history_variable_name="history",
maximum_iterations=10,
),
)
代理程式目前無法在Java中使用。
聊天迴圈
最後,我們能夠協調使用者與 代理程式群組聊天之間的互動。 從建立空循環開始。
注意:與其他範例不同,不會管理任何外部歷程記錄或 線程 。 代理程式群組聊天 會在內部管理交談歷程記錄。
bool isComplete = false;
do
{
} while (!isComplete);
is_complete: bool = False
while not is_complete:
# operational logic
代理程式目前無法在Java中使用。
現在讓我們在上一個迴圈中擷取用戶輸入。 在此案例中:
- 將會忽略空的輸入
- 字詞
EXIT
會發出交談已完成的訊號 - 此字詞
RESET
會清除 代理程式群組聊天 記錄 - 開頭
@
的任何字詞都會被視為將內容提供為輸入的檔案路徑 - 有效的輸入將會新增至 代理程式群組 Chaty 作為 使用者 訊息。
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("[Converation 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));
user_input = input("User:> ")
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
if user_input.startswith("@") and len(input) > 1:
file_path = input[1:]
try:
if not os.path.exists(file_path):
print(f"Unable to access file: {file_path}")
continue
with open(file_path) as file:
user_input = file.read()
except Exception:
print(f"Unable to access file: {file_path}")
continue
await chat.add_chat_message(ChatMessageContent(role=AuthorRole.USER, content=user_input))
代理程式目前無法在Java中使用。
若要初始化 Agent 共同作業以回應使用者輸入並顯示代理程序回應,請叫用代理程式群組聊天;不過,首先請務必從任何先前的調用重設完成狀態。
注意:正在攔截並顯示服務失敗,以避免損毀交談迴圈。
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 }));
}
}
}
chat.is_complete = False
async for response in chat.invoke():
print(f"# {response.role} - {response.name or '*'}: '{response.content}'")
if chat.is_complete:
is_complete = True
break
代理程式目前無法在Java中使用。
完成
將所有步驟結合在一起,我們有此範例的最終程序代碼。 以下提供完整的實作。
// Copyright (c) Microsoft. All rights reserved.
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 responsiblity 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 responsiblity 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("[Converation 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();
}
}
}
# Copyright (c) Microsoft. All rights reserved.
import asyncio
import os
from semantic_kernel.agents import AgentGroupChat, ChatCompletionAgent
from semantic_kernel.agents.strategies.selection.kernel_function_selection_strategy import (
KernelFunctionSelectionStrategy,
)
from semantic_kernel.agents.strategies.termination.kernel_function_termination_strategy import (
KernelFunctionTerminationStrategy,
)
from semantic_kernel.connectors.ai.open_ai.services.azure_chat_completion import AzureChatCompletion
from semantic_kernel.contents.chat_message_content import ChatMessageContent
from semantic_kernel.contents.utils.author_role import AuthorRole
from semantic_kernel.functions.kernel_function_decorator import kernel_function
from semantic_kernel.functions.kernel_function_from_prompt import KernelFunctionFromPrompt
from semantic_kernel.kernel import Kernel
###################################################################
# 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. #
###################################################################
class ClipboardAccess:
@kernel_function
def set_clipboard(content: str):
if not content.strip():
return
pyperclip.copy(content)
REVIEWER_NAME = "Reviewer"
COPYWRITER_NAME = "Writer"
def _create_kernel_with_chat_completion(service_id: str) -> Kernel:
kernel = Kernel()
kernel.add_service(AzureChatCompletion(service_id=service_id))
return kernel
async def main():
agent_reviewer = ChatCompletionAgent(
service_id=REVIEWER_NAME,
kernel=_create_kernel_with_chat_completion(REVIEWER_NAME),
name=REVIEWER_NAME,
instructions="""
Your responsiblity 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.
""",
)
agent_writer = ChatCompletionAgent(
service_id=COPYWRITER_NAME,
kernel=_create_kernel_with_chat_completion(COPYWRITER_NAME),
name=COPYWRITER_NAME,
instructions="""
Your sole responsiblity 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.
""",
)
selection_function = KernelFunctionFromPrompt(
function_name="selection",
prompt=f"""
Determine which participant takes the next turn in a conversation based on the the most recent participant.
State only the name of the participant to take the next turn.
No participant should take more than one turn in a row.
Choose only from these participants:
- {REVIEWER_NAME}
- {COPYWRITER_NAME}
Always follow these rules when selecting the next participant:
- After user input, it is {COPYWRITER_NAME}'s turn.
- After {COPYWRITER_NAME} replies, it is {REVIEWER_NAME}'s turn.
- After {REVIEWER_NAME} provides feedback, it is {COPYWRITER_NAME}'s turn.
History:
{{{{$history}}}}
""",
)
TERMINATION_KEYWORD = "yes"
termination_function = KernelFunctionFromPrompt(
function_name="termination",
prompt=f"""
Examine the RESPONSE and determine whether the content has been deemed satisfactory.
If 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:
{{{{$history}}}}
""",
)
chat = AgentGroupChat(
agents=[agent_writer, agent_reviewer],
selection_strategy=KernelFunctionSelectionStrategy(
function=selection_function,
kernel=_create_kernel_with_chat_completion("selection"),
result_parser=lambda result: str(result.value[0]) if result.value is not None else COPYWRITER_NAME,
agent_variable_name="agents",
history_variable_name="history",
),
termination_strategy=KernelFunctionTerminationStrategy(
agents=[agent_reviewer],
function=termination_function,
kernel=_create_kernel_with_chat_completion("termination"),
result_parser=lambda result: TERMINATION_KEYWORD in str(result.value[0]).lower(),
history_variable_name="history",
maximum_iterations=10,
),
)
is_complete: bool = False
while not is_complete:
user_input = input("User:> ")
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
if user_input.startswith("@") and len(input) > 1:
file_path = input[1:]
try:
if not os.path.exists(file_path):
print(f"Unable to access file: {file_path}")
continue
with open(file_path) as file:
user_input = file.read()
except Exception:
print(f"Unable to access file: {file_path}")
continue
await chat.add_chat_message(ChatMessageContent(role=AuthorRole.USER, content=user_input))
async for response in chat.invoke():
print(f"# {response.role} - {response.name or '*'}: '{response.content}'")
if chat.is_complete:
is_complete = True
break
if __name__ == "__main__":
asyncio.run(main())
代理程式目前無法在Java中使用。