Skip to main content

Tutorial: translator app

In this tutorial, you will build a Custom App that translates messages into a target language by calling an upstream LLM deployed in DIAL. You will learn the key composition pattern: how one DIAL application calls another deployment (a model) through DIAL Core using per-request key propagation. No prior experience with DIAL app development beyond the Getting started tutorial is required.

Prerequisites​

  • Completed the Getting started with DIAL SDK tutorial
  • Docker and Docker Compose (V2) installed
  • At least 4 GB of free RAM (for running Ollama with a small model)

What you will build​

A translator application that:

  1. Receives a user message through the Unified API.
  2. Calls a locally-running LLM (via Ollama) through DIAL Core to translate the message.
  3. Returns the translated text.
User → DIAL Core → Translator App → DIAL Core → LLM (Ollama) → DIAL Core → Translator App → DIAL Core → User

The critical concept in this tutorial is per-request key propagation — the mechanism that lets your app call other DIAL deployments securely without managing API keys manually.

Step 1: Create the project directory​

mkdir -p dial-translator-app/translator
mkdir -p dial-translator-app/core
cd dial-translator-app

Final project layout:

dial-translator-app/
├── docker-compose.yml
├── core/
│ └── config.json
└── translator/
├── app.py
├── requirements.txt
└── Dockerfile

Step 2: Implement the translator app​

Create translator/app.py:

import os
import json
import httpx
import uvicorn

from aidial_sdk import DIALApp
from aidial_sdk.chat_completion import ChatCompletion, Request, Response

DIAL_URL = os.environ.get("DIAL_URL", "http://core:8080")
MODEL_DEPLOYMENT = os.environ.get("MODEL_DEPLOYMENT", "chat-model")
OLLAMA_MODEL = os.environ.get("OLLAMA_MODEL", "llama3.2:1b")
TARGET_LANGUAGE = os.environ.get("TARGET_LANGUAGE", "French")


class TranslatorApplication(ChatCompletion):
async def chat_completion(
self, request: Request, response: Response
) -> None:
last_message = request.messages[-1]
user_text = last_message.content or ""

translated_text = await self._ask_model(request, user_text)

with response.create_single_choice() as choice:
choice.append_content(translated_text)

async def _ask_model(
self, request: Request, question: str
) -> str:
"""Call a language model through DIAL Core."""

url = (
f"{DIAL_URL}/openai/deployments/"
f"{MODEL_DEPLOYMENT}/chat/completions"
)
headers = {
"Content-Type": "application/json",
}
payload = {
"model": OLLAMA_MODEL,
"messages": [
{
"role": "system",
"content": (
f"You are a translator. Translate the user's message "
f"into {TARGET_LANGUAGE}. Reply with only the "
f"translation, nothing else."
),
},
{
"role": "user",
"content": question
},
],
}

async with httpx.AsyncClient() as client:
resp = await client.post(
url, headers=headers, json=payload, timeout=60.0
)
resp.raise_for_status()
data = resp.json()
return data["choices"][0]["message"]["content"]


app = DIALApp(dial_url=DIAL_URL, propagate_auth_headers=True)
app.add_chat_completion("translator", TranslatorApplication())

To run locally with uvicorn:

uvicorn app:app --host 0.0.0.0 --port 5000

Key points in this code:

  • DIALApp(dial_url=DIAL_URL, propagate_auth_headers=True) enables automatic per-request key propagation. When DIAL Core sends a request to your app, it includes a per-request API key in the api-key header. The SDK captures this key and automatically injects it into any outgoing HTTP requests your app makes to DIAL_URL.
  • The actual per-request key is injected automatically by the SDK's header propagation middleware. You never need to manage API keys manually.

Step 3: Add dependencies​

Create translator/requirements.txt:

aidial-sdk[httpx]==0.27.0
uvicorn==0.34.0

The [httpx] extra enables HTTPX support in the SDK, which is needed for header propagation to work with the OpenAI Python SDK (it uses HTTPX internally).

Step 4: Create the Dockerfile​

Create translator/Dockerfile:

FROM python:3.11-alpine

WORKDIR /app
COPY . /app
RUN pip install --no-cache-dir -r requirements.txt

EXPOSE 5000
CMD ["python", "app.py"]

Step 5: Configure DIAL Core​

Create core/config.json. This configuration registers both the upstream model (Ollama) and your translator application:

