# jev-java-sdk **Repository Path**: langchat/jev-java-sdk ## Basic Information - **Project Name**: jev-java-sdk - **Description**: No description available - **Primary Language**: Java - **License**: Apache-2.0 - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2026-09-23 - **Last Updated**: 2026-09-23 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # LangChat Jev SDK 把 **Jev**(System-1 决策模型)接进 Java 应用的 SDK:给它一个状态和若干带类型的问题,它在**一次前向**里返回带校准概率的答案。 它不做文本生成,因此没有输出需要解析,也没有幻觉可言——适合替代「让大模型输出 JSON 再解析」这类脆弱的判断环节。 > 官方 Jev(TypeSafe AI)是闭源的托管 API。本 SDK 走的是开源权重路线:加载 [receptron/laya-onnx](https://huggingface.co/receptron/laya-onnx)(Laya 的 ONNX 导出,Apache-2.0), > 在本地用 ONNX Runtime 推理,接口按 Jev 的三原语设计。术语上,本仓库把这类「状态 + 带类型问题 → 概率答案」的模型统称为 Jev。 ```java try (JevClient client = JevClient.open(JevOptions.defaults())) { Map questions = new LinkedHashMap<>(); questions.put("department", JevQuestion.choice( "Which department should handle this request?", Map.of("billing", "invoices and payments", "technical", "bugs and outages"))); questions.put("urgent", JevQuestion.noul("Is this urgent?")); JevResult result = client.decide("We were charged twice, please refund.", questions); System.out.println(result.answer("department").choice()); // billing System.out.println(result.answer("department").probabilityOf("billing")); } ``` ## 模块 | 模块 | 说明 | |---|---| | [`jev-sdk`](jev-sdk/) | SDK 本体,只依赖 ONNX Runtime + DJL Tokenizers + Jackson | | [`jev-examples`](jev-examples/) | 六组示例:常见决策场景,以及实测的模型短板 | | [`jev-tetris`](jev-tetris/) | 俄罗斯方块:每一块的落点由模型在候选落点中选出,含可视化界面 | ## 快速开始 ### 环境 - JDK 17+ - Maven 3.8+ - 首次运行需要联网下载权重(约 1.7 GB),或提前下好放到本地目录 ### 直接跑示例 ```bash # 从官方仓库下载并缓存权重(约 1.7 GB,缓存在 ~/.cache/langchat-jev) mvn -q -pl jev-sdk -am install mvn -q -pl jev-examples exec:java -Dexec.mainClass=cn.langchat.jev.examples.TicketTriageExample # 直连 huggingface.co 不通时,用镜像站点 JEV_MIRROR=1 mvn -q -pl jev-examples exec:java -Dexec.mainClass=cn.langchat.jev.examples.TicketTriageExample # 已经下好了权重,直接指向目录(不发起任何网络请求) mvn -q -pl jev-examples exec:java -Dexec.mainClass=cn.langchat.jev.examples.TicketTriageExample \ -Dexec.args=/path/to/laya-onnx ``` ### 跑俄罗斯方块 ```bash mvn -q -pl jev-tetris -am install mvn -q -pl jev-tetris spring-boot:run # 打开 http://127.0.0.1:8080,点「开始新对局」会自动连走 ``` 或在仓库根目录: ```bash mvn -q -DskipTests install cd jev-tetris && mvn -q spring-boot:run ``` ## 两种权重加载方式 ### 1. 指定本地目录(推荐用于生产) ```java JevClient client = JevClient.open(JevOptions.ofModelDir(Path.of("/opt/models/laya-onnx"))); ``` 目录需包含: | 文件 | 大小 | 说明 | |---|---|---| | `laya.onnx` | 3.8 MB | 计算图 | | `laya.onnx.data` | 1.69 GB | fp32 权重 | | `laya_config.json` | 369 B | `max_len`、`head_max_len`、各档温度 | | `tokenizer/tokenizer.json` | 3.4 MB | ModernBERT 分词器 | | `tokenizer/tokenizer_config.json` | 308 B | 分词器配置 | 浏览器下载会把 json 存成 `.txt`(`laya_config.txt`、`tokenizer_tokenizer.txt`),SDK 启动时会自动按内容补齐规范文件,**不改动原文件、也不需要重新下载**。 ### 2. 从互联网下载 ```java JevOptions options = JevOptions.defaults() .repo("receptron/laya-onnx") // 仓库标识 .baseUrl(JevOptions.MIRROR_BASE_URL) // 直连不通时用镜像 .onDownload(progress -> System.out.println(progress)); JevClient client = JevClient.open(options); ``` 下载器的行为(以下均为实测): - **断点续传**:先写 `.part` 临时文件,中断后重新调用从中断处继续。 实测从 100 MB 残片续传,日志输出「从 100 MB 处续传」,只补齐剩余部分; - **跳过已完整文件**:长度与远端一致的直接复用。实测五个文件只缺一个时, 日志显示「需要下载 1 个文件」,重复启动几乎零开销(第二次打开 9.8 秒,无下载); - **进度回调**:1.7 GB 的文件建议接上它做进度展示; - **环境变量**:`HF_ENDPOINT` 可整体替换站点,`LAYA_CACHE` / `XDG_CACHE_HOME` 可改缓存位置。 镜像站点会用 302 跳转到对象存储节点。SDK 已确认 **Range 请求头在跨域重定向后仍然生效** (返回 206 且 `Content-Range` 正确),因此断点续传在镜像站上同样可用。 实测数据(镜像站,家用带宽):完整下载 1.7 GB 耗时约 219 秒,其中大文件 1607 MB 约 200 秒; 之后每次启动只需加载模型的数秒。 ## API 概览 ### 三原语 | 原语 | 构造方式 | 返回 | |---|---|---| | `choice` | `JevQuestion.choice(问题, Map<选项, 说明>)` 或 `choice(问题, List<选项>)` | 命中选项 + 每项概率 + 置信度 | | `score` | `JevQuestion.score(问题, List<档位>)` | 期望分值(档位下标口径)+ 分布 + 图例 | | `noul` | `JevQuestion.noul(问题[, 真描述, 假描述])` | `P(true)` | 三类问题可以在**同一次调用**里一起问,模型一次前向全部回答——这比逐条调用快得多。 ### 客户端 ```java JevClient.open(JevOptions) // 打开客户端(必要时下载 + 加载模型) client.decide(state, questions) // Map<问题标识, 问题>,一次前向 client.decide(state, List<问题>) // 自动编号为 q0、q1… client.decide(state, 单个问题) // 快捷方式,返回单条答案 client.warmup() // 预热,避免首个真实请求多等 client.prefixTokenCount(问题) // 问题+选项占的 token 数 client.stateTokenRoom(问题) // 状态可用的 token 余量 client.inspect(问题) // 渲染诊断:选项、[MASK] 下标、是否被截断 client.metadata() // maxLen / headMaxLen / 权重路径 client.close() // 释放会话 ``` 线程安全:单实例可被多线程共享(内部串行化前向调用)。高并发场景按并发量起多个实例, 每个实例独立占用约 2 GB 内存。 ## 示例 | 示例 | 场景 | 要点 | |---|---|---| | [`TicketTriageExample`](jev-examples/src/main/java/cn/langchat/jev/examples/TicketTriageExample.java) | 工单分诊 | 四个问题一次前向:部门、紧急度、流失风险、是否要求退款 | | [`ContentModerationExample`](jev-examples/src/main/java/cn/langchat/jev/examples/ContentModerationExample.java) | 内容安全 | 有害判定 + 分类 + 严重度;按风险偏好调阈值 | | [`PromptInjectionGuardExample`](jev-examples/src/main/java/cn/langchat/jev/examples/PromptInjectionGuardExample.java) | 提示注入防护 | 把「这段外部内容是否在操纵我」做成模型判断 | | [`LeadScoringExample`](jev-examples/src/main/java/cn/langchat/jev/examples/LeadScoringExample.java) | 线索结构化与打分 | 六个字段一次前向,替代「让大模型输出 JSON」 | | [`LoadingModesExample`](jev-examples/src/main/java/cn/langchat/jev/examples/LoadingModesExample.java) | 加载方式对照 | 在线下载 vs 本地目录 | | [`LimitationsExample`](jev-examples/src/main/java/cn/langchat/jev/examples/LimitationsExample.java) | **模型短板** | 用实测数字暴露三个已知问题 | | [`OptionOrderBiasExample`](jev-examples/src/main/java/cn/langchat/jev/examples/OptionOrderBiasExample.java) | **选项顺序偏差** | 解释为什么未微调模型玩俄罗斯方块会迅速堆到顶部 | ## 局限性与注意事项 这部分是实测结果,不是免责声明。跑 `LimitationsExample` 与 `OptionOrderBiasExample` 可以复现。 ### 1. 高基数选项会坏掉校准,且是「过度自信」 正确率随选项增多而下降,但更糟的是**概率反而更笃定**:11 个选项以上会命中权重包里的 `choice:11+` 温度档(0.10),把分布压成近乎 one-hot。 同一句话的实测(`LimitationsExample`): | 选项数 | 选中 | 对不对 | 置信度 | 正确答案 billing 的概率 | |---|---|---|---|---| | 2 | billing | 对 | 0.667 | 0.939 | | 4 | billing | 对 | 0.669 | 0.892 | | 8 | returns | **错** | 0.584 | 0.482 | | 16 | returns | **错** | **0.966** | 0.019 | 最后一行最需要注意:**又错又笃定**。因此不能靠置信度阈值兜底,也不能拿它做拒答判断。 ### 2. 置信度是相对量,不要跨问题比较 置信度 = 1 − 归一化熵。选项越多、选项之间越像,同样选对时该值越低。 俄罗斯方块里 6 个候选通常只有 0.1 上下;而 2 选 1 动辄 0.6 以上。 把固定阈值(例如 0.15)用在所有问题上,会把大量正确决策判为不可信—— 本仓库的方块示例初期就踩过这个坑,日志里几乎每步都在回退。 ### 3. 未微调的模型不是决策引擎,只是可微调的底座 它在作者训练过的任务形态(分类、路由、打分、是非判断)上开箱可用, 换成领域专属的判定标准时往往只是略高于随机。官方基准也显示,在未微调的 `typed-decisions` 任务上零样本只有 0.362,接近随机 0.318,低于多数类基线 0.461。 正确姿势:用几百到几千条自己标注的数据微调决策头,而不是直接上线。 ### 4. 选项顺序会影响答案 同一个盘面、同样的六个候选,只调换顺序,答案就跟着变(`OptionOrderBiasExample` 实测): 三次实验中模型都选中「第 4 位」,且从未选中指标最优的那个候选—— 尽管最优解的「消 1 行、0 空洞、最矮最平」明确写在选项文本里。 ### 5. 需要空间推理的任务不要交给它 俄罗斯方块是典型例子。用同一种子各走 41 块: | 决策方式 | 消行 | 堆高 | 与启发式一致率 | |---|---|---|---| | 四特征启发式 | 15 行 | 2 行 | — | | Jev(未微调) | 10 行 | **11 行** | 0.56 | 也就是说它有 44% 的步骤做了不同的选择,而结果明显更差——这正是界面里方块很快堆到顶部的原因。 **不是代码写错了,是任务选错了**:让一个文本模型读字符网格来判断「哪个落点不容易堆高」, 本来就超出了它的能力范围。详见 [`jev-tetris/README.md`](jev-tetris/README.md)。 ### 6. 单字段会判错,需要按字段设校验 实测示例里两处值得注意(都可复现): - **时间表述识别不可靠**:一封明确写了「before Q4」的需求邮件,「是否有时间表」只给出 0.18(判错), 而同一次调用里的行业、规模、预算、报价意图都判对了(`LeadScoringExample`)。 - **显式辱骂判得偏弱**:一句明确的人身攻击,`harmful` 只有 0.52,勉强过线; 反倒是手机号与地址的识别很准(0.95,`ContentModerationExample`)。 也就是说它是「整体可用、个别字段不可靠」。关键字段仍应加规则校验或人工复核, 不要让单一概率决定最终动作。 ### 7. 其他工程约束 - **状态超过 `max_len`(512)会被静默截断**,长文档要先抽取关键片段; - **耗时随输入长度增长**,CPU 上是秒级(本项目实测 0.8–8 秒),不要整篇塞进去; - **官方 T4 上 33 ms 是 GPU 数字**,与 CPU 差一个数量级,别直接拿去估算线上容量; - **模型不生成文本**,需要自然语言解释时得自己把概率翻译成业务话术。 ## 权重与许可 - SDK 代码:Apache-2.0,署名 LangChat Team(https://langchat.cn) - 模型权重:[convaiinnovations/laya](https://huggingface.co/convaiinnovations/laya) → ONNX 导出 [receptron/laya-onnx](https://huggingface.co/receptron/laya-onnx),Apache-2.0 - 序列渲染规则对齐参考实现 [`@receptron/laya`](https://github.com/receptron/laya) 的 `sequence.ts` / `laya.ts`(其上游是权重包里的 `rl_common.py`) 本 SDK 与 TypeSafe AI 无关,也不使用其闭源服务;「Jev」在此指代同一类 状态+问题→概率 的决策模型接口形态。