合并两个不可分割的深化: Phase 2 — 智能作业结算统一(ADR-0012) - intelligence_jobs.execute(job_kind, campaign_id, ...) 作为结算的 唯一实现:建行 → 认领 → 校验 → generating → 落账,一处编排、 一处截断(500 字符)。两个 executor 退化为 ensure_queued / validate / work_fn 三个小 adapter。 - analysis.validate_analysis_request() 共享校验入口(活动终态 → 模型),路由捕获映射 400、executor 捕获落 failed 行,与 validate_comparison_request 先例同构。 - campaign_runner._auto_start_analysis 的跳过守卫收敛至 auto_intelligence_eligible 单一判断点。 - comparison.py 删除零调用的 build_comparison_payload; load_comparison_view 投影归位至 campaign_read_model。 - 新增 characterization 测试(认领竞争、重复触发、截断、恢复上限)。 Phase 3 — storage/repository.py 拆分 - AsyncJobRepository 及两个子类迁至 storage/async_job_repository.py(Phase 2 的 intelligence_jobs 与 comparison 必须 import 自该路径,故与 Phase 2 同 commit)。 - ExplorationSession / ExplorationMessage 迁至 storage/exploration_repository.py;repository.py 由 1180 行降至 约 814 行,grep 确认无残留符号。 - exploration 子模块与路由 import 全部更新;测试 import 跟随。 刻意不做:CAS 共享原语、app.py 五 registry 关停顺序归一 (ADR-0006 精神,等真实需求出现再议)。
85 lines
3.3 KiB
Python
85 lines
3.3 KiB
Python
"""Exploration findings aggregation (探索发现聚合).
|
||
|
||
把探索会话的体验记录聚合成探索摘要:会话数、目标达成率、问题清单
|
||
(blockers / misled 按出现次数降序)、judge 复核结论(已就位才纳入;
|
||
findings 全量收各档发现,poor 档排前)。这是体验记录这条第一手证据线
|
||
进入报告 / 分析 / 导出三个出口前的唯一聚合口径;只含统计与问题清单,
|
||
不含全量对话。
|
||
"""
|
||
|
||
from collections import Counter
|
||
from typing import Any, Optional
|
||
|
||
from sqlmodel import Session
|
||
|
||
from agenteval.exploration.models import ExplorationSession
|
||
|
||
_RATING_SEVERITY = {"poor": 0, "acceptable": 1, "good": 2}
|
||
|
||
|
||
def _issue_list(counter: Counter) -> list[dict[str, Any]]:
|
||
return [{"issue": issue, "count": count} for issue, count in counter.most_common()]
|
||
|
||
|
||
def summarize_exploration(sessions: list[ExplorationSession]) -> Optional[dict[str, Any]]:
|
||
"""聚合一组探索会话为探索摘要;无会话返回 None(缺则无痕)。"""
|
||
if not sessions:
|
||
return None
|
||
|
||
with_experience = [s for s in sessions if s.experience is not None]
|
||
blockers: Counter = Counter()
|
||
misled: Counter = Counter()
|
||
achieved = 0
|
||
for session_obj in with_experience:
|
||
experience = session_obj.experience
|
||
if experience.get("goal_achieved"):
|
||
achieved += 1
|
||
blockers.update(experience.get("blockers") or [])
|
||
misled.update(experience.get("misled") or [])
|
||
|
||
judge = _summarize_judge_reviews(sessions)
|
||
|
||
return {
|
||
"session_count": len(sessions),
|
||
"sessions_with_experience": len(with_experience),
|
||
"goal_achieved_count": achieved,
|
||
"goal_achievement_rate": round(achieved / len(with_experience), 4) if with_experience else None,
|
||
"issues": _issue_list(blockers),
|
||
"misled": _issue_list(misled),
|
||
"judge_review": judge,
|
||
}
|
||
|
||
|
||
def summarize_campaign_exploration(db_session: Session, campaign_id: str) -> Optional[dict[str, Any]]:
|
||
"""取数 + 聚合一步完成:报告 / 分析 / 导出三个出口共用的探索摘要取法。"""
|
||
from agenteval.storage.exploration_repository import ExplorationSessionRepository
|
||
|
||
return summarize_exploration(ExplorationSessionRepository(db_session).list_by_campaign(campaign_id))
|
||
|
||
|
||
def _summarize_judge_reviews(sessions: list[ExplorationSession]) -> Optional[dict[str, Any]]:
|
||
"""只纳入复核完成的会话;findings 全量收各档发现(poor 档排前),
|
||
summaries 收复核总体结论。"""
|
||
findings: list[dict[str, Any]] = []
|
||
summaries: list[str] = []
|
||
reviewed = 0
|
||
for session_obj in sessions:
|
||
review = session_obj.judge_review
|
||
if not review or review.get("status") != "completed":
|
||
continue
|
||
reviewed += 1
|
||
if review.get("summary"):
|
||
summaries.append(str(review["summary"]))
|
||
for item in review.get("dimensions") or []:
|
||
findings.append(
|
||
{
|
||
"dimension": item.get("dimension"),
|
||
"rating": item.get("rating"),
|
||
"comment": item.get("comment") or "",
|
||
}
|
||
)
|
||
if reviewed == 0:
|
||
return None
|
||
findings.sort(key=lambda f: _RATING_SEVERITY.get(f["rating"], len(_RATING_SEVERITY)))
|
||
return {"reviewed_sessions": reviewed, "findings": findings, "summaries": summaries}
|