Compartilhar via


Introdução aos aplicativos de chat de visão multimodal usando o OpenAI do Azure

Este artigo mostra como usar modelos multimodais do OpenAI do Azure para gerar respostas a mensagens de usuário e imagens carregadas em um aplicativo de chat. Este exemplo de aplicativo de chat também inclui toda a infraestrutura e configuração necessárias para provisionar recursos do Azure OpenAI e implantar o aplicativo nos Aplicativos de Contêiner do Azure usando a CLI do Desenvolvedor do Azure.

Seguindo as instruções neste artigo, você vai:

  • Implante um aplicativo de chat de contêiner do Azure que usa identidade gerenciada para autenticação.
  • Carregue imagens para serem usadas como parte do fluxo de bate-papo.
  • Converse com um LLM (Modelo de Linguagem Grande) multimodal OpenAI do Azure usando a biblioteca OpenAI.

Depois de concluir este artigo, você poderá começar a modificar o novo projeto com seu código personalizado.

Observação

Este artigo usa um ou mais modelos de aplicativo de IA como base para os exemplos e as diretrizes no artigo. Os modelos de aplicativo de IA fornecem implementações de referência regulares e fáceis de implantar que ajudam a garantir um ponto de partida de alta qualidade para os aplicativos de IA.

Visão geral da arquitetura

Uma arquitetura simples do aplicativo de bate-papo é mostrada no diagrama a seguir: Diagrama mostrando a arquitetura do cliente para o aplicativo de back-end.

O aplicativo de chat está em execução como um Aplicativo de Contêiner do Azure. O aplicativo usa a identidade gerenciada por meio da ID do Microsoft Entra para autenticar com o Azure OpenAI em produção, em vez de uma chave de API. Durante o desenvolvimento, o aplicativo dá suporte a vários métodos de autenticação, incluindo credenciais da CLI do Desenvolvedor do Azure, chaves de API e modelos do GitHub para teste sem recursos do Azure.

A arquitetura do aplicativo depende dos seguintes serviços e componentes:

  • O OpenAI do Azure representa o provedor de IA para o qual enviamos as consultas do usuário.
  • Os Aplicativos de Contêiner do Azure são o ambiente de contêiner em que o aplicativo está hospedado.
  • A Identidade Gerenciada nos ajuda a garantir a melhor segurança da categoria e elimina a necessidade de você, como desenvolvedor, gerenciar com segurança um segredo.
  • Arquivos Bicep para provisionamento de recursos do Azure, incluindo Azure OpenAI, Aplicativos de Contêiner do Azure, Registro de Contêiner do Azure, Azure Log Analytics e funções RBAC (controle de acesso baseado em função).
  • O Protocolo de Chat de IA da Microsoft fornece contratos de API padronizados em soluções e linguagens de IA. O aplicativo de bate-papo está em conformidade com o Microsoft AI Chat Protocol.
  • Um Python Quart que usa o openai pacote para gerar respostas a mensagens de usuário com arquivos de imagem carregados.
  • Um front-end HTML / JavaScript básico que transmite respostas do back-end usando JSON Lines em um ReadableStream.

Custo

Em uma tentativa de manter os preços o mais baixo possível neste exemplo, a maioria dos recursos usa um tipo de preço básico ou de consumo. Altere seu nível conforme necessário com base no uso pretendido. Para parar de incorrer em cobranças, exclua os recursos quando terminar o artigo.

Saiba mais sobre o custo no repositório de exemplo.

Pré-requisitos

Um ambiente de contêiner de desenvolvimento está disponível com todas as dependências necessárias para concluir este artigo. Você pode executar o contêiner de desenvolvimento em Codespaces do GitHub (em um navegador) ou localmente usando o Visual Studio Code.

Para usar este artigo, você precisa cumprir os seguintes pré-requisitos:

Abrir o ambiente de desenvolvimento

Use as instruções a seguir para implantar um ambiente de desenvolvimento pré-configurado contendo todas as dependências necessárias para concluir este artigo.

O GitHub Codespaces executa um contêiner de desenvolvimento gerenciado pelo GitHub com o Visual Studio Code para Web como interface do usuário. Para o ambiente de desenvolvimento mais simples, use os Codespaces do GitHub para que você tenha as ferramentas e dependências de desenvolvedor corretas pré-instaladas para concluir este artigo.

