神龙十问 LKeAP JSON 生成质量调研

调研日期:2026-04-11 调研背景:神龙十问切到 Tencent LKeAP (/plan/v3/chat/completions) 后报 Upstream error 400 涉及代码:service/ai_chat_service.py:analyze_vuln_questions() (line 462+)、configs/settings.py:117-133 状态:调研完成 + 修复已应用


TL;DR

维度结论
400 根因默认模型 deepseek-v3 不在 /plan/v3 endpoint 的可用模型列表里,每次调用都是 invalid model (code 20033)
错误日志 bugservice/ai_chat_service.py:630 只 yield 了 status code,没透出 response body,导致前端只能看到孤零零的 Upstream error 400
选定模型hunyuan-2.0-instruct —— 速度快(~12s vs turbos 的 ~30s)、内容质量到位、失败模式良性
真实数据通过率40 次跨 20 个 CVE 的生产数据测试:strict 90% / lenient 92.5%(lenient 即代码加 strict=False 后的实际通过率)
不要做的事不要换 prompt 去"强化 JSON 规则"——实测对多数模型反而是负面优化
真正的修复代码侧用 json.loads(s, strict=False) 容错 + 解析后规范化再入库;模型/prompt 调到合理值即可

1. 故障定位

1.1 用户报告

神龙十问也不行:{"error": "Upstream error 400"}

1.2 第一个发现:错误信息丢失

service/ai_chat_service.py:628-631:

python
with requests.post(lkeap_url, headers=headers, json=payload, stream=True, timeout=120) as response:
    if response.status_code != 200:
        yield f"data: {json.dumps({'error': f'Upstream error {response.status_code}'})}

"
        return

错误处理只读了 status_code,完全没读 response.text 或 response.content。导致前端永远看不到上游的具体错误。这是后续所有定位困难的元凶。

1.3 直接 curl 上游

bash
curl -sS -X POST 'https://api.lkeap.cloud.tencent.com/plan/v3/chat/completions' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer sk-tp-...' \
  -d '{"model":"deepseek-v3","messages":[{"role":"user","content":"hi"}],"stream":false,"max_tokens":50}'

返回:

json
{"id":"0ba4c8b63fa1c12c87ea3ebcf8aea379",
 "error":{"message":"invalid model","type":"runtime_error","param":null,"code":"20033"}}

HTTP 400。模型名错了。

1.4 发现可用模型列表

bash
curl -sS 'https://api.lkeap.cloud.tencent.com/plan/v3/models' \
  -H 'Authorization: Bearer sk-tp-...'

返回的可用模型:

minimax-m-2-5 / minimax-m2.5
glm-5-0       / glm-5
kimi-k-2-5    / kimi-k2.5
tc-code-latest
hunyuan-turbos
hunyuan-t1
hunyuan-2.0-instruct
hunyuan-2.0-thinking

注意:列表里完全没有 deepseek-* 系列。/plan/v3 是 LKeAP 的"plan"版本(相对于标准 /v1),托管的是另一组模型——主要是腾讯混元 + 三方品牌(minimax / kimi / glm)。

同步实验:

  • https://api.lkeap.cloud.tencent.com/v1/chat/completions 用同一个 key 返回 401 not_authorized,说明这个 key 是 plan 套餐专用 key,不能用标准 endpoint。
  • https://api.lkeap.cloud.tencent.com/v1/models 同样 401。

也就是说环境完全锁死在 /plan/v3 + 上述 8 个模型,不能跨 endpoint 切回 deepseek。


2. 模型质量初测

2.1 测试方法

完全复刻生产 prompt(service/ai_chat_service.py:567-594 中文分支),输入是真实结构的 CVE JSON(query_cve_details 输出形态),要求输出 Q1-Q10 的 JSON 对象。

判定标准:

  • strict:json.loads(content) 直接通过
  • lenient:json.loads(content, strict=False) 通过(允许字符串字面里出现 LF/CR/Tab 等控制字符)
  • keys:是否齐全 Q1-Q10 共 10 个键
  • strings:所有 value 是否都是 string

