mirror of
https://github.com/Monadical-SAS/reflector.git
synced 2025-12-21 04:39:06 +00:00
@@ -17,7 +17,7 @@ WHISPER_NUM_WORKERS: int = 1
|
|||||||
# Translation Model
|
# Translation Model
|
||||||
TRANSLATION_MODEL = "facebook/m2m100_1.2B"
|
TRANSLATION_MODEL = "facebook/m2m100_1.2B"
|
||||||
|
|
||||||
IMAGE_MODEL_DIR = "/root/transcription_models"
|
IMAGE_MODEL_DIR = f"/root/transcription_models/{TRANSLATION_MODEL}"
|
||||||
|
|
||||||
stub = Stub(name="reflector-transcriber")
|
stub = Stub(name="reflector-transcriber")
|
||||||
|
|
||||||
|
|||||||
27
server/poetry.lock
generated
27
server/poetry.lock
generated
@@ -1551,6 +1551,17 @@ files = [
|
|||||||
{file = "ifaddr-0.2.0.tar.gz", hash = "sha256:cc0cbfcaabf765d44595825fb96a99bb12c79716b73b44330ea38ee2b0c4aed4"},
|
{file = "ifaddr-0.2.0.tar.gz", hash = "sha256:cc0cbfcaabf765d44595825fb96a99bb12c79716b73b44330ea38ee2b0c4aed4"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "inflection"
|
||||||
|
version = "0.5.1"
|
||||||
|
description = "A port of Ruby on Rails inflector to Python"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.5"
|
||||||
|
files = [
|
||||||
|
{file = "inflection-0.5.1-py2.py3-none-any.whl", hash = "sha256:f38b2b640938a4f35ade69ac3d053042959b62a0f1076a5bbaa1b9526605a8a2"},
|
||||||
|
{file = "inflection-0.5.1.tar.gz", hash = "sha256:1a29730d366e996aaacffb2f1f1cb9593dc38e2ddd30c91250c6dde09ea9b417"},
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iniconfig"
|
name = "iniconfig"
|
||||||
version = "2.0.0"
|
version = "2.0.0"
|
||||||
@@ -2097,6 +2108,20 @@ files = [
|
|||||||
dev = ["pre-commit", "tox"]
|
dev = ["pre-commit", "tox"]
|
||||||
testing = ["pytest", "pytest-benchmark"]
|
testing = ["pytest", "pytest-benchmark"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "profanityfilter"
|
||||||
|
version = "2.0.6"
|
||||||
|
description = "A universal Python library for detecting and/or filtering profane words."
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
files = [
|
||||||
|
{file = "profanityfilter-2.0.6-py2.py3-none-any.whl", hash = "sha256:1706c080c2364f5bfe217b2330dc35d90e02e4afa0a00ed52d5673c410b45b64"},
|
||||||
|
{file = "profanityfilter-2.0.6.tar.gz", hash = "sha256:ca701e22799526696963415fc36d5e943c168f1917e3c83881ffda6bf5240a30"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
inflection = "*"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "prometheus-client"
|
name = "prometheus-client"
|
||||||
version = "0.17.1"
|
version = "0.17.1"
|
||||||
@@ -3744,4 +3769,4 @@ multidict = ">=4.0"
|
|||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.0"
|
||||||
python-versions = "^3.11"
|
python-versions = "^3.11"
|
||||||
content-hash = "b67b094055950a6b39da80dc7ca26b2a0e1c778f174016a00185d7219a3348b5"
|
content-hash = "a85cb09a0e4b68b29c4272d550e618d2e24ace5f16b707f29e8ac4ce915c1fae"
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ transformers = "^4.32.1"
|
|||||||
prometheus-fastapi-instrumentator = "^6.1.0"
|
prometheus-fastapi-instrumentator = "^6.1.0"
|
||||||
sentencepiece = "^0.1.99"
|
sentencepiece = "^0.1.99"
|
||||||
protobuf = "^4.24.3"
|
protobuf = "^4.24.3"
|
||||||
|
profanityfilter = "^2.0.6"
|
||||||
|
|
||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
[tool.poetry.group.dev.dependencies]
|
||||||
|
|||||||
@@ -6,11 +6,12 @@ from typing import TypeVar
|
|||||||
|
|
||||||
import nltk
|
import nltk
|
||||||
from prometheus_client import Counter, Histogram
|
from prometheus_client import Counter, Histogram
|
||||||
|
from transformers import GenerationConfig
|
||||||
|
|
||||||
from reflector.llm.llm_params import TaskParams
|
from reflector.llm.llm_params import TaskParams
|
||||||
from reflector.logger import logger as reflector_logger
|
from reflector.logger import logger as reflector_logger
|
||||||
from reflector.settings import settings
|
from reflector.settings import settings
|
||||||
from reflector.utils.retry import retry
|
from reflector.utils.retry import retry
|
||||||
from transformers import GenerationConfig
|
|
||||||
|
|
||||||
T = TypeVar("T", bound="LLM")
|
T = TypeVar("T", bound="LLM")
|
||||||
|
|
||||||
@@ -214,6 +215,9 @@ class LLM:
|
|||||||
# Change ( ABC ), [ ABC ], etc. ==> (ABC), [ABC], etc.
|
# Change ( ABC ), [ ABC ], etc. ==> (ABC), [ABC], etc.
|
||||||
pattern = r"(?<=[\[\{\(])\s+|\s+(?=[\]\}\)])"
|
pattern = r"(?<=[\[\{\(])\s+|\s+(?=[\]\}\)])"
|
||||||
title = re.sub(pattern, "", modified_title)
|
title = re.sub(pattern, "", modified_title)
|
||||||
|
# Irrespective of casing changes, the starting letter
|
||||||
|
# of title is always upper-cased
|
||||||
|
title = title[0].upper() + title[1:]
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
reflector_logger.info(
|
reflector_logger.info(
|
||||||
f"Failed to ensure casing on {title=} " f"with exception : {str(e)}"
|
f"Failed to ensure casing on {title=} " f"with exception : {str(e)}"
|
||||||
@@ -221,6 +225,29 @@ class LLM:
|
|||||||
|
|
||||||
return title
|
return title
|
||||||
|
|
||||||
|
def trim_title(self, title: str) -> str:
|
||||||
|
"""
|
||||||
|
List of manual trimming to the title.
|
||||||
|
|
||||||
|
Longer titles are prone to run into A prefix of phrases that don't
|
||||||
|
really add any descriptive information and in some cases, this
|
||||||
|
behaviour can be repeated for several consecutive topics. Trim the
|
||||||
|
titles to maintain quality of titles.
|
||||||
|
"""
|
||||||
|
phrases_to_remove = ["Discussing", "Discussion on", "Discussion about"]
|
||||||
|
try:
|
||||||
|
pattern = (
|
||||||
|
r"\b(?:"
|
||||||
|
+ "|".join(re.escape(phrase) for phrase in phrases_to_remove)
|
||||||
|
+ r")\b"
|
||||||
|
)
|
||||||
|
title = re.sub(pattern, "", title, flags=re.IGNORECASE)
|
||||||
|
except Exception as e:
|
||||||
|
reflector_logger.info(
|
||||||
|
f"Failed to trim {title=} " f"with exception : {str(e)}"
|
||||||
|
)
|
||||||
|
return title
|
||||||
|
|
||||||
async def _generate(
|
async def _generate(
|
||||||
self, prompt: str, gen_schema: dict | None, gen_cfg: dict | None, **kwargs
|
self, prompt: str, gen_schema: dict | None, gen_cfg: dict | None, **kwargs
|
||||||
) -> str:
|
) -> str:
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ class FinalLongSummaryParams(LLMTaskParams):
|
|||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
self._gen_cfg = GenerationConfig(
|
self._gen_cfg = GenerationConfig(
|
||||||
max_new_tokens=800, num_beams=3, do_sample=True, temperature=0.3
|
max_new_tokens=1000, num_beams=3, do_sample=True, temperature=0.3
|
||||||
)
|
)
|
||||||
self._instruct = """
|
self._instruct = """
|
||||||
Take the key ideas and takeaways from the text and create a short
|
Take the key ideas and takeaways from the text and create a short
|
||||||
@@ -65,7 +65,7 @@ class FinalShortSummaryParams(LLMTaskParams):
|
|||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
self._gen_cfg = GenerationConfig(
|
self._gen_cfg = GenerationConfig(
|
||||||
max_new_tokens=1300, num_beams=3, do_sample=True, temperature=0.3
|
max_new_tokens=800, num_beams=3, do_sample=True, temperature=0.3
|
||||||
)
|
)
|
||||||
self._instruct = """
|
self._instruct = """
|
||||||
Take the key ideas and takeaways from the text and create a short
|
Take the key ideas and takeaways from the text and create a short
|
||||||
@@ -116,7 +116,7 @@ class TopicParams(LLMTaskParams):
|
|||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
self._gen_cfg = GenerationConfig(
|
self._gen_cfg = GenerationConfig(
|
||||||
max_new_tokens=550, num_beams=6, do_sample=True, temperature=0.9
|
max_new_tokens=500, num_beams=6, do_sample=True, temperature=0.9
|
||||||
)
|
)
|
||||||
self._instruct = """
|
self._instruct = """
|
||||||
Create a JSON object as response.The JSON object must have 2 fields:
|
Create a JSON object as response.The JSON object must have 2 fields:
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
|
from profanityfilter import ProfanityFilter
|
||||||
from prometheus_client import Counter, Histogram
|
from prometheus_client import Counter, Histogram
|
||||||
|
|
||||||
from reflector.processors.base import Processor
|
from reflector.processors.base import Processor
|
||||||
from reflector.processors.types import AudioFile, Transcript
|
from reflector.processors.types import AudioFile, Transcript
|
||||||
|
|
||||||
@@ -38,6 +40,8 @@ class AudioTranscriptProcessor(Processor):
|
|||||||
self.m_transcript_call = self.m_transcript_call.labels(name)
|
self.m_transcript_call = self.m_transcript_call.labels(name)
|
||||||
self.m_transcript_success = self.m_transcript_success.labels(name)
|
self.m_transcript_success = self.m_transcript_success.labels(name)
|
||||||
self.m_transcript_failure = self.m_transcript_failure.labels(name)
|
self.m_transcript_failure = self.m_transcript_failure.labels(name)
|
||||||
|
self.profanity_filter = ProfanityFilter()
|
||||||
|
self.profanity_filter.set_censor("*")
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
async def _push(self, data: AudioFile):
|
async def _push(self, data: AudioFile):
|
||||||
@@ -56,3 +60,9 @@ class AudioTranscriptProcessor(Processor):
|
|||||||
|
|
||||||
async def _transcript(self, data: AudioFile):
|
async def _transcript(self, data: AudioFile):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def filter_profanity(self, text: str) -> str:
|
||||||
|
"""
|
||||||
|
Remove censored words from the transcript
|
||||||
|
"""
|
||||||
|
return self.profanity_filter.censor(text)
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ API will be a POST request to TRANSCRIPT_URL:
|
|||||||
from time import monotonic
|
from time import monotonic
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
|
|
||||||
from reflector.processors.audio_transcript import AudioTranscriptProcessor
|
from reflector.processors.audio_transcript import AudioTranscriptProcessor
|
||||||
from reflector.processors.audio_transcript_auto import AudioTranscriptAutoProcessor
|
from reflector.processors.audio_transcript_auto import AudioTranscriptAutoProcessor
|
||||||
from reflector.processors.types import AudioFile, Transcript, TranslationLanguages, Word
|
from reflector.processors.types import AudioFile, Transcript, TranslationLanguages, Word
|
||||||
@@ -86,7 +87,7 @@ class AudioTranscriptModalProcessor(AudioTranscriptProcessor):
|
|||||||
if source_language != target_language and target_language in result["text"]:
|
if source_language != target_language and target_language in result["text"]:
|
||||||
translation = result["text"][target_language]
|
translation = result["text"][target_language]
|
||||||
text = result["text"][source_language]
|
text = result["text"][source_language]
|
||||||
|
text = self.filter_profanity(text)
|
||||||
transcript = Transcript(
|
transcript = Transcript(
|
||||||
text=text,
|
text=text,
|
||||||
translation=translation,
|
translation=translation,
|
||||||
|
|||||||
@@ -60,6 +60,8 @@ class TranscriptFinalTitleProcessor(Processor):
|
|||||||
|
|
||||||
accumulated_titles = ".".join([chunk.title for chunk in self.chunks])
|
accumulated_titles = ".".join([chunk.title for chunk in self.chunks])
|
||||||
title_result = await self.get_title(accumulated_titles)
|
title_result = await self.get_title(accumulated_titles)
|
||||||
|
final_title = self.llm.trim_title(title_result["title"])
|
||||||
|
final_title = self.llm.ensure_casing(final_title)
|
||||||
|
|
||||||
final_title = FinalTitle(title=title_result["title"])
|
final_title = FinalTitle(title=final_title)
|
||||||
await self.emit(final_title)
|
await self.emit(final_title)
|
||||||
|
|||||||
@@ -54,9 +54,11 @@ class TranscriptTopicDetectorProcessor(Processor):
|
|||||||
text = self.transcript.text
|
text = self.transcript.text
|
||||||
self.logger.info(f"Topic detector got {len(text)} length transcript")
|
self.logger.info(f"Topic detector got {len(text)} length transcript")
|
||||||
topic_result = await self.get_topic(text=text)
|
topic_result = await self.get_topic(text=text)
|
||||||
|
title = self.llm.trim_title(topic_result["title"])
|
||||||
|
title = self.llm.ensure_casing(title)
|
||||||
|
|
||||||
summary = TitleSummary(
|
summary = TitleSummary(
|
||||||
title=self.llm.ensure_casing(topic_result["title"]),
|
title=title,
|
||||||
summary=topic_result["summary"],
|
summary=topic_result["summary"],
|
||||||
timestamp=self.transcript.timestamp,
|
timestamp=self.transcript.timestamp,
|
||||||
duration=self.transcript.duration,
|
duration=self.transcript.duration,
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ def dummy_processors():
|
|||||||
"reflector.processors.transcript_final_short_summary.TranscriptFinalShortSummaryProcessor.get_short_summary"
|
"reflector.processors.transcript_final_short_summary.TranscriptFinalShortSummaryProcessor.get_short_summary"
|
||||||
) as mock_short_summary:
|
) as mock_short_summary:
|
||||||
mock_topic.return_value = {"title": "LLM TITLE", "summary": "LLM SUMMARY"}
|
mock_topic.return_value = {"title": "LLM TITLE", "summary": "LLM SUMMARY"}
|
||||||
mock_title.return_value = {"title": "LLM FINAL TITLE"}
|
mock_title.return_value = {"title": "LLM TITLE"}
|
||||||
mock_long_summary.return_value = "LLM LONG SUMMARY"
|
mock_long_summary.return_value = "LLM LONG SUMMARY"
|
||||||
mock_short_summary.return_value = {"short_summary": "LLM SHORT SUMMARY"}
|
mock_short_summary.return_value = {"short_summary": "LLM SHORT SUMMARY"}
|
||||||
|
|
||||||
|
|||||||
@@ -185,7 +185,7 @@ async def test_transcript_rtc_and_websocket(
|
|||||||
|
|
||||||
assert "FINAL_TITLE" in eventnames
|
assert "FINAL_TITLE" in eventnames
|
||||||
ev = events[eventnames.index("FINAL_TITLE")]
|
ev = events[eventnames.index("FINAL_TITLE")]
|
||||||
assert ev["data"]["title"] == "LLM FINAL TITLE"
|
assert ev["data"]["title"] == "LLM TITLE"
|
||||||
|
|
||||||
# check status order
|
# check status order
|
||||||
statuses = [e["data"]["value"] for e in events if e["event"] == "STATUS"]
|
statuses = [e["data"]["value"] for e in events if e["event"] == "STATUS"]
|
||||||
@@ -330,7 +330,7 @@ async def test_transcript_rtc_and_websocket_and_fr(
|
|||||||
|
|
||||||
assert "FINAL_TITLE" in eventnames
|
assert "FINAL_TITLE" in eventnames
|
||||||
ev = events[eventnames.index("FINAL_TITLE")]
|
ev = events[eventnames.index("FINAL_TITLE")]
|
||||||
assert ev["data"]["title"] == "LLM FINAL TITLE"
|
assert ev["data"]["title"] == "LLM TITLE"
|
||||||
|
|
||||||
# check status order
|
# check status order
|
||||||
statuses = [e["data"]["value"] for e in events if e["event"] == "STATUS"]
|
statuses = [e["data"]["value"] for e in events if e["event"] == "STATUS"]
|
||||||
|
|||||||
Reference in New Issue
Block a user