nora-lib 0.0.5.dev2__tar.gz → 0.0.6__tar.gz
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.
- {nora_lib-0.0.5.dev2 → nora_lib-0.0.6}/PKG-INFO +2 -1
- {nora_lib-0.0.5.dev2 → nora_lib-0.0.6}/nora_lib/interactions/interactions_service.py +74 -9
- {nora_lib-0.0.5.dev2 → nora_lib-0.0.6}/nora_lib/interactions/models.py +10 -8
- nora_lib-0.0.6/nora_lib/tasks/state.py +163 -0
- {nora_lib-0.0.5.dev2 → nora_lib-0.0.6}/nora_lib.egg-info/PKG-INFO +2 -1
- {nora_lib-0.0.5.dev2 → nora_lib-0.0.6}/nora_lib.egg-info/requires.txt +1 -0
- {nora_lib-0.0.5.dev2 → nora_lib-0.0.6}/setup.py +2 -2
- nora_lib-0.0.5.dev2/nora_lib/tasks/state.py +0 -53
- {nora_lib-0.0.5.dev2 → nora_lib-0.0.6}/README.md +0 -0
- {nora_lib-0.0.5.dev2 → nora_lib-0.0.6}/nora_lib/__init__.py +0 -0
- {nora_lib-0.0.5.dev2 → nora_lib-0.0.6}/nora_lib/context/__init__.py +0 -0
- {nora_lib-0.0.5.dev2 → nora_lib-0.0.6}/nora_lib/context/context_service.py +0 -0
- {nora_lib-0.0.5.dev2 → nora_lib-0.0.6}/nora_lib/context/models.py +0 -0
- {nora_lib-0.0.5.dev2 → nora_lib-0.0.6}/nora_lib/interactions/__init__.py +0 -0
- {nora_lib-0.0.5.dev2 → nora_lib-0.0.6}/nora_lib/py.typed +0 -0
- {nora_lib-0.0.5.dev2 → nora_lib-0.0.6}/nora_lib/tasks/__init__.py +0 -0
- {nora_lib-0.0.5.dev2 → nora_lib-0.0.6}/nora_lib/tasks/models.py +0 -0
- {nora_lib-0.0.5.dev2 → nora_lib-0.0.6}/nora_lib.egg-info/SOURCES.txt +0 -0
- {nora_lib-0.0.5.dev2 → nora_lib-0.0.6}/nora_lib.egg-info/dependency_links.txt +0 -0
- {nora_lib-0.0.5.dev2 → nora_lib-0.0.6}/nora_lib.egg-info/top_level.txt +0 -0
- {nora_lib-0.0.5.dev2 → nora_lib-0.0.6}/pyproject.toml +0 -0
- {nora_lib-0.0.5.dev2 → nora_lib-0.0.6}/setup.cfg +0 -0
- {nora_lib-0.0.5.dev2 → nora_lib-0.0.6}/tests/tasks/__init__.py +0 -0
- {nora_lib-0.0.5.dev2 → nora_lib-0.0.6}/tests/tasks/test_state.py +0 -0
- {nora_lib-0.0.5.dev2 → nora_lib-0.0.6}/tests/test_placeholder.py +0 -0
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: nora_lib
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.6
|
|
4
4
|
Summary: For making and coordinating agents and tools
|
|
5
5
|
Home-page: https://github.com/allenai/nora_lib
|
|
6
6
|
Requires-Python: >=3.9
|
|
7
7
|
Requires-Dist: pydantic<3,>=2
|
|
8
8
|
Requires-Dist: requests
|
|
9
|
+
Requires-Dist: boto3
|
|
9
10
|
Provides-Extra: dev
|
|
10
11
|
Requires-Dist: mypy; extra == "dev"
|
|
11
12
|
Requires-Dist: pytest; extra == "dev"
|
|
@@ -2,6 +2,7 @@ from datetime import datetime
|
|
|
2
2
|
import logging
|
|
3
3
|
import requests
|
|
4
4
|
from typing import List, Optional
|
|
5
|
+
import json
|
|
5
6
|
|
|
6
7
|
from nora_lib.interactions.models import (
|
|
7
8
|
AnnotationBatch,
|
|
@@ -31,7 +32,7 @@ class InteractionsService:
|
|
|
31
32
|
message_url,
|
|
32
33
|
json=message.model_dump(),
|
|
33
34
|
headers=self.headers,
|
|
34
|
-
timeout=
|
|
35
|
+
timeout=self.timeout,
|
|
35
36
|
)
|
|
36
37
|
response.raise_for_status()
|
|
37
38
|
|
|
@@ -42,7 +43,7 @@ class InteractionsService:
|
|
|
42
43
|
event_url,
|
|
43
44
|
json=event.model_dump(),
|
|
44
45
|
headers=self.headers,
|
|
45
|
-
timeout=
|
|
46
|
+
timeout=self.timeout,
|
|
46
47
|
)
|
|
47
48
|
response.raise_for_status()
|
|
48
49
|
|
|
@@ -53,7 +54,7 @@ class InteractionsService:
|
|
|
53
54
|
annotation_url,
|
|
54
55
|
json=annotation.model_dump(),
|
|
55
56
|
headers=self.headers,
|
|
56
|
-
timeout=
|
|
57
|
+
timeout=self.timeout,
|
|
57
58
|
)
|
|
58
59
|
response.raise_for_status()
|
|
59
60
|
|
|
@@ -68,7 +69,7 @@ class InteractionsService:
|
|
|
68
69
|
message_url,
|
|
69
70
|
json=request_body,
|
|
70
71
|
headers=self.headers,
|
|
71
|
-
timeout=
|
|
72
|
+
timeout=self.timeout,
|
|
72
73
|
)
|
|
73
74
|
response.raise_for_status()
|
|
74
75
|
res_dict = response.json()["message"]
|
|
@@ -92,7 +93,7 @@ class InteractionsService:
|
|
|
92
93
|
message_url,
|
|
93
94
|
json=request_body,
|
|
94
95
|
headers=self.headers,
|
|
95
|
-
timeout=
|
|
96
|
+
timeout=self.timeout,
|
|
96
97
|
)
|
|
97
98
|
response.raise_for_status()
|
|
98
99
|
return response.json()
|
|
@@ -130,7 +131,7 @@ class InteractionsService:
|
|
|
130
131
|
e,
|
|
131
132
|
)
|
|
132
133
|
|
|
133
|
-
messages_with_events.sort(key=lambda x:
|
|
134
|
+
messages_with_events.sort(key=lambda x: x.ts)
|
|
134
135
|
return messages_with_events
|
|
135
136
|
|
|
136
137
|
def fetch_thread_messages_and_events_for_message(
|
|
@@ -143,7 +144,7 @@ class InteractionsService:
|
|
|
143
144
|
message_url,
|
|
144
145
|
json=request_body,
|
|
145
146
|
headers=self.headers,
|
|
146
|
-
timeout=
|
|
147
|
+
timeout=self.timeout,
|
|
147
148
|
)
|
|
148
149
|
response.raise_for_status()
|
|
149
150
|
json_response = response.json()
|
|
@@ -176,11 +177,46 @@ class InteractionsService:
|
|
|
176
177
|
thread_search_url,
|
|
177
178
|
json=request_body,
|
|
178
179
|
headers=self.headers,
|
|
179
|
-
timeout=
|
|
180
|
+
timeout=self.timeout,
|
|
180
181
|
)
|
|
181
182
|
response.raise_for_status()
|
|
182
183
|
return response.json()
|
|
183
184
|
|
|
185
|
+
def fetch_messages_and_events_for_forked_thread(
|
|
186
|
+
self, message_id: str, event_type: str
|
|
187
|
+
) -> List[ReturnedMessage]:
|
|
188
|
+
"""Build a history of messages for a given message including associated events.
|
|
189
|
+
This includes messages from pre-forked threads."""
|
|
190
|
+
returned_messages: List[ReturnedMessage] = []
|
|
191
|
+
|
|
192
|
+
messages_for_thread: ThreadRelationsResponse = (
|
|
193
|
+
self.fetch_thread_messages_and_events_for_message(message_id, [event_type])
|
|
194
|
+
)
|
|
195
|
+
if messages_for_thread.messages:
|
|
196
|
+
returned_messages.extend(messages_for_thread.messages)
|
|
197
|
+
|
|
198
|
+
# Lookup any thread_fork events (conversation across surfaces)
|
|
199
|
+
thread_fork_events = self.fetch_messages_and_events_for_thread(
|
|
200
|
+
messages_for_thread.thread_id, EventType.THREAD_FORK.value
|
|
201
|
+
)
|
|
202
|
+
for forked_thread_event in thread_fork_events.get("thread", {}).get(
|
|
203
|
+
"events", []
|
|
204
|
+
):
|
|
205
|
+
event_data = ThreadForkEventData.model_validate(
|
|
206
|
+
forked_thread_event.get("data", {})
|
|
207
|
+
)
|
|
208
|
+
forked_thread: ThreadRelationsResponse = (
|
|
209
|
+
self.fetch_thread_messages_and_events_for_message(
|
|
210
|
+
event_data.previous_message_id, [event_type]
|
|
211
|
+
)
|
|
212
|
+
)
|
|
213
|
+
if forked_thread.messages:
|
|
214
|
+
returned_messages.extend(forked_thread.messages)
|
|
215
|
+
|
|
216
|
+
returned_messages.sort(key=lambda x: x.ts)
|
|
217
|
+
|
|
218
|
+
return returned_messages
|
|
219
|
+
|
|
184
220
|
def fetch_events_for_message(
|
|
185
221
|
self,
|
|
186
222
|
message_id: str,
|
|
@@ -199,7 +235,7 @@ class InteractionsService:
|
|
|
199
235
|
message_search_url,
|
|
200
236
|
json=request_body,
|
|
201
237
|
headers=self.headers,
|
|
202
|
-
timeout=
|
|
238
|
+
timeout=self.timeout,
|
|
203
239
|
)
|
|
204
240
|
response.raise_for_status()
|
|
205
241
|
return response.json()
|
|
@@ -237,3 +273,32 @@ class InteractionsService:
|
|
|
237
273
|
}
|
|
238
274
|
},
|
|
239
275
|
}
|
|
276
|
+
|
|
277
|
+
@staticmethod
|
|
278
|
+
def prod() -> "InteractionsService":
|
|
279
|
+
return InteractionsService(
|
|
280
|
+
base_url="https://s2ub.prod.s2.allenai.org/service/noraretrieval",
|
|
281
|
+
timeout=30,
|
|
282
|
+
token=InteractionsService._fetch_bearer_token(
|
|
283
|
+
"nora/prod/interaction-bearer-token"
|
|
284
|
+
),
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
@staticmethod
|
|
288
|
+
def dev() -> "InteractionsService":
|
|
289
|
+
return InteractionsService(
|
|
290
|
+
base_url="https://s2ub.dev.s2.allenai.org/service/noraretrieval",
|
|
291
|
+
timeout=30,
|
|
292
|
+
token=InteractionsService._fetch_bearer_token(
|
|
293
|
+
"nora/dev/interaction-bearer-token"
|
|
294
|
+
),
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
@staticmethod
|
|
298
|
+
def _fetch_bearer_token(secret_id: str) -> str:
|
|
299
|
+
import boto3
|
|
300
|
+
|
|
301
|
+
secrets_manager = boto3.client("secretsmanager", region_name="us-west-2")
|
|
302
|
+
return json.loads(
|
|
303
|
+
secrets_manager.get_secret_value(SecretId=secret_id)["SecretString"]
|
|
304
|
+
)["token"]
|
|
@@ -43,7 +43,7 @@ class Message(BaseModel):
|
|
|
43
43
|
channel_id: str
|
|
44
44
|
surface: Surface
|
|
45
45
|
ts: datetime
|
|
46
|
-
annotations:
|
|
46
|
+
annotations: List[Annotation] = Field(default_factory=list)
|
|
47
47
|
|
|
48
48
|
@field_serializer("actor_id")
|
|
49
49
|
def serialize_actor_id(self, actor_id: UUID):
|
|
@@ -58,7 +58,9 @@ class EventType(str, Enum):
|
|
|
58
58
|
"""Event types for the interactions service"""
|
|
59
59
|
|
|
60
60
|
AGENT_CONTEXT = "agent:message_context"
|
|
61
|
+
S2_ANNOTATION = "s2_annotation"
|
|
61
62
|
THREAD_FORK = "thread_fork"
|
|
63
|
+
UI_INTERACTION = "ui_interaction"
|
|
62
64
|
|
|
63
65
|
|
|
64
66
|
class Event(BaseModel):
|
|
@@ -88,14 +90,14 @@ class ReturnedMessage(BaseModel):
|
|
|
88
90
|
"""Message format returned by interaction service"""
|
|
89
91
|
|
|
90
92
|
message_id: str
|
|
91
|
-
actor_id:
|
|
93
|
+
actor_id: UUID
|
|
92
94
|
text: str
|
|
93
|
-
ts:
|
|
95
|
+
ts: datetime
|
|
94
96
|
annotated_text: Optional[str] = None
|
|
95
97
|
events: List[Event] = Field(default_factory=list)
|
|
96
98
|
thread_id: Optional[str] = None
|
|
97
99
|
channel_id: Optional[str] = None
|
|
98
|
-
annotations:
|
|
100
|
+
annotations: List[Annotation] = Field(default_factory=list)
|
|
99
101
|
|
|
100
102
|
|
|
101
103
|
class AgentMessageData(BaseModel):
|
|
@@ -111,8 +113,8 @@ class AgentMessageData(BaseModel):
|
|
|
111
113
|
class ReturnedAgentContextEvent(BaseModel):
|
|
112
114
|
"""Event format returned by interaction service for agent context events"""
|
|
113
115
|
|
|
114
|
-
actor_id:
|
|
115
|
-
timestamp:
|
|
116
|
+
actor_id: UUID # agent that saved this context
|
|
117
|
+
timestamp: datetime
|
|
116
118
|
data: AgentMessageData
|
|
117
119
|
type: str
|
|
118
120
|
|
|
@@ -121,11 +123,11 @@ class ReturnedAgentContextMessage(BaseModel):
|
|
|
121
123
|
"""Message format returned by interaction service for search by thread"""
|
|
122
124
|
|
|
123
125
|
message_id: str
|
|
124
|
-
actor_id:
|
|
126
|
+
actor_id: UUID
|
|
125
127
|
text: str
|
|
126
128
|
ts: str
|
|
127
129
|
annotated_text: Optional[str] = None
|
|
128
|
-
events:
|
|
130
|
+
events: List[ReturnedAgentContextEvent] = Field(default_factory=list)
|
|
129
131
|
|
|
130
132
|
|
|
131
133
|
class ThreadForkEventData(BaseModel):
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Handles IO for asynchronous task-related state.
|
|
3
|
+
|
|
4
|
+
A StateManager instance must be initialized with
|
|
5
|
+
a concrete subclass of `AsyncTaskState`, as implemented
|
|
6
|
+
by dependent projects.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
import os
|
|
11
|
+
from uuid import UUID
|
|
12
|
+
from datetime import datetime, timezone
|
|
13
|
+
from typing import Generic, Optional, Type, Any
|
|
14
|
+
from abc import ABC, abstractmethod
|
|
15
|
+
|
|
16
|
+
from nora_lib.tasks.models import AsyncTaskState, R, TASK_STATUSES
|
|
17
|
+
from nora_lib.interactions.interactions_service import InteractionsService
|
|
18
|
+
from nora_lib.interactions.models import Event
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class NoSuchTaskException(Exception):
|
|
22
|
+
def __init__(self, task_id: str):
|
|
23
|
+
self._task_id = task_id
|
|
24
|
+
|
|
25
|
+
def __str__(self):
|
|
26
|
+
return f"No record found for task {self._task_id}"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class IStateManager(ABC, Generic[R]):
|
|
30
|
+
@abstractmethod
|
|
31
|
+
def read_state(self, task_id: str) -> AsyncTaskState[R]:
|
|
32
|
+
pass
|
|
33
|
+
|
|
34
|
+
@abstractmethod
|
|
35
|
+
def write_state(self, state: AsyncTaskState[R]) -> None:
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
def update_status(self, task_id: str, new_status: str) -> None:
|
|
39
|
+
state = self.read_state(task_id)
|
|
40
|
+
state.task_status = new_status
|
|
41
|
+
self.write_state(state)
|
|
42
|
+
|
|
43
|
+
def save_result(self, task_id: str, task_result: R) -> None:
|
|
44
|
+
state = self.read_state(task_id)
|
|
45
|
+
state.task_status = TASK_STATUSES["COMPLETED"]
|
|
46
|
+
state.task_result = task_result
|
|
47
|
+
self.write_state(state)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class StateManager(IStateManager[R]):
|
|
51
|
+
"""
|
|
52
|
+
Stores task state on local disk
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
def __init__(self, task_state_class: Type[AsyncTaskState[R]], state_dir) -> None:
|
|
56
|
+
self._task_state_class = task_state_class
|
|
57
|
+
self._state_dir = state_dir
|
|
58
|
+
|
|
59
|
+
def read_state(self, task_id: str) -> AsyncTaskState[R]:
|
|
60
|
+
task_state_path = os.path.join(self._state_dir, f"{task_id}.json")
|
|
61
|
+
if not os.path.isfile(task_state_path):
|
|
62
|
+
raise NoSuchTaskException(task_id)
|
|
63
|
+
|
|
64
|
+
with open(task_state_path, "r") as f:
|
|
65
|
+
return self._task_state_class(**json.loads(f.read()))
|
|
66
|
+
|
|
67
|
+
def write_state(self, state: AsyncTaskState[R]) -> None:
|
|
68
|
+
task_state_path = os.path.join(self._state_dir, f"{state.task_id}.json")
|
|
69
|
+
with open(task_state_path, "w") as f:
|
|
70
|
+
json.dump(state.model_dump(), f)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class TaskStateFetchException(Exception):
|
|
74
|
+
def __init__(self, message: str):
|
|
75
|
+
super().__init__(message)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class RemoteStateManagerFactory:
|
|
79
|
+
"""
|
|
80
|
+
Stores task state in the interaction store
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
def __init__(
|
|
84
|
+
self, agent_name: str, actor_id: UUID, interactions_service: InteractionsService
|
|
85
|
+
):
|
|
86
|
+
"""
|
|
87
|
+
:param agent_name: Used to form the event type that will hold the task state in the interactions store
|
|
88
|
+
:param actor_id: Associated with the events written to the interactions store
|
|
89
|
+
:param interactions_service:
|
|
90
|
+
"""
|
|
91
|
+
self.agent_name = agent_name
|
|
92
|
+
self.actor_id = actor_id
|
|
93
|
+
self.interactions_service = interactions_service
|
|
94
|
+
|
|
95
|
+
def for_message(self, message_id: str) -> IStateManager[R]:
|
|
96
|
+
return RemoteStateManager(
|
|
97
|
+
self.agent_name, self.actor_id, self.interactions_service, message_id
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class RemoteStateManager(IStateManager[R]):
|
|
102
|
+
"""
|
|
103
|
+
Stores task state in the interaction store
|
|
104
|
+
"""
|
|
105
|
+
|
|
106
|
+
_TASK_STATE_EVENT_TYPE = "agent:{}:task_state"
|
|
107
|
+
|
|
108
|
+
def __init__(
|
|
109
|
+
self,
|
|
110
|
+
agent_name: str,
|
|
111
|
+
actor_id: UUID,
|
|
112
|
+
interactions_service: InteractionsService,
|
|
113
|
+
message_id: str,
|
|
114
|
+
):
|
|
115
|
+
"""
|
|
116
|
+
:param agent_name: Agent that saved the task
|
|
117
|
+
:param actor_id: ID for the agent (ignored when reading)
|
|
118
|
+
:param message_id: The message that initiated the request for task status
|
|
119
|
+
"""
|
|
120
|
+
self.agent_name = agent_name
|
|
121
|
+
self.actor_id = actor_id
|
|
122
|
+
self.message_id = message_id
|
|
123
|
+
self.interactions_service = interactions_service
|
|
124
|
+
|
|
125
|
+
def read_state(self, task_id: str) -> AsyncTaskState[R]:
|
|
126
|
+
event_type = RemoteStateManager._TASK_STATE_EVENT_TYPE.format(self.agent_name)
|
|
127
|
+
response = (
|
|
128
|
+
self.interactions_service.fetch_thread_messages_and_events_for_message(
|
|
129
|
+
self.message_id, [event_type]
|
|
130
|
+
)
|
|
131
|
+
)
|
|
132
|
+
latest_state: Optional[AsyncTaskState[R]] = None
|
|
133
|
+
latest_timestamp = None
|
|
134
|
+
for msg in response.messages or []:
|
|
135
|
+
for event in msg.events or []:
|
|
136
|
+
try:
|
|
137
|
+
state = AsyncTaskState[Any].model_validate(event.data)
|
|
138
|
+
except Exception as e:
|
|
139
|
+
# Event json blob has unexpected format
|
|
140
|
+
raise TaskStateFetchException(
|
|
141
|
+
f"Event of type {event_type} for message {self.message_id} does not deserialize to AsyncTaskState: {e}"
|
|
142
|
+
)
|
|
143
|
+
if state.task_id != task_id:
|
|
144
|
+
continue
|
|
145
|
+
if latest_state is None or (
|
|
146
|
+
latest_timestamp and event.timestamp > latest_timestamp
|
|
147
|
+
):
|
|
148
|
+
latest_state = state
|
|
149
|
+
latest_timestamp = event.timestamp
|
|
150
|
+
|
|
151
|
+
if not latest_state:
|
|
152
|
+
raise NoSuchTaskException(task_id)
|
|
153
|
+
return latest_state
|
|
154
|
+
|
|
155
|
+
def write_state(self, state: AsyncTaskState[R]) -> None:
|
|
156
|
+
event = Event(
|
|
157
|
+
type=RemoteStateManager._TASK_STATE_EVENT_TYPE.format(self.agent_name),
|
|
158
|
+
actor_id=self.actor_id,
|
|
159
|
+
timestamp=datetime.now(tz=timezone.utc),
|
|
160
|
+
message_id=self.message_id,
|
|
161
|
+
data=state.model_dump(),
|
|
162
|
+
)
|
|
163
|
+
self.interactions_service.save_event(event)
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: nora_lib
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.6
|
|
4
4
|
Summary: For making and coordinating agents and tools
|
|
5
5
|
Home-page: https://github.com/allenai/nora_lib
|
|
6
6
|
Requires-Python: >=3.9
|
|
7
7
|
Requires-Dist: pydantic<3,>=2
|
|
8
8
|
Requires-Dist: requests
|
|
9
|
+
Requires-Dist: boto3
|
|
9
10
|
Provides-Extra: dev
|
|
10
11
|
Requires-Dist: mypy; extra == "dev"
|
|
11
12
|
Requires-Dist: pytest; extra == "dev"
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import setuptools
|
|
2
2
|
|
|
3
|
-
runtime_requirements = ["pydantic>=2,<3", "requests"]
|
|
3
|
+
runtime_requirements = ["pydantic>=2,<3", "requests", "boto3"]
|
|
4
4
|
|
|
5
5
|
# For running tests, linting, etc
|
|
6
6
|
dev_requirements = ["mypy", "pytest", "black", "types-requests"]
|
|
7
7
|
|
|
8
8
|
setuptools.setup(
|
|
9
9
|
name="nora_lib",
|
|
10
|
-
version="0.0.
|
|
10
|
+
version="0.0.6",
|
|
11
11
|
description="For making and coordinating agents and tools",
|
|
12
12
|
url="https://github.com/allenai/nora_lib",
|
|
13
13
|
packages=setuptools.find_packages(exclude=(["tests"])),
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Handles IO for asynchronous task-related state.
|
|
3
|
-
Currently just reads/writes from local disk,
|
|
4
|
-
the best and most robust mechanism.
|
|
5
|
-
|
|
6
|
-
A StateManager instance must be initialized with
|
|
7
|
-
a concrete subclass of `AsyncTaskState`, as implemented
|
|
8
|
-
by dependent projects.
|
|
9
|
-
"""
|
|
10
|
-
|
|
11
|
-
import json
|
|
12
|
-
import os
|
|
13
|
-
from typing import Generic, Optional, Type
|
|
14
|
-
|
|
15
|
-
from nora_lib.tasks.models import AsyncTaskState, R, TASK_STATUSES
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class NoSuchTaskException(Exception):
|
|
19
|
-
def __init__(self, task_id: str):
|
|
20
|
-
self._task_id = task_id
|
|
21
|
-
|
|
22
|
-
def __str__(self):
|
|
23
|
-
return f"No record found for task {self._task_id}"
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
class StateManager(Generic[R]):
|
|
27
|
-
def __init__(self, task_state_class: Type[AsyncTaskState[R]], state_dir) -> None:
|
|
28
|
-
self._task_state_class = task_state_class
|
|
29
|
-
self._state_dir = state_dir
|
|
30
|
-
|
|
31
|
-
def read_state(self, task_id: str) -> AsyncTaskState[R]:
|
|
32
|
-
task_state_path = os.path.join(self._state_dir, f"{task_id}.json")
|
|
33
|
-
if not os.path.isfile(task_state_path):
|
|
34
|
-
raise NoSuchTaskException(task_id)
|
|
35
|
-
|
|
36
|
-
with open(task_state_path, "r") as f:
|
|
37
|
-
return self._task_state_class(**json.loads(f.read()))
|
|
38
|
-
|
|
39
|
-
def write_state(self, state: AsyncTaskState[R]) -> None:
|
|
40
|
-
task_state_path = os.path.join(self._state_dir, f"{state.task_id}.json")
|
|
41
|
-
with open(task_state_path, "w") as f:
|
|
42
|
-
json.dump(state.model_dump(), f)
|
|
43
|
-
|
|
44
|
-
def update_status(self, task_id: str, new_status: str) -> None:
|
|
45
|
-
state = self.read_state(task_id)
|
|
46
|
-
state.task_status = new_status
|
|
47
|
-
self.write_state(state)
|
|
48
|
-
|
|
49
|
-
def save_result(self, task_id: str, task_result: R) -> None:
|
|
50
|
-
state = self.read_state(task_id)
|
|
51
|
-
state.task_status = TASK_STATUSES["COMPLETED"]
|
|
52
|
-
state.task_result = task_result
|
|
53
|
-
self.write_state(state)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|