dnotifier 1.0.2__tar.gz → 1.0.3__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.
- dnotifier-1.0.3/CHANGELOG.md +6 -0
- {dnotifier-1.0.2 → dnotifier-1.0.3}/PKG-INFO +1 -1
- {dnotifier-1.0.2 → dnotifier-1.0.3}/pyproject.toml +1 -1
- {dnotifier-1.0.2 → dnotifier-1.0.3}/src/dnotifier/dnotifier.py +32 -23
- {dnotifier-1.0.2 → dnotifier-1.0.3}/src/dnotifier/utils/logs.py +5 -5
- dnotifier-1.0.3/src/dnotifier/workflow/agent.py +13 -0
- {dnotifier-1.0.2 → dnotifier-1.0.3}/src/dnotifier/workflow/context.py +66 -62
- {dnotifier-1.0.2 → dnotifier-1.0.3}/src/dnotifier/workflow/workflow_observability_client.py +10 -9
- dnotifier-1.0.2/CHANGELOG.md +0 -7
- dnotifier-1.0.2/src/dnotifier/workflow/agent.py +0 -12
- {dnotifier-1.0.2 → dnotifier-1.0.3}/.gitignore +0 -0
- {dnotifier-1.0.2 → dnotifier-1.0.3}/LICENSE +0 -0
- {dnotifier-1.0.2 → dnotifier-1.0.3}/README.md +0 -0
- {dnotifier-1.0.2 → dnotifier-1.0.3}/src/dnotifier/__init__.py +0 -0
- {dnotifier-1.0.2 → dnotifier-1.0.3}/src/dnotifier/config/__init__.py +0 -0
- {dnotifier-1.0.2 → dnotifier-1.0.3}/src/dnotifier/config/endpoints.py +0 -0
- {dnotifier-1.0.2 → dnotifier-1.0.3}/src/dnotifier/transport/__init__.py +0 -0
- {dnotifier-1.0.2 → dnotifier-1.0.3}/src/dnotifier/transport/http_transport.py +0 -0
- {dnotifier-1.0.2 → dnotifier-1.0.3}/src/dnotifier/transport/websocket_transport.py +0 -0
- {dnotifier-1.0.2 → dnotifier-1.0.3}/src/dnotifier/utils/__init__.py +0 -0
- {dnotifier-1.0.2 → dnotifier-1.0.3}/src/dnotifier/utils/buffer.py +0 -0
- {dnotifier-1.0.2 → dnotifier-1.0.3}/src/dnotifier/utils/frame.py +0 -0
- {dnotifier-1.0.2 → dnotifier-1.0.3}/src/dnotifier/utils/id.py +0 -0
- {dnotifier-1.0.2 → dnotifier-1.0.3}/src/dnotifier/utils/payload.py +0 -0
- {dnotifier-1.0.2 → dnotifier-1.0.3}/src/dnotifier/workflow/__init__.py +0 -0
- {dnotifier-1.0.2 → dnotifier-1.0.3}/src/dnotifier/workflow/errors.py +0 -0
- {dnotifier-1.0.2 → dnotifier-1.0.3}/src/dnotifier/workflow/graph_parents.py +0 -0
- {dnotifier-1.0.2 → dnotifier-1.0.3}/src/dnotifier/workflow/ids.py +0 -0
- {dnotifier-1.0.2 → dnotifier-1.0.3}/src/dnotifier/workflow/json_payload.py +0 -0
- {dnotifier-1.0.2 → dnotifier-1.0.3}/src/dnotifier/workflow/observability.py +0 -0
- {dnotifier-1.0.2 → dnotifier-1.0.3}/src/dnotifier/workflow/remote_agent_client.py +0 -0
- {dnotifier-1.0.2 → dnotifier-1.0.3}/src/dnotifier/workflow/remote_agent_protocol.py +0 -0
- {dnotifier-1.0.2 → dnotifier-1.0.3}/src/dnotifier/workflow/runner.py +0 -0
- {dnotifier-1.0.2 → dnotifier-1.0.3}/src/dnotifier/workflow/step_types.py +0 -0
- {dnotifier-1.0.2 → dnotifier-1.0.3}/src/dnotifier/workflow/workflow.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dnotifier
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.3
|
|
4
4
|
Summary: Real-time notification and messaging SDK for Python. WebSocket & HTTP, auth, binary frames, AI, RAG, and workflows.
|
|
5
5
|
Project-URL: Homepage, https://dnotifier.com
|
|
6
6
|
Project-URL: Documentation, https://dnotifier.gitbook.io/product-docs
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "dnotifier"
|
|
7
|
-
version = "1.0.
|
|
7
|
+
version = "1.0.3"
|
|
8
8
|
description = "Real-time notification and messaging SDK for Python. WebSocket & HTTP, auth, binary frames, AI, RAG, and workflows."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "Proprietary" }
|
|
@@ -15,9 +15,9 @@ _U='messageId'
|
|
|
15
15
|
_T='receivers'
|
|
16
16
|
_S='future'
|
|
17
17
|
_R='loop'
|
|
18
|
-
_Q='
|
|
19
|
-
_P='
|
|
20
|
-
_O='
|
|
18
|
+
_Q='ai'
|
|
19
|
+
_P='sender_id required'
|
|
20
|
+
_O='text'
|
|
21
21
|
_N='handle'
|
|
22
22
|
_M=False
|
|
23
23
|
_L=True
|
|
@@ -50,6 +50,15 @@ from.workflow.observability import create_workflow_observability
|
|
|
50
50
|
_DEFAULT_WS_URL=DNotifierEndpoints.ws_url
|
|
51
51
|
_DEFAULT_AUTH_URL=DNotifierEndpoints.auth_url
|
|
52
52
|
_DEFAULT_HTTP_URL=DNotifierEndpoints.rpc_send_url
|
|
53
|
+
def _build_ai_send_data(message,*,provider=_A,model=_A):
|
|
54
|
+
D=model;C=provider;A=message;E=bool(C and str(C).strip());F=bool(D and str(D).strip())
|
|
55
|
+
if not E and not F:return A
|
|
56
|
+
if isinstance(A,dict):B=dict(A)
|
|
57
|
+
elif A is not _A:B={_O:A}
|
|
58
|
+
else:B={}
|
|
59
|
+
if E:B['provider']=C
|
|
60
|
+
if F:B['model']=D
|
|
61
|
+
return B
|
|
53
62
|
@dataclass
|
|
54
63
|
class DNotifierMessageMetadata:id:Optional[str];sender:str;timestamp:int;type:Optional[str]=_A
|
|
55
64
|
@dataclass
|
|
@@ -119,7 +128,7 @@ class DNotifier:
|
|
|
119
128
|
B=A.is_connected;A.is_connected=_M
|
|
120
129
|
if B:A.on_disconnected(code=code,reason=reason or'Disconnected')
|
|
121
130
|
def L(err):A.is_connected=_M;A.on_disconnected(code=500,reason=str(err))
|
|
122
|
-
B.on_message=J;B.on_close=K;B.on_error=L;await B.connect(A.url);M={_C:
|
|
131
|
+
B.on_message=J;B.on_close=K;B.on_error=L;await B.connect(A.url);M={_C:_O,_I:101,_H:A.app_id,_B:prefix_id(A.app_id,A.user_id),_F:int(time.time()*1000),_E:A.auth_token};N=to_bytes(json.dumps({_E:A.auth_token}));await B.send_binary(frame_packet(M,N));return _L
|
|
123
132
|
def _emit_message(B,packet,data):A=packet;C=DNotifierMessage(metadata=DNotifierMessageMetadata(id=A.get(_J),sender=str(A.get(_B,'')),timestamp=int(A.get(_F)or 0),type=A.get(_C)or A.get(_B)),payload=Payload(data));B._dispatch_inbound_message(C)
|
|
124
133
|
def _dispatch_inbound_message(B,msg):
|
|
125
134
|
O='agentName';N='output';M='state';I='correlationId';F=msg
|
|
@@ -184,9 +193,9 @@ class DNotifier:
|
|
|
184
193
|
async def send(A,sender_id,data,receiver_id=_A,receiver_ids=_A,save_history=_L):
|
|
185
194
|
I=receiver_ids;H=receiver_id;G=sender_id
|
|
186
195
|
if not A.is_connected:raise RuntimeError(_K)
|
|
187
|
-
if not G:raise ValueError(
|
|
196
|
+
if not G:raise ValueError(_P)
|
|
188
197
|
if not H and not I:raise ValueError('receiver_id or receiver_ids required')
|
|
189
|
-
K=I or[H];B=to_bytes(json.dumps(data));D=A.message_size_limit or 32768;C=_random_uuid();E={_J:C,_C:
|
|
198
|
+
K=I or[H];B=to_bytes(json.dumps(data));D=A.message_size_limit or 32768;C=_random_uuid();E={_J:C,_C:_O,_I:200,_Z:1,_a:1,_H:A.app_id,_B:prefix_id(A.app_id,G),_T:[prefix_id(A.app_id,B)for B in K if B],_F:int(time.time()*1000),_E:A.auth_token,_e:save_history}
|
|
190
199
|
if A._http is not _A:return await A._http.send({_D:E,_G:data})
|
|
191
200
|
F=A._ws
|
|
192
201
|
if F is _A:raise RuntimeError('WebSocket not available')
|
|
@@ -194,48 +203,48 @@ class DNotifier:
|
|
|
194
203
|
J=[B[A:A+D]for A in range(0,len(B),D)];L=len(J)
|
|
195
204
|
for(M,N)in enumerate(J):O={**E,_J:C,_Z:L,_a:M+1};await F.send_binary(frame_packet(O,N))
|
|
196
205
|
return C
|
|
197
|
-
async def send_ai(A,sender_id,message,save_history=_L,session_id=_A):
|
|
198
|
-
E=session_id;D=
|
|
206
|
+
async def send_ai(A,sender_id,message,save_history=_L,session_id=_A,provider=_A,model=_A):
|
|
207
|
+
F=model;E=session_id;D=sender_id
|
|
199
208
|
try:
|
|
200
209
|
if not A.is_connected:raise RuntimeError(_K)
|
|
201
|
-
if not
|
|
202
|
-
|
|
203
|
-
if A._http is not _A:
|
|
204
|
-
if A._ws is not _A:
|
|
205
|
-
return
|
|
206
|
-
except Exception as
|
|
210
|
+
if not D:raise ValueError(_P)
|
|
211
|
+
C=E or _random_uuid();G={_J:C,_C:_Q,_I:300,_H:A.app_id,_B:prefix_id(A.app_id,D),'receiverId':prefix_id(A.app_id,D),_F:int(time.time()*1000),_E:A.auth_token,_e:save_history};B=_build_ai_send_data(message,provider=provider,model=F);await A._logs_client.start_ai_logging(session_id=C,prompts=B,is_existing_session=bool(E),model=(B.get('model')if isinstance(B,dict)else _A)or F)
|
|
212
|
+
if A._http is not _A:H=await A._http.send({_D:G,_G:B});await A._logs_client.complete_ai_logging(session_id=C,response=H);return A._normalize_rpc_response(H)
|
|
213
|
+
if A._ws is not _A:I=to_bytes(json.dumps(B));await A._ws.send_binary(frame_packet(G,I))
|
|
214
|
+
return C
|
|
215
|
+
except Exception as J:print(f"error in send_ai: {J}");return
|
|
207
216
|
async def fetch_ai_history(A,sender_id):
|
|
208
217
|
B=sender_id
|
|
209
218
|
if not A.is_connected:raise RuntimeError(_K)
|
|
210
|
-
if not B:raise ValueError(
|
|
211
|
-
C={_J:_random_uuid(),_C:
|
|
219
|
+
if not B:raise ValueError(_P)
|
|
220
|
+
C={_J:_random_uuid(),_C:_Q,_I:301,_H:A.app_id,_B:prefix_id(A.app_id,B),_F:int(time.time()*1000),_E:A.auth_token}
|
|
212
221
|
if A._http is not _A:return A._normalize_rpc_response(await A._http.send({_D:C,_G:{}}))
|
|
213
222
|
if A._ws is not _A:await A._ws.send_binary(frame_packet(C,to_bytes('{}')))
|
|
214
223
|
async def fetch_chat_history(A,sender_id,receiver_ids):
|
|
215
224
|
C=receiver_ids;B=sender_id
|
|
216
225
|
if not A.is_connected:raise RuntimeError(_K)
|
|
217
|
-
if not B:raise ValueError(
|
|
226
|
+
if not B:raise ValueError(_P)
|
|
218
227
|
if not C:raise ValueError('receiver_ids required')
|
|
219
|
-
D={_J:_random_uuid(),_C:
|
|
228
|
+
D={_J:_random_uuid(),_C:_O,_I:201,_H:A.app_id,_B:prefix_id(A.app_id,B),_T:[prefix_id(A.app_id,B)for B in C],_F:int(time.time()*1000),_E:A.auth_token}
|
|
220
229
|
if A._http is not _A:return A._normalize_rpc_response(await A._http.send({_D:D,_G:{}}))
|
|
221
230
|
if A._ws is not _A:await A._ws.send_binary(frame_packet(D,to_bytes('{}')))
|
|
222
231
|
async def delete_ai_history_message(A,sender_id,message_id):
|
|
223
232
|
B=message_id
|
|
224
233
|
if not A.is_connected:raise RuntimeError(_K)
|
|
225
|
-
C={_J:_random_uuid(),_C:
|
|
234
|
+
C={_J:_random_uuid(),_C:_Q,_I:302,_H:A.app_id,_B:prefix_id(A.app_id,sender_id),_F:int(time.time()*1000),_E:A.auth_token,_U:B};D={_U:B}
|
|
226
235
|
if A._http is not _A:return A._normalize_rpc_response(await A._http.send({_D:C,_G:D}))
|
|
227
236
|
if A._ws is not _A:await A._ws.send_binary(frame_packet(C,to_bytes(json.dumps(D))))
|
|
228
237
|
async def delete_chat_history_message(A,sender_id,message_id):
|
|
229
238
|
B=message_id
|
|
230
239
|
if not A.is_connected:raise RuntimeError(_K)
|
|
231
|
-
C={_J:_random_uuid(),_C:
|
|
240
|
+
C={_J:_random_uuid(),_C:_O,_I:202,_H:A.app_id,_B:prefix_id(A.app_id,sender_id),_T:[],_F:int(time.time()*1000),_E:A.auth_token,_U:B};D={_U:B}
|
|
232
241
|
if A._http is not _A:return A._normalize_rpc_response(await A._http.send({_D:C,_G:D}))
|
|
233
242
|
if A._ws is not _A:await A._ws.send_binary(frame_packet(C,to_bytes(json.dumps(D))))
|
|
234
243
|
async def search(A,sender_id,query,limit=_A,min_similarity=_A,filterby_source=_A):
|
|
235
244
|
F=filterby_source;E=min_similarity;D=limit;C=query
|
|
236
245
|
if not A.is_connected:raise RuntimeError(_K)
|
|
237
246
|
if not C:raise ValueError('query required')
|
|
238
|
-
G={_J:_random_uuid(),_C:
|
|
247
|
+
G={_J:_random_uuid(),_C:_Q,_I:303,_H:A.app_id,_B:prefix_id(A.app_id,sender_id),_F:int(time.time()*1000),_E:A.auth_token};B={'query':C}
|
|
239
248
|
if D is not _A:B['limit']=D
|
|
240
249
|
if E is not _A:B['minSimilarity']=E
|
|
241
250
|
if F is not _A:B['filterbySource']=F
|
|
@@ -274,8 +283,8 @@ class DNotifier:
|
|
|
274
283
|
async def _send_document_rpc(A,sender_id,code,data):
|
|
275
284
|
B=sender_id
|
|
276
285
|
if not A.is_connected:raise RuntimeError(_K)
|
|
277
|
-
if not B:raise ValueError(
|
|
278
|
-
C={_J:_random_uuid(),_C:
|
|
286
|
+
if not B:raise ValueError(_P)
|
|
287
|
+
C={_J:_random_uuid(),_C:_Q,_I:code,_H:A.app_id,_B:prefix_id(A.app_id,B),_F:int(time.time()*1000),_E:A.auth_token}
|
|
279
288
|
if A._http is not _A:return A._normalize_rpc_response(await A._http.send({_D:C,_G:data}))
|
|
280
289
|
if A._ws is not _A:await A._ws.send_binary(frame_packet(C,to_bytes(json.dumps(data))))
|
|
281
290
|
async def send_binary(A,sender_id,receiver_ids,buffer,msg_type='binary'):
|
|
@@ -23,13 +23,13 @@ class LogsClient:
|
|
|
23
23
|
async def create_log(A,payload):return await A._transport(DNotifierEndpoints.add_log_url).send(payload)
|
|
24
24
|
async def update_log(A,payload):return await A._transport(DNotifierEndpoints.update_log_status_url).send(payload)
|
|
25
25
|
async def add_session_step_log(A,payload):return await A._transport(DNotifierEndpoints.add_session_step_url).send(payload)
|
|
26
|
-
async def start_ai_logging(A,session_id,prompts,is_existing_session=False):
|
|
27
|
-
|
|
26
|
+
async def start_ai_logging(A,session_id,prompts,is_existing_session=False,model=_A):
|
|
27
|
+
I='createdDate';H='model';G='appId';E=model;D='InProgress';B=session_id
|
|
28
28
|
if not A.enabled or not A.token or not A.app_id or not B:return
|
|
29
|
-
C=datetime.now(timezone.utc).isoformat()
|
|
29
|
+
C=datetime.now(timezone.utc).isoformat();F=E if E else A.model
|
|
30
30
|
if is_existing_session:await A.update_log({_C:B,_B:D,_D:C})
|
|
31
|
-
else:await A.create_log({
|
|
32
|
-
_session_step_tracker[B]={
|
|
31
|
+
else:await A.create_log({G:A.app_id,_C:B,_B:D,H:F,'calls':1,I:C,'step':1})
|
|
32
|
+
_session_step_tracker[B]={G:A.app_id,_C:B,_F:0,_B:D,_E:prompts,I:C,H:F}
|
|
33
33
|
async def complete_ai_logging(B,session_id,response=_A):
|
|
34
34
|
K='usage';J='data';I='Completed';C=session_id;A=response
|
|
35
35
|
if not B.enabled or not B.token or not C:return
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
_A=None
|
|
3
|
+
from typing import Awaitable,Callable,Optional,TYPE_CHECKING
|
|
4
|
+
from.errors import WorkflowError
|
|
5
|
+
if TYPE_CHECKING:from.context import WorkflowContext
|
|
6
|
+
AgentRun=Callable[['WorkflowContext'],Awaitable]
|
|
7
|
+
class Agent:
|
|
8
|
+
def __init__(A,name,run,*,provider=_A,model=_A):
|
|
9
|
+
D=model;C=provider;B=name
|
|
10
|
+
if not B or not isinstance(B,str):raise WorkflowError('Agent name is required')
|
|
11
|
+
if not callable(run):raise WorkflowError(f'Agent "{B}": run must be a callable')
|
|
12
|
+
A.name=B;A.run=run;A.provider=C if C else _A;A.model=D if D else _A
|
|
13
|
+
def define_agent(name,run,*,provider=_A,model=_A):return Agent(name=name,run=run,provider=provider,model=model)
|
|
@@ -1,27 +1,28 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
2
|
+
_a='usesTokens'
|
|
3
|
+
_Z='workflowId'
|
|
4
|
+
_Y='executionId'
|
|
5
|
+
_X='siblingIndex'
|
|
6
|
+
_W='branchKey'
|
|
7
|
+
_V='apiName'
|
|
8
|
+
_U='message'
|
|
9
|
+
_T='stepId'
|
|
10
|
+
_S='latencyMs'
|
|
11
|
+
_R='status'
|
|
12
|
+
_Q='ok'
|
|
13
|
+
_P='type'
|
|
14
|
+
_O='kind'
|
|
15
|
+
_N='agentName'
|
|
16
|
+
_M='key'
|
|
17
|
+
_L='state'
|
|
18
|
+
_K='error'
|
|
19
|
+
_J='tokensOut'
|
|
20
|
+
_I='tokensIn'
|
|
21
|
+
_H='provider'
|
|
22
|
+
_G='output'
|
|
23
|
+
_F='name'
|
|
24
|
+
_E='parentStepId'
|
|
25
|
+
_D='model'
|
|
25
26
|
_C='input'
|
|
26
27
|
_B='label'
|
|
27
28
|
_A=None
|
|
@@ -39,13 +40,13 @@ def _usage_from_response(response):
|
|
|
39
40
|
C=B.get(E)
|
|
40
41
|
if isinstance(C,dict):A=C.get(F)
|
|
41
42
|
if A is _A:A=B.get(F)
|
|
42
|
-
if not isinstance(A,dict):return{
|
|
43
|
-
G=A.get('prompt_tokens')or A.get('input_tokens')or A.get(
|
|
43
|
+
if not isinstance(A,dict):return{_I:0,_J:0,_D:_A}
|
|
44
|
+
G=A.get('prompt_tokens')or A.get('input_tokens')or A.get(_I)or 0;H=A.get('completion_tokens')or A.get('output_tokens')or A.get('total_tokens')or A.get(_J)or 0;D=_A
|
|
44
45
|
if isinstance(B,dict):
|
|
45
46
|
C=B.get(E)
|
|
46
|
-
if isinstance(C,dict):D=C.get(
|
|
47
|
-
if D is _A:D=A.get(
|
|
48
|
-
return{
|
|
47
|
+
if isinstance(C,dict):D=C.get(_D)
|
|
48
|
+
if D is _A:D=A.get(_D)
|
|
49
|
+
return{_I:G,_J:H,_D:D}
|
|
49
50
|
def _snapshot(value):
|
|
50
51
|
A=value
|
|
51
52
|
try:
|
|
@@ -54,25 +55,28 @@ def _snapshot(value):
|
|
|
54
55
|
except(TypeError,ValueError):return str(A)
|
|
55
56
|
def _api_input(opts):
|
|
56
57
|
if not opts:return
|
|
57
|
-
B={_B,'dependsOn','parentStepIds',
|
|
58
|
+
B={_B,'dependsOn','parentStepIds',_W,_X,_E,_C,_H,_D};A={A:C for(A,C)in opts.items()if A not in B};return A if A else _A
|
|
59
|
+
def _first_non_empty(*B):
|
|
60
|
+
for A in B:
|
|
61
|
+
if A is not _A and str(A).strip()!='':return A
|
|
58
62
|
def _normalize_run_agents_spec(spec):
|
|
59
63
|
D=spec
|
|
60
64
|
if isinstance(D,list):
|
|
61
65
|
A=[]
|
|
62
66
|
for(F,B)in enumerate(D):
|
|
63
|
-
if isinstance(B,str):A.append({
|
|
64
|
-
elif isinstance(B,dict)and B.get(
|
|
67
|
+
if isinstance(B,str):A.append({_F:B,_C:_A,_M:str(F)})
|
|
68
|
+
elif isinstance(B,dict)and B.get(_F):A.append({_F:B[_F],_C:B.get(_C),_M:str(F)})
|
|
65
69
|
else:raise WorkflowError('run_agents entry requires name')
|
|
66
70
|
return A
|
|
67
71
|
if isinstance(D,dict):
|
|
68
72
|
A=[]
|
|
69
73
|
for(E,C)in D.items():
|
|
70
|
-
if isinstance(C,dict)and
|
|
71
|
-
else:A.append({
|
|
74
|
+
if isinstance(C,dict)and _F in C:A.append({_F:C[_F],_C:C.get(_C),_M:E})
|
|
75
|
+
else:A.append({_F:E,_C:C,_M:E})
|
|
72
76
|
return A
|
|
73
77
|
raise WorkflowError('run_agents expects a list or dict spec')
|
|
74
78
|
class WorkflowContext:
|
|
75
|
-
def __init__(A,notifier,sender_id,workflow,input_data,state=_A,observability=_A,execution_id=_A,agent_name=_A,entry_step_id=_A,trace_parent_step_id=_A):F=entry_step_id;E=execution_id;D=observability;C=state;B=workflow;A.notifier=notifier;A.sender_id=sender_id;A.workflow=B;A.input=input_data;A.state=C if C is not _A else{};A._observability=D;A._execution_id=E;A._observability_enabled=bool(B.observability and D and E);A.agent_name=agent_name;A._entry_step_id=F;A._chain_head_step_id=F;A._trace_parent_step_id=trace_parent_step_id;A._current_step_id=_A;A.root_step_id=_A;A._terminal_step_ids=[]
|
|
79
|
+
def __init__(A,notifier,sender_id,workflow,input_data,state=_A,observability=_A,execution_id=_A,agent_name=_A,entry_step_id=_A,trace_parent_step_id=_A,provider=_A,model=_A):H=model;G=provider;F=entry_step_id;E=execution_id;D=observability;C=state;B=workflow;A.notifier=notifier;A.sender_id=sender_id;A.workflow=B;A.input=input_data;A.state=C if C is not _A else{};A._observability=D;A._execution_id=E;A._observability_enabled=bool(B.observability and D and E);A.agent_name=agent_name;A._entry_step_id=F;A._chain_head_step_id=F;A._trace_parent_step_id=trace_parent_step_id;A._current_step_id=_A;A.root_step_id=_A;A._terminal_step_ids=[];A._ai_provider=G if G else _A;A._ai_model=H if H else _A
|
|
76
80
|
def get_end_parent_step_id(A):
|
|
77
81
|
if A._terminal_step_ids:return format_parent_step_id(A._terminal_step_ids)
|
|
78
82
|
if A._entry_step_id:return A._entry_step_id
|
|
@@ -81,56 +85,56 @@ class WorkflowContext:
|
|
|
81
85
|
A._terminal_step_ids=[str(A)for A in step_ids if A is not _A and str(A).strip()]
|
|
82
86
|
def _resolve_parent_step_id(A,options=_A):
|
|
83
87
|
B=options;B=B or{}
|
|
84
|
-
if
|
|
88
|
+
if _E in B:return B[_E]
|
|
85
89
|
if A._current_step_id:return A._current_step_id
|
|
86
90
|
if A._trace_parent_step_id:return A._trace_parent_step_id
|
|
87
91
|
if A._chain_head_step_id:return A._chain_head_step_id
|
|
88
92
|
if A._entry_step_id:return A._entry_step_id
|
|
89
|
-
def export_trace_context(A):return{
|
|
93
|
+
def export_trace_context(A):return{_Y:A._execution_id,_Z:A.workflow.name,_E:A._current_step_id or A._chain_head_step_id or A._trace_parent_step_id or A._entry_step_id}
|
|
90
94
|
async def _invoke_sdk_api(A,api_name,invoke,meta=_A):
|
|
91
|
-
D=api_name;B=meta;B=B or{};
|
|
95
|
+
D=api_name;B=meta;B=B or{};I=int(time.time()*1000);E=_Q;C=_A;F=_A;G=to_log_step_type(D)
|
|
92
96
|
try:C=await invoke();return C
|
|
93
|
-
except Exception as
|
|
97
|
+
except Exception as J:E=_K;F={_U:str(J)};raise
|
|
94
98
|
finally:
|
|
95
|
-
if A._observability_enabled and A._observability and A._execution_id:
|
|
99
|
+
if A._observability_enabled and A._observability and A._execution_id:H=_usage_from_response(C)if B.get(_a)else{_I:0,_J:0,_D:_A};K=_first_non_empty(B.get(_D),H.get(_D),getattr(A.notifier,'current_model',_A));L=_first_non_empty(B.get(_H));M=A.agent_name or D;await A._observability.record_step(A._execution_id,{_V:D,_N:A.agent_name,_O:M,_P:G,_B:B.get(_B)or G,_R:E,_S:int(time.time()*1000)-I,_E:format_parent_step_id(A._current_step_id),**H,_D:K,_H:L,_C:_snapshot(B.get(_C)),_G:_snapshot(C),_L:_snapshot(A.state),_K:F})
|
|
96
100
|
async def _run_agent_observed(A,name,*,input_data=...,depends_on=_A,parent_step_ids=_A,branch_key=_A,sibling_index=_A,parent_step_id=...,timeout_ms=_A):
|
|
97
|
-
|
|
101
|
+
K=input_data;F=parent_step_id;C=name;D=A.workflow.get_agent_registration(C)
|
|
98
102
|
if not D:raise WorkflowError(f"Agent not found: {C}")
|
|
99
|
-
G=A.input if
|
|
100
|
-
if F is not...:
|
|
101
|
-
|
|
103
|
+
G=A.input if K is...else K;L={}
|
|
104
|
+
if F is not...:L[_E]=F
|
|
105
|
+
Q=resolve_recorded_parent_step_id(parent_step_id=F if F is not...else...,parent_step_ids=parent_step_ids,depends_on=depends_on,resolve_parent=lambda:A._resolve_parent_step_id(L));B=A._observability.allocate_step_id()if A._observability_enabled and A._observability else _A;R=int(time.time()*1000);H=_Q;E=_A;M=_A
|
|
102
106
|
try:
|
|
103
107
|
if is_remote_agent_ref(D):
|
|
104
|
-
assert isinstance(D,dict);
|
|
105
|
-
if A._execution_id:
|
|
106
|
-
|
|
107
|
-
if isinstance(
|
|
108
|
-
E=
|
|
109
|
-
|
|
110
|
-
if B:
|
|
111
|
-
return{
|
|
112
|
-
except Exception as S:H=
|
|
108
|
+
assert isinstance(D,dict);N=_A
|
|
109
|
+
if A._execution_id:N={_Y:A._execution_id,_Z:A.workflow.name,_E:B or A._current_step_id or A._chain_head_step_id or A._trace_parent_step_id or A._entry_step_id}
|
|
110
|
+
O=await invoke_remote_agent(notifier=A.notifier,sender_id=A.sender_id,receiver_id=remote_receiver_id(D),agent_name=C,input_data=G,state=A.state,trace=N,timeout_ms=timeout_ms);P=O.get(_L)
|
|
111
|
+
if isinstance(P,dict):A.state.update(P)
|
|
112
|
+
E=O.get(_G);return{_G:E,_T:B}
|
|
113
|
+
I=D;J=WorkflowContext(notifier=A.notifier,sender_id=A.sender_id,workflow=A.workflow,input_data=G,state=A.state,observability=A._observability,execution_id=A._execution_id,agent_name=C,entry_step_id=A._entry_step_id,trace_parent_step_id=A._trace_parent_step_id,provider=getattr(I,_H,_A),model=getattr(I,_D,_A));J._current_step_id=B;E=await I.run(J)
|
|
114
|
+
if B:J.root_step_id=B
|
|
115
|
+
return{_G:E,_T:B}
|
|
116
|
+
except Exception as S:H=_K;M={_U:str(S)};raise
|
|
113
117
|
finally:
|
|
114
118
|
if A._observability_enabled and A._observability and A._execution_id and B:
|
|
115
|
-
await A._observability.record_step(A._execution_id,{'id':B,
|
|
116
|
-
if H==
|
|
119
|
+
await A._observability.record_step(A._execution_id,{'id':B,_E:Q,_W:branch_key,_X:sibling_index,_N:C,_O:C,_P:STEP_TYPE_AGENT,_B:C,_R:H,_S:int(time.time()*1000)-R,_C:_snapshot(G),_G:_snapshot(E),_L:_snapshot(A.state),_K:M});A.root_step_id=B
|
|
120
|
+
if H==_Q and not A._current_step_id:A._chain_head_step_id=B
|
|
117
121
|
async def run_agent(A,name,input_data=_A,*,depends_on=_A,parent_step_ids=_A,branch_key=_A,sibling_index=_A,parent_step_id=...,timeout_ms=_A):
|
|
118
|
-
B=input_data;C=await A._run_agent_observed(name,input_data=...if B is _A else B,depends_on=depends_on,parent_step_ids=parent_step_ids,branch_key=branch_key,sibling_index=sibling_index,parent_step_id=parent_step_id,timeout_ms=timeout_ms);D=C.get(
|
|
122
|
+
B=input_data;C=await A._run_agent_observed(name,input_data=...if B is _A else B,depends_on=depends_on,parent_step_ids=parent_step_ids,branch_key=branch_key,sibling_index=sibling_index,parent_step_id=parent_step_id,timeout_ms=timeout_ms);D=C.get(_T)
|
|
119
123
|
if D and not A._current_step_id:A._mark_terminal_steps([D])
|
|
120
|
-
return C[
|
|
124
|
+
return C[_G]
|
|
121
125
|
async def wait_for_message(B,*,timeout_ms=_A):
|
|
122
126
|
A=getattr(B.notifier,'_wait_for_inbound_message',_A)
|
|
123
127
|
if not callable(A):raise WorkflowError('wait_for_message requires a DNotifier instance')
|
|
124
128
|
return await A(timeout_ms=timeout_ms)
|
|
125
129
|
async def run_agents(B,spec,*,parent_step_id=...,label=_A):
|
|
126
130
|
I='stepIds';H='results';A=parent_step_id;M=label;D=_normalize_run_agents_spec(spec);E={}
|
|
127
|
-
if A is not...:E[
|
|
131
|
+
if A is not...:E[_E]=A
|
|
128
132
|
J=resolve_recorded_parent_step_id(parent_step_id=A if A is not...else...,resolve_parent=lambda:B._resolve_parent_step_id(E))
|
|
129
|
-
async def K(entry,index):A=entry;return await B._run_agent_observed(A[
|
|
130
|
-
F=await asyncio.gather(*[K(B,A)for(A,B)in enumerate(D)]);G=[A[
|
|
133
|
+
async def K(entry,index):A=entry;return await B._run_agent_observed(A[_F],input_data=A[_C]if A[_C]is not _A else...,parent_step_id=J,sibling_index=index)
|
|
134
|
+
F=await asyncio.gather(*[K(B,A)for(A,B)in enumerate(D)]);G=[A[_G]for A in F];C=[A[_T]for A in F];B._mark_terminal_steps(C)
|
|
131
135
|
if isinstance(spec,list):return{H:G,I:C}
|
|
132
|
-
L={B[
|
|
133
|
-
async def send_ai(B,**A):return await B._invoke_sdk_api('sendAI',lambda:B.notifier.send_ai(sender_id=B.sender_id,**
|
|
136
|
+
L={B[_M]:G[A]for(A,B)in enumerate(D)};return{H:L,I:C}
|
|
137
|
+
async def send_ai(B,**A):C=A.get(_H,B._ai_provider);D=A.get(_D,B._ai_model);E={**A,_H:C,_D:D};E.pop(_B,_A);return await B._invoke_sdk_api('sendAI',lambda:B.notifier.send_ai(sender_id=B.sender_id,**E),{_B:A.get(_B),_C:A.get(_U)or _api_input(A),_a:True,_H:C,_D:D})
|
|
134
138
|
async def fetch_ai_history(A,**B):return await A._invoke_sdk_api('fetchAIHistory',lambda:A.notifier.fetch_ai_history(sender_id=A.sender_id,**B),{_B:B.get(_B),_C:_api_input(B)})
|
|
135
139
|
async def search(A,**B):return await A._invoke_sdk_api('search',lambda:A.notifier.search(sender_id=A.sender_id,**B),{_B:B.get(_B),_C:_api_input(B)})
|
|
136
140
|
async def add_document(A,**B):return await A._invoke_sdk_api('addDocument',lambda:A.notifier.add_document(sender_id=A.sender_id,**B),{_B:B.get(_B),_C:_api_input(B)})
|
|
@@ -141,4 +145,4 @@ class WorkflowContext:
|
|
|
141
145
|
async def record_step(B,step):
|
|
142
146
|
A=step
|
|
143
147
|
if not B._observability_enabled or not B._observability or not B._execution_id:return
|
|
144
|
-
C=to_log_step_type(A.get(
|
|
148
|
+
C=to_log_step_type(A.get(_P))if A.get(_P)else STEP_TYPE_CUSTOM;D=A.get(_E);await B._observability.record_step(B._execution_id,{**A,_V:A.get(_V)or A.get(_O),_N:A.get(_N)or B.agent_name,_O:A.get(_N)or B.agent_name or A.get(_O)or'custom',_P:C,_B:A.get(_B)or C,_I:A.get(_I,0),_J:A.get(_J,0),_D:A.get(_D),_S:A.get(_S,0),_R:A.get(_R,_Q),_K:A.get(_K),_E:format_parent_step_id(D)if D is not _A else format_parent_step_id(B._current_step_id),_C:_snapshot(A.get(_C)),_G:_snapshot(A.get(_G)),_L:_snapshot(A[_L]if _L in A else B.state)})
|
|
@@ -32,15 +32,16 @@ class WorkflowObservabilityClient:
|
|
|
32
32
|
await E._post(DNotifierEndpoints.workflow_register_url,C);return{'ok':True}
|
|
33
33
|
async def create_execution(F,payload):E='startedAt';D='userId';A=payload;C=A.get(_B)or short_id('exec');B={_B:C,_D:A[_D],_E:A[_E],D:A[D],_C:'in_progress',_H:0,E:A[E]};set_json_column(B,_F,A.get(_F));await F._post(DNotifierEndpoints.workflow_executions_url,B);return{_I:C,_J:B}
|
|
34
34
|
async def update_execution(G,execution_id,patch):F='result';E='durationMs';D='endedAt';C=execution_id;A=patch;B={_B:C,_C:A[_C],D:A[D],E:A[E],_H:A[_H]};set_json_column(B,_G,A.get(_G));set_json_column(B,F,A.get(F));await G._post(DNotifierEndpoints.workflow_execution_complete_url(C),B);return{'ok':True,_J:B}
|
|
35
|
-
async def add_step(
|
|
36
|
-
|
|
35
|
+
async def add_step(W,execution_id,step):
|
|
36
|
+
V='branchKey';U='siblingIndex';T='parentStepId';S='state';R='output';Q='tokensOut';P='tokensIn';O='latencyMs';N='executedAt';M='label';L='kind';H=execution_id;G='provider';F='model';D='apiName';C='agentName';A=step;I=resolve_step_type(A);X=A.get(C)or A.get(L)or A.get(D);B={_B:A[_B],_I:H,L:X,M:A.get(M),'type':I,'step_type':I,_C:A.get(_C),N:A.get(N),O:A.get(O,0),P:A.get(P,0),Q:A.get(Q,0)}
|
|
37
37
|
if A.get(D):B[D]=A[D]
|
|
38
38
|
if A.get(C):B[C]=A[C]
|
|
39
39
|
if A.get(F):B[F]=str(A[F])
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
40
|
+
if A.get(G):B[G]=str(A[G])
|
|
41
|
+
set_json_column(B,_F,A.get(_F));set_json_column(B,R,A.get(R));set_json_column(B,S,A.get(S));set_json_column(B,_G,A.get(_G));J=A.get(T)
|
|
42
|
+
if J:B[T]=J
|
|
43
|
+
E=A.get(U)
|
|
44
|
+
if E is not _A and E!='':B[U]=E
|
|
45
|
+
K=A.get(V)
|
|
46
|
+
if K:B[V]=K
|
|
47
|
+
await W._post(DNotifierEndpoints.workflow_execution_steps_url(H),B);return{'ok':True}
|
dnotifier-1.0.2/CHANGELOG.md
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
## 1.0.0
|
|
4
|
-
|
|
5
|
-
* Initial Python SDK release with feature parity to JS and Dart SDKs.
|
|
6
|
-
* WebSocket and HTTP transport, binary framing, chunking, AI pipeline, RAG documents, chat history, session logging, and workflows.
|
|
7
|
-
* Production endpoints at `api.dnotifier.com`.
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
from typing import Awaitable,Callable,TYPE_CHECKING
|
|
3
|
-
from.errors import WorkflowError
|
|
4
|
-
if TYPE_CHECKING:from.context import WorkflowContext
|
|
5
|
-
AgentRun=Callable[['WorkflowContext'],Awaitable]
|
|
6
|
-
class Agent:
|
|
7
|
-
def __init__(B,name,run):
|
|
8
|
-
A=name
|
|
9
|
-
if not A or not isinstance(A,str):raise WorkflowError('Agent name is required')
|
|
10
|
-
if not callable(run):raise WorkflowError(f'Agent "{A}": run must be a callable')
|
|
11
|
-
B.name=A;B.run=run
|
|
12
|
-
def define_agent(name,run):return Agent(name=name,run=run)
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|