Skip to content

Instantly share code, notes, and snippets.

@RETr4ce
Last active December 12, 2024 17:37
Show Gist options
  • Select an option

  • Save RETr4ce/b6bfe3e072a985f5148eec0147430211 to your computer and use it in GitHub Desktop.

Select an option

Save RETr4ce/b6bfe3e072a985f5148eec0147430211 to your computer and use it in GitHub Desktop.
import logging
import re
import asyncio
import string
from typing import TypeVar, TypeIs
from dataclasses import dataclass
from typing import Set, Final
from pathlib import Path
from pymongo import MongoClient
from langchain_ollama import ChatOllama
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
T = TypeVar("T", bound=str, default=str)
@dataclass(frozen=True)
class TextAnalysis:
text: str
sentiment: str
summary: str
topic: str
MONGO_URI = "mongodb://localhost:27017"
DATABASE_NAME = "bsky_feed"
COLLECTION_NAME = "posts"
BASE_URL = "http://localhost:11434"
MODEL_NAME = "gemma2"
STOPWORDS_FILE: Final[Path] = Path("./Stopwoorden/NL/STOP.txt")
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
force=True
)
logger = logging.getLogger(__name__)
logging.getLogger('httpx').setLevel(logging.WARNING)
client = MongoClient(MONGO_URI)
db = client[DATABASE_NAME]
collection = db[COLLECTION_NAME]
llm = ChatOllama(
model=MODEL_NAME,
base_url=BASE_URL
)
sentiment_prompt = ChatPromptTemplate.from_messages([
("system", "Je bent een sentimentanalyse model dat teksten classificeert als positief, negatief of neutraal."),
("human", "Beoordeel de volgende tekst en geef alleen 'positief', 'negatief' of 'neutraal' als antwoord:\n\n{text}")
])
summary_prompt = ChatPromptTemplate.from_messages([
("system", """
Je bent een taalmodel dat teksten samenvat en alleen kernwoorden terug geeft.
Analyseer eerst de tekst op:
1. Kernwoorden en thema's
2. Context en onderwerp
3. Specifieke verwijzingen
Geef daarna de meest passende samenvatting terug in maximaal 10 kernwoorden.
voorbeelden samenvatting:
- "Zalig, Roxette, die hoor ik echt graag." => Roxette, favoriete, muziek, hits
- "Bruidsmodezaak Tarwekamp is niet ‘t enige bedrijf op dit adres. Ook G.A.Zorg -zelfde eigenaresse-
laat zich vanuit hier inhuren. Nou en? Ken bedrijf niet. Iha: 1000den zzp era starten zorgbedrijf,
onbevoegd en zonder diploma’s, ouderen en zieken de dupe, veel declaratiefraude." => Bruidsmodezaak, Tarwekamp, G.A.Zorg, eigenaresse, zorgbedrijf, zzp, fraude, zorgverlening, bedrijven, kwetsbaar
- "Bij archeologisch onderzoek in de Merwedekanaalzone in Utrecht, is vorige week de weg naar het Romeinse
fort op het Domplein ontdekt. Het gaat om een aftakking van de limesweg. Zie het bericht bij de Gemeente Utrecht" => archeologen, utrecht, romeinen, limesweg, domplein, onderzoek, ontdekt
- "De vlucht naar voren, dat wat de PVV altijd al als enige optie zag wanneer ze met de werkelijkheid werden geconfronteerd." => PVV, confrontatie, optie, werkelijkheid
- "'Sluimervriend' verkozen tot 'Het ontbreekwoord van het jaar' 2024 door Radio 1-luisteraars" => sluimervriend, ontbreekwoord, 2024, Radio 1, luisteraars, verkiezing
- "Mag Israël op de terreurlijst aub? Of is die lijst alleen voor moslims? Kotsmisselijk word ik van de
hypocrisie bij sommige politici. #bbb #xenogeert #vvd #cu #cda #sgp #schoof" => israël, moslims, terreurlijst, politiek, hypocriet, bbb, vvd, cu, cda, sgp, schoof
- "Fryslân dreigt een provincie te worden waarin het bestuur een draaideur is voor sentimentgedreven
partijen, terwijl juist continuïteit en visie nodig zijn om te bouwen aan de toekomst." => friesland, bestuur, sentiment, continuïteit, toekomst
- "Besluit reguliere #omgevingsvergunning Eerste Moordrechtse Tiendeweg 5 t/m 13 in Gouda" => omgevingsvergunning, moordrechtse tiendeweg, besluit, vergunning, gouda
- "Kan die stotterende CU farizeeër @donceder.bsky.social niet meer aanhoren.
Hij is geboren voor de hel'lees bijbel' daarom zeg ook tegen tot hem ziens in de hel. Want Jezus accepteert geen WOKE farizeeërs.
#TweedeKamer #nieuwsbv #spraakmakers" => CU, farizeeër, woke, tweedekamer
- "Op 11 december 1916 is Perez Prado geboren, bandleider, zanger, pianist en componist.
Perez werd ook wel de ‘King of the mambo’ genoemd. De single ‘Patricia’ komt uit 1958.
Perez is op 14 september 1989 overleden." => perez prado, muzikant, bandleider, patricia, 1958, overleden
- "NIEUWS. Minister Faber van asiel vindt een kamer met een bed voor een statushouder niet sober genoeg als opvang.
Wat ze dan wil is onduidelijk. Stapelbedden …? Straks @eenvandaag" => faber, asiel, opvang, statushouders, politiek, kamer, bed, eenvandaag
"""),
("human", "Vat de volgende tekst samen in maximaal 10 kernwoorden, gescheiden door komma's, zonder nummering, bijvoeglijke naamwoorden, lidwoorden, bijwoorden of hashtags als antwoord:\n\n{text}\"")
])
topic_prompt = ChatPromptTemplate.from_messages([
("system", """
Je bent een onderwerpclassificatiemodel dat teksten classificeert in één van de volgende categorieën:
politiek, sport, technologie, gezondheid, economie, cultuur, entertainment, wetenschap,
buitenland, geweld, milieu, onderwijs, recht, vervoer, religie, kunst, werk, overheid.
Analyseer eerst de tekst op:
1. Kernwoorden en thema's
2. Context en onderwerp
3. Specifieke verwijzingen
Geef daarna alleen de meest passende categorie terug zonder uitleg of extra tekst.
Voorbeelden:
- "De verkiezingen van dit jaar zullen bepalend zijn voor de toekomstige koers van het land." => politiek
- "Het nationale voetbalteam bereikte de finale van het wereldkampioenschap." => sport
- "Nieuwe innovaties in kunstmatige intelligentie veranderen het technologische landschap." => technologie
- "Het internationale congres over klimaatverandering begon vandaag." => milieu
- "Er waren meldingen van geweld tijdens de protesten in de stad." => geweld
"""),
("human", "Classificeer de volgende tekst in één van de genoemde categorieën en geef alleen de categorie als antwoord:\n\n{text}")
])
sentiment_chain = sentiment_prompt | llm | StrOutputParser()
summary_chain = summary_prompt | llm | StrOutputParser()
topic_chain = topic_prompt | llm | StrOutputParser()
def is_valid_sentiment(text: str) -> TypeIs[str]:
return text in ['positief', 'negatief', 'neutraal']
def analyze_sentiment(text: str) -> str:
try:
sentiment = sentiment_chain.invoke({"text": text}).strip().lower()
if is_valid_sentiment(sentiment):
return sentiment
match = re.search(r'\b(positief|negatief|neutraal)\b', sentiment)
return match.group(1).lower() if match else "onbekend"
except Exception as e:
logger.error(f"Error in sentiment analysis: {e}")
return "onbekend"
def create_summary(text: str) -> str:
try:
summary = summary_chain.invoke({"text": text}).strip().lower()
summary_words = summary.split()
return ' '.join(summary_words[:10]) if len(summary_words) > 10 else summary
except Exception as e:
logger.error(f"Error in creating summary: {e}")
return "onbekend"
def analyze_topic(text: str) -> str:
try:
topic = topic_chain.invoke({"text": text}).strip().lower()
valid_topics = {'politiek', 'sport', 'technologie', 'gezondheid', 'economie',
'cultuur', 'entertainment', 'wetenschap', 'buitenland', 'geweld',
'milieu', 'onderwijs', 'recht', 'vervoer', 'religie', 'kunst', "werk", "overheid"}
return topic if topic in valid_topics else "overig"
except Exception as e:
logger.error(f"Error in topic analysis: {e}")
return "overig"
def load_stopwords(file_path: str) -> Set[str]:
try:
return {word.strip().lower() for word in file_path.read_text(encoding="utf-8").splitlines()}
except (FileNotFoundError, PermissionError) as e:
raise RuntimeError(f"Failed to load stopwords from {file_path}: {str(e)}")
stop_words: Final[Set[str]] = load_stopwords(STOPWORDS_FILE)
def remove_punctuation_except_comma(text):
punctuation_without_comma = string.punctuation.replace(',', '')
translator = str.maketrans('', '', punctuation_without_comma)
return text.translate(translator)
async def process_documents():
documents = collection.find()
for doc in documents:
try:
text = doc['commit']['record']['text']
if len(text.split()) <= 5:
continue
analysis = TextAnalysis(
text=text,
sentiment=analyze_sentiment(text),
summary=[word.strip() for word in remove_punctuation_except_comma(create_summary(text)).split(",") if word.strip().lower() not in stop_words and word.strip()],
topic=analyze_topic(text)
)
print(f"Text: {analysis.text}\n"
f"Sentiment: {analysis.sentiment}\n"
f"Summary: {analysis.summary}\n"
f"Topic: {analysis.topic}\n")
except KeyError as e:
logger.error(f"Missing field in document: {e}")
except Exception as e:
logger.error(f"Error processing document: {e}")
if __name__ == "__main__":
asyncio.run(process_documents())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment