"""Jev lesson: offline fixtures by default; --live calls the official cloud API. Python 3.10+, standard library only. Contract checked 2026-09-21: https://docs.typesafe.ai/introduction/quickstart """ import argparse import copy import json import math import os import sys import urllib.error import urllib.request LABELS = {"concept": "解释概念", "debug": "排查代码", "clarify": "先问清楚"} CASES = { "code": ("Python 程序报了 NameError,我不知道哪里写错了。", {"type": "choice", "choice": "debug", "confidence": .95, "probabilities": {"concept": .02, "debug": .97, "clarify": .01}}), "concept": ("向量到底是什么?为什么文字要变成数字?", {"type": "choice", "choice": "concept", "confidence": .90, "probabilities": {"concept": .94, "debug": .02, "clarify": .04}}), "vague": ("这个不行,我还是没弄懂。", {"type": "choice", "choice": "clarify", "confidence": .25, "probabilities": {"concept": .32, "debug": .28, "clarify": .40}}), } def unit_number(value): return type(value) in (int, float) and math.isfinite(value) and 0 <= value <= 1 def validate(answer): if not isinstance(answer, dict) or answer.get("type") != "choice": raise ValueError("接口没有返回 Choice,停止分流。") if answer.get("choice") not in LABELS or not unit_number(answer.get("confidence")): raise ValueError("未知选项或无效置信度,停止分流。") probabilities = answer.get("probabilities") if (not isinstance(probabilities, dict) or set(probabilities) != set(LABELS) or not all(unit_number(v) for v in probabilities.values()) or abs(sum(probabilities.values()) - 1) > .001): raise ValueError("概率分布不符合三个选项的约定,停止分流。") if probabilities[answer["choice"]] < max(probabilities.values()): raise ValueError("选择与概率分布不一致,停止分流。") return answer def route(answer, threshold): validate(answer) if not unit_number(threshold): raise ValueError("门槛必须在 0~1 之间。") if answer["choice"] == "clarify" or answer["confidence"] < threshold: return "先询问:你卡在哪一步?" return "打开「" + LABELS[answer["choice"]] + "」入口" def request_body(text): return { "model": "jev-latest", "state": {"student_request": text}, "questions": {"help_route": { "type": "choice", "instructions": "判断这条学习求助下一步应去哪个入口;只做分流。", "criteria": { "concept": "需要解释一个明确概念,不是在排查程序报错。", "debug": "需要排查代码或程序报错。", "clarify": "信息不足、同时跨多个类别或不属于以上类别,需要先澄清。", }, }}, } def live_answer(text, key): request = urllib.request.Request( "https://api.typesafe.ai/v1/systemone", data=json.dumps(request_body(text), ensure_ascii=False).encode("utf-8"), headers={"Authorization": "Bearer " + key, "Content-Type": "application/json"}, method="POST", ) with urllib.request.urlopen(request, timeout=30) as response: result = json.load(response) answer = result.get("answers", {}).get("help_route") if isinstance(result, dict) else None return validate(answer) def main(argv=None): parser = argparse.ArgumentParser(description="Jev 教学:默认离线,--live 才调用云端 API。") parser.add_argument("--case", choices=CASES, default="code") parser.add_argument("--threshold", type=float, default=.80) parser.add_argument("--live", action="store_true", help="向 TypeSafe 发送内置虚构情境,可能产生 API 费用") args = parser.parse_args(argv) if not unit_number(args.threshold): parser.error("--threshold 必须在 0~1 之间。") text, preset = CASES[args.case] if args.live: key = os.environ.get("TYPESAFE_API_KEY", "").strip() if not key: parser.error("请先在本机配置 TYPESAFE_API_KEY;未发出请求。") print("真实 API 模式:发送内置虚构求助到 TypeSafe 云端,可能计费。") try: answer = live_answer(text, key) except urllib.error.HTTPError as error: print(f"接口返回 HTTP {error.code},停止;没有改用模拟结果。", file=sys.stderr) return 1 except (urllib.error.URLError, TimeoutError, ValueError, TypeError, AttributeError): print("网络、响应格式或字段校验失败,停止;没有改用模拟结果。", file=sys.stderr) return 1 else: print("离线模拟:以下回包为课程预设,不是 Jev 实测。没有发出网络请求。") answer = copy.deepcopy(preset) print("求助:" + text) print("回包:" + json.dumps(answer, ensure_ascii=False)) print(f"采用门槛:{args.threshold:.2f}") print("程序下一步:" + route(answer, args.threshold)) print("这里只展示路由,不执行工具、不发送消息、不记录掌握状态。") return 0 if __name__ == "__main__": raise SystemExit(main())