2.2 hunyuan-turbos 首测

第一次测 hunyuan-turbos 在 Log4j 上,HTTP 200 / 30 秒,但解析失败:

JSON parse FAILED: Invalid control character at: line 3 column 35 (char 63)

更糟的是结构本身就崩了:

json
{
  "Q1": "这个漏洞是什么?(本质+后果)",
  "**Apache Log4j2 远程代码执行漏洞!** 💥
  **本质**:JNDI 功能未过滤攻击者可控输入。
  **后果**:攻击者可远程执行任意代码!🚨",

  "Q2": "根本原因?(CWE/缺陷点)",
  "**JNDI 注入漏洞!** ⚠️
  ..."
}

模型把"问题文本"当成 value,把答案塞成无 key 的悬空字符串——双层错误。结合 30s 的高延迟,turbos 直接淘汰。

2.3 hunyuan-2.0-instruct 首测

CVE时间结果
CVE-2021-4422814s✅ strict OK
CVE-2023-2339710s✅ strict OK
CVE-2024-309411s❌ JSON FAIL Invalid control character at: line 2 column 44

XZ 这个 case 内容质量很好,但每个 string value 都用了字面 LF 换行:

"Q1": "- **本质**:恶意代码藏进 `xz` 源码(5.6.0起)📦
- 构建时提取伪装的测试文件 → 生成对象文件 🧩
- **后果**:篡改 `liblzma` → 所有链接它的软件被劫持 🚨",

JSON 标准里 string 内不允许字面 LF,必须 。此时 json.loads(s) 抛 Invalid control character。

关键观察:内容是对的、键是对的、结构是对的——只是 LF 没转义。


3. 6 模型 × 2 prompt 横评

3.1 测试矩阵

模型:minimax-m2.5、kimi-k2.5、glm-5、hunyuan-2.0-instruct、hunyuan-2.0-thinking、hunyuan-t1

Prompt 变体:

  • Baseline:当前生产 prompt(analyze_vuln_questions line 567-594 原样)
  • Strict:显式给 schema、显式禁止字面换行、显式给 JSON 规则编号

参数:temperature=0.3、max_tokens=2500、stream=False、response_format={"type":"json_object"}

测试 CVE:CVE-2024-3094(前一轮 instruct 唯一翻车的那个)

3.2 Pass 1: Baseline prompt

模型时间tokens_outstrictlenient备注
minimax-m2.526.2s1214✅✅avg 110 字符,内容最长
kimi-k2.590.8s2500❌❌撞 max_tokens,输出不是 JSON
glm-548.3s2500❌❌撞 max_tokens,输出截断
hunyuan-2.0-instruct13.6s748✅✅avg 91 字符
hunyuan-2.0-thinking22.9s1945✅✅avg 92 字符
hunyuan-t133.1s2091✅✅avg 53 字符(偏短)

要点:

  • kimi-k2.5 / glm-5 不遵守 response_format=json_object——它们继续输出富文本(带推理过程或自由发挥),直接撞 max_tokens 上限。这两个对当前用法不可用。
  • 其余 4 个 baseline 都过了。

3.3 Pass 2: Strict prompt

Strict prompt 增加的约束:

