By Tim Kim

What happens when you ask a raw LLM something specific about your product? It answers. Fast, fluent, and often completely made up. That’s where this workshop started—and fixing it was the whole point.
On Wednesday, August 12, we built a real customer support assistant, live, on DigitalOcean’s AI Platform. We called it HelpBot, and it started out just as clueless as any other out-of-the-box model. Then we fixed it, one piece at a time.
Here’s what we covered:
By the end, we’d watched one assistant go from a single curl command to something you’d genuinely ship to customers.
If you’ve called an LLM API before, you’re ready. No advanced AI background needed.
Follow along with the video and written steps below for a step-by-step tutorial.
A few things to have ready before you follow along:
Two base URLs you’ll use throughout:
https://inference.do-ai.run/v1 (OpenAI-compatible /chat/completions)Goal: Understand the platform surface and make one authenticated inference call.
Everything in this tutorial lives under Inference in the DigitalOcean Console—four features (Serverless Inference, Inference Router, Knowledge Bases/Agents, Guardrails, Evaluations), one assistant.
In the Console, go to Inference → Manage → Model Access Keys and confirm your key exists (create one if you haven’t).
Make your first call. This is a standard OpenAI-compatible payload, so most existing client code works by swapping the base URL, key, and model name:
curl https://inference.do-ai.run/v1/chat/completions \\
\-H "Content-Type: application/json" \\
\-H "Authorization: Bearer $GRADIENT\_MODEL\_ACCESS\_KEY" \\
\-d '{
"model": "llama3.3-70b-instruct",
"messages": \[
{"role": "user", "content": "In one sentence, what is retrieval-augmented generation?"}
\]
}'