Importante

Todas as contas do GitHub podem usar Codespaces por até 60 horas gratuitas por mês com duas instâncias principais. Para saber mais, confira Armazenamento e horas por núcleo incluídos mensalmente no GitHub Codespaces.

Use as etapas a seguir para criar um novo GitHub Codespace no main branch do Azure-Samples/openai-chat-vision-quickstart repositório GitHub.

  1. Clique com o botão direito do mouse no botão a seguir e selecione Abrir link em uma nova janela. Essa ação permite que você tenha o ambiente de desenvolvimento e a documentação disponíveis para revisão.

    Abrir em GitHub Codespaces

  2. Na página Criar codespace , examine e selecione Criar novo codespace

  3. Aguarde até que o codespace seja iniciado. Esse processo de inicialização pode levar alguns minutos.

  4. Entre no Azure com a CLI do Desenvolvedor do Azure no terminal na parte inferior da tela.

    azd auth login
    
  5. Copie o código do terminal e cole-o em um navegador. Siga as instruções para autenticar com sua conta do Azure.

As tarefas restantes neste artigo ocorrem no contexto desse contêiner de desenvolvimento.

Implantar e executar

O repositório de exemplo contém todos os arquivos de código e configuração para a implantação do aplicativo de chat do Azure. As etapas a seguir orientam você pelo processo de implantação do aplicativo de chat de exemplo do Azure.

Implantar aplicativo de chat no Azure

Importante

Para manter os custos baixos, este exemplo usa tipos de preço básicos ou de consumo para a maioria dos recursos. Ajuste o nível conforme necessário e exclua os recursos quando terminar para evitar cobranças.

  1. Execute o seguinte comando da CLI do Desenvolvedor do Azure para provisionamento de recursos do Azure e implantação de código-fonte:

    azd up
    
  2. Use a seguinte tabela para responder aos prompts:

    Prompt Resposta
    Nome do ambiente Mantenha-o curto e em letras minúsculas. Adicione seu nome ou alias. Por exemplo, chat-vision. Ele é usado como parte do nome do grupo de recursos.
    Assinatura Selecione a assinatura para criar os recursos.
    Localização (para hospedagem) Selecione uma localização perto de você na lista.
    Local para o modelo OpenAI do Azure Selecione uma localização perto de você na lista. Se o mesmo local estiver disponível como seu primeiro local, selecione-o.
  3. Aguarde até que o aplicativo seja implantado. A implantação geralmente leva entre 5 e 10 minutos para ser concluída.

Usar o aplicativo de bate-papo para fazer perguntas ao Modelo de Linguagem Grande

  1. O terminal exibe uma URL após a implantação bem-sucedida do aplicativo.

  2. Selecione essa URL rotulada Deploying service web para abrir o aplicativo de chat em um navegador.

    Captura de tela mostrando uma imagem carregada, uma pergunta sobre a imagem, a resposta da IA e a caixa de texto.

  3. No navegador, carregue uma imagem clicando em Escolher arquivo e selecionando uma imagem.

  4. Faça uma pergunta sobre a imagem carregada, como "Sobre o que é a imagem?".

  5. A resposta vem do Azure OpenAI e o resultado é exibido.

Explorando o código de exemplo

Embora o OpenAI e o Serviço OpenAI do Azure dependam de uma biblioteca de clientes comum do Python, pequenas alterações de código são necessárias ao usar pontos de extremidade do OpenAI do Azure. Este exemplo usa um modelo multimodal do OpenAI do Azure para gerar respostas a mensagens do usuário e imagens carregadas.

Base64 Codificando a imagem carregada no front-end

A imagem carregada precisa ser codificada em Base64 para que possa ser usada diretamente como um URI de dados como parte da mensagem.

No exemplo, o snippet de código de front-end a seguir na scriptmarca do src/quartapp/templates/index.html arquivo lida com essa funcionalidade. A toBase64 função de seta usa o readAsDataURLFileReader método to read de forma assíncrona no arquivo de imagem carregado como uma cadeia de caracteres codificada em base64.

    const toBase64 = file => new Promise((resolve, reject) => {
        const reader = new FileReader();
        reader.readAsDataURL(file);
        reader.onload = () => resolve(reader.result);
        reader.onerror = reject;
    });

