Začínáme s multimodálními vizuálními chatovými aplikacemi pomocí Azure OpenAI

V tomto článku se dozvíte, jak používat Azure multimodální modely OpenAI ke generování odpovědí na zprávy uživatelů a nahraných obrázků v chatovací aplikaci. Tato ukázka chatovací aplikace zahrnuje také veškerou infrastrukturu a konfiguraci potřebnou ke zřízení Azure prostředků OpenAI a nasazení aplikace do Azure Container Apps pomocí rozhraní příkazového řádku pro vývojáře Azure.

Postupujte podle pokynů v tomto článku:

  • Nasaďte chatovací aplikaci Azure kontejneru, která k ověřování používá spravovanou identitu.
  • Nahrajte obrázky, které se použijí jako součást streamu chatu.
  • Chatujte s Azure multimodálním velkým jazykovým modelem OpenAI (LLM) pomocí rozhraní API odpovědí knihovny OpenAI.

Po dokončení tohoto článku můžete začít upravovat nový projekt pomocí vlastního kódu.

Poznámka:

Tento článek používá jednu nebo více šablon aplikací AI jako základ pro příklady a pokyny v tomto článku. Šablony aplikací AI poskytují dobře udržované a snadno použitelné referenční implementace, které pomáhají zajistit vysoce kvalitní výchozí bod pro vaše aplikace AI.

Přehled architektury

Jednoduchá architektura chatovací aplikace je znázorněná v následujícím diagramu: Diagram znázorňující architekturu z klienta do back-endové aplikace

Chatovací aplikace běží jako Azure Container App. Aplikace používá spravovanou identitu prostřednictvím Microsoft Entra ID k ověření pomocí Azure OpenAI v produkčním prostředí místo klíče rozhraní API. Během vývoje aplikace podporuje více metod ověřování, včetně Azure přihlašovacích údajů rozhraní příkazového řádku pro vývojáře a klíčů rozhraní API.

Architektura aplikace spoléhá na následující služby a komponenty:

  • Azure OpenAI představuje zprostředkovatele AI, kterému posíláme dotazy uživatele.
  • Azure Container Apps je prostředí kontejneru, kde je aplikace hostovaná.
  • Spravovaná identita nám pomáhá zajistit zabezpečení na nejvyšší úrovni a eliminuje požadavek, aby vy jako vývojář bezpečně spravoval tajný klíč.
  • Bicep soubory pro zřizování Azure prostředků, včetně Azure OpenAI, Azure Container Apps, Azure Container Registry, Azure Log Analytics a rolí řízení přístupu na základě role (RBAC).
  • Aplikace Python Quart, která používá balíček openai k vygenerování odpovědí na zprávy uživatelů s nahranými soubory obrázků.
  • Základní front-end HTML/JavaScript, který streamuje odpovědi z back-endu pomocí řádků JSON přes readableStream.

Náklady

Při pokusu o co nejnižší ceny v této ukázce používá většina prostředků cenovou úroveň Basic nebo Consumption. Podle potřeby upravte úroveň úrovně na základě zamýšleného využití. Pokud chcete přestat účtovat poplatky, odstraňte prostředky, až budete s článkem hotovi.

Přečtěte si další informace o nákladech v ukázkovém úložišti.

Požadavky

Vývojové prostředí kontejneru je k dispozici se všemi závislostmi potřebnými k dokončení tohoto článku. Vývojový kontejner můžete spustit v GitHub Codespaces (v prohlížeči) nebo místně pomocí Visual Studio Code.

Pokud chcete tento článek použít, musíte splnit následující požadavky:

Otevřené vývojové prostředí

Podle následujících pokynů nasaďte předkonfigurované vývojové prostředí obsahující všechny požadované závislosti pro dokončení tohoto článku.

GitHub Codespaces spouští vývojový kontejner spravovaný GitHub s Visual Studio Code pro web jako uživatelské rozhraní. Pro nejjednodušší vývojové prostředí použijte GitHub Codespaces, abyste měli předinstalované správné vývojářské nástroje a závislosti k dokončení tohoto článku.

Důležité

Všechny účty GitHub můžou každý měsíc používat Codespaces až na 60 hodin zdarma se dvěma základními instancemi. Další informace najdete v úložiště zahrnuté měsíčně a hodinách jádra na GitHub Codespaces.

