event.py 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. from dataclasses import asdict
  2. from datetime import datetime
  3. from opendevin.core.config import config
  4. from opendevin.events import Event, EventSource
  5. from opendevin.events.observation.observation import Observation
  6. from .action import action_from_dict
  7. from .observation import observation_from_dict
  8. from .utils import remove_fields
  9. # TODO: move `content` into `extras`
  10. TOP_KEYS = ['id', 'timestamp', 'source', 'message', 'cause', 'action', 'observation']
  11. UNDERSCORE_KEYS = ['id', 'timestamp', 'source', 'cause']
  12. DELETE_FROM_MEMORY_EXTRAS = {
  13. 'screenshot',
  14. 'dom_object',
  15. 'axtree_object',
  16. 'open_pages_urls',
  17. 'active_page_index',
  18. 'last_browser_action',
  19. 'last_browser_action_error',
  20. 'focused_element_bid',
  21. 'extra_element_properties',
  22. }
  23. def event_from_dict(data) -> 'Event':
  24. evt: Event
  25. if 'action' in data:
  26. evt = action_from_dict(data)
  27. elif 'observation' in data:
  28. evt = observation_from_dict(data)
  29. else:
  30. raise ValueError('Unknown event type: ' + data)
  31. for key in UNDERSCORE_KEYS:
  32. if key in data:
  33. value = data[key]
  34. if key == 'timestamp':
  35. value = datetime.fromisoformat(value)
  36. if key == 'source':
  37. value = EventSource(value)
  38. setattr(evt, '_' + key, value)
  39. return evt
  40. def event_to_dict(event: 'Event') -> dict:
  41. props = asdict(event)
  42. d = {}
  43. for key in TOP_KEYS:
  44. if hasattr(event, key) and getattr(event, key) is not None:
  45. d[key] = getattr(event, key)
  46. elif hasattr(event, f'_{key}') and getattr(event, f'_{key}') is not None:
  47. d[key] = getattr(event, f'_{key}')
  48. if key == 'id' and d.get('id') == -1:
  49. d.pop('id', None)
  50. if key == 'timestamp' and 'timestamp' in d:
  51. d['timestamp'] = d['timestamp'].isoformat()
  52. if key == 'source' and 'source' in d:
  53. d['source'] = d['source'].value
  54. props.pop(key, None)
  55. if 'action' in d:
  56. d['args'] = props
  57. elif 'observation' in d:
  58. d['content'] = props.pop('content', '')
  59. d['extras'] = props
  60. else:
  61. raise ValueError('Event must be either action or observation')
  62. return d
  63. def event_to_memory(event: 'Event') -> dict:
  64. d = event_to_dict(event)
  65. d.pop('id', None)
  66. d.pop('cause', None)
  67. d.pop('timestamp', None)
  68. d.pop('message', None)
  69. if 'extras' in d:
  70. remove_fields(d['extras'], DELETE_FROM_MEMORY_EXTRAS)
  71. if isinstance(event, Observation) and 'content' in d:
  72. d['content'] = truncate_content(d['content'])
  73. return d
  74. def truncate_content(content: str, max_chars: int = -1) -> str:
  75. """
  76. Truncate the middle of the observation content if it is too long.
  77. """
  78. if max_chars == -1:
  79. max_chars = config.llm.max_message_chars
  80. if len(content) <= max_chars:
  81. return content
  82. # truncate the middle and include a message to the LLM about it
  83. half = max_chars // 2
  84. return (
  85. content[:half]
  86. + '\n[... Observation truncated due to length ...]\n'
  87. + content[-half:]
  88. )