@elizaos/python 2.0.0-alpha.10 → 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.
- 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 +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/schedule_follow_up.py +70 -15
- package/elizaos/advanced_capabilities/actions/search_contacts.py +17 -3
- package/elizaos/advanced_capabilities/actions/send_message.py +183 -50
- 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/memory_service.py +15 -17
- package/elizaos/advanced_memory/test_advanced_memory.py +357 -0
- 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 +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/basic_capabilities/providers/time.py +7 -4
- package/elizaos/bootstrap/__init__.py +21 -2
- 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 +27 -3
- package/elizaos/bootstrap/providers/providers_list.py +1 -0
- package/elizaos/bootstrap/providers/relationships.py +1 -0
- 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 +156 -1
- 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 +2 -2
- package/elizaos/media/search.py +23 -23
- package/elizaos/runtime.py +215 -57
- 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 +30 -0
- package/elizaos/types/runtime.py +6 -2
- package/elizaos/utils/validation.py +76 -0
- package/package.json +3 -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_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,90 @@
|
|
|
1
|
+
from google.protobuf import struct_pb2 as _struct_pb2
|
|
2
|
+
from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper
|
|
3
|
+
from google.protobuf import descriptor as _descriptor
|
|
4
|
+
from google.protobuf import message as _message
|
|
5
|
+
from collections.abc import Mapping as _Mapping
|
|
6
|
+
from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union
|
|
7
|
+
|
|
8
|
+
DESCRIPTOR: _descriptor.FileDescriptor
|
|
9
|
+
|
|
10
|
+
class TEEMode(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
|
|
11
|
+
__slots__ = ()
|
|
12
|
+
TEE_MODE_UNSPECIFIED: _ClassVar[TEEMode]
|
|
13
|
+
TEE_MODE_OFF: _ClassVar[TEEMode]
|
|
14
|
+
TEE_MODE_LOCAL: _ClassVar[TEEMode]
|
|
15
|
+
TEE_MODE_DOCKER: _ClassVar[TEEMode]
|
|
16
|
+
TEE_MODE_PRODUCTION: _ClassVar[TEEMode]
|
|
17
|
+
|
|
18
|
+
class TeeType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
|
|
19
|
+
__slots__ = ()
|
|
20
|
+
TEE_TYPE_UNSPECIFIED: _ClassVar[TeeType]
|
|
21
|
+
TEE_TYPE_TDX_DSTACK: _ClassVar[TeeType]
|
|
22
|
+
TEE_MODE_UNSPECIFIED: TEEMode
|
|
23
|
+
TEE_MODE_OFF: TEEMode
|
|
24
|
+
TEE_MODE_LOCAL: TEEMode
|
|
25
|
+
TEE_MODE_DOCKER: TEEMode
|
|
26
|
+
TEE_MODE_PRODUCTION: TEEMode
|
|
27
|
+
TEE_TYPE_UNSPECIFIED: TeeType
|
|
28
|
+
TEE_TYPE_TDX_DSTACK: TeeType
|
|
29
|
+
|
|
30
|
+
class TeeAgent(_message.Message):
|
|
31
|
+
__slots__ = ("id", "agent_id", "agent_name", "created_at", "public_key", "attestation")
|
|
32
|
+
ID_FIELD_NUMBER: _ClassVar[int]
|
|
33
|
+
AGENT_ID_FIELD_NUMBER: _ClassVar[int]
|
|
34
|
+
AGENT_NAME_FIELD_NUMBER: _ClassVar[int]
|
|
35
|
+
CREATED_AT_FIELD_NUMBER: _ClassVar[int]
|
|
36
|
+
PUBLIC_KEY_FIELD_NUMBER: _ClassVar[int]
|
|
37
|
+
ATTESTATION_FIELD_NUMBER: _ClassVar[int]
|
|
38
|
+
id: str
|
|
39
|
+
agent_id: str
|
|
40
|
+
agent_name: str
|
|
41
|
+
created_at: int
|
|
42
|
+
public_key: str
|
|
43
|
+
attestation: str
|
|
44
|
+
def __init__(self, id: _Optional[str] = ..., agent_id: _Optional[str] = ..., agent_name: _Optional[str] = ..., created_at: _Optional[int] = ..., public_key: _Optional[str] = ..., attestation: _Optional[str] = ...) -> None: ...
|
|
45
|
+
|
|
46
|
+
class RemoteAttestationQuote(_message.Message):
|
|
47
|
+
__slots__ = ("quote", "timestamp")
|
|
48
|
+
QUOTE_FIELD_NUMBER: _ClassVar[int]
|
|
49
|
+
TIMESTAMP_FIELD_NUMBER: _ClassVar[int]
|
|
50
|
+
quote: str
|
|
51
|
+
timestamp: int
|
|
52
|
+
def __init__(self, quote: _Optional[str] = ..., timestamp: _Optional[int] = ...) -> None: ...
|
|
53
|
+
|
|
54
|
+
class DeriveKeyAttestationData(_message.Message):
|
|
55
|
+
__slots__ = ("agent_id", "public_key", "subject")
|
|
56
|
+
AGENT_ID_FIELD_NUMBER: _ClassVar[int]
|
|
57
|
+
PUBLIC_KEY_FIELD_NUMBER: _ClassVar[int]
|
|
58
|
+
SUBJECT_FIELD_NUMBER: _ClassVar[int]
|
|
59
|
+
agent_id: str
|
|
60
|
+
public_key: str
|
|
61
|
+
subject: str
|
|
62
|
+
def __init__(self, agent_id: _Optional[str] = ..., public_key: _Optional[str] = ..., subject: _Optional[str] = ...) -> None: ...
|
|
63
|
+
|
|
64
|
+
class AttestedMessage(_message.Message):
|
|
65
|
+
__slots__ = ("entity_id", "room_id", "content")
|
|
66
|
+
ENTITY_ID_FIELD_NUMBER: _ClassVar[int]
|
|
67
|
+
ROOM_ID_FIELD_NUMBER: _ClassVar[int]
|
|
68
|
+
CONTENT_FIELD_NUMBER: _ClassVar[int]
|
|
69
|
+
entity_id: str
|
|
70
|
+
room_id: str
|
|
71
|
+
content: str
|
|
72
|
+
def __init__(self, entity_id: _Optional[str] = ..., room_id: _Optional[str] = ..., content: _Optional[str] = ...) -> None: ...
|
|
73
|
+
|
|
74
|
+
class RemoteAttestationMessage(_message.Message):
|
|
75
|
+
__slots__ = ("agent_id", "timestamp", "message")
|
|
76
|
+
AGENT_ID_FIELD_NUMBER: _ClassVar[int]
|
|
77
|
+
TIMESTAMP_FIELD_NUMBER: _ClassVar[int]
|
|
78
|
+
MESSAGE_FIELD_NUMBER: _ClassVar[int]
|
|
79
|
+
agent_id: str
|
|
80
|
+
timestamp: int
|
|
81
|
+
message: AttestedMessage
|
|
82
|
+
def __init__(self, agent_id: _Optional[str] = ..., timestamp: _Optional[int] = ..., message: _Optional[_Union[AttestedMessage, _Mapping]] = ...) -> None: ...
|
|
83
|
+
|
|
84
|
+
class TeePluginConfig(_message.Message):
|
|
85
|
+
__slots__ = ("vendor", "vendor_config")
|
|
86
|
+
VENDOR_FIELD_NUMBER: _ClassVar[int]
|
|
87
|
+
VENDOR_CONFIG_FIELD_NUMBER: _ClassVar[int]
|
|
88
|
+
vendor: str
|
|
89
|
+
vendor_config: _struct_pb2.Struct
|
|
90
|
+
def __init__(self, vendor: _Optional[str] = ..., vendor_config: _Optional[_Union[_struct_pb2.Struct, _Mapping]] = ...) -> None: ...
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
|
3
|
+
# NO CHECKED-IN PROTOBUF GENCODE
|
|
4
|
+
# source: eliza/v1/testing.proto
|
|
5
|
+
# Protobuf Python Version: 6.33.5
|
|
6
|
+
"""Generated protocol buffer code."""
|
|
7
|
+
from google.protobuf import descriptor as _descriptor
|
|
8
|
+
from google.protobuf import descriptor_pool as _descriptor_pool
|
|
9
|
+
from google.protobuf import runtime_version as _runtime_version
|
|
10
|
+
from google.protobuf import symbol_database as _symbol_database
|
|
11
|
+
from google.protobuf.internal import builder as _builder
|
|
12
|
+
_runtime_version.ValidateProtobufRuntimeVersion(
|
|
13
|
+
_runtime_version.Domain.PUBLIC,
|
|
14
|
+
6,
|
|
15
|
+
33,
|
|
16
|
+
5,
|
|
17
|
+
'',
|
|
18
|
+
'eliza/v1/testing.proto'
|
|
19
|
+
)
|
|
20
|
+
# @@protoc_insertion_point(imports)
|
|
21
|
+
|
|
22
|
+
_sym_db = _symbol_database.Default()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x16\x65liza/v1/testing.proto\x12\x08\x65liza.v1\"Q\n\x08TestCase\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12\"\n\nhandler_id\x18\x02 \x01(\tH\x00R\thandlerId\x88\x01\x01\x42\r\n\x0b_handler_id\"I\n\tTestSuite\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12(\n\x05tests\x18\x02 \x03(\x0b\x32\x12.eliza.v1.TestCaseR\x05testsB\x8f\x01\n\x0c\x63om.eliza.v1B\x0cTestingProtoP\x01Z0github.com/elizaos/eliza/gen/go/eliza/v1;elizav1\xa2\x02\x03\x45XX\xaa\x02\x08\x45liza.V1\xca\x02\x08\x45liza\\V1\xe2\x02\x14\x45liza\\V1\\GPBMetadata\xea\x02\tEliza::V1b\x06proto3')
|
|
28
|
+
|
|
29
|
+
_globals = globals()
|
|
30
|
+
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
|
31
|
+
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'eliza.v1.testing_pb2', _globals)
|
|
32
|
+
if not _descriptor._USE_C_DESCRIPTORS:
|
|
33
|
+
_globals['DESCRIPTOR']._loaded_options = None
|
|
34
|
+
_globals['DESCRIPTOR']._serialized_options = b'\n\014com.eliza.v1B\014TestingProtoP\001Z0github.com/elizaos/eliza/gen/go/eliza/v1;elizav1\242\002\003EXX\252\002\010Eliza.V1\312\002\010Eliza\\V1\342\002\024Eliza\\V1\\GPBMetadata\352\002\tEliza::V1'
|
|
35
|
+
_globals['_TESTCASE']._serialized_start=36
|
|
36
|
+
_globals['_TESTCASE']._serialized_end=117
|
|
37
|
+
_globals['_TESTSUITE']._serialized_start=119
|
|
38
|
+
_globals['_TESTSUITE']._serialized_end=192
|
|
39
|
+
# @@protoc_insertion_point(module_scope)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from google.protobuf.internal import containers as _containers
|
|
2
|
+
from google.protobuf import descriptor as _descriptor
|
|
3
|
+
from google.protobuf import message as _message
|
|
4
|
+
from collections.abc import Iterable as _Iterable, Mapping as _Mapping
|
|
5
|
+
from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union
|
|
6
|
+
|
|
7
|
+
DESCRIPTOR: _descriptor.FileDescriptor
|
|
8
|
+
|
|
9
|
+
class TestCase(_message.Message):
|
|
10
|
+
__slots__ = ("name", "handler_id")
|
|
11
|
+
NAME_FIELD_NUMBER: _ClassVar[int]
|
|
12
|
+
HANDLER_ID_FIELD_NUMBER: _ClassVar[int]
|
|
13
|
+
name: str
|
|
14
|
+
handler_id: str
|
|
15
|
+
def __init__(self, name: _Optional[str] = ..., handler_id: _Optional[str] = ...) -> None: ...
|
|
16
|
+
|
|
17
|
+
class TestSuite(_message.Message):
|
|
18
|
+
__slots__ = ("name", "tests")
|
|
19
|
+
NAME_FIELD_NUMBER: _ClassVar[int]
|
|
20
|
+
TESTS_FIELD_NUMBER: _ClassVar[int]
|
|
21
|
+
name: str
|
|
22
|
+
tests: _containers.RepeatedCompositeFieldContainer[TestCase]
|
|
23
|
+
def __init__(self, name: _Optional[str] = ..., tests: _Optional[_Iterable[_Union[TestCase, _Mapping]]] = ...) -> None: ...
|
package/elizaos/types/model.py
CHANGED
|
@@ -6,6 +6,9 @@ from typing import TYPE_CHECKING, Any
|
|
|
6
6
|
|
|
7
7
|
from pydantic import BaseModel, Field
|
|
8
8
|
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from elizaos.types.runtime import IAgentRuntime
|
|
11
|
+
|
|
9
12
|
|
|
10
13
|
class LLMMode(str, Enum):
|
|
11
14
|
DEFAULT = "DEFAULT"
|
|
@@ -53,6 +56,9 @@ class ModelType(str, Enum):
|
|
|
53
56
|
# Research models (deep research)
|
|
54
57
|
RESEARCH = "RESEARCH"
|
|
55
58
|
|
|
59
|
+
# Safeguard models
|
|
60
|
+
SAFEGUARD = "SAFEGUARD"
|
|
61
|
+
|
|
56
62
|
|
|
57
63
|
# Type for model type names - allows string for extensibility
|
|
58
64
|
ModelTypeName = str
|
|
@@ -278,6 +284,30 @@ class ObjectGenerationParams(BaseModel):
|
|
|
278
284
|
model_config = {"populate_by_name": True, "extra": "allow"}
|
|
279
285
|
|
|
280
286
|
|
|
287
|
+
# ============================================================================
|
|
288
|
+
# Safeguard Model Types
|
|
289
|
+
# ============================================================================
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
class SafeguardParams(BaseModel):
|
|
293
|
+
"""Parameters for safeguard (content moderation) models."""
|
|
294
|
+
|
|
295
|
+
input: str = Field(..., description="Input text to classify")
|
|
296
|
+
model_type: str = Field(..., alias="modelType", description="Model type (must be SAFEGUARD)")
|
|
297
|
+
|
|
298
|
+
model_config = {"populate_by_name": True}
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
class SafeguardResult(BaseModel):
|
|
302
|
+
"""Result from a safeguard model classification."""
|
|
303
|
+
|
|
304
|
+
violation: float = Field(..., description="Violation score (0-1)")
|
|
305
|
+
category: str | None = Field(default=None, description="Violation category if any")
|
|
306
|
+
rationale: str | None = Field(default=None, description="Rationale for the classification")
|
|
307
|
+
|
|
308
|
+
model_config = {"populate_by_name": True}
|
|
309
|
+
|
|
310
|
+
|
|
281
311
|
# ============================================================================
|
|
282
312
|
# Research Model Types (Deep Research)
|
|
283
313
|
# ============================================================================
|
package/elizaos/types/runtime.py
CHANGED
|
@@ -7,7 +7,7 @@ from typing import TYPE_CHECKING, Any
|
|
|
7
7
|
from pydantic import BaseModel, Field
|
|
8
8
|
|
|
9
9
|
from elizaos.logger import Logger
|
|
10
|
-
from elizaos.types.database import IDatabaseAdapter
|
|
10
|
+
from elizaos.types.database import IDatabaseAdapter, MemorySearchOptions
|
|
11
11
|
from elizaos.types.primitives import UUID, Content
|
|
12
12
|
|
|
13
13
|
if TYPE_CHECKING:
|
|
@@ -231,7 +231,7 @@ class IAgentRuntime(ABC):
|
|
|
231
231
|
self,
|
|
232
232
|
message: Memory,
|
|
233
233
|
state: State | None = None,
|
|
234
|
-
) ->
|
|
234
|
+
) -> PreEvaluatorResult: ...
|
|
235
235
|
|
|
236
236
|
# Component registration
|
|
237
237
|
@abstractmethod
|
|
@@ -287,6 +287,7 @@ class IAgentRuntime(ABC):
|
|
|
287
287
|
include_list: list[str] | None = None,
|
|
288
288
|
only_include: bool = False,
|
|
289
289
|
skip_cache: bool = False,
|
|
290
|
+
trajectory_phase: str | None = None,
|
|
290
291
|
) -> State: ...
|
|
291
292
|
|
|
292
293
|
# Model usage
|
|
@@ -456,5 +457,8 @@ class IAgentRuntime(ABC):
|
|
|
456
457
|
@abstractmethod
|
|
457
458
|
async def get_relationships(self, params: dict[str, object]) -> list[object]: ...
|
|
458
459
|
|
|
460
|
+
@abstractmethod
|
|
461
|
+
async def search_memories(self, params: MemorySearchOptions) -> list[Memory]: ...
|
|
462
|
+
|
|
459
463
|
@abstractmethod
|
|
460
464
|
async def search_knowledge(self, query: str, limit: int = 5) -> list[object]: ...
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
from elizaos.types.memory import Memory
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def validate_action_keywords(
|
|
7
|
+
message: Memory, recent_messages: list[Memory], keywords: list[str]
|
|
8
|
+
) -> bool:
|
|
9
|
+
"""
|
|
10
|
+
Validates if any of the given keywords are present in the recent message history.
|
|
11
|
+
|
|
12
|
+
Checks:
|
|
13
|
+
1. The current message content
|
|
14
|
+
2. The last 5 messages in recent_messages
|
|
15
|
+
"""
|
|
16
|
+
if not keywords:
|
|
17
|
+
return False
|
|
18
|
+
|
|
19
|
+
relevant_text = []
|
|
20
|
+
|
|
21
|
+
# 1. Current message content
|
|
22
|
+
if message.content and message.content.text:
|
|
23
|
+
relevant_text.append(message.content.text)
|
|
24
|
+
|
|
25
|
+
# 2. Recent messages (last 5)
|
|
26
|
+
# Take the last 5 messages
|
|
27
|
+
recent_subset = recent_messages[-5:] if recent_messages else []
|
|
28
|
+
|
|
29
|
+
for msg in recent_subset:
|
|
30
|
+
if msg.content and msg.content.text:
|
|
31
|
+
relevant_text.append(msg.content.text)
|
|
32
|
+
|
|
33
|
+
if not relevant_text:
|
|
34
|
+
return False
|
|
35
|
+
|
|
36
|
+
combined_text = "\n".join(relevant_text).lower()
|
|
37
|
+
|
|
38
|
+
return any(keyword.lower() in combined_text for keyword in keywords)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def validate_action_regex(
|
|
42
|
+
message: Memory, recent_messages: list[Memory], regex_pattern: str
|
|
43
|
+
) -> bool:
|
|
44
|
+
"""
|
|
45
|
+
Validates if any of the recent message history matches the given regex pattern.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
message: The current message memory
|
|
49
|
+
recent_messages: List of recent memories
|
|
50
|
+
regex_pattern: The regular expression pattern to check against
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
bool: True if the regex matches any message content, False otherwise
|
|
54
|
+
"""
|
|
55
|
+
if not regex_pattern:
|
|
56
|
+
return False
|
|
57
|
+
|
|
58
|
+
relevant_text = []
|
|
59
|
+
|
|
60
|
+
# 1. Current message content
|
|
61
|
+
if message.content and message.content.text:
|
|
62
|
+
relevant_text.append(message.content.text)
|
|
63
|
+
|
|
64
|
+
# 2. Recent messages (last 5)
|
|
65
|
+
recent_subset = recent_messages[-5:] if recent_messages else []
|
|
66
|
+
|
|
67
|
+
for msg in recent_subset:
|
|
68
|
+
if msg.content and msg.content.text:
|
|
69
|
+
relevant_text.append(msg.content.text)
|
|
70
|
+
|
|
71
|
+
if not relevant_text:
|
|
72
|
+
return False
|
|
73
|
+
|
|
74
|
+
combined_text = "\n".join(relevant_text)
|
|
75
|
+
|
|
76
|
+
return bool(re.search(regex_pattern, combined_text, re.MULTILINE))
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elizaos/python",
|
|
3
|
-
"version": "2.0.0-alpha.
|
|
3
|
+
"version": "2.0.0-alpha.26",
|
|
4
4
|
"description": "elizaOS Core - Python runtime and types",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -15,5 +15,6 @@
|
|
|
15
15
|
"typecheck": "python3.13 -m mypy elizaos || echo 'mypy not installed, skipping typecheck'"
|
|
16
16
|
},
|
|
17
17
|
"author": "elizaOS",
|
|
18
|
-
"license": "MIT"
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"gitHead": "91dceb1d2e9762af27353dbc764e40e1a0599508"
|
|
19
20
|
}
|
|
@@ -19,7 +19,6 @@ from elizaos.types import (
|
|
|
19
19
|
)
|
|
20
20
|
|
|
21
21
|
|
|
22
|
-
@pytest.mark.skip(reason="Content proto doesn't have params field")
|
|
23
22
|
@pytest.mark.asyncio
|
|
24
23
|
async def test_process_actions_passes_validated_params_to_handler_options() -> None:
|
|
25
24
|
character = Character(name="ParamAgent", bio=["Test agent"], system="Test")
|
|
@@ -83,10 +82,10 @@ async def test_process_actions_passes_validated_params_to_handler_options() -> N
|
|
|
83
82
|
|
|
84
83
|
await runtime.process_actions(message, [response], state=None, callback=None)
|
|
85
84
|
|
|
86
|
-
|
|
85
|
+
# Content proto has no params field, so schema defaults are applied.
|
|
86
|
+
assert received == ["north"]
|
|
87
87
|
|
|
88
88
|
|
|
89
|
-
@pytest.mark.skip(reason="Content proto doesn't have params field")
|
|
90
89
|
@pytest.mark.asyncio
|
|
91
90
|
async def test_process_actions_skips_action_when_required_param_missing() -> None:
|
|
92
91
|
character = Character(name="ParamAgent", bio=["Test agent"], system="Test")
|
|
@@ -3,7 +3,26 @@ import pytest
|
|
|
3
3
|
from elizaos.action_docs import with_canonical_action_docs
|
|
4
4
|
from elizaos.bootstrap.actions import send_message_action
|
|
5
5
|
from elizaos.runtime import AgentRuntime
|
|
6
|
-
from elizaos.types import Character, Content, Memory, as_uuid
|
|
6
|
+
from elizaos.types import Action, Character, Content, Memory, as_uuid
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
async def _noop_handler(
|
|
10
|
+
runtime: AgentRuntime,
|
|
11
|
+
message: Memory,
|
|
12
|
+
state: object | None = None,
|
|
13
|
+
options: object | None = None,
|
|
14
|
+
callback: object | None = None,
|
|
15
|
+
responses: object | None = None,
|
|
16
|
+
) -> None:
|
|
17
|
+
return None
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
async def _always_valid(
|
|
21
|
+
runtime: AgentRuntime,
|
|
22
|
+
message: Memory,
|
|
23
|
+
state: object | None = None,
|
|
24
|
+
) -> bool:
|
|
25
|
+
return True
|
|
7
26
|
|
|
8
27
|
|
|
9
28
|
@pytest.mark.asyncio
|
|
@@ -37,3 +56,41 @@ async def test_actions_provider_includes_examples_and_parameter_examples() -> No
|
|
|
37
56
|
# Canonical docs include examples for SEND_MESSAGE parameters
|
|
38
57
|
assert "SEND_MESSAGE" in text
|
|
39
58
|
assert "# Action Call Examples" in text
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@pytest.mark.asyncio
|
|
62
|
+
async def test_actions_provider_does_not_trim_available_actions_to_top_10() -> None:
|
|
63
|
+
runtime = AgentRuntime(
|
|
64
|
+
character=Character(name="NoTrimTest", bio=["no trim test"], system="test"),
|
|
65
|
+
log_level="ERROR",
|
|
66
|
+
)
|
|
67
|
+
await runtime.initialize()
|
|
68
|
+
|
|
69
|
+
custom_action_names: list[str] = []
|
|
70
|
+
for index in range(12):
|
|
71
|
+
action_name = f"CUSTOM_ACTION_{index:02d}"
|
|
72
|
+
custom_action_names.append(action_name)
|
|
73
|
+
runtime.register_action(
|
|
74
|
+
Action(
|
|
75
|
+
name=action_name,
|
|
76
|
+
description=f"Custom action {index}",
|
|
77
|
+
handler=_noop_handler,
|
|
78
|
+
validate=_always_valid,
|
|
79
|
+
)
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
actions_provider = next(p for p in runtime.providers if p.name == "ACTIONS")
|
|
83
|
+
|
|
84
|
+
message = Memory(
|
|
85
|
+
id=as_uuid("42345678-1234-1234-1234-123456789012"),
|
|
86
|
+
entity_id=as_uuid("42345678-1234-1234-1234-123456789013"),
|
|
87
|
+
room_id=as_uuid("42345678-1234-1234-1234-123456789014"),
|
|
88
|
+
content=Content(text="show me every action"),
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
state = await runtime.compose_state(message)
|
|
92
|
+
result = await actions_provider.get(runtime, message, state)
|
|
93
|
+
|
|
94
|
+
text = result.text or ""
|
|
95
|
+
for action_name in custom_action_names:
|
|
96
|
+
assert action_name in text
|
|
@@ -11,7 +11,6 @@ from elizaos.types.memory import Memory
|
|
|
11
11
|
from elizaos.types.primitives import Content, as_uuid
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
@pytest.mark.skip(reason="MemoryService requires runtime settings.get() which isn't implemented")
|
|
15
14
|
@pytest.mark.asyncio
|
|
16
15
|
async def test_memory_provider_formats_long_term_memories() -> None:
|
|
17
16
|
runtime = AgentRuntime(
|
|
@@ -49,7 +48,6 @@ async def test_memory_provider_formats_long_term_memories() -> None:
|
|
|
49
48
|
assert result.text and "What I Know About You" in result.text
|
|
50
49
|
|
|
51
50
|
|
|
52
|
-
@pytest.mark.skip(reason="MemoryService runtime not set")
|
|
53
51
|
@pytest.mark.asyncio
|
|
54
52
|
async def test_get_long_term_memories_returns_top_confidence() -> None:
|
|
55
53
|
svc = MemoryService(runtime=None)
|
|
@@ -6,7 +6,6 @@ from elizaos.runtime import AgentRuntime
|
|
|
6
6
|
from elizaos.types.agent import Character
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
@pytest.mark.skip(reason="MemoryService requires runtime settings.get() which isn't implemented")
|
|
10
9
|
@pytest.mark.asyncio
|
|
11
10
|
async def test_advanced_memory_autoloads_when_enabled() -> None:
|
|
12
11
|
runtime = AgentRuntime(
|
|
@@ -19,7 +18,6 @@ async def test_advanced_memory_autoloads_when_enabled() -> None:
|
|
|
19
18
|
assert any(e.name == "MEMORY_SUMMARIZATION" for e in runtime.evaluators)
|
|
20
19
|
|
|
21
20
|
|
|
22
|
-
@pytest.mark.skip(reason="MemoryService requires runtime settings.get() which isn't implemented")
|
|
23
21
|
@pytest.mark.asyncio
|
|
24
22
|
async def test_advanced_memory_not_loaded_when_disabled() -> None:
|
|
25
23
|
runtime = AgentRuntime(
|
|
@@ -4,6 +4,7 @@ from typing import Any
|
|
|
4
4
|
from uuid import uuid4
|
|
5
5
|
|
|
6
6
|
import pytest
|
|
7
|
+
from google.protobuf.json_format import MessageToDict
|
|
7
8
|
|
|
8
9
|
from elizaos.advanced_planning.planning_service import ActionPlan, ActionStep
|
|
9
10
|
from elizaos.runtime import AgentRuntime
|
|
@@ -15,7 +16,6 @@ from elizaos.types.model import ModelType
|
|
|
15
16
|
from elizaos.types.primitives import Content, as_uuid
|
|
16
17
|
|
|
17
18
|
|
|
18
|
-
@pytest.mark.skip(reason="State.values.extra access issue with protobuf")
|
|
19
19
|
@pytest.mark.asyncio
|
|
20
20
|
async def test_advanced_planning_provider_parses_model_output() -> None:
|
|
21
21
|
character = Character(name="AdvPlanningProvider", bio=["Test"], advanced_planning=True)
|
|
@@ -49,7 +49,13 @@ async def test_advanced_planning_provider_parses_model_output() -> None:
|
|
|
49
49
|
state = await runtime.compose_state(msg)
|
|
50
50
|
result = await provider.get(runtime, msg, state)
|
|
51
51
|
assert result.data is not None
|
|
52
|
-
|
|
52
|
+
parsed_data = (
|
|
53
|
+
MessageToDict(result.data, preserving_proto_field_name=True)
|
|
54
|
+
if hasattr(result.data, "DESCRIPTOR")
|
|
55
|
+
else result.data
|
|
56
|
+
)
|
|
57
|
+
assert isinstance(parsed_data, dict)
|
|
58
|
+
assert parsed_data.get("planningRequired") is True
|
|
53
59
|
|
|
54
60
|
|
|
55
61
|
@pytest.mark.asyncio
|
|
@@ -72,7 +78,6 @@ async def test_advanced_planning_service_creates_simple_plan() -> None:
|
|
|
72
78
|
assert any(step.action_name == "SEND_EMAIL" for step in plan.steps)
|
|
73
79
|
|
|
74
80
|
|
|
75
|
-
@pytest.mark.skip(reason="State.values.extra access issue with protobuf")
|
|
76
81
|
@pytest.mark.asyncio
|
|
77
82
|
async def test_advanced_planning_service_creates_comprehensive_plan_and_executes() -> None:
|
|
78
83
|
character = Character(name="AdvPlanningSvcExec", bio=["Test"], advanced_planning=True)
|
|
@@ -124,7 +129,6 @@ async def test_advanced_planning_service_creates_comprehensive_plan_and_executes
|
|
|
124
129
|
assert result.total_steps >= 1
|
|
125
130
|
|
|
126
131
|
|
|
127
|
-
@pytest.mark.skip(reason="validate lambda returns bool, not coroutine")
|
|
128
132
|
@pytest.mark.asyncio
|
|
129
133
|
async def test_advanced_planning_dag_executes_in_dependency_order() -> None:
|
|
130
134
|
character = Character(name="AdvPlanningDag", bio=["Test"], advanced_planning=True)
|
|
@@ -220,6 +224,8 @@ async def test_advanced_planning_dag_executes_in_dependency_order() -> None:
|
|
|
220
224
|
content=Content(text="run"),
|
|
221
225
|
)
|
|
222
226
|
state = await runtime.compose_state(msg)
|
|
223
|
-
await planning_service.execute_plan(plan, msg, state=state, callback=None)
|
|
227
|
+
execution_result = await planning_service.execute_plan(plan, msg, state=state, callback=None)
|
|
224
228
|
|
|
229
|
+
assert execution_result.success is True
|
|
230
|
+
assert execution_result.completed_steps == 3
|
|
225
231
|
assert execution_order == ["STEP_A", "STEP_B", "STEP_C"]
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import unittest
|
|
3
|
+
import uuid
|
|
4
|
+
from unittest.mock import AsyncMock, MagicMock
|
|
5
|
+
|
|
6
|
+
from elizaos.bootstrap.services.embedding import EmbeddingService
|
|
7
|
+
from elizaos.types import ModelType
|
|
8
|
+
from elizaos.types.events import EventType
|
|
9
|
+
from elizaos.types.memory import Memory
|
|
10
|
+
from elizaos.types.primitives import Content
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TestAsyncEmbedding(unittest.IsolatedAsyncioTestCase):
|
|
14
|
+
async def test_async_embedding_generation(self):
|
|
15
|
+
# Mock runtime
|
|
16
|
+
runtime = (
|
|
17
|
+
MagicMock()
|
|
18
|
+
) # Don't use spec=IAgentRuntime to avoid abstract methods issues for now
|
|
19
|
+
runtime.agent_id = uuid.uuid4()
|
|
20
|
+
runtime.logger = MagicMock()
|
|
21
|
+
|
|
22
|
+
# Event handling
|
|
23
|
+
events = {}
|
|
24
|
+
|
|
25
|
+
def register_event(event, handler):
|
|
26
|
+
if event not in events:
|
|
27
|
+
events[event] = []
|
|
28
|
+
events[event].append(handler)
|
|
29
|
+
|
|
30
|
+
async def emit_event(event, payload):
|
|
31
|
+
handlers = events.get(event, [])
|
|
32
|
+
for handler in handlers:
|
|
33
|
+
if asyncio.iscoroutinefunction(handler):
|
|
34
|
+
await handler(payload)
|
|
35
|
+
else:
|
|
36
|
+
handler(payload)
|
|
37
|
+
|
|
38
|
+
async def use_model(model_type, **kwargs):
|
|
39
|
+
if model_type == ModelType.TEXT_SMALL:
|
|
40
|
+
return "intent"
|
|
41
|
+
if model_type == ModelType.TEXT_EMBEDDING:
|
|
42
|
+
return [0.1] * 384
|
|
43
|
+
return None
|
|
44
|
+
|
|
45
|
+
runtime.register_event = register_event
|
|
46
|
+
runtime.emit_event = AsyncMock(side_effect=emit_event)
|
|
47
|
+
runtime.use_model = AsyncMock(side_effect=use_model)
|
|
48
|
+
|
|
49
|
+
# Mock adapter
|
|
50
|
+
adapter = AsyncMock()
|
|
51
|
+
runtime.db = adapter
|
|
52
|
+
runtime._adapter = adapter
|
|
53
|
+
service = await EmbeddingService.start(runtime)
|
|
54
|
+
|
|
55
|
+
# Setup completion listener
|
|
56
|
+
completed_future = asyncio.Future()
|
|
57
|
+
|
|
58
|
+
async def on_completed(payload):
|
|
59
|
+
completed_future.set_result(payload)
|
|
60
|
+
|
|
61
|
+
runtime.register_event(
|
|
62
|
+
EventType.Name(EventType.EVENT_TYPE_EMBEDDING_GENERATION_COMPLETED), on_completed
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
# Create memory
|
|
66
|
+
memory_id = str(uuid.uuid4())
|
|
67
|
+
memory = Memory(
|
|
68
|
+
id=memory_id,
|
|
69
|
+
content=Content(
|
|
70
|
+
text="A very long message that should trigger intent generation and then embedding."
|
|
71
|
+
),
|
|
72
|
+
room_id=str(uuid.uuid4()),
|
|
73
|
+
entity_id=str(uuid.uuid4()),
|
|
74
|
+
agent_id=str(runtime.agent_id),
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
# Emit request
|
|
78
|
+
# Construct proper EventPayload
|
|
79
|
+
# EventPayload (protobuf) expects 'extra' to be a Struct or map<string, Value>
|
|
80
|
+
# But for python test convenience, we can use a mock that has attributes
|
|
81
|
+
|
|
82
|
+
# Actually EventPayload 'extra' field is google.protobuf.Struct usually
|
|
83
|
+
# But here we just need an object with .extra attribute for our code to work
|
|
84
|
+
# Or we can use the actual proto class
|
|
85
|
+
|
|
86
|
+
# Let's use a simple Namespace
|
|
87
|
+
from types import SimpleNamespace
|
|
88
|
+
|
|
89
|
+
payload = SimpleNamespace(extra={"memory": memory})
|
|
90
|
+
|
|
91
|
+
# Manually trigger handler because emit_event in MockRuntime doesn't wait for queue processing
|
|
92
|
+
# In real runtime, emit_event calls handlers. embedding service handler writes to queue.
|
|
93
|
+
# Worker reads from queue.
|
|
94
|
+
|
|
95
|
+
event_name = EventType.Name(EventType.EVENT_TYPE_EMBEDDING_GENERATION_REQUESTED)
|
|
96
|
+
await runtime.emit_event(event_name, payload)
|
|
97
|
+
|
|
98
|
+
# Wait for completion
|
|
99
|
+
try:
|
|
100
|
+
result = await asyncio.wait_for(completed_future, timeout=5.0)
|
|
101
|
+
self.assertEqual(result["memory_id"], str(memory_id))
|
|
102
|
+
except TimeoutError:
|
|
103
|
+
print("TIMEOUT! Logging errors:")
|
|
104
|
+
for call in runtime.logger.error.call_args_list:
|
|
105
|
+
print(call)
|
|
106
|
+
for call in runtime.logger.warning.call_args_list:
|
|
107
|
+
print(call)
|
|
108
|
+
self.fail("Embedding generation timed out")
|
|
109
|
+
|
|
110
|
+
# Verify db update
|
|
111
|
+
runtime._adapter.update_memory.assert_called_once()
|
|
112
|
+
call_args = runtime._adapter.update_memory.call_args
|
|
113
|
+
updated_memory = call_args[0][0]
|
|
114
|
+
self.assertIsNotNone(updated_memory.embedding)
|
|
115
|
+
self.assertEqual(len(updated_memory.embedding), 384)
|
|
116
|
+
# Check intent (metadata update)
|
|
117
|
+
# Verify in custom metadata
|
|
118
|
+
self.assertEqual(updated_memory.metadata.custom.custom_data["intent"], "intent")
|
|
119
|
+
|
|
120
|
+
await service.stop()
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
if __name__ == "__main__":
|
|
124
|
+
unittest.main()
|