8000 App object WIP next step: reconcile serialization · microsoft/Agents-for-python@8c8ab9e · GitHub
[go: up one dir, main page]

Skip to content

Commit 8c8ab9e

Browse files
committed
App object WIP next step: reconcile serialization
1 parent cbbd877 commit 8c8ab9e

File tree

10 files changed

+577
-34
lines changed

10 files changed

+577
-34
lines changed
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
"""
2+
Copyright (c) Microsoft Corporation. All rights reserved.
3+
Licensed under the MIT License.
4+
"""
5+
6+
from __future__ import annotations
7+
8+
from .activity_type import (
9+
ActivityType,
10+
ConversationUpdateType,
11+
MessageReactionType,
12+
MessageUpdateType,
13+
)
14+
from .app import Application
15+
from .app_error import ApplicationError
16+
from .app_options import ApplicationOptions
17+
from .input_file import InputFile, InputFileDownloader
18+
from .query import Query
19+
from .route import Route, RouteHandler
20+
from .typing import Typing
21+
22+
__all__ = [
23+
"ActivityType",
24+
"Application",
25+
"ApplicationError",
26+
"ApplicationOptions",
27+
"ConversationUpdateType",
28+
"InputFile",
29+
"InputFileDownloader",
30+
"MessageReactionType",
31+
"MessageUpdateType",
32+
"Query",
33+
"Route",
34+
"RouteHandler",
35+
"Typing",
36+
]

libraries/Builder/microsoft-agents-builder/microsoft/agents/builder/app/app.py

Lines changed: 7 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
from .. import Agent, TurnContext
2323
from microsoft.agents.core.models import Activity, ActivityTypes, InvokeResponse
2424

