attp-client 0.0.1__py3-none-any.whl → 0.0.3__py3-none-any.whl
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.
- attp_client/catalog.py +62 -4
- attp_client/interfaces/catalogs/tools/envelope.py +13 -0
- attp_client/interfaces/inference/message.py +2 -1
- attp_client/tools.py +2 -0
- {attp_client-0.0.1.dist-info → attp_client-0.0.3.dist-info}/METADATA +1 -1
- {attp_client-0.0.1.dist-info → attp_client-0.0.3.dist-info}/RECORD +7 -6
- {attp_client-0.0.1.dist-info → attp_client-0.0.3.dist-info}/WHEEL +0 -0
attp_client/catalog.py
CHANGED
@@ -1,11 +1,21 @@
|
|
1
|
+
import asyncio
|
1
2
|
from typing import Any, Callable, MutableMapping
|
3
|
+
|
4
|
+
from attp_client.errors.attp_exception import AttpException
|
2
5
|
from attp_client.errors.not_found import NotFoundError
|
6
|
+
from attp_client.interfaces.catalogs.tools.envelope import IEnvelope
|
7
|
+
from attp_client.interfaces.error import IErr
|
3
8
|
from attp_client.tools import ToolsManager
|
4
9
|
|
10
|
+
from reactivex import operators as ops
|
11
|
+
from reactivex.scheduler.eventloop import AsyncIOScheduler
|
12
|
+
|
13
|
+
from attp_core.rs_api import PyAttpMessage
|
14
|
+
|
5
15
|
|
6
16
|
class AttpCatalog:
|
7
|
-
attached_tools: MutableMapping[str, Callable[..., Any]]
|
8
|
-
tool_name_to_id_symlink: MutableMapping[str, str]
|
17
|
+
attached_tools: MutableMapping[str, Callable[..., Any]] # id, callback
|
18
|
+
tool_name_to_id_symlink: MutableMapping[str, str] # name, id
|
9
19
|
|
10
20
|
def __init__(
|
11
21
|
self,
|
@@ -18,12 +28,51 @@ class AttpCatalog:
|
|
18
28
|
self.tool_manager = manager
|
19
29
|
self.attached_tools = {}
|
20
30
|
self.tool_name_to_id_symlink = {}
|
31
|
+
|
32
|
+
self.responder = self.tool_manager.router.responder
|
33
|
+
|
34
|
+
async def start_tool_listener(self):
|
35
|
+
scheduler = AsyncIOScheduler(asyncio.get_event_loop())
|
36
|
+
|
37
|
+
def handle_call(item: IEnvelope):
|
38
|
+
asyncio.create_task(self.handle_call(item))
|
39
|
+
|
40
|
+
def send_err(err: AttpException):
|
41
|
+
asyncio.create_task(self.tool_manager.router.session.send_error(err=err.to_ierr(), correlation_id=None, route=1))
|
42
|
+
|
43
|
+
def envelopize(item: PyAttpMessage):
|
44
|
+
if not item.payload:
|
45
|
+
raise AttpException("EmptyPayload", detail={"message": "Payload was empty."})
|
46
|
+
try:
|
47
|
+
return IEnvelope.mps(item.payload)
|
48
|
+
except Exception as e:
|
49
|
+
raise AttpException("InvalidPayload", detail={"message": f"Payload was invalid: {str(e)}"})
|
50
|
+
|
51
|
+
def catch_handler(err: Any, _: Any):
|
52
|
+
# Convert any exception to AttpException if needed
|
53
|
+
if not isinstance(err, AttpException):
|
54
|
+
attp_err = AttpException("UnhandledException", detail={"message": str(err)})
|
55
|
+
else:
|
56
|
+
attp_err = err
|
57
|
+
send_err(attp_err)
|
58
|
+
# Return an empty observable to terminate the stream after error
|
59
|
+
from reactivex import empty
|
60
|
+
return empty()
|
61
|
+
|
62
|
+
self.responder.pipe(
|
63
|
+
ops.filter(lambda item: item.payload is not None and item.correlation_id == 1),
|
64
|
+
ops.map(lambda item: envelopize(item)),
|
65
|
+
ops.catch(catch_handler),
|
66
|
+
ops.filter(lambda item: item.catalog == self.catalog_name and item.tool_id in self.attached_tools),
|
67
|
+
ops.observe_on(scheduler),
|
68
|
+
).subscribe(lambda item: handle_call(item))
|
21
69
|
|
22
70
|
async def attach_tool(
|
23
71
|
self,
|
24
|
-
callback: Callable[
|
72
|
+
callback: Callable[[IEnvelope], Any],
|
25
73
|
name: str,
|
26
74
|
description: str | None = None,
|
75
|
+
schema: dict | None = None,
|
27
76
|
schema_id: str | None = None,
|
28
77
|
*,
|
29
78
|
return_direct: bool = False,
|
@@ -37,6 +86,7 @@ class AttpCatalog:
|
|
37
86
|
description=description,
|
38
87
|
schema_id=schema_id,
|
39
88
|
return_direct=return_direct,
|
89
|
+
schema=schema,
|
40
90
|
schema_ver=schema_ver,
|
41
91
|
timeout_ms=timeout_ms,
|
42
92
|
idempotent=idempotent
|
@@ -56,4 +106,12 @@ class AttpCatalog:
|
|
56
106
|
raise NotFoundError(f"Tool {name} not marked as registered and wasn't found in the catalog {self.catalog_name}.")
|
57
107
|
|
58
108
|
await self.tool_manager.unregister(self.catalog_name, tool_id)
|
59
|
-
return tool_id
|
109
|
+
return tool_id
|
110
|
+
|
111
|
+
async def handle_call(self, envelope: IEnvelope) -> Any:
|
112
|
+
tool = self.attached_tools.get(envelope.tool_id)
|
113
|
+
|
114
|
+
if not tool:
|
115
|
+
raise NotFoundError(f"Tool {envelope.tool_id} not marked as registered and wasn't found in the catalog {self.catalog_name}.")
|
116
|
+
|
117
|
+
return await tool(envelope)
|
@@ -0,0 +1,13 @@
|
|
1
|
+
from typing import Annotated, Any, Mapping
|
2
|
+
from pydantic import Field
|
3
|
+
from typing_extensions import Doc
|
4
|
+
from attp_client.misc.fixed_basemodel import FixedBaseModel
|
5
|
+
|
6
|
+
|
7
|
+
class IEnvelope(FixedBaseModel):
|
8
|
+
"""Uniform tool output"""
|
9
|
+
type: Annotated[str, Doc("Envelop discriminator; For tools only 'tool-call'")] = Field("tool-call")
|
10
|
+
catalog: Annotated[str, Doc("Tool catalog name where tool is registered and being called from")]
|
11
|
+
tool_id: Annotated[str, Doc("Tool ID being called")]
|
12
|
+
metadata: Annotated[Mapping[str, Any], Doc("AgentHub/trace metadata")] = Field(default_factory=dict)
|
13
|
+
data: Annotated[Mapping[str, Any], Doc("Tool output JSON matching provided tool arg schema")]
|
@@ -1,4 +1,4 @@
|
|
1
|
-
from typing import Literal
|
1
|
+
from typing import Any, Literal
|
2
2
|
from uuid import UUID
|
3
3
|
from attp_client.interfaces.inference.enums.message_emergency_type import MessageEmergencyTypeEnum
|
4
4
|
from attp_client.interfaces.inference.enums.message_type import MessageTypeEnum
|
@@ -34,6 +34,7 @@ class IMessageDTOV2(FixedBaseModel):
|
|
34
34
|
tool_error_detail: str | None = None
|
35
35
|
|
36
36
|
specialist_required: MessageEmergencyTypeEnum | None = None
|
37
|
+
metadata: dict[str, Any] | None = None
|
37
38
|
|
38
39
|
def to_wrap(self) -> dict:
|
39
40
|
w = self.model_dump()
|
attp_client/tools.py
CHANGED
@@ -15,6 +15,7 @@ class ToolsManager:
|
|
15
15
|
name: str,
|
16
16
|
description: str | None = None,
|
17
17
|
schema_id: str | None = None,
|
18
|
+
schema: dict | None = None,
|
18
19
|
*,
|
19
20
|
return_direct: bool = False,
|
20
21
|
schema_ver: str = "1.0",
|
@@ -31,6 +32,7 @@ class ToolsManager:
|
|
31
32
|
"schema_id": schema_id,
|
32
33
|
"return_direct": return_direct,
|
33
34
|
"schema_ver": schema_ver,
|
35
|
+
"schema": schema,
|
34
36
|
"timeout_ms": timeout_ms,
|
35
37
|
"idempotent": idempotent
|
36
38
|
}
|
@@ -1,4 +1,4 @@
|
|
1
|
-
attp_client/catalog.py,sha256=
|
1
|
+
attp_client/catalog.py,sha256=trWLiE_nDXnhlygr4yewti9JLGwanokDdMhHYffMNKs,4296
|
2
2
|
attp_client/client.py,sha256=aSjDaH1pHkmPsVZBwIrSIMX8QXDtbDfGpVBXR5KgnF4,3989
|
3
3
|
attp_client/consts.py,sha256=6UZyqWddycOp-TKW5yvb0JW2fdfcS-J2xFVVNfuJQbU,18
|
4
4
|
attp_client/errors/attp_exception.py,sha256=HePYyYMknS4t6tMrwU-p9L_LPdj1i7chntrlM53ueK4,347
|
@@ -9,6 +9,7 @@ attp_client/errors/serialization_error.py,sha256=Pa8PRzFJrrikA1Ikj0q-0euvXVUMb_q
|
|
9
9
|
attp_client/errors/unauthenticated_error.py,sha256=F0V1FjO0qVLMl6Y120y3AXKZnwb5iDD17c4GEMbL5aI,46
|
10
10
|
attp_client/inference.py,sha256=wZ-iF-O8eWDn_YosW234ClnXFa7Gw7I73eD2ugmXvfU,4626
|
11
11
|
attp_client/interfaces/catalogs/catalog.py,sha256=3PxlRwR3y2tbQVfXAkhDIv07AJPraMfH0c_pyi7Y6z8,146
|
12
|
+
attp_client/interfaces/catalogs/tools/envelope.py,sha256=6aUx06ou9If9OYv4BODKiBybrgBL2YWfSHZ6ukIR1K0,693
|
12
13
|
attp_client/interfaces/error.py,sha256=fIrk5XlAhMs6mbYQ5PzgwS0v-LIbtne3OlQue4CjWXs,139
|
13
14
|
attp_client/interfaces/handshake/auth.py,sha256=lCbu4Mz50N7-XC5szQKht6xnw-d8pPQMeBviSPfRB2U,337
|
14
15
|
attp_client/interfaces/handshake/hello.py,sha256=hlwWcvSpRebtnbS3lLBYmBm7AfHE4n6dp0ILgfqMUFU,680
|
@@ -16,18 +17,18 @@ attp_client/interfaces/handshake/ready.py,sha256=0gEqwbvgRHkHb3FIn4YEyJ8LuzzP260
|
|
16
17
|
attp_client/interfaces/inference/enums/message_data_type.py,sha256=v68IW8DMyb-ezGnyR6T5kPE70FPDZ14AvCP2mI-iLPo,397
|
17
18
|
attp_client/interfaces/inference/enums/message_emergency_type.py,sha256=8s6PONMUdNzrxVLM5aTAicdKSk6BsRFLujVUsm5jopM,149
|
18
19
|
attp_client/interfaces/inference/enums/message_type.py,sha256=joo6t9vLDeupEtfE1II_-oW6Y_c9zSAGe0bl32TqAlA,508
|
19
|
-
attp_client/interfaces/inference/message.py,sha256=
|
20
|
+
attp_client/interfaces/inference/message.py,sha256=zoKf1yxRsbFqPlsDpO5zVFVgIqDAbc7xkbIcivao1HU,1637
|
20
21
|
attp_client/interfaces/inference/tool.py,sha256=Oabb7w0HJtpyO-AcezmojdsJMGuLH4JHXft7h4mlyX8,134
|
21
22
|
attp_client/interfaces/route_mappings.py,sha256=j6hEdkCP5xPpHS16EWmlkdTlnHa7z6e8ukbY-NolKcQ,416
|
22
23
|
attp_client/misc/fixed_basemodel.py,sha256=0MTVmlTrA75Oxv0pVfLdXFTSp5AmBzgiNwvDiLFGF_w,1853
|
23
24
|
attp_client/misc/serializable.py,sha256=tU08TsjiLiafAhU1jKd5BxajlHdEDcdKeEiKPqhMSTI,2102
|
24
25
|
attp_client/router.py,sha256=UDHU2xsvTjgSIzMtV0jkPvOYK9R7GFjKfIrjjBHws-Q,4575
|
25
26
|
attp_client/session.py,sha256=YDrd1KYmwKsZW6Xdkt6UprXZzGz6pFFSkk6O7XS6ZsA,11409
|
26
|
-
attp_client/tools.py,sha256=
|
27
|
+
attp_client/tools.py,sha256=8GppeVYtnWNqc8M6a-s-S43QYpZROU4aYk67R9388yc,1839
|
27
28
|
attp_client/types/route_mapping.py,sha256=Kb9ZX88lqihRZr8IryfH1Vg_YAobW699Yjl6Raz1rdg,375
|
28
29
|
attp_client/utils/context_awaiter.py,sha256=oCptu5g8mY43j5cr-W4fOe85OCCaqQI9r_Pn92NgZSY,1035
|
29
30
|
attp_client/utils/route_mapper.py,sha256=uJNhKp6ipCSUxoiZS0Liix2lHOgUAnJM0kfgXWAh9RQ,554
|
30
31
|
attp_client/utils/serializer.py,sha256=O1tWYbQS9jC9aus-ISKtliKCgGmOEIb9hxykVrmMKGY,636
|
31
|
-
attp_client-0.0.
|
32
|
-
attp_client-0.0.
|
33
|
-
attp_client-0.0.
|
32
|
+
attp_client-0.0.3.dist-info/METADATA,sha256=hq0ydm3fM_FZX89y--twFaUAVlIshQjU6dFuXa3YUnY,7137
|
33
|
+
attp_client-0.0.3.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
34
|
+
attp_client-0.0.3.dist-info/RECORD,,
|
File without changes
|