feat(inference): chat_structured 接入 jsonschema 真校验(T1 架构审查整改)

- pyproject.toml 新增 jsonschema>=4.23 依赖
- engine.py: json.loads 后 jsonschema.validate,校验失败按解析失败重试(带错误信息)
- 新增 3 用例:违规重试成功/耗尽 parse_error 含详情/合法一次通过
- 全量 163 passed / 100.00% 覆盖(936 stmts/238 br),fail_under=99 达标
This commit is contained in:
lhl
2026-08-12 09:32:02 +08:00
parent 0a79b07356
commit 25fc472d9b
5 changed files with 241 additions and 0 deletions
+43
View File
@@ -318,6 +318,49 @@ def test_chat_structured_empty_schema_no_hint():
assert r.status == "ok" and r.data == {"v": True}
# ---------- T1: chat_structured 真 schema 校验(jsonschema ----------
def test_chat_structured_schema_violation_retries():
"""返回不合 schema 的 JSON 时带错误信息重试;第二次合法 → ok。"""
client = FakeLLMClient([("ok", '{"a": "not_a_number"}'), ("ok", '{"a": 2}')])
eng = make_engine(client)
r = eng.chat_structured(
session_id="s1", prompt=Prompt(name="p", version="v1", template="提取"),
variables={},
schema={"type": "object", "properties": {"a": {"type": "number"}}, "required": ["a"]},
)
assert r.status == "ok" and r.data == {"a": 2} and r.parse_attempts == 2
# 第二次调用带上次校验错误信息(重试提示)
assert "校验失败" in client.calls[1]["messages"][0]
def test_chat_structured_schema_violation_parse_error():
"""全部返回不合 schema 的 JSON → parse_errorerror 含校验详情。"""
client = FakeLLMClient([("ok", '{"a": "bad"}'), ("ok", '{"a": "bad"}')])
eng = make_engine(client)
r = eng.chat_structured(
session_id="s1", prompt=Prompt(name="p", version="v1", template="提取"),
variables={},
schema={"type": "object", "properties": {"a": {"type": "number"}}, "required": ["a"]},
retry_count=1,
)
assert r.status == "parse_error"
assert "校验失败" in (r.error or "")
assert r.parse_attempts == 2
def test_chat_structured_schema_valid_passes_without_retry():
"""返回合法 JSON 时一次通过,不触发重试。"""
client = FakeLLMClient([("ok", '{"a": 1}')])
eng = make_engine(client)
r = eng.chat_structured(
session_id="s1", prompt=Prompt(name="p", version="v1", template="提取"),
variables={},
schema={"type": "object", "properties": {"a": {"type": "number"}}, "required": ["a"]},
)
assert r.status == "ok" and r.data == {"a": 1} and r.parse_attempts == 1
def test_chat_truncation_callback_returns_none_keeps_variables():
"""truncate_cb 返回 None 时回退原 variables(覆盖 new_vars is None 分支)。"""
def truncate_cb(prompt_text, variables):