@elizaos/python 2.0.0-alpha.3 → 2.0.0-alpha.30
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.
- package/elizaos/__init__.py +0 -1
- package/elizaos/advanced_capabilities/__init__.py +6 -41
- package/elizaos/advanced_capabilities/actions/__init__.py +1 -21
- package/elizaos/advanced_capabilities/actions/add_contact.py +24 -13
- package/elizaos/advanced_capabilities/actions/follow_room.py +29 -29
- package/elizaos/advanced_capabilities/actions/image_generation.py +15 -28
- package/elizaos/advanced_capabilities/actions/mute_room.py +15 -28
- package/elizaos/advanced_capabilities/actions/remove_contact.py +17 -3
- package/elizaos/advanced_capabilities/actions/roles.py +17 -30
- package/elizaos/advanced_capabilities/actions/schedule_follow_up.py +70 -15
- package/elizaos/advanced_capabilities/actions/search_contacts.py +17 -3
- package/elizaos/advanced_capabilities/actions/send_message.py +184 -51
- package/elizaos/advanced_capabilities/actions/settings.py +17 -3
- package/elizaos/advanced_capabilities/actions/unfollow_room.py +15 -28
- package/elizaos/advanced_capabilities/actions/unmute_room.py +15 -28
- package/elizaos/advanced_capabilities/actions/update_contact.py +17 -3
- package/elizaos/advanced_capabilities/actions/update_entity.py +17 -3
- package/elizaos/advanced_capabilities/evaluators/__init__.py +2 -9
- package/elizaos/advanced_capabilities/evaluators/reflection.py +3 -132
- package/elizaos/advanced_capabilities/evaluators/relationship_extraction.py +5 -201
- package/elizaos/advanced_capabilities/providers/__init__.py +1 -12
- package/elizaos/advanced_capabilities/providers/knowledge.py +23 -3
- package/elizaos/advanced_capabilities/services/__init__.py +2 -9
- package/elizaos/advanced_capabilities/services/rolodex.py +2 -2
- package/elizaos/advanced_memory/actions/reset_session.py +143 -0
- package/elizaos/advanced_memory/evaluators/reflection.py +134 -0
- package/elizaos/advanced_memory/evaluators/relationship_extraction.py +203 -0
- package/elizaos/advanced_memory/memory_service.py +69 -27
- package/elizaos/advanced_memory/plugin.py +2 -1
- package/elizaos/advanced_memory/test_advanced_memory.py +357 -0
- package/elizaos/advanced_memory/types.py +2 -2
- package/elizaos/advanced_planning/actions/schedule_follow_up.py +222 -0
- package/elizaos/advanced_planning/planning_service.py +26 -14
- package/elizaos/basic_capabilities/__init__.py +0 -2
- package/elizaos/basic_capabilities/providers/__init__.py +0 -3
- package/elizaos/basic_capabilities/providers/actions.py +118 -29
- package/elizaos/basic_capabilities/providers/agent_settings.py +64 -0
- package/elizaos/basic_capabilities/providers/character.py +19 -21
- package/elizaos/basic_capabilities/providers/contacts.py +79 -0
- package/elizaos/basic_capabilities/providers/current_time.py +7 -4
- package/elizaos/basic_capabilities/providers/facts.py +87 -0
- package/elizaos/basic_capabilities/providers/follow_ups.py +117 -0
- package/elizaos/basic_capabilities/providers/knowledge.py +96 -0
- package/elizaos/basic_capabilities/providers/recent_messages.py +5 -0
- package/elizaos/basic_capabilities/providers/relationships.py +113 -0
- package/elizaos/basic_capabilities/providers/roles.py +96 -0
- package/elizaos/basic_capabilities/providers/settings.py +56 -0
- package/elizaos/basic_capabilities/providers/time.py +7 -4
- package/elizaos/basic_capabilities/services/embedding.py +10 -7
- package/elizaos/basic_capabilities/services/task.py +3 -3
- package/elizaos/bootstrap/__init__.py +21 -2
- package/elizaos/bootstrap/actions/__init__.py +3 -0
- package/elizaos/bootstrap/actions/reset_session.py +3 -0
- package/elizaos/bootstrap/actions/roles.py +5 -4
- package/elizaos/bootstrap/actions/schedule_follow_up.py +65 -7
- package/elizaos/bootstrap/actions/send_message.py +162 -15
- package/elizaos/bootstrap/autonomy/__init__.py +5 -1
- package/elizaos/bootstrap/autonomy/action.py +161 -0
- package/elizaos/bootstrap/autonomy/evaluators.py +217 -0
- package/elizaos/bootstrap/autonomy/service.py +238 -28
- package/elizaos/bootstrap/plugin.py +7 -0
- package/elizaos/bootstrap/providers/actions.py +118 -27
- package/elizaos/bootstrap/providers/agent_settings.py +1 -0
- package/elizaos/bootstrap/providers/attachments.py +1 -0
- package/elizaos/bootstrap/providers/capabilities.py +1 -0
- package/elizaos/bootstrap/providers/character.py +1 -0
- package/elizaos/bootstrap/providers/choice.py +1 -0
- package/elizaos/bootstrap/providers/contacts.py +1 -0
- package/elizaos/bootstrap/providers/current_time.py +8 -2
- package/elizaos/bootstrap/providers/entities.py +1 -0
- package/elizaos/bootstrap/providers/evaluators.py +1 -0
- package/elizaos/bootstrap/providers/facts.py +1 -0
- package/elizaos/bootstrap/providers/follow_ups.py +1 -0
- package/elizaos/bootstrap/providers/knowledge.py +26 -3
- package/elizaos/bootstrap/providers/providers_list.py +1 -0
- package/elizaos/bootstrap/providers/recent_messages.py +5 -0
- package/elizaos/bootstrap/providers/relationships.py +20 -13
- package/elizaos/bootstrap/providers/roles.py +1 -0
- package/elizaos/bootstrap/providers/settings.py +1 -0
- package/elizaos/bootstrap/providers/time.py +8 -4
- package/elizaos/bootstrap/providers/world.py +1 -0
- package/elizaos/bootstrap/services/embedding.py +206 -8
- package/elizaos/bootstrap/services/rolodex.py +2 -2
- package/elizaos/bootstrap/services/task.py +3 -3
- package/elizaos/deterministic.py +193 -0
- package/elizaos/generated/__init__.py +1 -0
- package/elizaos/generated/action_docs.py +3181 -0
- package/elizaos/generated/spec_helpers.py +175 -0
- package/elizaos/media/mime.py +4 -4
- package/elizaos/media/search.py +23 -23
- package/elizaos/runtime.py +223 -64
- package/elizaos/services/hook_service.py +3 -3
- package/elizaos/services/message_service.py +175 -29
- package/elizaos/types/components.py +2 -2
- package/elizaos/types/generated/__init__.py +12 -0
- package/elizaos/types/generated/eliza/v1/agent_pb2.py +63 -0
- package/elizaos/types/generated/eliza/v1/agent_pb2.pyi +159 -0
- package/elizaos/types/generated/eliza/v1/components_pb2.py +65 -0
- package/elizaos/types/generated/eliza/v1/components_pb2.pyi +160 -0
- package/elizaos/types/generated/eliza/v1/database_pb2.py +78 -0
- package/elizaos/types/generated/eliza/v1/database_pb2.pyi +305 -0
- package/elizaos/types/generated/eliza/v1/environment_pb2.py +58 -0
- package/elizaos/types/generated/eliza/v1/environment_pb2.pyi +135 -0
- package/elizaos/types/generated/eliza/v1/events_pb2.py +82 -0
- package/elizaos/types/generated/eliza/v1/events_pb2.pyi +322 -0
- package/elizaos/types/generated/eliza/v1/ipc_pb2.py +113 -0
- package/elizaos/types/generated/eliza/v1/ipc_pb2.pyi +367 -0
- package/elizaos/types/generated/eliza/v1/knowledge_pb2.py +41 -0
- package/elizaos/types/generated/eliza/v1/knowledge_pb2.pyi +26 -0
- package/elizaos/types/generated/eliza/v1/memory_pb2.py +55 -0
- package/elizaos/types/generated/eliza/v1/memory_pb2.pyi +111 -0
- package/elizaos/types/generated/eliza/v1/message_service_pb2.py +48 -0
- package/elizaos/types/generated/eliza/v1/message_service_pb2.pyi +69 -0
- package/elizaos/types/generated/eliza/v1/messaging_pb2.py +51 -0
- package/elizaos/types/generated/eliza/v1/messaging_pb2.pyi +97 -0
- package/elizaos/types/generated/eliza/v1/model_pb2.py +84 -0
- package/elizaos/types/generated/eliza/v1/model_pb2.pyi +280 -0
- package/elizaos/types/generated/eliza/v1/payment_pb2.py +44 -0
- package/elizaos/types/generated/eliza/v1/payment_pb2.pyi +70 -0
- package/elizaos/types/generated/eliza/v1/plugin_pb2.py +68 -0
- package/elizaos/types/generated/eliza/v1/plugin_pb2.pyi +145 -0
- package/elizaos/types/generated/eliza/v1/primitives_pb2.py +48 -0
- package/elizaos/types/generated/eliza/v1/primitives_pb2.pyi +92 -0
- package/elizaos/types/generated/eliza/v1/prompts_pb2.py +52 -0
- package/elizaos/types/generated/eliza/v1/prompts_pb2.pyi +74 -0
- package/elizaos/types/generated/eliza/v1/service_interfaces_pb2.py +211 -0
- package/elizaos/types/generated/eliza/v1/service_interfaces_pb2.pyi +1296 -0
- package/elizaos/types/generated/eliza/v1/service_pb2.py +42 -0
- package/elizaos/types/generated/eliza/v1/service_pb2.pyi +69 -0
- package/elizaos/types/generated/eliza/v1/settings_pb2.py +58 -0
- package/elizaos/types/generated/eliza/v1/settings_pb2.pyi +85 -0
- package/elizaos/types/generated/eliza/v1/state_pb2.py +60 -0
- package/elizaos/types/generated/eliza/v1/state_pb2.pyi +114 -0
- package/elizaos/types/generated/eliza/v1/task_pb2.py +42 -0
- package/elizaos/types/generated/eliza/v1/task_pb2.pyi +58 -0
- package/elizaos/types/generated/eliza/v1/tee_pb2.py +52 -0
- package/elizaos/types/generated/eliza/v1/tee_pb2.pyi +90 -0
- package/elizaos/types/generated/eliza/v1/testing_pb2.py +39 -0
- package/elizaos/types/generated/eliza/v1/testing_pb2.pyi +23 -0
- package/elizaos/types/model.py +33 -3
- package/elizaos/types/primitives.py +3 -3
- package/elizaos/types/runtime.py +17 -3
- package/elizaos/types/state.py +2 -2
- package/elizaos/utils/streaming.py +3 -3
- package/elizaos/utils/validation.py +76 -0
- package/package.json +4 -3
- package/pyproject.toml +1 -2
- package/requirements-dev.lock +2 -2
- package/requirements.in +1 -2
- package/requirements.lock +2 -2
- package/tests/test_action_parameters.py +2 -3
- package/tests/test_actions_provider_examples.py +58 -1
- package/tests/test_advanced_memory_behavior.py +0 -2
- package/tests/test_advanced_memory_flag.py +0 -2
- package/tests/test_advanced_planning_behavior.py +11 -5
- package/tests/test_async_embedding.py +124 -0
- package/tests/test_autonomy.py +24 -3
- package/tests/test_history_compaction.py +104 -0
- package/tests/test_memory_bounds.py +115 -0
- package/tests/test_runtime.py +8 -17
- package/tests/test_schedule_follow_up_action.py +260 -0
- package/tests/test_send_message_action_targets.py +114 -0
- package/tests/test_settings_crypto.py +0 -2
- package/tests/test_validation.py +141 -0
- package/tests/verify_memory_architecture.py +192 -0
- package/uv.lock +1565 -0
- package/elizaos/basic_capabilities/providers/capabilities.py +0 -62
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Awaitable, Callable
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
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
|
+
)
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from collections.abc import Awaitable, Callable
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
from elizaos.bootstrap.types import EvaluatorResult
|
|
8
|
+
from elizaos.generated.spec_helpers import require_evaluator_spec
|
|
9
|
+
from elizaos.types import ActionResult, Evaluator, HandlerOptions
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from elizaos.types import Content, IAgentRuntime, Memory, State
|
|
13
|
+
|
|
14
|
+
# Get text content from centralized specs
|
|
15
|
+
_spec = require_evaluator_spec("RELATIONSHIP_EXTRACTION")
|
|
16
|
+
|
|
17
|
+
X_HANDLE_PATTERN = re.compile(r"@[\w]+")
|
|
18
|
+
EMAIL_PATTERN = re.compile(r"[\w.+-]+@[\w.-]+\.\w+")
|
|
19
|
+
PHONE_PATTERN = re.compile(r"\+?[\d\s\-()]{10,}")
|
|
20
|
+
DISCORD_PATTERN = re.compile(r"[\w]+#\d{4}")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def extract_platform_identities(text: str) -> list[dict[str, str | bool | float]]:
|
|
24
|
+
identities: list[dict[str, str | bool | float]] = []
|
|
25
|
+
|
|
26
|
+
for match in X_HANDLE_PATTERN.finditer(text):
|
|
27
|
+
handle = match.group()
|
|
28
|
+
if handle.lower() not in ("@here", "@everyone", "@channel"):
|
|
29
|
+
identities.append(
|
|
30
|
+
{
|
|
31
|
+
"platform": "x",
|
|
32
|
+
"handle": handle,
|
|
33
|
+
"verified": False,
|
|
34
|
+
"confidence": 0.7,
|
|
35
|
+
}
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
for match in EMAIL_PATTERN.finditer(text):
|
|
39
|
+
identities.append(
|
|
40
|
+
{
|
|
41
|
+
"platform": "email",
|
|
42
|
+
"handle": match.group(),
|
|
43
|
+
"verified": False,
|
|
44
|
+
"confidence": 0.9,
|
|
45
|
+
}
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
for match in DISCORD_PATTERN.finditer(text):
|
|
49
|
+
identities.append(
|
|
50
|
+
{
|
|
51
|
+
"platform": "discord",
|
|
52
|
+
"handle": match.group(),
|
|
53
|
+
"verified": False,
|
|
54
|
+
"confidence": 0.8,
|
|
55
|
+
}
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
return identities
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def detect_relationship_indicators(text: str) -> list[dict[str, str | float]]:
|
|
62
|
+
indicators: list[dict[str, str | float]] = []
|
|
63
|
+
|
|
64
|
+
friend_patterns = [
|
|
65
|
+
r"my friend",
|
|
66
|
+
r"good friend",
|
|
67
|
+
r"best friend",
|
|
68
|
+
r"close friend",
|
|
69
|
+
r"we're friends",
|
|
70
|
+
]
|
|
71
|
+
for pattern in friend_patterns:
|
|
72
|
+
if re.search(pattern, text, re.IGNORECASE):
|
|
73
|
+
indicators.append(
|
|
74
|
+
{
|
|
75
|
+
"type": "friend",
|
|
76
|
+
"sentiment": "positive",
|
|
77
|
+
"confidence": 0.8,
|
|
78
|
+
}
|
|
79
|
+
)
|
|
80
|
+
break
|
|
81
|
+
|
|
82
|
+
colleague_patterns = [
|
|
83
|
+
r"my colleague",
|
|
84
|
+
r"coworker",
|
|
85
|
+
r"co-worker",
|
|
86
|
+
r"work together",
|
|
87
|
+
r"at work",
|
|
88
|
+
]
|
|
89
|
+
for pattern in colleague_patterns:
|
|
90
|
+
if re.search(pattern, text, re.IGNORECASE):
|
|
91
|
+
indicators.append(
|
|
92
|
+
{
|
|
93
|
+
"type": "colleague",
|
|
94
|
+
"sentiment": "neutral",
|
|
95
|
+
"confidence": 0.8,
|
|
96
|
+
}
|
|
97
|
+
)
|
|
98
|
+
break
|
|
99
|
+
|
|
100
|
+
family_patterns = [
|
|
101
|
+
r"my (brother|sister|mom|dad|mother|father|parent|son|daughter|child)",
|
|
102
|
+
r"my family",
|
|
103
|
+
r"family member",
|
|
104
|
+
]
|
|
105
|
+
for pattern in family_patterns:
|
|
106
|
+
if re.search(pattern, text, re.IGNORECASE):
|
|
107
|
+
indicators.append(
|
|
108
|
+
{
|
|
109
|
+
"type": "family",
|
|
110
|
+
"sentiment": "positive",
|
|
111
|
+
"confidence": 0.9,
|
|
112
|
+
}
|
|
113
|
+
)
|
|
114
|
+
break
|
|
115
|
+
|
|
116
|
+
return indicators
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
async def evaluate_relationship_extraction(
|
|
120
|
+
runtime: IAgentRuntime,
|
|
121
|
+
message: Memory,
|
|
122
|
+
state: State | None = None,
|
|
123
|
+
) -> EvaluatorResult:
|
|
124
|
+
text = message.content.text if message.content else ""
|
|
125
|
+
|
|
126
|
+
if not text:
|
|
127
|
+
return EvaluatorResult(
|
|
128
|
+
score=50,
|
|
129
|
+
passed=True,
|
|
130
|
+
reason="No text to analyze",
|
|
131
|
+
details={"noText": True},
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
identities = extract_platform_identities(text)
|
|
135
|
+
|
|
136
|
+
indicators = detect_relationship_indicators(text)
|
|
137
|
+
|
|
138
|
+
if identities and message.entity_id:
|
|
139
|
+
entity = await runtime.get_entity(str(message.entity_id))
|
|
140
|
+
if entity:
|
|
141
|
+
metadata = entity.metadata or {}
|
|
142
|
+
existing_identities = metadata.get("platformIdentities", [])
|
|
143
|
+
if isinstance(existing_identities, list):
|
|
144
|
+
for identity in identities:
|
|
145
|
+
exists = any(
|
|
146
|
+
i.get("platform") == identity["platform"]
|
|
147
|
+
and i.get("handle") == identity["handle"]
|
|
148
|
+
for i in existing_identities
|
|
149
|
+
if isinstance(i, dict)
|
|
150
|
+
)
|
|
151
|
+
if not exists:
|
|
152
|
+
existing_identities.append(identity)
|
|
153
|
+
metadata["platformIdentities"] = existing_identities
|
|
154
|
+
entity.metadata = metadata
|
|
155
|
+
await runtime.update_entity(entity)
|
|
156
|
+
|
|
157
|
+
runtime.logger.info(
|
|
158
|
+
f"Completed extraction: src=evaluator:relationship_extraction agentId={runtime.agent_id} identitiesFound={len(identities)} indicatorsFound={len(indicators)}"
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
return EvaluatorResult(
|
|
162
|
+
score=70,
|
|
163
|
+
passed=True,
|
|
164
|
+
reason=f"Found {len(identities)} identities and {len(indicators)} relationship indicators",
|
|
165
|
+
details={
|
|
166
|
+
"identitiesCount": len(identities),
|
|
167
|
+
"indicatorsCount": len(indicators),
|
|
168
|
+
},
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
async def validate_relationship_extraction(
|
|
173
|
+
runtime: IAgentRuntime,
|
|
174
|
+
message: Memory,
|
|
175
|
+
_state: State | None = None,
|
|
176
|
+
) -> bool:
|
|
177
|
+
return message.content is not None and bool(message.content.text)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
async def _relationship_extraction_handler(
|
|
181
|
+
runtime: IAgentRuntime,
|
|
182
|
+
message: Memory,
|
|
183
|
+
state: State | None = None,
|
|
184
|
+
options: HandlerOptions | None = None,
|
|
185
|
+
callback: Callable[[Content], Awaitable[list[Memory]]] | None = None,
|
|
186
|
+
responses: list[Memory] | None = None,
|
|
187
|
+
) -> ActionResult | None:
|
|
188
|
+
"""Wrapper handler that matches the expected signature."""
|
|
189
|
+
_ = options, callback, responses # Unused parameters
|
|
190
|
+
result = await evaluate_relationship_extraction(runtime, message, state)
|
|
191
|
+
# Return None as ActionResult - evaluators don't typically return action results
|
|
192
|
+
return ActionResult(text=result.reason, success=result.passed, values={}, data={})
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
relationship_extraction_evaluator = Evaluator(
|
|
196
|
+
name=str(_spec["name"]),
|
|
197
|
+
description=str(_spec["description"]),
|
|
198
|
+
similes=list(_spec.get("similes", [])) if _spec.get("similes") else [],
|
|
199
|
+
validate=validate_relationship_extraction,
|
|
200
|
+
handler=_relationship_extraction_handler,
|
|
201
|
+
always_run=bool(_spec.get("alwaysRun", False)),
|
|
202
|
+
examples=[],
|
|
203
|
+
)
|
|
@@ -3,7 +3,8 @@ from __future__ import annotations
|
|
|
3
3
|
import heapq
|
|
4
4
|
import re
|
|
5
5
|
import time
|
|
6
|
-
from
|
|
6
|
+
from collections import OrderedDict
|
|
7
|
+
from typing import TypeVar, cast
|
|
7
8
|
from uuid import UUID, uuid4
|
|
8
9
|
|
|
9
10
|
from elizaos.types.model import ModelType
|
|
@@ -27,6 +28,7 @@ from .types import (
|
|
|
27
28
|
_TABLE_SESSION_SUMMARY = "session_summary"
|
|
28
29
|
_TABLE_LONG_TERM_MEMORY = "long_term_memory"
|
|
29
30
|
_GLOBAL_LONG_TERM_ROOM_ID = string_to_uuid("advanced-memory:long-term")
|
|
31
|
+
_OrderedValueT = TypeVar("_OrderedValueT")
|
|
30
32
|
|
|
31
33
|
|
|
32
34
|
def _parse_summary_xml(xml: str) -> SummaryResult:
|
|
@@ -79,14 +81,28 @@ def _top_k_by_confidence(items: list[LongTermMemory], limit: int) -> list[LongTe
|
|
|
79
81
|
|
|
80
82
|
class MemoryService(Service):
|
|
81
83
|
service_type = "memory"
|
|
84
|
+
_MAX_LOCAL_SESSION_SUMMARIES = 500
|
|
85
|
+
_MAX_LOCAL_EXTRACTION_CHECKPOINTS = 500
|
|
86
|
+
_MAX_LOCAL_LONG_TERM_ENTITIES = 500
|
|
87
|
+
_MAX_LOCAL_LONG_TERM_PER_ENTITY = 200
|
|
82
88
|
|
|
83
89
|
def __init__(self, runtime=None) -> None:
|
|
84
90
|
super().__init__(runtime=runtime)
|
|
85
91
|
self._config: MemoryConfig = MemoryConfig()
|
|
86
92
|
# Fallback storage for runtimes without a DB adapter (tests/benchmarks).
|
|
87
|
-
self._session_summaries:
|
|
88
|
-
self._long_term:
|
|
89
|
-
self._extraction_checkpoints:
|
|
93
|
+
self._session_summaries: OrderedDict[str, SessionSummary] = OrderedDict()
|
|
94
|
+
self._long_term: OrderedDict[str, list[LongTermMemory]] = OrderedDict()
|
|
95
|
+
self._extraction_checkpoints: OrderedDict[str, int] = OrderedDict()
|
|
96
|
+
|
|
97
|
+
@staticmethod
|
|
98
|
+
def _touch_ordered(mapping: OrderedDict[str, _OrderedValueT], key: str) -> None:
|
|
99
|
+
if key in mapping:
|
|
100
|
+
mapping.move_to_end(key)
|
|
101
|
+
|
|
102
|
+
@staticmethod
|
|
103
|
+
def _prune_ordered(mapping: OrderedDict[str, _OrderedValueT], max_entries: int) -> None:
|
|
104
|
+
while len(mapping) > max_entries:
|
|
105
|
+
mapping.popitem(last=False)
|
|
90
106
|
|
|
91
107
|
@property
|
|
92
108
|
def capability_description(self) -> str:
|
|
@@ -95,38 +111,36 @@ class MemoryService(Service):
|
|
|
95
111
|
@classmethod
|
|
96
112
|
async def start(cls, runtime):
|
|
97
113
|
svc = cls(runtime=runtime)
|
|
98
|
-
|
|
99
|
-
settings = runtime.character.settings or {}
|
|
100
|
-
if (v := settings.get("MEMORY_SUMMARIZATION_THRESHOLD")) is not None and isinstance(
|
|
114
|
+
if (v := runtime.get_setting("MEMORY_SUMMARIZATION_THRESHOLD")) is not None and isinstance(
|
|
101
115
|
v, (int, float, str)
|
|
102
116
|
):
|
|
103
117
|
svc._config.short_term_summarization_threshold = int(v)
|
|
104
|
-
if (v :=
|
|
118
|
+
if (v := runtime.get_setting("MEMORY_RETAIN_RECENT")) is not None and isinstance(
|
|
105
119
|
v, (int, float, str)
|
|
106
120
|
):
|
|
107
121
|
svc._config.short_term_retain_recent = int(v)
|
|
108
|
-
if (v :=
|
|
122
|
+
if (v := runtime.get_setting("MEMORY_SUMMARIZATION_INTERVAL")) is not None and isinstance(
|
|
109
123
|
v, (int, float, str)
|
|
110
124
|
):
|
|
111
125
|
svc._config.short_term_summarization_interval = int(v)
|
|
112
|
-
if (v :=
|
|
126
|
+
if (v := runtime.get_setting("MEMORY_MAX_NEW_MESSAGES")) is not None and isinstance(
|
|
113
127
|
v, (int, float, str)
|
|
114
128
|
):
|
|
115
129
|
svc._config.summary_max_new_messages = int(v)
|
|
116
|
-
if (v :=
|
|
130
|
+
if (v := runtime.get_setting("MEMORY_LONG_TERM_ENABLED")) is not None:
|
|
117
131
|
if str(v).lower() == "false":
|
|
118
132
|
svc._config.long_term_extraction_enabled = False
|
|
119
133
|
elif str(v).lower() == "true":
|
|
120
134
|
svc._config.long_term_extraction_enabled = True
|
|
121
|
-
if (v :=
|
|
135
|
+
if (v := runtime.get_setting("MEMORY_CONFIDENCE_THRESHOLD")) is not None and isinstance(
|
|
122
136
|
v, (int, float, str)
|
|
123
137
|
):
|
|
124
138
|
svc._config.long_term_confidence_threshold = float(v)
|
|
125
|
-
if (v :=
|
|
139
|
+
if (v := runtime.get_setting("MEMORY_EXTRACTION_THRESHOLD")) is not None and isinstance(
|
|
126
140
|
v, (int, float, str)
|
|
127
141
|
):
|
|
128
142
|
svc._config.long_term_extraction_threshold = int(v)
|
|
129
|
-
if (v :=
|
|
143
|
+
if (v := runtime.get_setting("MEMORY_EXTRACTION_INTERVAL")) is not None and isinstance(
|
|
130
144
|
v, (int, float, str)
|
|
131
145
|
):
|
|
132
146
|
svc._config.long_term_extraction_interval = int(v)
|
|
@@ -146,7 +160,7 @@ class MemoryService(Service):
|
|
|
146
160
|
return f"memory:extraction:{entity_id}:{room_id}"
|
|
147
161
|
|
|
148
162
|
async def get_last_extraction_checkpoint(self, entity_id: UUID, room_id: UUID) -> int:
|
|
149
|
-
runtime = self.
|
|
163
|
+
runtime = self._runtime
|
|
150
164
|
key = self._checkpoint_key(entity_id, room_id)
|
|
151
165
|
if runtime is not None and getattr(runtime, "_adapter", None) is not None:
|
|
152
166
|
cached = await runtime.get_cache(key)
|
|
@@ -158,17 +172,23 @@ class MemoryService(Service):
|
|
|
158
172
|
return 0
|
|
159
173
|
except Exception:
|
|
160
174
|
return 0
|
|
161
|
-
|
|
175
|
+
checkpoint = self._extraction_checkpoints.get(key)
|
|
176
|
+
if checkpoint is None:
|
|
177
|
+
return 0
|
|
178
|
+
self._touch_ordered(self._extraction_checkpoints, key)
|
|
179
|
+
return int(checkpoint)
|
|
162
180
|
|
|
163
181
|
async def set_last_extraction_checkpoint(
|
|
164
182
|
self, entity_id: UUID, room_id: UUID, message_count: int
|
|
165
183
|
) -> None:
|
|
166
|
-
runtime = self.
|
|
184
|
+
runtime = self._runtime
|
|
167
185
|
key = self._checkpoint_key(entity_id, room_id)
|
|
168
186
|
if runtime is not None and getattr(runtime, "_adapter", None) is not None:
|
|
169
187
|
_ = await runtime.set_cache(key, int(message_count))
|
|
170
188
|
return
|
|
171
189
|
self._extraction_checkpoints[key] = int(message_count)
|
|
190
|
+
self._touch_ordered(self._extraction_checkpoints, key)
|
|
191
|
+
self._prune_ordered(self._extraction_checkpoints, self._MAX_LOCAL_EXTRACTION_CHECKPOINTS)
|
|
172
192
|
|
|
173
193
|
async def should_run_extraction(
|
|
174
194
|
self, entity_id: UUID, room_id: UUID, current_message_count: int
|
|
@@ -182,7 +202,7 @@ class MemoryService(Service):
|
|
|
182
202
|
return current_cp > last_cp
|
|
183
203
|
|
|
184
204
|
async def get_current_session_summary(self, room_id: UUID) -> SessionSummary | None:
|
|
185
|
-
runtime = self.
|
|
205
|
+
runtime = self._runtime
|
|
186
206
|
if runtime is None:
|
|
187
207
|
return None
|
|
188
208
|
|
|
@@ -221,7 +241,11 @@ class MemoryService(Service):
|
|
|
221
241
|
# Best-effort; ignore corrupt rows.
|
|
222
242
|
continue
|
|
223
243
|
|
|
224
|
-
|
|
244
|
+
summary = self._session_summaries.get(str(room_id))
|
|
245
|
+
if summary is None:
|
|
246
|
+
return None
|
|
247
|
+
self._touch_ordered(self._session_summaries, str(room_id))
|
|
248
|
+
return summary
|
|
225
249
|
|
|
226
250
|
async def store_session_summary(
|
|
227
251
|
self,
|
|
@@ -234,7 +258,7 @@ class MemoryService(Service):
|
|
|
234
258
|
topics: list[str] | None = None,
|
|
235
259
|
metadata: dict[str, object] | None = None,
|
|
236
260
|
) -> SessionSummary:
|
|
237
|
-
runtime = self.
|
|
261
|
+
runtime = self._runtime
|
|
238
262
|
s = SessionSummary(
|
|
239
263
|
id=uuid4(),
|
|
240
264
|
agent_id=agent_id,
|
|
@@ -273,13 +297,16 @@ class MemoryService(Service):
|
|
|
273
297
|
)
|
|
274
298
|
return s
|
|
275
299
|
|
|
276
|
-
|
|
300
|
+
room_key = str(room_id)
|
|
301
|
+
self._session_summaries[room_key] = s
|
|
302
|
+
self._touch_ordered(self._session_summaries, room_key)
|
|
303
|
+
self._prune_ordered(self._session_summaries, self._MAX_LOCAL_SESSION_SUMMARIES)
|
|
277
304
|
return s
|
|
278
305
|
|
|
279
306
|
async def update_session_summary(
|
|
280
307
|
self, summary_id: UUID, room_id: UUID, **updates: object
|
|
281
308
|
) -> None:
|
|
282
|
-
runtime = self.
|
|
309
|
+
runtime = self._runtime
|
|
283
310
|
if runtime is not None and getattr(runtime, "_adapter", None) is not None:
|
|
284
311
|
existing = await self.get_current_session_summary(room_id)
|
|
285
312
|
if not existing or existing.id != summary_id:
|
|
@@ -324,9 +351,11 @@ class MemoryService(Service):
|
|
|
324
351
|
)
|
|
325
352
|
return
|
|
326
353
|
|
|
327
|
-
|
|
354
|
+
room_key = str(room_id)
|
|
355
|
+
existing = self._session_summaries.get(room_key)
|
|
328
356
|
if not existing or existing.id != summary_id:
|
|
329
357
|
return
|
|
358
|
+
self._touch_ordered(self._session_summaries, room_key)
|
|
330
359
|
if "summary" in updates and isinstance(updates["summary"], str):
|
|
331
360
|
existing.summary = updates["summary"]
|
|
332
361
|
if "message_count" in updates and isinstance(updates["message_count"], (int, float, str)):
|
|
@@ -350,7 +379,7 @@ class MemoryService(Service):
|
|
|
350
379
|
source: str | None = None,
|
|
351
380
|
metadata: dict[str, object] | None = None,
|
|
352
381
|
) -> LongTermMemory:
|
|
353
|
-
runtime = self.
|
|
382
|
+
runtime = self._runtime
|
|
354
383
|
m = LongTermMemory(
|
|
355
384
|
id=uuid4(),
|
|
356
385
|
agent_id=agent_id,
|
|
@@ -383,7 +412,17 @@ class MemoryService(Service):
|
|
|
383
412
|
)
|
|
384
413
|
return m
|
|
385
414
|
|
|
386
|
-
|
|
415
|
+
entity_key = str(entity_id)
|
|
416
|
+
bucket = self._long_term.get(entity_key)
|
|
417
|
+
if bucket is None:
|
|
418
|
+
bucket = []
|
|
419
|
+
self._long_term[entity_key] = bucket
|
|
420
|
+
else:
|
|
421
|
+
self._touch_ordered(self._long_term, entity_key)
|
|
422
|
+
bucket.append(m)
|
|
423
|
+
if len(bucket) > self._MAX_LOCAL_LONG_TERM_PER_ENTITY:
|
|
424
|
+
del bucket[: len(bucket) - self._MAX_LOCAL_LONG_TERM_PER_ENTITY]
|
|
425
|
+
self._prune_ordered(self._long_term, self._MAX_LOCAL_LONG_TERM_ENTITIES)
|
|
387
426
|
return m
|
|
388
427
|
|
|
389
428
|
async def get_long_term_memories(
|
|
@@ -394,7 +433,7 @@ class MemoryService(Service):
|
|
|
394
433
|
) -> list[LongTermMemory]:
|
|
395
434
|
if limit <= 0:
|
|
396
435
|
return []
|
|
397
|
-
runtime = self.
|
|
436
|
+
runtime = self._runtime
|
|
398
437
|
if runtime is not None and getattr(runtime, "_adapter", None) is not None:
|
|
399
438
|
db_mems = await runtime.get_memories(
|
|
400
439
|
{
|
|
@@ -439,7 +478,10 @@ class MemoryService(Service):
|
|
|
439
478
|
continue
|
|
440
479
|
return _top_k_by_confidence(out, limit)
|
|
441
480
|
|
|
442
|
-
|
|
481
|
+
entity_key = str(entity_id)
|
|
482
|
+
local_mems = self._long_term.get(entity_key, [])
|
|
483
|
+
if entity_key in self._long_term:
|
|
484
|
+
self._touch_ordered(self._long_term, entity_key)
|
|
443
485
|
if category is not None:
|
|
444
486
|
local_mems = [m for m in local_mems if m.category == category]
|
|
445
487
|
return _top_k_by_confidence(local_mems, limit)
|
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from elizaos.types import Plugin
|
|
4
4
|
|
|
5
|
+
from .actions.reset_session import reset_session_action
|
|
5
6
|
from .evaluators import long_term_extraction_evaluator, summarization_evaluator
|
|
6
7
|
from .memory_service import MemoryService
|
|
7
8
|
from .providers import context_summary_provider, long_term_memory_provider
|
|
@@ -21,7 +22,7 @@ def create_advanced_memory_plugin() -> Plugin:
|
|
|
21
22
|
init=init_plugin,
|
|
22
23
|
config={},
|
|
23
24
|
services=[MemoryService],
|
|
24
|
-
actions=[],
|
|
25
|
+
actions=[reset_session_action],
|
|
25
26
|
providers=[long_term_memory_provider, context_summary_provider],
|
|
26
27
|
evaluators=[summarization_evaluator, long_term_extraction_evaluator],
|
|
27
28
|
)
|