@elizaos/python 2.0.0-alpha.11 → 2.0.0-alpha.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/elizaos/advanced_capabilities/__init__.py +6 -41
  2. package/elizaos/advanced_capabilities/actions/__init__.py +1 -21
  3. package/elizaos/advanced_capabilities/actions/add_contact.py +21 -11
  4. package/elizaos/advanced_capabilities/actions/follow_room.py +28 -28
  5. package/elizaos/advanced_capabilities/actions/image_generation.py +13 -26
  6. package/elizaos/advanced_capabilities/actions/mute_room.py +13 -26
  7. package/elizaos/advanced_capabilities/actions/remove_contact.py +16 -2
  8. package/elizaos/advanced_capabilities/actions/roles.py +13 -27
  9. package/elizaos/advanced_capabilities/actions/search_contacts.py +17 -3
  10. package/elizaos/advanced_capabilities/actions/send_message.py +317 -9
  11. package/elizaos/advanced_capabilities/actions/settings.py +16 -2
  12. package/elizaos/advanced_capabilities/actions/unfollow_room.py +13 -26
  13. package/elizaos/advanced_capabilities/actions/unmute_room.py +13 -26
  14. package/elizaos/advanced_capabilities/actions/update_contact.py +16 -2
  15. package/elizaos/advanced_capabilities/actions/update_entity.py +16 -2
  16. package/elizaos/advanced_capabilities/evaluators/__init__.py +2 -9
  17. package/elizaos/advanced_capabilities/evaluators/reflection.py +3 -132
  18. package/elizaos/advanced_capabilities/evaluators/relationship_extraction.py +5 -201
  19. package/elizaos/advanced_capabilities/providers/__init__.py +1 -12
  20. package/elizaos/advanced_capabilities/providers/knowledge.py +24 -3
  21. package/elizaos/advanced_capabilities/services/__init__.py +2 -9
  22. package/elizaos/advanced_memory/actions/reset_session.py +11 -0
  23. package/elizaos/advanced_memory/evaluators/reflection.py +134 -0
  24. package/elizaos/advanced_memory/evaluators/relationship_extraction.py +203 -0
  25. package/elizaos/advanced_memory/test_advanced_memory.py +357 -0
  26. package/elizaos/advanced_planning/actions/schedule_follow_up.py +222 -0
  27. package/elizaos/basic_capabilities/__init__.py +0 -2
  28. package/elizaos/basic_capabilities/providers/__init__.py +0 -3
  29. package/elizaos/basic_capabilities/providers/agent_settings.py +64 -0
  30. package/elizaos/basic_capabilities/providers/contacts.py +79 -0
  31. package/elizaos/basic_capabilities/providers/facts.py +87 -0
  32. package/elizaos/basic_capabilities/providers/follow_ups.py +117 -0
  33. package/elizaos/basic_capabilities/providers/knowledge.py +97 -0
  34. package/elizaos/basic_capabilities/providers/relationships.py +107 -0
  35. package/elizaos/basic_capabilities/providers/roles.py +96 -0
  36. package/elizaos/basic_capabilities/providers/settings.py +56 -0
  37. package/elizaos/bootstrap/autonomy/__init__.py +5 -1
  38. package/elizaos/bootstrap/autonomy/action.py +161 -0
  39. package/elizaos/bootstrap/autonomy/evaluators.py +217 -0
  40. package/elizaos/bootstrap/autonomy/service.py +8 -0
  41. package/elizaos/bootstrap/plugin.py +7 -0
  42. package/elizaos/bootstrap/providers/knowledge.py +26 -3
  43. package/elizaos/bootstrap/services/embedding.py +156 -1
  44. package/elizaos/runtime.py +63 -18
  45. package/elizaos/services/message_service.py +173 -23
  46. package/elizaos/types/generated/eliza/v1/agent_pb2.py +16 -16
  47. package/elizaos/types/generated/eliza/v1/agent_pb2.pyi +2 -4
  48. package/elizaos/types/model.py +27 -0
  49. package/elizaos/types/runtime.py +5 -1
  50. package/elizaos/utils/validation.py +76 -0
  51. package/package.json +2 -2
  52. package/tests/test_actions_provider_examples.py +58 -1
  53. package/tests/test_async_embedding.py +124 -0
  54. package/tests/test_autonomy.py +13 -2
  55. package/tests/test_validation.py +141 -0
  56. package/tests/verify_memory_architecture.py +192 -0
  57. package/elizaos/basic_capabilities/providers/capabilities.py +0 -62
