Coverage for agentos/core/state_machine.py: 50%

133 statements  

« prev     ^ index     » next       coverage.py v7.14.3, created at 2026-07-09 09:19 +0800

1""" 

2AgentOS v0.60 State Machine — Agent 生命周期状态管理。 

3状态:Idle → Thinking → Acting → Observing → (Complete|Failed|Paused) 

4含转换守卫、超时检测、恢复机制。 

5""" 

6 

7from __future__ import annotations 

8 

9import time 

10from collections.abc import Callable 

11from dataclasses import dataclass, field 

12from enum import StrEnum 

13 

14 

15class AgentState(StrEnum): 

16 """Agent 状态枚举。""" 

17 

18 IDLE = "idle" # 空闲,等待任务 

19 INITIALIZING = "initializing" # 加载配置/工具 

20 THINKING = "thinking" # 推理/规划 

21 ACTING = "acting" # 执行工具/调用模型 

22 OBSERVING = "observing" # 处理工具返回/反思 

23 WAITING = "waiting" # 等待外部输入(HITL) 

24 PAUSED = "paused" # 手动暂停 

25 COMPLETED = "completed" # 任务完成 

26 FAILED = "failed" # 任务失败 

27 CANCELLED = "cancelled" # 被取消 

28 ERROR = "error" # 系统错误 

29 

30 

31# 合法状态转换表 

32VALID_TRANSITIONS: dict[AgentState, set[AgentState]] = { 

33 AgentState.IDLE: {AgentState.INITIALIZING, AgentState.CANCELLED}, 

34 AgentState.INITIALIZING: { 

35 AgentState.IDLE, 

36 AgentState.THINKING, 

37 AgentState.FAILED, 

38 AgentState.ERROR, 

39 }, 

40 AgentState.THINKING: { 

41 AgentState.ACTING, 

42 AgentState.WAITING, 

43 AgentState.COMPLETED, 

44 AgentState.FAILED, 

45 AgentState.PAUSED, 

46 AgentState.ERROR, 

47 }, 

48 AgentState.ACTING: {AgentState.OBSERVING, AgentState.FAILED, AgentState.ERROR}, 

49 AgentState.OBSERVING: { 

50 AgentState.THINKING, 

51 AgentState.ACTING, 

52 AgentState.COMPLETED, 

53 AgentState.FAILED, 

54 AgentState.ERROR, 

55 }, 

56 AgentState.WAITING: { 

57 AgentState.THINKING, 

58 AgentState.ACTING, 

59 AgentState.CANCELLED, 

60 AgentState.PAUSED, 

61 AgentState.ERROR, 

62 }, 

63 AgentState.PAUSED: { 

64 AgentState.THINKING, 

65 AgentState.ACTING, 

66 AgentState.OBSERVING, 

67 AgentState.CANCELLED, 

68 AgentState.ERROR, 

69 }, 

70 AgentState.COMPLETED: set(), # 终态 

71 AgentState.FAILED: {AgentState.IDLE, AgentState.ERROR}, 

72 AgentState.CANCELLED: {AgentState.IDLE, AgentState.ERROR}, 

73 AgentState.ERROR: {AgentState.IDLE, AgentState.FAILED}, 

74} 

75 

76 

77@dataclass 

78class StateTransition: 

79 """状态转换事件记录。""" 

80 

81 from_state: AgentState 

82 to_state: AgentState 

83 timestamp: float = field(default_factory=time.time) 

84 reason: str = "" 

85 metadata: dict = field(default_factory=dict) 

86 

87 

88@dataclass 

89class StateMachineConfig: 

90 """状态机运行时配置。""" 

91 

92 max_thinking_time: float = 300.0 # 推理超时(秒) 

93 max_acting_time: float = 120.0 # 执行超时 

94 max_observing_time: float = 60.0 # 观察超时 

95 max_total_time: float = 3600.0 # 总超时 

96 max_transitions: int = 500 # 最大状态转换次数 

97 auto_recover: bool = True # 错误后自动恢复 

98 max_retries_after_error: int = 3 

99 

100 

101class TransitionError(Exception): 

102 """非法状态转换异常。""" 

103 

104 def __init__(self, from_state: AgentState, to_state: AgentState): 

105 super().__init__(f"Invalid transition: {from_state.value} → {to_state.value}") 

106 self.from_state = from_state 

107 self.to_state = to_state 

108 

109 

110class StateTimeoutError(Exception): 

111 """状态超时异常。""" 

112 

113 def __init__(self, state: AgentState, elapsed: float, limit: float): 

114 super().__init__(f"{state.value} timeout: {elapsed:.1f}s > {limit:.1f}s") 

115 self.state = state 

116 self.elapsed = elapsed 

117 

118 

119class AgentStateMachine: 

120 """Agent有限状态机,带守卫和超时检测。""" 

121 

122 def __init__(self, config: StateMachineConfig | None = None): 

123 self.config = config or StateMachineConfig() 

124 self._state: AgentState = AgentState.IDLE 

125 self._history: list[StateTransition] = [] 

126 self._state_enter_time: float = time.time() 

127 self._created_at: float = time.time() 

128 self._error_count: int = 0 

129 self._on_transition_hooks: dict[tuple[AgentState, AgentState], list[Callable]] = {} 