A toBase64 função é chamada por um ouvinte no evento do submit formulário.

O submit monitor de eventos gerencia o fluxo completo de interação do chat. Quando o usuário envia uma mensagem, o seguinte fluxo ocorre:

  1. Oculta o elemento "no-messages-heading" para mostrar a conversa iniciada
  2. Obtém e Base64 codifica o arquivo de imagem carregado (se presente)
  3. Cria e exibe a mensagem do usuário no chat, incluindo a imagem carregada
  4. Prepara um contêiner de mensagens do assistente com um "Digitando..." indicador
  5. Adiciona a mensagem do usuário à matriz de histórico de mensagens
  6. Chama o método getStreamedCompletion() do Cliente de Protocolo de Chat de IA com o histórico de mensagens e o contexto (incluindo a imagem codificada em Base64 e o nome do arquivo)
  7. Processa os blocos de resposta transmitidos e converte Markdown em HTML usando Showdown.js
  8. Lida com quaisquer erros durante o streaming
  9. Adiciona um botão de saída de fala após receber a resposta completa para que os usuários possam ouvir a resposta
  10. Limpa o campo de entrada e retorna o foco para a próxima mensagem
form.addEventListener("submit", async function(e) {
    e.preventDefault();

    // Hide the no-messages-heading when a message is added
    document.getElementById("no-messages-heading").style.display = "none";

    const file = document.getElementById("file").files[0];
    const fileData = file ? await toBase64(file) : null;

    const message = messageInput.value;

    const userTemplateClone = userTemplate.content.cloneNode(true);
    userTemplateClone.querySelector(".message-content").innerText = message;
    if (file) {
        const img = document.createElement("img");
        img.src = fileData;
        userTemplateClone.querySelector(".message-file").appendChild(img);
    }
    targetContainer.appendChild(userTemplateClone);

    const assistantTemplateClone = assistantTemplate.content.cloneNode(true);
    let messageDiv = assistantTemplateClone.querySelector(".message-content");
    targetContainer.appendChild(assistantTemplateClone);

    messages.push({
        "role": "user",
        "content": message
    });

    try {
        messageDiv.scrollIntoView();
        const result = await client.getStreamedCompletion(messages, {
            context: {
                file: fileData,
                file_name: file ? file.name : null
            }
        });

        let answer = "";
        for await (const response of result) {
            if (!response.delta) {
                continue;
            }
            if (response.delta.content) {
                // Clear out the DIV if its the first answer chunk we've received
                if (answer == "") {
                    messageDiv.innerHTML = "";
                }
                answer += response.delta.content;
                messageDiv.innerHTML = converter.makeHtml(answer);
                messageDiv.scrollIntoView();
            }
            if (response.error) {
                messageDiv.innerHTML = "Error: " + response.error;
            }
        }
        messages.push({
            "role": "assistant",
            "content": answer
        });

        messageInput.value = "";

        const speechOutput = document.createElement("speech-output-button");
        speechOutput.setAttribute("text", answer);
        messageDiv.appendChild(speechOutput);
        messageInput.focus();
    } catch (error) {
        messageDiv.innerHTML = "Error: " + error;
    }
});

Manipulando a imagem com o back-end

No arquivo, o código de back-end para manipulação de imagem é iniciado após a configuração da src\quartapp\chat.py autenticação sem chave.

Observação

Para obter mais informações sobre como usar conexões sem chave para autenticação e autorização para o Azure OpenAI, confira o artigo Introdução ao bloco de construção de segurança do Azure OpenAI Microsoft Learn.

Configuração de autenticação

A configure_openai() função configura o cliente OpenAI antes que o aplicativo comece a atender solicitações. Ele usa o decorador do @bp.before_app_serving Quart para configurar a autenticação com base em variáveis de ambiente. Esse sistema flexível permite que os desenvolvedores trabalhem em contextos diferentes sem alterar o código.

