# web.rest.aiDecision

`web.rest.aiDecision` 是纯 aardio 的 AI 结构化决策客户端，目前实现 OpenRouter 的 `POST /api/alpha/decisions` 协议。

## 定位与安装位置

- `web.rest.aiChat` 面向生成式聊天；`web.rest.aiDecision` 面向结构化判定、分类和评分。两者按能力命名，本库不继承聊天协议。
- **默认模型与全部示例使用 `inception/mercury-decide`，优先面向中文任务。** 微软说明其由 Qwen3.5-9B 后训练而来；中文质量仍应以自己的业务样本评估，不能仅凭基础模型推断所有任务的准确率。
- 本库按协议能力设计，不限定模型厂商。其他模型与服务必须支持 Decisions 协议，不能将任意聊天模型直接用于此接口。
- Jev（`~typesafe/jev-latest`）**仅作为兼容选择**。按现有使用反馈，Jev 中文支持较差，不推荐用于中文任务；本库不自动回退到 Jev。
- 公共库：`~/lib/web/rest/aiDecision.aardio`。
- 使用指南：`~/docs/library/web/rest/aiDecision/index.md`；
- 端点目前带有 `alpha`，应保留原始响应并按服务端实际支持的字段使用，不保证未来版本不变。

## 快速开始

```aardio
import web.rest.aiDecision;

var client = web.rest.aiDecision({
    // 省略 key 时读取环境变量 AI_DECISION_API_KEY。
    // 也可以指定 key = "你的 OpenRouter API Key"，不要分发真实密钥。
    model = "inception/mercury-decide";
});

if(!#client.config.key){
    print("请先设置环境变量 AI_DECISION_API_KEY，或在客户端配置中填写自己的 key。");
    return;
}

var questions = {
    department = ["choice", "这条工单应由哪个团队处理？", {
        ["财务"] = "付款、发票和退款问题";
        ["技术"] = "软件故障、服务中断和系统集成问题";
        ["销售"] = "价格咨询、套餐升级和购买意向";
        ["其他"] = "不属于以上类别或信息不足";
    }];
    urgent = ["noul", "这条工单是否需要立即处理？"];
    severity = ["score", "当前问题对业务造成的影响有多严重？", [
        "轻微影响，主要功能正常",
        "部分功能受影响，业务仍可继续",
        "核心业务完全中断，无法继续工作"
    ]];
};

var result,err,errCode = client.evaluate({
    state = {
        message = "生产系统从今天早上起完全无法登录，所有员工都无法开展工作，请立即修复！";
    };
    questions = questions;
});

if(!result){
    print("请求失败：",err,errCode);
    return;
}

print("实际模型：",result.model);
print("负责团队：",result.answers.department.choice);
print("需要立即处理的概率：",result.answers.urgent.noul);
print("业务影响评分（0～2）：",result.answers.severity.score);
print("输入、输出用量：",result.usage.input_tokens,result.usage.output_tokens);
```

后面的代码片段沿用这里的 `client`。协议字段、类型名与程序标识符保留原样，待判断内容、问题说明、选项和输出提示均使用中文。

## 问题声明

`questions` 是按问题 ID 命名的表，每项可使用以下两种数据形式，并允许混用：

- **数组简写**：`[type, instructions, criteria]`。`noul` 通常只需前两项；`choice` 的第 3 项为选项到说明的映射表，`score` 的第 3 项为从低到高的等级数组。
- **原生对象**：`{type="noul";instructions="是否紧急？"}`。需要自解释字段名或额外协议字段时优先使用。

例如，同一请求中可以混用：

