layahost
Menu · Code examples

Code examples

Complete examples you can copy and run. Each reads the API key from the LAYAHOST_API_KEY environment variable, or TYPESAFE_API_KEY for the Jev SDKs.

Moderate comments before publishing

Publish clean comments, reject clear abuse, and send the uncertain middle to a person. The thresholds are examples: tune them on your own comments.

Moderation
async function moderate(comment) {
  const res = await fetch("https://layahost.com/v1/decide", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.LAYAHOST_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ text: comment, template: "moderation" }),
  });
  if (!res.ok) throw new Error(`layahost ${res.status}: ${await res.text()}`);

  const { probabilities } = await res.json();
  if (probabilities.toxic >= 0.9) return "reject";
  if (probabilities.toxic >= 0.5) return "review";
  return "publish";
}

console.log(await moderate("Thanks, this guide saved me hours!"));
Moderation
import os
import requests

def moderate(comment: str) -> str:
    res = requests.post(
        "https://layahost.com/v1/decide",
        headers={"Authorization": f"Bearer {os.environ['LAYAHOST_API_KEY']}"},
        json={"text": comment, "template": "moderation"},
        timeout=10,
    )
    res.raise_for_status()

    toxic = res.json()["probabilities"]["toxic"]
    if toxic >= 0.9:
        return "reject"
    if toxic >= 0.5:
        return "review"
    return "publish"

print(moderate("Thanks, this guide saved me hours!"))
Moderation
function moderate(string $comment): string
{
    $client = new GuzzleHttp\Client(['base_uri' => 'https://layahost.com', 'timeout' => 10]);

    $res = $client->post('/v1/decide', [
        'headers' => ['Authorization' => 'Bearer ' . getenv('LAYAHOST_API_KEY')],
        'json' => ['text' => $comment, 'template' => 'moderation'],
    ]);

    $toxic = json_decode($res->getBody(), true)['probabilities']['toxic'];

    return match (true) {
        $toxic >= 0.9 => 'reject',
        $toxic >= 0.5 => 'review',
        default => 'publish',
    };
}

echo moderate('Thanks, this guide saved me hours!'), PHP_EOL;

Triage a support ticket with the Jev SDKs

Ask three questions about one ticket in a single /v1/systemone request: what the customer wants, how urgent it is, and whether they mention an order number. The answers are typed, so the SDKs know the labels for each question. Set TYPESAFE_BASE_URL=https://layahost.com and TYPESAFE_API_KEY first.

Ticket triage
from typesafe_sdk import Choice, Noul, Score, TypeSafeClient

ticket = "Order #48213 still hasn't shipped after 10 days. I need it for a wedding on Saturday!"

with TypeSafeClient() as client:  # reads TYPESAFE_BASE_URL and TYPESAFE_API_KEY
    result = client.system_one(
        model="laya-auto",
        state=ticket,
        questions={
            "intent": Choice(
                instructions="What does the customer want?",
                criteria={
                    "delivery_status": "asks where an order is or when it arrives",
                    "refund": "wants money back",
                    "cancellation": "wants to cancel an order or account",
                    "other": "anything else",
                },
            ),
            "priority": Score(
                instructions="How urgent is this ticket?",
                criteria=["low", "normal", "high", "urgent"],
            ),
            "has_order_number": Noul(instructions="Does the message include an order number?"),
        },
    )

levels = ["low", "normal", "high", "urgent"]
print({
    "intent": result.choices["intent"].choice,
    "priority": levels[round(result.scores["priority"].score)],
    "has_order_number": result.nouls["has_order_number"].noul >= 0.5,
})
Ticket triage
import { choice, noul, score, TypeSafeClient } from "@typesafe-ai/sdk";

const ticket = "Order #48213 still hasn't shipped after 10 days. I need it for a wedding on Saturday!";

const client = new TypeSafeClient(); // reads TYPESAFE_BASE_URL and TYPESAFE_API_KEY

const { answers } = await client.systemOne({
  model: "laya-auto",
  state: ticket,
  questions: {
    intent: choice("What does the customer want?", {
      delivery_status: "asks where an order is or when it arrives",
      refund: "wants money back",
      cancellation: "wants to cancel an order or account",
      other: "anything else",
    }),
    priority: score("How urgent is this ticket?", ["low", "normal", "high", "urgent"]),
    has_order_number: noul("Does the message include an order number?"),
  },
});

const levels = ["low", "normal", "high", "urgent"];
console.log({
  intent: answers.intent.choice,
  priority: levels[Math.round(answers.priority.score)],
  has_order_number: answers.has_order_number.noul >= 0.5,
});

This request costs three decisions, $0.000015. See POST /v1/systemone for every field.

Retry with backoff

Retry 429, 503 and 529 after the number of seconds in retry-after, and fail fast on everything else. The Jev SDKs already do this; you only need it for plain HTTP calls.

Retries
const RETRYABLE = new Set([429, 503, 529]);

