@elizaos/python 2.0.0-alpha.11 → 2.0.0-alpha.27
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/advanced_capabilities/__init__.py +6 -41
- package/elizaos/advanced_capabilities/actions/__init__.py +1 -21
- package/elizaos/advanced_capabilities/actions/add_contact.py +21 -11
- package/elizaos/advanced_capabilities/actions/follow_room.py +28 -28
- package/elizaos/advanced_capabilities/actions/image_generation.py +13 -26
- package/elizaos/advanced_capabilities/actions/mute_room.py +13 -26
- package/elizaos/advanced_capabilities/actions/remove_contact.py +16 -2
- package/elizaos/advanced_capabilities/actions/roles.py +13 -27
- package/elizaos/advanced_capabilities/actions/search_contacts.py +17 -3
- package/elizaos/advanced_capabilities/actions/send_message.py +317 -9
- package/elizaos/advanced_capabilities/actions/settings.py +16 -2
- package/elizaos/advanced_capabilities/actions/unfollow_room.py +13 -26
- package/elizaos/advanced_capabilities/actions/unmute_room.py +13 -26
- package/elizaos/advanced_capabilities/actions/update_contact.py +16 -2
- package/elizaos/advanced_capabilities/actions/update_entity.py +16 -2
- 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 +24 -3
- package/elizaos/advanced_capabilities/services/__init__.py +2 -9
- package/elizaos/advanced_memory/actions/reset_session.py +11 -0
- package/elizaos/advanced_memory/evaluators/reflection.py +134 -0
- package/elizaos/advanced_memory/evaluators/relationship_extraction.py +203 -0
- package/elizaos/advanced_memory/test_advanced_memory.py +357 -0
- package/elizaos/advanced_planning/actions/schedule_follow_up.py +222 -0
- package/elizaos/basic_capabilities/__init__.py +0 -2
- package/elizaos/basic_capabilities/providers/__init__.py +0 -3
- package/elizaos/basic_capabilities/providers/agent_settings.py +64 -0
- package/elizaos/basic_capabilities/providers/contacts.py +79 -0
- 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 +97 -0
- package/elizaos/basic_capabilities/providers/relationships.py +107 -0
- package/elizaos/basic_capabilities/providers/roles.py +96 -0
- package/elizaos/basic_capabilities/providers/settings.py +56 -0
- 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 +8 -0
- package/elizaos/bootstrap/plugin.py +7 -0
- package/elizaos/bootstrap/providers/knowledge.py +26 -3
- package/elizaos/bootstrap/services/embedding.py +156 -1
- package/elizaos/runtime.py +63 -18
- package/elizaos/services/message_service.py +173 -23
- package/elizaos/types/generated/eliza/v1/agent_pb2.py +16 -16
- package/elizaos/types/generated/eliza/v1/agent_pb2.pyi +2 -4
- package/elizaos/types/model.py +27 -0
- package/elizaos/types/runtime.py +5 -1
- package/elizaos/utils/validation.py +76 -0
- package/package.json +2 -2
- package/tests/test_actions_provider_examples.py +58 -1
- package/tests/test_async_embedding.py +124 -0
- package/tests/test_autonomy.py +13 -2
- package/tests/test_validation.py +141 -0
- package/tests/verify_memory_architecture.py +192 -0
- package/elizaos/basic_capabilities/providers/capabilities.py +0 -62
|
@@ -1,203 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
"""Compatibility wrapper for relationship extraction evaluator."""
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
|
|
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=[],
|
|
3
|
+
from elizaos.bootstrap.evaluators.relationship_extraction import (
|
|
4
|
+
relationship_extraction_evaluator,
|
|
203
5
|
)
|
|
6
|
+
|
|
7
|
+
__all__ = ["relationship_extraction_evaluator"]
|
|
@@ -4,32 +4,21 @@ Extended providers that can be enabled with `advanced_capabilities=True`.
|
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
6
|
from .agent_settings import agent_settings_provider
|
|
7
|
-
from .contacts import contacts_provider
|
|
8
|
-
from .facts import facts_provider
|
|
9
|
-
from .follow_ups import follow_ups_provider
|
|
10
7
|
from .knowledge import knowledge_provider
|
|
11
|
-
from .relationships import relationships_provider
|
|
12
8
|
from .roles import roles_provider
|
|
13
9
|
from .settings import settings_provider
|
|
14
10
|
|
|
15
11
|
__all__ = [
|
|
16
12
|
"agent_settings_provider",
|
|
17
|
-
"contacts_provider",
|
|
18
|
-
"facts_provider",
|
|
19
|
-
"follow_ups_provider",
|
|
20
13
|
"knowledge_provider",
|
|
21
|
-
"relationships_provider",
|
|
22
14
|
"roles_provider",
|
|
23
15
|
"settings_provider",
|
|
24
16
|
"advanced_providers",
|
|
25
17
|
]
|
|
26
18
|
|
|
19
|
+
# Rolodex/contact providers are provided by plugin-rolodex.
|
|
27
20
|
advanced_providers = [
|
|
28
|
-
contacts_provider,
|
|
29
|
-
facts_provider,
|
|
30
|
-
follow_ups_provider,
|
|
31
21
|
knowledge_provider,
|
|
32
|
-
relationships_provider,
|
|
33
22
|
roles_provider,
|
|
34
23
|
agent_settings_provider,
|
|
35
24
|
settings_provider,
|
|
@@ -3,6 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
from typing import TYPE_CHECKING
|
|
4
4
|
|
|
5
5
|
from elizaos.types import Provider, ProviderResult
|
|
6
|
+
from elizaos.types.database import MemorySearchOptions
|
|
6
7
|
|
|
7
8
|
if TYPE_CHECKING:
|
|
8
9
|
from elizaos.types import IAgentRuntime, Memory, State
|
|
@@ -25,11 +26,31 @@ async def get_knowledge_context(
|
|
|
25
26
|
text="", values={"knowledgeCount": 0, "hasKnowledge": False}, data={"entries": []}
|
|
26
27
|
)
|
|
27
28
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
limit=5,
|
|
29
|
+
# 1. Fetch recent messages to get embeddings
|
|
30
|
+
recent_messages = await runtime.get_memories(
|
|
31
|
+
room_id=message.room_id, limit=5, table_name="messages"
|
|
31
32
|
)
|
|
32
33
|
|
|
34
|
+
# 2. Extract valid embeddings
|
|
35
|
+
embeddings = [m.embedding for m in recent_messages if m and m.embedding]
|
|
36
|
+
|
|
37
|
+
relevant_knowledge = []
|
|
38
|
+
# 3. Search using the most recent embedding if available
|
|
39
|
+
if embeddings:
|
|
40
|
+
primary_embedding = embeddings[0]
|
|
41
|
+
params = MemorySearchOptions(
|
|
42
|
+
table_name="knowledge",
|
|
43
|
+
room_id=message.room_id,
|
|
44
|
+
embedding=primary_embedding,
|
|
45
|
+
match_threshold=0.75,
|
|
46
|
+
match_count=5,
|
|
47
|
+
unique=True,
|
|
48
|
+
)
|
|
49
|
+
relevant_knowledge = await runtime.search_memories(params)
|
|
50
|
+
elif query_text:
|
|
51
|
+
# Fallback skipped for parity with TS/Bootstrap
|
|
52
|
+
pass
|
|
53
|
+
|
|
33
54
|
for entry in relevant_knowledge:
|
|
34
55
|
# Handle both dict and object entries
|
|
35
56
|
if isinstance(entry, dict):
|
|
@@ -3,16 +3,9 @@
|
|
|
3
3
|
Services that can be enabled with `advanced_capabilities=True`.
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
-
from .follow_up import FollowUpService
|
|
7
|
-
from .rolodex import RolodexService
|
|
8
|
-
|
|
9
6
|
__all__ = [
|
|
10
|
-
"FollowUpService",
|
|
11
|
-
"RolodexService",
|
|
12
7
|
"advanced_services",
|
|
13
8
|
]
|
|
14
9
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
FollowUpService,
|
|
18
|
-
]
|
|
10
|
+
# Rolodex/follow-up services are owned by plugin-rolodex.
|
|
11
|
+
advanced_services: list[type] = []
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from ...types import Action
|
|
2
|
+
|
|
3
|
+
# TODO: Implement reset_session action
|
|
4
|
+
reset_session_action: Action = {
|
|
5
|
+
"name": "RESET_SESSION",
|
|
6
|
+
"similes": [],
|
|
7
|
+
"description": "Reset the session (Implementation TODO)",
|
|
8
|
+
"handler": None,
|
|
9
|
+
"validate": None,
|
|
10
|
+
"examples": [],
|
|
11
|
+
}
|
|
@@ -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
|
+
)
|