# JSON RULES — VIOLATIONS WILL BREAK THE PARSER
1. Output ONLY the JSON object. No prose before/after. No ```json fence.
2. Each value is a SINGLE-LINE JSON string. Multi-line content MUST use the
   literal two-character sequence backslash + n (
) for line breaks.
3. NEVER place a raw newline (LF / CR) inside a string value.
4. Escape any double-quote inside a value as \".
5. Keys are exactly Q1, Q2, ..., Q10.
6. All 10 keys are required.
模型时间strictlenient备注
minimax-m2.520.2s❌❌被搞崩了,Expecting ',' delimiter
kimi-k2.591.0s❌❌仍撞 max_tokens
glm-546.6s❌❌仍撞 max_tokens
hunyuan-2.0-instruct7.5s✅✅avg 跌到 42 字符——内容被压缩太短
hunyuan-2.0-thinking18.8s✅✅avg 68(也变短)
hunyuan-t134.1s❌❌被搞崩了

3.4 横评结论

严化 prompt 是负面优化。

  • 让原本能跑通的 minimax 和 t1 直接 break
  • 让 instruct 变得过度简洁(42 字符 avg),失去神龙十问该有的内容密度
  • 对真正问题(字面换行符)零修复——instruct 之所以"看起来"通过了 strict prompt 测试,实际是因为内容被压短到根本没换行

模型选型基本锁定在 hunyuan-2.0-instruct、hunyuan-2.0-thinking,prompt 维持现状。


4. 合成数据稳定性测试

4.1 设计

模型 × 温度组合:

  • hunyuan-2.0-instruct @ T=0.3
  • hunyuan-2.0-instruct @ T=0.5(生产当前值)
  • hunyuan-2.0-thinking @ T=0.3

每个组合 × 3 个 CVE × 3 次 run = 27 次调用。

4.2 汇总

配置strict 通过平均延迟平均字符失败模式
instruct @ T=0.37/9 (78%)11.8s94全是字面换行(lenient 100% 通过)
instruct @ T=0.56/9 (67%)10.6s90全是字面换行(lenient 100% 通过)
thinking @ T=0.37/9 (78%)20.4s772 次结构性硬崩(lenient 也过不了)

4.3 决定性观察

  1. 没有任何模型能 100% 严格 JSON。即使 instruct + T=0.3,9 次里仍有 2 次字面换行。这是 LLM 的本质特性,不是 prompt 工程能根治的。
  2. instruct 的失败 100% 是良性的——所有 strict 失败的 case 全部能 lenient parse。json.loads(s, strict=False) 一行代码全部救回。
  3. thinking 的失败是恶性的——会出结构损坏(kv 错位、引号丢失等),lenient 都救不回。同样 78% 通过率,但失败是真失败。
  4. 温度 0.3 vs 0.5:通过率 78% vs 67%,11 个百分点的差距,足够支撑降温度。
  5. 延迟:instruct 11.8s ≈ thinking 20.4s 的 60%。

5. 真实数据大盘测试(决定性)

合成 CVE 描述毕竟简化,需要用 query_cve_details() 拉真实库数据(包括 POC + references + 完整 CVSS vector)做大盘测试。

5.1 测试集

20 个高影响力 CVE,覆盖 12 种不同漏洞类型:

#CVE通用名类型真实数据长度
1CVE-2021-44228Log4ShellRCE/JNDI9872 chars
2CVE-2014-0160Heartbleedmemory disclosure10365 chars
3CVE-2024-3094XZ backdoorsupply chain10117 chars
4CVE-2017-5638Struts2 OGNLRCE7974 chars
5CVE-2019-0708BlueKeep RDPRCE wormable3760 chars
6CVE-2021-26855ProxyLogonSSRF/auth bypass8253 chars
7CVE-2022-22965Spring4ShellRCE10919 chars
8CVE-2018-7600Drupalgeddon2RCE9544 chars
9CVE-2020-1472Zerologoncrypto/privesc8853 chars
10CVE-2023-44487HTTP/2 Rapid ResetDoS9486 chars
11CVE-2022-30190Follina MSDTRCE6274 chars
12CVE-2014-6271Shellshockenv injection7327 chars
13CVE-2017-0144EternalBlueSMB RCE9656 chars
14CVE-2023-22515Confluenceauth bypass9487 chars
15CVE-2018-1000861Jenkins Staplerauth bypass3294 chars
16CVE-2023-46805Ivanti Connectauth bypass10184 chars
17CVE-2024-21887Ivanti Connectcmd injection5795 chars
18CVE-2024-21413Outlook MonikerNTLM leak11864 chars
19CVE-2023-34362MOVEit TransferSQL injection7765 chars
20CVE-2022-26134Confluence OGNLRCE9703 chars

每个 CVE 跑 2 次 = 40 次调用(hunyuan-2.0-instruct + T=0.3 + json_object)。

5.2 总指标

Total calls: 40, errors: 0, valid: 40

JSON quality:
  strict pass:  36/40 (90.0%)
  lenient pass: 37/40 (92.5%)        ← 即代码加 strict=False 后的实际通过率
  all Q1-Q10:   37/40 (92.5%)
  all strings:  37/40 (92.5%)

Content metrics (37 well-formed responses):
  avg_chars:   104  [min run 73, max run 140]
  shortest answer field across all runs: 45 chars
  longest  answer field across all runs: 332 chars
  empty/near-empty answers (<10 chars): 0 / 370 fields
  latency:     12.0s [min 9.3, max 15.9]
  tokens_out:  699 avg

Style compliance:
  mentions CVE ID:   22/37 (59%)
  has emojis:        37/37 (100%)
  has bold markdown: 37/37 (100%)
  has bullet points: 37/37 (100%)

5.3 失败分布

40 次结果分类:

  • 36 次 strict 通过 —— 直接合法 JSON
  • 1 次 strict 失败但 lenient 通过 —— 仅字面换行符问题,strict=False 救回
  • 3 次 hard fail —— 结构性损坏,lenient 也救不回

3 次 hard fail 涉及的 CVE:

  • CVE-2014-6271 Shellshock(run 2)
  • CVE-2018-1000861 Jenkins Stapler(run 1)
  • CVE-2024-21413 Outlook Moniker(run 1)

每个 CVE 第二次 run 都正常通过——说明 hard fail 是概率性偶发,不是 CVE 数据本身的问题。简单的"失败重试一次"策略可以把通过率推到接近 100%(当前未实现,留给后续优化)。

5.4 内容质量

风格合规率 100%:37 个有效响应全部满足风格指南——

  • 100% 含 emoji
  • 100% 用 **bold** markdown
  • 100% 用 - bullet points
  • 0 个空/近空答案(370 个字段全部有实质内容)

字符密度健康:

  • 平均每答 104 字符
  • 单答最短 45 字符(仍有完整意思),最长 332 字符(仍受 max_tokens 控制)
  • 单次 run 平均 73-140 字符——稳定区间

CVE ID 显式提及率 59%:22/37 次响应在答案文本里提到了 CVE ID。剩余 41% 不提及不算问题——前端展示已经有大标题挂着 CVE ID,重复提没必要。

延迟均匀:9.3s - 15.9s,p50 ~12s。比之前用 deepseek 也没变慢。

5.5 抽样输出(首 3 个 CVE)

CVE-2021-44228 Log4Shell

Q1: **漏洞本质**:Apache Log4j 日志工具处理数据时触发📝。
- **后果**:攻击者可构造恶意请求 → 被记录 → 触发**远程代码执行(RCE)** 🚨。
- 服务器可被完全控制!

Q5: **利用门槛**:
- **极低** 🟢。
- 无需认证🔑。
- 只需让恶意字符串被日志记录📝。
- 常见入口:HTTP头、参数、聊天消息等。

Q6: **已有Exp**:
- ✅ 多个 PoC 公开 🧪。
- GitHub 示例:
  - `tangxiaofeng7/CVE-2021-44228-Apache-Log4j-Rce` 💻。
  - `jas502n/Log4j2-CVE-2021-44228` 💻。
- 已在野利用🚨。

Q9: **无补丁临时方案**:
- 设置系统属性:`log4j2.formatMsgNoLookups=True` 🛡️。
- 移除 `JndiLookup` 类 ❌。
- 用 RASP 工具拦截(如 `CloudArmor`)🛡️。
- 网络层阻断 LDAP/RMI 出站 🚧。

CVE-2014-0160 Heartbleed

Q1: 🚨 **漏洞本质**:OpenSSL处理TLS/DTLS心跳包时,**缺少边界检查**。
💥 **后果**:远程攻击者读取服务器内存敏感数据。
📂 可泄露:用户名、密码、Cookie、**私钥**等。

Q2: 🔍 **根本原因**:
- 位于`d1_both.c`和`t1_lib.c`。
- **未校验心跳包长度字段** ➜ 内存越界读取。
- 类似**CWE-125**(越界读取)。

Q6: 🧪 **现成PoC**:
- 🔗 [FiloSottile/Heartbleed](https://github.com/FiloSottile/Heartbleed) 🛠️检测工具。
- 🔗 [titanous/heartbleeder](https://github.com/titanous/heartbleeder) 等。
- 多PoC可用 ➜ **野外利用已出现**。

CVE-2024-3094 XZ Backdoor

Q1: **漏洞本质**:xz 5.6.0/5.6.1 被植入恶意代码 🚨
**后果**:攻击者可远程未授权访问系统 💻⚠️

Q2: **根本原因**:构建流程被篡改 🧩
- 恶意对象文件伪装成测试文件混入源码 📦
- 编译时修改 liblzma 函数逻辑 🛠️
- 类似 **供应链投毒**(CWE-506 内嵌后门)

Q6: **已有 PoC**:是 🔍
- GitHub 多个检测/利用脚本 🧪
- 示例:
  - [CVE-2024-3094-checker](https://github.com/FabioBaroni/CVE-2024-3094-checker) 🔧
  - [xzbot](https://github.com/amlweems/xzbot) 🤖
- 存在**在野利用迹象** 🚨

Q10: **优先级**:🔥极高!
- CVSS 3.1:**9.8** 🚨
- 影响广+易利用+可远程控制 🌐
- **立刻排查&降级** 🏃💨

结论:技术准确性、风格、可读性均达到生产可用标准。

5.6 是否够用?

够用。

  • 92.5% 的实际通过率(lenient 模式下)
  • 失败完全偶发、可通过重试覆盖
  • 内容质量稳定,没有"通过但内容差"的情况
  • 失败案例的下一次 run 都恢复正常

如果未来想推到 99%+,加 1 次失败重试即可——属于增量优化。


6. 最终方案 / 已应用的修改

6.1 改了 2 个文件 / 6 处

1. configs/settings.py:128

python
# Before
SHENLONG_ANALYSIS_MODEL = os.environ.get('SHENLONG_ANALYSIS_MODEL', 'deepseek-v3')

# After
SHENLONG_ANALYSIS_MODEL = os.environ.get('SHENLONG_ANALYSIS_MODEL', 'hunyuan-2.0-instruct')

2. service/ai_chat_service.py payload (~line 616)

python
payload = {
    "model": lkeap_model,
    "messages": messages,
    "max_tokens": 2000,
    "temperature": 0.3,                              # 0.5 → 0.3
    "stream": True,
    "response_format": {"type": "json_object"},      # 新增
}

3. service/ai_chat_service.py 上游错误处理 (~line 629)

python
# Before
if response.status_code != 200:
    yield f"data: {json.dumps({'error': f'Upstream error {response.status_code}'})}

"
    return

# After
if response.status_code != 200:
    body = response.text[:500] if response.text else ''
    error_msg = f"Upstream {response.status_code}: {body}"
    print(f"[Shenlong] {error_msg}")
    yield f"data: {json.dumps({'error': error_msg})}

"
    return

4. service/ai_chat_service.py JSON 解析 + 入库前规范化 (~line 660)

python
# Before
try:
    json.loads(clean_json)  # validate only
    if language == 'en':
        analysis.questions_en = clean_json
    else:
        analysis.questions = clean_json

# After
try:
    parsed = json.loads(clean_json, strict=False)         # 容错
    canonical_json = json.dumps(parsed, ensure_ascii=False)    # 规范化
    if language == 'en':
        analysis.questions_en = canonical_json
    else:
        analysis.questions = canonical_json

同时把 yield 给前端的 complete 信号也改用 canonical_json,确保 frontend 最终态 = DB 持久态。

5. analyze_vuln_questions:482-489 缓存读取路径同步加 strict=False:

python
json.loads(analysis.questions_en, strict=False)
json.loads(analysis.questions, strict=False)

兜底防御——历史 record 可能也存了带字面换行的 raw 文本。

6.2 不做的事

不做原因
❌ 强化 prompt 加 JSON 规则横评里实测让 minimax / t1 直接崩、让 instruct 输出过短
❌ 切到 thinking同样 78% 通过率,但失败是结构性硬崩,lenient 救不回;延迟还多一倍
❌ 切到 minimax-m2.5baseline 能过但 26s 太慢,是 instruct 的 2 倍
❌ 用 kimi-k2.5 / glm-5不遵守 response_format=json_object,撞 max_tokens
❌ 调回 deepseekAPI key 锁死在 /plan/v3 plan 套餐,没有 deepseek
❌ 自己写正则去 escape 换行Python 内置 strict=False 直接覆盖此场景,不要造轮子
❌ 加失败重试逻辑增量优化,本次范围之外;92.5% 已经够用

7. 经验沉淀

7.1 关于 LLM JSON 输出

  • **永远不要假设 LLM 输出 100% 严格 JSON。**生产侧必须有容错路径。
  • json.loads(s, strict=False) 是 Python 的免费午餐——专门覆盖"字符串里有字面控制字符"这一类最常见的 LLM 失败模式。
  • 解析后规范化再持久化(json.loads → json.dumps),不要把 raw 文本写库,否则缓存读取路径会重复踩同一个坑。
  • 温度有用但有限:T=0.3 比 T=0.5 通过率高 ~10pp,但不是决定性的。容错代码才是决定性的。
  • 大盘测试不能省:合成数据的 27 次测试给出 78% 通过率;真实数据 40 次给出 92.5%。差距来自 prompt 里 cve_data 的真实长度和复杂度——合成数据偏简化,模型反而更容易在简单文本上偏离格式。

7.2 关于错误日志

  • status_code 不是错误信息。任何 HTTP 错误处理路径都必须 capture body(至少前 500 字节)。这次问题如果第一时间能看到 invalid model code 20033,定位时间能从一小时压缩到一分钟。

7.3 关于 prompt 工程

  • 加规则不一定提升输出质量——有时候反而是负面优化。本次 strict prompt 在 6 个模型上:1 个变好(其实只是变短)、3 个崩、2 个变短。
  • prompt 能改善的是"模型理解任务",治不了"模型偏离格式"。后者属于解码层问题,要么靠 grammar-constrained decoding(LKeAP 不支持),要么靠 response_format=json_object(部分模型也不遵守),要么靠代码侧容错。

7.4 关于 /plan/v3 这个 endpoint

  • 它是 LKeAP 的 plan 套餐 endpoint,对应一组国内合规模型:腾讯混元 + minimax + kimi + glm + tc-code。
  • 不包含 DeepSeek。如果业务方说"我要 deepseek-v3",要么换 endpoint(标准 /v1,需要不同的 key),要么换模型。
  • API key 前缀 sk-tp- 是 plan 套餐专用 key,不能跨 endpoint 使用。

8. 附:测试脚本

调研期间的脚本(都在 /tmp/,下次用前最好复制到项目里 archive):

文件用途
/tmp/test_hunyuan.pyhunyuan-turbos 单点测试(首次发现问题)
/tmp/test_models.pyhunyuan-2.0-instruct 单模型测试
/tmp/test_compare.py6 模型 × 2 prompt 横评
/tmp/test_stability.py27 次合成数据稳定性测试
/tmp/test_realdata.py40 次真实库数据大盘测试(决定性数据)
/tmp/shenlong_samples.json18 个 CVE 的完整样本输出

Comments (0)

No comments yet

imfht
April 11, 2026 · Updated April 11, 2026