feat:客户agent以及记忆模块功能开发
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
"""客服对话记忆的基础置信度计算工具。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
|
||||
class BaseConfidenceCalcTool:
|
||||
"""根据来源、证据、冲突和时间计算单条记忆的长期置信度。"""
|
||||
|
||||
SOURCE_INITIAL = {
|
||||
"dialogue_confirmed": 0.75,
|
||||
"dialogue_stated": 0.50,
|
||||
"dialogue_inferred": 0.45,
|
||||
}
|
||||
MEMORY_THRESHOLDS = {
|
||||
"PROFILE_FACT": 0.75,
|
||||
"CUSTOMER_PREFERENCE": 0.65,
|
||||
"INVESTMENT_GOAL": 0.70,
|
||||
"SERVICE_FACT": 0.75,
|
||||
}
|
||||
DEFAULT_THRESHOLD = 0.80
|
||||
VERSION = "confidence-v1"
|
||||
|
||||
def calc(
|
||||
self,
|
||||
tag: str,
|
||||
source: str,
|
||||
evidence_count: int,
|
||||
conflict_count: int,
|
||||
age_days: int,
|
||||
) -> float:
|
||||
"""计算基础置信度分数,返回范围为 0 到 1 的浮点数。"""
|
||||
self._validate(tag, source, evidence_count, conflict_count, age_days)
|
||||
base = self.SOURCE_INITIAL[source]
|
||||
gain = min(evidence_count * 0.05, 0.30)
|
||||
penalty = min(conflict_count * 0.10, 0.50)
|
||||
decay = max(0.80, 1 - age_days / 365 * 0.20)
|
||||
return max(0.0, min(1.0, (base + gain - penalty) * decay))
|
||||
|
||||
def evaluate(
|
||||
self,
|
||||
*,
|
||||
tag: str,
|
||||
source: str,
|
||||
evidence_count: int,
|
||||
conflict_count: int,
|
||||
age_days: int,
|
||||
memory_type: str | None = None,
|
||||
threshold: float | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""返回可供记忆模块保存的完整置信度评估结果。"""
|
||||
score = self.calc(tag, source, evidence_count, conflict_count, age_days)
|
||||
if threshold is None:
|
||||
threshold = self.MEMORY_THRESHOLDS.get(
|
||||
memory_type or "", self.DEFAULT_THRESHOLD
|
||||
)
|
||||
if not 0.0 <= threshold <= 1.0:
|
||||
raise ValueError("threshold 必须在 [0.0, 1.0] 范围内")
|
||||
base = self.SOURCE_INITIAL[source]
|
||||
status = "confirmed" if score >= threshold else "candidate"
|
||||
return {
|
||||
"source_confidence": base,
|
||||
"confidence": score,
|
||||
"status": status,
|
||||
"evidence_count": evidence_count,
|
||||
"conflict_count": conflict_count,
|
||||
"age_days": age_days,
|
||||
"threshold": threshold,
|
||||
"confidence_reason": self._reason(
|
||||
source, evidence_count, conflict_count, age_days
|
||||
),
|
||||
"confidence_version": self.VERSION,
|
||||
}
|
||||
|
||||
def batch_calc(self, tags: list[dict[str, Any]]) -> list[float]:
|
||||
"""批量计算基础分数。"""
|
||||
return [self.calc(**tag) for tag in tags]
|
||||
|
||||
def batch_evaluate(self, items: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||
"""批量生成完整评估结果。"""
|
||||
return [self.evaluate(**item) for item in items]
|
||||
|
||||
@classmethod
|
||||
def _validate(
|
||||
cls,
|
||||
tag: str,
|
||||
source: str,
|
||||
evidence_count: int,
|
||||
conflict_count: int,
|
||||
age_days: int,
|
||||
) -> None:
|
||||
"""校验工具输入,避免非法计数污染记忆分数。"""
|
||||
if not tag or not tag.strip():
|
||||
raise ValueError("tag 不能为空")
|
||||
if source not in cls.SOURCE_INITIAL:
|
||||
raise ValueError(f"不支持的客服对话来源: {source}")
|
||||
for name, value in (
|
||||
("evidence_count", evidence_count),
|
||||
("conflict_count", conflict_count),
|
||||
("age_days", age_days),
|
||||
):
|
||||
if not isinstance(value, int) or isinstance(value, bool) or value < 0:
|
||||
raise ValueError(f"{name} 必须是非负整数")
|
||||
|
||||
@staticmethod
|
||||
def _reason(source: str, evidence_count: int, conflict_count: int, age_days: int) -> str:
|
||||
"""生成便于审计和排查的评分原因。"""
|
||||
return (
|
||||
f"来源={source}; 支持证据={evidence_count}; 冲突证据={conflict_count}; "
|
||||
f"存在天数={age_days}; 采用证据增益、冲突惩罚和时间衰减"
|
||||
)
|
||||
|
||||
|
||||
__all__ = ["BaseConfidenceCalcTool"]
|
||||
@@ -0,0 +1,99 @@
|
||||
"""客服记忆候选的综合置信分重排工具。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from copy import deepcopy
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
|
||||
class FinalConfidenceRankTool:
|
||||
"""仅服务客服记忆召回的临时重排工具。"""
|
||||
|
||||
WEIGHTS = {
|
||||
"semantic": 0.30,
|
||||
"timeliness": 0.20,
|
||||
"accuracy": 0.20,
|
||||
"base": 0.25,
|
||||
"conflict": 0.05,
|
||||
}
|
||||
INVALID_STATUSES = frozenset({"expired", "rejected", "archived"})
|
||||
|
||||
def rank(
|
||||
self,
|
||||
memory_units: list[dict[str, Any]],
|
||||
*,
|
||||
top_k: int | None = None,
|
||||
now: datetime | None = None,
|
||||
) -> list[dict[str, Any]]:
|
||||
"""过滤无效候选并按当前客服召回分数降序返回副本。"""
|
||||
if top_k is not None and (not isinstance(top_k, int) or top_k < 0):
|
||||
raise ValueError("top_k 必须是非负整数或 None")
|
||||
now = now or datetime.now()
|
||||
ranked = []
|
||||
for original in memory_units:
|
||||
unit = self._as_dict(original)
|
||||
if self._is_invalid(unit, now):
|
||||
continue
|
||||
semantic = self._bounded(unit.get("semantic_similarity", 0.5), 0.5)
|
||||
timeliness = self._calc_timeliness(unit.get("age_days", 0))
|
||||
accuracy = self._bounded(unit.get("historical_accuracy", 0.5), 0.5)
|
||||
base = self._bounded(unit.get("confidence", 0.5), 0.5)
|
||||
conflict_penalty = min(self._non_negative_int(unit.get("conflict_count", 0)), 5) * 0.1
|
||||
final_score = (
|
||||
self.WEIGHTS["semantic"] * semantic
|
||||
+ self.WEIGHTS["timeliness"] * timeliness
|
||||
+ self.WEIGHTS["accuracy"] * accuracy
|
||||
+ self.WEIGHTS["base"] * base
|
||||
- self.WEIGHTS["conflict"] * conflict_penalty
|
||||
)
|
||||
unit["final_score"] = max(0.0, min(1.0, final_score))
|
||||
ranked.append(unit)
|
||||
ranked.sort(key=lambda item: item["final_score"], reverse=True)
|
||||
return ranked if top_k is None else ranked[:top_k]
|
||||
|
||||
@staticmethod
|
||||
def _as_dict(unit: dict[str, Any] | Any) -> dict[str, Any]:
|
||||
"""兼容字典和 Pydantic/ORM 风格候选对象。"""
|
||||
if isinstance(unit, dict):
|
||||
return deepcopy(unit)
|
||||
if hasattr(unit, "model_dump"):
|
||||
return deepcopy(unit.model_dump())
|
||||
return deepcopy(vars(unit))
|
||||
|
||||
@classmethod
|
||||
def _is_invalid(cls, unit: dict[str, Any], now: datetime) -> bool:
|
||||
"""过滤拒绝、归档和已过有效期的记忆。"""
|
||||
if unit.get("status") in cls.INVALID_STATUSES:
|
||||
return True
|
||||
valid_until = unit.get("valid_until")
|
||||
return valid_until is not None and valid_until <= now
|
||||
|
||||
@staticmethod
|
||||
def _bounded(value: Any, default: float) -> float:
|
||||
"""将缺失或异常评分转换为保守默认值。"""
|
||||
try:
|
||||
value = float(value)
|
||||
except (TypeError, ValueError):
|
||||
return default
|
||||
return max(0.0, min(1.0, value))
|
||||
|
||||
@staticmethod
|
||||
def _non_negative_int(value: Any) -> int:
|
||||
"""将冲突次数转换为非负整数。"""
|
||||
try:
|
||||
return max(0, int(value))
|
||||
except (TypeError, ValueError):
|
||||
return 0
|
||||
|
||||
@staticmethod
|
||||
def _calc_timeliness(age_days: Any) -> float:
|
||||
"""按每年 20% 计算平滑时效分,最低保留 0.8。"""
|
||||
try:
|
||||
age_days = max(0, int(age_days))
|
||||
except (TypeError, ValueError):
|
||||
age_days = 0
|
||||
return max(0.80, 1 - age_days / 365 * 0.20)
|
||||
|
||||
|
||||
__all__ = ["FinalConfidenceRankTool"]
|
||||
Reference in New Issue
Block a user