# AICU Jev Skill — TypeSafe の Jev を api.aicu.ai から使う

> `POST /v1/jev` の中身は **TypeSafe AI の Jev**（応答の `model` は `jev-1.13.0`）です。
> 文章ではなく**数値**を返します。選択肢ごとの確率 / 段階の期待値 / 0〜1 の確からしさ。
> JSON を文章から取り出す処理が要りません。
>
> ⚠️ **返るのは確率であって、判断ではありません。**
> `choice` が返す名前は「いちばん確率が高かった選択肢」であって、決定ではありません。
> **しきい値を決めて決定にするのは、呼び出すあなたの仕事です。**
> （2026-09-23 まで `/v1/decisions` という名前でした。「判断」が実物より重い語なので改めました。
> 旧パスは 2026-10-31 まで別名として動き、`Deprecation` / `Sunset` ヘッダを返します）

**Cloudflare Workers AI の `typesafe/jev` と同じモデルです**
（https://developers.cloudflare.com/ai/models/typesafe/jev/ ）。
違うのは**要らないもの**のほうです。

| | Cloudflare / TypeSafe 直 | api.aicu.ai |
|---|---|---|
| TypeSafe のアカウント | 要る | **要らない** |
| Cloudflare のアカウントと AI 残高 | 要る | **要らない** |
| 鍵 | それぞれ別に発行 | **いつもの `aicu_live_` のまま**（scope `llm`） |
| 支払い | 別請求 | AP（前払い）にまとまる |

上流のサンプルは **body をそのまま貼れば動きます**（`questions` は素通しです）。

## Package Info

- **Name**: aicu-jev
- **Version**: 0.1.0
- **Base URL**: https://api.aicu.ai/v1/jev
- **Upstream model**: TypeSafe AI `Jev`（Cloudflare Workers AI 上の `typesafe/jev`）
- **Auth**: APIキー必須 (Bearer Token)・スコープ `llm`（**チャット補完が叩けるキーならそのまま使える**）
- **課金**: **基本 2 AP ＋ 入力 1,000 文字ごとに 1 AP**（切り捨て・2026-09-21 00:00 UTC 発効）。典型的な 1 件は 2 AP。上流の呼び出しに失敗したら全額返金

## なぜこれを使うか

問い合わせ 10 件を、同じ内容で **3 方式 × 3 回ずつ（n=30）** 流した実測です
（2026-09-22・すべて api.aicu.ai 経由・救済処理なし）。

| 方式 | 素直にパースできた | 宣言どおりの値 | p50 | p95 | AP/回 |
|---|---|---|---|---|---|
| **POST /v1/jev** | **30 / 30** | **30 / 30** | **439 ms** | **525 ms** | 2 |
| chat: gpt-5.4-nano に JSON を依頼 | 30 / 30 | 30 / 30 | 949 ms | 1,451 ms | 2 |
| chat: deepseek-v3 に JSON を依頼 | 20 / 30 | 20 / 30 | 1,697 ms | 2,402 ms | 2 |

**「LLM は JSON を返せない」という話ではありません。**
n=6 で測っていた前版はそう読める表を載せていましたが、n=30 に増やすと
gpt-5.4-nano は 30/30 でした。差が残ったのは次の 3 点です。

1. **速い** — p50 で 2.2 倍、p95 で 2.8 倍。裾が短い
2. **同じ入力で同じ答えが返る** — Jev は 10 件すべて 3/3 一致。nano は 1 件で割れた
3. **確率と confidence が付く** — しきい値で「人に回す」分岐が書ける（下記）

deepseek-v3 は 30 回中 10 回、そのままでは JSON として読めませんでした。

## Quick Start

```bash
curl -X POST https://api.aicu.ai/v1/jev \
  -H "Authorization: Bearer aicu_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "state": "問い合わせ本文: 「昨日から /v1/chat/completions が 401 を返します。キーは先週発行したものです。」",
    "questions": {
      "route": { "type": "choice", "instructions": "一次担当を決める",
                 "criteria": { "tech": "技術サポート", "billing": "請求", "sales": "営業", "abuse": "不正利用調査" } },
      "urgency": { "type": "score", "instructions": "緊急度",
                   "criteria": ["翌営業日でよい", "当日中", "数時間以内", "即時"] },
      "needs_human": { "type": "noul", "instructions": "人間に引き継ぐべきか" }
    }
  }'
```

返り値（実際の応答）:

```json
{
  "object": "jev",
  "model": "jev-1.13.0",
  "answers": {
    "route": { "type": "choice", "choice": "tech",
               "probabilities": { "tech": 0.89, "billing": 0.1, "abuse": 0.01, "sales": 0 },
               "confidence": 0.85 },
    "urgency": { "type": "score", "score": 1.57,
                 "legend": { "0": "翌営業日でよい", "1": "当日中", "2": "数時間以内", "3": "即時" },
                 "probabilities": { "0": 0.04, "1": 0.4, "2": 0.5, "3": 0.06 },
                 "confidence": 0.45 },
    "needs_human": { "type": "noul", "noul": 0.8 }
  },
  "usage": { "input_tokens": 492, "output_tokens": 79 },
  "ap_cost": 2
}
```

## 質問の 3 タイプ