@@ -1,12 +1,320 @@
1
- """Advanced capabilities SEND_MESSAGE action.
1
+ from __future__ import annotations
2
2
 
3
- Keep this action implementation aligned with bootstrap to guarantee identical
4
- target parsing and dispatch behavior across capability variants.
5
- """
3
+ import contextlib
4
+ from dataclasses import dataclass, field
5
+ from typing import TYPE_CHECKING, Any
6
6
 
7
- from elizaos.bootstrap.actions.send_message import ( # re-export for parity
8
- SendMessageAction,
9
- send_message_action,
10
- )
7
+ from elizaos.generated.spec_helpers import require_action_spec
8
+ from elizaos.types import Action, ActionExample, ActionResult, Content
9
+ from elizaos.types.memory import Memory as MemoryType
10
+ from elizaos.types.primitives import UUID, as_uuid
11
+
12
+ if TYPE_CHECKING:
13
+ from elizaos.types import HandlerCallback, HandlerOptions, IAgentRuntime, Memory, State
14
+
15
+ # Get text content from centralized specs
16
+ _spec = require_action_spec("SEND_MESSAGE")
17
+
18
+
19
+ def _convert_spec_examples() -> list[list[ActionExample]]:
20
+ """Convert spec examples to ActionExample format."""
21
+ spec_examples = _spec.get("examples", [])
22
+ if spec_examples:
23
+ return [
24
+ [
25
+ ActionExample(
26
+ name=msg.get("name", ""),
27
+ content=Content(
28
+ text=msg.get("content", {}).get("text", ""),
29
+ actions=msg.get("content", {}).get("actions"),
30
+ ),
31
+ )
32
+ for msg in example
33
+ ]
34
+ for example in spec_examples
35
+ ]
36
+ return []
37
+
38
+
39
+ def _parse_uuid(value: object) -> UUID | None:
40
+ if not isinstance(value, str) or not value.strip():
41
+ return None
42
+ with contextlib.suppress(Exception):
43
+ return as_uuid(value.strip())
44
+ return None
45
+
46
+
47
+ def _normalize_parameters(options: HandlerOptions | None) -> dict[str, Any]:
48
+ raw = getattr(options, "parameters", None)
49
+ if isinstance(raw, dict):
50
+ return raw
51
+
52
+ if raw is None:
53
+ return {}
54
+
55
+ if hasattr(raw, "items"):
56
+ try:
57
+ return {str(k): v for k, v in raw.items()}
58
+ except Exception:
59
+ return {}
60
+
61
+ return {}
62
+
63
+
64
+ def _coerce_entity_name(entity: object) -> list[str]:
65
+ if isinstance(entity, dict):
66
+ names = entity.get("names")
67
+ if isinstance(names, list):
68
+ return [str(n).strip() for n in names if isinstance(n, str) and n.strip()]
69
+ name = entity.get("name")
70
+ if isinstance(name, str) and name.strip():
71
+ return [name.strip()]
72
+ return []
73
+
74
+ names = getattr(entity, "names", None)
75
+ if isinstance(names, list):
76
+ clean = [str(n).strip() for n in names if isinstance(n, str) and str(n).strip()]
77
+ if clean:
78
+ return clean
79
+
80
+ name = getattr(entity, "name", None)
81
+ if isinstance(name, str) and name.strip():
82
+ return [name.strip()]
83
+
84
+ return []
85
+
86
+
87
+ @dataclass
88
+ class SendMessageAction:
89
+ name: str = _spec["name"]
90
+ similes: list[str] = field(default_factory=lambda: list(_spec.get("similes", [])))
91
+ description: str = _spec["description"]
92
+
93
+ async def validate(
94
+ self, runtime: IAgentRuntime, message: Memory, _state: State | None = None
95
+ ) -> bool:
96
+ if message.content and message.content.target:
97
+ return True
98
+ return True
99
+
100
+ async def handler(
101
+ self,
102
+ runtime: IAgentRuntime,
103
+ message: Memory,
104
+ state: State | None = None,
105
+ options: HandlerOptions | None = None,
106
+ callback: HandlerCallback | None = None,
107
+ responses: list[Memory] | None = None,
108
+ ) -> ActionResult:
109
+ params = _normalize_parameters(options)
110
+
111
+ text_param = params.get("text")
112
+ message_text = str(text_param).strip() if isinstance(text_param, str) else ""
113
+ if not message_text and responses and responses[0].content:
114
+ message_text = str(responses[0].content.text or "").strip()
115
+ if not message_text and message.content and isinstance(message.content.text, str):
116
+ message_text = message.content.text.strip()
11
117
 
