Upload folder using huggingface_hub
Browse files- README.md +13 -0
- application.py +66 -0
- client.py +43 -0
- generate_token.py +24 -0
- push_to_hf.py +31 -0
README.md
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Qwen FastAPI Inference API
|
| 2 |
+
|
| 3 |
+
This repository contains a secure, OpenAI-compatible REST API for the **Qwen2.5-0.5B-Instruct** model, built with FastAPI.
|
| 4 |
+
|
| 5 |
+
## Features
|
| 6 |
+
- **OpenAI Compatible**: Implements the `/v1/chat/completions` endpoint.
|
| 7 |
+
- **Secure**: Uses JWT Authentication (HS256) for all inference requests.
|
| 8 |
+
- **Lightweight**: Optimized to run on consumer hardware or free-tier cloud environments like Google Colab.
|
| 9 |
+
|
| 10 |
+
## Structure
|
| 11 |
+
- `application.py`: The main FastAPI server.
|
| 12 |
+
- `client.py`: A test client to verify the API.
|
| 13 |
+
- `generate_token.py`: Utility to generate JWT tokens for authentication.
|
application.py
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import jwt
|
| 2 |
+
import time
|
| 3 |
+
import os
|
| 4 |
+
from datetime import datetime, timedelta
|
| 5 |
+
from fastapi import FastAPI, Depends, HTTPException
|
| 6 |
+
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
| 7 |
+
from pydantic import BaseModel
|
| 8 |
+
from transformers import AutoModelForCausalLM, AutoTokenizer
|
| 9 |
+
import torch
|
| 10 |
+
from dotenv import load_dotenv
|
| 11 |
+
|
| 12 |
+
# --- Load Environment Variables ---
|
| 13 |
+
load_dotenv()
|
| 14 |
+
SECRET_KEY = os.getenv("JWT_SECRET_KEY", "default-fallback-secret")
|
| 15 |
+
ALGORITHM = "HS256"
|
| 16 |
+
MODEL_NAME = "Qwen/Qwen2.5-0.5B-Instruct"
|
| 17 |
+
|
| 18 |
+
security = HTTPBearer()
|
| 19 |
+
|
| 20 |
+
def verify_token(credentials: HTTPAuthorizationCredentials = Depends(security)):
|
| 21 |
+
token = credentials.credentials
|
| 22 |
+
try:
|
| 23 |
+
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
|
| 24 |
+
return payload
|
| 25 |
+
except Exception:
|
| 26 |
+
raise HTTPException(status_code=401, detail="Unauthorized")
|
| 27 |
+
|
| 28 |
+
# --- FastAPI Setup ---
|
| 29 |
+
app = FastAPI()
|
| 30 |
+
tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
|
| 31 |
+
model = AutoModelForCausalLM.from_pretrained(MODEL_NAME, torch_dtype="auto", device_map="auto")
|
| 32 |
+
|
| 33 |
+
class ChatMessage(BaseModel):
|
| 34 |
+
role: str
|
| 35 |
+
content: str
|
| 36 |
+
|
| 37 |
+
class ChatCompletionRequest(BaseModel):
|
| 38 |
+
messages: list[ChatMessage]
|
| 39 |
+
max_tokens: int = 100
|
| 40 |
+
|
| 41 |
+
@app.get("/")
|
| 42 |
+
def read_root():
|
| 43 |
+
return {"message": "Qwen OpenAI-style API is running with .env auth"}
|
| 44 |
+
|
| 45 |
+
@app.post("/v1/chat/completions")
|
| 46 |
+
async def chat_generate(request: ChatCompletionRequest, user=Depends(verify_token)):
|
| 47 |
+
chat_msgs = [msg.dict() for msg in request.messages]
|
| 48 |
+
text = tokenizer.apply_chat_template(chat_msgs, tokenize=False, add_generation_prompt=True)
|
| 49 |
+
model_inputs = tokenizer([text], return_tensors="pt").to(model.device)
|
| 50 |
+
|
| 51 |
+
generated_ids = model.generate(
|
| 52 |
+
**model_inputs,
|
| 53 |
+
max_new_tokens=request.max_tokens
|
| 54 |
+
)
|
| 55 |
+
generated_ids = [output_ids[len(input_ids):] for input_ids, output_ids in zip(model_inputs.input_ids, generated_ids)]
|
| 56 |
+
response = tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]
|
| 57 |
+
|
| 58 |
+
return {
|
| 59 |
+
"id": f"chatcmpl-{int(time.time())}",
|
| 60 |
+
"object": "chat.completion",
|
| 61 |
+
"model": MODEL_NAME,
|
| 62 |
+
"choices": [{
|
| 63 |
+
"message": {"role": "assistant", "content": response},
|
| 64 |
+
"finish_reason": "stop"
|
| 65 |
+
}]
|
| 66 |
+
}
|
client.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import requests
|
| 2 |
+
import argparse
|
| 3 |
+
import sys
|
| 4 |
+
|
| 5 |
+
BASE_URL = "http://127.0.0.1:8000"
|
| 6 |
+
|
| 7 |
+
def test_openai_style(token):
|
| 8 |
+
headers = {"Authorization": f"Bearer {token}"}
|
| 9 |
+
|
| 10 |
+
# Send OpenAI-style Chat Messages
|
| 11 |
+
payload = {
|
| 12 |
+
"messages": [
|
| 13 |
+
{"role": "system", "content": "You are a concise AI assistant."},
|
| 14 |
+
{"role": "user", "content": "What is the main benefit of open-source LLMs?"}
|
| 15 |
+
],
|
| 16 |
+
"max_tokens": 60
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
print(f"π€ Sending Chat Completion request to {BASE_URL}...")
|
| 20 |
+
try:
|
| 21 |
+
response = requests.post(f"{BASE_URL}/v1/chat/completions", json=payload, headers=headers)
|
| 22 |
+
|
| 23 |
+
if response.status_code == 200:
|
| 24 |
+
result = response.json()
|
| 25 |
+
print("\n⨠Model Response:")
|
| 26 |
+
print(result['choices'][0]['message']['content'])
|
| 27 |
+
elif response.status_code == 401:
|
| 28 |
+
print("β Error: 401 Unauthorized. Please check if your token is valid and matches the SECRET_KEY.")
|
| 29 |
+
else:
|
| 30 |
+
print(f"β Error {response.status_code}: {response.text}")
|
| 31 |
+
except Exception as e:
|
| 32 |
+
print(f"β Connection failed: {e}")
|
| 33 |
+
|
| 34 |
+
if __name__ == "__main__":
|
| 35 |
+
parser = argparse.ArgumentParser(description="Test the Qwen API with a JWT token.")
|
| 36 |
+
parser.add_argument("--token", type=str, help="The JWT token generated by generate_token.py")
|
| 37 |
+
args = parser.parse_args()
|
| 38 |
+
|
| 39 |
+
if not args.token:
|
| 40 |
+
print("β Error: Please provide a token using --token <YOUR_TOKEN>")
|
| 41 |
+
sys.exit(1)
|
| 42 |
+
|
| 43 |
+
test_openai_style(args.token)
|
generate_token.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import jwt
|
| 2 |
+
import os
|
| 3 |
+
from datetime import datetime, timedelta
|
| 4 |
+
from dotenv import load_dotenv
|
| 5 |
+
|
| 6 |
+
load_dotenv()
|
| 7 |
+
SECRET_KEY = os.getenv("JWT_SECRET_KEY")
|
| 8 |
+
ALGORITHM = "HS256"
|
| 9 |
+
|
| 10 |
+
def create_token(user_id: str):
|
| 11 |
+
if not SECRET_KEY:
|
| 12 |
+
print("β Error: JWT_SECRET_KEY not found in .env")
|
| 13 |
+
return
|
| 14 |
+
|
| 15 |
+
payload = {
|
| 16 |
+
"sub": user_id,
|
| 17 |
+
"exp": datetime.utcnow() + timedelta(hours=24)
|
| 18 |
+
}
|
| 19 |
+
token = jwt.encode(payload, SECRET_KEY, algorithm=ALGORITHM)
|
| 20 |
+
print(f"β
Token generated for {user_id}:\n{token}")
|
| 21 |
+
return token
|
| 22 |
+
|
| 23 |
+
if __name__ == "__main__":
|
| 24 |
+
create_token("admin_user")
|
push_to_hf.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import argparse
|
| 2 |
+
import os
|
| 3 |
+
from huggingface_hub import HfApi, create_repo
|
| 4 |
+
|
| 5 |
+
def push_api_to_hf(token, repo_name="qwen-api-fastapi"):
|
| 6 |
+
username = "convaiinnovations"
|
| 7 |
+
repo_id = f"{username}/{repo_name}"
|
| 8 |
+
api = HfApi()
|
| 9 |
+
|
| 10 |
+
print(f"π Creating/Accessing repo: {repo_id}...")
|
| 11 |
+
try:
|
| 12 |
+
create_repo(repo_id=repo_id, token=token, repo_type="model", exist_ok=True)
|
| 13 |
+
|
| 14 |
+
print(f"π€ Uploading files from 'api/' folder...")
|
| 15 |
+
api.upload_folder(
|
| 16 |
+
folder_path=".",
|
| 17 |
+
repo_id=repo_id,
|
| 18 |
+
repo_type="model",
|
| 19 |
+
token=token
|
| 20 |
+
)
|
| 21 |
+
print(f"β
Successfully pushed to https://huggingface.co/{repo_id}")
|
| 22 |
+
except Exception as e:
|
| 23 |
+
print(f"β Error: {e}")
|
| 24 |
+
|
| 25 |
+
if __name__ == "__main__":
|
| 26 |
+
parser = argparse.ArgumentParser(description="Push API folder to Hugging Face.")
|
| 27 |
+
parser.add_argument("--token", type=str, required=True, help="Your Hugging Face Write Token")
|
| 28 |
+
parser.add_argument("--repo", type=str, default="qwen-api-fastapi", help="Name of the repo")
|
| 29 |
+
args = parser.parse_args()
|
| 30 |
+
|
| 31 |
+
push_api_to_hf(args.token, args.repo)
|