- 新增 app_metadata 模型、仓储与服务 - 将默认 edge 验证步骤改为由 app_metadata 驱动生成 - 新增 chat_session / chat_message 会话层模型与 chat service - 新增 demo chat API,支持会话创建、消息发送、任务确认 - 新增最小 Web Demo 页面,形成聊天式演示入口 - 增强任务报告,补充 audit_summary 与更细粒度 task_metrics - 增强 edge-agent 执行器:tcp_probe、日志时间范围过滤、进程指标与更灵活健康检查 - 更新 README 与当前进度总结,MVP 进度推进到约 94%
169 lines
6.9 KiB
Python
169 lines
6.9 KiB
Python
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,
|
||
)
|