| """
|
| Layer 4: Network Effects
|
| Part of SOVEREIGN PYTHON LLM ENGINE
|
|
|
| Explicit network effect handling with httpx.
|
| All HTTP operations are async and auditable.
|
| """
|
|
|
| from typing import AsyncIterator, Any
|
| import httpx
|
| import json
|
| from datetime import datetime
|
|
|
| from ..core.types import Temperature
|
|
|
|
|
|
|
|
|
|
|
|
|
| class NetworkRuntime:
|
| """
|
| Explicit network effect handler.
|
|
|
| Wraps httpx with explicit timeouts and retry logic.
|
| """
|
|
|
| def __init__(
|
| self,
|
| timeout: float = 60.0,
|
| max_retries: int = 2,
|
| follow_redirects: bool = True
|
| ):
|
| """
|
| Initialize network runtime.
|
|
|
| Args:
|
| timeout: Request timeout in seconds
|
| max_retries: Maximum retry attempts
|
| follow_redirects: Follow HTTP redirects
|
| """
|
| self.timeout = httpx.Timeout(timeout)
|
| self.max_retries = max_retries
|
| self.follow_redirects = follow_redirects
|
|
|
| self.client = httpx.AsyncClient(
|
| timeout=self.timeout,
|
| follow_redirects=follow_redirects
|
| )
|
|
|
| async def get(
|
| self,
|
| url: str,
|
| headers: dict[str, str] | None = None,
|
| params: dict[str, Any] | None = None
|
| ) -> httpx.Response:
|
| """
|
| HTTP GET request.
|
|
|
| Args:
|
| url: Request URL
|
| headers: Optional headers
|
| params: Optional query parameters
|
|
|
| Returns:
|
| HTTP response
|
|
|
| Raises:
|
| httpx.HTTPError: On request failure
|
| """
|
| response = await self.client.get(url, headers=headers, params=params)
|
| response.raise_for_status()
|
| return response
|
|
|
| async def post(
|
| self,
|
| url: str,
|
| data: dict[str, Any] | None = None,
|
| json_data: dict[str, Any] | None = None,
|
| headers: dict[str, str] | None = None
|
| ) -> httpx.Response:
|
| """
|
| HTTP POST request.
|
|
|
| Args:
|
| url: Request URL
|
| data: Form data
|
| json_data: JSON body
|
| headers: Optional headers
|
|
|
| Returns:
|
| HTTP response
|
| """
|
| response = await self.client.post(
|
| url,
|
| data=data,
|
| json=json_data,
|
| headers=headers
|
| )
|
| response.raise_for_status()
|
| return response
|
|
|
| async def post_json(
|
| self,
|
| url: str,
|
| payload: dict[str, Any],
|
| headers: dict[str, str] | None = None
|
| ) -> dict[str, Any]:
|
| """
|
| POST JSON and parse JSON response.
|
|
|
| Args:
|
| url: Request URL
|
| payload: JSON payload
|
| headers: Optional headers
|
|
|
| Returns:
|
| Parsed JSON response
|
| """
|
| response = await self.post(url, json_data=payload, headers=headers)
|
| return response.json()
|
|
|
| async def put(
|
| self,
|
| url: str,
|
| data: dict[str, Any] | None = None,
|
| json_data: dict[str, Any] | None = None,
|
| headers: dict[str, str] | None = None
|
| ) -> httpx.Response:
|
| """HTTP PUT request"""
|
| response = await self.client.put(
|
| url,
|
| data=data,
|
| json=json_data,
|
| headers=headers
|
| )
|
| response.raise_for_status()
|
| return response
|
|
|
| async def delete(
|
| self,
|
| url: str,
|
| headers: dict[str, str] | None = None
|
| ) -> httpx.Response:
|
| """HTTP DELETE request"""
|
| response = await self.client.delete(url, headers=headers)
|
| response.raise_for_status()
|
| return response
|
|
|
| async def stream_get(
|
| self,
|
| url: str,
|
| headers: dict[str, str] | None = None
|
| ) -> AsyncIterator[bytes]:
|
| """
|
| Stream GET response.
|
|
|
| Args:
|
| url: Request URL
|
| headers: Optional headers
|
|
|
| Yields:
|
| Response chunks
|
| """
|
| async with self.client.stream('GET', url, headers=headers) as response:
|
| response.raise_for_status()
|
| async for chunk in response.aiter_bytes():
|
| yield chunk
|
|
|
| async def stream_sse(
|
| self,
|
| url: str,
|
| payload: dict[str, Any],
|
| headers: dict[str, str] | None = None
|
| ) -> AsyncIterator[dict[str, Any]]:
|
| """
|
| Stream Server-Sent Events (SSE).
|
|
|
| Args:
|
| url: Request URL
|
| payload: JSON payload
|
| headers: Optional headers
|
|
|
| Yields:
|
| Parsed SSE events
|
| """
|
| async with self.client.stream('POST', url, json=payload, headers=headers) as response:
|
| response.raise_for_status()
|
|
|
| async for line in response.aiter_lines():
|
|
|
| if line.startswith('data: '):
|
| data_str = line[6:]
|
| try:
|
| yield json.loads(data_str)
|
| except json.JSONDecodeError:
|
| continue
|
|
|
| async def close(self) -> None:
|
| """Close HTTP client"""
|
| await self.client.aclose()
|
|
|
| async def __aenter__(self):
|
| """Context manager entry"""
|
| return self
|
|
|
| async def __aexit__(self, exc_type, exc_val, exc_tb):
|
| """Context manager exit"""
|
| await self.close()
|
|
|
|
|
|
|
|
|
|
|
|
|
| class RetryableHTTPClient:
|
| """HTTP client with automatic retry logic"""
|
|
|
| def __init__(
|
| self,
|
| network: NetworkRuntime,
|
| max_retries: int = 2,
|
| backoff_factor: float = 0.5,
|
| retry_statuses: set[int] | None = None
|
| ):
|
| """
|
| Initialize retryable client.
|
|
|
| Args:
|
| network: Base network runtime
|
| max_retries: Maximum retry attempts
|
| backoff_factor: Backoff multiplier (seconds)
|
| retry_statuses: HTTP statuses to retry (None = 408, 429, 5xx)
|
| """
|
| self.network = network
|
| self.max_retries = max_retries
|
| self.backoff_factor = backoff_factor
|
|
|
| if retry_statuses is None:
|
| self.retry_statuses = {408, 429, 500, 502, 503, 504}
|
| else:
|
| self.retry_statuses = retry_statuses
|
|
|
| async def post_with_retry(
|
| self,
|
| url: str,
|
| payload: dict[str, Any],
|
| headers: dict[str, str] | None = None
|
| ) -> dict[str, Any]:
|
| """
|
| POST with automatic retry.
|
|
|
| Args:
|
| url: Request URL
|
| payload: JSON payload
|
| headers: Optional headers
|
|
|
| Returns:
|
| Parsed JSON response
|
|
|
| Raises:
|
| httpx.HTTPError: After all retries exhausted
|
| """
|
| import asyncio
|
|
|
| last_error = None
|
|
|
| for attempt in range(self.max_retries + 1):
|
| try:
|
| return await self.network.post_json(url, payload, headers)
|
| except httpx.HTTPStatusError as e:
|
| last_error = e
|
|
|
|
|
| if e.response.status_code not in self.retry_statuses:
|
| raise
|
|
|
|
|
| if attempt == self.max_retries:
|
| raise
|
|
|
|
|
| wait_time = self.backoff_factor * (2 ** attempt)
|
| await asyncio.sleep(wait_time)
|
|
|
| except (httpx.ConnectError, httpx.TimeoutException) as e:
|
| last_error = e
|
|
|
|
|
| if attempt == self.max_retries:
|
| raise
|
|
|
|
|
| wait_time = self.backoff_factor * (2 ** attempt)
|
| await asyncio.sleep(wait_time)
|
|
|
|
|
| raise last_error
|
|
|
|
|
|
|
|
|
|
|
|
|
| class LlamaAPIClient:
|
| """
|
| Client for Llama API (from llama-api-python analysis).
|
| """
|
|
|
| def __init__(
|
| self,
|
| api_key: str,
|
| base_url: str = "https://api.llama.com/v1",
|
| network: NetworkRuntime | None = None
|
| ):
|
| """
|
| Initialize Llama API client.
|
|
|
| Args:
|
| api_key: API key
|
| base_url: Base URL for API
|
| network: Network runtime (creates if None)
|
| """
|
| self.api_key = api_key
|
| self.base_url = base_url
|
|
|
| if network is None:
|
| self.network = NetworkRuntime()
|
| else:
|
| self.network = network
|
|
|
| def _auth_headers(self) -> dict[str, str]:
|
| """Get authorization headers"""
|
| return {"Authorization": f"Bearer {self.api_key}"}
|
|
|
| async def generate(
|
| self,
|
| messages: list[dict[str, str]],
|
| model: str = "Llama-3.3-70B-Instruct",
|
| temperature: float = 0.0,
|
| max_tokens: int | None = None
|
| ) -> str:
|
| """
|
| Generate completion.
|
|
|
| Args:
|
| messages: Conversation messages
|
| model: Model ID
|
| temperature: Sampling temperature
|
| max_tokens: Max tokens to generate
|
|
|
| Returns:
|
| Generated text
|
| """
|
| url = f"{self.base_url}/chat/completions"
|
|
|
| payload: dict[str, Any] = {
|
| "messages": messages,
|
| "model": model,
|
| "temperature": temperature
|
| }
|
|
|
| if max_tokens is not None:
|
| payload["max_completion_tokens"] = max_tokens
|
|
|
| headers = self._auth_headers()
|
| response = await self.network.post_json(url, payload, headers)
|
|
|
| return response["completion_message"]["content"]
|
|
|
| async def generate_stream(
|
| self,
|
| messages: list[dict[str, str]],
|
| model: str = "Llama-3.3-70B-Instruct",
|
| temperature: float = 0.0
|
| ) -> AsyncIterator[str]:
|
| """
|
| Generate completion with streaming.
|
|
|
| Yields:
|
| Text chunks
|
| """
|
| url = f"{self.base_url}/chat/completions"
|
|
|
| payload = {
|
| "messages": messages,
|
| "model": model,
|
| "temperature": temperature,
|
| "stream": True
|
| }
|
|
|
| headers = self._auth_headers()
|
|
|
| async for event in self.network.stream_sse(url, payload, headers):
|
| if "event" in event and "delta" in event["event"]:
|
| delta = event["event"]["delta"]
|
| if "text" in delta:
|
| yield delta["text"]
|
|
|
|
|
|
|
|
|
|
|
|
|
| class GenericLLMClient:
|
| """
|
| Generic LLM client (OpenAI-compatible API).
|
| """
|
|
|
| def __init__(
|
| self,
|
| api_key: str,
|
| base_url: str,
|
| network: NetworkRuntime | None = None
|
| ):
|
| self.api_key = api_key
|
| self.base_url = base_url
|
|
|
| if network is None:
|
| self.network = NetworkRuntime()
|
| else:
|
| self.network = network
|
|
|
| def _auth_headers(self) -> dict[str, str]:
|
| return {"Authorization": f"Bearer {self.api_key}"}
|
|
|
| async def generate(
|
| self,
|
| messages: list[dict[str, str]],
|
| model: str,
|
| temperature: float = 0.0,
|
| max_tokens: int | None = None
|
| ) -> str:
|
| """OpenAI-compatible chat completion"""
|
| url = f"{self.base_url}/chat/completions"
|
|
|
| payload: dict[str, Any] = {
|
| "messages": messages,
|
| "model": model,
|
| "temperature": temperature
|
| }
|
|
|
| if max_tokens is not None:
|
| payload["max_tokens"] = max_tokens
|
|
|
| headers = self._auth_headers()
|
| response = await self.network.post_json(url, payload, headers)
|
|
|
| return response["choices"][0]["message"]["content"]
|
|
|
|
|
|
|
|
|
|
|
|
|
| class RateLimiter:
|
| """
|
| Token bucket rate limiter for API requests.
|
| """
|
|
|
| def __init__(self, requests_per_second: float):
|
| """
|
| Initialize rate limiter.
|
|
|
| Args:
|
| requests_per_second: Maximum requests per second
|
| """
|
| self.requests_per_second = requests_per_second
|
| self.interval = 1.0 / requests_per_second
|
| self.last_request_time: float | None = None
|
|
|
| async def acquire(self) -> None:
|
| """
|
| Acquire rate limit token (wait if necessary).
|
| """
|
| import asyncio
|
| import time
|
|
|
| now = time.time()
|
|
|
| if self.last_request_time is not None:
|
| elapsed = now - self.last_request_time
|
| if elapsed < self.interval:
|
| wait_time = self.interval - elapsed
|
| await asyncio.sleep(wait_time)
|
|
|
| self.last_request_time = time.time()
|
|
|
|
|
|
|
|
|
|
|
|
|
| class LoggedNetworkRuntime:
|
| """
|
| Network runtime with request/response logging.
|
| """
|
|
|
| def __init__(self, network: NetworkRuntime, log_path: str | None = None):
|
| """
|
| Initialize logged runtime.
|
|
|
| Args:
|
| network: Base network runtime
|
| log_path: Path to log file (None = no file logging)
|
| """
|
| self.network = network
|
| self.log_path = log_path
|
| self.requests_log: list[dict[str, Any]] = []
|
|
|
| async def post_json(
|
| self,
|
| url: str,
|
| payload: dict[str, Any],
|
| headers: dict[str, str] | None = None
|
| ) -> dict[str, Any]:
|
| """POST with logging"""
|
| start_time = datetime.now()
|
|
|
| try:
|
| response = await self.network.post_json(url, payload, headers)
|
| elapsed = (datetime.now() - start_time).total_seconds()
|
|
|
|
|
| log_entry = {
|
| "timestamp": start_time.isoformat(),
|
| "method": "POST",
|
| "url": url,
|
| "payload": payload,
|
| "response": response,
|
| "elapsed_seconds": elapsed,
|
| "success": True
|
| }
|
|
|
| self.requests_log.append(log_entry)
|
|
|
| return response
|
|
|
| except Exception as e:
|
| elapsed = (datetime.now() - start_time).total_seconds()
|
|
|
|
|
| log_entry = {
|
| "timestamp": start_time.isoformat(),
|
| "method": "POST",
|
| "url": url,
|
| "payload": payload,
|
| "error": str(e),
|
| "elapsed_seconds": elapsed,
|
| "success": False
|
| }
|
|
|
| self.requests_log.append(log_entry)
|
| raise
|
|
|
| def get_logs(self) -> list[dict[str, Any]]:
|
| """Get all logged requests"""
|
| return self.requests_log
|
|
|
| async def save_logs(self) -> None:
|
| """Save logs to file"""
|
| if self.log_path is None:
|
| return
|
|
|
| import aiofiles
|
| from pathlib import Path
|
|
|
| log_path = Path(self.log_path)
|
| log_path.parent.mkdir(parents=True, exist_ok=True)
|
|
|
| async with aiofiles.open(log_path, 'w') as f:
|
| for entry in self.requests_log:
|
| await f.write(json.dumps(entry) + '\n')
|
|
|