refactor: --id 改为真实 ID 列,新增 --sn 对应总排号
此前接口 --id 实际按总排号列查询,与数据库真实 ID 字段语义混淆。
现明确区分两种键类型:
- --id -> 数据库真实 ID 列(config id_field)
- --sn -> 总排号列(config id_column,即原先 --id 的语义)
- --ids-file 视为总排号(键类型 sn)
db.py: fetch_params_by_ids 改为接受 (标识符, 键类型) 列表,返回
{标识符: {param, sn}};id 键回取对应总排号;新增 id_field 配置读取
classifier.py: classify_batch/classify_single 透传键类型,输出 zong_pai_hao
一律为回查到的总排号,not_found 时回退输入标识符便于追溯
main.py / write_attachments.py: 新增 --sn,--id 改指真实 ID,二者可混用;
全表扫描回退仍按总排号(sn)查询
README.md: 对齐 --id/--sn 语义并补充 id_field 配置说明
注:config.yaml 含密钥被 .gitignore 忽略,id_field 仅存于本地配置;
db.py 在未配置 id_field 时优雅降级为按总排号查询并告警。
Co-Authored-By: WorkBuddy <workbuddy@tencent.com>
This commit is contained in:
@@ -15,9 +15,9 @@
|
||||
日志模块的核心约定:不因为"这次调用失败了"就跳过记录,失败的调用恰恰最
|
||||
需要被记下来供排查。
|
||||
|
||||
批量处理(classify_batch)在此基础上先查数据库拿到 {总排号: 新参数} 映射,
|
||||
对查不到的总排号直接标记 status="not_found",不发起LLM调用(也不生成日志,
|
||||
因为根本没有"新参数"可供判断,没有对话可记)。
|
||||
批量处理(classify_batch)在此基础上先按 (标识符, 键类型) 查数据库,回取出
|
||||
{标识符: {param, 总排号}} 映射;对查不到的标识符直接标记 status="not_found",
|
||||
不发起LLM调用(也不生成日志,因为根本没有"新参数"可供判断,没有对话可记)。
|
||||
|
||||
enable_other 控制是否允许模型使用"其他"兜底类目(默认 False),从
|
||||
classify_batch/classify_single 一路透传到 llm_client.classify_raw(决定
|
||||
@@ -174,7 +174,7 @@ def classify_param_text(
|
||||
def classify_batch(
|
||||
db_cfg: dict[str, Any],
|
||||
llm_cfg: dict[str, Any],
|
||||
zong_pai_hao_list: list[str],
|
||||
id_list: list[tuple[str, str]],
|
||||
max_workers: int = 8,
|
||||
mode: str = "coarse",
|
||||
log_dir: str | Path | None = None,
|
||||
@@ -182,43 +182,56 @@ def classify_batch(
|
||||
) -> list[dict[str, Any]]:
|
||||
"""批量分类入口:查库 -> 并发调用LLM -> 组装结果列表。
|
||||
|
||||
id_list: list[(标识符, 键类型)],键类型取值:
|
||||
"sn" -> 标识符为总排号,按总排号列查询;
|
||||
"id" -> 标识符为数据库真实 ID,按 id_field 列查询并回取总排号。
|
||||
两种键类型可在同一次调用中混用。
|
||||
|
||||
log_dir 若提供,会为每个实际发起LLM调用的总排号在该目录下生成一个日志
|
||||
文件;查库失败(not_found)或参数为空(empty_param)的总排号不生成日志文件,
|
||||
因为它们本就没有与LLM的对话内容可记。
|
||||
|
||||
返回结果顺序与输入 zong_pai_hao_list 一致,即使某些查询失败或格式错误也会
|
||||
补全为对应 status 的占位结果,保证"输入N个总排号,输出N条结果"。
|
||||
返回结果顺序与输入 id_list 一致,即使某些查询失败或格式错误也会
|
||||
补全为对应 status 的占位结果,保证"输入N条,输出N条"。结果中的
|
||||
zong_pai_hao 一律为回查到的总排号(键类型为 id 时由数据库回取;键类型为
|
||||
sn 时即输入本身;not_found 时回退为输入标识符以便追溯)。
|
||||
"""
|
||||
# 去重但保留顺序,避免用户传入重复总排号导致重复查询/调用
|
||||
# 去重但保留顺序,避免用户传入重复标识符导致重复查询/调用
|
||||
seen: set[str] = set()
|
||||
unique_ids: list[str] = []
|
||||
for zph in zong_pai_hao_list:
|
||||
if zph not in seen:
|
||||
seen.add(zph)
|
||||
unique_ids.append(zph)
|
||||
unique_ids: list[tuple[str, str]] = []
|
||||
for item in id_list:
|
||||
idt = item[0]
|
||||
if idt not in seen:
|
||||
seen.add(idt)
|
||||
unique_ids.append(item)
|
||||
|
||||
try:
|
||||
param_map = fetch_params_by_ids(db_cfg, unique_ids)
|
||||
except DatabaseError as e:
|
||||
logger.error("数据库查询失败,本批次全部标记为 db_error: %s", e)
|
||||
return [_empty_result(zph, "db_error") for zph in zong_pai_hao_list]
|
||||
return [_empty_result(idt, "db_error") for idt, _ in id_list]
|
||||
|
||||
llm_client = LLMClient(llm_cfg)
|
||||
results_by_id: dict[str, dict[str, Any]] = {}
|
||||
|
||||
ids_to_classify = [zph for zph in unique_ids if param_map.get(zph) is not None]
|
||||
for zph in unique_ids:
|
||||
if param_map.get(zph) is None:
|
||||
results_by_id[zph] = _empty_result(zph, "not_found")
|
||||
# 待分类项:(标识符, 对应总排号);not_found(未查到) 直接占位
|
||||
ids_to_classify: list[tuple[str, str]] = []
|
||||
for idt, _ in unique_ids:
|
||||
entry = param_map.get(idt)
|
||||
if entry is not None and entry["param"] is not None:
|
||||
ids_to_classify.append((idt, entry["sn"]))
|
||||
else:
|
||||
# 未查到:zong_pai_hao 回退为输入标识符(可能是 ID 或 SN)
|
||||
results_by_id[idt] = _empty_result(idt, "not_found")
|
||||
|
||||
def _worker(zph: str) -> tuple[str, dict[str, Any]]:
|
||||
order_logger = OrderLogger(log_dir, zph, mode) if log_dir is not None else None
|
||||
def _worker(idt: str, sn: str | None) -> tuple[str, dict[str, Any]]:
|
||||
order_logger = OrderLogger(log_dir, sn, mode) if log_dir is not None else None
|
||||
status, has_attachment, types, meta = classify_param_text(
|
||||
llm_client, param_map[zph], mode=mode, order_logger=order_logger,
|
||||
llm_client, param_map[idt]["param"], mode=mode, order_logger=order_logger,
|
||||
enable_other=enable_other,
|
||||
)
|
||||
return zph, {
|
||||
"zong_pai_hao": zph,
|
||||
return idt, {
|
||||
"zong_pai_hao": sn,
|
||||
"status": status,
|
||||
"has_attachment": has_attachment,
|
||||
"types": types,
|
||||
@@ -227,26 +240,27 @@ def classify_batch(
|
||||
|
||||
if ids_to_classify:
|
||||
with ThreadPoolExecutor(max_workers=max_workers) as pool:
|
||||
futures = [pool.submit(_worker, zph) for zph in ids_to_classify]
|
||||
futures = [pool.submit(_worker, idt, sn) for idt, sn in ids_to_classify]
|
||||
for fut in as_completed(futures):
|
||||
zph, result = fut.result()
|
||||
results_by_id[zph] = result
|
||||
idt, result = fut.result()
|
||||
results_by_id[idt] = result
|
||||
|
||||
# 按原始输入顺序(含重复项)展开最终结果
|
||||
return [results_by_id[zph] for zph in zong_pai_hao_list]
|
||||
return [results_by_id[idt] for idt, _ in id_list]
|
||||
|
||||
|
||||
def classify_single(
|
||||
db_cfg: dict[str, Any],
|
||||
llm_cfg: dict[str, Any],
|
||||
zong_pai_hao: str,
|
||||
identifier: str,
|
||||
key: str = "sn",
|
||||
mode: str = "coarse",
|
||||
log_dir: str | Path | None = None,
|
||||
enable_other: bool = False,
|
||||
) -> dict[str, Any]:
|
||||
"""单个总排号分类的便捷封装。"""
|
||||
"""单个总排号/ID 分类的便捷封装。key 取值 "sn"(总排号) 或 "id"(真实ID)。"""
|
||||
return classify_batch(
|
||||
db_cfg, llm_cfg, [zong_pai_hao], max_workers=1, mode=mode, log_dir=log_dir,
|
||||
db_cfg, llm_cfg, [(identifier, key)], max_workers=1, mode=mode, log_dir=log_dir,
|
||||
enable_other=enable_other,
|
||||
)[0]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user