proxyllm-client 1.0.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,153 @@
|
|
|
1
|
+
"""Client helper for ProxyLLM (https://proxyllm.ai), the OpenAI-compatible
|
|
2
|
+
LLM gateway.
|
|
3
|
+
|
|
4
|
+
Two jobs, kept small:
|
|
5
|
+
|
|
6
|
+
1. Point any OpenAI SDK at the gateway in one line:
|
|
7
|
+
|
|
8
|
+
import proxyllm_client
|
|
9
|
+
client = proxyllm_client.OpenAI(api_key="pllm_...")
|
|
10
|
+
|
|
11
|
+
2. Let an agent create an account autonomously (email OTP, no captcha), then
|
|
12
|
+
provision routing keys. Unpaid accounts answer HTTP 402 with a checkout
|
|
13
|
+
link for the human operator; ``account()`` polls until the plan flips.
|
|
14
|
+
Full flow: https://proxyllm.ai/auth.md
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import json
|
|
20
|
+
import urllib.error
|
|
21
|
+
import urllib.request
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
"OpenAI",
|
|
25
|
+
"signup",
|
|
26
|
+
"verify",
|
|
27
|
+
"account",
|
|
28
|
+
"models",
|
|
29
|
+
"key_info",
|
|
30
|
+
"create_routing_key",
|
|
31
|
+
"savings",
|
|
32
|
+
"GATEWAY_URL",
|
|
33
|
+
"BASE_URL",
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
GATEWAY_URL = "https://api.proxyllm.ai"
|
|
37
|
+
BASE_URL = GATEWAY_URL + "/v1"
|
|
38
|
+
_DASHBOARD = "https://proxyllm.ai"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _call(url: str, method: str = "GET", token: str | None = None, body=None):
|
|
42
|
+
"""One HTTP call. Non-2xx responses return their parsed body instead of
|
|
43
|
+
raising: ProxyLLM's 402/410 bodies carry the next step (checkout link,
|
|
44
|
+
expiry) and are the useful part."""
|
|
45
|
+
req = urllib.request.Request(url, method=method)
|
|
46
|
+
req.add_header("Content-Type", "application/json")
|
|
47
|
+
if token:
|
|
48
|
+
req.add_header("Authorization", f"Bearer {token}")
|
|
49
|
+
data = None if body is None else json.dumps(body).encode()
|
|
50
|
+
try:
|
|
51
|
+
with urllib.request.urlopen(req, data) as res:
|
|
52
|
+
return {"status": res.status, **json.loads(res.read().decode() or "{}")}
|
|
53
|
+
except urllib.error.HTTPError as e:
|
|
54
|
+
try:
|
|
55
|
+
return {"status": e.code, **json.loads(e.read().decode() or "{}")}
|
|
56
|
+
except (ValueError, TypeError):
|
|
57
|
+
return {"status": e.code}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def OpenAI(**kwargs):
|
|
61
|
+
"""An ``openai.OpenAI`` client pre-pointed at the ProxyLLM gateway.
|
|
62
|
+
|
|
63
|
+
Pass ``api_key="pllm_..."`` (a routing key). Every other keyword goes to
|
|
64
|
+
the OpenAI constructor unchanged. Requires the ``openai`` package
|
|
65
|
+
(``pip install proxyllm-client[openai]``).
|
|
66
|
+
"""
|
|
67
|
+
try:
|
|
68
|
+
import openai
|
|
69
|
+
except ImportError as e:
|
|
70
|
+
raise ImportError(
|
|
71
|
+
"The openai package is required: pip install proxyllm-client[openai]"
|
|
72
|
+
) from e
|
|
73
|
+
kwargs.setdefault("base_url", BASE_URL)
|
|
74
|
+
return openai.OpenAI(**kwargs)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def signup(email: str, source: str = "pypi") -> dict:
|
|
78
|
+
"""Start account creation: emails a 6-digit code to the operator's inbox.
|
|
79
|
+
|
|
80
|
+
New accounts are unpaid and removed after 48 hours unless a human
|
|
81
|
+
activates the membership. Follow with :func:`verify`.
|
|
82
|
+
"""
|
|
83
|
+
return _call(
|
|
84
|
+
f"{_DASHBOARD}/api/auth?action=agent-signup",
|
|
85
|
+
"POST",
|
|
86
|
+
body={"email": email, "source": source},
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def verify(email: str, code: str) -> dict:
|
|
91
|
+
"""Exchange the emailed code for the sk_ account token (returned once).
|
|
92
|
+
|
|
93
|
+
While unpaid, the response carries ``payment.checkout_url`` to relay to
|
|
94
|
+
the human operator.
|
|
95
|
+
"""
|
|
96
|
+
return _call(
|
|
97
|
+
f"{_DASHBOARD}/api/auth?action=agent-verify",
|
|
98
|
+
"POST",
|
|
99
|
+
body={"email": email, "code": code},
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def account(token: str) -> dict:
|
|
104
|
+
"""Account state. Works while unpaid; poll until ``plan == "pro"``."""
|
|
105
|
+
return _call(f"{GATEWAY_URL}/v1/organizations/me", token=token)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def models(routing_key: str | None = None) -> dict:
|
|
109
|
+
"""OpenAI-shaped model list. No auth needed; key-scoped with one."""
|
|
110
|
+
return _call(f"{GATEWAY_URL}/v1/models", token=routing_key)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def key_info(routing_key: str) -> dict:
|
|
114
|
+
"""What a routing key routes to: lanes, models, budget remaining."""
|
|
115
|
+
return _call(f"{GATEWAY_URL}/v1/key", token=routing_key)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def create_routing_key(token: str, label: str = "default", **body) -> dict:
|
|
119
|
+
"""Mint a pllm_ routing key (``raw_key`` returned once). Optional keyword
|
|
120
|
+
arguments pass through: ``monthly_budget_usd``, ``mode``, ``providers``."""
|
|
121
|
+
return _call(
|
|
122
|
+
f"{GATEWAY_URL}/v1/organizations/routing-keys",
|
|
123
|
+
"POST",
|
|
124
|
+
token=token,
|
|
125
|
+
body={"label": label, **body},
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
_TIERS = (
|
|
130
|
+
("ChatGPT Plus", 20, 700),
|
|
131
|
+
("ChatGPT Pro 5x", 100, 3500),
|
|
132
|
+
("ChatGPT Pro 20x", 200, 14000),
|
|
133
|
+
)
|
|
134
|
+
_FEE = 129
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def savings(monthly_openai_bill_usd: float) -> dict:
|
|
138
|
+
"""API bill in, subscription tier + flat total + monthly savings out."""
|
|
139
|
+
bill = max(0.0, float(monthly_openai_bill_usd))
|
|
140
|
+
tier = next((t for t in _TIERS if bill <= t[2]), None)
|
|
141
|
+
if tier is None:
|
|
142
|
+
seats = -(-int(bill) // _TIERS[2][2])
|
|
143
|
+
tier = (f"{seats}x ChatGPT Pro 20x", seats * _TIERS[2][1], seats * _TIERS[2][2])
|
|
144
|
+
name, cost, capacity = tier
|
|
145
|
+
total = cost + _FEE
|
|
146
|
+
return {
|
|
147
|
+
"plan": name,
|
|
148
|
+
"plan_cost_usd": cost,
|
|
149
|
+
"plan_capacity_usd": capacity,
|
|
150
|
+
"proxyllm_fee_usd": _FEE,
|
|
151
|
+
"new_monthly_total_usd": total,
|
|
152
|
+
"monthly_savings_usd": max(0, round(bill - total)),
|
|
153
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: proxyllm-client
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Client helper for ProxyLLM, the OpenAI-compatible LLM gateway: one-line OpenAI client wiring plus the autonomous email-OTP account signup flow.
|
|
5
|
+
Author-email: ProxyLLM <support@proxyllm.ai>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://proxyllm.ai/docs/api
|
|
8
|
+
Project-URL: Documentation, https://proxyllm.ai/auth.md
|
|
9
|
+
Project-URL: OpenAPI, https://proxyllm.ai/openapi.json
|
|
10
|
+
Keywords: llm,openai-compatible,gateway,proxyllm,agent,http-402
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
15
|
+
Requires-Python: >=3.9
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
Provides-Extra: openai
|
|
18
|
+
Requires-Dist: openai>=1.0; extra == "openai"
|
|
19
|
+
|
|
20
|
+
# proxyllm-client
|
|
21
|
+
|
|
22
|
+
Client helper for [ProxyLLM](https://proxyllm.ai), the OpenAI-compatible LLM gateway. One routing key rides provider lanes with fallback (a flat-fee ChatGPT/Codex subscription, self-hosted Claude Code bridges, metered API keys), budgets, and request logs.
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pip install proxyllm-client[openai]
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Point the OpenAI SDK at the gateway:
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
import proxyllm_client
|
|
32
|
+
|
|
33
|
+
client = proxyllm_client.OpenAI(api_key="pllm_...")
|
|
34
|
+
client.chat.completions.create(model="flagship", messages=[{"role": "user", "content": "hi"}])
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Or set two environment variables and change nothing else: `OPENAI_BASE_URL=https://api.proxyllm.ai/v1`, `OPENAI_API_KEY=pllm_...`.
|
|
38
|
+
|
|
39
|
+
Agents can create the account themselves (email OTP, no captcha or browser):
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
proxyllm_client.signup("operator@example.com") # code lands in the inbox
|
|
43
|
+
r = proxyllm_client.verify("operator@example.com", "123456")
|
|
44
|
+
token = r["account_token"] # sk_..., returned once
|
|
45
|
+
|
|
46
|
+
state = proxyllm_client.account(token) # 402 payload until a human pays:
|
|
47
|
+
print(state["payment"]["checkout_url"]) # relay this to your operator
|
|
48
|
+
# poll account(token) until state["plan"] == "pro", then:
|
|
49
|
+
key = proxyllm_client.create_routing_key(token, label="agent")["raw_key"]
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Unpaid accounts answer HTTP 402 with the checkout link, the flat price ($129/mo), and the 48-hour removal deadline; error bodies are returned as values because they contain the next step. Free helpers: `models()`, `key_info(routing_key)`, `savings(monthly_bill)`.
|
|
53
|
+
|
|
54
|
+
Full agent flow: <https://proxyllm.ai/auth.md>. OpenAPI: <https://proxyllm.ai/openapi.json>. MCP server: `npx -y proxyllm-mcp`.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
proxyllm_client/__init__.py,sha256=zMUVyqwqD2pve_MCILBACIPx1pBkHFSJDSHegx_s0JA,4891
|
|
2
|
+
proxyllm_client-1.0.0.dist-info/METADATA,sha256=q7XlFBYbbftiMHBpoW5EfhYWubrh07C8rhT6-JL3OIg,2529
|
|
3
|
+
proxyllm_client-1.0.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
4
|
+
proxyllm_client-1.0.0.dist-info/top_level.txt,sha256=__xTeIMXiLqdxFKrr37NJe6kSOo19IhthbIuf7Gul2o,16
|
|
5
|
+
proxyllm_client-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
proxyllm_client
|