ailiteracynepal 🇳🇵
पाठ आकार

अध्याय १ · खण्ड II · 22 मिनेट

एक मोडेललाई API मा wrap गर्ने

एक notebook र प्रणाली बीचको सबैभन्दा सानो, सबैभन्दा ईमानदार पुल: एक FastAPI server जुन request स्वीकार गर्दछ, एक मोडेल call गर्दछ, र जवाफ फर्काउँछ। बीस पंक्ति Python, र यसले तपाईंको काम गर्ने क्षमतालाई बदल्दछ।

हरेक deployed AI प्रणाली, जति नै sophisticated होस्, त्यो एउटै सानो धड्किरहेको हृदय छ: एउटा function जुन input स्वीकार गर्दछ, एक मोडेल call गर्दछ, र output फर्काउँछ। त्यो function लाई HTTP endpoint पछाडि wrap गर्नुहोस् र तपाईंले software engineering मा सबैभन्दा ठूलो सीमा पार गर्नुभयो — तपाईंको कोडबाट serving कोड सम्म। यो खण्ड सबैभन्दा सानो working उदाहरण हो, बीस ईमानदार पंक्ति Python मा।

FastAPI पाँच लाइनमा

FastAPI Python HTTP सेवाहरूको लागि modern standard हो। यो छिटो छ, यो सानो छ, र यसका राम्रा defaults छन्। स्थापना गर्नुहोस्:

pip install fastapi uvicorn anthropic python-dotenv

सम्भावित सबैभन्दा सानो service:

# service.py
from fastapi import FastAPI

app = FastAPI()

@app.get("/hello")
def hello():
    return {"message": "Namaste from the API."}

चलाउनुहोस्:

uvicorn service:app --reload

एउटा browser मा http://localhost:8000/hello खोल्नुहोस्। यसले प्रतिक्रिया दिन्छ। त्यो एउटा service हो — एक सानो program जुन port मा HTTP requests को लागि सुन्दछ र प्रतिक्रियाहरू फर्काउँछ।

--reload flag ले यसलाई कोड परिवर्तनमा restart गर्दछ; production मा यसलाई हटाउनुहोस्।

मोडेल थप्ने

अब real कुरा: एउटा endpoint जुन नेपाली अनुच्छेद स्वीकार गर्दछ र सारांश फर्काउँछ।

# service.py
import os
from fastapi import FastAPI
from pydantic import BaseModel
from anthropic import Anthropic
from dotenv import load_dotenv

load_dotenv()
app = FastAPI()
client = Anthropic()


class SummariseRequest(BaseModel):
    text: str
    max_sentences: int = 2


class SummariseResponse(BaseModel):
    summary: str
    input_length: int


@app.post("/summarise", response_model=SummariseResponse)
def summarise(req: SummariseRequest):
    prompt = (
        f"Summarise the following text in {req.max_sentences} sentences. "
        f"Keep the summary in the same language as the input.\n\n"
        f"Text:\n{req.text}"
    )

    response = client.messages.create(
        model="claude-3-5-sonnet-latest",
        max_tokens=300,
        messages=[{"role": "user", "content": prompt}],
    )

    return SummariseResponse(
        summary=response.content[0].text,
        input_length=len(req.text),
    )

Command line बाट परीक्षण गर्नुहोस्:

curl -X POST http://localhost:8000/summarise \
  -H "Content-Type: application/json" \
  -d '{"text": "काठमाडौँ नेपालको राजधानी हो। यो शहर बागमती प्रदेशमा पर्दछ र नेपालको सबैभन्दा ठूलो शहर हो। यहाँ २५ लाख भन्दा बढी मानिस बस्दछन्।", "max_sentences": 1}'

प्रतिक्रिया:

{
  "summary": "काठमाडौँ बागमती प्रदेशमा अवस्थित नेपालको राजधानी र सबैभन्दा ठूलो शहर हो जहाँ २५ लाख भन्दा बढी मानिस बस्दछन्।",
  "input_length": 138
}

बीस पंक्ति। एक काम गर्ने AI service।

हरेक टुक्रा किन महत्त्वपूर्ण छ

बिस्तारै जाऔँ र प्रत्येक टुक्राले वास्तवमा के गर्दछ त्यो बुझौँ।

BaseModel र Pydantic। FastAPI ले request र response आकारहरू परिभाषित गर्न Pydantic प्रयोग गर्दछ। JSON SummariseRequest सँग मेल नखाने कुनै पनि request स्वचालित रूपमा 422 error सँग अस्वीकार गरिन्छ — तपाईंको कोड चल्न अघि। तपाईंले validation निःशुल्क पाउनुहुन्छ।

Typed inputs। text: str को मतलब API गैर-string inputs अस्वीकार गर्दछ। max_sentences: int = 2 को मतलब field वैकल्पिक हो (defaults to 2) तर दिइएमा integer हुनुपर्दछ। यो surface-area constraint ले bugs को पूरै classes लाई रोक्दछ।

response_model प्रतिक्रिया type घोषणा गर्नुले दुई प्रभाव पार्दछ: यसले तपाईंले फर्काएको कुरालाई validate गर्दछ, र यसले स्वचालित OpenAPI docs उत्पादन गर्दछ। http://localhost:8000/docs भ्रमण गर्नुहोस् र तपाईंले एक interactive Swagger UI देख्नुहुनेछ जहाँ जो कोहीले पनि endpoint परीक्षण गर्न सक्दछ। यो FastAPI को superpower हो।

प्रति endpoint एक जिम्मेवारी। /summarise ले एउटा काम गर्दछ। जब तपाईं features (translate, extract entities, classify) थप्नुहुन्छ, प्रत्येक आफ्नै endpoint बन्दछ। यसले कोड पठनीय राख्दछ र प्रत्येक URL लाई isolation मा testable बनाउँछ।

