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

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 dataclasses import dataclass, field 

11from enum import Enum 

12from typing import Callable 

13 

14 

15class AgentState(str, Enum): 

16 

17 """Agent 状态枚举。""" 

18 

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

30 

31 

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} 

46 

47 

48@dataclass 

49class StateTransition: 

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

51 

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) 

57 

58 

59@dataclass 

60class StateMachineConfig: 

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

62 

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 

70 

71 

72class TransitionError(Exception): 

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

74 

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 

79 

80 

81class StateTimeoutError(Exception): 

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

83 

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 

88 

89 

90class AgentStateMachine: 

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

92 

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

101 

102 @property 

103 def state(self) -> AgentState: 

104 return self._state 

105 

106 @property 

107 def elapsed_total(self) -> float: 

108 return time.time() - self._created_at 

109 

110 @property 

111 def elapsed_in_state(self) -> float: 

112 return time.time() - self._state_enter_time 

113 

114 @property 

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

116 return list(self._history) 

117 

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) 

123 

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

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

126 

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

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

129 

130 return True 

131 

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) 

142 

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) 

148 

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 

160 

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 

168 

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) 

173 

174 # ── 便利方法 ────────────────────────────────────────────────────────── 

175 

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

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

178 

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

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

181 

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

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

184 

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

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

187 

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

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

190 

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

192 self._error_count += 1 

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

194 

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

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

197 

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

204 

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

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

207 

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

209 self._error_count += 1 

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

211 

212 def is_active(self) -> bool: 

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

214 

215 def is_terminal(self) -> bool: 

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

217 

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) 

223 

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 }