async function callLayahost(path, body, retries = 3) {
  for (let attempt = 0; ; attempt++) {
    const res = await fetch(`https://layahost.com${path}`, {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.LAYAHOST_API_KEY}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify(body),
    });
    if (res.ok) return res.json();

    if (!RETRYABLE.has(res.status) || attempt === retries) {
      const requestId = res.headers.get("x-request-id") ?? "no request id";
      throw new Error(`layahost ${res.status} (${requestId}): ${await res.text()}`);
    }

    const retryAfter = Number(res.headers.get("retry-after"));
    const delayMs = retryAfter > 0 ? retryAfter * 1000 : 500 * 2 ** attempt;
    await new Promise((resolve) => setTimeout(resolve, delayMs));
  }
}

const result = await callLayahost("/v1/decide", {
  text: "Where is my refund? It has been three weeks.",
  template: "support_intent",
});
console.log(result.decision, result.request_id);
Retries
import os
import time

import requests

RETRYABLE = {429, 503, 529}

def call_layahost(path: str, body: dict, retries: int = 3) -> dict:
    for attempt in range(retries + 1):
        res = requests.post(
            f"https://layahost.com{path}",
            headers={"Authorization": f"Bearer {os.environ['LAYAHOST_API_KEY']}"},
            json=body,
            timeout=10,
        )
        if res.ok:
            return res.json()

        if res.status_code not in RETRYABLE or attempt == retries:
            request_id = res.headers.get("x-request-id", "no request id")
            raise RuntimeError(f"layahost {res.status_code} ({request_id}): {res.text}")

        retry_after = float(res.headers.get("retry-after", 0))
        time.sleep(retry_after if retry_after > 0 else 0.5 * 2**attempt)

result = call_layahost("/v1/decide", {
    "text": "Where is my refund? It has been three weeks.",
    "template": "support_intent",
})
print(result["decision"], result["request_id"])

Classify a batch within the rate limit

Each key may send 600 requests per minute, which is 10 per second. For a batch of texts, space the requests out rather than starting them all at once. These examples allow at most 9 requests per second, with a few in flight at a time.

Batch
import os
import threading
import time
from concurrent.futures import ThreadPoolExecutor

import requests

MAX_PER_SECOND = 9  # the limit is 600 requests per minute per key
lock = threading.Lock()
next_slot = time.monotonic()

def wait_for_slot() -> None:
    global next_slot
    with lock:
        now = time.monotonic()
        slot = max(now, next_slot)
        next_slot = slot + 1 / MAX_PER_SECOND
    time.sleep(slot - now)

def sentiment(text: str) -> str:
    wait_for_slot()
    res = requests.post(
        "https://layahost.com/v1/decide",
        headers={"Authorization": f"Bearer {os.environ['LAYAHOST_API_KEY']}"},
        json={"text": text, "template": "sentiment"},
        timeout=10,
    )
    res.raise_for_status()
    return res.json()["decision"]

reviews = [
    "Arrived on time and works perfectly.",
    "The battery died after two days.",
    "It does the job. Nothing special.",
]

with ThreadPoolExecutor(max_workers=4) as pool:
    for text, label in zip(reviews, pool.map(sentiment, reviews)):
        print(f"{label:>8}  {text}")
Batch
const MAX_PER_SECOND = 9; // the limit is 600 requests per minute per key
let nextSlot = Date.now();

async function waitForSlot() {
  const now = Date.now();
  const slot = Math.max(now, nextSlot);
  nextSlot = slot + 1000 / MAX_PER_SECOND;
  await new Promise((resolve) => setTimeout(resolve, slot - now));
}

async function sentiment(text) {
  await waitForSlot();
  const res = await fetch("https://layahost.com/v1/decide", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.LAYAHOST_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ text, template: "sentiment" }),
  });
  if (!res.ok) throw new Error(`layahost ${res.status}: ${await res.text()}`);
  return (await res.json()).decision;
}

const reviews = [
  "Arrived on time and works perfectly.",
  "The battery died after two days.",
  "It does the job. Nothing special.",
];

const labels = await Promise.all(reviews.map(sentiment));
reviews.forEach((text, i) => console.log(labels[i].padStart(8), text));

A batch of 10,000 texts takes about 19 minutes at this pace. If you ask several questions about each text, send them together in one /v1/systemone request: the rate limit counts requests, not questions.

Laravel HTTP client

In a Laravel app, add the key to config/services.php as 'layahost' => ['key' => env('LAYAHOST_API_KEY')], then use the HTTP client with retries on temporary errors:

Laravel
use Illuminate\Http\Client\RequestException;
use Illuminate\Support\Facades\Http;

$result = Http::withToken(config('services.layahost.key'))
    ->baseUrl('https://layahost.com')
    ->timeout(10)
    ->retry(3, 1000, fn ($e) => $e instanceof RequestException
        && in_array($e->response->status(), [429, 503, 529]))
    ->post('/v1/decide', [
        'text' => 'Where is my refund? It has been three weeks.',
        'template' => 'support_intent',
    ])
    ->json();

echo $result['decision'];

With retry(), a request that still fails after the last attempt throws RequestException. The fixed one-second wait suits 503 and 529; a 429 can ask you to wait up to a minute.