trymockagent 0.1.0__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.
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: trymockagent
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python client for the MockAgent API
|
|
5
|
+
Requires-Python: >=3.9
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
|
|
8
|
+
# trymockagent
|
|
9
|
+
|
|
10
|
+
Dependency-free Python client for the MockAgent API. Supports Python 3.9+.
|
|
11
|
+
|
|
12
|
+
After publishing the package, install it with:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pip install trymockagent
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
```python
|
|
19
|
+
import os
|
|
20
|
+
import uuid
|
|
21
|
+
|
|
22
|
+
from trymockagent import MockAgent, MockAgentError
|
|
23
|
+
|
|
24
|
+
mock = MockAgent(
|
|
25
|
+
tool_id="YOUR_TOOL_ID",
|
|
26
|
+
api_key=os.getenv("MOCKAGENT_API_KEY"), # optional unless the tool requires a key
|
|
27
|
+
)
|
|
28
|
+
run_id = str(uuid.uuid4())
|
|
29
|
+
|
|
30
|
+
try:
|
|
31
|
+
result = mock.call({"user_id": "vip", "reason": "duplicate"}, run_id=run_id)
|
|
32
|
+
print(result)
|
|
33
|
+
# If this tool has final-answer QA criteria configured:
|
|
34
|
+
qa = mock.submit_final_answer("The VIP refund was processed.", run_id=run_id)
|
|
35
|
+
print(qa)
|
|
36
|
+
except MockAgentError as error:
|
|
37
|
+
print(error.status, error.body, error.retryable, error.retry_after_seconds)
|
|
38
|
+
raise
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
`get_tool()` fetches the tool's schema and description. `call(arguments,
|
|
42
|
+
run_id=...)` posts JSON and forwards the run ID in `x-mockagent-run-id`.
|
|
43
|
+
`submit_final_answer(text, run_id=...)` submits configured final-answer checks.
|
|
44
|
+
No automatic retries are made; inspect the exception and follow the response's
|
|
45
|
+
retry guidance. Set `base_url` for a self-hosted MockAgent instance.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
trymockagent.py,sha256=_4kcE5D3m2CtRlT5tgPNB-WWU1twQfTms9BTNJE1xgY,4787
|
|
2
|
+
trymockagent-0.1.0.dist-info/METADATA,sha256=qd26vj55MMKJWuRymbTnUbJqFGBNqpyqJPtDG8jpcPs,1397
|
|
3
|
+
trymockagent-0.1.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
4
|
+
trymockagent-0.1.0.dist-info/top_level.txt,sha256=U8dogot8XZ7oxMHCIWidT1gt_IJrulaS1kkk-KmT4rk,13
|
|
5
|
+
trymockagent-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
trymockagent
|
trymockagent.py
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"""A dependency-free Python client for the MockAgent API."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from typing import Any, Dict, Optional
|
|
7
|
+
from urllib.error import HTTPError, URLError
|
|
8
|
+
from urllib.parse import quote
|
|
9
|
+
from urllib.request import Request, urlopen
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class MockAgentError(Exception):
|
|
13
|
+
"""An HTTP, network, or response-format error from MockAgent."""
|
|
14
|
+
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
message: str,
|
|
18
|
+
*,
|
|
19
|
+
status: Optional[int] = None,
|
|
20
|
+
body: Any = None,
|
|
21
|
+
retry_after_seconds: Optional[int] = None,
|
|
22
|
+
cause: Optional[BaseException] = None,
|
|
23
|
+
) -> None:
|
|
24
|
+
super().__init__(message)
|
|
25
|
+
self.status = status
|
|
26
|
+
self.body = body
|
|
27
|
+
self.retry_after_seconds = retry_after_seconds
|
|
28
|
+
self.retryable = body.get("retryable") if isinstance(body, dict) else None
|
|
29
|
+
if cause is not None:
|
|
30
|
+
self.__cause__ = cause
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class MockAgent:
|
|
34
|
+
"""Call one MockAgent tool and submit final-answer QA for its runs."""
|
|
35
|
+
|
|
36
|
+
def __init__(
|
|
37
|
+
self,
|
|
38
|
+
tool_id: str,
|
|
39
|
+
*,
|
|
40
|
+
api_key: Optional[str] = None,
|
|
41
|
+
base_url: str = "https://mockagent.online",
|
|
42
|
+
timeout: Optional[float] = 30,
|
|
43
|
+
) -> None:
|
|
44
|
+
if not tool_id:
|
|
45
|
+
raise ValueError("tool_id is required.")
|
|
46
|
+
self.tool_id = tool_id
|
|
47
|
+
self.api_key = api_key
|
|
48
|
+
self.base_url = base_url.rstrip("/")
|
|
49
|
+
self.timeout = timeout
|
|
50
|
+
self.tool_url = "{}/api/v1/mock/{}".format(self.base_url, quote(tool_id, safe=""))
|
|
51
|
+
|
|
52
|
+
def get_tool(self) -> Any:
|
|
53
|
+
"""Fetch the tool's description and expected JSON schema."""
|
|
54
|
+
return self._request(self.tool_url, "GET")
|
|
55
|
+
|
|
56
|
+
def call(self, arguments: Any, *, run_id: Optional[str] = None) -> Any:
|
|
57
|
+
"""Post tool-call arguments, optionally associating them with a run."""
|
|
58
|
+
headers = {"x-mockagent-run-id": run_id} if run_id else {}
|
|
59
|
+
return self._request(self.tool_url, "POST", arguments, headers)
|
|
60
|
+
|
|
61
|
+
def submit_final_answer(self, final_answer: str, *, run_id: str) -> Any:
|
|
62
|
+
"""Submit the final answer for configured checks on this run."""
|
|
63
|
+
if not run_id:
|
|
64
|
+
raise ValueError("run_id is required to submit a final answer.")
|
|
65
|
+
return self._request(
|
|
66
|
+
self.tool_url + "/final-answer",
|
|
67
|
+
"POST",
|
|
68
|
+
{"final_answer": final_answer},
|
|
69
|
+
{"x-mockagent-run-id": run_id},
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
def _request(
|
|
73
|
+
self,
|
|
74
|
+
url: str,
|
|
75
|
+
method: str,
|
|
76
|
+
body: Any = None,
|
|
77
|
+
extra_headers: Optional[Dict[str, str]] = None,
|
|
78
|
+
) -> Any:
|
|
79
|
+
headers = dict(extra_headers or {})
|
|
80
|
+
if body is not None:
|
|
81
|
+
headers["Content-Type"] = "application/json"
|
|
82
|
+
if self.api_key:
|
|
83
|
+
headers["Authorization"] = "Bearer " + self.api_key
|
|
84
|
+
encoded_body = None if body is None else json.dumps(body).encode("utf-8")
|
|
85
|
+
request = Request(url, data=encoded_body, headers=headers, method=method)
|
|
86
|
+
|
|
87
|
+
try:
|
|
88
|
+
with urlopen(request, timeout=self.timeout) as response:
|
|
89
|
+
status = response.status
|
|
90
|
+
response_headers = response.headers
|
|
91
|
+
raw_body = response.read()
|
|
92
|
+
except HTTPError as error:
|
|
93
|
+
status = error.code
|
|
94
|
+
response_headers = error.headers
|
|
95
|
+
try:
|
|
96
|
+
raw_body = error.read()
|
|
97
|
+
finally:
|
|
98
|
+
error.close()
|
|
99
|
+
except URLError as error:
|
|
100
|
+
raise MockAgentError(
|
|
101
|
+
"Could not connect to MockAgent.",
|
|
102
|
+
cause=error,
|
|
103
|
+
) from error
|
|
104
|
+
|
|
105
|
+
text = raw_body.decode("utf-8")
|
|
106
|
+
try:
|
|
107
|
+
parsed_body = json.loads(text) if text else None
|
|
108
|
+
except json.JSONDecodeError as error:
|
|
109
|
+
raise MockAgentError(
|
|
110
|
+
"MockAgent returned a non-JSON response.",
|
|
111
|
+
status=status,
|
|
112
|
+
body=text,
|
|
113
|
+
cause=error,
|
|
114
|
+
) from error
|
|
115
|
+
|
|
116
|
+
if status < 200 or status >= 300:
|
|
117
|
+
message = "MockAgent request failed ({})".format(status)
|
|
118
|
+
if isinstance(parsed_body, dict):
|
|
119
|
+
message = parsed_body.get("error") or parsed_body.get("message") or message
|
|
120
|
+
retry_after = response_headers.get("Retry-After")
|
|
121
|
+
try:
|
|
122
|
+
retry_after_seconds = int(retry_after) if retry_after is not None else None
|
|
123
|
+
except ValueError:
|
|
124
|
+
retry_after_seconds = None
|
|
125
|
+
raise MockAgentError(
|
|
126
|
+
message,
|
|
127
|
+
status=status,
|
|
128
|
+
body=parsed_body,
|
|
129
|
+
retry_after_seconds=retry_after_seconds,
|
|
130
|
+
)
|
|
131
|
+
return parsed_body
|