Coverage for agentos/core/state_machine.py: 51%
134 statements
« prev ^ index » next coverage.py v7.14.3, created at 2026-07-02 09:59 +0800
« prev ^ index » next coverage.py v7.14.3, created at 2026-07-02 09: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
10import asyncio
11from dataclasses import dataclass, field
12from enum import Enum
13from typing import Optional, Callable, Awaitable
16class AgentState(str, Enum):
18 """Agent 状态枚举。"""
20 IDLE = "idle" # 空闲,等待任务
21 INITIALIZING = "initializing" # 加载配置/工具
22 THINKING = "thinking" # 推理/规划
23 ACTING = "acting" # 执行工具/调用模型
24 OBSERVING = "observing" # 处理工具返回/反思
25 WAITING = "waiting" # 等待外部输入(HITL)
26 PAUSED = "paused" # 手动暂停
27 COMPLETED = "completed" # 任务完成
28 FAILED = "failed" # 任务失败
29 CANCELLED = "cancelled" # 被取消
30 ERROR = "error" # 系统错误
33# 合法状态转换表
34VALID_TRANSITIONS: dict[AgentState, set[AgentState]] = {
35 AgentState.IDLE: {AgentState.INITIALIZING, AgentState.CANCELLED},
36 AgentState.INITIALIZING: {AgentState.IDLE, AgentState.THINKING, AgentState.FAILED, AgentState.ERROR},
37 AgentState.THINKING: {AgentState.ACTING, AgentState.WAITING, AgentState.COMPLETED, AgentState.FAILED, AgentState.PAUSED, AgentState.ERROR},
38 AgentState.ACTING: {AgentState.OBSERVING, AgentState.FAILED, AgentState.ERROR},
39 AgentState.OBSERVING: {AgentState.THINKING, AgentState.ACTING, AgentState.COMPLETED, AgentState.FAILED, AgentState.ERROR},
40 AgentState.WAITING: {AgentState.THINKING, AgentState.ACTING, AgentState.CANCELLED, AgentState.PAUSED, AgentState.ERROR},
41 AgentState.PAUSED: {AgentState.THINKING, AgentState.ACTING, AgentState.OBSERVING, AgentState.CANCELLED, AgentState.ERROR},
42 AgentState.COMPLETED: set(), # 终态
43 AgentState.FAILED: {AgentState.IDLE, AgentState.ERROR},
44 AgentState.CANCELLED: {AgentState.IDLE, AgentState.ERROR},
45 AgentState.ERROR: {AgentState.IDLE, AgentState.FAILED},
46}
49@dataclass
50class StateTransition:
51 """状态转换事件记录。"""
53 from_state: AgentState
54 to_state: AgentState
55 timestamp: float = field(default_factory=time.time)
56 reason: str = ""
57 metadata: dict = field(default_factory=dict)
60@dataclass
61class StateMachineConfig:
62 """状态机运行时配置。"""
64 max_thinking_time: float = 300.0 # 推理超时(秒)
65 max_acting_time: float = 120.0 # 执行超时
66 max_observing_time: float = 60.0 # 观察超时
67 max_total_time: float = 3600.0 # 总超时
68 max_transitions: int = 500 # 最大状态转换次数
69 auto_recover: bool = True # 错误后自动恢复
70 max_retries_after_error: int = 3
73class TransitionError(Exception):
74 """非法状态转换异常。"""
76 def __init__(self, from_state: AgentState, to_state: AgentState):
77 super().__init__(f"Invalid transition: {from_state.value} → {to_state.value}")
78 self.from_state = from_state
79 self.to_state = to_state
82class StateTimeoutError(Exception):
83 """状态超时异常。"""
85 def __init__(self, state: AgentState, elapsed: float, limit: float):
86 super().__init__(f"{state.value} timeout: {elapsed:.1f}s > {limit:.1f}s")
87 self.state = state
88 self.elapsed = elapsed
91class AgentStateMachine:
92 """Agent有限状态机,带守卫和超时检测。"""
94 def __init__(self, config: StateMachineConfig | None = None):
95 self.config = config or StateMachineConfig()
96 self._state: AgentState = AgentState.IDLE
97 self._history: list[StateTransition] = []
98 self._state_enter_time: float = time.time()
99 self._created_at: float = time.time()
100 self._error_count: int = 0
101 self._on_transition_hooks: dict[tuple[AgentState, AgentState], list[Callable]] = {}
103 @property
104 def state(self) -> AgentState:
105 return self._state
107 @property
108 def elapsed_total(self) -> float:
109 return time.time() - self._created_at
111 @property
112 def elapsed_in_state(self) -> float:
113 return time.time() - self._state_enter_time
115 @property
116 def history(self) -> list[StateTransition]:
117 return list(self._history)
119 def _guard(self, target: AgentState) -> bool:
120 """状态转换守卫。"""
121 valid = VALID_TRANSITIONS.get(self._state, set())
122 if target not in valid:
123 raise TransitionError(self._state, target)
125 if len(self._history) >= self.config.max_transitions:
126 raise RuntimeError(f"Max transitions ({self.config.max_transitions}) exceeded")
128 if self.elapsed_total >= self.config.max_total_time:
129 raise StateTimeoutError(self._state, self.elapsed_total, self.config.max_total_time)
131 return True
133 def _check_timeout(self):
134 """检查当前状态是否超时。"""
135 limits = {
136 AgentState.THINKING: self.config.max_thinking_time,
137 AgentState.ACTING: self.config.max_acting_time,
138 AgentState.OBSERVING: self.config.max_observing_time,
139 }
140 limit = limits.get(self._state)
141 if limit and self.elapsed_in_state > limit:
142 raise StateTimeoutError(self._state, self.elapsed_in_state, limit)
144 def transition(self, to_state: AgentState, reason: str = "",
145 metadata: dict | None = None) -> StateTransition:
146 """执行状态转换。"""
147 self._check_timeout()
148 self._guard(to_state)
150 transition = StateTransition(
151 from_state=self._state,
152 to_state=to_state,
153 reason=reason,
154 metadata=metadata or {},
155 )
156 self._history.append(transition)
157 self._state = to_state
158 self._state_enter_time = time.time()
159 self._fire_hooks(transition)
160 return transition
162 def on_transition(self, from_state: AgentState, to_state: AgentState):
163 """装饰器:注册状态转换钩子。"""
164 def decorator(fn):
165 key = (from_state, to_state)
166 self._on_transition_hooks.setdefault(key, []).append(fn)
167 return fn
168 return decorator
170 def _fire_hooks(self, transition: StateTransition):
171 key = (transition.from_state, transition.to_state)
172 for hook in self._on_transition_hooks.get(key, []):
173 hook(transition)
175 # ── 便利方法 ──────────────────────────────────────────────────────────
177 def start(self, reason: str = ""):
178 return self.transition(AgentState.INITIALIZING, reason)
180 def think(self, reason: str = ""):
181 return self.transition(AgentState.THINKING, reason)
183 def act(self, reason: str = ""):
184 return self.transition(AgentState.ACTING, reason)
186 def observe(self, reason: str = ""):
187 return self.transition(AgentState.OBSERVING, reason)
189 def complete(self, reason: str = ""):
190 return self.transition(AgentState.COMPLETED, reason)
192 def fail(self, reason: str = ""):
193 self._error_count += 1
194 return self.transition(AgentState.FAILED, reason)
196 def pause(self, reason: str = ""):
197 return self.transition(AgentState.PAUSED, reason)
199 def resume(self, reason: str = ""):
200 """从暂停恢复。"""
201 if self._state != AgentState.PAUSED:
202 raise TransitionError(self._state, AgentState.IDLE)
203 prev = self._history[-1].from_state if self._history else AgentState.IDLE
204 return self.transition(prev, reason=f"resumed: {reason}")
206 def cancel(self, reason: str = ""):
207 return self.transition(AgentState.CANCELLED, reason)
209 def error(self, reason: str = ""):
210 self._error_count += 1
211 return self.transition(AgentState.ERROR, reason)
213 def is_active(self) -> bool:
214 return self._state in (AgentState.THINKING, AgentState.ACTING, AgentState.OBSERVING)
216 def is_terminal(self) -> bool:
217 return self._state in (AgentState.COMPLETED, AgentState.FAILED, AgentState.CANCELLED)
219 def run_idle(self):
220 """错误/失败后回到空闲。"""
221 if self._state in (AgentState.FAILED, AgentState.CANCELLED, AgentState.ERROR):
222 return self.transition(AgentState.IDLE, "reset")
223 raise TransitionError(self._state, AgentState.IDLE)
225 def summary(self) -> dict:
226 return {
227 "state": self._state.value,
228 "elapsed_total": f"{self.elapsed_total:.1f}s",
229 "elapsed_in_state": f"{self.elapsed_in_state:.1f}s",
230 "transitions": len(self._history),
231 "error_count": self._error_count,
232 "is_active": self.is_active(),
233 "is_terminal": self.is_terminal(),
234 }