Modos de autenticação explicados
  • Desenvolvimento local (OPENAI_HOST=local): Conecta-se a um serviço de API compatível com OpenAI local (como Ollama ou LocalAI) sem autenticação. Use esse modo para teste sem custos de Internet ou API.
  • Modelos do GitHub (OPENAI_HOST=github): usa o marketplace de modelos de IA do GitHub com uma GITHUB_TOKEN autenticação. Ao usar modelos do GitHub, prefixe o nome do modelo com openai/ (por exemplo, openai/gpt-4o). Esse modo permite que os desenvolvedores experimentem modelos antes de provisionar recursos do Azure.
  • Azure OpenAI com chave de API (AZURE_OPENAI_KEY_FOR_CHATVISION variável de ambiente): usa uma chave de API para autenticação. Evite esse modo em produção porque as chaves de API exigem rotação manual e representam riscos de segurança se expostas. Use-o para testes locais dentro de um contêiner do Docker sem as credenciais da CLI do Azure.
  • Produção com Identidade Gerenciada (RUNNING_IN_PRODUCTION=true): usa ManagedIdentityCredential para autenticar com o Azure OpenAI por meio da identidade gerenciada do aplicativo de contêiner. Esse método é recomendado para produção porque remove a necessidade de gerenciar segredos. Os Aplicativos de Contêiner do Azure fornecem automaticamente a identidade gerenciada e concedem permissões durante a implantação por meio do Bicep.
  • Desenvolvimento com a CLI do Azure (modo padrão): usa AzureDeveloperCliCredential para autenticar com o Azure OpenAI usando credenciais da CLI do Azure assinadas localmente. Esse modo simplifica o desenvolvimento local sem gerenciar chaves de API.
Principais detalhes de implementação
  • A get_bearer_token_provider() função atualiza as credenciais do Azure e as usa como tokens de portador.
  • O caminho do endpoint do OpenAI do Azure inclui /openai/v1/ para atender aos requisitos da biblioteca cliente do OpenAI.
  • O registro em log mostra qual modo de autenticação está ativo.
  • A função é assíncrona para dar suporte a operações de credencial do Azure.

Aqui está o código de configuração de autenticação completo de chat.py:

@bp.before_app_serving
async def configure_openai():
    bp.model_name = os.getenv("OPENAI_MODEL", "gpt-4o")
    openai_host = os.getenv("OPENAI_HOST", "github")

    if openai_host == "local":
        bp.openai_client = AsyncOpenAI(api_key="no-key-required", base_url=os.getenv("LOCAL_OPENAI_ENDPOINT"))
        current_app.logger.info("Using local OpenAI-compatible API service with no key")
    elif openai_host == "github":
        bp.model_name = f"openai/{bp.model_name}"
        bp.openai_client = AsyncOpenAI(
            api_key=os.environ["GITHUB_TOKEN"],
            base_url="https://models.github.ai/inference",
        )
        current_app.logger.info("Using GitHub models with GITHUB_TOKEN as key")
    elif os.getenv("AZURE_OPENAI_KEY_FOR_CHATVISION"):
        bp.openai_client = AsyncOpenAI(
            base_url=os.environ["AZURE_OPENAI_ENDPOINT"],
            api_key=os.getenv("AZURE_OPENAI_KEY_FOR_CHATVISION"),
        )
        current_app.logger.info("Using Azure OpenAI with key")
    elif os.getenv("RUNNING_IN_PRODUCTION"):
        client_id = os.environ["AZURE_CLIENT_ID"]
        azure_credential = ManagedIdentityCredential(client_id=client_id)
        token_provider = get_bearer_token_provider(azure_credential, "https://cognitiveservices.azure.com/.default")
        bp.openai_client = AsyncOpenAI(
            base_url=os.environ["AZURE_OPENAI_ENDPOINT"] + "/openai/v1/",
            api_key=token_provider,
        )
        current_app.logger.info("Using Azure OpenAI with managed identity credential for client ID %s", client_id)
    else:
        tenant_id = os.environ["AZURE_TENANT_ID"]
        azure_credential = AzureDeveloperCliCredential(tenant_id=tenant_id)
        token_provider = get_bearer_token_provider(azure_credential, "https://cognitiveservices.azure.com/.default")
        bp.openai_client = AsyncOpenAI(
            base_url=os.environ["AZURE_OPENAI_ENDPOINT"] + "/openai/v1/",
            api_key=token_provider,
        )
        current_app.logger.info("Using Azure OpenAI with az CLI credential for tenant ID: %s", tenant_id)

