fix(server): 按 S01–S10 评测修复读标抽取(F1–F8、口径 Q1–Q4)
- 合并时待确认条目不再借正式条目进清单,没通过的类别记在 pending_categories - 结构识别:章(按章跳过不相干的部分)、表后「上述……否则无效」说明、★ 节标题 - INVALID 补写法;输出用到上限 92% 按截断处理,拆到底时采用已有输出 - 格式范本正文、整表概括句不收;项目信息多看评标方法所在分块 - WPS 旧版 DOC 直接修正字体表字符集;表格按单元格切句(s3-v4) - 口径:★ 节继承实质性、★ 定义为计分项时降为重要、「必须/应」声明记 keyword 规则、 政府采购资格条件按 87 号令第六十三条带无效类别 - 提示词 substantive v5,qualification / schedule / tech_param v4 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,10 @@ from __future__ import annotations
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Protocol
|
||||
|
||||
from bid_doctor.core.config import get_settings
|
||||
|
||||
NEAR_LIMIT = 0.92
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class Completion:
|
||||
@@ -12,10 +16,17 @@ class Completion:
|
||||
tokens_in: int | None = None
|
||||
tokens_out: int | None = None
|
||||
output_mode: str = "json_mode"
|
||||
# 输出不完整:被截断(finish_reason=length)或不是合法 JSON。调用方要拆小输入重跑,不能当成「没有结果」
|
||||
# 输出不完整:被截断(finish_reason=length)、用到了上限的 92% 以上,或不是合法 JSON。
|
||||
# 调用方要拆小输入重跑,不能当成「没有结果」
|
||||
incomplete: bool = False
|
||||
|
||||
|
||||
def near_limit(tokens_out: int | None) -> bool:
|
||||
"""输出用到了上限的 92% 以上:模型常在快到上限时自己把 JSON 收尾,后面的条目整段不输出,
|
||||
finish_reason 却是正常结束(评测 §3.5:S03 一块 7757 / 8192 token,后两页参数全丢)。按截断处理。"""
|
||||
return tokens_out is not None and tokens_out >= NEAR_LIMIT * get_settings().ai_max_output_tokens
|
||||
|
||||
|
||||
class TransientError(Exception):
|
||||
"""限流、5xx、网络抖动:可以重试;重试后仍失败按基础设施错误处理(退回额度)。"""
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import json
|
||||
|
||||
import httpx
|
||||
|
||||
from bid_doctor.ai.providers.base import Completion, TransientError
|
||||
from bid_doctor.ai.providers.base import Completion, TransientError, near_limit
|
||||
from bid_doctor.core.config import get_settings
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ class OpenAICompatProvider:
|
||||
"model": model,
|
||||
"temperature": 0,
|
||||
"response_format": {"type": "json_object"},
|
||||
"max_tokens": s.ai_max_output_tokens,
|
||||
"messages": [{"role": "system", "content": system}, {"role": "user", "content": user}],
|
||||
}
|
||||
try:
|
||||
@@ -44,7 +45,7 @@ class OpenAICompatProvider:
|
||||
choice = payload["choices"][0]
|
||||
raw = choice["message"]["content"] or ""
|
||||
usage = payload.get("usage") or {}
|
||||
incomplete = choice.get("finish_reason") == "length"
|
||||
incomplete = choice.get("finish_reason") == "length" or near_limit(usage.get("completion_tokens"))
|
||||
try:
|
||||
data = json.loads(raw)
|
||||
except json.JSONDecodeError:
|
||||
|
||||
@@ -21,7 +21,12 @@ RETRIES = 2
|
||||
|
||||
|
||||
class OutputIncomplete(Exception):
|
||||
"""模型输出被截断或不是合法 JSON:调用方把输入拆小重跑(extract.run),不能当成「没有结果」。"""
|
||||
"""模型输出被截断、接近上限或不是合法 JSON:调用方把输入拆小重跑(extract.run),不能当成「没有结果」。
|
||||
data 是已经解析出来的输出(接近上限时 JSON 往往是完整的),拆到底还不完整时调用方用它。"""
|
||||
|
||||
def __init__(self, task: str, data: dict[str, Any] | None = None) -> None:
|
||||
super().__init__(task)
|
||||
self.data: dict[str, Any] = data or {}
|
||||
|
||||
|
||||
def provider() -> Provider:
|
||||
@@ -72,5 +77,5 @@ async def run_json(
|
||||
raise error
|
||||
assert result is not None
|
||||
if result.incomplete:
|
||||
raise OutputIncomplete(task)
|
||||
raise OutputIncomplete(task, result.data)
|
||||
return result.data
|
||||
|
||||
Reference in New Issue
Block a user