feat(runtime): implement single-article consolidation runtime and modularize codebase
This commit is contained in:
@@ -0,0 +1,148 @@
|
||||
"""Minimal provider HTTP adapters for Groq, DeepSeek, and OpenAI-compatible endpoints using httpx."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
import httpx
|
||||
|
||||
|
||||
def _load_env_file() -> None:
|
||||
"""Loads environment variables from .env if present."""
|
||||
for parent in [Path.cwd(), Path(__file__).resolve().parent.parent.parent.parent]:
|
||||
env_file = parent / ".env"
|
||||
if env_file.is_file():
|
||||
try:
|
||||
for line in env_file.read_text(encoding="utf-8").splitlines():
|
||||
line = line.strip()
|
||||
if line and not line.startswith("#") and "=" in line:
|
||||
key, val = line.split("=", 1)
|
||||
key = key.strip()
|
||||
val = val.strip().strip("'\"")
|
||||
if key and key not in os.environ:
|
||||
os.environ[key] = val
|
||||
except Exception:
|
||||
pass
|
||||
break
|
||||
|
||||
|
||||
_load_env_file()
|
||||
|
||||
|
||||
class ProviderAdapter:
|
||||
def __init__(self, provider_name: str, base_url: Optional[str] = None):
|
||||
self.provider_name = provider_name
|
||||
self.base_url = base_url
|
||||
|
||||
async def execute_call(
|
||||
self,
|
||||
model: str,
|
||||
messages: List[Dict[str, str]],
|
||||
temperature: float = 0.0,
|
||||
timeout_seconds: int = 30,
|
||||
response_format: Optional[Dict[str, Any]] = None,
|
||||
) -> Dict[str, Any]:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class GroqAdapter(ProviderAdapter):
|
||||
def __init__(self, base_url: str = "https://api.groq.com/openai/v1"):
|
||||
super().__init__("groq", base_url)
|
||||
self.api_key = os.environ.get("GROQ_API_KEY", "")
|
||||
|
||||
async def execute_call(
|
||||
self,
|
||||
model: str,
|
||||
messages: List[Dict[str, str]],
|
||||
temperature: float = 0.0,
|
||||
timeout_seconds: int = 30,
|
||||
response_format: Optional[Dict[str, Any]] = None,
|
||||
) -> Dict[str, Any]:
|
||||
headers = {
|
||||
"Authorization": f"Bearer {self.api_key or os.environ.get('GROQ_API_KEY', '')}",
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
payload: Dict[str, Any] = {
|
||||
"model": model,
|
||||
"messages": messages,
|
||||
"temperature": temperature,
|
||||
}
|
||||
if response_format:
|
||||
payload["response_format"] = response_format
|
||||
|
||||
async with httpx.AsyncClient(timeout=float(timeout_seconds)) as client:
|
||||
resp = await client.post(
|
||||
f"{self.base_url}/chat/completions", headers=headers, json=payload
|
||||
)
|
||||
resp.raise_for_status()
|
||||
return resp.json()
|
||||
|
||||
|
||||
class DeepSeekAdapter(ProviderAdapter):
|
||||
def __init__(self, base_url: str = "https://api.deepseek.com/v1"):
|
||||
super().__init__("deepseek", base_url)
|
||||
self.api_key = os.environ.get("DEEPSEEK_API_KEY", "")
|
||||
|
||||
async def execute_call(
|
||||
self,
|
||||
model: str,
|
||||
messages: List[Dict[str, str]],
|
||||
temperature: float = 0.0,
|
||||
timeout_seconds: int = 30,
|
||||
response_format: Optional[Dict[str, Any]] = None,
|
||||
) -> Dict[str, Any]:
|
||||
headers = {
|
||||
"Authorization": f"Bearer {self.api_key or os.environ.get('DEEPSEEK_API_KEY', '')}",
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
payload: Dict[str, Any] = {
|
||||
"model": model,
|
||||
"messages": messages,
|
||||
"temperature": temperature,
|
||||
}
|
||||
if response_format:
|
||||
payload["response_format"] = response_format
|
||||
|
||||
async with httpx.AsyncClient(timeout=float(timeout_seconds)) as client:
|
||||
resp = await client.post(
|
||||
f"{self.base_url}/chat/completions", headers=headers, json=payload
|
||||
)
|
||||
resp.raise_for_status()
|
||||
return resp.json()
|
||||
|
||||
|
||||
class OpenAIAdapter(ProviderAdapter):
|
||||
def __init__(self, base_url: Optional[str] = None):
|
||||
url = base_url or os.environ.get("OPENAI_BASE_URL", "https://api.openai.com/v1")
|
||||
super().__init__("openai", url)
|
||||
self.api_key = os.environ.get("OPENAI_API_KEY", "")
|
||||
|
||||
async def execute_call(
|
||||
self,
|
||||
model: str,
|
||||
messages: List[Dict[str, str]],
|
||||
temperature: float = 0.0,
|
||||
timeout_seconds: int = 30,
|
||||
response_format: Optional[Dict[str, Any]] = None,
|
||||
) -> Dict[str, Any]:
|
||||
key = self.api_key or os.environ.get("OPENAI_API_KEY", "")
|
||||
headers = {
|
||||
"Authorization": f"Bearer {key}",
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
payload: Dict[str, Any] = {
|
||||
"model": model,
|
||||
"messages": messages,
|
||||
"temperature": temperature,
|
||||
}
|
||||
if response_format:
|
||||
payload["response_format"] = response_format
|
||||
|
||||
async with httpx.AsyncClient(timeout=float(timeout_seconds)) as client:
|
||||
resp = await client.post(
|
||||
f"{self.base_url}/chat/completions", headers=headers, json=payload
|
||||
)
|
||||
resp.raise_for_status()
|
||||
return resp.json()
|
||||
Reference in New Issue
Block a user