Função de manipulador de bate-papo

A função chat_handler() processa solicitações de chat enviadas para o endpoint /chat/stream. Ele recebe uma solicitação POST com um conteúdo JSON que segue o Protocolo de Chat de IA da Microsoft.

O conteúdo JSON inclui:

  • mensagens: uma lista do histórico de conversas. Cada mensagem tem um role ("usuário" ou "assistente") e content (o texto da mensagem).
  • contexto: Dados extras para processamento, incluindo:
    • arquivo: dados de imagem codificados em Base64 (por exemplo, data:image/png;base64,...).
    • file_name: o nome de arquivo original da imagem carregada (útil para registrar em log ou identificar o tipo de imagem).
  • temperatura (opcional): um float que controla a aleatoriedade da resposta (o padrão é 0,5).

O manipulador extrai o histórico de mensagens e os dados da imagem. Se nenhuma imagem for carregada, o valor da imagem será nulle o código manipulará esse caso.

@bp.post("/chat/stream")
async def chat_handler():
    request_json = await request.get_json()
    request_messages = request_json["messages"]
    # Get the base64 encoded image from the request context
    # This will be None if no image was uploaded
    image = request_json["context"]["file"]
    # The context also includes the filename for reference
    # file_name = request_json["context"]["file_name"]

Criando a matriz de mensagens para solicitações de visão

A response_stream() função prepara a matriz de mensagens que é enviada para a API do Azure OpenAI. O @stream_with_context decorador mantém o contexto da solicitação ao transmitir a resposta.

Lógica de preparação de mensagem

  1. Comece com o histórico de conversas: a função começa com all_messages, que inclui uma mensagem do sistema e todas as mensagens anteriores, exceto a mais recente (request_messages[0:-1]).
  2. Manipule a mensagem do usuário atual com base na presença da imagem:
    • Com a imagem: formate a mensagem do usuário como uma matriz de conteúdo de várias partes com texto e objetos image_url. O image_url objeto contém os dados de imagem codificados em Base64 e um detail parâmetro.
    • Sem imagem: acrescente a mensagem do usuário como texto sem formatação.
  3. O detail parâmetro: defina como "automático" para permitir que o modelo escolha entre detalhes "baixo" e "alto" com base no tamanho da imagem. Detalhes baixos são mais rápidos e mais baratos, enquanto detalhes altos fornecem uma análise mais precisa para imagens complexas.
    @stream_with_context
    async def response_stream():
        # This sends all messages, so API request may exceed token limits
        all_messages = [
            {"role": "system", "content": "You are a helpful assistant."},
        ] + request_messages[0:-1]
        all_messages = request_messages[0:-1]
        if image:
            user_content = []
            user_content.append({"text": request_messages[-1]["content"], "type": "text"})
            user_content.append({"image_url": {"url": image, "detail": "auto"}, "type": "image_url"})
            all_messages.append({"role": "user", "content": user_content})
        else:
            all_messages.append(request_messages[-1])

Observação

Para obter mais informações sobre o parâmetro de imagem detail e as configurações relacionadas, confira a seção Configurações do parâmetro Detalhe no artigo "Usar modelos de chat habilitados para visão" do Microsoft Learn.

Em seguida, bp.openai_client.chat.completions obtém conclusões de chat por meio de uma chamada à API do OpenAI do Azure e transmite a resposta.

        chat_coroutine = bp.openai_client.chat.completions.create(
            # Azure OpenAI takes the deployment name as the model name
            model=bp.model_name,
            messages=all_messages,
            stream=True,
            temperature=request_json.get("temperature", 0.5),
        )