{
"models": {
"chat-model": {
"type": "chat",
"displayName": "Local LLM (Ollama)",
"endpoint": "http://ollama:11434/v1/chat/completions"
}
},
"applications": {
"translator": {
"displayName": "Translator",
"description": "Translates messages into French using a local LLM",
"endpoint": "http://translator:5000/openai/deployments/translator/chat/completions"
}
},
"keys": {
"dial_api_key": {
"project": "TEST-PROJECT",
"role": "default"
}
},
"roles": {
"default": {
"limits": {
"chat-model": {},
"translator": {}
}
}
}
}

The limits section grants the default role access to both the chat-model and the translator application. The translator app itself uses the per-request key (which carries the caller's role) to access chat-model through DIAL Core.

Step 6: Write the Docker Compose file​

Create docker-compose.yml:

services:
translator:
build: translator
depends_on:
- core
- ollama
environment:
DIAL_URL: "http://core:8080"
OLLAMA_MODEL: "llama3.2:1b"
MODEL_DEPLOYMENT: "chat-model"
TARGET_LANGUAGE: "French"

ollama:
image: ollama/ollama:0.3.10
ports:
- "11434:11434"
volumes:
- ./.ollama:/root/.ollama

# ========= DIAL =========
core:
image: epam/ai-dial-core:0.25.1
platform: linux/amd64
environment:
aidial.config.files: '["/opt/config/config.json"]'
aidial.storage.overrides: '{ "jclouds.filesystem.basedir": "data" }'
aidial.redis.singleServerConfig.address: "redis://redis:6379"
aidial.identityProviders.test.rolePath: 'roles'
aidial.identityProviders.test.disableJwtVerification: 'true'
aidial.encryption.secret: 'salt'
aidial.encryption.key: 'password'
ports:
- "8080:8080"
volumes:
- ./core:/opt/config
depends_on:
- redis

themes:
image: epam/ai-dial-chat-themes:0.10.0
platform: linux/amd64

chat:
image: epam/ai-dial-chat:0.26.0
platform: linux/amd64
ports:
- "3000:3000"
depends_on:
- themes
- core
environment:
NEXTAUTH_SECRET: "secret"
THEMES_CONFIG_HOST: "http://themes:8080"
DIAL_API_HOST: "http://core:8080"
DIAL_API_KEY: "dial_api_key"
ENABLED_FEATURES: "conversations-section,prompts-section,top-settings,top-clear-conversation,top-chat-info,top-chat-model-settings,empty-chat-settings,header,footer,request-api-key,report-an-issue,likes,conversations-sharing,input-files,attachments-manager,custom-applications,marketplace"

redis:
image: redis:7.2.4-alpine3.19
command: >
redis-server
--maxmemory 256mb
--maxmemory-policy volatile-lfu
--save ""
--appendonly no

Step 7: Pull a model into Ollama​

Start the stack:

docker compose up --build -d

Wait for all containers to start, then pull a small model into Ollama:

docker compose exec ollama ollama pull llama3.2:1b

This downloads a 1B parameter model (approximately 1.3 GB). Wait for the download to complete.

Verify: Confirm the model responds:

docker compose exec ollama ollama run llama3.2:1b "Say hello"

You should see a text response from the model.

Step 8: Test the translator in DIAL Chat​

  1. Open http://localhost:3000 in your browser.
  2. Open the Catalog and select Translator.
  3. Type a message — for example, Hello, how are you today? — and send it.

Verify: The app responds with a French translation, such as Bonjour, comment allez-vous aujourd'hui?.

Step 9: Test via the API​

curl http://localhost:8080/openai/deployments/translator/chat/completions \
-H "Content-Type: application/json" \
-H "api-key: dial_api_key" \
-d '{
"messages": [
{"role": "user", "content": "The weather is beautiful today."}
]
}'

Verify: The response contains a French translation in choices[0].message.content.

Step 10: Experiment with configuration​

Change the target language by updating the environment variable and restarting the translator:

docker compose up -d --build translator

Or modify the TARGET_LANGUAGE value in docker-compose.yml to "Spanish", "German", or any other language, then restart.

Step 11: Stop the stack​

docker compose down

What you learned​

  • How to call an upstream model from a Custom App using the OpenAI Python SDK pointed at DIAL Core.
  • How per-request key propagation works — DIALApp(dial_url=..., propagate_auth_headers=True) automatically injects authentication headers into outgoing requests.
  • How to configure DIAL Core with both a model and an application that depends on it.
  • The app composition pattern: applications can call models (and other applications) through DIAL Core, inheriting platform governance (rate limits, logging, access control) on every hop.

Video walkthrough​

A video version of this tutorial is available: Develop Translator App. The written tutorial above is the authoritative reference — the video may show older UI or versions.

Further reading​