| type | 渡すもの | 返るもの |
|---|---|---|
| `choice` | `criteria` = 名前つきの選択肢（オブジェクト） | `choice`（宣言したキーのどれか）＋ `probabilities` ＋ `confidence` |
| `score` | `criteria` = 低い順のラベル（配列） | `score`（連続値）＋ `legend` ＋ `probabilities` ＋ `confidence` |
| `noul` | `instructions` だけ | `noul`（0〜1） |

`noul` は真偽値ではありません。**しきい値は呼び出す側が決めます**
（確認ダイアログなら 0.5、削除なら 0.9、のように）。

**Cloudflare Workers AI（`env.AI.run('typesafe/jev', ...)`）のサンプルは、body をそのまま貼れば動きます。**
`questions` は上流へ素通しです。上流のサンプルが `noul` に付けている
`criteria: { true: ..., false: ... }` も、付けたまま通ります（こちらでは読みません）。
違いは 1 点だけ: **`model` は `questions` と同じ階層（body の直下）に置きます**。
binding の入力（`{ state, questions }`）の中に `model` を入れると上流が 7003 を返すため、
こちらで受け取って検証し、上流へは渡しません。実際に使われた版は応答の `model` に入ります。

## state は文字列でなくてよい

`state` には**構造のあるオブジェクトや配列**を渡せます（上流の仕様）。
`instructions` の中から `` `ticket.message` `` のようにフィールドを参照できるので、
「チケット本文」「注文の明細」「社内規定」を別々に渡して突き合わせさせる、という書き方ができます。
Cloudflare の "Structured refund review" の例がこれです。

```bash
curl -X POST https://api.aicu.ai/v1/jev \
  -H "Authorization: Bearer aicu_live_xxx" -H "Content-Type: application/json" \
  -d '{
    "state": {
      "ticket": { "subject": "Duplicate charge", "message": "I was charged twice for order A-104." },
      "order":  { "id": "A-104", "charges": [ { "amount_usd": 49, "status": "captured" },
                                              { "amount_usd": 49, "status": "captured" } ] },
      "refund_policy": "Duplicate charges are eligible for a refund."
    },
    "questions": {
      "refund_requested":       { "type": "noul", "instructions": "Does `ticket.message` request a refund?" },
      "policy_supports_refund": { "type": "noul", "instructions": "Does `refund_policy` support it, given `order.charges`?" }
    }
  }'
```

受ける型（上流へ直接投げて実測・2026-09-22）: **文字列 / オブジェクト / 配列**。
数値・真偽値・null は上流が受け付けないので、こちらが 400 と理由を返します。
大きさの上限（下記）は、オブジェクトと配列では **JSON にしたときの長さ**で数えます。

## 逃げ道のない選択肢を作らない

`choice` は**宣言した選択肢の中から必ず 1 つ選びます。**「どれでもない」入力を渡すと、
自信のある値で外します。実測（2026-09-22）:

```
state: "Love the new voice model. No question, just wanted to say thanks!"
criteria: { billing, technical, sales, abuse }    ← praise / other が無い
→ choice: "technical"   probabilities: { technical: 0.92, ... }   confidence: 0.90
   urgency: 0.04（正しい）    needs_human: 0.52（唯一ここだけ迷っている）
```

**`confidence` は「宣言した選択肢の中での確からしさ」であって、「そもそも答えられる問いか」ではありません。**
`other` や `none` を 1 つ足しておくこと。足さないなら、別の `noul` で
「この選択肢のどれかに当てはまるか」を一緒に聞いてください。

## confidence を読む

**同じ応答の中で質問ごとに confidence が違います。** 上の例は `route` が 0.85、
`urgency` が 0.45。エージェントは低い confidence を「人に渡す」分岐に使えます。

```python
r = res["answers"]["route"]
if r["confidence"] < 0.6:
    queue_for_review(ticket)
else:
    assign(r["choice"])
```

## 上限

| 項目 | 値 |
|---|---|
| 1 リクエストの質問数 | 20 |
| `state` | 20,000 文字（必須） |
| `instructions`（質問ごと） | 2,000 文字 |
| `criteria` の項目数 / 1 項目 | 20 個 / 500 文字 |
| `questions` 全体 | 64,000 バイト |
| 上流の待ち時間 | 20 秒 |

`model` は任意。`jev-latest`（既定）または `jev-1.13.0` のみ。

## エラー

| status | code | いつ |
|---|---|---|
| 400 | `invalid_parameter` | `state` 欠落 / 質問の形が不正 / 未知の `model` / 上限超過 |
| 402 | `insufficient_credits` | AP 不足 |
| 403 | — | キーに `llm` スコープが無い |
| 502 | `generation_failed` | 上流が失敗（**返金済み**） |
| 503 | `internal_error` | Jev が利用できない（**返金済み**） |

## 使い分け

- **数値で分岐したい** → ここ（しきい値は自分で決める）
- **人が読む文章が欲しい** → `POST /v1/chat/completions`
- 実務では両方使う。**まず Jev で振り分け、文章が要るものにだけ LLM を使う**のが安くて速い

## Support

- Docs: https://api.aicu.ai/docs/decisions-api
- Skill (旧パス): https://api.aicu.ai/skills/decisions は 2026-10-31 まで同じ内容を返します
- Dashboard: https://api.aicu.ai/dashboard

---
*AICU Inc.*
