feat(inference): chat_structured 解析重试走降级链(T2 架构审查整改)
- Issue2: 解析重试按 primary→fallback 顺序尝试,不再硬编码首选模型 - Issue10: 提取 names 局部变量,删除 4 处重复 _model_names(None)[0] 调用 - LLMError 不再 early return,继续降级链;全部失败按 last_was_parse_error 区分 parse_error/failed - 新增 2 用例(解析/网络失败降级 fallback),同步更新 7 个既有用例至降级链语义 - 全量 165 passed / 100.00% 覆盖(941 stmts/242 br),fail_under=99 达标
This commit is contained in:
@@ -2,7 +2,7 @@ from __future__ import annotations
|
||||
|
||||
import json
|
||||
import time
|
||||
from typing import Any, Callable
|
||||
from typing import Any, Callable, Literal
|
||||
|
||||
import jsonschema
|
||||
|
||||
@@ -144,59 +144,58 @@ class InferenceEngine:
|
||||
schema_hint = json.dumps(schema, ensure_ascii=False) if schema else ""
|
||||
base_rendered = rendered + (f'\n\n请输出符合以下 JSON Schema 的 JSON:{schema_hint}' if schema_hint else "")
|
||||
|
||||
names = self._model_names(None) # 降级链:解析重试也按 primary→fallback 顺序(T2/Issue10)
|
||||
start = time.monotonic()
|
||||
attempts = 0
|
||||
last_raw = ""
|
||||
last_error: str | None = None
|
||||
last_error_code: str | None = None
|
||||
last_was_parse_error = False
|
||||
|
||||
while attempts <= retry_count:
|
||||
attempts += 1
|
||||
try:
|
||||
text, usage = self._call(
|
||||
model=self._model_names(None)[0], # 解析重试用首选模型
|
||||
rendered=base_rendered,
|
||||
temperature=0.0, max_tokens=4096,
|
||||
)
|
||||
last_raw = text
|
||||
data = json.loads(text)
|
||||
if schema:
|
||||
# 真 schema 校验:不合 schema 时按解析失败重试(T1)
|
||||
jsonschema.validate(instance=data, schema=schema)
|
||||
return StructuredResult(
|
||||
data=data, raw_text=text, parse_attempts=attempts,
|
||||
model=self._model_names(None)[0],
|
||||
prompt_version=getattr(prompt, "version", "inline"),
|
||||
usage=usage,
|
||||
duration_ms=int((time.monotonic() - start) * 1000),
|
||||
# fallback 语义保留给模型降级;解析重试成功仍为 ok
|
||||
status="ok",
|
||||
)
|
||||
except json.JSONDecodeError as exc:
|
||||
last_error = f"JSON 解析失败: {exc}"
|
||||
last_error_code = "LLM_PARSE_ERROR"
|
||||
# 带错误信息重试
|
||||
base_rendered = base_rendered + f"\n\n上次解析失败:{exc}。请重新输出合法 JSON。"
|
||||
except jsonschema.ValidationError as exc:
|
||||
last_error = f"校验失败: {exc.message}"
|
||||
last_error_code = "LLM_PARSE_ERROR"
|
||||
# 带校验错误信息重试
|
||||
base_rendered = base_rendered + f"\n\n上次校验失败:{exc.message}。请重新输出符合 JSON Schema 的 JSON。"
|
||||
except LLMError as exc:
|
||||
return StructuredResult(
|
||||
data={}, raw_text="", parse_attempts=attempts,
|
||||
model=self._model_names(None)[0],
|
||||
prompt_version=getattr(prompt, "version", "inline"),
|
||||
usage=TokenUsage(),
|
||||
duration_ms=int((time.monotonic() - start) * 1000),
|
||||
status="failed", error=str(exc), error_code=exc.error_code,
|
||||
)
|
||||
for idx, name in enumerate(names):
|
||||
try:
|
||||
text, usage = self._call(
|
||||
model=name,
|
||||
rendered=base_rendered,
|
||||
temperature=0.0, max_tokens=4096,
|
||||
)
|
||||
last_raw = text
|
||||
data = json.loads(text)
|
||||
if schema:
|
||||
# 真 schema 校验:不合 schema 时按解析失败重试(T1)
|
||||
jsonschema.validate(instance=data, schema=schema)
|
||||
return StructuredResult(
|
||||
data=data, raw_text=text, parse_attempts=attempts,
|
||||
model=name,
|
||||
prompt_version=getattr(prompt, "version", "inline"),
|
||||
usage=usage,
|
||||
duration_ms=int((time.monotonic() - start) * 1000),
|
||||
# 首选模型成功为 ok;降级链模型成功为 fallback
|
||||
status="ok" if idx == 0 else "fallback",
|
||||
)
|
||||
except (json.JSONDecodeError, jsonschema.ValidationError) as exc:
|
||||
last_error = f"解析/校验失败: {exc}"
|
||||
last_error_code = "LLM_PARSE_ERROR"
|
||||
last_was_parse_error = True
|
||||
# 带错误信息继续降级链(备用模型重试时可见)
|
||||
base_rendered = base_rendered + f"\n\n上次失败:{last_error}。请重新输出合法 JSON。"
|
||||
except LLMError as exc:
|
||||
last_error = str(exc)
|
||||
last_error_code = exc.error_code
|
||||
last_was_parse_error = False
|
||||
# 继续降级链尝试下一模型
|
||||
|
||||
if last_was_parse_error:
|
||||
status: Literal["ok", "fallback", "parse_error", "failed"] = "parse_error"
|
||||
else:
|
||||
status = "failed"
|
||||
return StructuredResult(
|
||||
data={}, raw_text=last_raw, parse_attempts=attempts,
|
||||
model=self._model_names(None)[0],
|
||||
model=names[0],
|
||||
prompt_version=getattr(prompt, "version", "inline"),
|
||||
usage=TokenUsage(),
|
||||
duration_ms=int((time.monotonic() - start) * 1000),
|
||||
status="parse_error", error=last_error, error_code=last_error_code,
|
||||
status=status, error=last_error, error_code=last_error_code,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user