Pomocí následujícího postupu vytvořte nový GitHub Codespace ve větvi main úložiště Azure-Samples/openai-chat-vision-quickstart GitHub.

  1. Klikněte pravým tlačítkem myši na následující tlačítko a vyberte Otevřít odkaz v novém okně. Tato akce umožňuje mít vývojové prostředí a dokumentaci k dispozici ke kontrole.

    Open in GitHub Codespaces

  2. Na stránce Create codespace (Vytvořit kódový prostor) zkontrolujte a pak vyberte Create new codespace (Vytvořit nový prostor kódu).

  3. Počkejte, až se kódový prostor spustí. Tento proces spuštění může trvat několik minut.

  4. Přihlaste se k Azure pomocí rozhraní příkazového řádku pro vývojáře Azure v terminálu v dolní části obrazovky.

    azd auth login
    
  5. Zkopírujte kód z terminálu a vložte ho do prohlížeče. Postupujte podle pokynů k ověření u vašeho účtu Azure.

Zbývající úlohy v tomto článku probíhají v kontextu tohoto vývojového kontejneru.

Nasazení a spuštění

Ukázkové úložiště obsahuje všechny soubory kódu a konfigurace pro nasazení chatovací aplikace na Azure. Následující kroky vás provedou ukázkovým procesem nasazení chatovací aplikace Azure.

Nasazení chatovací aplikace do Azure

Důležité

Pokud chcete zachovat nízké náklady, tato ukázka používá u většiny prostředků základní nebo spotřební cenové úrovně. Podle potřeby upravte úroveň a odstraňte prostředky, abyste se vyhnuli poplatkům.

  1. Spusťte následující příkaz rozhraní příkazového řádku Azure Developer CLI pro zřízení prostředků Azure a nasazení zdrojového kódu.

    azd up
    
  2. Pomocí následující tabulky odpovězte na výzvy:

    Výzva Odpověď
    Název prostředí Udržujte to krátké a psané malými písmeny. Přidejte svoje jméno nebo alias. Například chat-vision. Používá se jako součást názvu skupiny prostředků.
    Předplatné Vyberte předplatné, v němž chcete vytvořit prostředky.
    Umístění (pro hostování) V seznamu vyberte umístění blízko vás.
    Umístění modelu Azure OpenAI V seznamu vyberte umístění blízko vás. Pokud je stejné umístění dostupné jako vaše první umístění, vyberte ho.
  3. Počkejte, až se aplikace nasadí. Dokončení nasazení obvykle trvá 5 až 10 minut.

Použití chatovací aplikace k kladení otázek do velkého jazykového modelu

  1. V terminálu se po úspěšném nasazení aplikace zobrazí adresa URL.

  2. Chcete-li otevřít chatovací aplikaci v prohlížeči, vyberte adresu URL označenou Deploying service web.

    Snímek obrazovky znázorňující nahraný obrázek, otázku o obrázku, odpověď umělé inteligence a textové pole

  3. V prohlížeči nahrajte obrázek kliknutím na Zvolit soubor a výběrem obrázku.

  4. Položte otázku týkající se nahraného obrázku, například "O čem je obrázek?".

  5. Odpověď pochází z Azure OpenAI a zobrazí se výsledek.

Prozkoumání ukázkového kódu

Tato ukázka používá multimodální model OpenAI Azure k vygenerování odpovědí na uživatelské zprávy a nahrané obrázky.

Kódování Base64 nahraného obrázku v front-endu

Nahraný obrázek musí být kódovaný v Base64, aby ho bylo možné použít přímo jako identifikátor URI dat jako součást zprávy.

V ukázce následující fragment kódu front-endu scriptve značce src/quartapp/templates/index.html souboru zpracovává tyto funkce. Šipková funkce toBase64 používá metodu readAsDataURLFileReader pro asynchronní čtení uloženého obrázku jako řetězec kódovaný v base64.

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

Funkce toBase64 je volána naslouchadlem pro událost formuláře submit.

