完善会话报告与消息撤回控制
新增 AI 案例推理方案报告字段,支持方案追加到当前报告;增加会话消息成对撤回接口并记录审计日志;扩展流式响应消息标识,支持前端停止生成与撤回处理。
This commit is contained in:
parent
5c7163d79a
commit
9e13c95ba8
@ -74,6 +74,22 @@ async def send_conversation_message(
|
||||
)
|
||||
|
||||
|
||||
@router.delete("/{conversation_id}/messages/{message_id}", response_model=ApiResponse[ConversationData])
|
||||
async def withdraw_conversation_message(
|
||||
conversation_id: int,
|
||||
message_id: int,
|
||||
session: DatabaseSession,
|
||||
current_user: Annotated[UserAccount, Depends(require_permission("risk.assess", operate=True))],
|
||||
) -> ApiResponse[ConversationData]:
|
||||
return ApiResponse(
|
||||
data=await ConversationService(session).withdraw_turn(
|
||||
conversation_id,
|
||||
message_id,
|
||||
current_user,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@router.post("/{conversation_id}/stream")
|
||||
async def stream_conversation_message(
|
||||
conversation_id: int,
|
||||
|
||||
@ -85,6 +85,24 @@ class SimilarCase(ApiSchema):
|
||||
similarity: float | None = None
|
||||
|
||||
|
||||
class AiSolutionRisk(ApiSchema):
|
||||
level: str
|
||||
title: str
|
||||
detail: str
|
||||
|
||||
|
||||
class AiSolutionReport(ApiSchema):
|
||||
title: str = "AI 案例推理生成"
|
||||
confidence: str | None = None
|
||||
analysis_base: str | None = None
|
||||
standard_fallback_enabled: bool = False
|
||||
standards: list[StandardJudgment] = Field(default_factory=list)
|
||||
similar_cases: list[SimilarCase] = Field(default_factory=list)
|
||||
risks: list[AiSolutionRisk] = Field(default_factory=list)
|
||||
suggestions: list[str] = Field(default_factory=list)
|
||||
warning: str | None = None
|
||||
|
||||
|
||||
class RiskAssessmentResult(ApiSchema):
|
||||
subject_id: str
|
||||
score: float
|
||||
@ -99,6 +117,7 @@ class RiskAssessmentResult(ApiSchema):
|
||||
dimension_scores: list[RiskDimensionScore] = Field(default_factory=list)
|
||||
similar_cases: list[SimilarCase] = Field(default_factory=list)
|
||||
ai_suggestions: list[str] = Field(default_factory=list)
|
||||
ai_solution_cards: list[AiSolutionReport] = Field(default_factory=list)
|
||||
|
||||
|
||||
class ModelInfo(ApiSchema):
|
||||
|
||||
@ -7,11 +7,18 @@ from collections.abc import AsyncGenerator, Sequence
|
||||
from datetime import UTC, date, datetime, timedelta
|
||||
from typing import Any, Literal
|
||||
|
||||
from sqlalchemy import func, or_, select
|
||||
from sqlalchemy import delete, func, or_, select
|
||||
|
||||
from app.core.exceptions import AppException
|
||||
from app.domain.risk_engine import BaselineRiskEngine, IndicatorValue, RiskLevel
|
||||
from app.models.entities import AnomalyCase, Conversation, ConversationMessage, StandardRule, UserAccount
|
||||
from app.models.entities import (
|
||||
AnomalyCase,
|
||||
Conversation,
|
||||
ConversationMessage,
|
||||
OperationAudit,
|
||||
StandardRule,
|
||||
UserAccount,
|
||||
)
|
||||
from app.repositories.conversations import ConversationRepository
|
||||
from app.schemas.conversation import (
|
||||
ConversationData,
|
||||
@ -202,7 +209,10 @@ class ConversationService:
|
||||
await self.session.flush()
|
||||
await self.session.commit()
|
||||
|
||||
yield {"type": "start", "data": {"conversationId": conversation.id}}
|
||||
yield {
|
||||
"type": "start",
|
||||
"data": {"conversationId": conversation.id, "messageId": user_message.id},
|
||||
}
|
||||
try:
|
||||
semantics = await self.llm.extract_semantics(
|
||||
text,
|
||||
@ -567,6 +577,7 @@ class ConversationService:
|
||||
if hasattr(self, "session")
|
||||
else None
|
||||
)
|
||||
|
||||
has_order_data = bool(cooperation_count)
|
||||
cooperation_display = cooperation_count if has_order_data else None
|
||||
problems = await self._top_anomaly_types(
|
||||
@ -614,6 +625,46 @@ class ConversationService:
|
||||
"dataAvailable": has_order_data,
|
||||
}
|
||||
|
||||
async def withdraw_turn(
|
||||
self,
|
||||
conversation_id: int,
|
||||
message_id: int,
|
||||
user: UserAccount,
|
||||
) -> ConversationData:
|
||||
"""Delete one user turn and its immediately paired assistant reply."""
|
||||
|
||||
conversation = await self.repository.get_for_user(conversation_id, user.id)
|
||||
if conversation is None:
|
||||
raise AppException("对话不存在或无权访问", code=404, status_code=404)
|
||||
message = next((item for item in conversation.messages if item.id == message_id), None)
|
||||
if message is None or message.role != "user":
|
||||
raise AppException("只能撤回用户消息", code=422, status_code=422)
|
||||
messages = sorted(conversation.messages, key=lambda item: (item.created_at, item.id))
|
||||
index = messages.index(message)
|
||||
ids = [message.id]
|
||||
if index + 1 < len(messages) and messages[index + 1].role == "assistant":
|
||||
ids.append(messages[index + 1].id)
|
||||
await self.session.execute(
|
||||
delete(ConversationMessage).where(ConversationMessage.id.in_(ids))
|
||||
)
|
||||
conversation.updated_at = datetime.now(UTC)
|
||||
self.session.add(
|
||||
OperationAudit(
|
||||
actor_user_id=user.id,
|
||||
actor_emp_no=user.emp_no,
|
||||
action="conversation.withdraw",
|
||||
resource_type="conversation_message",
|
||||
resource_id=str(message_id),
|
||||
audit_metadata={"deleted_message_ids": ids},
|
||||
)
|
||||
)
|
||||
await self.session.flush()
|
||||
page = await self.repository.get_page_for_user(conversation_id, user.id, limit=20, offset=0)
|
||||
if page is None:
|
||||
raise AppException("对话读取失败", code=500, status_code=500)
|
||||
persisted, page_messages, total = page
|
||||
return self._data(persisted, messages=page_messages, total=total, offset=0)
|
||||
|
||||
async def _material_card(self, text: str, semantics: dict[str, Any]) -> dict[str, Any]:
|
||||
material = self._first_match(text, ("软管", "泵头", "瓶体", "瓶子", "彩盒", "铝瓶", "喷头", "PET"))
|
||||
material = material or self._semantic_text(semantics, "packaging") or "未指定物料"
|
||||
@ -790,7 +841,10 @@ class ConversationService:
|
||||
),
|
||||
"standardFallbackEnabled": standard_fallback_enabled,
|
||||
"standards": [self._standard_dict(item) for item in standards[:4]],
|
||||
"similarCases": [item.case.model_dump(mode="json", by_alias=False) for item in similar_cases],
|
||||
"similarCases": [
|
||||
item.case.model_dump(mode="json", by_alias=False)
|
||||
for item in similar_cases
|
||||
],
|
||||
"risks": risks,
|
||||
"suggestions": suggestions,
|
||||
"warning": "以上方案由 AI 推理生成,实际执行前请咨询品控部门确认。",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user