437 lines
17 KiB
Python
437 lines
17 KiB
Python
"""统一反馈中心的原因池与候选生成。"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from collections import OrderedDict
|
|
from typing import Any
|
|
|
|
|
|
_REASON_DEFINITIONS: list[dict[str, Any]] = [
|
|
{
|
|
"code": "owner_mismatch",
|
|
"label": "负责人不匹配",
|
|
"description": "AI 推荐、人工指派或协作责任边界需要复核。",
|
|
"source_types": {"task_breakdown", "task_override"},
|
|
"field_names": {"assignee_id"},
|
|
"confidence": 0.96,
|
|
},
|
|
{
|
|
"code": "capability_mismatch",
|
|
"label": "能力匹配偏差",
|
|
"description": "人员能力、技能覆盖或职责分配与任务要求不一致。",
|
|
"source_types": {"task_breakdown", "task_override"},
|
|
"field_names": {"assignee_id", "required_skill_tag_ids", "required_skill_names"},
|
|
"confidence": 0.91,
|
|
},
|
|
{
|
|
"code": "skill_gap",
|
|
"label": "技能要求不准",
|
|
"description": "任务技能标签、能力要求或技能覆盖范围需要修正。",
|
|
"source_types": {"task_breakdown", "task_override"},
|
|
"field_names": {"required_skill_tag_ids", "required_skill_names"},
|
|
"confidence": 0.94,
|
|
},
|
|
{
|
|
"code": "priority_misalignment",
|
|
"label": "优先级错配",
|
|
"description": "优先级建议与业务紧急度或协作节奏不匹配。",
|
|
"source_types": {"task_breakdown", "task_override"},
|
|
"field_names": {"priority"},
|
|
"confidence": 0.89,
|
|
},
|
|
{
|
|
"code": "dependency_wrong",
|
|
"label": "依赖关系错误",
|
|
"description": "任务之间的依赖、先后顺序或阻塞链路判断有误。",
|
|
"source_types": {"task_breakdown", "task_override"},
|
|
"field_names": {"depends_on_task_keys"},
|
|
"confidence": 0.92,
|
|
},
|
|
{
|
|
"code": "risk_underestimated",
|
|
"label": "风险低估",
|
|
"description": "任务风险等级、风险说明或执行风险识别偏低。",
|
|
"source_types": {"task_breakdown", "task_override", "signal_feedback"},
|
|
"field_names": {"risk_level", "risk_notes", "strategic_classification"},
|
|
"confidence": 0.9,
|
|
},
|
|
{
|
|
"code": "effort_estimation_bias",
|
|
"label": "工时预估偏差",
|
|
"description": "任务预估工时与人工判断差距较大。",
|
|
"source_types": {"task_breakdown", "task_override"},
|
|
"field_names": {"estimated_hours"},
|
|
"confidence": 0.95,
|
|
},
|
|
{
|
|
"code": "scope_overbroad",
|
|
"label": "范围过大",
|
|
"description": "任务标题、描述或业务范围切分不够清晰。",
|
|
"source_types": {"task_breakdown", "task_override"},
|
|
"field_names": {"title", "description"},
|
|
"confidence": 0.82,
|
|
},
|
|
{
|
|
"code": "incomplete_output",
|
|
"label": "输出信息不完整",
|
|
"description": "AI 输出缺少关键字段、上下文或业务约束。",
|
|
"source_types": {"task_breakdown", "task_override", "qa_feedback"},
|
|
"field_names": {"task_object", "qa_feedback"},
|
|
"confidence": 0.78,
|
|
},
|
|
{
|
|
"code": "answer_incorrect",
|
|
"label": "答案不正确",
|
|
"description": "QA 回复存在事实错误、遗漏或推断偏差。",
|
|
"source_types": {"qa_feedback"},
|
|
"field_names": {"qa_feedback"},
|
|
"confidence": 0.93,
|
|
},
|
|
{
|
|
"code": "source_issue",
|
|
"label": "来源问题",
|
|
"description": "QA 的来源引用、检索材料或证据组织存在问题。",
|
|
"source_types": {"qa_feedback"},
|
|
"field_names": {"qa_feedback"},
|
|
"confidence": 0.86,
|
|
},
|
|
{
|
|
"code": "wrong_fast_path",
|
|
"label": "误走快速路径",
|
|
"description": "问题本应进入更重的检索路径,却被过早走了 fast path。",
|
|
"source_types": {"qa_feedback"},
|
|
"field_names": {"qa_feedback"},
|
|
"confidence": 0.9,
|
|
},
|
|
{
|
|
"code": "should_have_gone_deep",
|
|
"label": "应该进入深检索",
|
|
"description": "当前问题需要更深层的证据检索与合成,但 runtime 未升级。",
|
|
"source_types": {"qa_feedback"},
|
|
"field_names": {"qa_feedback"},
|
|
"confidence": 0.91,
|
|
},
|
|
{
|
|
"code": "wrong_source_priority",
|
|
"label": "来源优先级错误",
|
|
"description": "检索时对事实源的优先级排序不合理,导致引用偏差。",
|
|
"source_types": {"qa_feedback"},
|
|
"field_names": {"qa_feedback"},
|
|
"confidence": 0.88,
|
|
},
|
|
{
|
|
"code": "insufficient_evidence",
|
|
"label": "证据不足",
|
|
"description": "回答缺少足够内部证据支撑,应澄清或拒答。",
|
|
"source_types": {"qa_feedback"},
|
|
"field_names": {"qa_feedback"},
|
|
"confidence": 0.94,
|
|
},
|
|
{
|
|
"code": "good_retrieval_bad_synthesis",
|
|
"label": "检索好但合成差",
|
|
"description": "证据命中基本正确,但最终答案组织、归纳或结论表达有偏差。",
|
|
"source_types": {"qa_feedback"},
|
|
"field_names": {"qa_feedback"},
|
|
"confidence": 0.83,
|
|
},
|
|
{
|
|
"code": "over_deep_latency",
|
|
"label": "过度深检索延迟",
|
|
"description": "问题本可快速回答,但进入过深链路导致延迟偏高。",
|
|
"source_types": {"qa_feedback"},
|
|
"field_names": {"qa_feedback"},
|
|
"confidence": 0.84,
|
|
},
|
|
{
|
|
"code": "permission_scope_miss",
|
|
"label": "权限范围漏检",
|
|
"description": "检索没有正确命中用户应有权限范围内的事实源。",
|
|
"source_types": {"qa_feedback"},
|
|
"field_names": {"qa_feedback"},
|
|
"confidence": 0.87,
|
|
},
|
|
{
|
|
"code": "strategic_judgement_mismatch",
|
|
"label": "战略判断偏差",
|
|
"description": "AI 对信号是否战略相关的判断与人工判断不一致。",
|
|
"source_types": {"signal_feedback"},
|
|
"field_names": {"strategic_classification"},
|
|
"confidence": 0.97,
|
|
},
|
|
{
|
|
"code": "signal_noise",
|
|
"label": "噪声信号",
|
|
"description": "信号本身噪声过高或不值得进入后续流程。",
|
|
"source_types": {"signal_feedback"},
|
|
"field_names": {"strategic_classification"},
|
|
"confidence": 0.8,
|
|
},
|
|
{
|
|
"code": "manual_override",
|
|
"label": "人工覆盖",
|
|
"description": "字段由人工介入修改后形成反馈样本。",
|
|
"source_types": {"task_breakdown", "task_override", "qa_feedback", "signal_feedback"},
|
|
"field_names": {"*", "task_object", "qa_feedback", "strategic_classification"},
|
|
"confidence": 0.75,
|
|
},
|
|
{
|
|
"code": "manual_review_needed",
|
|
"label": "需要人工复核",
|
|
"description": "当前样本仍需要人工确认,不足以自动通过。",
|
|
"source_types": {"task_breakdown", "task_override", "qa_feedback", "signal_feedback"},
|
|
"field_names": {"*"},
|
|
"confidence": 0.7,
|
|
},
|
|
]
|
|
|
|
|
|
def _candidate_payload(definition: dict[str, Any], *, source_type: str, field_name: str) -> dict[str, Any]:
|
|
return {
|
|
"code": definition["code"],
|
|
"label": definition["label"],
|
|
"description": definition["description"],
|
|
"source_type": source_type,
|
|
"field_name": field_name,
|
|
"confidence": float(definition.get("confidence") or 0.0),
|
|
}
|
|
|
|
|
|
def list_reason_catalog(
|
|
*,
|
|
source_type: str | None = None,
|
|
field_name: str | None = None,
|
|
) -> list[dict[str, Any]]:
|
|
items: list[dict[str, Any]] = []
|
|
for definition in _REASON_DEFINITIONS:
|
|
if source_type and source_type not in definition["source_types"]:
|
|
continue
|
|
if field_name and "*" not in definition["field_names"] and field_name not in definition["field_names"]:
|
|
continue
|
|
items.append(
|
|
{
|
|
"code": definition["code"],
|
|
"label": definition["label"],
|
|
"description": definition["description"],
|
|
"source_types": sorted(definition["source_types"]),
|
|
"field_names": sorted(definition["field_names"]),
|
|
"confidence": float(definition.get("confidence") or 0.0),
|
|
}
|
|
)
|
|
return items
|
|
|
|
|
|
def _append_unique(candidate_map: OrderedDict[str, dict[str, Any]], candidate: dict[str, Any]) -> None:
|
|
code = str(candidate.get("code") or "").strip()
|
|
if not code or code in candidate_map:
|
|
return
|
|
candidate_map[code] = candidate
|
|
|
|
|
|
def _normalize_text(value: Any) -> str:
|
|
return str(value or "").strip().lower()
|
|
|
|
|
|
def _is_truthy_change(before_value: Any, after_value: Any) -> bool:
|
|
if before_value is None and after_value is None:
|
|
return False
|
|
return before_value != after_value
|
|
|
|
|
|
def _estimated_hours_direction(before_value: Any, after_value: Any) -> str:
|
|
try:
|
|
before = float(before_value or 0)
|
|
after = float(after_value or 0)
|
|
except (TypeError, ValueError):
|
|
return "unknown"
|
|
if after > before:
|
|
return "increase"
|
|
if after < before:
|
|
return "decrease"
|
|
return "flat"
|
|
|
|
|
|
def build_reason_candidates(
|
|
*,
|
|
source_type: str,
|
|
field_name: str,
|
|
before_value: Any = None,
|
|
after_value: Any = None,
|
|
) -> list[dict[str, Any]]:
|
|
"""基于字段差异构建稳定、可解释的候选原因。"""
|
|
|
|
source_key = _normalize_text(source_type)
|
|
field_key = _normalize_text(field_name)
|
|
candidates: OrderedDict[str, dict[str, Any]] = OrderedDict()
|
|
|
|
def add(code: str, label: str, confidence: float, description: str) -> None:
|
|
_append_unique(
|
|
candidates,
|
|
{
|
|
"code": code,
|
|
"label": label,
|
|
"description": description,
|
|
"source_type": source_key,
|
|
"field_name": field_key,
|
|
"confidence": float(confidence),
|
|
},
|
|
)
|
|
|
|
if source_key == "task_override" or source_key == "task_breakdown":
|
|
if field_key in {"assignee_id", "owner_id"}:
|
|
add(
|
|
"owner_mismatch",
|
|
"负责人不匹配",
|
|
0.96 if _is_truthy_change(before_value, after_value) else 0.88,
|
|
"负责人或责任归属与任务实际执行人不一致。",
|
|
)
|
|
add(
|
|
"capability_mismatch",
|
|
"能力匹配偏差",
|
|
0.91,
|
|
"人员能力、技能覆盖或职责分配与任务要求不一致。",
|
|
)
|
|
if field_key in {"required_skill_tag_ids", "required_skill_names"}:
|
|
add(
|
|
"skill_gap",
|
|
"技能要求不准",
|
|
0.94 if _is_truthy_change(before_value, after_value) else 0.82,
|
|
"任务所需技能标签需要复核。",
|
|
)
|
|
add(
|
|
"capability_mismatch",
|
|
"能力匹配偏差",
|
|
0.9,
|
|
"人员能力、技能覆盖或职责分配与任务要求不一致。",
|
|
)
|
|
if field_key in {"depends_on_task_keys", "dependency", "dependencies"}:
|
|
add(
|
|
"dependency_wrong",
|
|
"依赖关系错误",
|
|
0.92,
|
|
"任务依赖、前置关系或阻塞关系需要修正。",
|
|
)
|
|
if field_key == "priority":
|
|
add(
|
|
"priority_misalignment",
|
|
"优先级错配",
|
|
0.9,
|
|
"优先级建议与业务紧急度或协作节奏不匹配。",
|
|
)
|
|
if field_key == "estimated_hours":
|
|
direction = _estimated_hours_direction(before_value, after_value)
|
|
confidence = 0.95 if direction in {"increase", "decrease"} else 0.8
|
|
add(
|
|
"effort_estimation_bias",
|
|
"工时预估偏差",
|
|
confidence,
|
|
"AI 工时预估与人工判断存在明显差异。",
|
|
)
|
|
if direction == "increase":
|
|
add(
|
|
"scope_overbroad",
|
|
"范围过大",
|
|
0.78,
|
|
"任务范围可能需要进一步拆分。",
|
|
)
|
|
if field_key in {"risk_level", "risk_notes"}:
|
|
add(
|
|
"risk_underestimated",
|
|
"风险低估",
|
|
0.9,
|
|
"任务风险说明可能低估了实际执行复杂度。",
|
|
)
|
|
if field_key in {"title", "description"}:
|
|
add(
|
|
"scope_overbroad",
|
|
"范围过大",
|
|
0.84,
|
|
"任务标题或描述可能覆盖了过多内容。",
|
|
)
|
|
if field_key == "task_object":
|
|
add(
|
|
"incomplete_output",
|
|
"输出信息不完整",
|
|
0.76,
|
|
"模型输出仍可能缺失关键细节,需要人工补全。",
|
|
)
|
|
add("manual_override", "人工覆盖", 0.78, "AI 拆解结果经过人工修改后形成学习样本。")
|
|
elif source_key == "qa_feedback":
|
|
rating = _normalize_text((after_value or {}).get("rating") if isinstance(after_value, dict) else None)
|
|
error_types = []
|
|
if isinstance(after_value, dict):
|
|
error_types = [str(item).strip().lower() for item in after_value.get("error_types") or [] if str(item).strip()]
|
|
if rating == "negative":
|
|
if any(token in {"hallucination", "幻觉", "错误", "wrong"} for token in error_types):
|
|
add(
|
|
"answer_incorrect",
|
|
"答案不正确",
|
|
0.95,
|
|
"反馈显示回答内容存在事实错误或幻觉。",
|
|
)
|
|
add(
|
|
"source_issue",
|
|
"来源问题",
|
|
0.84,
|
|
"回答所依赖的来源或证据组织需要复核。",
|
|
)
|
|
add(
|
|
"incomplete_output",
|
|
"输出信息不完整",
|
|
0.78,
|
|
"回答缺少关键信息或上下文不够完整。",
|
|
)
|
|
grounding_reason_map = {
|
|
"wrong_fast_path": ("wrong_fast_path", "误走快速路径", 0.91, "问题本应进入更重的检索路径,却被过早走了 fast path。"),
|
|
"fast_path": ("wrong_fast_path", "误走快速路径", 0.89, "问题本应进入更重的检索路径,却被过早走了 fast path。"),
|
|
"should_have_gone_deep": ("should_have_gone_deep", "应该进入深检索", 0.92, "当前问题需要更深层的证据检索与合成,但 runtime 未升级。"),
|
|
"deep": ("should_have_gone_deep", "应该进入深检索", 0.88, "当前问题需要更深层的证据检索与合成,但 runtime 未升级。"),
|
|
"wrong_source_priority": ("wrong_source_priority", "来源优先级错误", 0.88, "检索时对事实源的优先级排序不合理。"),
|
|
"insufficient_evidence": ("insufficient_evidence", "证据不足", 0.94, "回答缺少足够内部证据支撑,应澄清或拒答。"),
|
|
"good_retrieval_bad_synthesis": ("good_retrieval_bad_synthesis", "检索好但合成差", 0.83, "证据命中基本正确,但最终答案组织或结论表达有偏差。"),
|
|
"over_deep_latency": ("over_deep_latency", "过度深检索延迟", 0.84, "问题本可快速回答,但进入过深链路导致延迟偏高。"),
|
|
"permission_scope_miss": ("permission_scope_miss", "权限范围漏检", 0.87, "检索没有正确命中用户应有权限范围内的事实源。"),
|
|
}
|
|
for token in error_types:
|
|
mapping = grounding_reason_map.get(token)
|
|
if mapping is None:
|
|
continue
|
|
add(mapping[0], mapping[1], mapping[2], mapping[3])
|
|
else:
|
|
add("manual_review_needed", "需要人工复核", 0.72, "QA 反馈建议保留人工复核以确认样本质量。")
|
|
elif source_key == "signal_feedback":
|
|
predicted = None
|
|
actual = None
|
|
if isinstance(before_value, dict):
|
|
predicted = before_value.get("ai_predicted_strategic")
|
|
if isinstance(after_value, dict):
|
|
actual = after_value.get("was_strategic")
|
|
if predicted is not None and actual is not None and bool(predicted) != bool(actual):
|
|
add(
|
|
"strategic_judgement_mismatch",
|
|
"战略性判断偏差",
|
|
0.97,
|
|
"AI 对信号是否需要进入战略流程的判断与人工结果不一致。",
|
|
)
|
|
add(
|
|
"signal_noise",
|
|
"噪声信号",
|
|
0.82,
|
|
"当前信号可能缺少足够强的业务价值信号。",
|
|
)
|
|
else:
|
|
add("manual_review_needed", "需要人工复核", 0.7, "当前样本需要进一步人工确认。")
|
|
|
|
if not candidates:
|
|
add(
|
|
"manual_review_needed",
|
|
"需要人工复核",
|
|
0.7,
|
|
"当前样本需要进一步人工确认。",
|
|
)
|
|
|
|
return list(candidates.values())
|
|
|
|
|
|
__all__ = ["build_reason_candidates", "list_reason_catalog"]
|