@elizaos/python 2.0.0-alpha.27 → 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/advanced_capabilities/actions/add_contact.py +4 -3
- package/elizaos/advanced_capabilities/actions/follow_room.py +2 -2
- package/elizaos/advanced_capabilities/actions/image_generation.py +2 -2
- package/elizaos/advanced_capabilities/actions/mute_room.py +2 -2
- package/elizaos/advanced_capabilities/actions/remove_contact.py +2 -2
- package/elizaos/advanced_capabilities/actions/roles.py +5 -4
- package/elizaos/advanced_capabilities/actions/search_contacts.py +3 -3
- package/elizaos/advanced_capabilities/actions/send_message.py +2 -2
- package/elizaos/advanced_capabilities/actions/settings.py +2 -2
- package/elizaos/advanced_capabilities/actions/unfollow_room.py +2 -2
- package/elizaos/advanced_capabilities/actions/unmute_room.py +2 -2
- package/elizaos/advanced_capabilities/actions/update_contact.py +2 -2
- package/elizaos/advanced_capabilities/actions/update_entity.py +2 -2
- package/elizaos/advanced_capabilities/providers/knowledge.py +8 -9
- package/elizaos/advanced_capabilities/services/rolodex.py +2 -2
- package/elizaos/advanced_memory/actions/reset_session.py +143 -11
- package/elizaos/advanced_memory/memory_service.py +54 -10
- package/elizaos/advanced_memory/plugin.py +2 -1
- package/elizaos/advanced_memory/types.py +2 -2
- package/elizaos/advanced_planning/actions/schedule_follow_up.py +2 -2
- package/elizaos/basic_capabilities/providers/contacts.py +1 -1
- package/elizaos/basic_capabilities/providers/follow_ups.py +1 -1
- package/elizaos/basic_capabilities/providers/knowledge.py +8 -9
- package/elizaos/basic_capabilities/providers/recent_messages.py +5 -0
- package/elizaos/basic_capabilities/providers/relationships.py +19 -13
- package/elizaos/basic_capabilities/services/embedding.py +10 -7
- package/elizaos/basic_capabilities/services/task.py +3 -3
- 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/providers/knowledge.py +8 -9
- package/elizaos/bootstrap/providers/recent_messages.py +5 -0
- package/elizaos/bootstrap/providers/relationships.py +19 -13
- package/elizaos/bootstrap/services/embedding.py +53 -10
- package/elizaos/bootstrap/services/rolodex.py +2 -2
- package/elizaos/bootstrap/services/task.py +3 -3
- package/elizaos/media/mime.py +2 -2
- package/elizaos/runtime.py +10 -9
- package/elizaos/services/hook_service.py +3 -3
- package/elizaos/types/generated/eliza/v1/agent_pb2.py +4 -4
- package/elizaos/types/generated/eliza/v1/components_pb2.py +4 -4
- package/elizaos/types/generated/eliza/v1/database_pb2.py +4 -4
- package/elizaos/types/generated/eliza/v1/environment_pb2.py +4 -4
- package/elizaos/types/generated/eliza/v1/events_pb2.py +4 -4
- package/elizaos/types/generated/eliza/v1/ipc_pb2.py +4 -4
- package/elizaos/types/generated/eliza/v1/knowledge_pb2.py +4 -4
- package/elizaos/types/generated/eliza/v1/memory_pb2.py +4 -4
- package/elizaos/types/generated/eliza/v1/message_service_pb2.py +4 -4
- package/elizaos/types/generated/eliza/v1/messaging_pb2.py +4 -4
- package/elizaos/types/generated/eliza/v1/model_pb2.py +4 -4
- package/elizaos/types/generated/eliza/v1/payment_pb2.py +4 -4
- package/elizaos/types/generated/eliza/v1/plugin_pb2.py +4 -4
- package/elizaos/types/generated/eliza/v1/primitives_pb2.py +4 -4
- package/elizaos/types/generated/eliza/v1/prompts_pb2.py +4 -4
- package/elizaos/types/generated/eliza/v1/service_interfaces_pb2.py +4 -4
- package/elizaos/types/generated/eliza/v1/service_pb2.py +4 -4
- package/elizaos/types/generated/eliza/v1/settings_pb2.py +4 -4
- package/elizaos/types/generated/eliza/v1/state_pb2.py +4 -4
- package/elizaos/types/generated/eliza/v1/task_pb2.py +4 -4
- package/elizaos/types/generated/eliza/v1/tee_pb2.py +4 -4
- package/elizaos/types/generated/eliza/v1/testing_pb2.py +4 -4
- package/elizaos/types/model.py +3 -3
- package/elizaos/types/primitives.py +3 -3
- package/elizaos/types/runtime.py +12 -2
- package/elizaos/types/state.py +2 -2
- package/elizaos/utils/streaming.py +3 -3
- package/package.json +3 -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_history_compaction.py +104 -0
- package/tests/test_memory_bounds.py +115 -0
- package/tests/test_validation.py +1 -1
- package/uv.lock +10 -10
|
@@ -30,7 +30,7 @@ async def get_contacts_context(
|
|
|
30
30
|
|
|
31
31
|
contact_details: list[dict[str, str]] = []
|
|
32
32
|
for contact in contacts:
|
|
33
|
-
entity = await runtime.get_entity(contact.entity_id)
|
|
33
|
+
entity = await runtime.get_entity(str(contact.entity_id))
|
|
34
34
|
name = entity.name if entity and entity.name else "Unknown"
|
|
35
35
|
contact_details.append(
|
|
36
36
|
{
|
|
@@ -38,7 +38,7 @@ async def get_follow_ups_context(
|
|
|
38
38
|
|
|
39
39
|
for task in upcoming:
|
|
40
40
|
scheduled = datetime.fromisoformat(task.scheduled_at.replace("Z", "+00:00"))
|
|
41
|
-
entity = await runtime.get_entity(task.entity_id)
|
|
41
|
+
entity = await runtime.get_entity(str(task.entity_id))
|
|
42
42
|
name = entity.name if entity and entity.name else "Unknown"
|
|
43
43
|
|
|
44
44
|
item = {
|
|
@@ -4,7 +4,6 @@ from typing import TYPE_CHECKING
|
|
|
4
4
|
|
|
5
5
|
from elizaos.generated.spec_helpers import require_provider_spec
|
|
6
6
|
from elizaos.types import Provider, ProviderResult
|
|
7
|
-
from elizaos.types.database import MemorySearchOptions
|
|
8
7
|
|
|
9
8
|
if TYPE_CHECKING:
|
|
10
9
|
from elizaos.types import IAgentRuntime, Memory, State
|
|
@@ -42,14 +41,14 @@ async def get_knowledge_context(
|
|
|
42
41
|
# 3. Search using the most recent embedding if available
|
|
43
42
|
if embeddings:
|
|
44
43
|
primary_embedding = embeddings[0]
|
|
45
|
-
params =
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
embedding
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
unique
|
|
52
|
-
|
|
44
|
+
params = {
|
|
45
|
+
"tableName": "knowledge",
|
|
46
|
+
"roomId": str(message.room_id),
|
|
47
|
+
"embedding": primary_embedding,
|
|
48
|
+
"matchThreshold": 0.75,
|
|
49
|
+
"matchCount": 5,
|
|
50
|
+
"unique": True,
|
|
51
|
+
}
|
|
53
52
|
relevant_knowledge = await runtime.search_memories(params)
|
|
54
53
|
elif query_text:
|
|
55
54
|
# Fallback to search_knowledge if no embeddings found?
|
|
@@ -25,12 +25,17 @@ async def get_recent_messages_context(
|
|
|
25
25
|
|
|
26
26
|
sections: list[str] = []
|
|
27
27
|
message_list: list[dict[str, str | int]] = []
|
|
28
|
+
room = await runtime.get_room(room_id)
|
|
29
|
+
last_compaction_at = None
|
|
30
|
+
if room and getattr(room, "metadata", None):
|
|
31
|
+
last_compaction_at = room.metadata.get("lastCompactionAt")
|
|
28
32
|
|
|
29
33
|
recent_messages = await runtime.get_memories(
|
|
30
34
|
room_id=room_id,
|
|
31
35
|
limit=20,
|
|
32
36
|
order_by="created_at",
|
|
33
37
|
order_direction="desc",
|
|
38
|
+
start=last_compaction_at,
|
|
34
39
|
)
|
|
35
40
|
|
|
36
41
|
recent_messages = list(reversed(recent_messages))
|
|
@@ -39,11 +39,10 @@ async def get_relationships(
|
|
|
39
39
|
)
|
|
40
40
|
|
|
41
41
|
try:
|
|
42
|
-
relationships = await runtime.get_relationships(entity_id
|
|
42
|
+
relationships = await runtime.get_relationships({"entityId": str(entity_id)})
|
|
43
43
|
except Exception as e:
|
|
44
44
|
runtime.logger.debug(
|
|
45
|
-
|
|
46
|
-
"Failed to get relationships",
|
|
45
|
+
f"Failed to get relationships: src=provider:relationships agentId={runtime.agent_id} error={e}"
|
|
47
46
|
)
|
|
48
47
|
relationships = []
|
|
49
48
|
|
|
@@ -54,24 +53,31 @@ async def get_relationships(
|
|
|
54
53
|
data={"relationships": []},
|
|
55
54
|
)
|
|
56
55
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
56
|
+
def _get_interactions(relationship: object) -> int:
|
|
57
|
+
if isinstance(relationship, dict):
|
|
58
|
+
metadata = relationship.get("metadata", {})
|
|
59
|
+
if isinstance(metadata, dict):
|
|
60
|
+
value = metadata.get("interactions", 0)
|
|
61
|
+
return int(value) if isinstance(value, (int, float)) else 0
|
|
62
|
+
return 0
|
|
63
|
+
|
|
64
|
+
sorted_relationships = sorted(relationships, key=_get_interactions, reverse=True)[:30]
|
|
62
65
|
|
|
63
66
|
formatted_relationships: list[str] = []
|
|
64
|
-
entity_cache: dict[
|
|
67
|
+
entity_cache: dict[str, str] = {}
|
|
65
68
|
for rel in sorted_relationships:
|
|
69
|
+
if not isinstance(rel, dict):
|
|
70
|
+
continue
|
|
66
71
|
target_id = rel.get("targetEntityId")
|
|
67
72
|
if not target_id:
|
|
68
73
|
continue
|
|
69
74
|
|
|
70
|
-
|
|
75
|
+
target_id_str = str(target_id)
|
|
76
|
+
target_name = entity_cache.get(target_id_str)
|
|
71
77
|
if target_name is None:
|
|
72
|
-
target_entity = await runtime.get_entity(
|
|
73
|
-
target_name = target_entity.name if target_entity else
|
|
74
|
-
entity_cache[
|
|
78
|
+
target_entity = await runtime.get_entity(target_id_str)
|
|
79
|
+
target_name = target_entity.name if target_entity else target_id_str[:8]
|
|
80
|
+
entity_cache[target_id_str] = target_name
|
|
75
81
|
|
|
76
82
|
formatted_relationships.append(format_relationship(rel, target_name))
|
|
77
83
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from collections import OrderedDict
|
|
3
4
|
from typing import TYPE_CHECKING
|
|
4
5
|
|
|
5
6
|
from elizaos.types import ModelType, Service, ServiceType
|
|
@@ -18,7 +19,7 @@ class EmbeddingService(Service):
|
|
|
18
19
|
|
|
19
20
|
def __init__(self) -> None:
|
|
20
21
|
self._runtime: IAgentRuntime | None = None
|
|
21
|
-
self._cache:
|
|
22
|
+
self._cache: OrderedDict[str, list[float]] = OrderedDict()
|
|
22
23
|
self._cache_enabled: bool = True
|
|
23
24
|
self._max_cache_size: int = 1000
|
|
24
25
|
|
|
@@ -51,7 +52,9 @@ class EmbeddingService(Service):
|
|
|
51
52
|
raise ValueError("Embedding service not started - no runtime available")
|
|
52
53
|
|
|
53
54
|
if self._cache_enabled and text in self._cache:
|
|
54
|
-
|
|
55
|
+
embedding = self._cache.pop(text)
|
|
56
|
+
self._cache[text] = embedding
|
|
57
|
+
return embedding
|
|
55
58
|
|
|
56
59
|
# Truncate to stay within embedding model token limits
|
|
57
60
|
embed_text = text
|
|
@@ -87,9 +90,10 @@ class EmbeddingService(Service):
|
|
|
87
90
|
return embeddings
|
|
88
91
|
|
|
89
92
|
def _add_to_cache(self, text: str, embedding: list[float]) -> None:
|
|
90
|
-
if
|
|
91
|
-
|
|
92
|
-
|
|
93
|
+
if text in self._cache:
|
|
94
|
+
self._cache.pop(text)
|
|
95
|
+
elif len(self._cache) >= self._max_cache_size:
|
|
96
|
+
self._cache.popitem(last=False)
|
|
93
97
|
self._cache[text] = embedding
|
|
94
98
|
|
|
95
99
|
def clear_cache(self) -> None:
|
|
@@ -105,8 +109,7 @@ class EmbeddingService(Service):
|
|
|
105
109
|
raise ValueError("Cache size must be positive")
|
|
106
110
|
self._max_cache_size = size
|
|
107
111
|
while len(self._cache) > self._max_cache_size:
|
|
108
|
-
|
|
109
|
-
del self._cache[oldest_key]
|
|
112
|
+
self._cache.popitem(last=False)
|
|
110
113
|
|
|
111
114
|
async def similarity(self, text1: str, text2: str) -> float:
|
|
112
115
|
embedding1 = await self.embed(text1)
|
|
@@ -2,7 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from dataclasses import dataclass, field
|
|
4
4
|
from datetime import UTC, datetime
|
|
5
|
-
from enum import
|
|
5
|
+
from enum import StrEnum
|
|
6
6
|
from typing import TYPE_CHECKING
|
|
7
7
|
from uuid import UUID, uuid4
|
|
8
8
|
|
|
@@ -12,7 +12,7 @@ if TYPE_CHECKING:
|
|
|
12
12
|
from elizaos.types import IAgentRuntime
|
|
13
13
|
|
|
14
14
|
|
|
15
|
-
class TaskStatus(
|
|
15
|
+
class TaskStatus(StrEnum):
|
|
16
16
|
PENDING = "PENDING"
|
|
17
17
|
IN_PROGRESS = "IN_PROGRESS"
|
|
18
18
|
COMPLETED = "COMPLETED"
|
|
@@ -20,7 +20,7 @@ class TaskStatus(str, Enum):
|
|
|
20
20
|
CANCELLED = "CANCELLED"
|
|
21
21
|
|
|
22
22
|
|
|
23
|
-
class TaskPriority(
|
|
23
|
+
class TaskPriority(StrEnum):
|
|
24
24
|
LOW = "LOW"
|
|
25
25
|
MEDIUM = "MEDIUM"
|
|
26
26
|
HIGH = "HIGH"
|
|
@@ -7,6 +7,7 @@ from .mute_room import mute_room_action
|
|
|
7
7
|
from .none import none_action
|
|
8
8
|
from .remove_contact import remove_contact_action
|
|
9
9
|
from .reply import reply_action
|
|
10
|
+
from .reset_session import reset_session_action
|
|
10
11
|
from .roles import update_role_action
|
|
11
12
|
from .schedule_follow_up import schedule_follow_up_action
|
|
12
13
|
from .search_contacts import search_contacts_action
|
|
@@ -26,6 +27,7 @@ __all__ = [
|
|
|
26
27
|
"mute_room_action",
|
|
27
28
|
"none_action",
|
|
28
29
|
"remove_contact_action",
|
|
30
|
+
"reset_session_action",
|
|
29
31
|
"reply_action",
|
|
30
32
|
"schedule_follow_up_action",
|
|
31
33
|
"search_contacts_action",
|
|
@@ -54,6 +56,7 @@ EXTENDED_ACTIONS = [
|
|
|
54
56
|
generate_image_action,
|
|
55
57
|
mute_room_action,
|
|
56
58
|
remove_contact_action,
|
|
59
|
+
reset_session_action,
|
|
57
60
|
schedule_follow_up_action,
|
|
58
61
|
search_contacts_action,
|
|
59
62
|
send_message_action,
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from dataclasses import dataclass, field
|
|
4
|
-
from enum import
|
|
5
|
-
from typing import TYPE_CHECKING
|
|
4
|
+
from enum import StrEnum
|
|
5
|
+
from typing import TYPE_CHECKING, Any, cast
|
|
6
6
|
from uuid import UUID
|
|
7
7
|
|
|
8
8
|
from elizaos.bootstrap.utils.xml import parse_key_value_xml
|
|
9
9
|
from elizaos.generated.spec_helpers import require_action_spec
|
|
10
|
+
from elizaos.prompts import UPDATE_ROLE_TEMPLATE
|
|
10
11
|
from elizaos.types import Action, ActionExample, ActionResult, Content, ModelType
|
|
11
12
|
|
|
12
13
|
if TYPE_CHECKING:
|
|
@@ -18,7 +19,7 @@ _spec = require_action_spec("UPDATE_ROLE")
|
|
|
18
19
|
|
|
19
20
|
def _convert_spec_examples() -> list[list[ActionExample]]:
|
|
20
21
|
"""Convert spec examples to ActionExample format."""
|
|
21
|
-
spec_examples = _spec.get("examples", [])
|
|
22
|
+
spec_examples = cast(list[list[dict[str, Any]]], _spec.get("examples", []))
|
|
22
23
|
if spec_examples:
|
|
23
24
|
return [
|
|
24
25
|
[
|
|
@@ -36,7 +37,7 @@ def _convert_spec_examples() -> list[list[ActionExample]]:
|
|
|
36
37
|
return []
|
|
37
38
|
|
|
38
39
|
|
|
39
|
-
class Role(
|
|
40
|
+
class Role(StrEnum):
|
|
40
41
|
OWNER = "OWNER"
|
|
41
42
|
ADMIN = "ADMIN"
|
|
42
43
|
MEMBER = "MEMBER"
|
|
@@ -4,7 +4,6 @@ from typing import TYPE_CHECKING
|
|
|
4
4
|
|
|
5
5
|
from elizaos.generated.spec_helpers import require_provider_spec
|
|
6
6
|
from elizaos.types import Provider, ProviderResult
|
|
7
|
-
from elizaos.types.database import MemorySearchOptions
|
|
8
7
|
|
|
9
8
|
if TYPE_CHECKING:
|
|
10
9
|
from elizaos.types import IAgentRuntime, Memory, State
|
|
@@ -42,14 +41,14 @@ async def get_knowledge_context(
|
|
|
42
41
|
# 3. Search using the most recent embedding if available
|
|
43
42
|
if embeddings:
|
|
44
43
|
primary_embedding = embeddings[0]
|
|
45
|
-
params =
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
embedding
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
unique
|
|
52
|
-
|
|
44
|
+
params = {
|
|
45
|
+
"tableName": "knowledge",
|
|
46
|
+
"roomId": str(message.room_id),
|
|
47
|
+
"embedding": primary_embedding,
|
|
48
|
+
"matchThreshold": 0.75,
|
|
49
|
+
"matchCount": 5,
|
|
50
|
+
"unique": True,
|
|
51
|
+
}
|
|
53
52
|
relevant_knowledge = await runtime.search_memories(params)
|
|
54
53
|
elif query_text:
|
|
55
54
|
# Fallback to search_knowledge if no embeddings found?
|
|
@@ -25,12 +25,17 @@ async def get_recent_messages_context(
|
|
|
25
25
|
|
|
26
26
|
sections: list[str] = []
|
|
27
27
|
message_list: list[dict[str, str | int]] = []
|
|
28
|
+
room = await runtime.get_room(room_id)
|
|
29
|
+
last_compaction_at = None
|
|
30
|
+
if room and getattr(room, "metadata", None):
|
|
31
|
+
last_compaction_at = room.metadata.get("lastCompactionAt")
|
|
28
32
|
|
|
29
33
|
recent_messages = await runtime.get_memories(
|
|
30
34
|
room_id=room_id,
|
|
31
35
|
limit=20,
|
|
32
36
|
order_by="created_at",
|
|
33
37
|
order_direction="desc",
|
|
38
|
+
start=last_compaction_at,
|
|
34
39
|
)
|
|
35
40
|
|
|
36
41
|
recent_messages = list(reversed(recent_messages))
|
|
@@ -39,11 +39,10 @@ async def get_relationships(
|
|
|
39
39
|
)
|
|
40
40
|
|
|
41
41
|
try:
|
|
42
|
-
relationships = await runtime.get_relationships(entity_id
|
|
42
|
+
relationships = await runtime.get_relationships({"entityId": str(entity_id)})
|
|
43
43
|
except Exception as e:
|
|
44
44
|
runtime.logger.debug(
|
|
45
|
-
|
|
46
|
-
"Failed to get relationships",
|
|
45
|
+
f"Failed to get relationships: src=provider:relationships agentId={runtime.agent_id} error={e}"
|
|
47
46
|
)
|
|
48
47
|
relationships = []
|
|
49
48
|
|
|
@@ -54,24 +53,31 @@ async def get_relationships(
|
|
|
54
53
|
data={"relationships": []},
|
|
55
54
|
)
|
|
56
55
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
56
|
+
def _get_interactions(relationship: object) -> int:
|
|
57
|
+
if isinstance(relationship, dict):
|
|
58
|
+
metadata = relationship.get("metadata", {})
|
|
59
|
+
if isinstance(metadata, dict):
|
|
60
|
+
value = metadata.get("interactions", 0)
|
|
61
|
+
return int(value) if isinstance(value, (int, float)) else 0
|
|
62
|
+
return 0
|
|
63
|
+
|
|
64
|
+
sorted_relationships = sorted(relationships, key=_get_interactions, reverse=True)[:30]
|
|
62
65
|
|
|
63
66
|
formatted_relationships: list[str] = []
|
|
64
|
-
entity_cache: dict[
|
|
67
|
+
entity_cache: dict[str, str] = {}
|
|
65
68
|
for rel in sorted_relationships:
|
|
69
|
+
if not isinstance(rel, dict):
|
|
70
|
+
continue
|
|
66
71
|
target_id = rel.get("targetEntityId")
|
|
67
72
|
if not target_id:
|
|
68
73
|
continue
|
|
69
74
|
|
|
70
|
-
|
|
75
|
+
target_id_str = str(target_id)
|
|
76
|
+
target_name = entity_cache.get(target_id_str)
|
|
71
77
|
if target_name is None:
|
|
72
|
-
target_entity = await runtime.get_entity(
|
|
73
|
-
target_name = target_entity.name if target_entity else
|
|
74
|
-
entity_cache[
|
|
78
|
+
target_entity = await runtime.get_entity(target_id_str)
|
|
79
|
+
target_name = target_entity.name if target_entity else target_id_str[:8]
|
|
80
|
+
entity_cache[target_id_str] = target_name
|
|
75
81
|
|
|
76
82
|
formatted_relationships.append(format_relationship(rel, target_name))
|
|
77
83
|
|
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import contextlib
|
|
5
|
+
from collections import OrderedDict
|
|
5
6
|
from typing import TYPE_CHECKING, Any
|
|
6
7
|
|
|
7
8
|
from elizaos.types import ModelType, Service, ServiceType
|
|
@@ -21,10 +22,14 @@ class EmbeddingService(Service):
|
|
|
21
22
|
|
|
22
23
|
def __init__(self) -> None:
|
|
23
24
|
self._runtime: IAgentRuntime | None = None
|
|
24
|
-
self._cache:
|
|
25
|
+
self._cache: OrderedDict[str, list[float]] = OrderedDict()
|
|
25
26
|
self._cache_enabled: bool = True
|
|
26
27
|
self._max_cache_size: int = 1000
|
|
27
|
-
self.
|
|
28
|
+
self._queue_max_size: int = 1000
|
|
29
|
+
self._queue: asyncio.Queue[tuple[str | None, Any]] = asyncio.Queue(
|
|
30
|
+
maxsize=self._queue_max_size
|
|
31
|
+
)
|
|
32
|
+
self._pending_payload_keys: set[str] = set()
|
|
28
33
|
self._worker_task: asyncio.Task | None = None
|
|
29
34
|
|
|
30
35
|
@classmethod
|
|
@@ -60,6 +65,8 @@ class EmbeddingService(Service):
|
|
|
60
65
|
agentId=str(self._runtime.agent_id),
|
|
61
66
|
)
|
|
62
67
|
self._cache.clear()
|
|
68
|
+
self._pending_payload_keys.clear()
|
|
69
|
+
self._queue = asyncio.Queue(maxsize=self._queue_max_size)
|
|
63
70
|
self._runtime = None
|
|
64
71
|
|
|
65
72
|
# Max characters for embedding input (~8K tokens at ~4 chars/token)
|
|
@@ -70,7 +77,9 @@ class EmbeddingService(Service):
|
|
|
70
77
|
raise ValueError("Embedding service not started - no runtime available")
|
|
71
78
|
|
|
72
79
|
if self._cache_enabled and text in self._cache:
|
|
73
|
-
|
|
80
|
+
embedding = self._cache.pop(text)
|
|
81
|
+
self._cache[text] = embedding
|
|
82
|
+
return embedding
|
|
74
83
|
|
|
75
84
|
# Truncate to stay within embedding model token limits
|
|
76
85
|
embed_text = text
|
|
@@ -106,9 +115,10 @@ class EmbeddingService(Service):
|
|
|
106
115
|
return embeddings
|
|
107
116
|
|
|
108
117
|
def _add_to_cache(self, text: str, embedding: list[float]) -> None:
|
|
109
|
-
if
|
|
110
|
-
|
|
111
|
-
|
|
118
|
+
if text in self._cache:
|
|
119
|
+
self._cache.pop(text)
|
|
120
|
+
elif len(self._cache) >= self._max_cache_size:
|
|
121
|
+
self._cache.popitem(last=False)
|
|
112
122
|
self._cache[text] = embedding
|
|
113
123
|
|
|
114
124
|
def clear_cache(self) -> None:
|
|
@@ -124,8 +134,7 @@ class EmbeddingService(Service):
|
|
|
124
134
|
raise ValueError("Cache size must be positive")
|
|
125
135
|
self._max_cache_size = size
|
|
126
136
|
while len(self._cache) > self._max_cache_size:
|
|
127
|
-
|
|
128
|
-
del self._cache[oldest_key]
|
|
137
|
+
self._cache.popitem(last=False)
|
|
129
138
|
|
|
130
139
|
async def similarity(self, text1: str, text2: str) -> float:
|
|
131
140
|
embedding1 = await self.embed(text1)
|
|
@@ -142,13 +151,45 @@ class EmbeddingService(Service):
|
|
|
142
151
|
|
|
143
152
|
async def _handle_embedding_request(self, payload: Any) -> None:
|
|
144
153
|
"""Handle embedding generation request event."""
|
|
145
|
-
|
|
154
|
+
payload_key = self._get_payload_key(payload)
|
|
155
|
+
if payload_key is not None:
|
|
156
|
+
if payload_key in self._pending_payload_keys:
|
|
157
|
+
return
|
|
158
|
+
self._pending_payload_keys.add(payload_key)
|
|
159
|
+
|
|
160
|
+
try:
|
|
161
|
+
await self._queue.put((payload_key, payload))
|
|
162
|
+
except Exception:
|
|
163
|
+
if payload_key is not None:
|
|
164
|
+
self._pending_payload_keys.discard(payload_key)
|
|
165
|
+
raise
|
|
166
|
+
|
|
167
|
+
def _get_payload_key(self, payload: Any) -> str | None:
|
|
168
|
+
memory_data = getattr(payload, "memory", None)
|
|
169
|
+
if memory_data is None:
|
|
170
|
+
extra = getattr(payload, "extra", None)
|
|
171
|
+
if hasattr(extra, "__getitem__"):
|
|
172
|
+
with contextlib.suppress(Exception):
|
|
173
|
+
if "memory" in extra:
|
|
174
|
+
memory_data = extra["memory"]
|
|
175
|
+
if memory_data is None and isinstance(payload, dict):
|
|
176
|
+
memory_data = payload.get("memory")
|
|
177
|
+
if memory_data is None:
|
|
178
|
+
return None
|
|
179
|
+
|
|
180
|
+
if isinstance(memory_data, dict):
|
|
181
|
+
memory_id = memory_data.get("id")
|
|
182
|
+
else:
|
|
183
|
+
memory_id = getattr(memory_data, "id", None)
|
|
184
|
+
if memory_id is None:
|
|
185
|
+
return None
|
|
186
|
+
return str(memory_id)
|
|
146
187
|
|
|
147
188
|
async def _worker(self) -> None:
|
|
148
189
|
"""Background worker for processing embedding requests."""
|
|
149
190
|
while True:
|
|
150
191
|
try:
|
|
151
|
-
payload = await self._queue.get()
|
|
192
|
+
payload_key, payload = await self._queue.get()
|
|
152
193
|
except asyncio.CancelledError:
|
|
153
194
|
break
|
|
154
195
|
|
|
@@ -158,6 +199,8 @@ class EmbeddingService(Service):
|
|
|
158
199
|
if self._runtime:
|
|
159
200
|
self._runtime.logger.error(f"Error in embedding worker: {e}", exc_info=True)
|
|
160
201
|
finally:
|
|
202
|
+
if payload_key is not None:
|
|
203
|
+
self._pending_payload_keys.discard(payload_key)
|
|
161
204
|
self._queue.task_done()
|
|
162
205
|
|
|
163
206
|
async def _process_embedding_request(self, payload: Any) -> None:
|
|
@@ -2,7 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from dataclasses import dataclass, field
|
|
4
4
|
from datetime import datetime
|
|
5
|
-
from enum import
|
|
5
|
+
from enum import StrEnum
|
|
6
6
|
from typing import TYPE_CHECKING
|
|
7
7
|
from uuid import UUID
|
|
8
8
|
|
|
@@ -12,7 +12,7 @@ if TYPE_CHECKING:
|
|
|
12
12
|
from elizaos.types import IAgentRuntime
|
|
13
13
|
|
|
14
14
|
|
|
15
|
-
class ContactCategory(
|
|
15
|
+
class ContactCategory(StrEnum):
|
|
16
16
|
FRIEND = "friend"
|
|
17
17
|
FAMILY = "family"
|
|
18
18
|
COLLEAGUE = "colleague"
|
|
@@ -14,7 +14,7 @@ import asyncio
|
|
|
14
14
|
import contextlib
|
|
15
15
|
import time
|
|
16
16
|
from dataclasses import dataclass
|
|
17
|
-
from enum import
|
|
17
|
+
from enum import StrEnum
|
|
18
18
|
from typing import TYPE_CHECKING, Any, Protocol, runtime_checkable
|
|
19
19
|
from uuid import UUID, uuid4
|
|
20
20
|
|
|
@@ -27,7 +27,7 @@ if TYPE_CHECKING:
|
|
|
27
27
|
TICK_INTERVAL_MS = 1000
|
|
28
28
|
|
|
29
29
|
|
|
30
|
-
class TaskStatus(
|
|
30
|
+
class TaskStatus(StrEnum):
|
|
31
31
|
"""Task status enum."""
|
|
32
32
|
|
|
33
33
|
PENDING = "pending"
|
|
@@ -37,7 +37,7 @@ class TaskStatus(str, Enum):
|
|
|
37
37
|
CANCELLED = "cancelled"
|
|
38
38
|
|
|
39
39
|
|
|
40
|
-
class TaskPriority(
|
|
40
|
+
class TaskPriority(StrEnum):
|
|
41
41
|
"""Task priority enum."""
|
|
42
42
|
|
|
43
43
|
LOW = "low"
|
package/elizaos/media/mime.py
CHANGED
|
@@ -7,7 +7,7 @@ Provides robust MIME type detection from file buffers, headers, and extensions.
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
9
|
import re
|
|
10
|
-
from enum import
|
|
10
|
+
from enum import StrEnum
|
|
11
11
|
from urllib.parse import urlparse
|
|
12
12
|
|
|
13
13
|
# Try to import python-magic for MIME sniffing, fallback to filetype
|
|
@@ -26,7 +26,7 @@ except ImportError:
|
|
|
26
26
|
HAS_FILETYPE = False
|
|
27
27
|
|
|
28
28
|
|
|
29
|
-
class MediaKind(
|
|
29
|
+
class MediaKind(StrEnum):
|
|
30
30
|
"""Media kind categories."""
|
|
31
31
|
|
|
32
32
|
IMAGE = "image"
|
package/elizaos/runtime.py
CHANGED
|
@@ -1680,11 +1680,11 @@ class AgentRuntime(IAgentRuntime):
|
|
|
1680
1680
|
if self._adapter:
|
|
1681
1681
|
await self._adapter.ensure_embedding_dimension(dimension)
|
|
1682
1682
|
|
|
1683
|
-
async def get_entity(self, entity_id: UUID) -> Any | None:
|
|
1683
|
+
async def get_entity(self, entity_id: UUID | str) -> Any | None:
|
|
1684
1684
|
"""Get a single entity by ID."""
|
|
1685
1685
|
if not self._adapter:
|
|
1686
1686
|
return None
|
|
1687
|
-
entities = await self._adapter.get_entities_by_ids([entity_id])
|
|
1687
|
+
entities = await self._adapter.get_entities_by_ids([str(entity_id)])
|
|
1688
1688
|
return entities[0] if entities else None
|
|
1689
1689
|
|
|
1690
1690
|
async def get_entities_by_ids(self, entity_ids: list[UUID]) -> list[Any] | None:
|
|
@@ -1744,7 +1744,7 @@ class AgentRuntime(IAgentRuntime):
|
|
|
1744
1744
|
if self._adapter:
|
|
1745
1745
|
await self._adapter.delete_component(component_id)
|
|
1746
1746
|
|
|
1747
|
-
async def search_memories(self, params: MemorySearchOptions) -> list[Memory]:
|
|
1747
|
+
async def search_memories(self, params: MemorySearchOptions | dict[str, Any]) -> list[Memory]:
|
|
1748
1748
|
"""Search memories by embedding."""
|
|
1749
1749
|
if not self._adapter:
|
|
1750
1750
|
raise RuntimeError("Database adapter not set")
|
|
@@ -1828,11 +1828,6 @@ class AgentRuntime(IAgentRuntime):
|
|
|
1828
1828
|
return AgentRunSummaryResult(runs=[], total=0, has_more=False)
|
|
1829
1829
|
return await self._adapter.get_agent_run_summaries(params)
|
|
1830
1830
|
|
|
1831
|
-
async def search_memories(self, params: dict[str, Any]) -> list[Any]:
|
|
1832
|
-
if not self._adapter:
|
|
1833
|
-
return []
|
|
1834
|
-
return await self._adapter.search_memories(params)
|
|
1835
|
-
|
|
1836
1831
|
async def create_memory(
|
|
1837
1832
|
self,
|
|
1838
1833
|
memory: dict[str, object] | None = None,
|
|
@@ -2412,6 +2407,12 @@ end code: {final_code}
|
|
|
2412
2407
|
step_id = CURRENT_TRAJECTORY_STEP_ID.get()
|
|
2413
2408
|
traj_svc = self.get_service("trajectory_logger")
|
|
2414
2409
|
if step_id and traj_svc is not None and hasattr(traj_svc, "log_llm_call"):
|
|
2410
|
+
max_tokens_value = params.get("maxTokens", 0)
|
|
2411
|
+
max_tokens = (
|
|
2412
|
+
int(max_tokens_value)
|
|
2413
|
+
if isinstance(max_tokens_value, (int, float, str, bytes, bytearray))
|
|
2414
|
+
else 0
|
|
2415
|
+
)
|
|
2415
2416
|
traj_svc.log_llm_call( # type: ignore[call-arg]
|
|
2416
2417
|
step_id=step_id,
|
|
2417
2418
|
model=stream_model_type,
|
|
@@ -2419,7 +2420,7 @@ end code: {final_code}
|
|
|
2419
2420
|
user_prompt=str(params.get("prompt", ""))[:2000],
|
|
2420
2421
|
response=response_str[:2000],
|
|
2421
2422
|
temperature=0.0,
|
|
2422
|
-
max_tokens=
|
|
2423
|
+
max_tokens=max_tokens,
|
|
2423
2424
|
purpose="action",
|
|
2424
2425
|
action_type="dynamic_prompt_exec.stream",
|
|
2425
2426
|
latency_ms=0,
|
|
@@ -21,7 +21,7 @@ import uuid
|
|
|
21
21
|
from collections.abc import Callable
|
|
22
22
|
from dataclasses import dataclass, field
|
|
23
23
|
from datetime import datetime
|
|
24
|
-
from enum import
|
|
24
|
+
from enum import StrEnum
|
|
25
25
|
from typing import TYPE_CHECKING, Any
|
|
26
26
|
|
|
27
27
|
from elizaos.types.service import Service, ServiceType
|
|
@@ -30,7 +30,7 @@ if TYPE_CHECKING:
|
|
|
30
30
|
from elizaos.types.runtime import IAgentRuntime
|
|
31
31
|
|
|
32
32
|
|
|
33
|
-
class HookSource(
|
|
33
|
+
class HookSource(StrEnum):
|
|
34
34
|
"""Source of a hook registration."""
|
|
35
35
|
|
|
36
36
|
BUNDLED = "bundled"
|
|
@@ -40,7 +40,7 @@ class HookSource(str, Enum):
|
|
|
40
40
|
RUNTIME = "runtime"
|
|
41
41
|
|
|
42
42
|
|
|
43
|
-
class HookEventType(
|
|
43
|
+
class HookEventType(StrEnum):
|
|
44
44
|
"""Hook-specific event types."""
|
|
45
45
|
|
|
46
46
|
HOOK_COMMAND_NEW = "HOOK_COMMAND_NEW"
|