Create handler.py
Browse files- handler.py +88 -0
handler.py
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from typing import Dict, List, Any
|
| 3 |
+
import torch
|
| 4 |
+
from unsloth import FastLanguageModel
|
| 5 |
+
|
| 6 |
+
class EndpointHandler:
|
| 7 |
+
def __init__(self, model_dir: str = ""):
|
| 8 |
+
print(f"[DEBUG] Original model_dir: {model_dir}")
|
| 9 |
+
# For this task, we force the model to be loaded from your checkpoint.
|
| 10 |
+
# (Note: In an endpoint, you may want to allow model_dir to be passed in,
|
| 11 |
+
# but here we override it for clarity.)
|
| 12 |
+
model_dir = "RichardLu/mistral7b_aspectsentiment_res"
|
| 13 |
+
print(f"[DEBUG] Using model_dir: {model_dir}")
|
| 14 |
+
|
| 15 |
+
# Inference configuration.
|
| 16 |
+
max_seq_length = 2048
|
| 17 |
+
load_in_4bit = True # Set to True or False as needed.
|
| 18 |
+
|
| 19 |
+
# Load the model and tokenizer from the checkpoint.
|
| 20 |
+
self.model, self.tokenizer = FastLanguageModel.from_pretrained(
|
| 21 |
+
model_name=model_dir,
|
| 22 |
+
max_seq_length=max_seq_length,
|
| 23 |
+
load_in_4bit=load_in_4bit
|
| 24 |
+
)
|
| 25 |
+
print("[DEBUG] Model and tokenizer loaded successfully.")
|
| 26 |
+
|
| 27 |
+
# Switch the model to inference mode.
|
| 28 |
+
FastLanguageModel.for_inference(self.model)
|
| 29 |
+
|
| 30 |
+
# Define the instructabsa instruction text with examples.
|
| 31 |
+
self.instructabsa_instruction = """Definition: The output will be 'positive' if the aspect identified in the sentence contains a positive sentiment. If the sentiment of the identified aspect in the input is negative the answer will be 'negative'.
|
| 32 |
+
Otherwise, the output should be 'neutral'. For aspects which are classified as noaspectterm, the sentiment is none.
|
| 33 |
+
Positive example 1-
|
| 34 |
+
input: With the great variety on the menu , I eat here often and never get bored. The aspect is menu.
|
| 35 |
+
output: positive
|
| 36 |
+
Positive example 2-
|
| 37 |
+
input: Great food, good size menu, great service and an unpretensious setting. The aspect is food.
|
| 38 |
+
output: positive
|
| 39 |
+
Negative example 1-
|
| 40 |
+
input: They did not have mayonnaise, forgot our toast, left out ingredients (ie cheese in an omelet), below hot temperatures and the bacon was so over cooked it crumbled on the plate when you touched it. The aspect is toast.
|
| 41 |
+
output: negative
|
| 42 |
+
Negative example 2-
|
| 43 |
+
input: The seats are uncomfortable if you are sitting against the wall on wooden benches. The aspect is seats.
|
| 44 |
+
output: negative
|
| 45 |
+
Neutral example 1-
|
| 46 |
+
input: I asked for seltzer with lime, no ice. The aspect is seltzer with lime.
|
| 47 |
+
output: neutral
|
| 48 |
+
Neutral example 2-
|
| 49 |
+
input: They wouldnt even let me finish my glass of wine before offering another. The aspect is glass of wine.
|
| 50 |
+
output: neutral
|
| 51 |
+
Now complete the following example-
|
| 52 |
+
"""
|
| 53 |
+
|
| 54 |
+
# Define the Alpaca-style prompt template.
|
| 55 |
+
self.alpaca_prompt = """Below is an instruction that describes a task, paired with an input that provides further context. Write a response that appropriately completes the request.
|
| 56 |
+
### Instruction:
|
| 57 |
+
{}
|
| 58 |
+
### Input:
|
| 59 |
+
{}
|
| 60 |
+
### Response:
|
| 61 |
+
{}"""
|
| 62 |
+
|
| 63 |
+
def __call__(self, data: Dict[str, Any]) -> List[Dict[str, Any]]:
|
| 64 |
+
# Expecting two keys in the payload: 'review' and 'aspect'
|
| 65 |
+
review_text = data.get("review", "")
|
| 66 |
+
aspect = data.get("aspect", "")
|
| 67 |
+
if not review_text or not aspect:
|
| 68 |
+
return [{"error": "Both 'review' and 'aspect' are required."}]
|
| 69 |
+
|
| 70 |
+
# Build the combined input text.
|
| 71 |
+
full_input = f"{review_text} The aspect is {aspect}."
|
| 72 |
+
prompt = self.alpaca_prompt.format(self.instructabsa_instruction, full_input, "")
|
| 73 |
+
|
| 74 |
+
# Set device for inference.
|
| 75 |
+
device = "cuda" if torch.cuda.is_available() else "cpu"
|
| 76 |
+
inputs = self.tokenizer(prompt, return_tensors="pt", truncation=True).to(device)
|
| 77 |
+
|
| 78 |
+
# Generate the output.
|
| 79 |
+
output_ids = self.model.generate(**inputs, max_new_tokens=128)
|
| 80 |
+
output_text = self.tokenizer.decode(output_ids[0], skip_special_tokens=True)
|
| 81 |
+
|
| 82 |
+
# Extract the predicted sentiment.
|
| 83 |
+
if "### Response:" in output_text:
|
| 84 |
+
predicted = output_text.split("### Response:")[-1].strip()
|
| 85 |
+
else:
|
| 86 |
+
predicted = output_text.strip()
|
| 87 |
+
|
| 88 |
+
return [{"predicted": predicted}]
|