macore-client-dev 0.1.52__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,3 @@
1
+ """The single release version shared by the client and server packages."""
2
+
3
+ __version__ = "0.1.52"
@@ -0,0 +1,9 @@
1
+ # client/macore_client/__init__.py
2
+ # Author: Vasiliy Zdanovskiy <vasilyvz@gmail.com>
3
+ # Project: model_access_core (71b1e006-b777-4edb-854e-f0a47120f793)
4
+ """Client package for the Model Access Core (MAC) server."""
5
+
6
+ from macore_client.client import MacClient
7
+ from macore_client.__about__ import __version__
8
+
9
+ __all__ = ["MacClient", "__version__"]
macore_client/cli.py ADDED
@@ -0,0 +1,77 @@
1
+ # client/macore_client/cli.py
2
+ # Author: Vasiliy Zdanovskiy <vasilyvz@gmail.com>
3
+ # Project: model_access_core (71b1e006-b777-4edb-854e-f0a47120f793)
4
+ """``macore-client`` console script: one subcommand per MAC command."""
5
+
6
+ from __future__ import annotations
7
+
8
+ import argparse
9
+ import asyncio
10
+ import json
11
+ import os
12
+ import sys
13
+ from typing import Any
14
+
15
+ from macore_client.client import COMMANDS, MacClient
16
+
17
+
18
+ def default_base_url() -> str:
19
+ """Resolve the server base URL from the environment."""
20
+ return os.environ.get("MAC_VERIFY_BASE_URL", "http://127.0.0.1:8010")
21
+
22
+
23
+ def build_parser() -> argparse.ArgumentParser:
24
+ """Build the CLI parser with one subcommand per client command method."""
25
+ parser = argparse.ArgumentParser(
26
+ prog="macore-client",
27
+ description="Call one Model Access Core server command over JSON-RPC.",
28
+ )
29
+ parser.add_argument(
30
+ "--base-url",
31
+ default=None,
32
+ help="Adapter base URL (default: env MAC_VERIFY_BASE_URL or http://127.0.0.1:8010)",
33
+ )
34
+ parser.add_argument("--timeout", type=float, default=None, help="HTTP timeout seconds")
35
+ subparsers = parser.add_subparsers(dest="command", required=True)
36
+ for command in COMMANDS:
37
+ sub = subparsers.add_parser(command.replace("_", "-"), help=f"Run {command}")
38
+ sub.add_argument(
39
+ "--params",
40
+ default="{}",
41
+ help="Command parameters as a JSON object string",
42
+ )
43
+ return parser
44
+
45
+
46
+ async def _run(base_url: str, timeout: float | None, command: str, params: dict[str, Any]) -> Any:
47
+ client = MacClient(base_url, timeout=timeout)
48
+ try:
49
+ return await client.call(command, params)
50
+ finally:
51
+ await client.close()
52
+
53
+
54
+ def main(argv: list[str] | None = None) -> int:
55
+ """Entry point of the ``macore-client`` command."""
56
+ args = build_parser().parse_args(argv)
57
+ command = str(args.command).replace("-", "_")
58
+ try:
59
+ params = json.loads(args.params)
60
+ except json.JSONDecodeError as exc:
61
+ print(f"macore-client: --params is not valid JSON: {exc}", file=sys.stderr)
62
+ return 2
63
+ if not isinstance(params, dict):
64
+ print("macore-client: --params must be a JSON object", file=sys.stderr)
65
+ return 2
66
+ base_url = args.base_url or default_base_url()
67
+ try:
68
+ result = asyncio.run(_run(base_url, args.timeout, command, params))
69
+ except Exception as exc: # surface transport/command failure as text, not traceback
70
+ print(f"macore-client: {command} failed: {exc}", file=sys.stderr)
71
+ return 1
72
+ print(json.dumps(result, ensure_ascii=False, indent=2, default=str))
73
+ return 0
74
+
75
+
76
+ if __name__ == "__main__":
77
+ raise SystemExit(main())
@@ -0,0 +1,201 @@
1
+ # client/macore_client/client.py
2
+ # Author: Vasiliy Zdanovskiy <vasilyvz@gmail.com>
3
+ # Project: model_access_core (71b1e006-b777-4edb-854e-f0a47120f793)
4
+ """The MAC client: one method per public MAC command.
5
+
6
+ Wraps the ``mcp_proxy_adapter`` framework JSON-RPC client. No method builds a
7
+ URL, a JSON-RPC envelope or a raw transport payload; transport, protocol
8
+ selection, serialization and queued-job handling all belong to the framework
9
+ client. Every method mirrors a command the MAC server registers, with the same
10
+ name, so client and server read against each other without a translation table.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import contextlib
16
+ import sys
17
+ from typing import Any
18
+ from urllib.parse import urlparse
19
+
20
+
21
+ def _import_jsonrpc_client() -> Any:
22
+ """Import the framework client with its import-time banner kept off stdout."""
23
+ with contextlib.redirect_stdout(sys.stderr):
24
+ from mcp_proxy_adapter.client.jsonrpc_client.client import JsonRpcClient
25
+ return JsonRpcClient
26
+
27
+ # Public MAC commands, one client method each. model_selftest is queued on the
28
+ # server; the rest answer inline.
29
+ COMMANDS: tuple[str, ...] = (
30
+ "info",
31
+ "list_vectorization_models",
32
+ "count_vectorization_tokens",
33
+ "vectorize",
34
+ "openai_models",
35
+ "health",
36
+ "model_health",
37
+ "model_providers",
38
+ "model_provider_status",
39
+ "model_vram_status",
40
+ "model_queue_status",
41
+ "model_session_create",
42
+ "model_session_get",
43
+ "model_session_delete",
44
+ "model_chat",
45
+ "model_count_tokens",
46
+ "model_estimate_request_capacity",
47
+ "model_request_status",
48
+ "model_request_cancel",
49
+ "model_cache_create",
50
+ "model_cache_set_active",
51
+ "model_cache_list",
52
+ "model_cache_delete",
53
+ "model_selftest",
54
+ "hardware_calibration",
55
+ )
56
+
57
+ _QUEUED_COMMANDS: frozenset[str] = frozenset({"model_selftest"})
58
+
59
+
60
+ def command_methods() -> tuple[str, ...]:
61
+ """Return the client's public command-method names (for surface checks)."""
62
+ return COMMANDS
63
+
64
+
65
+ def _unwrap(result: Any) -> Any:
66
+ """Unwrap the unified-execution envelope down to the command result."""
67
+ seen = 0
68
+ while isinstance(result, dict) and seen < 8:
69
+ seen += 1
70
+ if result.get("mode") in {"immediate", "queued"} and "result" in result:
71
+ result = result["result"]
72
+ continue
73
+ if "result" in result and set(result.keys()) <= {"result", "job_id", "status", "queued", "command", "raw_status", "mode"}:
74
+ result = result["result"]
75
+ continue
76
+ break
77
+ return result
78
+
79
+
80
+ class MacClient:
81
+ """Full-surface client for one Model Access Core server.
82
+
83
+ Attributes:
84
+ transport: The framework JSON-RPC client carrying every request.
85
+ """
86
+
87
+ def __init__(
88
+ self,
89
+ base_url: str = "http://127.0.0.1:8010",
90
+ *,
91
+ token: str | None = None,
92
+ token_header: str | None = None,
93
+ cert: str | None = None,
94
+ key: str | None = None,
95
+ ca: str | None = None,
96
+ check_hostname: bool = False,
97
+ timeout: float | None = None,
98
+ ) -> None:
99
+ parsed = urlparse(base_url)
100
+ protocol = parsed.scheme or "http"
101
+ host = parsed.hostname or "127.0.0.1"
102
+ port = parsed.port or (443 if protocol == "https" else 80)
103
+ json_rpc_client = _import_jsonrpc_client()
104
+ self.transport = json_rpc_client(
105
+ protocol=protocol,
106
+ host=host,
107
+ port=port,
108
+ token=token,
109
+ token_header=token_header,
110
+ cert=cert,
111
+ key=key,
112
+ ca=ca,
113
+ check_hostname=check_hostname,
114
+ timeout=timeout,
115
+ )
116
+
117
+ async def call(self, command: str, params: dict[str, Any] | None = None) -> Any:
118
+ """Execute one MAC command and return its unwrapped result."""
119
+ if command in _QUEUED_COMMANDS:
120
+ raw = await self.transport.execute_command_unified(
121
+ command,
122
+ params or {},
123
+ auto_poll=True,
124
+ )
125
+ else:
126
+ raw = await self.transport.execute_command_unified(
127
+ command,
128
+ params or {},
129
+ expect_queue=False,
130
+ )
131
+ return _unwrap(raw)
132
+
133
+ async def close(self) -> None:
134
+ """Close the underlying transport."""
135
+ close = getattr(self.transport, "close", None)
136
+ if close is not None:
137
+ await close()
138
+
139
+ async def info(self, section: str, *, page: int = 1, page_size: int = 1000) -> Any:
140
+ """Return one bounded page of embedded MAC reference documentation."""
141
+ return await self.call("info", {"section": section, "page": page, "page_size": page_size})
142
+
143
+ async def list_vectorization_models(self) -> Any:
144
+ """Return the ordered vectorization model inventory."""
145
+ return await self.call("list_vectorization_models")
146
+
147
+ async def openai_models(self) -> Any:
148
+ """Return the raw OpenAI model list with the added capability marker (no parameters)."""
149
+ return await self.call("openai_models")
150
+
151
+ async def vectorize(
152
+ self,
153
+ text: str | list[str],
154
+ provider_id: str,
155
+ model_id: str,
156
+ dimension: int | None = None,
157
+ ) -> Any:
158
+ """Generate vectors through the selected provider and model."""
159
+ params: dict[str, Any] = {
160
+ "text": text,
161
+ "provider_id": provider_id,
162
+ "model_id": model_id,
163
+ }
164
+ if dimension is not None:
165
+ params["dimension"] = dimension
166
+ return await self.call("vectorize", params)
167
+
168
+ async def count_vectorization_tokens(
169
+ self,
170
+ text: str | list[str],
171
+ provider_id: str,
172
+ model_id: str,
173
+ ) -> Any:
174
+ """Count vectorization input tokens without generating embeddings."""
175
+ return await self.call("count_vectorization_tokens", {
176
+ "text": text,
177
+ "provider_id": provider_id,
178
+ "model_id": model_id,
179
+ })
180
+
181
+
182
+ def _make_method(command: str) -> Any:
183
+ async def method(self: MacClient, params: dict[str, Any] | None = None) -> Any:
184
+ return await self.call(command, params)
185
+
186
+ method.__name__ = command
187
+ method.__qualname__ = f"MacClient.{command}"
188
+ method.__doc__ = f"Execute the MAC ``{command}`` command."
189
+ return method
190
+
191
+
192
+ for _command in COMMANDS:
193
+ if _command in {
194
+ "info",
195
+ "list_vectorization_models",
196
+ "vectorize",
197
+ "count_vectorization_tokens",
198
+ "openai_models",
199
+ }:
200
+ continue
201
+ setattr(MacClient, _command, _make_method(_command))
@@ -0,0 +1,226 @@
1
+ # client/macore_client/live_check.py
2
+ # Author: Vasiliy Zdanovskiy <vasilyvz@gmail.com>
3
+ # Project: model_access_core (71b1e006-b777-4edb-854e-f0a47120f793)
4
+ """Client-driven live acceptance helpers for a deployed MAC server."""
5
+
6
+ from __future__ import annotations
7
+
8
+ import asyncio
9
+ import os
10
+ from typing import Any
11
+
12
+ from macore_client.client import MacClient
13
+
14
+
15
+ def _base_url() -> str:
16
+ return os.environ.get("MAC_VERIFY_BASE_URL", "http://127.0.0.1:8010")
17
+
18
+
19
+ def _timeout() -> float:
20
+ raw = os.environ.get("MAC_VERIFY_TIMEOUT", "300")
21
+ try:
22
+ return float(raw)
23
+ except ValueError:
24
+ return 300.0
25
+
26
+
27
+ async def _with_client(coro: Any) -> int:
28
+ client = MacClient(
29
+ _base_url(),
30
+ ca=os.environ.get("MAC_VERIFY_CA"),
31
+ check_hostname=True,
32
+ timeout=_timeout(),
33
+ )
34
+ try:
35
+ return await coro(client)
36
+ finally:
37
+ await client.close()
38
+
39
+
40
+ def select_catalog_chat_model(catalog: Any, provider_id: str) -> dict[str, int | str] | None:
41
+ """Return one catalog-backed chat identity for ``provider_id``.
42
+
43
+ The returned mapping is deliberately small and stable for pipeline callers:
44
+ ``provider_id``, ``model_id``, ``context_window``, and
45
+ ``max_output_tokens``. It contains only positive integer limits and the
46
+ exact model ID advertised by MAC; it never translates provider aliases.
47
+ """
48
+
49
+ data = catalog.get("data") if isinstance(catalog, dict) else None
50
+ providers = data if isinstance(data, list) else None
51
+ if providers is None and isinstance(data, dict):
52
+ candidate = data.get("providers")
53
+ providers = candidate if isinstance(candidate, list) else None
54
+ if not isinstance(providers, list):
55
+ return None
56
+
57
+ provider = next(
58
+ (row for row in providers if isinstance(row, dict) and row.get("provider_id") == provider_id),
59
+ None,
60
+ )
61
+ summaries = provider.get("capability_summary") if isinstance(provider, dict) else None
62
+ if not isinstance(summaries, list):
63
+ return None
64
+
65
+ selections: list[dict[str, int | str]] = []
66
+ for summary in summaries:
67
+ if not isinstance(summary, dict):
68
+ continue
69
+ model_id = summary.get("model_id")
70
+ context_window = summary.get("context_window_tokens")
71
+ max_output_tokens = summary.get("max_output_tokens")
72
+ if (
73
+ not isinstance(model_id, str)
74
+ or not model_id
75
+ or not isinstance(context_window, int)
76
+ or isinstance(context_window, bool)
77
+ or context_window <= 0
78
+ or not isinstance(max_output_tokens, int)
79
+ or isinstance(max_output_tokens, bool)
80
+ or max_output_tokens <= 0
81
+ ):
82
+ continue
83
+ selections.append(
84
+ {
85
+ "provider_id": provider_id,
86
+ "model_id": model_id,
87
+ "context_window": context_window,
88
+ "max_output_tokens": max_output_tokens,
89
+ }
90
+ )
91
+
92
+ return min(selections, key=lambda selection: str(selection["model_id"])) if selections else None
93
+
94
+
95
+ async def _run_catalog_chat_live(client: MacClient, provider_id: str) -> int:
96
+ catalog = await client.model_providers()
97
+ selection = select_catalog_chat_model(catalog, provider_id)
98
+ if selection is None:
99
+ print(f"FAIL {provider_id}-live: no chat model with positive limits in catalog")
100
+ return 1
101
+
102
+ result = await client.model_chat(
103
+ {
104
+ "provider_id": selection["provider_id"],
105
+ "provider_key_id": "noauth:default",
106
+ "model_id": selection["model_id"],
107
+ "messages": [
108
+ {"role": "user", "content": [{"type": "text", "text": "Reply OK."}]}
109
+ ],
110
+ "reserved_output_tokens": min(32, int(selection["max_output_tokens"])),
111
+ "stream": False,
112
+ "generation_options": {},
113
+ }
114
+ )
115
+ data = result.get("data") if isinstance(result, dict) else None
116
+ message = data.get("message") if isinstance(data, dict) else None
117
+ blocks = message.get("content") if isinstance(message, dict) else None
118
+ text_blocks = [
119
+ block.get("text")
120
+ for block in blocks
121
+ if isinstance(block, dict) and block.get("type") == "text"
122
+ ] if isinstance(blocks, list) else []
123
+ usage = data.get("usage") if isinstance(data, dict) else None
124
+ total_tokens = usage.get("total_tokens") if isinstance(usage, dict) else None
125
+ if (
126
+ not isinstance(result, dict)
127
+ or result.get("success") is not True
128
+ or not isinstance(data, dict)
129
+ or data.get("finish_reason") not in {"stop", "length"}
130
+ or not any(isinstance(text, str) and text.strip() for text in text_blocks)
131
+ or not isinstance(total_tokens, int)
132
+ or total_tokens <= 0
133
+ ):
134
+ print(f"FAIL {provider_id}-live: {selection}: {str(result)[:240]}")
135
+ return 1
136
+ print(f"PASS {provider_id}-live: {selection}: tokens={total_tokens}")
137
+ return 0
138
+
139
+
140
+ def run_health_live() -> int:
141
+ """The deployed server answers health and model_health."""
142
+
143
+ async def check(client: MacClient) -> int:
144
+ health = await client.call("health")
145
+ model_health = await client.call("model_health")
146
+ data = model_health.get("data") if isinstance(model_health, dict) else None
147
+ adapter = (data or {}).get("adapter") if isinstance(data, dict) else None
148
+ registry = (data or {}).get("registry") if isinstance(data, dict) else None
149
+ providers = (registry or {}).get("providers") if isinstance(registry, dict) else None
150
+ if not isinstance(health, dict):
151
+ print("FAIL health-live: health returned no object")
152
+ return 1
153
+ if adapter != "ok" or not isinstance(providers, int) or providers < 1:
154
+ print(f"FAIL health-live: adapter={adapter!r} providers={providers!r}")
155
+ return 1
156
+ print(f"PASS health-live: adapter=ok providers={providers}")
157
+ return 0
158
+
159
+ return asyncio.run(_with_client(check))
160
+
161
+
162
+ def run_providers_live() -> int:
163
+ """The deployed server lists at least one provider with models."""
164
+
165
+ async def check(client: MacClient) -> int:
166
+ result = await client.call("model_providers")
167
+ data = result.get("data") if isinstance(result, dict) else None
168
+ providers = data if isinstance(data, list) else None
169
+ if providers is None and isinstance(data, dict):
170
+ providers = data.get("providers") if isinstance(data.get("providers"), list) else None
171
+ if not isinstance(providers, list) or not providers:
172
+ print(f"FAIL providers-live: no providers in {str(result)[:200]}")
173
+ return 1
174
+ names = [p.get("provider_id") for p in providers if isinstance(p, dict)]
175
+ print(f"PASS providers-live: {len(providers)} providers: {', '.join(map(str, names))}")
176
+ return 0
177
+
178
+ return asyncio.run(_with_client(check))
179
+
180
+
181
+ def run_openrouter_qwen_live() -> int:
182
+ """One current OpenRouter chat model answers using its catalog identity."""
183
+
184
+ async def check(client: MacClient) -> int:
185
+ return await _run_catalog_chat_live(client, "openrouter-qwen")
186
+
187
+ return asyncio.run(_with_client(check))
188
+
189
+
190
+ def run_lmrs_live() -> int:
191
+ """One current LMRS chat model answers using its catalog identity."""
192
+
193
+ async def check(client: MacClient) -> int:
194
+ return await _run_catalog_chat_live(client, "lmrs")
195
+
196
+ return asyncio.run(_with_client(check))
197
+
198
+
199
+ def run_selftest_live() -> int:
200
+ """Drive the server's own full live regression (model_selftest)."""
201
+
202
+ async def check(client: MacClient) -> int:
203
+ result = await client.call("model_selftest", {})
204
+ node: Any = result
205
+ for _ in range(6):
206
+ if isinstance(node, dict) and isinstance(node.get("data"), dict):
207
+ node = node["data"]
208
+ continue
209
+ break
210
+ if not isinstance(node, dict) or "exit_code" not in node:
211
+ print(f"FAIL selftest-live: unrecognized selftest result: {str(result)[:300]}")
212
+ return 1
213
+ ok = bool(node.get("ok"))
214
+ exit_code = int(node.get("exit_code") or 0)
215
+ passed = node.get("passed")
216
+ failed = node.get("failed")
217
+ n_pass = len(passed) if isinstance(passed, list) else 0
218
+ n_fail = len(failed) if isinstance(failed, list) else 0
219
+ line = f"selftest-live: ok={ok} exit_code={exit_code} passed={n_pass} failed={n_fail}"
220
+ if not ok or exit_code != 0 or n_pass == 0:
221
+ print(f"FAIL {line}")
222
+ return 1
223
+ print(f"PASS {line}")
224
+ return 0
225
+
226
+ return asyncio.run(_with_client(check))
@@ -0,0 +1,6 @@
1
+ Metadata-Version: 2.5
2
+ Name: macore-client-dev
3
+ Version: 0.1.52
4
+ Summary: Client for the Model Access Core (MAC) server
5
+ Requires-Python: >=3.11
6
+ Requires-Dist: mcp-proxy-adapter<9,>=8.11.11
@@ -0,0 +1,9 @@
1
+ macore_client/__about__.py,sha256=mYN-ijU3nYK2qTVaGi3rQd84BxsMwnXyBfgi6P5pe1I,99
2
+ macore_client/__init__.py,sha256=ejRlRdmkN48Fk4K7epRhySctVL0TALJ_Z0YzOQWiLgI,346
3
+ macore_client/cli.py,sha256=77uk0JK3PoXrcWS-EXLpBsxcZSLJYj2XMiZMcLi2ApU,2694
4
+ macore_client/client.py,sha256=d6w66Dr8UM80zkaRyx_4SP5lwKiEak6iCaIpVAUxl7Q,6539
5
+ macore_client/live_check.py,sha256=z64baKK15v-fN1LtEHQPQeJR5LZgeGOl31pLn__-y48,8609
6
+ macore_client_dev-0.1.52.dist-info/METADATA,sha256=bYtdvMQ_LMVJH9BhjLEld9JULp2OPFIgQa_ojee_aaA,186
7
+ macore_client_dev-0.1.52.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
8
+ macore_client_dev-0.1.52.dist-info/entry_points.txt,sha256=PANZvFVZnfTM6zi14v0o9aSxsM-JC82HBdoMFgN4wvo,57
9
+ macore_client_dev-0.1.52.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.33.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ macore-client = macore_client.cli:main