A small, production-minded demo that turns an incoming support ticket into a structured operations brief using DigitalOcean Serverless Inference.
The model summarizes the issue, assigns a category and urgency, suggests routing tags and a next action, and drafts a customer response. FastAPI keeps the model access key on the server, while a lightweight browser interface renders the validated result.
- Calling DigitalOcean's OpenAI-compatible Chat Completions endpoint.
- Using a single function tool and JSON Schema for structured model output.
- Validating untrusted model output before returning it to the browser.
- Keeping model credentials out of front-end code.
- Handling authentication, rate-limit, timeout, and malformed-response failures.
- Running the same application locally, in Docker, and on App Platform.
- Provisioning the App Platform application with Terraform.
- The browser sends a ticket to the FastAPI backend.
- The backend validates length, plan, and field constraints.
- It sends the ticket to
https://inference.do-ai.run/v1/chat/completions. - The model calls
submit_ticket_triagewith structured arguments. - Pydantic validates those arguments before the API returns them to the browser.
Serverless Inference runs the model. App Platform runs the application that consumes the model.
- Python 3.11 or later.
- A DigitalOcean account with a positive Serverless Inference prepaid balance.
- A model access key
scoped to
mimo-v2.5-pro, or another model that supports tool calling.
Model access keys are preferable to broad personal access tokens for application workloads because they can be scoped to selected models.
Create a virtual environment and install the application:
python3 -m venv .venv
source .venv/bin/activate
python -m pip install -e ".[dev]"Configure the runtime without committing credentials:
cp .env.example .envEdit .env, then export its values and start the API:
set -a
source .env
set +a
uvicorn app.main:app --reload --port 8080Open http://localhost:8080.
You can also call the API directly:
curl --request POST http://localhost:8080/api/triage \
--header 'Content-Type: application/json' \
--data '{
"subject": "Production API is timing out",
"description": "Every request takes more than 30 seconds and checkout is blocked.",
"customer_plan": "business"
}'If APP_ACCESS_TOKEN is configured, add:
--header 'X-App-Access-Token: your-demo-access-code'The automated suite mocks DigitalOcean Inference. It does not need credentials and does not incur inference charges.
ruff check .
ruff format --check .
pytestdocker build -t do-inference-ticket-triage .
docker run --rm \
--publish 8080:8080 \
--env DIGITALOCEAN_INFERENCE_KEY \
--env DIGITALOCEAN_INFERENCE_MODEL=mimo-v2.5-pro \
do-inference-ticket-triageOpen http://localhost:8080/health to check the container without sending a
billable inference request.
Push this project to a GitHub repository, then follow terraform/README.md.
The deployment configuration creates an App Platform service from the repository's Dockerfile and supplies the model access key as an encrypted runtime environment variable.
Terraform state still contains the original secret value. Protect the state, and never commit it.
| Variable | Required | Default | Purpose |
|---|---|---|---|
DIGITALOCEAN_INFERENCE_KEY |
Yes | — | Model access key used by the backend |
DIGITALOCEAN_INFERENCE_MODEL |
No | mimo-v2.5-pro |
Tool-capable model ID |
DIGITALOCEAN_INFERENCE_BASE_URL |
No | https://inference.do-ai.run/v1 |
Serverless API base URL |
INFERENCE_TIMEOUT_SECONDS |
No | 45 |
Upstream request timeout |
APP_ACCESS_TOKEN |
No | — | Shared code for a short-lived public demo |
- Never put the model access key in JavaScript, HTML, screenshots, or Git history.
- Scope the model access key only to models the application needs.
- Ticket text is untrusted input. The system prompt explicitly treats it as data, but prompt-injection defenses are not a substitute for authorization.
- The optional shared access code deters casual misuse; it is not production identity or authorization.
- Add proper authentication and per-user rate limits before operating this as a public service.
- Do not log raw ticket descriptions unless your privacy and retention policies explicitly allow it.
- Human review is required before sending a generated response to a customer.
- Destroy short-lived demo resources and revoke their keys when finished.
Serverless Inference is charged per input and output token and requires a positive prepaid balance. App Platform is billed separately for the running service. The API response shows token usage when the selected model reports it, making it possible to add application-level cost telemetry later.
See the current Inference pricing documentation before publishing cost examples.
.
├── app/
│ ├── config.py # Environment-backed settings
│ ├── inference.py # DigitalOcean API client and tool schema
│ ├── main.py # FastAPI routes and safe error mapping
│ ├── models.py # Input and model-output validation
│ └── static/ # Browser interface
├── terraform/ # App Platform deployment
├── tests/ # Mocked API and inference-client tests
├── Dockerfile
└── pyproject.toml
Prompting a model to return JSON does not guarantee a stable shape. This demo
defines submit_ticket_triage as the only function tool, instructs the model to
call it exactly once, and supplies the Pydantic-generated JSON Schema as its
parameters. It uses tool_choice: "auto" because DigitalOcean's MiMo adapter
currently rejects the named forced-tool object. The returned arguments are then
validated again, making failures explicit instead of quietly sending malformed
or unexpected fields to the UI.