from __future__ import annotations import json from uuid import uuid4 from sqlalchemy.orm import Session from app.core.time import format_now from app.models.chat_message import ChatMessage from app.models.chat_session import ChatSession from app.repositories.chat_repository import ChatMessageRepository, ChatSessionRepository from app.schemas.chat import ChatMessageItem from app.schemas.task import ConfirmTaskRequest, CreateTaskRequest, ParsedIntent from app.services.task_service import TaskService class ChatSessionNotFoundError(Exception): pass class ChatService: SAMPLE_PROMPTS = [ "deploy order-service 1.2.3 to test", "deploy payment-service 1.2.3 to test", "deploy order-service 1.2.3 to prod", ] def __init__(self, db: Session, timezone_name: str) -> None: self.db = db self.timezone_name = timezone_name self.session_repository = ChatSessionRepository(db) self.message_repository = ChatMessageRepository(db) self.task_service = TaskService(db, timezone_name) def create_session(self, tenant_id: str, channel: str) -> ChatSession: current_time = format_now(self.timezone_name) session = ChatSession( session_id=f"chat-{uuid4().hex[:12]}", tenant_id=tenant_id, channel=channel, title="Agent Demo Session", last_task_id=None, context_json=json.dumps({}, ensure_ascii=False), created_at=current_time, updated_at=current_time, ) created_session = self.session_repository.add(session) self._add_message( session_id=created_session.session_id, role="assistant", content="请输入一句自然语言,例如:deploy order-service 1.2.3 to test", message_type="welcome", task_id=None, ) return created_session def get_session(self, session_id: str) -> ChatSession: session = self.session_repository.get_by_session_id(session_id) if not session: raise ChatSessionNotFoundError() return session def list_messages(self, session_id: str) -> list[ChatMessage]: self.get_session(session_id) return self.message_repository.list_by_session_id(session_id) def handle_user_message(self, session_id: str, content: str, context: dict | None = None) -> tuple[ChatSession, ChatMessage, dict]: session = self.get_session(session_id) self._add_message(session_id=session_id, role="user", content=content, message_type="user_input", task_id=None) request_id = f"chat-req-{uuid4().hex[:12]}" task = self.task_service.create_task( CreateTaskRequest( input_text=content, channel=session.channel, session_id=session.session_id, tenant_id=session.tenant_id, context=context or {}, ), request_id=request_id, ) session.last_task_id = task.task_id session.updated_at = format_now(self.timezone_name) self.session_repository.update(session) parsed_intent = json.loads(task.parsed_intent_json) missing_slots = json.loads(task.missing_slots_json) next_action = "CONFIRM_TASK" if not missing_slots else "FILL_MISSING_SLOTS" assistant_text = self._build_parse_reply(parsed_intent, missing_slots, task.risk_level, next_action) assistant_message = self._add_message( session_id=session_id, role="assistant", content=assistant_text, message_type="task_parse", task_id=task.task_id, ) return session, assistant_message, { "task_id": task.task_id, "task_status": task.task_status, "parsed_intent": ParsedIntent(**parsed_intent), "missing_slots": missing_slots, "risk_level": task.risk_level, "next_action": next_action, } def confirm_task(self, session_id: str, task_id: str, comment: str | None) -> tuple[ChatSession, ChatMessage, dict]: session = self.get_session(session_id) task, approval_id = self.task_service.confirm_task( task_id, ConfirmTaskRequest(confirmed=True, comment=comment), request_id=f"chat-confirm-{uuid4().hex[:12]}", ) session.last_task_id = task.task_id session.updated_at = format_now(self.timezone_name) self.session_repository.update(session) assistant_text = self._build_confirm_reply(task.task_status, task.approval_status, task.software_a_task_status, approval_id) assistant_message = self._add_message( session_id=session_id, role="assistant", content=assistant_text, message_type="task_confirm", task_id=task.task_id, ) return session, assistant_message, { "task_id": task.task_id, "task_status": task.task_status, "approval_status": task.approval_status, } def _build_parse_reply(self, parsed_intent: dict, missing_slots: list[str], risk_level: str, next_action: str) -> str: if missing_slots: return f"我已解析任务,但还缺少字段:{', '.join(missing_slots)}。请补充后再继续。" return ( "我已解析任务:" f"动作={parsed_intent.get('action_type')}," f"应用={parsed_intent.get('app_code')}," f"环境={parsed_intent.get('env')}," f"版本={parsed_intent.get('version')}。" f" 风险等级={risk_level},下一步={next_action}。" ) def _build_confirm_reply(self, task_status: str, approval_status: str, software_a_task_status: str | None, approval_id: str | None) -> str: if approval_status == "PENDING" and approval_id: return f"任务已确认,当前进入审批阶段。approval_id={approval_id}" return f"任务已确认并进入执行。task_status={task_status},software_a_task_status={software_a_task_status}" def _add_message(self, session_id: str, role: str, content: str, message_type: str, task_id: str | None) -> ChatMessage: message = ChatMessage( message_id=f"msg-{uuid4().hex[:12]}", session_id=session_id, role=role, content=content, message_type=message_type, task_id=task_id, created_at=format_now(self.timezone_name), ) return self.message_repository.add(message) @staticmethod def to_message_item(message: ChatMessage) -> ChatMessageItem: return ChatMessageItem( message_id=message.message_id, role=message.role, content=message.content, message_type=message.message_type, task_id=message.task_id, created_at=message.created_at, )