state.py 2.8 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879
  1. import base64
  2. import pickle
  3. from dataclasses import dataclass, field
  4. from opendevin.controller.state.task import RootTask
  5. from opendevin.core.logger import opendevin_logger as logger
  6. from opendevin.core.metrics import Metrics
  7. from opendevin.core.schema import AgentState
  8. from opendevin.events.action import (
  9. Action,
  10. MessageAction,
  11. )
  12. from opendevin.events.observation import (
  13. CmdOutputObservation,
  14. Observation,
  15. )
  16. from opendevin.storage import get_file_store
  17. RESUMABLE_STATES = [
  18. AgentState.RUNNING,
  19. AgentState.PAUSED,
  20. AgentState.AWAITING_USER_INPUT,
  21. AgentState.FINISHED,
  22. ]
  23. @dataclass
  24. class State:
  25. root_task: RootTask = field(default_factory=RootTask)
  26. iteration: int = 0
  27. max_iterations: int = 100
  28. # number of characters we have sent to and received from LLM so far for current task
  29. num_of_chars: int = 0
  30. background_commands_obs: list[CmdOutputObservation] = field(default_factory=list)
  31. history: list[tuple[Action, Observation]] = field(default_factory=list)
  32. updated_info: list[tuple[Action, Observation]] = field(default_factory=list)
  33. inputs: dict = field(default_factory=dict)
  34. outputs: dict = field(default_factory=dict)
  35. error: str | None = None
  36. agent_state: AgentState = AgentState.LOADING
  37. resume_state: AgentState | None = None
  38. metrics: Metrics = Metrics()
  39. # root agent has level 0, and every delegate increases the level by one
  40. delegate_level: int = 0
  41. def save_to_session(self, sid: str):
  42. fs = get_file_store()
  43. pickled = pickle.dumps(self)
  44. encoded = base64.b64encode(pickled).decode('utf-8')
  45. try:
  46. fs.write(f'sessions/{sid}/agent_state.pkl', encoded)
  47. except Exception as e:
  48. logger.error(f'Failed to save state to session: {e}')
  49. raise e
  50. @staticmethod
  51. def restore_from_session(sid: str) -> 'State':
  52. fs = get_file_store()
  53. try:
  54. encoded = fs.read(f'sessions/{sid}/agent_state.pkl')
  55. pickled = base64.b64decode(encoded)
  56. state = pickle.loads(pickled)
  57. except Exception as e:
  58. logger.error(f'Failed to restore state from session: {e}')
  59. raise e
  60. if state.agent_state in RESUMABLE_STATES:
  61. state.resume_state = state.agent_state
  62. else:
  63. state.resume_state = None
  64. state.agent_state = AgentState.LOADING
  65. return state
  66. def get_current_user_intent(self):
  67. # TODO: this is used to understand the user's main goal, but it's possible
  68. # the latest message is an interruption. We should look for a space where
  69. # the agent goes to FINISHED, and then look for the next user message.
  70. for action, obs in reversed(self.history):
  71. if isinstance(action, MessageAction) and action.source == 'user':
  72. return action.content