mirror of
https://github.com/hanzo-apps/hanzo.ai.git
synced 2026-10-29 07:20:43 +00:00
339 lines
14 KiB
TypeScript
339 lines
14 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'
|
||
|
||
/**
|
||
* /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', '`criteria`: option → description, or a list of options.', '`choice`, `probabilities` (one per option, summing to 1), `confidence`.'],
|
||
['score', '`criteria`: the levels, lowest first.', '`score` (the expected level index), `legend`, `probabilities` by level index.'],
|
||
['noul', '`instructions`: a yes/no question.', '`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][] = [
|
||
['Model', '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.'],
|
||
['Questions', 'Up to 100 a request; 101 is refused with 422.'],
|
||
[
|
||
'State',
|
||
'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`.',
|
||
],
|
||
['Time', '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`.'],
|
||
['Price', '$0.021 per million input tokens; output is free. Jev costs $0.042.'],
|
||
]
|
||
|
||
/** A sentence whose `code` spans are set in the mono face. */
|
||
function Prose({ text }: { text: string }) {
|
||
return (
|
||
<>
|
||
{text.split('`').map((part, i) =>
|
||
i % 2 ? (
|
||
<Text key={i} fontFamily="$mono">
|
||
{part}
|
||
</Text>
|
||
) : (
|
||
part
|
||
),
|
||
)}
|
||
</>
|
||
)
|
||
}
|
||
|
||
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="Kai Guide"
|
||
badge="Hanzo decision model · 0 output tokens"
|
||
title="How to Use Kai"
|
||
lede={
|
||
<>
|
||
Send a state and typed questions to <Text fontFamily="$mono">POST /v1/decisions</Text> with{' '}
|
||
<Text fontFamily="$mono">model: "kai"</Text>. Kai answers each question with a probability for every answer it
|
||
allows, and writes no text.
|
||
</>
|
||
}
|
||
>
|
||
<Action href={KEYS} fill>
|
||
Get an API key
|
||
</Action>
|
||
<Action href={`${DOCS}/docs/decisions`}>Read the reference</Action>
|
||
</Hero>
|
||
|
||
<Band pad="$11" measure={960}>
|
||
<Head eyebrow="Questions" title="Three question types" lede="Name each question; its answer comes back under the same name." />
|
||
<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">
|
||
Send <Prose text={send} />
|
||
</Line>
|
||
<Line color="var(--muted-foreground)" size="sm">
|
||
Get <Prose text={get} />
|
||
</Line>
|
||
</Card>
|
||
))}
|
||
</Grid>
|
||
</Band>
|
||
|
||
<Band pad="$11" measure={960} ground={INK}>
|
||
<Head
|
||
eyebrow="Call it"
|
||
title="One request, three questions"
|
||
lede="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."
|
||
/>
|
||
<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="Limits" title="What a request may hold" />
|
||
<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">
|
||
{name}
|
||
</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="Recipes & Blueprints"
|
||
title="Composable Decision Recipes"
|
||
lede="Modular Rust structs and TypeScript workflows that sit inside your pipelines: retail triage, industrial diagnostics, confidence routing, and agent loops."
|
||
/>
|
||
<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">
|
||
Shelf inventory triage, return fraud checks, and corporate pricing safety guardrails.
|
||
</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">
|
||
Multi-criteria powertrain trade studies and telemetry equipment readiness gating.
|
||
</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">
|
||
Confidence margin gates that stand on high-certainty decisions and escalate low-confidence queries to Zen.
|
||
</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">
|
||
OpenAI-compatible request builders and SSE stream accumulators for reasoning thought tokens.
|
||
</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">
|
||
Autonomous test-and-repair agent state machine gating code patch generation vs human review.
|
||
</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">
|
||
Conversational assistant harness managing multi-turn history and Model Context Protocol (MCP) dispatch.
|
||
</Line>
|
||
</Card>
|
||
</Grid>
|
||
<XStack mt="$6" flexWrap="wrap" gap="$4">
|
||
<Action href={`${DOCS}/docs/guides/cookbook/recipes`}>Read the Recipes Guide</Action>
|
||
<Action href={`${DOCS}/docs/guides/cookbook`}>View All Cookbook Pipelines</Action>
|
||
<Action href="https://github.com/hanzoai/kai/tree/main/recipes" target="_blank" rel="noopener noreferrer">
|
||
Kai recipes on GitHub ↗
|
||
</Action>
|
||
</XStack>
|
||
</Band>
|
||
|
||
<Band pad="$11" measure={960} ground={INK}>
|
||
<Head
|
||
eyebrow="Coming from Jev"
|
||
title="Change the URL, the key and the model"
|
||
lede="Kai takes the request body Jev takes. The same endpoint also serves typesafe/jev-1.13 with a Hanzo key, at Jev’s price."
|
||
/>
|
||
<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">
|
||
What differs: Kai answers at most 100 questions a request, refuses a state past 32,768 tokens with 422, and kai-1.3, the default, ignores a
|
||
noul’s criteria. OpenAI’s Decisions body (<Text fontFamily="$mono">input</Text> and a list of questions) is refused.
|
||
</Line>
|
||
{tally && revision && tally.kai != null && tally.jev != null ? (
|
||
<Line color="var(--muted-foreground)" size="sm" mt="$4">
|
||
On the frozen harness, {revision} averaged {rate(tally.kai)} accuracy against Jev’s {rate(tally.jev)} across the {tally.total} suites it
|
||
leads, both models on the same questions.
|
||
</Line>
|
||
) : null}
|
||
{tally ? (
|
||
<XStack mt="$4">
|
||
<More href="/kai#benchmarks">The runs →</More>
|
||
</XStack>
|
||
) : null}
|
||
</Band>
|
||
</Box>
|
||
<SiteFooter />
|
||
</>
|
||
)
|
||
}
|