```aardio
var questions = {
    urgent = ["noul", "这条工单是否需要立即处理？"];
    department = {
        type = "choice";
        instructions = "这条工单应由哪个团队处理？";
        criteria = {
            ["财务"] = "付款、发票和退款问题";
            ["技术"] = "软件故障、服务中断和系统集成问题";
            ["销售"] = "价格咨询、套餐升级和购买意向";
            ["其他"] = "不属于以上类别或信息不足";
        };
    };
    outage = {type="noul";instructions="当前核心业务是否完全中断？";criteria={
        ["true"] = "无法继续开展核心业务";
        ["false"] = "核心业务仍然可以继续";
    }};
};
```

**声明只是数据，不会请求模型。** `evaluate`（包括其别名）调用 `buildPayload` 时，才将问题的纯数组 `[]` 转换为接口要求的对象。不要把数组简写直接传给底层 `post`。

仅转换 `questions` 的直接成员；不转换 `state` 中的数组、`choice` 选项说明或 `score` 等级数组。不修改调用者的声明，原生问题对象中的扩展字段会保留。数组简写必须有 2～3 项且前两项为字符串，否则 `buildPayload` / `evaluate` 返回 `null,err`，不会发送请求；其他协议约束由服务端检查。

`noul.criteria` 可省略；指定时使用字符串键 `"true"` 与 `"false"`，不要改为中文键或布尔键。它们的说明可以是中文。需要结构化 `instructions` 或服务支持的扩展字段时，使用原生问题对象；数组简写仍要求前两项为字符串。

## 两种调用方式

### `(state, questions[, model])`

```aardio
var result = client.evaluate(
    "同一订单扣了两次款，请退回重复扣款。",
    {
        refund = ["noul", "客户是否要求退回款项？"];
        team = ["choice", "这条工单应由哪个团队处理？", {
            ["财务"] = "付款和退款问题";
            ["客服"] = "一般使用咨询或其他问题";
        }];
    }
);
```

### 完整请求表

```aardio
var result = client.evaluate({
    state = "这笔付款是正常消费，不需要退款，只想补开发票。";
    model = "inception/mercury-decide";
    questions = {
        refund = {type="noul";instructions="客户是否要求退回款项？"};
    };
    session_id = "中文工单演示";
});
```

完整请求表保留 `state`、显式指定的 `model` 和其他扩展字段；省略 `model` 时补充客户端默认值。`questions` 同样支持原生对象和数组简写，发送前统一为对象形式。

## 配置

```aardio
var client = web.rest.aiDecision({
    // key = "你的 OpenRouter API Key";
    url = "https://openrouter.ai/api/alpha/decisions";
    model = "inception/mercury-decide";
    extraHeaders = {
        ["HTTP-Referer"] = "https://example.com";
    };
});
```

-  省略 `key` 字段则改用环境变量 `AI_DECISION_API_KEY`
-  省略 `url` 字段则改用环境变量 `AI_DECISION_API_URL` 或者 `https://openrouter.ai/api/alpha/decisions`
-  省略 `model` 字段则改用环境变量 `AI_DECISION_API_MODEL`；两者都未指定时，OpenRouter 端点默认使用 `inception/mercury-decide`。自定义服务不会自动注入这个模型 ID，应显式指定该服务支持的模型。
-  单次调用的模型优先级：完整请求中显式指定的 `model` → 调用参数 `model` → 客户端配置。客户端配置的优先级：构造参数 → 环境变量 → OpenRouter 默认值。
-  `url` 是完整端点，不是基础地址。本库不会自动拼接 `/api/v1` 或 `/chat/completions`，也不会因模型名称变化自动切换协议。
- 支持：`extraHeaders`、`extraParameters`、`extraUrlParameters`、`proxy`、`proxyBypass`、`httpFlags` 等 `web.rest` 配置。

## 答案读取

客户端不把决策答案转换成文本，也不按厂商重新包装，返回值保持 OpenRouter 原始结构：