12
- __all__ = ["SendMessageAction", "send_message_action"]
118
+ if not message_text:
119
+ return ActionResult(
120
+ text="No message content to send",
121
+ values={"success": False, "error": "no_content"},
122
+ data={"actionName": "SEND_MESSAGE"},
123
+ success=False,
124
+ )
125
+
126
+ target_room_id = message.room_id
127
+ target_entity_id: UUID | None = None
128
+ target_type = "room"
129
+
130
+ target_type_param = params.get("targetType") or params.get("target_type")
131
+ target_param = params.get("target")
132
+ source_param = params.get("source")
133
+
134
+ source = (
135
+ source_param.strip()
136
+ if isinstance(source_param, str) and source_param.strip()
137
+ else (
138
+ message.content.source
139
+ if message.content and isinstance(message.content.source, str)
140
+ else "agent"
141
+ )
142
+ )
143
+
144
+ if isinstance(target_type_param, str):
145
+ normalized_target_type = target_type_param.strip().lower()
146
+ if normalized_target_type in {"user", "entity"}:
147
+ target_type = "user"
148
+ elif normalized_target_type == "room":
149
+ target_type = "room"
150
+
151
+ if isinstance(target_param, str) and target_param.strip():
152
+ target_value = target_param.strip()
153
+ if target_type == "room":
154
+ parsed_room = _parse_uuid(target_value)
155
+ if parsed_room:
156
+ target_room_id = parsed_room
157
+ else:
158
+ world_id = None
159
+ room_data = (
160
+ getattr(getattr(state, "data", None), "room", None) if state else None
161
+ )
162
+ if room_data is not None:
163
+ world_id = getattr(room_data, "world_id", None) or getattr(
164
+ room_data, "worldId", None
165
+ )
166
+ if world_id is None:
167
+ with contextlib.suppress(Exception):
168
+ current_room = await runtime.get_room(message.room_id)
169
+ if current_room:
170
+ world_id = getattr(current_room, "world_id", None) or getattr(
171
+ current_room, "worldId", None
172
+ )
173
+
174
+ if world_id is not None:
175
+ with contextlib.suppress(Exception):
176
+ rooms = await runtime.get_rooms(world_id)
177
+ for room in rooms:
178
+ room_name = getattr(room, "name", None)
179
+ if (
180
+ isinstance(room_name, str)
181
+ and room_name.strip().lower() == target_value.lower()
182
+ ):
183
+ room_id = getattr(room, "id", None)
184
+ if room_id is not None:
185
+ target_room_id = as_uuid(str(room_id))
186
+ break
187
+ else:
188
+ parsed_entity = _parse_uuid(target_value)
189
+ if parsed_entity:
190
+ target_entity_id = parsed_entity
191
+ else:
192
+ with contextlib.suppress(Exception):
193
+ entities = await runtime.get_entities_for_room(message.room_id)
194
+ for entity in entities:
195
+ names = _coerce_entity_name(entity)
196
+ if any(name.lower() == target_value.lower() for name in names):
197
+ entity_id = getattr(entity, "id", None)
198
+ if entity_id is not None:
199
+ target_entity_id = as_uuid(str(entity_id))
200
+ break
201
+
202
+ if message.content and message.content.target:
203
+ target = message.content.target
204
+ if isinstance(target, dict):
205
+ room_str = target.get("roomId")
206
+ entity_str = target.get("entityId")
207
+ if room_str and target_type == "room":
208
+ with contextlib.suppress(Exception):
209
+ target_room_id = as_uuid(room_str)
210
+ if entity_str and target_type == "user":
211
+ with contextlib.suppress(Exception):
212
+ target_entity_id = as_uuid(entity_str)
213
+
214
+ if not target_room_id:
215
+ return ActionResult(
216
+ text="No target room specified",
217
+ values={"success": False, "error": "no_target"},
218
+ data={"actionName": "SEND_MESSAGE"},
219
+ success=False,
220
+ )
221
+
222
+ message_content = Content(
223
+ text=message_text,
224
+ source=source,
225
+ actions=["SEND_MESSAGE"],
226
+ )
227
+
228
+ send_message_to_target = getattr(runtime, "send_message_to_target", None)
229
+ if callable(send_message_to_target):
230
+ with contextlib.suppress(Exception):
231
+ from elizaos.types.runtime import TargetInfo
232
+
233
+ await send_message_to_target(
234
+ TargetInfo(
235
+ roomId=str(target_room_id),
236
+ entityId=str(target_entity_id) if target_entity_id else None,
237
+ source=source,
238
+ ),
239
+ message_content,
240
+ )
241
+
242
+ # Create the message memory
243
+ import time
244
+ import uuid as uuid_module
245
+
246
+ message_memory = MemoryType(
247
+ id=as_uuid(str(uuid_module.uuid4())),
248
+ entity_id=runtime.agent_id,
249
+ room_id=target_room_id,
250
+ content=message_content,
251
+ created_at=int(time.time() * 1000),
252
+ )
253
+
254
+ await runtime.create_memory(
255
+ content=message_content,
256
+ room_id=target_room_id,
257
+ entity_id=runtime.agent_id,
258
+ memory_type="message",
259
+ metadata={
260
+ "type": "SEND_MESSAGE",
261
+ "targetEntityId": str(target_entity_id) if target_entity_id else None,
262
+ },
263
+ )
264
+
265
+ # Emit MESSAGE_SENT event
266
+ await runtime.emit_event(
267
+ "MESSAGE_SENT",
268
+ {
269
+ "runtime": runtime,
270
+ "source": "send-message-action",
271
+ "message": message_memory,
272
+ },
273
+ )
274
+
275
+ response_content = Content(
276
+ text=f"Message sent: {message_text[:50]}...",
277
+ actions=["SEND_MESSAGE"],
278
+ )
279
+
280
+ if callback:
281
+ await callback(response_content)
282
+
283
+ target_id = (
284
+ target_entity_id if target_type == "user" and target_entity_id else target_room_id
285
+ )
286
+ return ActionResult(
287
+ text="Message sent",
288
+ values={
289
+ "success": True,
290
+ "messageSent": True,
291
+ "targetType": target_type,
292
+ "target": str(target_id),
293
+ "source": source,
294
+ "targetRoomId": str(target_room_id),
295
+ "targetEntityId": str(target_entity_id) if target_entity_id else None,
296
+ },
297
+ data={
298
+ "actionName": "SEND_MESSAGE",
299
+ "targetType": target_type,
300
+ "target": str(target_id),
301
+ "source": source,
302
+ "targetRoomId": str(target_room_id),
303
+ "messagePreview": message_text[:100],
304
+ },
305
+ success=True,
306
+ )
307
+
308
+ @property
309
+ def examples(self) -> list[list[ActionExample]]:
310
+ return _convert_spec_examples()
311
+
312
+
313
+ send_message_action = Action(
314
+ name=SendMessageAction.name,
315
+ similes=SendMessageAction().similes,
316
+ description=SendMessageAction.description,
317
+ validate=SendMessageAction().validate,
318
+ handler=SendMessageAction().handler,
319
+ examples=SendMessageAction().examples,
320
+ )
@@ -7,7 +7,6 @@ from elizaos.bootstrap.utils.xml import parse_key_value_xml
7
7
  from elizaos.generated.spec_helpers import require_action_spec
