Lär dig hur du kör en lokal LLM med FastAPI genom att bygga ett flerspråkigt API för sentimentanalys med Aya-23 (8B) och llama-cpp-python på Google Colab. Denna guide vägleder dig genom modellkonfiguration, API-utveckling och publik driftsättning via ngrok. Ingen GPU eller betald API krävs!
Stora språkmodeller (LLM:er) har blivit så vanliga att nästan alla interagerar med dem dagligen. De är otroligt kraftfulla verktyg som gör det möjligt för oss att automatisera komplexa, intelligenta uppgifter som ansågs praktiskt taget omöjliga för bara några år sedan.
I denna praktiska guide kommer vi att använda den kraften för en specifik utmaning: att utföra sentimentanalys på skrapade sociala medietexter för att bestämma innehållets underliggande känslomässiga polaritet (om innehållet är Positivt / Negativt / Neutralt).
Rykteshantering är ett nyckelfokus för alla stora företag. För att göra det behöver företag utföra sentimentanalys på inlägg i sociala medier för att bestämma vilken inverkan de har haft på varumärket. För att uppnå detta ville jag samla in textinnehåll från sociala medier om ett varumärke och analysera dess sentiment. [SEG 6] I detta sökande utforskade jag flera tillvägagångssätt
Klassificeringsmodeller (Maskininlärning)
- Stödvektormaskin (SVM)
- Transformatorer med endast encoder
- distilbert-base-uncased
- cardiffnlp/twitter-xlm-roberta-base-sentiment
- Transformatorer med endast decoder (GPT-stil)
- Aya 23 (En flerspråkig instruktionsjusterad LLM utvecklad av Cohere)
- Bland dessa tillvägagångssätt presterade Aya 23 bäst. Därför är applikationen byggd med Aya 23-modellen för sentimentanalys.
Problem: Flaskhalsen med externa LLM:er
Även om det kan verka enklast att bara ansluta till ett kommersiellt moln-API (som OpenAI eller Anthropic) för att få detta gjort, tar vi ett annat tillvägagångssätt. Text från sociala medier är i sig kaotisk, en rörig kombination av slang, saknad interpunktion, flera språk och emojis. Att skicka massiva strömmar av denna ostrukturerade, potentiellt känsliga data till tredjepartsservrar introducerar två stora hinder för företagsteam.
- Fullständig datasekretess:Att skicka strömmar av skrapad användardata till externa servrar är ofta inte ett alternativ för strikta efterlevnads- och säkerhetsstandarder. Oförutsägbara kostnader:
- Att betala per token för att klassificera tusentals eller miljontals dagliga inlägg dränerar snabbt ingenjörsbudgeten. Lösningen: Att ha LLM:en internt
Genom att själv hosta en lokal LLM får du total kontroll över din miljö, håller din data helt privat samtidigt som du helt eliminerar API-kostnader per förfrågan.
I den här guiden kommer vi att gå igenom hur vårt team byggde ett mycket effektivt, flerspråkigt API för sentimentanalys. Vi kringgick massiva GPU-krav genom att använda
Aya-23 (8B) , en modell med öppna vikter optimerad för flerspråkiga uppgifter, och körde den med hjälp avFastAPI för maximal prestanda. Arkitekturen: Tekniska beslut
För att göra detta oberoende av lokal maskinvara bygger vi denna stack i Google Colab (som tillhandahåller en gratis Linux-miljö).
Här är en uppdelning av motorn som driver det hela
Motorn (llama.cpp):
- Vi använder llama-cpp-python bindning för att exekvera modellen. Denna mycket optimerade C++-port är konstruerad för att köra stora modeller med maximal effektivitet på standard CPU-hårdvara. Modellen (Aya-23 8B GGUF):
- Aya 23 är en forskningsversion med öppna vikter av en instruktionsfinjusterad modell med mycket avancerade flerspråkiga funktioner som passar perfekt med våra krav. Vi valde GGUF-kvantiserade formatet för att drastiskt minska minnesavtrycket, genom att använda 8 trådar och ett kontextfönster på 2048 tokens för att upprätthålla hanterbar resursanvändning. API:et (FastAPI):
- Ett modernt, blixtsnabbt webbramverk utformat för att hantera asynkron Python nativt för högpresterande applikationer. Bryggan (nest_asyncio & pyngrok):
- Dessa viktiga verktyg gör det möjligt för oss att köra Uvicorn webbserver inom Colabs befintliga händelseloop och exponera tjänsten säkert för det publika webben. Implementering
Innan vi dyker in i koden, här är den steg-för-steg-plan vi kommer att följa
Kodförberedelse - Installera nödvändiga Python-paket.
- Importer & Globala variabler - Ställa in vår miljö och konstanter.
- Modellnedladdning - Hämta den kvantiserade Aya-23-modellen från Hugging Face.
- FastAPI-modeller & Logik - Definiera Pydantic-modeller och skapa LLM-prompten.
- FastAPI-applikation - Bygga API-slutpunkterna.
- Publik tunnel - Exponera den lokala servern för internet med ngrok.
- Använda API:et - Testa vår live-slutpunkt med verkliga JSON-nyttolaster.
- Steg 1 : Kodförberedelse
Öppna en notebook i Google Colab och kör
pip install kommandot för att installera alla relevanta paket för projektet.
!pip install -q llama-cpp-python huggingface_hub fastapi uvicorn nest_asyncio pyngrokSteg 2 : Importer och globala variabler
Importera alla nödvändiga Python-bibliotek och definiera globala konstanter som används i hela notebooken, såsom modellvägar och serverkonfiguration.
# Import the json module for working with JSON data.
import json
# Import the logging module for logging messages and events.
import logging
# Import the os module for interacting with the operating system (e.g., environment variables).
import os
# Import Path from pathlib for object-oriented filesystem paths.
from pathlib import Path
# Import List and Literal from typing for type hints.
from typing import List, Literal
# Import nest_asyncio to patch asyncio for nested event loops.
import nest_asyncio
# Import uvicorn, an ASGI server for running FastAPI.
import uvicorn
# Import FastAPI and HTTPException for building the web API.
from fastapi import FastAPI, HTTPException
# Import snapshot_download from huggingface_hub for downloading models from Hugging Face.
from huggingface_hub import snapshot_download
# Import Llama from llama_cpp for interacting with local GGUF models.
from llama_cpp import Llama
# Import BaseModel and Field from pydantic for data validation and settings management.
from pydantic import BaseModel, Field
# Import ngrok from pyngrok for creating public URLs.
from pyngrok import ngrok
# Configure basic logging with INFO level and a specific format.
logging.basicConfig(level=logging.INFO, format='%(asctime)s | %(levelname)s | %(message)s')
# Get a logger instance specific to 'aya-sentiment' for custom logging.
logger = logging.getLogger('aya-sentiment')
# Define the Hugging Face repository ID for the model.
MODEL_REPO_ID = 'bartowski/aya-23-8B-GGUF'
# Define the specific model file name within the repository.
MODEL_FILE = 'aya-23-8B-Q5_K_M.gguf'
# Define the local directory where the model will be stored.
MODEL_DIR = Path('/content/aya')
# Construct the full path to the model file.
MODEL_PATH = MODEL_DIR / MODEL_FILE
# Define the batch size for processing multiple texts at once.
BATCH_SIZE = 20
# Define the port number for the FastAPI application.
PORT = 8000Steg 3 : Modellnedladdning och initiering
Flerspråkiga LLM-modeller som AYA kan ofta vara stora filer, från 2 GB till 8 GB. Därför måste vi se till att vi bara laddar ner modellen när den inte redan finns lokalt
Grattis, din lokala LLM är nu initierad och redo.
# Create the model directory if it doesn't exist.
`parents = True` creates any necessary parent directories,
`exist_ok = True` prevents an error if the directory already exists.
MODEL_DIR.mkdir(parents=True, exist_ok=True)
# Check if the model file already exists locally.
if not MODEL_PATH.exists():
logger.info('Model file not found. Downloading from Hugging Face...')
# Download the model snapshot from the Hugging Face repository.
snapshot_download(
repo_id=MODEL_REPO_ID, # The ID of the repository on Hugging Face.
local_dir=str(MODEL_DIR), # The local directory where the model will be saved.
allow_patterns=MODEL_FILE # Only download files matching this pattern (i.e., the specific GGUF model file).
)
# Initialize the Llama model using llama_cpp.
llm = Llama(
model_path=str(MODEL_PATH), # Path to the downloaded GGUF model file.
n_gpu_layers=0, # Number of GPU layers to offload (0 means CPU only in this case).
n_threads=8, # Number of threads to use for inference.
n_ctx=2048, # The context window size for the model (maximum tokens).
)
# Log a message confirming the LLM has been initialized.
logger.info('LLM initialized from %s', MODEL_PATH)Steg 4 : FastAPI-modeller och inferensfunktion
Detta definierar Pydantic-modellerna som används för att validera inkommande förfrågningsdata och strukturera utgående svarsdata för FastAPI-applikationen. Den innehåller också
_infer_batch funktion, som ansvarar för att ta en batch text, konstruera en prompt för LLM:en, göra inferensanropet och tolka LLM:ens JSON-utdata till ett strukturerat format. Nu har du AYA-modellen som returnerar ett svar för den fråga du gjorde med prompten och indata-nyttolasten.
# Defines a Pydantic model for the request payload of the sentiment analysis endpoint.
class SentimentInput(BaseModel):
sm_content: List[str] = Field(..., min_length=1, description='List of texts') # 'sm_content' = list of strings required, and must have at least 1 item.
# Defines a Pydantic model for a single sentiment prediction response.
class SentimentItem(BaseModel):
text: str # The original text that was analyzed.
sentiment: Literal['positive', 'negative', 'neutral', 'unknown'] # The predicted sentiment
reason: str # A reason or explanation for the predicted sentiment.
# Defines a Pydantic model for how each sentiment result is structured in the response.
class SentimentResponseItem(BaseModel):
sm_content: str # Original content that was sent for analysis.
sentiment_prediction: SentimentItem # The sentiment prediction for that content.
# Defines the overall Pydantic model for the sentiment analysis API response.
class SentimentResponse(BaseModel):
# A list of SentimentResponseItem objects, containing results for all input texts.
sentiment_results: List[SentimentResponseItem]
# Helper function to extract and parse the first JSON object from a raw text string.
def _extract_json_object(raw_text: str) -> dict:
"""Extract first JSON object from model text and parse it."""
# Find the JSON object.
start = raw_text.find('{')
end = raw_text.rfind('}')
# Check if both braces were found and in the correct order.
if start == -1 or end == -1 or end <= start:
# If not, raise an error indicating invalid JSON.
raise ValueError('Model output does not contain a valid JSON object')
# Extract the JSON string and parse it into a Python dictionary.
return json.loads(raw_text[start:end + 1])
# Function to perform sentiment inference for a batch of texts.
def _infer_batch(batch: List[str]) -> List[SentimentItem]:
# Create a list of dictionaries, each containing an index and the text for processing.
indexed_payload = [{'index': i, 'text': text} for i, text in enumerate(batch)]
# Construct the prompt for the LLM, instructing it on the task and desired output format.
prompt = (
'You are a sentiment analysis assistant. ' # System instruction for the LLM's role.
'Classify each input text as positive, negative, or neutral. ' # Task description.
'Return ONLY valid JSON with this shape: ' # Strict output format requirement.
'{"results":[{"index":0,"text":"...","sentiment":"positive|negative|neutral","reason":"..."}]}. ' # Example JSON structure.
'Do not include markdown, tables, or extra text.' # Further output constraints.
f'Inputs: {json.dumps(indexed_payload, ensure_ascii=False)}' # The actual inputs to be analyzed, formatted as JSON.
)
# Call the LLM (llama_cpp model) for chat completion.
response = llm.create_chat_completion(
messages=[
{'role': 'system', 'content': 'You must output strict JSON only.'}, # System message for strict JSON output.
{'role': 'user', 'content': prompt}, # The user's prompt containing the task and inputs.
],
max_tokens=1024, # Maximum number of tokens the model can generate.
temperature=0.1, # Controls randomness; lower values mean more deterministic output.
)
raw = response['choices'][0]['message']['content'] # Extract the raw content of the model's response.
parsed = _extract_json_object(raw) # Parse the raw response to extract the JSON object.
raw_results = parsed.get('results', [])
# Create a dictionary to store parsed results, indexed by their original position.
by_index = {}
# Iterate through each item in the raw results from the model.
for item in raw_results:
idx = item.get('index')
if isinstance(idx, int):
# Get the sentiment and convert to lowercase.
sentiment = str(item.get('sentiment', 'unknown')).lower()
# Validate the sentiment against allowed values; default to 'unknown' if invalid.
if sentiment not in {'positive', 'negative', 'neutral'}:
sentiment = 'unknown'
# Store the parsed sentiment item in the dictionary.
by_index[idx] = SentimentItem(
text=str(item.get('text', batch[idx] if 0 <= idx < len(batch) else '')),
sentiment=sentiment,
reason=str(item.get('reason', 'No reason provided by model'))
)
# Initialize a list to store the final, ordered sentiment results.
results: List[SentimentItem] = []
# Iterate through the original batch of texts with their indices.
for i, text in enumerate(batch):
# Append the parsed result for the current index, or a default 'unknown' if not found.
results.append(
by_index.get(
i,
SentimentItem(text=text, sentiment='unknown', reason='No valid parsed result for this input')
)
)
return results # Return the list of sentiment items.
Steg 5 : FastAPI-applikation
Initierar FastAPI-applikationen och definierar två API-slutpunkter: en /health-slutpunkt för statuskontroller och en /analyze_sentiment-slutpunkt för att utföra sentimentanalys.
# Initialize a FastAPI application with a title and version.
app = FastAPI(title='Aya 23 Sentiment API', version='2.0.0')
# Define a GET endpoint for health checks.
@app.get('/health')
async def health():
return {'status': 'ok'}
# Define a POST endpoint for sentiment analysis.
@app.post('/analyze_sentiment', response_model=SentimentResponse)
async def analyze_sentiment(payload: SentimentInput):
try:
texts = payload.sm_content
sentiment_results: List[SentimentResponseItem] = []
# Process texts in batches to optimize LLM calls.
for i in range(0, len(texts), BATCH_SIZE):
batch = texts[i:i + BATCH_SIZE]
batch_predictions = _infer_batch(batch) # Get sentiment predictions for the current batch.
# Pair original texts with their predictions & add to results
for original_text, prediction in zip(batch, batch_predictions):
sentiment_results.append(
SentimentResponseItem(
sm_content=original_text,
sentiment_prediction=prediction
)
)
# Return the comprehensive sentiment response.
return SentimentResponse(sentiment_results=sentiment_results)
except Exception as e:
logger.exception('Sentiment processing failed')
raise HTTPException(status_code=500, detail=f'Error processing request: {str(e)}')Steg 6 : Tillhandahålla API:et med en publik URL
Detta steg exponerar FastAPI-servern publikt med hjälp av
ngrok och startar uvicorn servern i en separat tråd för att undvika att blockera Colab-miljön. Du måste skapa ett gratis konto hos Ngrok och skaffa authtoken. Grattis, du har nu en publikt tillgänglig REST-tjänst som kör en flerspråkig stor språkmodell med sentimentanalysförmåga.
import threading
token = os.getenv('NGROK_AUTHTOKEN') # Set your secret token in env file
if token:
ngrok.set_auth_token(token)
else:
logger.warning('NGROK_AUTHTOKEN is not set. Public tunnel will fail unless token is configured.')
public_url = ngrok.connect(PORT)
print(f'Public URL: {public_url}')
nest_asyncio.apply() # For Google Colab
def run_uvicorn():
uvicorn.run(app, host='0.0.0.0', port=PORT)
thread = threading.Thread(target=run_uvicorn)
thread.start()
logger.info('FastAPI server started in a background thread.')
Steg 7 : Använda API:et
Nu kan du anropa
“analyze_sentiment” POST-slutpunkten på den ngrok-tunnelade URL:en för att utföra sentimentanalys på valfritt textinnehåll från vilken app som helst.
Slutpunktsinformation
URL = api_url/analyze_sentiment
- Method = POST
- Content-Type = application/json
- Förfrågningskroppen måste vara ett JSON-objekt som innehåller en array av strängar.
Exempel:
Förväntad förfrågningsnyttolast
Förväntat svar
{
"sm_content": [
"I love this product!",
"This is the worst update ever.",
"It is okay, nothing special."
]
}
{
"sentiment_results": [
{
"sm_content": "I love this product!",
"sentiment_prediction": {
"text": "I love this product!",
"sentiment": "positive",
"reason": "The use of positive words like \"love\" indicates a positive sentiment."
}
},
{
"sm_content": "This is the worst update ever.",
"sentiment_prediction": {
"text": "This is the worst update ever.",
"sentiment": "negative",
"reason": "The phrase \"worst update ever\" conveys a highly negative opinion."
}
},
{
"sm_content": "It is okay, nothing special.",
"sentiment_prediction": {
"text": "It is okay, nothing special.",
"sentiment": "neutral",
"reason": "The sentiment is neutral as the statement provides no positive or negative emotion, only a description."
}
}
]
}Slutsats
Sammanfattningsvis sätter detta projekt upp en FastAPI-baserad sentimentanalystjänst med hjälp av en lokal Aya 23 GGUF stor språkmodell.
Här är en sammanfattning av vad vi har gjort
Modellhosting - Vi laddar ner och laddar Aya 23 GGUF-modellen lokalt med hjälp av
- llama-cpp-python för effektiv, offline-inferens.
- API-utveckling – En FastAPI-applikation byggs med Pydantic-modeller för strukturerad in-/utdata, vilket erbjuder en /health slutpunkt och en /analyze_sentiment slutpunkt.
- Sentimentinferens – Kärnlogiken inkluderar batchbearbetning av textinmatningar, att skapa en specifik prompt för LLM:en och att tolka LLM:ens JSON-utdata för att extrahera sentiment (positivt, negativt, neutralt, okänt) och en anledning.
- Offentlig exponering – FastAPI-tjänsten körs på uvicorn i en separat tråd i Colab-miljön och exponeras offentligt med ngrok, vilket ger en bekväm URL för att interagera med API:et utanför Colab.
I grund och botten har du ett funktionellt, lokalt LLM-drivet sentimentanalys-API redo att ta emot text och returnera kategoriserade sentiment.



