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

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 

10import asyncio 

11from dataclasses import dataclass, field 

12from enum import Enum 

13from typing import Optional, Callable, Awaitable 

14 

15 

16class AgentState(str, Enum): 

17 

18 """Agent 状态枚举。""" 

19 

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" # 系统错误 

31 

32 

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} 

47 

48 

49@dataclass 

50class StateTransition: 

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

52 

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) 

58 

59 

60@dataclass 

61class StateMachineConfig: 

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

63 

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 

71 

72 

73class TransitionError(Exception): 

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

75 

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 

80 

81 

82class StateTimeoutError(Exception): 

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

84 

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 

89 

90 

91class AgentStateMachine: 

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

93 

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]] = {} 

102 

103 @property 

104 def state(self) -> AgentState: 

105 return self._state 

106 

107 @property 

108 def elapsed_total(self) -> float: 

109 return time.time() - self._created_at 

110 

111 @property 

112 def elapsed_in_state(self) -> float: 

113 return time.time() - self._state_enter_time 

114 

115 @property 

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

117 return list(self._history) 

118 

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) 

124 

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

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

127 

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

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

130 

131 return True 

132 

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) 

143 

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) 

149 

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 

161 

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 

169 

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) 

174 

175 # ── 便利方法 ────────────────────────────────────────────────────────── 

176 

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

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

179 

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

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

182 

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

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

185 

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

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

188 

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

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

191 

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

193 self._error_count += 1 

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

195 

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

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

198 

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}") 

205 

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

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

208 

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

210 self._error_count += 1 

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

212 

213 def is_active(self) -> bool: 

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

215 

216 def is_terminal(self) -> bool: 

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

218 

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) 

224 

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 }