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

156 lines
5.9 KiB
Python

from __future__ import annotations
from typing import Annotated
from uuid import uuid4
from fastapi import APIRouter, Depends, Header, HTTPException, status
from sqlalchemy.orm import Session
from app.core.config import get_settings
from app.core.constants import ERROR_CODE_OK
from app.core.time import format_now
from app.db.session import get_db
from app.schemas.chat import (
ChatConfirmTaskData,
ChatConfirmTaskRequest,
ChatSendMessageData,
ChatSendMessageRequest,
ChatSessionCreateRequest,
ChatSessionData,
)
from app.schemas.common import ApiResponse
from app.services.chat_service import ChatService, ChatSessionNotFoundError
router = APIRouter(prefix="/api/demo/chat", tags=["demo-chat"])
def build_request_id(header_value: str | None) -> str:
return header_value or f"req-{uuid4().hex[:12]}"
@router.post("/sessions", response_model=ApiResponse[ChatSessionData])
def create_session(
payload: ChatSessionCreateRequest,
db: Annotated[Session, Depends(get_db)],
x_request_id: Annotated[str | None, Header(alias="X-Request-Id")] = None,
) -> ApiResponse[ChatSessionData]:
settings = get_settings()
request_id = build_request_id(x_request_id)
service = ChatService(db, settings.default_timezone)
session = service.create_session(payload.tenant_id, payload.channel)
messages = [service.to_message_item(item) for item in service.list_messages(session.session_id)]
return ApiResponse[ChatSessionData](
request_id=request_id,
success=True,
code=ERROR_CODE_OK,
message="success",
data=ChatSessionData(
session_id=session.session_id,
tenant_id=session.tenant_id,
channel=session.channel,
title=session.title,
last_task_id=session.last_task_id,
sample_prompts=ChatService.SAMPLE_PROMPTS,
messages=messages,
),
timestamp=format_now(settings.default_timezone),
)
@router.get("/sessions/{session_id}", response_model=ApiResponse[ChatSessionData])
def get_session(
session_id: str,
db: Annotated[Session, Depends(get_db)],
x_request_id: Annotated[str | None, Header(alias="X-Request-Id")] = None,
) -> ApiResponse[ChatSessionData]:
settings = get_settings()
request_id = build_request_id(x_request_id)
service = ChatService(db, settings.default_timezone)
try:
session = service.get_session(session_id)
except ChatSessionNotFoundError as exc:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail={"code": "NOT_FOUND", "message": "chat session not found"}) from exc
messages = [service.to_message_item(item) for item in service.list_messages(session.session_id)]
return ApiResponse[ChatSessionData](
request_id=request_id,
success=True,
code=ERROR_CODE_OK,
message="success",
data=ChatSessionData(
session_id=session.session_id,
tenant_id=session.tenant_id,
channel=session.channel,
title=session.title,
last_task_id=session.last_task_id,
sample_prompts=ChatService.SAMPLE_PROMPTS,
messages=messages,
),
timestamp=format_now(settings.default_timezone),
)
@router.post("/sessions/{session_id}/messages", response_model=ApiResponse[ChatSendMessageData])
def send_message(
session_id: str,
payload: ChatSendMessageRequest,
db: Annotated[Session, Depends(get_db)],
x_request_id: Annotated[str | None, Header(alias="X-Request-Id")] = None,
) -> ApiResponse[ChatSendMessageData]:
settings = get_settings()
request_id = build_request_id(x_request_id)
service = ChatService(db, settings.default_timezone)
try:
_, assistant_message, task_data = service.handle_user_message(session_id, payload.content, payload.context)
except ChatSessionNotFoundError as exc:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail={"code": "NOT_FOUND", "message": "chat session not found"}) from exc
return ApiResponse[ChatSendMessageData](
request_id=request_id,
success=True,
code=ERROR_CODE_OK,
message="success",
data=ChatSendMessageData(
session_id=session_id,
task_id=task_data["task_id"],
task_status=task_data["task_status"],
parsed_intent=task_data["parsed_intent"],
missing_slots=task_data["missing_slots"],
risk_level=task_data["risk_level"],
next_action=task_data["next_action"],
assistant_message=service.to_message_item(assistant_message),
),
timestamp=format_now(settings.default_timezone),
)
@router.post("/sessions/{session_id}/tasks/{task_id}/confirm", response_model=ApiResponse[ChatConfirmTaskData])
def confirm_task_from_chat(
session_id: str,
task_id: str,
payload: ChatConfirmTaskRequest,
db: Annotated[Session, Depends(get_db)],
x_request_id: Annotated[str | None, Header(alias="X-Request-Id")] = None,
) -> ApiResponse[ChatConfirmTaskData]:
settings = get_settings()
request_id = build_request_id(x_request_id)
service = ChatService(db, settings.default_timezone)
try:
_, assistant_message, task_data = service.confirm_task(session_id, task_id, payload.comment)
except ChatSessionNotFoundError as exc:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail={"code": "NOT_FOUND", "message": "chat session not found"}) from exc
return ApiResponse[ChatConfirmTaskData](
request_id=request_id,
success=True,
code=ERROR_CODE_OK,
message="success",
data=ChatConfirmTaskData(
session_id=session_id,
task_id=task_data["task_id"],
task_status=task_data["task_status"],
approval_status=task_data["approval_status"],
assistant_message=service.to_message_item(assistant_message),
),
timestamp=format_now(settings.default_timezone),
)