Partilhar via


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

Este artigo mostra como usar modelos multimodais do Azure OpenAI para gerar respostas a mensagens do usuário e imagens carregadas em um aplicativo de bate-papo. 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 em Aplicativos de Contêiner do Azure usando a CLI do Desenvolvedor do Azure.

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

  • 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 Modelo de Linguagem Grande (LLM) multimodal do Azure OpenAI usando a biblioteca OpenAI.

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

Nota

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

Descriçã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á sendo executado como um Aplicativo de Contêiner do Azure. O aplicativo usa identidade gerenciada por meio do Microsoft Entra ID 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 testes sem recursos do Azure.

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

  • O Azure OpenAI 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 onde 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 um segredo com segurança.
  • Arquivos de bíceps para provisionar recursos do Azure, incluindo Azure OpenAI, Aplicativos de Contêiner do Azure, Registro de Contêiner do Azure, Análise de Log do Azure e funções de controle de acesso baseado em função (RBAC).
  • O Microsoft AI Chat Protocol fornece contratos de API padronizados em soluções e idiomas 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 do usuário com arquivos de imagem carregados.
  • Um frontend HTML/JavaScript básico que transmite respostas do back-end usando linhas JSON em um ReadableStream.

Custo

Na tentativa de manter o preço o mais baixo possível neste exemplo, a maioria dos recursos usa um nível de preço básico ou de consumo. Altere o nível da camada 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 amostra.

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 no GitHub Codespaces (em um navegador) ou localmente usando o Visual Studio Code.

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

Ambiente de desenvolvimento aberto

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 for the Web como interface do usuário. Para o ambiente de desenvolvimento mais simples, use o GitHub Codespaces para que você tenha as ferramentas de desenvolvedor corretas e as dependências 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 obter mais informações, consulte GitHub Codespaces mensalmente incluído armazenamento e horas principais.

Use as etapas a seguir para criar um novo espaço de código do GitHub na main ramificação do Azure-Samples/openai-chat-vision-quickstart repositório do GitHub.

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

    Abrir no GitHub Codespaces

  2. Na página Criar espaço de código, revise e selecione Criar novo espaço de código

  3. Aguarde até que o espaço de código inicie. Este processo de arranque pode demorar 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 Azure do aplicativo de chat de exemplo.

Implantar o aplicativo de chat no Azure

Importante

Para manter os custos baixos, este exemplo usa níveis de preços básicos ou de consumo para a maioria dos recursos. Ajuste a camada conforme necessário e exclua 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 tabela a seguir para responder aos prompts:

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

Use 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 esse URL rotulado Deploying service web para abrir o aplicativo de bate-papo 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 "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 cliente Python comum, pequenas alterações de código são necessárias ao usar pontos de extremidade do Azure OpenAI. Este exemplo usa um modelo multimodal do Azure OpenAI para gerar respostas a mensagens do usuário e imagens carregadas.