25-
from .activity_type import (
25+
from . import (
2626
ActivityType,
2727
ConversationUpdateType,
2828
MessageReactionType,
@@ -32,11 +32,9 @@
3232
from .app_error import ApplicationError
3333
from .app_options import ApplicationOptions
3434
from .auth import AuthManager, OAuth, OAuthOptions
35-
from .message_extensions.message_extensions import MessageExtensions
3635
from .route import Route, RouteHandler
37-
from .state import TurnState
38-
from .task_modules import TaskModules
39-
from .teams_adapter import TeamsAdapter
36+
from ..state import TurnState
37+
from ..channel_service_adapter import ChannelServiceAdapter
4038
from .typing import Typing
4139

4240
StateT = TypeVar("StateT", bound=TurnState)
@@ -59,15 +57,13 @@ class Application(Agent, Generic[StateT]):
5957
typing: Typing
6058

6159
_options: ApplicationOptions
62-
_adapter: Optional[TeamsAdapter] = None
60+
_adapter: Optional[ChannelServiceAdapter] = None
6361
_auth: Optional[AuthManager[StateT]] = None
6462
_before_turn: List[RouteHandler[StateT]] = []
6563
_after_turn: List[RouteHandler[StateT]] = []
6664
_routes: List[Route[StateT]] = []
6765
_error: Optional[Callable[[TurnContext, Exception], Awaitable[None]]] = None
6866
_turn_state_factory: Optional[Callable[[TurnContext], Awaitable[StateT]]] = None
69-
_message_extensions: MessageExtensions[StateT]
70-
_task_modules: TaskModules[StateT]
7167

7268
def __init__(self, options: ApplicationOptions = ApplicationOptions()) -> None:
7369
"""
@@ -76,10 +72,6 @@ def __init__(self, options: ApplicationOptions = ApplicationOptions()) -> None:
7672
self.typing = Typing()
7773
self._options = options
7874
self._routes = []
79-
self._message_extensions = MessageExtensions[StateT](self._routes)
80-
self._task_modules = TaskModules[StateT](
81-
self._routes, options.task_modules.task_data_filter
82-
)
8375

8476
if options.long_running_messages and (
8577
not options.adapter or not options.bot_app_id
@@ -102,7 +94,7 @@ def __init__(self, options: ApplicationOptions = ApplicationOptions()) -> None:
10294
self._auth.set(name, OAuth[StateT](opts))
10395

10496
@property
105-
def adapter(self) -> TeamsAdapter:
97+
def adapter(self) -> ChannelServiceAdapter:
10698
"""
10799
The bot's adapter.
108100
"""
@@ -139,22 +131,8 @@ def options(self) -> ApplicationOptions:
139131
"""
140132
return self._options
141133

142-
@property
143-
def message_extensions(self) -> MessageExtensions[StateT]:
144-
"""
145-
Message Extensions
146-
"""
147-
return self._message_extensions
148-
149-
@property
150-
def task_modules(self) -> TaskModules[StateT]:
151-
"""
152-
Access the application's task modules functionalities.
153-
"""
154-
return self._task_modules
155-
156134
def activity(
157-
self, type: ActivityType
135+
self, type: Union[str, Pattern[str], List[Union[str, Pattern[str]]]]
158136
) -> Callable[[RouteHandler[StateT]], RouteHandler[StateT]]:
159137
"""
160138
Registers a new activity event listener. This method can be used as either
@@ -185,7 +163,7 @@ def __call__(func: RouteHandler[StateT]) -> RouteHandler[StateT]:
185163
return __call__
186164

187165
def message(
188-
self, select: Union[str, Pattern[str]]
166+
self, select: Union[str, Pattern[str], List[Union[str, Pattern[str]]]]
189167
) -> Callable[[RouteHandler[StateT]], RouteHandler[StateT]]:
190168
"""
191169
Registers a new message activity event listener. This method can be used as either

libraries/Builder/microsoft-agents-builder/microsoft/agents/builder/app/route.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@
88
from typing import Awaitable, Callable, Generic, TypeVar
99

1010
from .. import TurnContext
11-
12-
from .. import TurnState
11+
from ..state import TurnState
1312

1413
StateT = TypeVar("StateT", bound=TurnState)
1514
RouteHandler = Callable[[TurnContext, StateT], Awaitable[bool]]

libraries/Builder/microsoft-agents-builder/microsoft/agents/builder/app/typing.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
from threading import Timer
99
from typing import Optional
1010

11-
from botbuilder.core import TurnContext
12-
from botbuilder.schema import Activity, ActivityTypes, ErrorResponseException
11+
from .. import TurnContext
12+
from microsoft.agents.core.models import Activity, ActivityTypes, ErrorResponseException
1313

1414

1515
class Typing:
Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,22 @@
11
from .agent_state import AgentState
22
from .state_property_accessor import StatePropertyAccessor
33
from .user_state import UserState
4+
from .conversation_state import ConversationState
5+
from .memory import Memory, MemoryBase
6+
from .state impor F42D t State, StatePropertyAccessor, state
7+
from .temp_state import TempState
8+
from .turn_state import TurnState
49

5-
__all__ = ["AgentState", "StatePropertyAccessor", "UserState"]
10+
__all__ = [
11+
"AgentState",
12+
"StatePropertyAccessor",
13+
"ConversationState",
14+
"Memory",
15+
"MemoryBase",
16+
"state",
17+
"State",
18+
"StatePropertyAccessor",
19+
"TurnState",
20+
"UserState",
21+
"TempState",
22+
]
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
"""
2+
Copyright (c) Microsoft Corporation. All rights reserved.
3+
Licensed under the MIT License.
4+
"""
5+
6+
from __future__ import annotations
7+
8+
from typing import Any, Dict, Optional
9+
10+
from microsoft.agents.storage import Storage, StoreItem
11+
12+
from ..turn_context import TurnContext
13+
from .state import State, state
14+
15+
16+
@state
17+
class ConversationState(State):
18+
"""
19+
Default Conversation State
20+
"""
21+
22+
@classmethod
23+
async def load(
24+
cls, context: TurnContext, storage: Optional[Storage] = None
25+
) -> "ConversationState":
26+
activity = context.activity
27+
28+
if not activity.channel_id:
29+
raise ValueError("missing activity.channel_id")
30+
if not activity.conversation:
31+
raise ValueError("missing activity.conversation")
32+
if not activity.recipient:
33+
raise ValueError("missing activity.recipient")
34+
35+
channel_id = activity.channel_id
36+
conversation_id = activity.conversation.id
37+
bot_id = activity.recipient.id
38+
key = f"{channel_id}/{bot_id}/conversations/{conversation_id}"
39+
40+
if not storage:
41+
return cls(__key__=key)
42+
43+
data: Dict[str, Any] = await storage.read([key])
44+
45+
if key in data:
46+
if isinstance(data[key], StoreItem):
47+
return cls(__key__=key, **vars(data[key]))
48+
return cls(__key__=key, **data[key])
49+
50+
return cls(__key__=key, **data)
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
"""
2+
Copyright (c) Microsoft Corporation. All rights reserved.
3+
Licensed under the MIT License.
4+
"""
5+
6+
from __future__ import annotations
7+
8+
from abc import ABC, abstractmethod
9+
from collections import UserDict
10+
from typing import Any, Dict, MutableMapping, Optional, Tuple
11+
12+
from ..app import ApplicationError
13+
14+
15+
class MemoryBase(ABC):
16+
"""
17+
Memory Base
18+
"""
19+
20+
@abstractmethod
21+
def has(self, path: str) -> bool:
22+
"""
23+
Checks if a value exists in the memory.
24+
25+
Args:
26+
path (str): Path to the value to check in the form of `[scope].property`.
27+
If scope is omitted, the value is checked in the temporary scope.
28+
29+
Returns:
30+
bool: True if the value exists, False otherwise.
31+
"""
32+
33+
@abstractmethod
34+
def get(self, path: str) -> Optional[Any]:
35+
"""
36+
Retrieves a value from the memory.
37+
38+
Args:
39+
path (str): Path to the value to retrieve in the form of `[scope].property`.
40+
If scope is omitted, the value is retrieved from the temporary scope.
41+
42+
Returns:
43+
Any: The value or None if not found.
44+
"""
45+
46+
@abstractmethod
47+
def set(self, path: str, value: Any) -> None:
48+
"""
49+
Assigns a value to the memory.
50+
51+
Args:
52+
path (str): Path to the value to assign in the form of `[scope].property`.
53+
If scope is omitted, the value is assigned to the temporary scope.
54+
value (Any): Value to assign.
55+
"""
56+
57+
@abstractmethod
58+
def delete(self, path: str) -> None:
59+
"""
60+
Deletes a value from the memory.
61+
62+
Args:
63+
path (str): Path to the value to delete in the form of `[scope].property`.
64+
If scope is omitted, the value is deleted from the temporary scope.
65+
"""
66+
67+
def _get_scope_and_name(self, path: str) -> Tuple[str, str]:
68+
parts = path.split(".")
69+
70+
if len(parts) > 2:
71+
raise ApplicationError(f"Invalid state path: {path}")
72+
73+
if len(parts) == 1:
74+
parts.insert(0, "temp")
75+
76+
return (parts[0], parts[1])
77+
78+
79+
class Memory(MemoryBase):
80+
"""
81+
Represents a memory.
82+
83+
A memory is a key-value store that can be used to store and retrieve values.
84+
"""
85+
86+
_parent: Optional[MemoryBase]
87+
_scopes: Dict[str, MutableMapping[str, Any]]
88+
89+
def __init__(self, parent: Optional[MemoryBase] = None) -> None:
90+
self._parent = parent
91+
self._scopes = {}
92+
93+
def has(self, path: str) -> bool:
94+
scope, name = self._get_scope_and_name(path)
95+
96+
if scope in self._scopes and name in self._scopes[scope]:
97+
return True
98+
99+
if self._parent:
100+
return self._parent.has(path)
101+
102+
return False
103+
104+
def get(self, path: str) -> Optional[Any]:
105+
scope, name = self._get_scope_and_name(path)
106+
107+
if scope in self._scopes and name in self._scopes[scope]:
108+
return self._scopes[scope][name]
109+
110+
if self._parent:
111+
return self._parent.get(path)
112+
113+
return None
114+
115+
def set(self, path: str, value: Any) -> None:
116+
scope, name = self._get_scope_and_name(path)
117+
118+
if not scope in self._scopes:
119+
self._scopes[scope] = UserDict()
120+
121+
self._scopes[scope][name] = value
122+
123+
def delete(self, path: str) -> None:
124+
scope, name = self._get_scope_and_name(path)
125+
126+
if scope in self._scopes and name in self._scopes[scope]:
127+
del self._scopes[scope][name]

0 commit comments

Comments
 (0)
0