feat: add settlement contract boundary
This commit is contained in:
@@ -7,6 +7,8 @@ import time
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
import uuid
|
||||
from typing import Any
|
||||
|
||||
|
||||
class _GatewayHTTPServer(http.server.HTTPServer):
|
||||
@@ -17,11 +19,15 @@ class _GatewayHTTPServer(http.server.HTTPServer):
|
||||
inference_route: list[str] | None,
|
||||
tracker_url: str | None,
|
||||
model_presets: list[str] | None,
|
||||
contracts: Any | None,
|
||||
minimum_stake: int,
|
||||
):
|
||||
super().__init__(addr, handler)
|
||||
self.inference_route = inference_route
|
||||
self.tracker_url = tracker_url
|
||||
self.model_presets = model_presets
|
||||
self.contracts = contracts
|
||||
self.minimum_stake = minimum_stake
|
||||
|
||||
|
||||
class _GatewayHandler(http.server.BaseHTTPRequestHandler):
|
||||
@@ -108,6 +114,9 @@ class _GatewayHandler(http.server.BaseHTTPRequestHandler):
|
||||
or not all(isinstance(node_url, str) and node_url for node_url in route)
|
||||
):
|
||||
raise ValueError("invalid tracker route")
|
||||
route_nodes = tracker_resp.get("nodes", [])
|
||||
if route_nodes and not _valid_route_nodes(route_nodes):
|
||||
raise ValueError("invalid tracker route metadata")
|
||||
except urllib.error.HTTPError as exc:
|
||||
error_body = _safe_error_body(exc)
|
||||
self._send_openai_error(
|
||||
@@ -121,6 +130,7 @@ class _GatewayHandler(http.server.BaseHTTPRequestHandler):
|
||||
return
|
||||
else:
|
||||
route = server.inference_route # type: ignore[assignment]
|
||||
route_nodes = []
|
||||
|
||||
messages = body.get("messages", [])
|
||||
|
||||
@@ -137,6 +147,24 @@ class _GatewayHandler(http.server.BaseHTTPRequestHandler):
|
||||
|
||||
text = node_resp["text"]
|
||||
model = body.get("model", "stub-model")
|
||||
if server.contracts is not None and route_nodes:
|
||||
api_key = _api_key_from_authorization(self.headers.get("Authorization"))
|
||||
if api_key:
|
||||
session_id = str(uuid.uuid4())
|
||||
tokens = max(1, _estimate_token_count(messages, text))
|
||||
for node in route_nodes:
|
||||
wallet_address = node.get("wallet_address")
|
||||
if not wallet_address:
|
||||
continue
|
||||
server.contracts.payment.record_attribution(
|
||||
session_id=session_id,
|
||||
api_key=api_key,
|
||||
node_wallet=wallet_address,
|
||||
layer_start=node["shard_start"],
|
||||
layer_end=node["shard_end"],
|
||||
tokens=tokens,
|
||||
speed_score=float(node.get("score", 1.0)),
|
||||
)
|
||||
|
||||
if streaming:
|
||||
self._send_sse_response(text, model)
|
||||
@@ -215,6 +243,43 @@ def _safe_error_body(exc: urllib.error.HTTPError) -> dict:
|
||||
return body if isinstance(body, dict) else {}
|
||||
|
||||
|
||||
def _valid_route_nodes(route_nodes: object) -> bool:
|
||||
if not isinstance(route_nodes, list):
|
||||
return False
|
||||
for node in route_nodes:
|
||||
if not isinstance(node, dict):
|
||||
return False
|
||||
if not isinstance(node.get("endpoint"), str) or not node["endpoint"]:
|
||||
return False
|
||||
wallet_address = node.get("wallet_address")
|
||||
if wallet_address is not None and not isinstance(wallet_address, str):
|
||||
return False
|
||||
try:
|
||||
shard_start = int(node["shard_start"])
|
||||
shard_end = int(node["shard_end"])
|
||||
except (KeyError, TypeError, ValueError):
|
||||
return False
|
||||
if shard_start < 0 or shard_end < shard_start:
|
||||
return False
|
||||
node["shard_start"] = shard_start
|
||||
node["shard_end"] = shard_end
|
||||
return True
|
||||
|
||||
|
||||
def _api_key_from_authorization(header: str | None) -> str | None:
|
||||
if not header:
|
||||
return None
|
||||
scheme, _, token = header.partition(" ")
|
||||
if scheme.lower() != "bearer" or not token.strip():
|
||||
return None
|
||||
return token.strip()
|
||||
|
||||
|
||||
def _estimate_token_count(messages: object, completion_text: str) -> int:
|
||||
"""Request-token placeholder until shard nodes return tokenizer usage."""
|
||||
return 1
|
||||
|
||||
|
||||
def _post_json(url: str, payload: dict, timeout: float = 5.0) -> dict:
|
||||
data = json.dumps(payload).encode()
|
||||
req = urllib.request.Request(url, data=data, headers={"Content-Type": "application/json"}, method="POST")
|
||||
@@ -246,6 +311,8 @@ class GatewayServer:
|
||||
inference_route: list[str] | None = None,
|
||||
tracker_url: str | None = None,
|
||||
model_presets: list[str] | None = None,
|
||||
contracts: Any | None = None,
|
||||
minimum_stake: int = 0,
|
||||
host: str = "127.0.0.1",
|
||||
port: int = 0,
|
||||
):
|
||||
@@ -263,6 +330,8 @@ class GatewayServer:
|
||||
self._inference_route: list[str] | None = inference_route
|
||||
self._tracker_url: str | None = tracker_url.rstrip("/") if tracker_url is not None else None
|
||||
self._model_presets: list[str] | None = model_presets
|
||||
self._contracts = contracts
|
||||
self._minimum_stake = minimum_stake
|
||||
self._host = host
|
||||
self._requested_port = port
|
||||
self._server: _GatewayHTTPServer | None = None
|
||||
@@ -279,6 +348,8 @@ class GatewayServer:
|
||||
self._inference_route,
|
||||
self._tracker_url,
|
||||
self._model_presets,
|
||||
self._contracts,
|
||||
self._minimum_stake,
|
||||
)
|
||||
self.port = self._server.server_address[1]
|
||||
self._thread = threading.Thread(target=self._server.serve_forever, daemon=True)
|
||||
|
||||
Reference in New Issue
Block a user