Base64 Codificando a imagem carregada no frontend

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 seguinte trecho de código de front-end na scripttag do arquivo manipula src/quartapp/templates/index.html essa funcionalidade. A toBase64 função de setareadAsDataURL usa o FileReader método do para ler assincronamente 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 ouvinte de eventos lida com todo o fluxo de interação do chat. Quando o usuário envia uma mensagem, ocorre o seguinte fluxo:

  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 bate-papo, incluindo a imagem carregada
  4. Prepara uma caixa de mensagens do assistente com um indicador de "A escrever..."
  5. Adiciona a mensagem do usuário à matriz de histórico de mensagens
  6. Chama o método do AI Chat Protocol Client getStreamedCompletion() com o histórico e o contexto da mensagem (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 depois de 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.

Nota

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. Este sistema flexível permite que os desenvolvedores trabalhem em diferentes contextos sem alterar o código.

Modos de autenticação explicados
  • Desenvolvimento local (OPENAI_HOST=local): Conecta-se a um serviço de API local compatível com OpenAI (como Ollama ou LocalAI) sem autenticação. Use este modo para testar sem custos de internet ou API.
  • Modelos do GitHub (OPENAI_HOST=github): Usa o mercado de modelos de IA do GitHub com um GITHUB_TOKEN para 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 de 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 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. Este método é recomendado para produção porque elimina a necessidade de gerir segredos. Os Aplicativos de Contêiner do Azure fornecem automaticamente a identidade gerenciada e concedem permissões durante a implantação via Bicep.
  • Desenvolvimento com a CLI do Azure (modo padrão): usa AzureDeveloperCliCredential para autenticar com o Azure OpenAI usando credenciais da CLI do Azure conectadas localmente. Esse modo simplifica o desenvolvimento local sem gerenciar chaves de API.
Principais detalhes da implementação
  • A get_bearer_token_provider() função atualiza as credenciais do Azure e as usa como tokens de portador.
  • O caminho do ponto de extremidade do Azure OpenAI inclui /openai/v1/ para corresponder aos requisitos da biblioteca de cliente 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 credenciais 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 chat_handler() função processa solicitações de chat enviadas para o /chat/stream endpoint. Ele recebe uma solicitação POST com uma carga JSON que segue o Microsoft AI Chat Protocol.

A carga útil 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 adicionais para processamento, incluindo:
    • arquivo: dados de imagem codificados em Base64 (por exemplo, data:image/png;base64,...).
    • file_name: O nome do ficheiro original da imagem carregada (útil para registar ou identificar o tipo de imagem).
  • temperatura (opcional): um flutuador 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á null, e o código lida com 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 OpenAI do Azure. O @stream_with_context decorador mantém o contexto do pedido enquanto transmite a resposta.

Lógica de preparação de mensagens

  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 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: anexe a mensagem do usuário como texto sem formatação.
  3. O detail parâmetro: Defina como "auto" para permitir que o modelo escolha entre detalhes "baixos" e "altos" com base no tamanho da imagem. O baixo detalhe é mais rápido e barato, enquanto o alto detalhe fornece 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])

Nota

Para obter mais informações sobre o parâmetro de imagem detail e configurações relacionadas, consulte a seção Configurações de parâmetros detalhados no artigo "Usar modelos de bate-papo habilitados para visão" do Microsoft Learn.

Em seguida, bp.openai_client.chat.completions obtém a conclusão do chat por meio de uma chamada de API do Azure OpenAI 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),
        )

Finalmente, 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 frontend usa APIs e bibliotecas modernas do navegador para criar uma experiência de bate-papo interativa. Os desenvolvedores podem personalizar a interface ou adicionar recursos entendendo estes componentes:

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

    • <speech-input-button>: Converte fala em texto usando as APIs de Fala da Web.SpeechRecognition 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ê 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 de navegador em vez dos Serviços de Fala do Azure?

    • Sem custos - funciona inteiramente no navegador
    • Resposta instantânea - sem latência de rede
    • Privacidade - os dados de voz permanecem no dispositivo do utilizador
    • Não há necessidade de recursos extras do Azure

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

  2. Visualização de 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. Ícones de Bootstrap 5 e Bootstrap: Fornece componentes e ícones de interface do usuário responsivos. O aplicativo usa o tema Cosmo do Bootswatch para um visual moderno.

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

Outros exemplos de recursos a explorar

Além do exemplo de aplicativo de bate-papo, há outros recursos no repositório para explorar para aprendizado adicional. Confira os seguintes blocos de anotações no notebooks diretório:

Bloco de Notas Description
chat_pdf_images.ipynb Este bloco de anotações 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 cadernos 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

Clean up resources (Limpar recursos)

Limpar recursos do Azure

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

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

azd down --purge

Limpar espaços de código do GitHub

Excluir o ambiente do GitHub Codespaces garante que você possa maximizar a quantidade de direitos de horas gratuitas por núcleo que você obtém para sua conta.

Importante

Para obter mais informações sobre os direitos da sua conta do GitHub, consulte Codespaces do GitHub mensalmente incluídos armazenamento e horas principais.

  1. Faça login no painel do GitHub Codespaces.

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

  3. Abra o menu de contexto do espaço de código e selecione Excluir.

Obter ajuda

Registre seu problema nos problemas do repositório.

Próximos passos