Como fazer
Aviso
O Semantic Kernel Agent Framework é experimental, ainda está em desenvolvimento e está sujeito a alterações.
Visão geral
Neste exemplo, exploraremos como usar a ferramenta de pesquisa de arquivos de um Open AI Assistant Agent para concluir tarefas de compreensão. A abordagem será passo a passo, garantindo clareza e precisão em todo o processo. Como parte da tarefa, o agente fornecerá citações de documentos na resposta.
O streaming será usado para fornecer as respostas do agente. Isso fornecerá atualizações em tempo real à medida que a tarefa avança.
Introdução
Antes de prosseguir com a codificação de recursos, verifique se o ambiente de desenvolvimento está totalmente instalado e configurado.
Para adicionar dependências de pacote da linha de comando, use o dotnet
comando:
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
dotnet add package Microsoft.SemanticKernel.Agents.OpenAI --prerelease
Se estiver gerenciando pacotes NuGet no Visual Studio, verifique se
Include prerelease
está marcado.
O arquivo de projeto (.csproj
) deve conter as seguintes PackageReference
definições:
<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" Version="<latest>" />
<PackageReference Include="Microsoft.SemanticKernel.Agents.OpenAI" Version="<latest>" />
</ItemGroup>
A Estrutura do Agente é experimental e requer supressão de aviso. Isso pode ser abordado como uma propriedade no arquivo do projeto (.csproj
):
<PropertyGroup>
<NoWarn>$(NoWarn);CA2007;IDE1006;SKEXP0001;SKEXP0110;OPENAI001</NoWarn>
</PropertyGroup>
Além disso, copie Grimms-The-White-Snake.txt
o conteúdo de Grimms-The-Water-of-Life.txt
domínio público e doGrimms-The-King-of-the-Golden-Mountain.txt
Semantic KernelLearnResources
Project. Adicione esses arquivos à pasta do projeto e configure para que eles sejam copiados para o diretório de saída:
<ItemGroup>
<None Include="Grimms-The-King-of-the-Golden-Mountain.txt">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Include="Grimms-The-Water-of-Life.txt">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Include="Grimms-The-White-Snake.txt">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
Comece criando uma pasta que conterá seu script (.py
arquivo) e os recursos de exemplo. Inclua as seguintes importações na parte superior do arquivo .py
:
import asyncio
import os
from semantic_kernel.agents.open_ai.azure_assistant_agent import AzureAssistantAgent
from semantic_kernel.contents.chat_message_content import ChatMessageContent
from semantic_kernel.contents.streaming_annotation_content import StreamingAnnotationContent
from semantic_kernel.contents.utils.author_role import AuthorRole
from semantic_kernel.kernel import Kernel
Além disso, copie Grimms-The-White-Snake.txt
o conteúdo de Grimms-The-Water-of-Life.txt
domínio público e doGrimms-The-King-of-the-Golden-Mountain.txt
Semantic KernelLearnResources
Project. Adicione esses arquivos à pasta do projeto.
No momento, os agentes não estão disponíveis em Java.
Configuração
Este exemplo requer uma definição de configuração para se conectar a serviços remotos. Você precisará definir configurações para Open AI ou 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" "https://lightspeed-team-shared-openai-eastus.openai.azure.com/"
dotnet user-secrets set "AzureOpenAISettings:ChatModelDeployment" "gpt-4o"
A classe a seguir é usada em todos os exemplos de Agent. Certifique-se de incluí-lo em seu projeto para garantir a funcionalidade adequada. Essa classe serve como um componente fundamental para os exemplos a seguir.
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();
}
}
A maneira mais rápida de começar com a configuração adequada para executar o código de exemplo é criar um .env
arquivo na raiz do seu projeto (onde seu script é executado).
Defina as seguintes configurações em seu .env
arquivo para o OpenAI do Azure ou o 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=""
Depois de configuradas, as respectivas classes de serviço de IA selecionarão as variáveis necessárias e as usarão durante a instanciação.
No momento, os agentes não estão disponíveis em Java.
Codificação
O processo de codificação para este exemplo envolve:
- Configuração - Inicializando as configurações e o plug-in.
- Definição do agente - Crie o Chat_Completion_Agent com instruções e plug-ins modelados.
- O Loop de Chat - Escreva o loop que impulsiona a interação usuário/agente.
O código de exemplo completo é fornecido na seção Final . Consulte essa seção para obter a implementação completa.
Instalação
Antes de criar um Open AI Assistant Agent, verifique se as definições de configuração estão disponíveis e prepare os recursos de arquivo.
Instancie a classe referenciada Settings
na seção Configuração anterior. Use as configurações para criar também um OpenAIClientProvider
que será usado para a Definição do Agente, bem como o upload de arquivos e a criação de um VectorStore
arquivo .
Settings settings = new();
OpenAIClientProvider clientProvider =
OpenAIClientProvider.ForAzureOpenAI(
new AzureCliCredential(),
new Uri(settings.AzureOpenAI.Endpoint));
No momento, os agentes não estão disponíveis em Java.
Agora, crie um _Vector Store vazio para uso com a ferramenta File Search :
Use o OpenAIClientProvider
para acessar um VectorStoreClient
e criar um VectorStore
.
Console.WriteLine("Creating store...");
VectorStoreClient storeClient = clientProvider.Client.GetVectorStoreClient();
CreateVectorStoreOperation operation = await storeClient.CreateVectorStoreAsync(waitUntilCompleted: true);
string storeId = operation.VectorStoreId;
def get_filepath_for_filename(filename: str) -> str:
base_directory = os.path.dirname(os.path.realpath(__file__))
return os.path.join(base_directory, filename)
No momento, os agentes não estão disponíveis em Java.
Vamos declarar os três arquivos de conteúdo descritos na seção de configuração anterior:
private static readonly string[] _fileNames =
[
"Grimms-The-King-of-the-Golden-Mountain.txt",
"Grimms-The-Water-of-Life.txt",
"Grimms-The-White-Snake.txt",
];
filenames = [
"Grimms-The-King-of-the-Golden-Mountain.txt",
"Grimms-The-Water-of-Life.txt",
"Grimms-The-White-Snake.txt",
]
No momento, os agentes não estão disponíveis em Java.
Agora carregue esses arquivos e adicione-os ao Vector Store usando os clientes criados VectorStoreClient
anteriormente para carregar cada arquivo com um OpenAIFileClient
e adicioná-lo ao Vector Store, preservando as referências de arquivo resultantes.
Dictionary<string, OpenAIFile> fileReferences = [];
Console.WriteLine("Uploading files...");
OpenAIFileClient fileClient = clientProvider.Client.GetOpenAIFileClient();
foreach (string fileName in _fileNames)
{
OpenAIFile fileInfo = await fileClient.UploadFileAsync(fileName, FileUploadPurpose.Assistants);
await storeClient.AddFileToVectorStoreAsync(storeId, fileInfo.Id, waitUntilCompleted: true);
fileReferences.Add(fileInfo.Id, fileInfo);
}
No momento, os agentes não estão disponíveis em Java.
Definição de agente
Agora estamos prontos para instanciar um Agente Assistente OpenAI. O agente é configurado com seu modelo de destino, Instruções e a ferramenta Pesquisa de Arquivos ativada. Além disso, associamos explicitamente o Vector Store à ferramenta File Search .
Utilizaremos o OpenAIClientProvider
novamente como parte da criação do OpenAIAssistantAgent
:
Console.WriteLine("Defining agent...");
OpenAIAssistantAgent agent =
await OpenAIAssistantAgent.CreateAsync(
clientProvider,
new OpenAIAssistantDefinition(settings.AzureOpenAI.ChatModelDeployment)
{
Name = "SampleAssistantAgent",
Instructions =
"""
The document store contains the text of fictional stories.
Always analyze the document store to provide an answer to the user's question.
Never rely on your knowledge of stories not included in the document store.
Always format response using markdown.
""",
EnableFileSearch = true,
VectorStoreId = storeId,
},
new Kernel());
agent = await AzureAssistantAgent.create(
kernel=Kernel(),
service_id="agent",
name="SampleAssistantAgent",
instructions="""
The document store contains the text of fictional stories.
Always analyze the document store to provide an answer to the user's question.
Never rely on your knowledge of stories not included in the document store.
Always format response using markdown.
""",
enable_file_search=True,
vector_store_filenames=[get_filepath_for_filename(filename) for filename in filenames],
)
No momento, os agentes não estão disponíveis em Java.
O loop de bate-papo
Por fim, somos capazes de coordenar a interação entre o usuário e o agente. Comece criando um Thread do Assistente para manter o estado da conversa e criando um loop vazio.
Vamos também garantir que os recursos sejam removidos no final da execução para minimizar cobranças desnecessárias.
Console.WriteLine("Creating thread...");
string threadId = await agent.CreateThreadAsync();
Console.WriteLine("Ready!");
try
{
bool isComplete = false;
do
{
// Processing occurrs here
} while (!isComplete);
}
finally
{
Console.WriteLine();
Console.WriteLine("Cleaning-up...");
await Task.WhenAll(
[
agent.DeleteThreadAsync(threadId),
agent.DeleteAsync(),
storeClient.DeleteVectorStoreAsync(storeId),
..fileReferences.Select(fileReference => fileClient.DeleteFileAsync(fileReference.Key))
]);
}
print("Creating thread...")
thread_id = await agent.create_thread()
try:
is_complete: bool = False
while not is_complete:
# Processing occurs here
finally:
print("Cleaning up resources...")
if agent is not None:
[await agent.delete_file(file_id) for file_id in agent.file_search_file_ids]
await agent.delete_thread(thread_id)
await agent.delete()
No momento, os agentes não estão disponíveis em Java.
Agora vamos capturar a entrada do usuário no loop anterior. Nesse caso, a entrada vazia será ignorada e o termo EXIT
sinalizará que a conversa foi concluída. O nput válido será adicionado ao Thread do Assistente como uma mensagem do usuário.
Console.WriteLine();
Console.Write("> ");
string input = Console.ReadLine();
if (string.IsNullOrWhiteSpace(input))
{
continue;
}
if (input.Trim().Equals("EXIT", StringComparison.OrdinalIgnoreCase))
{
isComplete = true;
break;
}
await agent.AddChatMessageAsync(threadId, new ChatMessageContent(AuthorRole.User, input));
Console.WriteLine();
user_input = input("User:> ")
if not user_input:
continue
if user_input.lower() == "exit":
is_complete = True
await agent.add_chat_message(
thread_id=thread_id, message=ChatMessageContent(role=AuthorRole.USER, content=user_input)
)
No momento, os agentes não estão disponíveis em Java.
Antes de invocar a resposta do Agente , vamos adicionar um método auxiliar para reformatar os colchetes de anotação unicode para colchetes ANSI.
private static string ReplaceUnicodeBrackets(this string content) =>
content?.Replace('【', '[').Replace('】', ']');
# No special handling required.
No momento, os agentes não estão disponíveis em Java.
Para gerar uma resposta do Agente à entrada do usuário, chame o agente especificando o Thread do Assistente. Neste exemplo, escolhemos uma resposta transmitida e capturamos todas as anotações de citação associadas para exibição no final do ciclo de resposta. Observe que cada parte transmitida está sendo reformatada usando o método auxiliar anterior.
List<StreamingAnnotationContent> footnotes = [];
await foreach (StreamingChatMessageContent chunk in agent.InvokeStreamingAsync(threadId))
{
// Capture annotations for footnotes
footnotes.AddRange(chunk.Items.OfType<StreamingAnnotationContent>());
// Render chunk with replacements for unicode brackets.
Console.Write(chunk.Content.ReplaceUnicodeBrackets());
}
Console.WriteLine();
// Render footnotes for captured annotations.
if (footnotes.Count > 0)
{
Console.WriteLine();
foreach (StreamingAnnotationContent footnote in footnotes)
{
Console.WriteLine($"#{footnote.Quote.ReplaceUnicodeBrackets()} - {fileReferences[footnote.FileId!].Filename} (Index: {footnote.StartIndex} - {footnote.EndIndex})");
}
}
footnotes: list[StreamingAnnotationContent] = []
async for response in agent.invoke_stream(thread_id=thread_id):
footnotes.extend([item for item in response.items if isinstance(item, StreamingAnnotationContent)])
print(f"{response.content}", end="", flush=True)
print()
if len(footnotes) > 0:
for footnote in footnotes:
print(
f"\n`{footnote.quote}` => {footnote.file_id} "
f"(Index: {footnote.start_index} - {footnote.end_index})"
)
No momento, os agentes não estão disponíveis em Java.
Final
Juntando todas as etapas, temos o código final para este exemplo. A implementação completa é fornecida abaixo.
Tente usar estas entradas sugeridas:
- Qual é a contagem de parágrafos para cada uma das histórias?
- Crie uma tabela que identifique o protagonista e o antagonista de cada história.
- Qual é a moral em The White Snake?
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Azure.Identity;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents.OpenAI;
using Microsoft.SemanticKernel.ChatCompletion;
using OpenAI.Files;
using OpenAI.VectorStores;
namespace AgentsSample;
public static class Program
{
private static readonly string[] _fileNames =
[
"Grimms-The-King-of-the-Golden-Mountain.txt",
"Grimms-The-Water-of-Life.txt",
"Grimms-The-White-Snake.txt",
];
/// <summary>
/// The main entry point for the application.
/// </summary>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static async Task Main()
{
// Load configuration from environment variables or user secrets.
Settings settings = new();
OpenAIClientProvider clientProvider =
OpenAIClientProvider.ForAzureOpenAI(
new AzureCliCredential(),
new Uri(settings.AzureOpenAI.Endpoint));
Console.WriteLine("Creating store...");
VectorStoreClient storeClient = clientProvider.Client.GetVectorStoreClient();
CreateVectorStoreOperation operation = await storeClient.CreateVectorStoreAsync(waitUntilCompleted: true);
string storeId = operation.VectorStoreId;
// Retain file references.
Dictionary<string, OpenAIFile> fileReferences = [];
Console.WriteLine("Uploading files...");
OpenAIFileClient fileClient = clientProvider.Client.GetOpenAIFileClient();
foreach (string fileName in _fileNames)
{
OpenAIFile fileInfo = await fileClient.UploadFileAsync(fileName, FileUploadPurpose.Assistants);
await storeClient.AddFileToVectorStoreAsync(storeId, fileInfo.Id, waitUntilCompleted: true);
fileReferences.Add(fileInfo.Id, fileInfo);
}
Console.WriteLine("Defining agent...");
OpenAIAssistantAgent agent =
await OpenAIAssistantAgent.CreateAsync(
clientProvider,
new OpenAIAssistantDefinition(settings.AzureOpenAI.ChatModelDeployment)
{
Name = "SampleAssistantAgent",
Instructions =
"""
The document store contains the text of fictional stories.
Always analyze the document store to provide an answer to the user's question.
Never rely on your knowledge of stories not included in the document store.
Always format response using markdown.
""",
EnableFileSearch = true,
VectorStoreId = storeId,
},
new Kernel());
Console.WriteLine("Creating thread...");
string threadId = await agent.CreateThreadAsync();
Console.WriteLine("Ready!");
try
{
bool isComplete = false;
do
{
Console.WriteLine();
Console.Write("> ");
string input = Console.ReadLine();
if (string.IsNullOrWhiteSpace(input))
{
continue;
}
if (input.Trim().Equals("EXIT", StringComparison.OrdinalIgnoreCase))
{
isComplete = true;
break;
}
await agent.AddChatMessageAsync(threadId, new ChatMessageContent(AuthorRole.User, input));
Console.WriteLine();
List<StreamingAnnotationContent> footnotes = [];
await foreach (StreamingChatMessageContent chunk in agent.InvokeStreamingAsync(threadId))
{
// Capture annotations for footnotes
footnotes.AddRange(chunk.Items.OfType<StreamingAnnotationContent>());
// Render chunk with replacements for unicode brackets.
Console.Write(chunk.Content.ReplaceUnicodeBrackets());
}
Console.WriteLine();
// Render footnotes for captured annotations.
if (footnotes.Count > 0)
{
Console.WriteLine();
foreach (StreamingAnnotationContent footnote in footnotes)
{
Console.WriteLine($"#{footnote.Quote.ReplaceUnicodeBrackets()} - {fileReferences[footnote.FileId!].Filename} (Index: {footnote.StartIndex} - {footnote.EndIndex})");
}
}
} while (!isComplete);
}
finally
{
Console.WriteLine();
Console.WriteLine("Cleaning-up...");
await Task.WhenAll(
[
agent.DeleteThreadAsync(threadId),
agent.DeleteAsync(),
storeClient.DeleteVectorStoreAsync(storeId),
..fileReferences.Select(fileReference => fileClient.DeleteFileAsync(fileReference.Key))
]);
}
}
private static string ReplaceUnicodeBrackets(this string content) =>
content?.Replace('【', '[').Replace('】', ']');
}
import asyncio
import os
from semantic_kernel.agents.open_ai.azure_assistant_agent import AzureAssistantAgent
from semantic_kernel.contents.chat_message_content import ChatMessageContent
from semantic_kernel.contents.streaming_annotation_content import StreamingAnnotationContent
from semantic_kernel.contents.utils.author_role import AuthorRole
from semantic_kernel.kernel import Kernel
def get_filepath_for_filename(filename: str) -> str:
base_directory = os.path.dirname(os.path.realpath(__file__))
return os.path.join(base_directory, filename)
filenames = [
"Grimms-The-King-of-the-Golden-Mountain.txt",
"Grimms-The-Water-of-Life.txt",
"Grimms-The-White-Snake.txt",
]
async def main():
agent = await AzureAssistantAgent.create(
kernel=Kernel(),
service_id="agent",
name="SampleAssistantAgent",
instructions="""
The document store contains the text of fictional stories.
Always analyze the document store to provide an answer to the user's question.
Never rely on your knowledge of stories not included in the document store.
Always format response using markdown.
""",
enable_file_search=True,
vector_store_filenames=[get_filepath_for_filename(filename) for filename in filenames],
)
print("Creating thread...")
thread_id = await agent.create_thread()
try:
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
await agent.add_chat_message(
thread_id=thread_id, message=ChatMessageContent(role=AuthorRole.USER, content=user_input)
)
footnotes: list[StreamingAnnotationContent] = []
async for response in agent.invoke_stream(thread_id=thread_id):
footnotes.extend([item for item in response.items if isinstance(item, StreamingAnnotationContent)])
print(f"{response.content}", end="", flush=True)
print()
if len(footnotes) > 0:
for footnote in footnotes:
print(
f"\n`{footnote.quote}` => {footnote.file_id} "
f"(Index: {footnote.start_index} - {footnote.end_index})"
)
finally:
print("Cleaning up resources...")
if agent is not None:
[await agent.delete_file(file_id) for file_id in agent.file_search_file_ids]
await agent.delete_thread(thread_id)
await agent.delete()
if __name__ == "__main__":
asyncio.run(main())
No momento, os agentes não estão disponíveis em Java.