phonegpu-space / sdk /python /m5_client.py
josephrw's picture
Upload folder using huggingface_hub
d958e80 verified
Raw
History Blame Contribute Delete
5.1 kB
"""
M5 Python SDK
"""
import httpx
from typing import List, Dict, Optional, AsyncGenerator
from dataclasses import dataclass
@dataclass
class Message:
role: str
content: str
@dataclass
class ChatChoice:
index: int
message: Dict
finish_reason: str
@dataclass
class Usage:
prompt_tokens: int
completion_tokens: int
total_tokens: int
@dataclass
class ChatCompletion:
id: str
object: str
created: int
model: str
choices: List[ChatChoice]
usage: Optional[Usage] = None
class M5Client:
"""M5 API Client for Python"""
def __init__(self, api_key: str, base_url: str = "https://m5.hf.space"):
self.api_key = api_key
self.base_url = base_url.rstrip("/")
self.headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json"
}
async def chat_completion(
self,
model: str,
messages: List[Message],
temperature: float = 0.7,
max_tokens: Optional[int] = None,
stream: bool = False,
ollama_url: Optional[str] = None
) -> ChatCompletion:
"""Create a chat completion."""
payload = {
"model": model,
"messages": [{"role": m.role, "content": m.content} for m in messages],
"temperature": temperature,
"stream": stream
}
if max_tokens:
payload["max_tokens"] = max_tokens
if ollama_url:
payload["ollama_url"] = ollama_url
async with httpx.AsyncClient() as client:
response = await client.post(
f"{self.base_url}/v1/chat/completions",
headers=self.headers,
json=payload
)
response.raise_for_status()
data = response.json()
return ChatCompletion(
id=data["id"],
object=data["object"],
created=data["created"],
model=data["model"],
choices=[
ChatChoice(
index=c["index"],
message=c["message"],
finish_reason=c["finish_reason"]
)
for c in data["choices"]
],
usage=Usage(**data["usage"]) if "usage" in data else None
)
async def chat_completion_stream(
self,
model: str,
messages: List[Message],
temperature: float = 0.7,
max_tokens: Optional[int] = None,
ollama_url: Optional[str] = None
) -> AsyncGenerator[str, None]:
"""Stream a chat completion."""
payload = {
"model": model,
"messages": [{"role": m.role, "content": m.content} for m in messages],
"temperature": temperature,
"stream": True
}
if max_tokens:
payload["max_tokens"] = max_tokens
if ollama_url:
payload["ollama_url"] = ollama_url
async with httpx.AsyncClient() as client:
async with client.stream(
"POST",
f"{self.base_url}/v1/chat/completions",
headers=self.headers,
json=payload
) as response:
response.raise_for_status()
async for line in response.aiter_lines():
if line.startswith("data: "):
data = line[6:]
if data == "[DONE]":
break
try:
import json
chunk = json.loads(data)
if "choices" in chunk and chunk["choices"]:
delta = chunk["choices"][0].get("delta", {})
if "content" in delta:
yield delta["content"]
except:
pass
async def list_models(self) -> List[Dict]:
"""List available models."""
async with httpx.AsyncClient() as client:
response = await client.get(
f"{self.base_url}/v1/models",
headers=self.headers
)
response.raise_for_status()
data = response.json()
return data.get("data", [])
# Convenience classes
class Chat:
"""Chat completions interface"""
def __init__(self, client: M5Client):
self.client = client
async def create(self, **kwargs) -> ChatCompletion:
return await self.client.chat_completion(**kwargs)
async def create_stream(self, **kwargs) -> AsyncGenerator[str, None]:
async for chunk in self.client.chat_completion_stream(**kwargs):
yield chunk
class M5:
"""Main M5 client"""
def __init__(self, api_key: str, base_url: str = "https://m5.hf.space"):
self.client = M5Client(api_key, base_url)
self.chat = Chat(self.client)