Posluchač submit událostí zpracovává celý průběh interakce chatu. Když uživatel odešle zprávu, dojde k následujícímu toku:

  1. Získá nahraný soubor obrázku (pokud je k dispozici) a zakóduje jako Base64.
  2. Vytvoří a zobrazí zprávu uživatele v chatu, včetně nahraného obrázku.
  3. Připraví kontejner zpráv asistenta s indikátorem „Píše...”
  4. Přidá zprávu uživatele do pole historie zpráv ve formátu rozhraní API pro odpovědi.
  5. fetch Odešle požadavek POST do koncového /chat/stream bodu s historií a kontextem zprávy (včetně zakódovaného obrázku a názvu souboru Base64).
  6. Zpracovává streamovanou odpověď ve formátu JSON Lines tak, aby zobrazovala každý textový rozdíl postupně.
  7. Zpracovává všechny chyby během streamování.
  8. Přidá tlačítko výstupu řeči po přijetí úplné odpovědi, aby uživatelé mohli slyšet odpověď.
  9. Vymaže vstupní pole a vrátí fokus pro další zprávu.
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": [{"type": "input_text", "text": message}]
    });

    try {
        messageDiv.scrollIntoView();
        const response = await fetch("/chat/stream", {
            method: "POST",
            headers: {"Content-Type": "application/json"},
            body: JSON.stringify({
                messages: messages,
                context: {
                    file: fileData,
                    file_name: file ? file.name : null
                }
            })
        });

        if (!response.ok || !response.body) {
            throw new Error(`Request failed (${response.status})`);
        }

        let answer = "";
        for await (const chunk of readNDJSONStream(response.body)) {
            if (chunk.type === "error" || chunk.type === "response.failed") {
                messageDiv.innerHTML = "Error: " + (chunk.error || "Unknown error");
                break;
            }
            if (chunk.type === "response.output_text.delta") {
                // Clear out the DIV if its the first answer chunk we've received
                if (answer == "") {
                    messageDiv.innerHTML = "";
                }
                answer += chunk.delta;
                messageDiv.innerHTML = converter.makeHtml(answer);
                messageDiv.scrollIntoView();
            }
        }

        messages.push({
            "role": "assistant",
            "content": [{"type": "output_text", "text": 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;
    }
});

Zpracování image pomocí back-endu

src\quartapp\chat.py V souboru se spustí back-endový kód pro zpracování obrázků po konfiguraci ověřování bez klíčů.

Poznámka:

Další informace o tom, jak používat připojení bez klíčů pro ověřování a autorizaci v Azure OpenAI, najdete v článku Get started with the Azure OpenAI security building block na Microsoft Learn.

Konfigurace ověřování

Funkce configure_openai() nastaví klienta OpenAI před tím, než aplikace začne obsluhovat požadavky. K konfiguraci ověřování na základě proměnných prostředí používá dekorátor Quartu @bp.before_app_serving . Tento flexibilní systém umožňuje vývojářům pracovat v různých kontextech beze změny kódu.

Vysvětlení režimů ověřování
  • Místní vývoj (OPENAI_HOST=local): Umožňuje připojení k místní službě rozhraní API kompatibilní s OpenAI (jako je Ollama nebo LocalAI) bez ověřování. Tento režim použijte k testování bez nákladů na internet nebo rozhraní API.
  • Azure OpenAI s API klíčem (AZURE_OPENAI_KEY_FOR_CHATVISION proměnnou prostředí): Používá API klíč k autentizaci. Vyhýbejte se tomuto režimu v produkčním prostředí, protože klíče rozhraní API vyžadují ruční obměnu a v případě vystavení představují bezpečnostní rizika. Použijte ho pro místní testování v kontejneru Dockeru bez Azure CLI přihlašovacích údajů.
  • Production with Managed Identity (RUNNING_IN_PRODUCTION=true): Používá ManagedIdentityCredential k ověření pomocí Azure OpenAI prostřednictvím spravované identity aplikace kontejneru. Tato metoda se doporučuje pro produkční prostředí, protože eliminuje potřebu správy tajných kódů. Azure Container Apps automaticky poskytují spravovanou identitu a udělují oprávnění během nasazení prostřednictvím Bicep.
  • Vývoj s Azure CLI (výchozí režim): Používá AzureDeveloperCliCredential k ověření službou Azure OpenAI pomocí přihlašovacích údajů, které jsou lokálně přihlášeny přes Azure CLI. Tento režim zjednodušuje místní vývoj bez správy klíčů rozhraní API.
Klíčové podrobnosti implementace
  • Funkce get_bearer_token_provider() aktualizuje Azure přihlašovací údaje a používá je jako nosné tokeny.
  • Cesta koncového bodu Azure OpenAI končí /openai/v1/, obecně dostupným koncovým bodem, který je kompatibilní s OpenAI pro modely Microsoft Foundry.
  • Funkce je asynchronní, protože Quart je asynchronní architektura webové aplikace. Quart umožňuje obslužným rutinám požadavků být asynchronní, takže zatímco aplikace čeká na pomalé odpovědi rozhraní LLM API, může server dál zpracovávat další požadavky.

Tady je kompletní kód nastavení ověřování z 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", "azure")

    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 os.getenv("AZURE_OPENAI_KEY_FOR_CHATVISION"):
        bp.openai_client = AsyncOpenAI(
            base_url=os.environ["AZURE_OPENAI_ENDPOINT"].rstrip("/") + "/openai/v1",
            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"].rstrip("/") + "/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"].rstrip("/") + "/openai/v1",
            api_key=token_provider,
        )
        current_app.logger.info("Using Azure OpenAI with az CLI credential for tenant ID: %s", tenant_id)

Funkce obslužné rutiny chatu

Funkce chat_handler() zpracovává žádosti o chat odeslané do koncového /chat/stream bodu. Obdrží požadavek POST s datovou částí JSON.

Datová část JSON zahrnuje:

  • zprávy: Seznam historie konverzací. Každá zpráva má role ("uživatel" nebo "asistent") a content (pole částí obsahu pomocí vstupního formátu rozhraní API odpovědi).
  • kontext: Dodatečné údaje pro zpracování, včetně:
    • soubor: Data obrázku s kódováním Base64 (například data:image/png;base64,...).
    • file_name: Původní název nahraného obrázku (užitečný pro protokolování nebo identifikaci typu obrázku).

Obslužná rutina extrahuje historii zpráv a data obrázků. Pokud se nenahraje žádný obrázek, hodnota obrázku je nulla kód tento případ zpracuje.

@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"]

Sestavení vstupního pole pro požadavky na zpracování obrazu

Funkce response_stream() připraví vstupní pole, které se odešle do rozhraní API pro odpovědi OpenAI Azure. Dekorátor @stream_with_context zachová kontext požadavku při streamování odpovědi.

Logika přípravy vstupu

  1. Začněte s historií konverzací: Funkce začíná all_inputfunkcí , která obsahuje všechny předchozí zprávy s výjimkou poslední (request_messages[0:-1]). Zprávy jsou již ve formátu rozhraní API pro odpovědi z front-endu.
  2. Zpracování aktuální zprávy uživatele na základě přítomnosti obrázku:
    • S obrázkem: Připojte input_image část obsahu k existujícímu poli obsahu uživatele.
    • Bez obrázku: Připojte zprávu uživatele as-is.
    @stream_with_context
    async def response_stream():
        # This sends all messages, so API request may exceed token limits
        all_input = list(request_messages[0:-1])

        # Add the current user message, appending image if provided
        if image:
            user_content = request_messages[-1]["content"] + [{"type": "input_image", "image_url": image}]
            all_input.append({"role": "user", "content": user_content})
        else:
            all_input.append(request_messages[-1])

Dále bp.openai_client.responses.create volá rozhraní API pro odpovědi OpenAI Azure a streamuje odpověď. Tento store=False parametr pokynuje API, aby neukládalo odpovědi na server, čímž se volání stává bezstavovým.

        openai_stream = await bp.openai_client.responses.create(
            model=bp.model_name,
            input=all_input,
            stream=True,
            temperature=0.3,
            store=False,
        )

Odpověď se nakonec streamuje zpět do klienta. Rozhraní API pro odpovědi generuje mnoho typů událostí, ale pouze response.output_text.delta událost je potřebná pro streamovaný vygenerovaný text. Události chyb se protokolují a předávají do front-endu.

        try:
            async for event in openai_stream:
                if event.type == "response.output_text.delta":
                    yield json.dumps({"type": event.type, "delta": event.delta}, ensure_ascii=False) + "\n"
                elif event.type in ("response.failed", "error"):
                    current_app.logger.error("Responses API error: %s", event)
                    yield json.dumps({"type": event.type}, ensure_ascii=False) + "\n"
        except Exception as e:
            current_app.logger.exception("Error in response stream")
            yield json.dumps({"error": str(e)}, ensure_ascii=False) + "\n"

    return Response(response_stream())

Front-endové knihovny a funkce

Front-end používá moderní rozhraní API a knihovny prohlížeče k vytvoření interaktivního chatovacího prostředí. Vývojáři můžou rozhraní přizpůsobit nebo přidat funkce pochopením těchto součástí:

  1. Vstup a výstup řeči: Vlastní webové komponenty používají rozhraní Speech API prohlížeče:

    • <speech-input-button>: Převede řeč na text pomocí rozhraní API SpeechRecognitionslužby Web Speech . Poskytuje tlačítko mikrofonu, které naslouchá hlasovému vstupu a vysílá speech-input-result událost s přepisovaným textem.

    • <speech-output-button>: Předčítá text nahlas pomocí rozhraní API SpeechSynthesis. Zobrazí se po každé odpovědi asistenta s ikonou mluvčího, která uživatelům umožní slyšet odpověď.

    Proč používat rozhraní API prohlížeče místo služby Azure Speech Services?

    • Žádné náklady – běží zcela v prohlížeči.
    • Okamžitá odpověď – žádná latence sítě
    • Ochrana osobních údajů – hlasová data zůstávají na zařízení uživatele
    • Nepotřebujete další Azure prostředky

    Tyto komponenty jsou v src/quartapp/static/speech-input.js a speech-output.js.

  2. Náhled obrázku: Zobrazí nahraný obrázek v chatu před odesláním analýzy a potvrzením. Náhled se automaticky aktualizuje, když je vybraný soubor.

    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. Ikony Bootstrap 5 a Bootstrap: Poskytuje responzivní komponenty a ikony uživatelského rozhraní. Aplikace používá motiv Cosmo ze bootswatch pro moderní vzhled.

  4. Vykreslování zpráv založené na šablonách: Používá prvky HTML <template> pro opakovaně použitelné rozložení zpráv a zajišťuje konzistentní styl a strukturu.

Další ukázkové zdroje informací k prozkoumání

Kromě ukázky chatovací aplikace jsou v úložišti k dispozici i další zdroje informací, které je potřeba prozkoumat. Podívejte se na následující poznámkové bloky v adresáři notebooks.

Notebook Popis
chat_pdf_images.ipynb Tento poznámkový blok ukazuje, jak převést stránky PDF na obrázky a odeslat je do modelu zpracování obrazu pro odvozování.
chat_vision.ipynb Tento poznámkový blok je k dispozici pro ruční experimentování s modelem zpracování obrazu používaným v aplikaci.

Lokalizovaný obsah: Španělské verze poznámkových bloků jsou v notebooks/Spanish/ adresáři a nabízejí stejné praktické učení pro španělské vývojáře. V anglickém i španělském poznámkovém bloku se zobrazují:

  • Jak používat modely počítačového vidění přímo pro experimentování
  • Jak převést stránky PDF na obrázky pro analýzu
  • Jak upravit parametry a testovat výzvy

Uvolnění prostředků

Vyčištění prostředků Azure

Prostředky Azure vytvořené v tomto článku se účtují na vaše předplatné Azure. Pokud v budoucnu tyto prostředky nepotřebujete, odstraňte je, abyste se vyhnuli účtování dalších poplatků.

Pokud chcete odstranit Azure prostředky a odebrat zdrojový kód, spusťte následující příkaz rozhraní příkazového řádku pro vývojáře Azure:

azd down --purge

Vyčistit GitHub Codespaces

Odstraněním prostředí GitHub Codespaces zajistíte, že můžete maximalizovat nárok na počet bezplatných hodin na jádro, které získáte pro svůj účet.

Důležité

Další informace o oprávněních účtu GitHub najdete v tématu měsíčně zahrnuté úložiště a procesorové hodiny GitHub Codespaces.

  1. Přihlaste se k řídicímu panelu GitHub Codespaces.

  2. Najděte své aktuálně běžící Codespaces pocházející z úložiště Azure-Samples//openai-chat-vision-quickstart GitHub.

  3. Otevřete místní nabídku pro codespace a vyberte Odstranit.

Získání pomoci

Zapište svůj problém do Issues úložiště.

Další kroky