feat: init commint
This commit is contained in:
@@ -18,25 +18,16 @@ from apps.knowledge.embeddings import KnowledgeEmbeddingError
|
||||
from apps.knowledge.models import KnowledgeItem
|
||||
from apps.knowledge.services import retrieve_knowledge
|
||||
from apps.solutions.models import Solution
|
||||
from apps.tickets.models import Ticket, TicketActorSource, TicketSupportAnalysis
|
||||
from apps.tickets.models import Ticket, TicketActorSource, TicketStatus, TicketSupportAnalysis
|
||||
from apps.tickets.replies import ensure_customer_reply_task
|
||||
from apps.tickets.services import (
|
||||
TicketDataError,
|
||||
TicketStatusError,
|
||||
adopt_solution_for_ticket,
|
||||
get_allowed_transitions,
|
||||
from apps.tickets.services import get_allowed_transitions
|
||||
from apps.tickets.support_contract import (
|
||||
SUPPORT_OUTPUT_FIELDS,
|
||||
missing_support_output_fields,
|
||||
)
|
||||
|
||||
SUPPORT_AGENT_CODE = "support-analysis"
|
||||
RETRIEVAL_POLICY_VERSION = "published_scope_hybrid_rerank_v2"
|
||||
SUPPORT_OUTPUT_FIELDS = {
|
||||
"analysis_summary",
|
||||
"troubleshooting_steps",
|
||||
"candidate_solution_ids",
|
||||
"customer_reply_draft",
|
||||
"citations",
|
||||
"auto_resolve_recommended",
|
||||
}
|
||||
|
||||
|
||||
class SupportAnalysisError(ValueError):
|
||||
@@ -326,6 +317,10 @@ def retrieve_support_references(ticket: Ticket, *, limit: int = 8) -> tuple[str,
|
||||
}
|
||||
)
|
||||
|
||||
minimum_score = min(max(float(settings.SUPPORT_REFERENCE_MIN_SCORE), 0.0), 1.0)
|
||||
candidates = [
|
||||
item for item in candidates if float(item.get("score", 0)) >= minimum_score
|
||||
]
|
||||
candidates.sort(key=lambda item: (item["score"], item["source_version"]), reverse=True)
|
||||
references = []
|
||||
for rank, item in enumerate(candidates[:limit], start=1):
|
||||
@@ -343,8 +338,7 @@ def validate_support_agent_contract(version: AgentVersion) -> None:
|
||||
output_schema = last_step.skill_version.output_schema
|
||||
if not isinstance(output_schema, dict) or output_schema.get("type") != "object":
|
||||
raise SupportAnalysisError("支持分析 Agent 的最终输出必须是对象")
|
||||
required = set(output_schema.get("required", []))
|
||||
missing = sorted(SUPPORT_OUTPUT_FIELDS - required)
|
||||
missing = missing_support_output_fields(output_schema)
|
||||
if missing:
|
||||
raise SupportAnalysisError(
|
||||
f"支持分析 Agent 最终输出契约缺少字段:{', '.join(missing)}"
|
||||
@@ -463,10 +457,24 @@ def _automation_decision(
|
||||
and not solution.requires_production_change
|
||||
),
|
||||
"ticket_not_archived": ticket.archived_at is None,
|
||||
"status_transition_allowed": "resolved"
|
||||
in get_allowed_transitions(
|
||||
ticket.status,
|
||||
ticket.category.code if ticket.category_id else None,
|
||||
"status_transition_allowed": (
|
||||
"resolved"
|
||||
in get_allowed_transitions(
|
||||
ticket.status,
|
||||
ticket.category.code if ticket.category_id else None,
|
||||
)
|
||||
or (
|
||||
"pending_confirm"
|
||||
in get_allowed_transitions(
|
||||
ticket.status,
|
||||
ticket.category.code if ticket.category_id else None,
|
||||
)
|
||||
and "resolved"
|
||||
in get_allowed_transitions(
|
||||
TicketStatus.PENDING_CONFIRM,
|
||||
ticket.category.code if ticket.category_id else None,
|
||||
)
|
||||
)
|
||||
),
|
||||
"no_conflicting_resolution": existing_resolution is None
|
||||
or existing_resolution.linked_solution_id in {None, solution.id if solution else None},
|
||||
@@ -490,6 +498,7 @@ def _automation_decision(
|
||||
decision = {
|
||||
"policy_version": "support_auto_resolve_v1",
|
||||
"status": "eligible" if not reasons else "manual_recommendation",
|
||||
"eligible": not reasons,
|
||||
"threshold": threshold,
|
||||
"solution_id": solution.id if solution else None,
|
||||
"score": round(float(reference.get("score", 0)), 4) if reference else 0.0,
|
||||
@@ -520,32 +529,22 @@ def process_support_analysis_result(run: AgentRun) -> TicketSupportAnalysis | No
|
||||
if analysis.processed_at is not None:
|
||||
return analysis
|
||||
output = run.final_output if isinstance(run.final_output, dict) else {}
|
||||
ensure_customer_reply_task(
|
||||
reply_task = ensure_customer_reply_task(
|
||||
analysis,
|
||||
str(output.get("customer_reply_draft") or ""),
|
||||
)
|
||||
if reply_task is None:
|
||||
raise SupportAnalysisError("支持分析未生成可发送的客户回复")
|
||||
decision, solution = _automation_decision(analysis, output)
|
||||
analysis.result_mode = "trusted" if analysis.references else "manual_only"
|
||||
if decision["status"] == "eligible" and solution is not None:
|
||||
try:
|
||||
adopt_solution_for_ticket(
|
||||
analysis.ticket,
|
||||
solution,
|
||||
run.created_by,
|
||||
remark="支持分析 Agent 通过统一策略自动采纳解决方案",
|
||||
source=TicketActorSource.AGENT,
|
||||
agent_run_id=run.id,
|
||||
)
|
||||
except (TicketDataError, TicketStatusError):
|
||||
decision["status"] = "manual_recommendation"
|
||||
decision["reasons"].append("domain_service_rejected")
|
||||
else:
|
||||
decision["status"] = "auto_resolved"
|
||||
decision["applied"] = True
|
||||
analysis.applied_solution = solution
|
||||
# 方案只在技术支持执行“一次批准发送”后采纳,分析阶段不提前改变工单事实。
|
||||
decision["status"] = "waiting_approval"
|
||||
analysis.automation_decision = decision
|
||||
analysis.processed_at = timezone.now()
|
||||
analysis.save(
|
||||
update_fields=[
|
||||
"result_mode",
|
||||
"automation_decision",
|
||||
"applied_solution",
|
||||
"processed_at",
|
||||
@@ -580,7 +579,12 @@ def process_support_analysis_result(run: AgentRun) -> TicketSupportAnalysis | No
|
||||
|
||||
|
||||
@transaction.atomic
|
||||
def start_ticket_support_analysis(ticket: Ticket, *, created_by=None) -> TicketSupportAnalysis:
|
||||
def start_ticket_support_analysis(
|
||||
ticket: Ticket,
|
||||
*,
|
||||
created_by=None,
|
||||
source: str = TicketActorSource.HUMAN,
|
||||
) -> TicketSupportAnalysis:
|
||||
"""为工单创建唯一活动分析并投递绑定正式 Agent 版本的运行。"""
|
||||
|
||||
ticket = (
|
||||
@@ -680,7 +684,7 @@ def start_ticket_support_analysis(ticket: Ticket, *, created_by=None) -> TicketS
|
||||
"reference_count": len(references),
|
||||
},
|
||||
operator=created_by,
|
||||
source=TicketActorSource.HUMAN,
|
||||
source=source,
|
||||
agent_run_id=run.id,
|
||||
metadata={"retrieval_policy_version": RETRIEVAL_POLICY_VERSION},
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user