feat: add structured output ladder with bounded feedback retries
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
"""JsonRepairStrategy: 阶梯②修复(D7/D14;蓝本 VT core/agent/loop.py:341-377)。
|
||||
|
||||
处理链: 围栏剥离 → json_repair → json.loads → 可选 normalize 钩子。
|
||||
业务 schema 相关的变体归一化(如 VT `_normalize_action` 的 DeepSeek 平铺
|
||||
收拢)**不入库**——零业务假设铁律;业务侧经 `normalize` 注入自带函数。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from polygateway.errors import ResultInvalidError
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable
|
||||
|
||||
try:
|
||||
from json_repair import repair_json
|
||||
except ImportError as _exc: # pragma: no cover - 依赖缺失路径
|
||||
repair_json = None
|
||||
_IMPORT_ERROR = _exc
|
||||
else:
|
||||
_IMPORT_ERROR = None
|
||||
|
||||
# VT loop.py:37 同款围栏正则
|
||||
_CODE_FENCE_RE = re.compile(r"^\s*```(?:json)?\s*\n?|\n?\s*```\s*$")
|
||||
|
||||
|
||||
class JsonRepairStrategy:
|
||||
"""prompt 约定 + 事后修复策略;不改请求体(request_overlay 恒空)。"""
|
||||
|
||||
def __init__(self, normalize: Callable[[Any], Any] | None = None) -> None:
|
||||
if repair_json is None:
|
||||
raise ImportError(
|
||||
"结构化输出需要 json_repair 包: pip install 'polygateway[structured]'"
|
||||
) from _IMPORT_ERROR
|
||||
self._normalize = normalize
|
||||
|
||||
def request_overlay(self, schema: dict[str, Any] | None) -> dict[str, Any]:
|
||||
return {}
|
||||
|
||||
def parse(self, text: str) -> Any:
|
||||
"""修复并解析;失败抛 ResultInvalidError(坏结果 ≠ 坏服务)。"""
|
||||
stripped = _CODE_FENCE_RE.sub("", text).strip()
|
||||
if not stripped:
|
||||
raise ResultInvalidError("结构化输出为空", raw_text=text, repair_error="empty content")
|
||||
try:
|
||||
data = json.loads(repair_json(stripped))
|
||||
except (json.JSONDecodeError, ValueError) as exc:
|
||||
raise ResultInvalidError(
|
||||
"JSON 修复失败", raw_text=text, repair_error=str(exc)
|
||||
) from exc
|
||||
if self._normalize is not None:
|
||||
data = self._normalize(data)
|
||||
return data
|
||||
@@ -0,0 +1,32 @@
|
||||
"""NativeSchemaStrategy: 阶梯①预防(D7/D14)——网关支持时用协议级约束。
|
||||
|
||||
请求侧注入 response_format(有 schema 用 json_schema 严格模式,无 schema
|
||||
退为 json_object);响应侧仍复用修复链兜底——原生约束下模型偶发的围栏/
|
||||
噪声不至于直接判死。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from polygateway.structured.json_repair import JsonRepairStrategy
|
||||
|
||||
|
||||
class NativeSchemaStrategy:
|
||||
"""response_format 注入策略;由装配层按 provider 注册表能力选择。"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._repair = JsonRepairStrategy()
|
||||
|
||||
def request_overlay(self, schema: dict[str, Any] | None) -> dict[str, Any]:
|
||||
if schema is None:
|
||||
return {"response_format": {"type": "json_object"}}
|
||||
return {
|
||||
"response_format": {
|
||||
"type": "json_schema",
|
||||
"json_schema": {"name": "structured_output", "strict": True, "schema": schema},
|
||||
}
|
||||
}
|
||||
|
||||
def parse(self, text: str) -> Any:
|
||||
return self._repair.parse(text)
|
||||
Reference in New Issue
Block a user