Jev 案例:贷款申请风险分级(完整代码)

更新于 适用版本 Jev 1.13 示例数据(example fixture)

TL;DR: 本案例演示一个小型信贷平台的风控分档流水线:第一次是选择题,把每份申请分进 low、medium、high 三档风险;第二次是判断题,决定这份申请是否需要人工复核。路由靠置信度阈值驱动——0.8 以上自动出档,中间区间进人工复核,0.5 以下全部走人工。完整请求体、示例数据(example fixture)格式的响应,以及可直接运行的 Python 脚本都在下文。

场景

我们合作的一家小型消费信贷平台,每月处理大约 9000 份贷款申请。他们现有的规则引擎只认硬性 cutoff:FICO 低于 620 直接拒绝,FICO 高于 720 且债务收入比低于 36% 自动通过,剩下的全堆到人工复核桌上。这套规则对数字很好使——但数字没说的部分,它一个都接不住。

差距是在复盘报告里暴露的。就业描述自相矛盾的申请(“在某公司干了五年”,而那家公司 2023 年才成立)、申报收入和申报行业明显对不上的申请、贷款用途在不同字段之间前后不一致的申请——只要征信分数恰好过线,这些申请全都顺利通过了。规则引擎没有能力权衡这类软性信号,而人工复核也不可能在 9000 份申请的量上铺开。

所以我们加了每个申请两次 Jev 调用,卡在规则引擎和复核队列之间:

  1. 选择题——把申请分进且只分进一个风险档:low、medium、high;
  2. 判断题——不管分在哪一档,这份申请是否需要人工复核?回答 yes、no 或 unclear。

Jev 是 TypeSafe AI 在 2026 年 9 月发布的 System One 判断模型。它适合这个位置,是因为输出是带置信度的类型化 JSON,而不是自由文本——路由门禁要的正是这种东西。下文的示例申请是刻意挑的混合样本:FICO 678 分,按老规则会触发中层档复核;另外还埋了两处规则引擎根本看不见的软性矛盾。

请求

第一次调用——风险分档(选择题):

{
  "model": "typesafe/jev-1.13",
  "messages": [
    {
      "role": "system",
      "content": "Choice question. Assign this loan application to exactly one risk tier: low, medium, or high. Consider stated income vs. industry norms, internal consistency of employment history, and coherence of the stated loan purpose. Reply with JSON only: {\"answer\": <low|medium|high>, \"confidence\": <0-1>, \"rationale\": <one sentence>}"
    },
    {
      "role": "user",
      "content": "Loan application #A-48213: FICO 678. Stated monthly income: $9,200, occupation: warehouse associate. Employment: \"shift lead at a regional logistics company for 5 years.\" Requested amount: $12,000 over 36 months. Purpose field 1: \"consolidate credit cards.\" Purpose field 2 (free text): \"help pay for my nursing school tuition starting this fall.\" DTI: 31%."
    }
  ]
}

第二次调用——人工复核检查(判断题):

{
  "model": "typesafe/jev-1.13",
  "messages": [
    {
      "role": "system",
      "content": "Judgment question. Answer yes, no, or unclear. Reply with JSON only: {\"answer\": <yes|no|unclear>, \"confidence\": <0-1>, \"rationale\": <one sentence>}"
    },
    {
      "role": "user",
      "content": "Loan application #A-48213: FICO 678, DTI 31%. Claims $9,200/month as a warehouse associate and 5 years at a logistics company. Loan purpose is stated as credit card consolidation in one field and nursing school tuition in another. Does this application need manual review?"
    }
  ]
}

响应

以下两个响应均为示例数据(example fixture),用于说明格式,不是线上实测抓包。

第一次调用:

{
  "answer": "medium",
  "confidence": 0.71,
  "rationale": "Credit metrics are acceptable, but the stated income is high for the occupation, and the loan purpose is inconsistent between fields."
}

第二次调用:

{
  "answer": "yes",
  "confidence": 0.88,
  "rationale": "The mismatched loan purposes and income-to-occupation gap are exactly the soft signals the rules engine cannot evaluate."
}

复现脚本

保存为 risk_tier.py,填入你自己的 BASE_URL 和 API key 后运行 python3 risk_tier.py。脚本使用 OpenAI 兼容 SDK,任何 OpenAI 兼容端点都能跑:

import json, os
from openai import OpenAI

BASE_URL = "https://YOUR-ENDPOINT.example.com/v1"  # replace with your provider
client = OpenAI(base_url=BASE_URL, api_key=os.environ["JEV_API_KEY"])
MODEL = "typesafe/jev-1.13"  # confirm exact slug with your provider

def ask(system, user):
    res = client.chat.completions.create(
        model=MODEL,
        messages=[{"role": "system", "content": system},
                  {"role": "user", "content": user}])
    return json.loads(res.choices[0].message.content)

CHOICE = ('Choice question. Assign this loan application to exactly one risk '
          'tier: low, medium, or high. Consider stated income vs. industry '
          'norms, internal consistency of employment history, and coherence '
          'of the stated loan purpose. Reply with JSON only: '
          '{"answer": <low|medium|high>, "confidence": <0-1>, '
          '"rationale": <one sentence>}')

JUDGE = ('Judgment question. Answer yes, no, or unclear. Reply with JSON '
         'only: {"answer": <yes|no|unclear>, "confidence": <0-1>, '
         '"rationale": <one sentence>}')

app = ("Loan application #A-48213: FICO 678. Stated monthly income: $9,200, "
       "occupation: warehouse associate. Employment: 'shift lead at a "
       "regional logistics company for 5 years.' Requested amount: $12,000 "
       "over 36 months. Purpose field 1: 'consolidate credit cards.' "
       "Purpose field 2 (free text): 'help pay for my nursing school "
       "tuition starting this fall.' DTI: 31%.")

tier = ask(CHOICE, app)
review = ask(JUDGE, "Loan application:\n" + app + "\n\nDoes this application need manual review?")

def route(tier, review):
    conf = tier["confidence"]
    if conf >= 0.8:
        return "auto:" + tier["answer"]
    if conf >= 0.5:
        return "human-review"
    return "human-review-priority"

print(tier)
print(review)
print(route(tier, review))

置信度路由

光有档位还不是最终决策——置信度才是。我们的路由规则:

tier confidence >= 0.8  ----> 自动套用档位
tier confidence 0.5-0.8 --> 人工复核队列
tier confidence < 0.5   ----> 优先人工复核
review answer = yes     ----> 强制人工复核(覆盖档位结果)
review answer = unclear --> 按 yes 处理

大部分申请实际落在中间区间,这没关系——门禁的目的不是把一切都自动化,而是确保只有”看得清”的案子才有资格跳过人工。

关键参数

字段取值说明
modeltypesafe/jev-1.13固定版本号,避免新版本发布后悄悄改变风控结论。准确 slug 请以服务方为准。
messages[0](system)题型 + 选项列表 + JSON 结构 + 要权衡的软性信号明确列出软性信号(收入 vs. 行业、一致性、用途连贯性),能让打分在不同申请之间保持稳定。
messages[1](user)申请字段原文传原始字段值,不要传摘要——矛盾点就藏在细节里。
answer(响应)medium / yes(示例数据)档位映射到路由表;复核标记可以覆盖档位。
confidence(响应)0.71 / 0.88(示例数据)路由的主要输入:≥0.8 自动、0.5–0.8 复核、<0.5 优先复核。
rationale(响应)一句话直接进复核员的案件备注。

注意事项

继续阅读