- `result.answers[id].type`
- Noul：`result.answers[id].noul`，回答“是”的概率，范围为 0 到 1；`0` 是合法结果，接近 `0.5` 表示不确定，不是“中等严重”。
- Choice：`choice` 是选中的选项键，可直接是中文；`probabilities` 是各选项概率，`confidence` 描述分布集中程度，不代表保证正确。
- Score：n 个等级从 **0 到 n-1** 编号；`score` 是概率加权的位置，可能为小数，不是统一的 0～1 概率，也不是 aardio 数组下标。 OpenRouter 上的决策模型遵循相同的评分结构。
- `legend` 将 `"0"`、`"1"` 等字符串键映射回等级说明；`probabilities` 同样按字符串键读取。不能用小数 `score` 直接索引数组。
- OpenAPI 将 `probabilities`、`confidence`、`legend` 标为可选字段，跨模型使用时应先检查它们是否存在，不能因为某次响应包含这些字段就假定永远存在。
- `result.model` 为实际服务该请求的模型版本；`result.provider` 是可选的服务商标识。
- `result.usage.input_tokens` 与 `result.usage.output_tokens` 为用量，`result.usage.cost` 为可选的美元费用。不同模型的分词器及用量统计可能不同，不按固定输出 token 数判断协议兼容性。

也可以使用：

```aardio
var answer = client.answer(result,"department");
var probability = client.noulValue(result,"urgent");
```

读取答案时，缺失问题或类型不符应进入错误处理/人工复核，不要当作“否”或安全放行。特别注意 aardio 中 `0` 是假值，判断数值是否缺失应使用 `===null`，不要使用 `if(!probability)`。

## 协议兼容与模型切换

- **在 OpenRouter 统一接口上**，Microsoft-Decision-1 与 Jev 使用相同的认证、端点及 `model / state / questions` 请求结构，三类问题仍是 `noul / choice / score`。切换这两个模型不需要修改问题协议，不能把 OpenRouter 的协议与厂商直连接口混为一谈。
- 本库不硬编码模型白名单、问题数量上限、上下文长度或业务阈值。可以通过完整请求传入服务端支持的 `provider`、`session_id`、`trace`、`user` 等字段；原生问题对象与响应中的扩展字段也会保留。透传字段不等于服务端一定支持，实际能力由目标模型和端点决定。
- `state` 可为字符串、对象或数组；只提供判断所需上下文。问题之间相互独立，同一请求中的问题不能读取彼此的答案；有依赖关系时应分成多次调用。
- 可通过 `GET https://openrouter.ai/api/v1/models?output_modalities=decisions` 查询当前可用模型，而不是依靠固定厂商清单。选择模型还要核对上下文、输入模态、费用和业务效果。
- 直接连接 Microsoft Foundry、TypeSafe 或其他厂商时，需另外核对端点、认证、字段及响应格式。当前实现不包含这些原生 API 的专用适配器，不承诺只改 `url` 就能通用。
- Microsoft-Decision-1 的中文 `choice / noul / score` 已做少量真实接口验证；这验证了调用链与返回结构，并不构成中文能力的全面评测。模型更新或切换后，应使用包含明确正反例、否定句、模糊与无关输入的业务数据重新评估阈值。
- Jev 的 `~typesafe/jev-latest` 仅供兼容旧配置；它是可能随版本变化的别名，且中文支持较差。生产使用应记录响应中的实际 `model`，若服务提供可调用的固定版本，则可固定版本并验证业务效果。
- 本库不自动重试或切换模型。失败返回 `null,err[,errCode]`；数组简写错误属于本地错误，不产生新的 HTTP 请求，`lastStatusCode` 可能仍保留上次请求状态。不要将它误认为本次本地错误的 HTTP 状态。

## 参考

- [Mercury Decide 模型页](https://openrouter.ai/inception/mercury-decide)
- [OpenRouter 决策模型排行榜）](https://openrouter.ai/rankings/decisions)
- [OpenRouter Decisions API 参考](https://openrouter.ai/docs/api/api-reference/alphadecisions/submit-a-decisions-request)
- [OpenRouter 通用决策模型使用说明](https://openrouter.ai/skills/openrouter-decisions)
