auto_agent/backend/app/services/chat_service.py
2521690 ce299cbb18 feat: 增加 Agent 演示入口与 app_metadata 驱动验证链路
- 新增 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%
2026-04-09 14:10:13 +08:00

169 lines
6.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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,
)