Por fim, a resposta é transmitida de volta para o cliente, com tratamento de erros para quaisquer exceções.

        try:
            async for event in await chat_coroutine:
                event_dict = event.model_dump()
                if event_dict["choices"]:
                    yield json.dumps(event_dict["choices"][0], ensure_ascii=False) + "\n"
        except Exception as e:
            current_app.logger.error(e)
            yield json.dumps({"error": str(e)}, ensure_ascii=False) + "\n"

    return Response(response_stream())

Bibliotecas e recursos de front-end

O front-end usa APIs e bibliotecas modernas do navegador para criar uma experiência de chat interativa. Os desenvolvedores podem personalizar a interface ou adicionar recursos compreendendo estes componentes:

  1. Entrada/Saída de Fala: os componentes da Web personalizados usam as APIs de Fala do navegador:

    • <speech-input-button>: transforma fala em texto usando a API de Reconhecimento de Fala da SpeechRecognitionWeb. Ele fornece um botão de microfone que escuta a entrada de voz e emite um speech-input-result evento com o texto transcrito.

    • <speech-output-button>: lê o texto em voz alta usando a SpeechSynthesis API. Ele aparece após cada resposta do assistente com um ícone de alto-falante, permitindo que os usuários ouçam a resposta.

    Por que usar APIs do navegador em vez dos Serviços de Fala do Azure?

    • Sem custo – é executado inteiramente no navegador
    • Resposta instantânea – sem latência de rede
    • Privacidade – os dados de voz permanecem no dispositivo do usuário
    • Não há necessidade de recursos extras do Azure

    Esses componentes estão dentro src/quartapp/static/speech-input.js e speech-output.js.

  2. Visualização da imagem: exibe a imagem carregada no chat antes do envio da análise para confirmação. A visualização é atualizada automaticamente quando um arquivo é selecionado.

    fileInput.addEventListener("change", async function() {
        const file = fileInput.files[0];
        if (file) {
            const fileData = await toBase64(file);
            imagePreview.src = fileData;
            imagePreview.style.display = "block";
        }
    });
    
  3. Bootstrap 5 e Bootstrap Icons: fornece componentes e ícones responsivos da interface do usuário. O aplicativo usa o tema cosmo do Bootswatch para uma aparência moderna.

  4. Renderização de Mensagens baseada em modelo: usa elementos HTML <template> para layouts de mensagem reutilizáveis, garantindo estilo e estrutura consistentes.

Outros exemplos de recursos a serem explorados

Além do exemplo de aplicativo de bate-papo, há outros recursos no repositório a serem explorados para aprendizado adicional. Confira os seguintes notebooks no notebooks diretório:

Notebook Descrição
chat_pdf_images.ipynb Este notebook demonstra como converter páginas PDF em imagens e enviá-las para um modelo de visão para inferência.
chat_vision.ipynb Este notebook é fornecido para experimentação manual com o modelo de visão usado no aplicativo.

Conteúdo Localizado: As versões em espanhol dos notebooks estão no diretório notebooks/Spanish/, oferecendo o mesmo aprendizado prático para desenvolvedores hispanofalantes. Os blocos de anotações em inglês e espanhol mostram:

  • Como chamar modelos de visão diretamente para experimentação
  • Como converter páginas PDF em imagens para análise
  • Como ajustar parâmetros e prompts de teste

Limpar os recursos

Limpar recursos do Azure

Os recursos do Azure criados neste artigo são cobrados para sua assinatura do Azure. Se você não espera precisar desses recursos no futuro, exclua-os para evitar incorrer em mais encargos.

Para excluir os recursos do Azure e remover o código-fonte, execute o seguinte comando da Azure Developer CLI:

azd down --purge

Limpar GitHub Codespaces

A exclusão do ambiente GitHub Codespaces garante que você possa maximizar a quantidade de horas gratuitas por núcleo que você tem direito na sua conta.

Importante

Para saber mais sobre os direitos da sua conta do GitHub, confira O GitHub Codespaces inclui mensalmente armazenamento e horas de núcleo.

  1. Entre no painel do GitHub Codespaces.

  2. Localize seus Codespaces atualmente em execução, originados do repositório Azure-Samples//openai-chat-vision-quickstart do GitHub.

  3. Abra o menu de contexto do codespace e selecione Excluir.

Obter ajuda

Registre seu problema nos Problemas do repositório.

Próximas etapas