mirror of
https://github.com/hanzo-apps/hanzo.ai.git
synced 2026-10-30 14:58:08 +00:00
The product pages from /s3 to /zap, /university and its course data, /trust, /security, /sdks, /support, /tasks, /startups, /sms-opt-in, the buying specialist, Zen, Kai's guide, Studio, the settings pages, and the shared sales, solutions, zap and ui pieces. Client code draws with t(), server components with <T>, copy held as data is marked with phrase() and drawn through t() where it lands. A link or a figure inside a sentence is a value, so a translator reorders the whole sentence. Rebased on main: every page main moved onto Fold or Steps keeps main's layout, with its words wrapped (label, title, lede, points). Steps now draws a step's body through t, as it already drew the title. Settings keeps main's usage panel, refresh, rewards wallet and sign-in wallet rows, each label wrapped. Left English: the skills page's install line (a command and two URLs), and the solution eyebrow and sibling heading built from a kind label. Claude-Session: https://claude.ai/code/session_015XYKLjZtDTJEnMo4eK3AFH
350 lines
15 KiB
TypeScript
350 lines
15 KiB
TypeScript
import type { Metadata } from 'next'
|
||
import { Hero } from '@/components/models/kit'
|
||
import { Box, Text, XStack } from '@/components/ui'
|
||
import { Action, Line, More } from '@hanzo/ui/marketing'
|
||
import { Grid } from '@hanzo/ui/grid'
|
||
import { Band, Card, Head, INK } from '@/components/marketing/band'
|
||
import { Code } from '@/components/code'
|
||
import { SiteHeader, SiteFooter } from '@/components/home/shell'
|
||
import { DOCS, KEYS } from '@/components/home/nav-data'
|
||
import { KAI } from '@/lib/bench-data'
|
||
import { named } from '@/lib/kai'
|
||
import { rate, record } from '@/lib/tally'
|
||
import { phrase } from '@hanzo/i18n'
|
||
import { T } from '@hanzo/i18n/react'
|
||
import { Prose } from './prose'
|
||
|
||
/**
|
||
* /kai/guide: how to call Kai, from the public cookbook (hanzoai/kai) and the
|
||
* Decisions reference. The request and the SDK calls are the cookbook's; the
|
||
* limits are its measured facts and the production read of 2026-10-10. The
|
||
* accuracy row is derived from the research runs (KAI), named by the revision
|
||
* they were measured on.
|
||
*/
|
||
|
||
const TITLE = 'How to use Kai, Hanzo’s decision model'
|
||
const DESCRIPTION =
|
||
'Developer guide for Kai decision models: API reference, structured outputs, low-latency reasoning, and token pricing on Hanzo Cloud.'
|
||
|
||
export const metadata: Metadata = {
|
||
title: TITLE,
|
||
description: DESCRIPTION,
|
||
alternates: { canonical: 'https://hanzo.ai/kai/guide' },
|
||
openGraph: {
|
||
title: TITLE,
|
||
description: DESCRIPTION,
|
||
url: 'https://hanzo.ai/kai/guide',
|
||
siteName: 'Hanzo AI',
|
||
type: 'article',
|
||
},
|
||
twitter: { card: 'summary_large_image', title: TITLE, description: DESCRIPTION },
|
||
}
|
||
|
||
const CURL = `curl -sS --fail-with-body https://api.hanzo.ai/v1/decisions \\
|
||
-H "Authorization: Bearer $HANZO_API_KEY" \\
|
||
-H "Content-Type: application/json" \\
|
||
-d '{
|
||
"model": "kai",
|
||
"state": "Customer email: Cancel my plan immediately. Your service was down during our product launch.",
|
||
"questions": {
|
||
"department": {
|
||
"type": "choice",
|
||
"instructions": "Route ticket to appropriate team",
|
||
"criteria": {
|
||
"executive_escalations": "high-value customer cancellation threats",
|
||
"support": "general technical inquiries",
|
||
"billing": "standard invoice updates"
|
||
}
|
||
},
|
||
"urgency": {
|
||
"type": "score",
|
||
"instructions": "Rate ticket urgency",
|
||
"criteria": ["low", "normal", "high", "critical"]
|
||
},
|
||
"churn_risk": {
|
||
"type": "noul",
|
||
"instructions": "Is this account at risk of churning?"
|
||
}
|
||
}
|
||
}'`
|
||
|
||
const PYTHON = `# pip install hanzoai
|
||
import os
|
||
|
||
from hanzoai.cloud import ApiClient, Configuration
|
||
from hanzoai.cloud.api import AiApi
|
||
from hanzoai.cloud.models.ai_decisions_request import AiDecisionsRequest
|
||
|
||
ai = AiApi(ApiClient(Configuration(access_token=os.environ["HANZO_API_KEY"])))
|
||
decision = ai.post_decisions(AiDecisionsRequest.from_dict({
|
||
"model": "kai",
|
||
"state": {"ticket_id": 4821, "text": "Can I get an extension on my payment due date?"},
|
||
"questions": {
|
||
"team": {
|
||
"type": "choice",
|
||
"instructions": "Which team handles this?",
|
||
"criteria": {"billing": "invoices and payments", "support": "technical issues"},
|
||
},
|
||
"is_churn_risk": {"type": "noul", "instructions": "Is the user churning?"},
|
||
},
|
||
}))
|
||
|
||
print(decision.answers["team"].choice, decision.answers["team"].probabilities)
|
||
print(decision.answers["is_churn_risk"].noul)`
|
||
|
||
const TYPESCRIPT = `// npm install hanzoai
|
||
import { AiApi, Configuration } from 'hanzoai'
|
||
|
||
const ai = new AiApi(new Configuration({ accessToken: process.env.HANZO_API_KEY }))
|
||
const { data: decision } = await ai.postDecisions({
|
||
aiDecisionsRequest: {
|
||
model: 'kai',
|
||
state: { pull_request: 104, diff_lines: 480, files: ['auth.ts', 'token.go'] },
|
||
questions: {
|
||
risk: {
|
||
type: 'score',
|
||
instructions: 'Rate security risk of code change',
|
||
criteria: ['low: documentation or cosmetic', 'medium: internal logic', 'high: security or auth paths'],
|
||
},
|
||
},
|
||
},
|
||
})
|
||
|
||
console.log(decision.answers.risk.score, decision.answers.risk.probabilities)`
|
||
|
||
const BEFORE = `# Jev on OpenRouter
|
||
curl https://openrouter.ai/api/alpha/decisions \\
|
||
-H "Authorization: Bearer $OPENROUTER_API_KEY" \\
|
||
-H "Content-Type: application/json" \\
|
||
-d '{"model": "typesafe/jev-1.13", "state": "…", "questions": {…}}'`
|
||
|
||
const AFTER = `# Kai on Hanzo: the same body
|
||
curl https://api.hanzo.ai/v1/decisions \\
|
||
-H "Authorization: Bearer $HANZO_API_KEY" \\
|
||
-H "Content-Type: application/json" \\
|
||
-d '{"model": "kai", "state": "…", "questions": {…}}'`
|
||
|
||
/** The three question types: what a question sends and what its answer holds, as the cookbook states them. */
|
||
const TYPES: readonly [string, string, string][] = [
|
||
['choice', phrase('`criteria`: option → description, or a list of options.'), phrase('`choice`, `probabilities` (one per option, summing to 1), `confidence`.')],
|
||
['score', phrase('`criteria`: the levels, lowest first.'), phrase('`score` (the expected level index), `legend`, `probabilities` by level index.')],
|
||
['noul', phrase('`instructions`: a yes/no question.'), phrase('`noul`: P(true). kai-1.3, the default, does not read a noul’s `criteria`.')],
|
||
]
|
||
|
||
/** What a request may hold and what it costs, from the cookbook's measured facts and production's read. */
|
||
const LIMITS: readonly [string, string][] = [
|
||
[phrase('Model'), phrase('The model id `kai` answers from kai-1.3, the default. Ask for `kai-1.4` by id for the unified version: one encoder and one head, trained on long states. Ask for `kai-1.5` by id for the agentic preview: whether a tool fits a request, and whether to call one at all.')],
|
||
[phrase('Questions'), phrase('Up to 100 a request; 101 is refused with 422.')],
|
||
[
|
||
phrase('State'),
|
||
phrase('kai-1.3, the default, was trained on 1,024 tokens of state beside each question. Up to 32,768 is answered with `routing.extrapolated: true` and is unreliable: a fact at the end of a 3,982-token state read 0.507. `kai-1.4` was trained on long states and marks no read up to 32,768 extrapolated. Past 32,768 the answer is 422 `state_too_long`.'),
|
||
],
|
||
[phrase('Time'), phrase('One yes/no question takes <1 ms of server time at p50 in production on GPU (0.27 ms cached in process). Every response reports its own `latency_ms`.')],
|
||
[phrase('Price'), phrase('$0.021 per million input tokens; output is free. Jev costs $0.042.')],
|
||
]
|
||
|
||
export default function KaiGuidePage() {
|
||
const tally = KAI ? record(KAI.index.wins) : null
|
||
const revision = KAI ? named(KAI.run) : null
|
||
return (
|
||
<>
|
||
<SiteHeader />
|
||
<Box render="main" minH="100vh" bg="$background" pt="$10" $platform-web={{ color: 'var(--foreground)' }}>
|
||
<Hero
|
||
crumb={phrase('Kai Guide')}
|
||
badge={phrase('Hanzo decision model · 0 output tokens')}
|
||
title={phrase('How to Use Kai')}
|
||
lede={
|
||
<T
|
||
values={{
|
||
endpoint: <Text fontFamily="$mono">POST /v1/decisions</Text>,
|
||
model: <Text fontFamily="$mono">model: "kai"</Text>,
|
||
}}
|
||
>
|
||
{'Send a state and typed questions to {endpoint} with {model}. Kai answers each question with a probability for every answer it allows, and writes no text.'}
|
||
</T>
|
||
}
|
||
>
|
||
<Action href={KEYS} fill>
|
||
<T>Get an API key</T>
|
||
</Action>
|
||
<Action href={`${DOCS}/docs/decisions`}>
|
||
<T>Read the reference</T>
|
||
</Action>
|
||
</Hero>
|
||
|
||
<Band pad="$11" measure={960}>
|
||
<Head
|
||
eyebrow={phrase('Questions')}
|
||
title={<T>Three question types</T>}
|
||
lede={<T>Name each question; its answer comes back under the same name.</T>}
|
||
/>
|
||
<Grid columns={{ min: 260, max: 3 }} gap={16}>
|
||
{TYPES.map(([type, send, get]) => (
|
||
<Card key={type} p="$5">
|
||
<Line weight="700" mb="$2">
|
||
<Text fontFamily="$mono">{type}</Text>
|
||
</Line>
|
||
<Line color="var(--muted-foreground)" size="sm" mb="$2">
|
||
<T>Send</T> <Prose text={send} />
|
||
</Line>
|
||
<Line color="var(--muted-foreground)" size="sm">
|
||
<T>Get</T> <Prose text={get} />
|
||
</Line>
|
||
</Card>
|
||
))}
|
||
</Grid>
|
||
</Band>
|
||
|
||
<Band pad="$11" measure={960} ground={INK}>
|
||
<Head
|
||
eyebrow={phrase('Call it')}
|
||
title={<T>One request, three questions</T>}
|
||
lede={
|
||
<T>
|
||
Every response also carries id, model, usage (output_tokens is 0), routing (the checkpoint that answered: kai-1.3 for the model id kai), state_hash and latency_ms.
|
||
</T>
|
||
}
|
||
/>
|
||
<Grid columns={1} gap={24}>
|
||
<Card p="$4" bg="$panel">
|
||
<Code lang="bash" code={CURL} />
|
||
</Card>
|
||
<Card p="$4" bg="$panel">
|
||
<Code lang="python" code={PYTHON} />
|
||
</Card>
|
||
<Card p="$4" bg="$panel">
|
||
<Code lang="typescript" code={TYPESCRIPT} />
|
||
</Card>
|
||
</Grid>
|
||
</Band>
|
||
|
||
<Band pad="$11" measure={960}>
|
||
<Head eyebrow={phrase('Limits')} title={<T>What a request may hold</T>} />
|
||
<Box>
|
||
{LIMITS.map(([name, text]) => (
|
||
<XStack key={name} gap="$4" py="$3" borderTopWidth={1} borderColor="var(--border)" items="baseline" flexWrap="wrap">
|
||
<Text width={120} $max-sm={{ width: '100%' }} fontSize="$3" lineHeight="$3" fontWeight="600" color="$ink">
|
||
<T>{name}</T>
|
||
</Text>
|
||
<Text flex={1} minW={240} fontSize="$2" lineHeight="$2" color="var(--muted-foreground)">
|
||
<Prose text={text} />
|
||
</Text>
|
||
</XStack>
|
||
))}
|
||
</Box>
|
||
</Band>
|
||
|
||
<Band pad="$11" measure={960}>
|
||
<Head
|
||
eyebrow={phrase('Recipes & Blueprints')}
|
||
title={<T>Composable Decision Recipes</T>}
|
||
lede={
|
||
<T>
|
||
Modular Rust structs and TypeScript workflows that sit inside your pipelines: retail triage, industrial diagnostics, confidence routing, and agent loops.
|
||
</T>
|
||
}
|
||
/>
|
||
<Grid columns={{ min: 280, max: 3 }} gap={16}>
|
||
<Card p="$5">
|
||
<Line weight="700" mb="$2">
|
||
<Text fontFamily="$mono">recipes::retail</Text>
|
||
</Line>
|
||
<Line color="var(--muted-foreground)" size="sm" mb="$3">
|
||
<T>Shelf inventory triage, return fraud checks, and corporate pricing safety guardrails.</T>
|
||
</Line>
|
||
</Card>
|
||
<Card p="$5">
|
||
<Line weight="700" mb="$2">
|
||
<Text fontFamily="$mono">recipes::industrial</Text>
|
||
</Line>
|
||
<Line color="var(--muted-foreground)" size="sm" mb="$3">
|
||
<T>Multi-criteria powertrain trade studies and telemetry equipment readiness gating.</T>
|
||
</Line>
|
||
</Card>
|
||
<Card p="$5">
|
||
<Line weight="700" mb="$2">
|
||
<Text fontFamily="$mono">recipes::routing</Text>
|
||
</Line>
|
||
<Line color="var(--muted-foreground)" size="sm" mb="$3">
|
||
<T>Confidence margin gates that stand on high-certainty decisions and escalate low-confidence queries to Zen.</T>
|
||
</Line>
|
||
</Card>
|
||
<Card p="$5">
|
||
<Line weight="700" mb="$2">
|
||
<Text fontFamily="$mono">recipes::zen</Text>
|
||
</Line>
|
||
<Line color="var(--muted-foreground)" size="sm" mb="$3">
|
||
<T>OpenAI-compatible request builders and SSE stream accumulators for reasoning thought tokens.</T>
|
||
</Line>
|
||
</Card>
|
||
<Card p="$5">
|
||
<Line weight="700" mb="$2">
|
||
<Text fontFamily="$mono">recipes::kata</Text>
|
||
</Line>
|
||
<Line color="var(--muted-foreground)" size="sm" mb="$3">
|
||
<T>Autonomous test-and-repair agent state machine gating code patch generation vs human review.</T>
|
||
</Line>
|
||
</Card>
|
||
<Card p="$5">
|
||
<Line weight="700" mb="$2">
|
||
<Text fontFamily="$mono">recipes::gen</Text>
|
||
</Line>
|
||
<Line color="var(--muted-foreground)" size="sm" mb="$3">
|
||
<T>Conversational assistant harness managing multi-turn history and Model Context Protocol (MCP) dispatch.</T>
|
||
</Line>
|
||
</Card>
|
||
</Grid>
|
||
<XStack mt="$6" flexWrap="wrap" gap="$4">
|
||
<Action href={`${DOCS}/docs/guides/cookbook/recipes`}>
|
||
<T>Read the Recipes Guide</T>
|
||
</Action>
|
||
<Action href={`${DOCS}/docs/guides/cookbook`}>
|
||
<T>View All Cookbook Pipelines</T>
|
||
</Action>
|
||
<Action href="https://github.com/hanzoai/kai/tree/main/recipes" target="_blank" rel="noopener noreferrer">
|
||
<T>Kai recipes on GitHub ↗</T>
|
||
</Action>
|
||
</XStack>
|
||
</Band>
|
||
|
||
<Band pad="$11" measure={960} ground={INK}>
|
||
<Head
|
||
eyebrow={phrase('Coming from Jev')}
|
||
title={<T>Change the URL, the key and the model</T>}
|
||
lede={<T values={{ model: 'typesafe/jev-1.13' }}>{'Kai takes the request body Jev takes. The same endpoint also serves {model} with a Hanzo key, at Jev’s price.'}</T>}
|
||
/>
|
||
<Grid columns={{ min: 320, max: 2 }} gap={20}>
|
||
<Card p="$5">
|
||
<Code lang="bash" code={BEFORE} />
|
||
</Card>
|
||
<Card p="$5">
|
||
<Code lang="bash" code={AFTER} />
|
||
</Card>
|
||
</Grid>
|
||
<Line color="var(--muted-foreground)" size="sm" mt="$6">
|
||
<T values={{ questions: '100', tokens: '32,768', status: '422', input: <Text fontFamily="$mono">input</Text> }}>
|
||
{'What differs: Kai answers at most {questions} questions a request, refuses a state past {tokens} tokens with {status}, and kai-1.3, the default, ignores a noul’s criteria. OpenAI’s Decisions body ({input} and a list of questions) is refused.'}
|
||
</T>
|
||
</Line>
|
||
{tally && revision && tally.kai != null && tally.jev != null ? (
|
||
<Line color="var(--muted-foreground)" size="sm" mt="$4">
|
||
<T values={{ revision, kai: rate(tally.kai), jev: rate(tally.jev), total: tally.total }}>
|
||
{'On the frozen harness, {revision} averaged {kai} accuracy against Jev’s {jev} across the {total} suites it leads, both models on the same questions.'}
|
||
</T>
|
||
</Line>
|
||
) : null}
|
||
{tally ? (
|
||
<XStack mt="$4">
|
||
<More href="/kai#benchmarks">
|
||
<T>The runs →</T>
|
||
</More>
|
||
</XStack>
|
||
) : null}
|
||
</Band>
|
||
</Box>
|
||
<SiteFooter />
|
||
</>
|
||
)
|
||
}
|