Skip to content

Repository files navigation

Foundry Test Chatbot (chatbot)

CI Docker Hub Image size License: MIT .NET

A minimal .NET 10 web application that lets users chat with an AI assistant powered by Microsoft Foundry through the Microsoft Agent Framework. The backend talks either directly to a deployed model or to an existing Foundry agent, streams responses to a lightweight HTML/JS UI, and is fully configured through environment variables.

The chat experience is also packaged as a zero-dependency embeddable widget that can be dropped onto any website with a single <script> tag.


Run the published image

A ready-to-run container image is published on Docker Hub: congiuluc/foundry-test-chatbot. The full source code lives at github.com/congiuluc/foundry-test-chatbot.

docker run -p 8080:8080 `
    -e AI_MODE=model `
    -e AZURE_OPENAI_ENDPOINT="https://<your-resource>.openai.azure.com/" `
    -e AZURE_OPENAI_DEPLOYMENT_NAME="gpt-4o-mini" `
    -e AZURE_OPENAI_API_KEY="<your-api-key>" `
    congiuluc/foundry-test-chatbot:latest

Running outside Azure (local Docker, another cloud, on-prem): managed identity is not available, so you must provide AZURE_OPENAI_API_KEY. When the image runs on Azure with a managed identity, you can omit the key.

Deploy to Azure: Azure App Service · Azure Container Apps · Azure Kubernetes Service (AKS)


Table of contents


Features

  • ASP.NET Core minimal API (no Blazor, no MVC) serving a static chat page from wwwroot.
  • Two backend modes, selected by the AI_MODE environment variable:
    • model — chat completions against an Azure OpenAI / Foundry model deployment.
    • agent — an existing agent hosted on Microsoft Foundry.
  • Flexible authentication: uses an API key when AZURE_OPENAI_API_KEY is set; otherwise falls back to managed identity (DefaultAzureCredential, honoring AZURE_CLIENT_ID for a user-assigned identity).
  • Streamed responses (token-by-token) over Server-Sent Events.
  • Light / dark / system theme toggle in the chat widget and the demo page.
  • Production-ready plumbing: Serilog logging (console + rolling file), Swagger UI, health checks, response compression, and consistent JSON error handling.
  • Containerized and deployable to Azure Container Apps via the included scripts, or pulled from Docker Hub through the automated release workflow.
  • Embeddable widget: a single self-contained chatbot-widget.js with no runtime dependencies, fully themeable through data-* attributes.

Architecture

src/ChatApp/
├── Program.cs              # App startup, DI, middleware, endpoint mapping
├── Endpoints/              # Minimal API endpoints (chat, health)
├── Services/               # Chat agent providers and credential factory
├── Middleware/             # Centralized error handling
├── Models/                 # Request/response DTOs
├── Configuration/          # Strongly-typed options bound from env vars
├── wwwroot/                # Static chat page + built widget bundle
└── widget/                 # TypeScript sources for the embeddable widget

docs/                       # GitHub Pages site (widget reference & examples)
scripts/                    # PowerShell build/deploy helpers

At runtime the app selects a chat provider based on AI_MODE, resolves credentials (API key or managed identity), and exposes a streaming chat endpoint consumed by both the bundled chat page and the embeddable widget.

Quick start

Prerequisites: .NET 10 SDK, and (for deployment) the Azure CLI with the containerapp extension.

git clone https://github.com/congiuluc/foundry-test-chatbot.git
cd foundry-test-chatbot
dotnet restore src/ChatApp/ChatApp.csproj
dotnet build src/ChatApp/ChatApp.csproj -c Release

Configuration

All configuration is supplied through environment variables:

Variable Mode Description
AI_MODE both model (default) or agent.
AZURE_OPENAI_ENDPOINT model Azure OpenAI / Foundry models endpoint.
AZURE_OPENAI_DEPLOYMENT_NAME model Model deployment name.
AZURE_OPENAI_API_KEY model Optional. When omitted, managed identity is used.
AZURE_FOUNDRY_PROJECT_ENDPOINT agent Foundry project endpoint.
AZURE_FOUNDRY_AGENT_ID agent Name of the existing Foundry agent.
AZURE_FOUNDRY_AGENT_VERSION agent Optional agent version (latest when omitted).
AZURE_CLIENT_ID both Optional user-assigned managed identity client id.
CHAT_SYSTEM_PROMPT model Optional system instructions.
CHAT_AGENT_NAME model Optional agent display name.

Authentication: the API key is optional when running on Azure. By default the app authenticates with managed identity (DefaultAzureCredential, also honoring az login locally and AZURE_CLIENT_ID for a user-assigned identity). If you prefer, or when the image runs outside Azure (local Docker, another cloud, on-prem) where managed identity is unavailable, pass an API key through the AZURE_OPENAI_API_KEY environment variable — when it is set, it takes precedence over managed identity.

Running locally

$env:AI_MODE = "model"
$env:AZURE_OPENAI_ENDPOINT = "https://<your-resource>.openai.azure.com/"
$env:AZURE_OPENAI_DEPLOYMENT_NAME = "gpt-4o-mini"
# Leave AZURE_OPENAI_API_KEY unset to use your `az login` / managed identity.
dotnet run --project src/ChatApp/ChatApp.csproj

Then open the printed URL. Swagger is available at /swagger and the health check at /healthz.

Deployment

Deploy to Azure App Service

The quickest path: run the published Docker Hub image directly as a Linux Web App for Containers — no build required.

az group create --name rg-chat --location westeurope

az appservice plan create `
    --name plan-chat --resource-group rg-chat `
    --is-linux --sku B1

az webapp create `
    --name <app-name> --resource-group rg-chat --plan plan-chat `
    --container-image-name docker.io/congiuluc/foundry-test-chatbot:latest

# The container listens on 8080; route to it and set the app configuration.
az webapp config appsettings set `
    --name <app-name> --resource-group rg-chat `
    --settings `
        WEBSITES_PORT=8080 `
        AI_MODE=model `
        AZURE_OPENAI_ENDPOINT="https://<your-resource>.openai.azure.com/" `
        AZURE_OPENAI_DEPLOYMENT_NAME="gpt-4o-mini"

Image name: use congiuluc/foundry-test-chatbot:latest (Docker Hub is the default registry). Do not prefix it with hub.docker.com/ — that is the website, not the registry endpoint, and causes an "image not found" pull error.

To authenticate with a managed identity instead of an API key, enable the Web App's system-assigned identity and grant it the Cognitive Services OpenAI User role on your Azure OpenAI / Foundry resource, then leave AZURE_OPENAI_API_KEY unset.

Build & push the container image

./scripts/build-and-push.ps1 -RegistryName <acrName> -Tag v1

Deploy to Azure Container Apps

./scripts/deploy-containerapp.ps1 `
    -ResourceGroup rg-chat `
    -EnvironmentName cae-chat `
    -AppName chatapp `
    -RegistryName <acrName> `
    -Tag v1 `
    -EnvVars @{
        AI_MODE = "model"
        AZURE_OPENAI_ENDPOINT = "https://<your-resource>.openai.azure.com/"
        AZURE_OPENAI_DEPLOYMENT_NAME = "gpt-4o-mini"
    }

The script enables a system-assigned managed identity and grants it AcrPull. Remember to also grant that identity access to your Foundry resource (e.g. Azure AI User or Cognitive Services OpenAI User).

Or deploy the published Docker Hub image directly, without building or pushing to ACR:

az containerapp create `
    --name chatapp --resource-group rg-chat `
    --environment cae-chat `
    --image docker.io/congiuluc/foundry-test-chatbot:latest `
    --target-port 8080 --ingress external `
    --env-vars `
        AI_MODE=model `
        AZURE_OPENAI_ENDPOINT="https://<your-resource>.openai.azure.com/" `
        AZURE_OPENAI_DEPLOYMENT_NAME="gpt-4o-mini"

Deploy to Azure Kubernetes Service (AKS)

Apply a Deployment and a Service that pull the public image. Save this as chatapp.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: chatapp
spec:
  replicas: 2
  selector:
    matchLabels:
      app: chatapp
  template:
    metadata:
      labels:
        app: chatapp
    spec:
      containers:
        - name: chatapp
          image: congiuluc/foundry-test-chatbot:latest
          ports:
            - containerPort: 8080
          env:
            - name: AI_MODE
              value: "model"
            - name: AZURE_OPENAI_ENDPOINT
              value: "https://<your-resource>.openai.azure.com/"
            - name: AZURE_OPENAI_DEPLOYMENT_NAME
              value: "gpt-4o-mini"
          readinessProbe:
            httpGet:
              path: /healthz
              port: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: chatapp
spec:
  type: LoadBalancer
  selector:
    app: chatapp
  ports:
    - port: 80
      targetPort: 8080
az aks get-credentials --resource-group rg-chat --name <aks-cluster>
kubectl apply -f chatapp.yaml
kubectl get service chatapp   # note the EXTERNAL-IP once provisioned

For production, keep secrets such as AZURE_OPENAI_API_KEY in a Kubernetes Secret and reference them with valueFrom.secretKeyRef, or use workload identity for managed-identity authentication to Azure OpenAI.

Published container image

Tagging a release (v*.*.*) triggers the release workflow, which builds the image and pushes it to Docker Hub as congiuluc/foundry-test-chatbot. Each release is tagged with its version plus a channel tag derived from the Git tag:

Git tag example Channel tag GitHub release
v1.2.3 latest stable
v1.2.3-prerelease prerelease pre-release
v1.2.3-dev dev pre-release

The workflow requires the DOCKERHUB_USERNAME and DOCKERHUB_TOKEN repository secrets. Pull and run the published image with:

docker pull congiuluc/foundry-test-chatbot:latest
docker run -p 8080:8080 `
    -e AI_MODE=model `
    -e AZURE_OPENAI_ENDPOINT="https://<your-resource>.openai.azure.com/" `
    -e AZURE_OPENAI_DEPLOYMENT_NAME="gpt-4o-mini" `
    congiuluc/foundry-test-chatbot:latest

Embeddable widget

The chat UI is also shipped as a single, self-contained script (chatbot-widget.js, built from the TypeScript sources in src/ChatApp/widget). Drop it on any website to render a floating launcher and chat panel:

Foundry Chatbot widget preview

<script
    src="https://your-app.example.com/chatbot-widget.js"
    data-title="Support"
    data-accent="#2563eb"
    defer></script>

All behaviour and theming is controlled through data-* attributes on the script tag:

Attribute Default Description
data-api-base script origin Backend base URL the widget calls.
data-title Chatbot Header title text.
data-accent #2563eb Accent colour (launcher and primary actions).
data-icon URL of a custom launcher icon image (https:, data:image/, or /path).
data-panel #1e293b Chat panel background colour.
data-user-bubble accent Background colour of user (outgoing) bubbles.
data-bot-bubble #334155 Background colour of bot (incoming) bubbles.
data-text #e2e8f0 Primary text colour inside the panel.
data-position bottom-right Anchor corner: bottom-right or bottom-left.
data-allow-settings true Set to false to hide the in-panel settings UI.
data-greeting Ask me anything to get started. Opening assistant message.
data-storage-key derived LocalStorage key for persisting the conversation.
data-debug false Set to true to enable verbose console logging.

Colour values accept hex (#0f172a), rgb()/rgba(), hsl()/hsla() or CSS named colours; invalid values are ignored and fall back to the defaults.

The widget also includes an interactive light / dark / system theme toggle (a header icon button plus a selector in the settings panel); the chosen theme is remembered per session.

A full reference with live, copyable examples is published as a GitHub Pages site (sources in docs/).

Rebuilding the widget bundle

cd src/ChatApp/widget
npm install
npm run build      # outputs ../wwwroot/chatbot-widget.js

Documentation site

A full widget reference with live, copyable examples is published as a GitHub Pages site (sources in docs/). It is deployed automatically by the Pages workflow whenever the docs/ directory changes on main.

Contributing

Contributions are welcome! Please read CONTRIBUTING.md for the development setup and conventions, and note our Code of Conduct. Changes are tracked in CHANGELOG.md. To report a security issue, follow SECURITY.md instead of opening a public issue.

License

Licensed under the MIT License. See LICENSE for details.

About

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Used by

Contributors

Languages