Jeevant10 commited on
Commit
5ff4fb2
Β·
1 Parent(s): 5da4724

lets run this

Browse files
Files changed (4) hide show
  1. .dockerignore +43 -0
  2. Dockerfile +53 -0
  3. app.py +61 -16
  4. src/textSummarizer/pipeline/prediction.py +30 -16
.dockerignore ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ── Version control ───────────────────────────────────────────────────────────
2
+ .git
3
+ .gitignore
4
+
5
+ # ── Python bytecode ───────────────────────────────────────────────────────────
6
+ __pycache__/
7
+ *.pyc
8
+ *.pyo
9
+ *.pyd
10
+ *.egg-info/
11
+ dist/
12
+ build/
13
+
14
+ # ── Virtual environments ──────────────────────────────────────────────────────
15
+ .venv/
16
+ venv/
17
+ env/
18
+
19
+ # ── Notebooks / research ──────────────────────────────────────────────────────
20
+ research/
21
+ *.ipynb
22
+ .ipynb_checkpoints/
23
+
24
+ # ── Logs ──────────────────────────────────────────────────────────────────────
25
+ logs/
26
+ *.log
27
+
28
+ # ── Training data (large – not needed at inference time) ──────────────────────
29
+ artifacts/data_ingestion/
30
+ artifacts/data_transformation/
31
+ artifacts/data_validation/
32
+
33
+ # ── Intermediate training checkpoints (only final model is needed) ────────────
34
+ artifacts/model_trainer/checkpoint-*/
35
+
36
+ # ── Evaluation outputs (optional, not needed to serve) ────────────────────────
37
+ artifacts/model_evaluation/
38
+
39
+ # ── Misc ──────────────────────────────────────────────────────────────────────
40
+ .env
41
+ .env.*
42
+ *.DS_Store
43
+ Thumbs.db
Dockerfile CHANGED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ── Base image ────────────────────────────────────────────────────────────────
2
+ # Use slim Python 3.11 image to keep the final image as small as possible.
3
+ FROM python:3.11-slim
4
+
5
+ # ── System dependencies ───────────────────────────────────────────────────────
6
+ RUN apt-get update && apt-get install -y --no-install-recommends \
7
+ build-essential \
8
+ git \
9
+ && rm -rf /var/lib/apt/lists/*
10
+
11
+ # ── Working directory ─────────────────────────────────────────────────────────
12
+ WORKDIR /app
13
+
14
+ # ── Install Python dependencies ───────────────────────────────────────────────
15
+ # Copy only the files needed for the package install first so Docker can cache
16
+ # this expensive layer and only rebuild it when requirements change.
17
+ COPY requirements.txt setup.py README.md ./
18
+ COPY src/ ./src/
19
+
20
+ RUN pip install --no-cache-dir --upgrade pip \
21
+ && pip install --no-cache-dir -r requirements.txt
22
+
23
+ # ── Copy the rest of the application ─────────────────────────────────────────
24
+ COPY config/ ./config/
25
+ COPY params.yaml .
26
+ COPY app.py .
27
+ COPY main.py .
28
+
29
+ # ── Copy trained model artifacts ──────────────────────────────────────────────
30
+ # Only the inference artefacts are needed at runtime – skip training data.
31
+ COPY artifacts/model_trainer/pegasus-samsum-model/ \
32
+ ./artifacts/model_trainer/pegasus-samsum-model/
33
+ COPY artifacts/model_trainer/tokenizer/ \
34
+ ./artifacts/model_trainer/tokenizer/
35
+
36
+ # ── Non-root user (security best practice) ───────────────────────────────────
37
+ RUN useradd -m -u 1000 appuser \
38
+ && chown -R appuser:appuser /app
39
+ USER appuser
40
+
41
+ # ── Port ──────────────────────────────────────────────────────────────────────
42
+ # β€’ Hugging Face Spaces: must be 7860
43
+ # β€’ Render / Railway / Fly.io: inject PORT env-var and the app picks it up
44
+ # β€’ Oracle Cloud / local: default falls back to 7860 as well
45
+ EXPOSE 7860
46
+
47
+ # ── Health check ──────────────────────────────────────────────────────────────
48
+ # Docker / orchestrators will mark the container unhealthy if /health fails.
49
+ HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
50
+ CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:${PORT:-7860}/health')"
51
+
52
+ # ── Start server ──────────────────────────────────────────────────────────────
53
+ CMD ["python", "app.py"]
app.py CHANGED
@@ -1,20 +1,60 @@
1
- from fastapi import FastAPI
 
 
2
  import uvicorn
3
  import subprocess
4
  import sys
 
5
  from starlette.responses import RedirectResponse
6
  from fastapi.responses import Response
7
  from textSummarizer.pipeline.prediction import PredictionPipeline
8
 
9
- app = FastAPI(title="AI Text Summarizer", version="1.0.0")
 
10
 
11
- @app.get("/", tags=["authentication"] )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  async def index():
13
  return RedirectResponse(url="/docs")
14
 
15
 
16
- @app.get("/train")
 
 
 
 
 
 
17
  async def training():
 
18
  try:
19
  result = subprocess.run(
20
  [sys.executable, "main.py"],
@@ -26,18 +66,23 @@ async def training():
26
  except subprocess.TimeoutExpired:
27
  return Response("Training timed out!", status_code=504)
28
  except Exception as e:
29
- return Response(f"Error Occurred! {e}", status_code=500)
30
-
31
-
32
- @app.post("/predict")
33
- async def predict_route(text):
 
 
 
34
  try:
35
-
36
- obj = PredictionPipeline()
37
- text = obj.predict(text)
38
- return text
39
  except Exception as e:
40
- raise e
41
-
 
 
42
  if __name__ == "__main__":
43
- uvicorn.run(app, host="0.0.0.0", port=8000)
 
 
 
1
+ from contextlib import asynccontextmanager
2
+ from fastapi import FastAPI, HTTPException
3
+ from pydantic import BaseModel
4
  import uvicorn
5
  import subprocess
6
  import sys
7
+ import os
8
  from starlette.responses import RedirectResponse
9
  from fastapi.responses import Response
10
  from textSummarizer.pipeline.prediction import PredictionPipeline
11
 
12
+ # ── Shared state ──────────────────────────────────────────────────────────────
13
+ _pipeline: PredictionPipeline | None = None
14
 
15
+
16
+ @asynccontextmanager
17
+ async def lifespan(app: FastAPI):
18
+ """Load the model once at startup and keep it in memory."""
19
+ global _pipeline
20
+ _pipeline = PredictionPipeline()
21
+ _pipeline.load_model() # pre-warms tokenizer + model into RAM
22
+ yield
23
+ _pipeline = None # clean up on shutdown
24
+
25
+
26
+ app = FastAPI(
27
+ title="AI Text Summarizer",
28
+ version="1.0.0",
29
+ description="Abstractive text summarization powered by Pegasus/T5.",
30
+ lifespan=lifespan,
31
+ )
32
+
33
+
34
+ # ── Request / Response schemas ────────────────────────────────────────────────
35
+ class SummarizeRequest(BaseModel):
36
+ text: str
37
+
38
+
39
+ class SummarizeResponse(BaseModel):
40
+ summary: str
41
+
42
+
43
+ # ── Routes ────────────────────────────────────────────────────────────────────
44
+ @app.get("/", tags=["root"])
45
  async def index():
46
  return RedirectResponse(url="/docs")
47
 
48
 
49
+ @app.get("/health", tags=["health"])
50
+ async def health_check():
51
+ """Lightweight endpoint for UptimeRobot / keep-alive pings."""
52
+ return {"status": "healthy", "model_loaded": _pipeline is not None}
53
+
54
+
55
+ @app.get("/train", tags=["training"])
56
  async def training():
57
+ """Kick off the full training pipeline (long-running)."""
58
  try:
59
  result = subprocess.run(
60
  [sys.executable, "main.py"],
 
66
  except subprocess.TimeoutExpired:
67
  return Response("Training timed out!", status_code=504)
68
  except Exception as e:
69
+ return Response(f"Error: {e}", status_code=500)
70
+
71
+
72
+ @app.post("/predict", response_model=SummarizeResponse, tags=["inference"])
73
+ async def predict_route(request: SummarizeRequest):
74
+ """Summarize the provided text."""
75
+ if _pipeline is None:
76
+ raise HTTPException(status_code=503, detail="Model is not loaded yet.")
77
  try:
78
+ summary = _pipeline.predict(request.text)
79
+ return SummarizeResponse(summary=summary)
 
 
80
  except Exception as e:
81
+ raise HTTPException(status_code=500, detail=str(e))
82
+
83
+
84
+ # ── Entry-point ───────────────────────────────────────────────────────────────
85
  if __name__ == "__main__":
86
+ # PORT env-var lets each platform (HF Spaces=7860, Render=10000, etc.) inject its own port
87
+ port = int(os.environ.get("PORT", 7860))
88
+ uvicorn.run(app, host="0.0.0.0", port=port)
src/textSummarizer/pipeline/prediction.py CHANGED
@@ -1,23 +1,37 @@
1
  from textSummarizer.config.configuration import ConfigurationManager
 
2
  from transformers import AutoTokenizer
3
- from transformers import pipeline
 
4
 
5
  class PredictionPipeline:
 
 
 
 
6
  def __init__(self):
7
  self.config = ConfigurationManager().get_model_evaluation_config()
8
-
9
-
10
- def predict(self, text):
11
- tokenizer = AutoTokenizer.from_pretrained(self.config.tokenizer_path)
12
- gen_kwargs = {"length_penalty": 0.8, "num_beams": 8 , "max_length":128 }
13
-
14
- pipe = pipeline('summarization', model=self.config.model_path, tokenizer=tokenizer)
15
-
16
- print("Dialogue:")
17
- print(text)
18
-
19
- output = pipe(text, **gen_kwargs)[0]['summary_text']
20
- print("\nModel Summary:")
21
- print(output)
22
-
 
 
 
 
 
 
 
 
23
  return output
 
1
  from textSummarizer.config.configuration import ConfigurationManager
2
+ from textSummarizer.logging import logger
3
  from transformers import AutoTokenizer
4
+ from transformers import pipeline as hf_pipeline
5
+
6
 
7
  class PredictionPipeline:
8
+ """Wraps the summarisation model. Call load_model() once to pre-warm,
9
+ then call predict() for every inference request. The pipeline object is
10
+ cached on the instance so the model is only loaded from disk once."""
11
+
12
  def __init__(self):
13
  self.config = ConfigurationManager().get_model_evaluation_config()
14
+ self._pipe = None # lazy-loaded / pre-warmed via load_model()
15
+
16
+ # ------------------------------------------------------------------
17
+ def load_model(self) -> None:
18
+ """Load the tokenizer and model into memory (call once at startup)."""
19
+ logger.info("Loading summarisation model from %s", self.config.model_path)
20
+ tokenizer = AutoTokenizer.from_pretrained(str(self.config.tokenizer_path))
21
+ self._pipe = hf_pipeline(
22
+ "summarization",
23
+ model=str(self.config.model_path),
24
+ tokenizer=tokenizer,
25
+ )
26
+ logger.info("Model loaded and ready.")
27
+
28
+ # ------------------------------------------------------------------
29
+ def predict(self, text: str) -> str:
30
+ """Return the summary for *text*. Loads the model on first call if not
31
+ already pre-warmed (useful for standalone / testing usage)."""
32
+ if self._pipe is None:
33
+ self.load_model()
34
+
35
+ gen_kwargs = {"length_penalty": 0.8, "num_beams": 8, "max_length": 128}
36
+ output: str = self._pipe(text, **gen_kwargs)[0]["summary_text"]
37
  return output