base_agent/uas/action/action_model.py

254 lines
10 KiB
Python
Raw Normal View History

2026-06-18 03:28:14 +00:00
# -*- coding: utf-8 -*-
"""
Action 数据模型 & 状态机
Action 核心行为对象携带类型/参数/处理函数
ActionResult 执行结果含产生的 Event 列表
ActionState 状态机枚举
"""
from __future__ import annotations
import logging
import time
import uuid
from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum
from typing import Any, Callable, Dict, List, Optional, Union
from uas.action.action_type import ActionType, ActionTypeMeta, action_registry
logger = logging.getLogger(__name__)
# ─────────────────────────────────────────────────────────────────
# Action 状态机
# ─────────────────────────────────────────────────────────────────
class ActionState(Enum):
CREATED = "created" # 已创建
QUEUED = "queued" # 已入队等待
RUNNING = "running" # 执行中
PAUSED = "paused" # 已暂停
COMPLETED = "completed" # 执行完成
FAILED = "failed" # 执行失败
CANCELLED = "cancelled" # 已取消
TIMEOUT = "timeout" # 执行超时
INTERRUPTED = "interrupted" # 被中断
# ─────────────────────────────────────────────────────────────────
# Action 执行结果
# ─────────────────────────────────────────────────────────────────
@dataclass
class ActionResult:
"""
Action 执行结果
核心特性携带处理函数产生的 Event 列表
"""
action_id: str
action_type: str
success: bool
data: Any = None # 处理函数返回的业务数据
error: str = ""
elapsed_ms: float = 0.0
events: List[Any] = field(default_factory=list) # 产生的 Event 对象列表
@property
def event_count(self) -> int:
return len(self.events)
def __repr__(self):
status = "" if self.success else ""
return (f"ActionResult({status} {self.action_type} "
f"{self.elapsed_ms:.1f}ms events={self.event_count})")
# ─────────────────────────────────────────────────────────────────
# 核心 Action 对象
# ─────────────────────────────────────────────────────────────────
@dataclass
class Action:
"""
核心 Action 对象
字段说明
action_type 行为类型ActionType 枚举 自定义字符串
handler 处理函数 fn(action, context) -> ActionResult
处理函数内部可产生一个或多个 Event 对象
params 行为参数任意字典
source 触发来源设备ID/模块名
priority 执行优先级数值越小越先
"""
action_type: Union[ActionType, str]
handler: Optional[Callable] = None
params: Dict[str, Any] = field(default_factory=dict)
source: str = "unknown"
priority: int = 0
description: str = ""
# 自动填充
action_id: str = field(default_factory=lambda: str(uuid.uuid4()))
created_at: float = field(default_factory=time.time)
state: ActionState = field(default=ActionState.CREATED)
# 运行时字段
result: Optional[ActionResult] = field(default=None, repr=False)
_start_time: Optional[float] = field(default=None, repr=False)
_end_time: Optional[float] = field(default=None, repr=False)
_cancel_flag: bool = field(default=False, repr=False)
_pause_flag: bool = field(default=False, repr=False)
def __post_init__(self):
# 若未设置 description从注册表读取
if not self.description:
meta = action_registry.get(self.type_key)
if meta:
self.description = meta.description
# ── 属性 ──────────────────────────────────────────────────────
@property
def type_key(self) -> str:
return (self.action_type.value
if isinstance(self.action_type, ActionType)
else str(self.action_type))
@property
def meta(self) -> Optional[ActionTypeMeta]:
return action_registry.get(self.type_key)
@property
def elapsed_ms(self) -> float:
if self._start_time and self._end_time:
return (self._end_time - self._start_time) * 1000
return 0.0
@property
def is_running(self) -> bool:
return self.state == ActionState.RUNNING
@property
def is_done(self) -> bool:
return self.state in (
ActionState.COMPLETED, ActionState.FAILED,
ActionState.CANCELLED, ActionState.TIMEOUT,
ActionState.INTERRUPTED,
)
@property
def created_datetime(self) -> str:
return datetime.fromtimestamp(self.created_at).strftime("%H:%M:%S.%f")[:-3]
# ── 状态操作 ──────────────────────────────────────────────────
def mark_queued(self):
self.state = ActionState.QUEUED
def mark_running(self):
self.state = ActionState.RUNNING
self._start_time = time.time()
def mark_completed(self, result: ActionResult):
self.state = ActionState.COMPLETED
self.result = result
self._end_time = time.time()
def mark_failed(self, error: str):
self.state = ActionState.FAILED
self._end_time = time.time()
self.result = ActionResult(
action_id = self.action_id,
action_type = self.type_key,
success = False,
error = error,
elapsed_ms = self.elapsed_ms,
)
def mark_cancelled(self):
self._cancel_flag = True
self.state = ActionState.CANCELLED
self._end_time = time.time()
def mark_timeout(self):
self.state = ActionState.TIMEOUT
self._end_time = time.time()
def request_cancel(self) -> bool:
"""请求取消(仅 interruptible 类型可取消)"""
meta = self.meta
if meta and not meta.interruptible:
logger.warning(f"Action {self.type_key} 不可中断,取消请求被拒绝")
return False
self._cancel_flag = True
return True
def request_pause(self):
self._pause_flag = True
def request_resume(self):
self._pause_flag = False
if self.state == ActionState.PAUSED:
self.state = ActionState.RUNNING
@property
def cancel_requested(self) -> bool:
return self._cancel_flag
@property
def pause_requested(self) -> bool:
return self._pause_flag
# ── 序列化 ────────────────────────────────────────────────────
def to_dict(self) -> dict:
return {
"action_id": self.action_id,
"action_type": self.type_key,
"source": self.source,
"description": self.description,
"params": self.params,
"priority": self.priority,
"state": self.state.value,
"created_at": self.created_datetime,
"elapsed_ms": self.elapsed_ms,
"result": str(self.result) if self.result else None,
}
def __repr__(self):
return (f"Action(id={self.action_id[:8]}, type={self.type_key!r}, "
f"src={self.source!r}, state={self.state.value})")
# ─────────────────────────────────────────────────────────────────
# 便捷工厂函数
# ─────────────────────────────────────────────────────────────────
def make_action(
action_type: Union[ActionType, str],
handler: Optional[Callable] = None,
source: str = "system",
priority: int = 0,
**params
) -> Action:
"""
快速创建 Action
示例
a = make_action(ActionType.CAPTURE_PHOTO,
handler=photo_handler,
source="camera-01",
resolution="4K", format="jpg")
"""
return Action(
action_type = action_type,
handler = handler,
source = source,
priority = priority,
params = params,
)