Como fazer: Open AI Assistant Agent File Search
Aviso
O Semantic Kernel Agent Framework está em pré-visualização e está sujeito a alterações.
Descriçã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 ao longo de todo o processo. Como parte da tarefa, o agente fornecerá citações de documentos na resposta.
O streaming será usado para entregar as respostas do agente. Isso fornecerá atualizações em tempo real à medida que a tarefa progride.
Introdução
Antes de prosseguir com a codificação de recursos, verifique se o ambiente de desenvolvimento está totalmente configurado e configurado.
Para adicionar dependências de pacote a partir 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 ficheiro 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>
O Agent Framework é experimental e requer supressão de aviso. Isso pode ser abordado como uma propriedade no arquivo de projeto (.csproj
):
<PropertyGroup>
<NoWarn>$(NoWarn);CA2007;IDE1006;SKEXP0001;SKEXP0110;OPENAI001</NoWarn>
</PropertyGroup>
Além disso, copie o conteúdo e o Grimms-The-King-of-the-Golden-Mountain.txt
Grimms-The-Water-of-Life.txt
conteúdo de domínio público doLearnResources
. Adicione esses arquivos na pasta do projeto e configure para copiá-los 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 armazenará 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 o conteúdo e o Grimms-The-King-of-the-Golden-Mountain.txt
Grimms-The-Water-of-Life.txt
conteúdo de domínio público doLearnResources
. Adicione esses arquivos na pasta do projeto.
Os agentes estão atualmente indisponíveis em Java.
Configuração
Este exemplo requer 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 agente. Certifique-se de incluí-lo em seu projeto para garantir a funcionalidade adequada. Esta classe serve como um componente fundamental para os exemplos que se seguem.
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 o script é executado).
Configure as seguintes configurações em seu .env
arquivo para o Azure OpenAI ou 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=""
Uma vez configuradas, as respetivas classes de serviço de IA pegarão as variáveis necessárias e as usarão durante a instanciação.
Os agentes estão atualmente indisponí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 modeladas e plug-in.
- The Chat Loop - 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.
Configurar
Antes de criar um Open AI Assistant Agent, verifique se as definições de configuração estão disponíveis e prepare os recursos do arquivo.
Instancie a Settings
classe referenciada na seção Configuração anterior. Use as configurações para também criar 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));
Os agentes estão atualmente indisponíveis em Java.
Agora crie um _Vetor Store vazio para usar com a ferramenta de pesquisa de arquivos:
Use o OpenAIClientProvider
para acessar um VectorStoreClient
e criar um VectorStore
arquivo .
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)
Os agentes estão atualmente indisponíveis em Java.
Vamos declarar os três arquivos de conteúdo descritos na seção 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",
]
Os agentes estão atualmente indisponíveis em Java.
Agora, carregue esses arquivos e adicione-os ao Vetor Store usando os clientes criados anteriormente para carregar cada arquivo com um VectorStoreClient
e adicioná-lo ao OpenAIFileClient
, 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);
}
Os agentes estão atualmente indisponíveis em Java.
Definição do agente
Agora estamos prontos para instanciar um OpenAI Assistant Agent. O agente é configurado com seu modelo de destino, Instruções e a ferramenta de Pesquisa de Arquivos habilitada. Além disso, associamos explicitamente o Vetor Store à ferramenta de Pesquisa deFicheiros.
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],
)
Os agentes estão atualmente indisponíveis em Java.
O Loop do Chat
Finalmente, somos capazes de coordenar a interação entre o usuário e o Agente. Comece criando um Thread 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()
Os agentes estão atualmente indisponíveis em Java.
Agora vamos capturar a entrada do usuário dentro do loop anterior. Neste caso, a entrada vazia será ignorada e o termo EXIT
sinalizará que a conversa está concluída. Nput válido será adicionado ao thread do assistente como uma mensagem de 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)
)
Os agentes estão atualmente indisponí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.
Os agentes estão atualmente indisponíveis em Java.
Para gerar uma resposta do Agente à entrada do usuário, invoque o agente especificando o Thread do Assistente. Neste exemplo, escolhemos uma resposta transmitida em fluxo 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})"
)
Os agentes estão atualmente indisponí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 sugestões de entradas:
- Qual é a contagem de parágrafos para cada uma das histórias?
- Crie uma tabela que identifique o protagonista e antagonista de cada história.
- Qual é a moral em A Serpente Branca?
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())
Os agentes estão atualmente indisponíveis em Java.