130 

131 @property 

132 def state(self) -> AgentState: 

133 return self._state 

134 

135 @property 

136 def elapsed_total(self) -> float: 

137 return time.time() - self._created_at 

138 

139 @property 

140 def elapsed_in_state(self) -> float: 

141 return time.time() - self._state_enter_time 

142 

143 @property 

144 def history(self) -> list[StateTransition]: 

145 return list(self._history) 

146 

147 def _guard(self, target: AgentState) -> bool: 

148 """状态转换守卫。""" 

149 valid = VALID_TRANSITIONS.get(self._state, set()) 

150 if target not in valid: 

151 raise TransitionError(self._state, target) 

152 

153 if len(self._history) >= self.config.max_transitions: 

154 raise RuntimeError(f"Max transitions ({self.config.max_transitions}) exceeded") 

155 

156 if self.elapsed_total >= self.config.max_total_time: 

157 raise StateTimeoutError(self._state, self.elapsed_total, self.config.max_total_time) 

158 

159 return True 

160 

161 def _check_timeout(self): 

162 """检查当前状态是否超时。""" 

163 limits = { 

164 AgentState.THINKING: self.config.max_thinking_time, 

165 AgentState.ACTING: self.config.max_acting_time, 

166 AgentState.OBSERVING: self.config.max_observing_time, 

167 } 

168 limit = limits.get(self._state) 

169 if limit and self.elapsed_in_state > limit: 

170 raise StateTimeoutError(self._state, self.elapsed_in_state, limit) 

171 

172 def transition( 

173 self, to_state: AgentState, reason: str = "", metadata: dict | None = None 

174 ) -> StateTransition: 

175 """执行状态转换。""" 

176 self._check_timeout() 

177 self._guard(to_state) 

178 

179 transition = StateTransition( 

180 from_state=self._state, 

181 to_state=to_state, 

182 reason=reason, 

183 metadata=metadata or {}, 

184 ) 

185 self._history.append(transition) 

186 self._state = to_state 

187 self._state_enter_time = time.time() 

188 self._fire_hooks(transition) 

189 return transition 

190 

191 def on_transition(self, from_state: AgentState, to_state: AgentState): 

192 """装饰器:注册状态转换钩子。""" 

193 

194 def decorator(fn): 

195 key = (from_state, to_state) 

196 self._on_transition_hooks.setdefault(key, []).append(fn) 

197 return fn 

198 

199 return decorator 

200 

201 def _fire_hooks(self, transition: StateTransition): 

202 key = (transition.from_state, transition.to_state) 

203 for hook in self._on_transition_hooks.get(key, []): 

204 hook(transition) 

205 

206 # ── 便利方法 ────────────────────────────────────────────────────────── 

207 

208 def start(self, reason: str = ""): 

209 return self.transition(AgentState.INITIALIZING, reason) 

210 

211 def think(self, reason: str = ""): 

212 return self.transition(AgentState.THINKING, reason) 

213 

214 def act(self, reason: str = ""): 

215 return self.transition(AgentState.ACTING, reason) 

216 

217 def observe(self, reason: str = ""): 

218 return self.transition(AgentState.OBSERVING, reason) 

219 

220 def complete(self, reason: str = ""): 

221 return self.transition(AgentState.COMPLETED, reason) 

222 

223 def fail(self, reason: str = ""): 

224 self._error_count += 1 

225 return self.transition(AgentState.FAILED, reason) 

226 

227 def pause(self, reason: str = ""): 

228 return self.transition(AgentState.PAUSED, reason) 

229 

230 def resume(self, reason: str = ""): 

231 """从暂停恢复。""" 

232 if self._state != AgentState.PAUSED: 

233 raise TransitionError(self._state, AgentState.IDLE) 

234 prev = self._history[-1].from_state if self._history else AgentState.IDLE 

235 return self.transition(prev, reason=f"resumed: {reason}") 

236 

237 def cancel(self, reason: str = ""): 

238 return self.transition(AgentState.CANCELLED, reason) 

239 

240 def error(self, reason: str = ""): 

241 self._error_count += 1 

242 return self.transition(AgentState.ERROR, reason) 

243 

244 def is_active(self) -> bool: 

245 return self._state in (AgentState.THINKING, AgentState.ACTING, AgentState.OBSERVING) 

246 

247 def is_terminal(self) -> bool: 

248 return self._state in (AgentState.COMPLETED, AgentState.FAILED, AgentState.CANCELLED) 

249 

250 def run_idle(self): 

251 """错误/失败后回到空闲。""" 

252 if self._state in (AgentState.FAILED, AgentState.CANCELLED, AgentState.ERROR): 

253 return self.transition(AgentState.IDLE, "reset") 

254 raise TransitionError(self._state, AgentState.IDLE) 

255 

256 def summary(self) -> dict: 

257 return { 

258 "state": self._state.value, 

259 "elapsed_total": f"{self.elapsed_total:.1f}s", 

260 "elapsed_in_state": f"{self.elapsed_in_state:.1f}s", 

261 "transitions": len(self._history), 

262 "error_count": self._error_count, 

263 "is_active": self.is_active(), 

264 "is_terminal": self.is_terminal(), 

265 }