त्रुटिहरू सही रूपमा सम्हाल्ने

माथिको naive संस्करण मोडेल call असफल हुँदा ungracefully असफल हुन्छ। Fix:

from fastapi import HTTPException
from anthropic import APIError

@app.post("/summarise", response_model=SummariseResponse)
def summarise(req: SummariseRequest):
    if not req.text.strip():
        raise HTTPException(status_code=400, detail="Text cannot be empty.")

    if len(req.text) > 10_000:
        raise HTTPException(status_code=400, detail="Text is too long (max 10,000 chars).")

    try:
        response = client.messages.create(
            model="claude-3-5-sonnet-latest",
            max_tokens=300,
            messages=[{"role": "user", "content": _build_prompt(req)}],
        )
    except APIError as e:
        raise HTTPException(status_code=502, detail=f"Model provider error: {e}")

    return SummariseResponse(
        summary=response.content[0].text,
        input_length=len(req.text),
    )

अब तीन कुराहरू:

  1. सीमाहरूलाई validate गर्नुहोस्। Empty र oversize input कुनै पनि मोडेल call हुनुभन्दा पहिले स्पष्ट 400 errors सँग अस्वीकार हुन्छ।
  2. Provider errors लाई convert गर्नुहोस्। एउटा Anthropic API असफलता caller को लागि एक स्वच्छ 502 बन्दछ — तिनीहरूले stack traces देख्दैनन्।
  3. आवाजका साथ असफल हुनुहोस्, सफा असफल हुनुहोस्। Errors वर्णनात्मक detail सँग HTTP status codes हुन्। कुनै आधा-जवाफ छैन, कुनै भ्रमित clients छैनन्।

यो एक सानो pattern — validate → try → catch → convert — हरेक राम्रो व्यवहार गर्ने endpoint को आकार हो।

Rate limiting, संक्षेपमा

एउटा public endpoint जुन LLM लाई rate limits बिना कल गर्दछ, त्यो आगोमा wallet हो। सबैभन्दा सरल defense:

from slowapi import Limiter
from slowapi.util import get_remote_address
from slowapi.errors import RateLimitExceeded

limiter = Limiter(key_func=get_remote_address)
app.state.limiter = limiter

@app.post("/summarise", response_model=SummariseResponse)
@limiter.limit("10/minute")
def summarise(request: Request, req: SummariseRequest):
    ...

प्रति IP दस requests प्रति मिनेट। एक वास्तविक production service ले per-user (एक पटक तपाईंसँग auth छ भने), per-plan, र per-endpoint limits चाहनेछ — तर दिन 1 मा, यसले wallet जल्नबाट रोक्दछ।

हामी Chapter 2 मा costs र rate limiting मा उचित रूपमा फर्कनेछौँ।

Service, चलिरहेको

तपाईंको directory अब यस्तो देखिन्छ:

my-service/
├── service.py        # the code above
├── .env              # OPENAI_API_KEY, ANTHROPIC_API_KEY
├── requirements.txt  # fastapi uvicorn anthropic ...

Development मा चलाउनुहोस्:

uvicorn service:app --reload --port 8000

Production को लागि, --reload छाड्नुहोस् र एउटा supervisor (systemd, Docker, वा Fly.io जस्तै platform) लाई process alive राख्न दिनुहोस्:

uvicorn service:app --host 0.0.0.0 --port 8000 --workers 4

--workers 4 ले समवर्ती requests सम्हाल्न चार worker processes spawn गर्दछ — Python को GIL को मतलब एकल worker ले एक पटकमा एक request सर्भ गर्दछ, तर चार workers ले तपाईंलाई चार समानान्तर सर्भ गर्न दिन्छ। सामान्य hardware मा, यो प्रति सेकेन्ड दस requests को लागि पर्याप्त छ, जुन सानो Nepal-scale service को लागि पर्याप्त छ।

हामीले के बनायौँ

बीस पंक्ति production-shaped Python। यसले:

  • Notebook होइन, process को रूपमा निरन्तर चल्दछ।
  • कहीँ बाट पनि HTTP requests स्वीकार गर्दछ।
  • सीमामा input validate गर्दछ।
  • एक मोडेल call गर्दछ र प्रतिक्रिया फर्काउँछ।
  • Errors लाई स्वच्छ रूपमा सम्हाल्दछ।
  • Abuse लाई rate-limit गर्दछ।
  • स्वचालित API docs छ।
  • धेरै workers मा scale गर्न सकिन्छ।

त्यो एक real service हो। ठूलो होइन, fancy होइन, तर real। अर्को खण्ड जहाँ यो जान्दछ — अन्य मानिसहरू पुग्न सक्ने computer मा यो कोड कसरी चलाउने।

आफ्नो बुझाइ जाँच्नुहोस्

Quick check

FastAPI services मा input handling को लागि Pydantic (BaseModel मार्फत) किन standard हो?

Quick check

तपाईंको service ले नेपाली अनुच्छेद summarisation सम्हाल्दछ। एक प्रयोगकर्ताले 5MB अनुच्छेदहरू पठाउन थाल्दछन्। सही प्रतिक्रिया के हो?

अब के आउँछ

Service तपाईंको laptop मा काम गर्दछ। त्यो सानो संसार हो। खण्ड 1.3 मध्यम-आकारको संसार हो: यसलाई एउटा computer मा चलाउने जहाँ सबैले पुग्न सक्दछन् — real infrastructure मा, real URL द्वारा addressable, real deployment। हरेक AI उत्पादको दिन-एकको बाधा, ईमानदार बनाइयो।