8
8
  from elizaos.prompts import UPDATE_SETTINGS_TEMPLATE
9
9
  from elizaos.types import Action, ActionExample, ActionResult, Content, ModelType
10
- from elizaos.utils.spec_examples import convert_spec_examples
11
10
 
12
11
  if TYPE_CHECKING:
13
12
  from elizaos.types import HandlerCallback, HandlerOptions, IAgentRuntime, Memory, State
@@ -18,7 +17,22 @@ _spec = require_action_spec("UPDATE_SETTINGS")
18
17
 
19
18
  def _convert_spec_examples() -> list[list[ActionExample]]:
20
19
  """Convert spec examples to ActionExample format."""
21
- return convert_spec_examples(_spec)
20
+ spec_examples = _spec.get("examples", [])
21
+ if spec_examples:
22
+ return [
23
+ [
24
+ ActionExample(
25
+ name=msg.get("name", ""),
26
+ content=Content(
27
+ text=msg.get("content", {}).get("text", ""),
28
+ actions=msg.get("content", {}).get("actions"),
29
+ ),
30
+ )
31
+ for msg in example
32
+ ]
33
+ for example in spec_examples
34
+ ]
35
+ return []
22
36
 
23
37
 
24
38
  @dataclass
@@ -16,34 +16,21 @@ _spec = require_action_spec("UNFOLLOW_ROOM")
16
16
  def _convert_spec_examples() -> list[list[ActionExample]]:
