summarizer

summarizer
git clone https://git.prime8.dev/summarizer.git
Log | Files | Refs | README | LICENSE

commit ee882aac260fabe2e45b39a8f887dc5b0547db89
parent c18182494ca9e7f877f31cb636257ac19a0a959a
Author: Damian Myrda <damian@prime8.dev>
Date:   Wed, 18 Feb 2026 14:36:41 -0600

fix critical bug and add error handling and graceful shutdown

Diffstat:
Mmain.py | 140+++++++++++++++++++++++++++++++++++++++++++++++--------------------------------
Msummarize/main.py | 92++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------
Mtranscribe/main.py | 108++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------
3 files changed, 221 insertions(+), 119 deletions(-)

diff --git a/main.py b/main.py @@ -1,21 +1,32 @@ -from fastapi import FastAPI, UploadFile -from fastapi.responses import HTMLResponse import os -from aiokafka import AIOKafkaProducer, AIOKafkaConsumer -from uuid import uuid4 as uuid import pickle +import signal +import sys +from contextlib import asynccontextmanager +from uuid import uuid4 as uuid + +from aiokafka import AIOKafkaConsumer, AIOKafkaProducer +from fastapi import FastAPI, HTTPException, UploadFile +from fastapi.responses import HTMLResponse -app = FastAPI() address = os.environ.get("KAFKA_ADDRESS", "localhost:9092") +@asynccontextmanager +async def lifespan(app: FastAPI): + yield + + +app = FastAPI(lifespan=lifespan) + + @app.get("/", response_class=HTMLResponse) async def index(): return HTMLResponse( content=""" <html> <form enctype="multipart/form-data" method="post"> - <input type="file" name="file" /> + <input type="file" name="file" accept="audio/*" /> <button type="submit">Submit</button> </form> </html> @@ -27,54 +38,71 @@ async def index(): async def summarize(file: UploadFile): job_id = str.encode(str(uuid())) - producer = AIOKafkaProducer( - max_request_size=26214400, bootstrap_servers=address - ) - await producer.start() - print("transribing file") - consumer = AIOKafkaConsumer( - "transcription.output", bootstrap_servers=address - ) - await consumer.start() - await producer.send_and_wait( - "transcription.input", - key=job_id, - value=pickle.dumps( - { - "name": file.filename, - "data": await file.read(), - } - ), - ) - text = "" - while True: - message = await consumer.getone() - if message.key == job_id: - text = message.value.decode() - break - await consumer.stop() - - print("summarizing text") - consumer = AIOKafkaConsumer( - "summarization.output", bootstrap_servers=address - ) - await consumer.start() - await producer.send_and_wait( - "summarization.input", key=job_id, value=str.encode(text) - ) + consumer = None + producer = None + + try: + consumer = AIOKafkaConsumer( + "transcription.output", + bootstrap_servers=address, + consumer_timeout_ms=300000, + ) + await consumer.start() + + producer = AIOKafkaProducer( + max_request_size=26214400, bootstrap_servers=address + ) + await producer.start() + + print("transcribing file") + await producer.send_and_wait( + "transcription.input", + key=job_id, + value=pickle.dumps( + { + "name": file.filename, + "data": await file.read(), + } + ), + ) + + text = "" + async for message in consumer: + if message.key == job_id: + text = message.value.decode() + break + + print("summarizing text") + await consumer.stop() + consumer = AIOKafkaConsumer( + "summarization.output", + bootstrap_servers=address, + consumer_timeout_ms=300000, + ) + await consumer.start() + + await producer.send_and_wait( + "summarization.input", key=job_id, value=str.encode(text) + ) + + summary = "" + async for message in consumer: + if message.key == job_id: + summary = message.value.decode() + break + + print("request completed") + return { + "text": text, + "summary": summary, + } + + except Exception as e: + print(f"Error processing request: {e}") + raise HTTPException(status_code=500, detail=str(e)) - summary = "" - while True: - message = await consumer.getone() - if message.key == job_id: - summary = message.value.decode() - break - await consumer.stop() - - await producer.stop() - - print("request completed") - return { - "text": text, - "summary": summary, - } + finally: + if consumer: + await consumer.stop() + if producer: + await producer.stop() diff --git a/summarize/main.py b/summarize/main.py @@ -1,40 +1,74 @@ -import os import asyncio -from aiokafka import AIOKafkaProducer, AIOKafkaConsumer +import os +import signal + +from aiokafka import AIOKafkaConsumer, AIOKafkaProducer from openai import AsyncOpenAI -client = AsyncOpenAI() -address = os.environ.get("KAFKA_ADDRESS", "localhost:9092") +openAIClient = AsyncOpenAI() + +kafkaAddress = os.environ.get("KAFKA_ADDRESS", "localhost:9092") + +running = True + + +def signal_handler(signum, frame): + global running + print(f"Received signal {signum}, shutting down...") + running = False async def summarizer(): - producer = AIOKafkaProducer(bootstrap_servers=address) - await producer.start() - consumer = AIOKafkaConsumer( - "summarization.input", bootstrap_servers=address - ) - await consumer.start() - - while True: - message = await consumer.getone() - text = message.value.decode() - - summarization = await client.chat.completions.create( - messages=[ - { - "role": "user", - "content": "summarize the following:\n" + text, - } - ], - model="gpt-4-turbo", - ) - summary = summarization.choices[0].message.content - await producer.send_and_wait( - "summarization.output", key=message.key, value=str.encode(summary) + global running + + signal.signal(signal.SIGTERM, signal_handler) + signal.signal(signal.SIGINT, signal_handler) + + producer = None + consumer = None + + try: + producer = AIOKafkaProducer(bootstrap_servers=kafkaAddress) + await producer.start() + + consumer = AIOKafkaConsumer( + "summarization.input", bootstrap_servers=kafkaAddress ) + await consumer.start() + + while running: + try: + message = await asyncio.wait_for(consumer.getone(), timeout=1.0) + except asyncio.TimeoutError: + continue + + text = message.value.decode() + + try: + summarization = await openAIClient.chat.completions.create( + messages=[ + { + "role": "user", + "content": "summarize the following:\n" + text, + } + ], + model="gpt-4-turbo", + ) + summary = summarization.choices[0].message.content + except Exception as e: + print(f"Summarization failed: {e}") + summary = f"[ERROR: Summarization failed - {e}]" + + await producer.send_and_wait( + "summarization.output", key=message.key, value=str.encode(summary) + ) - await producer.stop() - await consumer.stop() + finally: + if consumer: + await consumer.stop() + if producer: + await producer.stop() + print("Summarizer shut down") if __name__ == "__main__": diff --git a/transcribe/main.py b/transcribe/main.py @@ -1,45 +1,85 @@ -import os import asyncio -from openai import AsyncOpenAI -from aiokafka import AIOKafkaProducer, AIOKafkaConsumer -import pickle import io +import os +import pickle +import signal +import sys + +from aiokafka import AIOKafkaConsumer, AIOKafkaProducer +from openai import AsyncOpenAI -client = AsyncOpenAI() -address = os.environ.get("KAFKA_ADDRESS", "localhost:9092") +CHUNK_SIZE = 26214400 - 1024 + +openAIClient = AsyncOpenAI() + +kafkaAddress = os.environ.get("KAFKA_ADDRESS", "localhost:9092") + +running = True + + +def signal_handler(signum, frame): + global running + print(f"Received signal {signum}, shutting down...") + running = False async def transcribe(): - producer = AIOKafkaProducer( - max_request_size=26214400, bootstrap_servers=address - ) - await producer.start() - consumer = AIOKafkaConsumer( - "transcription.input", bootstrap_servers=address - ) - await consumer.start() - - while True: - message = await consumer.getone() - file = pickle.loads(message.value) - - text = "" - - CHUNK_SIZE = 26214400 - 1024 - for i in range(0, len(file["data"]), CHUNK_SIZE): - audio_file = io.BytesIO(file["data"][i : i + CHUNK_SIZE]) - audio_file.name = file["name"] - - transcription = await client.audio.transcriptions.create( - model="whisper-1", file=audio_file - ) - text += transcription.text - await producer.send_and_wait( - "transcription.output", key=message.key, value=str.encode(text) + global running + + signal.signal(signal.SIGTERM, signal_handler) + signal.signal(signal.SIGINT, signal_handler) + + producer = None + consumer = None + + try: + producer = AIOKafkaProducer( + max_request_size=26214400, bootstrap_servers=kafkaAddress ) + await producer.start() + + consumer = AIOKafkaConsumer( + "transcription.input", bootstrap_servers=kafkaAddress + ) + await consumer.start() + + while running: + try: + message = await asyncio.wait_for(consumer.getone(), timeout=1.0) + except asyncio.TimeoutError: + continue + + try: + file = pickle.loads(message.value) + except Exception as e: + print(f"Failed to deserialize message: {e}") + continue + + text = "" + + try: + for i in range(0, len(file["data"]), CHUNK_SIZE): + audio_file = io.BytesIO(file["data"][i : i + CHUNK_SIZE]) + audio_file.name = file["name"] + + transcription = await openAIClient.audio.transcriptions.create( + model="whisper-1", file=audio_file + ) + text += transcription.text + except Exception as e: + print(f"Transcription failed: {e}") + text = f"[ERROR: Transcription failed - {e}]" + + await producer.send_and_wait( + "transcription.output", key=message.key, value=str.encode(text) + ) - await producer.stop() - await consumer.stop() + finally: + if consumer: + await consumer.stop() + if producer: + await producer.stop() + print("Transcriber shut down") if __name__ == "__main__":