Coverage for agentos/core/state_machine.py: 50%
133 statements
« prev ^ index » next coverage.py v7.14.3, created at 2026-07-06 10:59 +0800
« prev ^ index » next coverage.py v7.14.3, created at 2026-07-06 10:59 +0800
1"""
2AgentOS v0.60 State Machine — Agent 生命周期状态管理。
3状态:Idle → Thinking → Acting → Observing → (Complete|Failed|Paused)
4含转换守卫、超时检测、恢复机制。
5"""
7from __future__ import annotations
9import time
10from dataclasses import dataclass, field
11from enum import Enum
12from typing import Callable
15class AgentState(str, Enum):
17 """Agent 状态枚举。"""
19 IDLE = "idle" # 空闲,等待任务
20 INITIALIZING = "initializing" # 加载配置/工具
21 THINKING = "thinking" # 推理/规划
22 ACTING = "acting" # 执行工具/调用模型
23 OBSERVING = "observing" # 处理工具返回/反思
24 WAITING = "waiting" # 等待外部输入(HITL)
25 PAUSED = "paused" # 手动暂停
26 COMPLETED = "completed" # 任务完成
27 FAILED = "failed" # 任务失败
28 CANCELLED = "cancelled" # 被取消
29 ERROR = "error" # 系统错误
32# 合法状态转换表
33VALID_TRANSITIONS: dict[AgentState, set[AgentState]] = {
34 AgentState.IDLE: {AgentState.INITIALIZING, AgentState.CANCELLED},
35 AgentState.INITIALIZING: {AgentState.IDLE, AgentState.THINKING, AgentState.FAILED, AgentState.ERROR},
36 AgentState.THINKING: {AgentState.ACTING, AgentState.WAITING, AgentState.COMPLETED, AgentState.FAILED, AgentState.PAUSED, AgentState.ERROR},
37 AgentState.ACTING: {AgentState.OBSERVING, AgentState.FAILED, AgentState.ERROR},
38 AgentState.OBSERVING: {AgentState.THINKING, AgentState.ACTING, AgentState.COMPLETED, AgentState.FAILED, AgentState.ERROR},
39 AgentState.WAITING: {AgentState.THINKING, AgentState.ACTING, AgentState.CANCELLED, AgentState.PAUSED, AgentState.ERROR},
40 AgentState.PAUSED: {AgentState.THINKING, AgentState.ACTING, AgentState.OBSERVING, AgentState.CANCELLED, AgentState.ERROR},
41 AgentState.COMPLETED: set(), # 终态
42 AgentState.FAILED: {AgentState.IDLE, AgentState.ERROR},
43 AgentState.CANCELLED: {AgentState.IDLE, AgentState.ERROR},
44 AgentState.ERROR: {AgentState.IDLE, AgentState.FAILED},
45}
48@dataclass
49class StateTransition:
50 """状态转换事件记录。"""
52 from_state: AgentState
53 to_state: AgentState
54 timestamp: float = field(default_factory=time.time)
55 reason: str = ""
56 metadata: dict = field(default_factory=dict)
59@dataclass
60class StateMachineConfig:
61 """状态机运行时配置。"""
63 max_thinking_time: float = 300.0 # 推理超时(秒)
64 max_acting_time: float = 120.0 # 执行超时
65 max_observing_time: float = 60.0 # 观察超时
66 max_total_time: float = 3600.0 # 总超时
67 max_transitions: int = 500 # 最大状态转换次数
68 auto_recover: bool = True # 错误后自动恢复
69 max_retries_after_error: int = 3
72class TransitionError(Exception):
73 """非法状态转换异常。"""
75 def __init__(self, from_state: AgentState, to_state: AgentState):
76 super().__init__(f"Invalid transition: {from_state.value} → {to_state.value}")
77 self.from_state = from_state
78 self.to_state = to_state
81class StateTimeoutError(Exception):
82 """状态超时异常。"""
84 def __init__(self, state: AgentState, elapsed: float, limit: float):
85 super().__init__(f"{state.value} timeout: {elapsed:.1f}s > {limit:.1f}s")
86 self.state = state
87 self.elapsed = elapsed
90class AgentStateMachine:
91 """Agent有限状态机,带守卫和超时检测。"""
93 def __init__(self, config: StateMachineConfig | None = None):
94 self.config = config or StateMachineConfig()
95 self._state: AgentState = AgentState.IDLE
96 self._history: list[StateTransition] = []
97 self._state_enter_time: float = time.time()
98 self._created_at: float = time.time()
99 self._error_count: int = 0
100 self._on_transition_hooks: dict[tuple[AgentState, AgentState], list[Callable]] = {}
102 @property
103 def state(self) -> AgentState:
104 return self._state
106 @property
107 def elapsed_total(self) -> float:
108 return time.time() - self._created_at
110 @property
111 def elapsed_in_state(self) -> float:
112 return time.time() - self._state_enter_time
114 @property
115 def history(self) -> list[StateTransition]:
116 return list(self._history)
118 def _guard(self, target: AgentState) -> bool:
119 """状态转换守卫。"""
120 valid = VALID_TRANSITIONS.get(self._state, set())
121 if target not in valid:
122 raise TransitionError(self._state, target)
124 if len(self._history) >= self.config.max_transitions:
125 raise RuntimeError(f"Max transitions ({self.config.max_transitions}) exceeded")
127 if self.elapsed_total >= self.config.max_total_time:
128 raise StateTimeoutError(self._state, self.elapsed_total, self.config.max_total_time)
130 return True
132 def _check_timeout(self):
133 """检查当前状态是否超时。"""
134 limits = {
135 AgentState.THINKING: self.config.max_thinking_time,
136 AgentState.ACTING: self.config.max_acting_time,
137 AgentState.OBSERVING: self.config.max_observing_time,
138 }
139 limit = limits.get(self._state)
140 if limit and self.elapsed_in_state > limit:
141 raise StateTimeoutError(self._state, self.elapsed_in_state, limit)
143 def transition(self, to_state: AgentState, reason: str = "",
144 metadata: dict | None = None) -> StateTransition:
145 """执行状态转换。"""
146 self._check_timeout()
147 self._guard(to_state)
149 transition = StateTransition(
150 from_state=self._state,
151 to_state=to_state,
152 reason=reason,
153 metadata=metadata or {},
154 )
155 self._history.append(transition)
156 self._state = to_state
157 self._state_enter_time = time.time()
158 self._fire_hooks(transition)
159 return transition
161 def on_transition(self, from_state: AgentState, to_state: AgentState):
162 """装饰器:注册状态转换钩子。"""
163 def decorator(fn):
164 key = (from_state, to_state)
165 self._on_transition_hooks.setdefault(key, []).append(fn)
166 return fn
167 return decorator
169 def _fire_hooks(self, transition: StateTransition):
170 key = (transition.from_state, transition.to_state)
171 for hook in self._on_transition_hooks.get(key, []):
172 hook(transition)
174 # ── 便利方法 ──────────────────────────────────────────────────────────
176 def start(self, reason: str = ""):
177 return self.transition(AgentState.INITIALIZING, reason)
179 def think(self, reason: str = ""):
180 return self.transition(AgentState.THINKING, reason)
182 def act(self, reason: str = ""):
183 return self.transition(AgentState.ACTING, reason)
185 def observe(self, reason: str = ""):
186 return self.transition(AgentState.OBSERVING, reason)
188 def complete(self, reason: str = ""):
189 return self.transition(AgentState.COMPLETED, reason)
191 def fail(self, reason: str = ""):
192 self._error_count += 1
193 return self.transition(AgentState.FAILED, reason)
195 def pause(self, reason: str = ""):
196 return self.transition(AgentState.PAUSED, reason)
198 def resume(self, reason: str = ""):
199 """从暂停恢复。"""
200 if self._state != AgentState.PAUSED:
201 raise TransitionError(self._state, AgentState.IDLE)
202 prev = self._history[-1].from_state if self._history else AgentState.IDLE
203 return self.transition(prev, reason=f"resumed: {reason}")
205 def cancel(self, reason: str = ""):
206 return self.transition(AgentState.CANCELLED, reason)
208 def error(self, reason: str = ""):
209 self._error_count += 1
210 return self.transition(AgentState.ERROR, reason)
212 def is_active(self) -> bool:
213 return self._state in (AgentState.THINKING, AgentState.ACTING, AgentState.OBSERVING)
215 def is_terminal(self) -> bool:
216 return self._state in (AgentState.COMPLETED, AgentState.FAILED, AgentState.CANCELLED)
218 def run_idle(self):
219 """错误/失败后回到空闲。"""
220 if self._state in (AgentState.FAILED, AgentState.CANCELLED, AgentState.ERROR):
221 return self.transition(AgentState.IDLE, "reset")
222 raise TransitionError(self._state, AgentState.IDLE)
224 def summary(self) -> dict:
225 return {
226 "state": self._state.value,
227 "elapsed_total": f"{self.elapsed_total:.1f}s",
228 "elapsed_in_state": f"{self.elapsed_in_state:.1f}s",
229 "transitions": len(self._history),
230 "error_count": self._error_count,
231 "is_active": self.is_active(),
232 "is_terminal": self.is_terminal(),
233 }