17
17
  """Convert spec examples to ActionExample format."""
18
18
  spec_examples = _spec.get("examples", [])
19
- if not isinstance(spec_examples, list):
20
- return []
21
- result: list[list[ActionExample]] = []
22
- for example in spec_examples:
23
- if not isinstance(example, list):
24
- continue
25
- row: list[ActionExample] = []
26
- for msg in example:
27
- if not isinstance(msg, dict):
28
- continue
29
- content = msg.get("content", {})
30
- text = ""
31
- actions: list[str] | None = None
32
- if isinstance(content, dict):
33
- text_val = content.get("text", "")
34
- text = str(text_val) if text_val else ""
35
- actions_val = content.get("actions")
36
- if isinstance(actions_val, list) and all(isinstance(a, str) for a in actions_val):
37
- actions = list(actions_val)
38
- row.append(
19
+ if spec_examples:
20
+ return [
21
+ [
39
22
  ActionExample(
40
- name=str(msg.get("name", "")),
41
- content=Content(text=text, actions=actions),
23
+ name=msg.get("name", ""),
24
+ content=Content(
25
+ text=msg.get("content", {}).get("text", ""),
26
+ actions=msg.get("content", {}).get("actions"),
27
+ ),
42
28
  )
43
- )
44
- if row:
45
- result.append(row)
46
- return result
29
+ for msg in example
30
+ ]
31
+ for example in spec_examples
32
+ ]
33
+ return []
47
34
 
48
35
 
49
36
  @dataclass
@@ -16,34 +16,21 @@ _spec = require_action_spec("UNMUTE_ROOM")
16
16
  def _convert_spec_examples() -> list[list[ActionExample]]:
17
17
  """Convert spec examples to ActionExample format."""
18
18
  spec_examples = _spec.get("examples", [])
19
- if not isinstance(spec_examples, list):
20
- return []
21
- result: list[list[ActionExample]] = []
22
- for example in spec_examples:
23
- if not isinstance(example, list):
24
- continue
25
- row: list[ActionExample] = []
26
- for msg in example:
27
- if not isinstance(msg, dict):
28
- continue
29
- content = msg.get("content", {})
30
- text = ""
31
- actions: list[str] | None = None
32
- if isinstance(content, dict):
33
- text_val = content.get("text", "")
34
- text = str(text_val) if text_val else ""
35
- actions_val = content.get("actions")
36
- if isinstance(actions_val, list) and all(isinstance(a, str) for a in actions_val):
37
- actions = list(actions_val)
38
- row.append(
19
+ if spec_examples:
20
+ return [
21
+ [
39
22
  ActionExample(
40
- name=str(msg.get("name", "")),
41
- content=Content(text=text, actions=actions),
23
+ name=msg.get("name", ""),
24
+ content=Content(
25
+ text=msg.get("content", {}).get("text", ""),
26
+ actions=msg.get("content", {}).get("actions"),
27
+ ),
42
28
  )
43
- )
44
- if row:
45
- result.append(row)
46
- return result
29
+ for msg in example
30
+ ]
31
+ for example in spec_examples
32
+ ]
33
+ return []
47
34
 
48
35
 
49
36
  @dataclass