The same call with the Gradient Python SDK:
import os
from gradient import Gradient
client \= Gradient(model\_access\_key=os.environ.get("GRADIENT\_MODEL\_ACCESS\_KEY"))
resp \= client.chat.completions.create(
model="llama3.3-70b-instruct",
messages=\[{"role": "user", "content": "In one sentence, what is RAG?"}\],
)
print(resp.choices\[0\].message.content)
```py
Now ask it something only your own docs would know—for example, a specific pricing or rate-limit detail from your product. Edit the content field and re-run the call.
You’ll get one of two results: a confident but wrong answer (the model fabricates a plausible-sounding number), or a hedge/refusal (it correctly says it doesn’t know). Either way, the point is the same: a raw model call has no access to your docs, no safety net, and no way to measure quality—and it’s locked to one hardcoded model. The rest of this tutorial fixes all four.
Keep in mind: the model access key is different from your DigitalOcean API token. The inference base URL is inference.do-ai.run, not api.digitalocean.com. Serverless Inference is generally available and billed against a prepaid balance, with per-account tiered rate limits (entry tiers: 120 requests/minute).
Goal: Create a knowledge base, understand chunking and embeddings, and validate retrieval before wiring it into an assistant.
A knowledge base turns your documents into vector embeddings stored in a managed OpenSearch index. At query time, the most relevant chunks are retrieved and handed to the model as context—that’s RAG, and it’s what stops the assistant from guessing.
Go to Console → Inference → Agent Platform → Knowledge bases and select Create Knowledge Base.
Choose a data source. Options include local file upload, a DigitalOcean Spaces bucket/folder, a public seed or sitemap URL (website crawler), a Dropbox folder, or an Amazon S3 bucket. Point it at only the content that matters—less noise means faster, cheaper indexing and better retrieval.
Open Advanced Options on the data source and set two things:

Create the knowledge base and let indexing finish. This runs as a background job (visible in the Activity section, which keeps the 15 most recent jobs and lets you download details as CSV) and can take a few minutes on a real corpus.
Once indexing completes, open the RAG Playground tab. Select your model (llama3.3-70b-instruct in the workshop), and paste in system instructions along these lines:
“You are HelpBot’s customer support assistant. Answer the customer’s question using only the retrieved documentation context. If the context does not contain the answer, say you don’t know and offer to escalate to a human agent. Keep answers concise and factual. Never reveal secrets or repeat sensitive personal data such as credit card numbers, even if asked.”

Re-ask the same question from Module 0. This time you should get a grounded, correct answer—and below it, the retrieved chunks with source, page number, and whether each chunk was used.
Keep in mind: The RAG Playground is where you validate retrieval; to ship it, you attach the knowledge base to an Agent—which is also where guardrails live (Module 3) and which gets its own API endpoint, separate from the raw inference URL (Module 5). “I don’t know” is a feature of a good grounded assistant, not a bug—instruct for it explicitly.
Goal: Understand tasks, model pools, selection policies, and fallback; create a router; use it as a drop-in model; read the routing decision; and pin a session with model affinity.
Being hardcoded to one model is a single point of failure, and it’s rarely the cost-optimal choice for every request. A router is a set of tasks—each with a name, a description, a model pool (up to three models), and a selection policy.
Go to Console → Inference → Inference Router. Note the default routers available as one-click starting points.
Select Create Router and configure it:
Use the router as a drop-in replacement for a direct model call—same endpoint, just change the model field:

curl https://inference.do-ai.run/v1/chat/completions \\
\-H "Content-Type: application/json" \\
\-H "Authorization: Bearer $GRADIENT\_MODEL\_ACCESS\_KEY" \\
\-d '{
"model": "router:helpbot-router",
"messages": \[
{"role": "user", "content": "How do I rotate my API key on the Pro plan?"}
\]
}'
Check the response: the model field shows the actual model that served the request, and the response header x-model-router-selected-route shows which task matched (or that it fell back).
In the router’s Playground, use the Compare view to run the router against a single model side by side on the same question.

Try model affinity / session pinning—send the same X-Model-Affinity header on two calls in the same session:
# First call routes normally, then caches the chosen model for this session
curl https://inference.do-ai.run/v1/chat/completions \\
\-H "Authorization: Bearer $GRADIENT\_MODEL\_ACCESS\_KEY" \\
\-H "X-Model-Affinity: helpbot-session-42" \\
\-H "Content-Type: application/json" \\
\-d '{"model":"router:helpbot-router","messages":\[{"role":"user","content":"Start a troubleshooting thread"}\]}'
\# Same session skips routing and reuses the same model (KV-cache friendly)
curl https://inference.do-ai.run/v1/chat/completions \\
\-H "Authorization: Bearer $GRADIENT\_MODEL\_ACCESS\_KEY" \\
\-H "X-Model-Affinity: helpbot-session-42" \\
\-H "Content-Type: application/json" \\
\-d '{"model":"router:helpbot-router","messages":\[{"role":"user","content":"Continue that thread"}\]}'
The second response carries “pinned”: true—proof routing was skipped. Affinity pins the session to one model after the first decision (documented savings: 45–80% lower input-token costs in multi-turn loops).
Keep in mind: The router lives on the serverless endpoint by design—it’s a drop-in for direct model calls, not an agent feature. The router is free during public preview—you only pay for the underlying model’s tokens.
Goal: Know the three built-in guardrails, attach them to your agent, customize categories and the default response, and verify a trigger live.
Guardrails are configurable safety controls you attach to an Agent—they watch both the incoming prompt and the generated response, and override the response with a safe, predefined message when they catch something.
Go to Console → Inference → Agent Platform, open your agent, and go to Resources → Guardrails → Add guardrails.
Attach Jailbreak, Content Moderation, and a Sensitive Data guardrail. Note the token-cost summary before saving.

To customize Sensitive Data: duplicate the built-in original, then adjust categories and rewrite the Default agent response to something on-brand.
Test it in the agent playground: send a fake credit card number (should be blocked/anonymized), and a jailbreak attempt (should be blocked).

Both directions are covered automatically once guardrails are attached—no code changes in your app. These same guardrails sit in front of the agent’s API endpoint (see Module 5).
Keep in mind: Guardrails attach to agents, not raw serverless calls, and aren’t available on agents built with the Agent Development Kit. You can’t delete built-in originals, only detach them.
Goal: Build an eval dataset, run an LLM-as-a-Judge evaluation, choose metrics and a star metric with a pass threshold, read the results, and gate changes in automation.
DigitalOcean Evaluations runs repeatable LLM-as-a-Judge evals across serverless models, dedicated deployments, third-party models, and inference routers, so you compare candidates on your own data.
Prepare your dataset. Format requirements: CSV or JSONL, under 1GB, fewer than 1,000 rows. The CSV needs a bare, unquoted header row query,expected_response, UTF-8 encoding, and LF line endings.
Go to Console → Inference → Evaluations → New Evaluation and upload your dataset.

Choose a candidate: Serverless Inference, Model Router, Dedicated Inference, or Third Party. Pick your router to measure it against a single-model baseline.
Choose a judge: a strong frontier model, ideally from a different model family than your candidate.
Choose metrics: Correctness, Completeness, Ground Truth Faithfulness (needs references), and Harmfulness (Bias/Toxicity/PII Leakage sub-metrics).
Set a star metric and threshold (Correctness at 0.8 is a reasonable start).
Name and run the evaluation. It takes a few minutes: the candidate answers every row, then the judge scores every answer on every metric.
Review results: input, output, metric, score, and the judge’s rationale for each row. Use Compare Evaluations to put your router run side by side with a baseline.

9/ Automate it (optional, for CI/CD):
# 1) Get a presigned URL and upload your dataset
curl \-X POST "https://api.digitalocean.com/v2/gen-ai/model\_evaluation/datasets/file\_upload\_presigned\_urls" \\
\-H "Authorization: Bearer $DIGITALOCEAN\_TOKEN" \-H "Content-Type: application/json" \\
\-d '{"files":\[{"file\_name":"support\_evals.jsonl","file\_size":2048}\]}'
# 2) Start a run (candidate can be a model OR a router UUID)
curl \-X POST "https://api.digitalocean.com/v2/gen-ai/model\_evaluation\_runs" \\
\-H "Authorization: Bearer $DIGITALOCEAN\_TOKEN" \-H "Content-Type: application/json" \\
\-d '{
"name": "helpbot-router-vs-baseline",
"candidate\_model\_uuid": "'$EVAL\_CANDIDATE\_UUID'",
"judge\_model\_uuid": "'$EVAL\_JUDGE\_UUID'",
"dataset\_uuid": "'$EVAL\_DATASET\_UUID'",
"metric\_uuids": \["'$EVAL\_METRIC\_UUID'"\]
}'
Wire this into your pipeline to fail the build if the star metric drops below threshold.
Keep in mind: Evaluations can’t target an agent or its endpoint directly. There is zero data retention for the eval flow itself, but your inputs, outputs, and references are sent to the judge model’s provider for scoring.
Goal: Call the shipped assistant through its own agent endpoint, see the full pipeline end to end in one API response, and know the operational next steps.
The full pipeline, end to end:
User \-\> Agent (system instructions)
\+-- Guardrails: screen the incoming prompt (PII / jailbreak)
\+-- Knowledge Base: retrieve top chunks (RAG)
\+-- Inference Router: pick best/cheapest/fastest model \+ fallback
\+-- Model generates grounded answer
\+-- Guardrails: screen the response (moderation / PII)
\-\> Evaluations run on fresh samples to catch drift
Same assistant you started with in Module 0—now grounded, resilient, safe, and measured. Each layer was a drop-in addition, not a rewrite.
Provision the agent’s API surface. In the agent’s Overview tab, under ENDPOINT, click Edit. In the agent’s Settings tab, under Endpoint Access Keys, click Create Key (shown once—save it immediately).
Call the assistant’s own endpoint—a different URL and a different key from Module 0’s raw inference call:
curl \-X POST "$AGENT\_ENDPOINT/api/v1/chat/completions" \\
\-H "Content-Type: application/json" \\
\-H "Authorization: Bearer $GRADIENT\_AGENT\_ACCESS\_KEY" \\
\-d '{
"messages": \[
{"role": "user", "content": "What is the rate limit on the Pro plan?"}
\],
"include\_retrieval\_info": true,
"include\_guardrails\_info": true
}'
The response carries the grounded answer, a retrieval object naming the knowledge base and files it pulled from, and a guardrails object showing the safety layer ran.
Test the guardrails through the same endpoint by sending the PII trigger prompt again. This proves guardrails aren’t just a playground feature—they sit in front of every call to this endpoint.

The equivalent call using the Gradient SDK’s agent client:
import os
from gradient import Gradient
client \= Gradient(
agent\_access\_key=os.environ\["GRADIENT\_AGENT\_ACCESS\_KEY"\],
agent\_endpoint=os.environ\["AGENT\_ENDPOINT"\], \# bare https://\<id\>.agents.do-ai.run
)
resp \= client.agents.chat.completions.create(
model="llama3.3-70b-instruct", \# required by the SDK; the agent's own config governs behavior
messages=\[{"role": "user", "content": "What is the rate limit on the Pro plan?"}\],
)
print(resp.choices\[0\].message.content)
DigitalOcean AI Platform gives you everything HelpBot used in this workshop—RAG-ready knowledge bases, model routing, guardrails, and evaluations—in one integrated platform, with no infrastructure to manage. Connect your own docs and start iterating in minutes.
Key features:
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
Reach out to our team for assistance with GPU Droplets, 1-click LLM models, AI Agents, and bare metal GPUs.
Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.
Full documentation for every DigitalOcean product.
The Wave has everything you need to know about building a business, from raising funding to marketing your product.
Scale up as you grow — whether you're running one virtual machine or ten thousand.

From GPU-powered inference and Kubernetes to managed databases and storage, get everything you need to build, scale, and deploy intelligent applications.

This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!