@@ -13,7 +13,6 @@ from elizaos.types import (
13
13
  Content,
14
14
  ModelType,
15
15
  )
16
- from elizaos.utils.spec_examples import convert_spec_examples
17
16
 
18
17
  if TYPE_CHECKING:
19
18
  from elizaos.types import (
@@ -30,7 +29,22 @@ _spec = require_action_spec("UPDATE_CONTACT")
30
29
 
31
30
  def _convert_spec_examples() -> list[list[ActionExample]]:
32
31
  """Convert spec examples to ActionExample format."""
33
- return convert_spec_examples(_spec)
32
+ spec_examples = _spec.get("examples", [])
33
+ if spec_examples:
34
+ return [
35
+ [
36
+ ActionExample(
37
+ name=msg.get("name", ""),
38
+ content=Content(
39
+ text=msg.get("content", {}).get("text", ""),
40
+ actions=msg.get("content", {}).get("actions"),
41
+ ),
42
+ )
43
+ for msg in example
44
+ ]
45
+ for example in spec_examples
46
+ ]
47
+ return []
34
48
 
35
49
 
36
50
  @dataclass
@@ -8,7 +8,6 @@ from elizaos.bootstrap.utils.xml import parse_key_value_xml
8
8
  from elizaos.generated.spec_helpers import require_action_spec
9
9
  from elizaos.prompts import UPDATE_ENTITY_TEMPLATE
10
10
  from elizaos.types import Action, ActionExample, ActionResult, Content, ModelType
11
- from elizaos.utils.spec_examples import convert_spec_examples
12
11
 
13
12
  if TYPE_CHECKING:
14
13
  from elizaos.types import HandlerCallback, HandlerOptions, IAgentRuntime, Memory, State
@@ -19,7 +18,22 @@ _spec = require_action_spec("UPDATE_ENTITY")
19
18
 
20
19
  def _convert_spec_examples() -> list[list[ActionExample]]:
21
20
  """Convert spec examples to ActionExample format."""
22
- return convert_spec_examples(_spec)
21
+ spec_examples = _spec.get("examples", [])
22
+ if spec_examples:
23
+ return [
24
+ [
25
+ ActionExample(
26
+ name=msg.get("name", ""),
27
+ content=Content(
28
+ text=msg.get("content", {}).get("text", ""),
29
+ actions=msg.get("content", {}).get("actions"),
30
+ ),
31
+ )
32
+ for msg in example
33
+ ]
34
+ for example in spec_examples
35
+ ]
36
+ return []
23
37
 
24
38
 
25
39
  @dataclass
@@ -3,16 +3,9 @@
3
3
  Evaluators that can be enabled with `advanced_capabilities=True`.
4
4
  """
5
5
 
6
- from .reflection import reflection_evaluator
7
- from .relationship_extraction import relationship_extraction_evaluator
8
-
9
6
  __all__ = [
10
- "reflection_evaluator",
11
- "relationship_extraction_evaluator",
12
7
  "advanced_evaluators",
13
8
  ]
14
9
 
15
- advanced_evaluators = [
16
- reflection_evaluator,
17
- relationship_extraction_evaluator,
18
- ]
10
+ # Relationship evaluators are owned by the plugin-rolodex package.
11
+ advanced_evaluators: list = []
@@ -1,134 +1,5 @@
1
- from __future__ import annotations
1
+ """Compatibility wrapper for reflection evaluator."""
2
2
 
3
- from collections.abc import Awaitable, Callable
4
- from typing import TYPE_CHECKING
3
+ from elizaos.bootstrap.evaluators.reflection import reflection_evaluator
5
4
 
6
- from elizaos.bootstrap.types import EvaluatorResult
7
- from elizaos.bootstrap.utils.xml import parse_key_value_xml
8
- from elizaos.generated.spec_helpers import require_evaluator_spec
9
- from elizaos.prompts import REFLECTION_TEMPLATE
10
- from elizaos.types import ActionResult, Evaluator, HandlerOptions, ModelType
11
-
12
- if TYPE_CHECKING:
13
- from elizaos.types import Content, IAgentRuntime, Memory, State
14
-
15
- # Get text content from centralized specs
16
- _spec = require_evaluator_spec("REFLECTION")
17
-
18
-
19
- async def evaluate_reflection(
20
- runtime: IAgentRuntime,
21
- message: Memory,
22
- state: State | None = None,
23
- ) -> EvaluatorResult:
24
- if state is None:
25
- return EvaluatorResult(
26
- score=50,
27
- passed=True,
28
- reason="No state for reflection",
29
- details={},
30
- )
31
-
32
- recent_interactions: list[str] = []
33
- room_id = message.room_id
34
-
35
- if room_id:
36
- recent_messages = await runtime.get_memories(
37
- room_id=room_id,
38
- limit=10,
39
- order_by="created_at",
40
- order_direction="desc",
41
- )
42
-
43
- for msg in recent_messages:
44
- if msg.content and msg.content.text:
45
- sender = "Unknown"
46
- if msg.entity_id:
47
- if str(msg.entity_id) == str(runtime.agent_id):
48
- sender = runtime.character.name
49
- else:
50
- entity = await runtime.get_entity(msg.entity_id)
51
- if entity and entity.name:
52
- sender = entity.name
53
- recent_interactions.append(f"{sender}: {msg.content.text}")
54
-
55
- if not recent_interactions:
56
- return EvaluatorResult(
57
- score=50,
58
- passed=True,
59
- reason="No recent interactions to reflect on",
60
- details={"noInteractions": True},
61
- )
62
-
63
- interactions_text = "\n".join(recent_interactions)
64
-
65
- template = (
66
- runtime.character.templates.get("reflectionTemplate")
67
- if runtime.character.templates and "reflectionTemplate" in runtime.character.templates
68
- else REFLECTION_TEMPLATE
69
- )
70
- prompt = runtime.compose_prompt(state=state, template=template)
71
- prompt = prompt.replace("{{recentInteractions}}", interactions_text)
72
-
73
- response_text = await runtime.use_model(ModelType.TEXT_LARGE, prompt=prompt)
74
- parsed_xml = parse_key_value_xml(response_text)
75
-
76
- if parsed_xml is None:
77
- raise ValueError("Failed to parse reflection response")
78
-
79
- quality_str = str(parsed_xml.get("quality_score", "50"))
80
- quality_score = max(0, min(100, int(quality_str)))
81
-
82
- thought = str(parsed_xml.get("thought", ""))
83
- strengths = str(parsed_xml.get("strengths", ""))
84
- improvements = str(parsed_xml.get("improvements", ""))
85
- learnings = str(parsed_xml.get("learnings", ""))
86
-
87
- passed = quality_score >= 50
88
-
89
- return EvaluatorResult(
90
- score=quality_score,
91
- passed=passed,
92
- reason=f"Strengths: {strengths}\nImprovements: {improvements}",
93
- details={
94
- "thought": thought,
95
- "strengths": strengths,
96
- "improvements": improvements,
97
- "learnings": learnings,
98
- "interactionCount": len(recent_interactions),
99
- },
100
- )
101
-
102
-
103
- async def validate_reflection(
104
- runtime: IAgentRuntime,
105
- message: Memory,
106
- _state: State | None = None,
107
- ) -> bool:
108
- return True
109
-
110
-
111
- async def _reflection_handler(
112
- runtime: IAgentRuntime,
113
- message: Memory,
114
- state: State | None = None,
115
- options: HandlerOptions | None = None,
116
- callback: Callable[[Content], Awaitable[list[Memory]]] | None = None,
117
- responses: list[Memory] | None = None,
118
- ) -> ActionResult | None:
119
- """Wrapper handler that matches the expected signature."""
120
- _ = options, callback, responses # Unused parameters
121
- result = await evaluate_reflection(runtime, message, state)
122
- # Return ActionResult - evaluators don't typically return action results
123
- return ActionResult(text=result.reason, success=result.passed, values={}, data={})
124
-
125
-
126
- reflection_evaluator = Evaluator(
127
- name=str(_spec["name"]),
128
- description=str(_spec["description"]),
129
- similes=list(_spec.get("similes", [])) if _spec.get("similes") else [],
130
- validate=validate_reflection,
131
- handler=_reflection_handler,
132
- always_run=bool(_spec.get("alwaysRun", False)),
133
- examples=[],
134
- )
5
+ __all__ = ["reflection_evaluator"]