Compare commits
5 commits
84ea46f142
...
9ec2f1d89a
| Author | SHA1 | Date | |
|---|---|---|---|
| 9ec2f1d89a | |||
| 836e7b565b | |||
| 2514ad1296 | |||
| 63131b7f11 | |||
| 333d825cb7 |
5 changed files with 168 additions and 12 deletions
86
Dockerfile
Normal file
86
Dockerfile
Normal file
|
|
@ -0,0 +1,86 @@
|
||||||
|
FROM python:3.13-slim-bookworm AS build
|
||||||
|
|
||||||
|
SHELL ["sh", "-exc"]
|
||||||
|
|
||||||
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
|
|
||||||
|
RUN <<EOT
|
||||||
|
apt-get update -qy
|
||||||
|
apt-get install -qyy \
|
||||||
|
build-essential \
|
||||||
|
ca-certificates \
|
||||||
|
python3-setuptools \
|
||||||
|
EOT
|
||||||
|
|
||||||
|
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
|
||||||
|
|
||||||
|
# no warnings about missing hardlinking
|
||||||
|
ENV UV_LINK_MODE=copy \
|
||||||
|
# bytecode compilation for fast startup
|
||||||
|
UV_COMPILE_BYTECODE=1 \
|
||||||
|
# don't download isolated python setups
|
||||||
|
UV_PYTHON_DOWNLOADS=never \
|
||||||
|
# stick to debian available
|
||||||
|
UV_PYTHON=python3.13 \
|
||||||
|
# set our working env
|
||||||
|
UV_PROJECT_ENVIRONMENT=/app
|
||||||
|
|
||||||
|
## END of build prepping
|
||||||
|
|
||||||
|
# sync all DEPENDENCIES (without actual application)
|
||||||
|
RUN --mount=type=cache,target=/root/.cache \
|
||||||
|
--mount=type=bind,source=uv.lock,target=uv.lock \
|
||||||
|
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \
|
||||||
|
uv sync \
|
||||||
|
--locked \
|
||||||
|
--no-dev \
|
||||||
|
--no-install-project
|
||||||
|
|
||||||
|
# sync app itself
|
||||||
|
COPY . /src
|
||||||
|
WORKDIR /src
|
||||||
|
RUN --mount=type=cache,target=/root/.cache \
|
||||||
|
uv sync \
|
||||||
|
--locked \
|
||||||
|
--no-dev \
|
||||||
|
--no-editable
|
||||||
|
|
||||||
|
############################ APP CONTAINER ############################
|
||||||
|
|
||||||
|
FROM python:3.13-slim-bookworm
|
||||||
|
SHELL ["sh", "-exc"]
|
||||||
|
|
||||||
|
ENV PATH=/app/bin:$PATH
|
||||||
|
|
||||||
|
# run app rootless
|
||||||
|
RUN <<EOT
|
||||||
|
groupadd -r app
|
||||||
|
useradd -r -d /app -g app -N app
|
||||||
|
EOT
|
||||||
|
|
||||||
|
ENTRYPOINT ["tini", "-v", "--", "prophet"]
|
||||||
|
STOPSIGNAL SIGINT
|
||||||
|
|
||||||
|
RUN <<EOT
|
||||||
|
apt-get update -qy
|
||||||
|
apt-get install -qyy \
|
||||||
|
python3.11 \
|
||||||
|
libpython3.11 \
|
||||||
|
tini
|
||||||
|
|
||||||
|
apt-get clean
|
||||||
|
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||||
|
EOT
|
||||||
|
|
||||||
|
COPY --from=build --chown=app:app /app /app
|
||||||
|
COPY --from=build --chown=app:app /src/static /app/static
|
||||||
|
COPY --from=build --chown=app:app /src/templates /app/templates
|
||||||
|
|
||||||
|
USER app
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
RUN <<EOT
|
||||||
|
python -V
|
||||||
|
python -Im site
|
||||||
|
python -Ic 'import prophet'
|
||||||
|
EOT
|
||||||
|
|
@ -122,7 +122,7 @@ async def fetch_update(debug_print: bool = True):
|
||||||
def start() -> None:
|
def start() -> None:
|
||||||
from uvicorn import run
|
from uvicorn import run
|
||||||
|
|
||||||
run("prophet.app:app", reload=True)
|
run("prophet.app:app", reload=True, host="0.0.0.0")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
from typing import Protocol
|
from typing import Protocol
|
||||||
|
|
||||||
|
from prophet.domain.improvement import Improvement
|
||||||
from prophet.domain.original import Original
|
from prophet.domain.original import Original
|
||||||
|
|
||||||
|
|
||||||
class LLMClient(Protocol):
|
class LLMClient(Protocol):
|
||||||
def get_alternative_title_suggestions(self, original_content: str) -> str:
|
def rewrite(self, original: Original) -> Improvement:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def rewrite_title(
|
def rewrite_title(
|
||||||
|
|
@ -16,3 +17,6 @@ class LLMClient(Protocol):
|
||||||
self, original: Original, improved_title: str | None = None
|
self, original: Original, improved_title: str | None = None
|
||||||
) -> str:
|
) -> str:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def get_alternative_title_suggestions(self, original_content: str) -> str:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,14 @@
|
||||||
|
from typing import override
|
||||||
|
|
||||||
from groq import Groq
|
from groq import Groq
|
||||||
|
|
||||||
from prophet.config import AiConfig
|
from prophet.config import AiConfig
|
||||||
|
from prophet.domain.improvement import Improvement
|
||||||
from prophet.domain.llm import LLMClient
|
from prophet.domain.llm import LLMClient
|
||||||
from prophet.domain.original import Original
|
from prophet.domain.original import Original
|
||||||
|
|
||||||
|
AVOID_SHOCKING_TURN_OF_EVENTS: bool = True
|
||||||
|
|
||||||
|
|
||||||
class GroqClient(LLMClient):
|
class GroqClient(LLMClient):
|
||||||
config_ai: AiConfig
|
config_ai: AiConfig
|
||||||
|
|
@ -15,12 +20,42 @@ class GroqClient(LLMClient):
|
||||||
self.config_ai = config_ai if config_ai else AiConfig.from_env()
|
self.config_ai = config_ai if config_ai else AiConfig.from_env()
|
||||||
self.client = client if client else Groq(api_key=self.config_ai.API_KEY)
|
self.client = client if client else Groq(api_key=self.config_ai.API_KEY)
|
||||||
|
|
||||||
def get_alternative_title_suggestions(self, original_content: str) -> str:
|
@override
|
||||||
|
def rewrite(self, original: Original) -> Improvement:
|
||||||
|
suggestions = self.get_alternative_title_suggestions(original.title)
|
||||||
|
new_title = self.rewrite_title(original.title, suggestions)
|
||||||
|
new_summary = self.rewrite_summary(original, new_title)
|
||||||
|
|
||||||
|
return Improvement(original=original, title=new_title, summary=new_summary)
|
||||||
|
|
||||||
|
@override
|
||||||
|
def get_alternative_title_suggestions(
|
||||||
|
self, original_content: str, custom_prompt: str | None = None
|
||||||
|
) -> str:
|
||||||
|
prompt = (
|
||||||
|
custom_prompt
|
||||||
|
if custom_prompt
|
||||||
|
else """
|
||||||
|
|
||||||
|
Political context: We are in the year 2025, Donald Trump is
|
||||||
|
President of the United States again. There has been a crackdown on
|
||||||
|
'illegal' immigration, with controversial disappearings happening
|
||||||
|
almost every day. Many are calling the United States an
|
||||||
|
increasingly fascist state.
|
||||||
|
|
||||||
|
You are a comedy writer at a left-leaning satirical newspaper.
|
||||||
|
Improve on the following satirical headline. Your new headline is
|
||||||
|
funny, can involve current political events. and has an edge to it.
|
||||||
|
It should be roughly the length of the original headline. Print
|
||||||
|
only new suggestions, with one suggestion on each line.
|
||||||
|
|
||||||
|
"""
|
||||||
|
)
|
||||||
suggestions = self.client.chat.completions.create(
|
suggestions = self.client.chat.completions.create(
|
||||||
messages=[
|
messages=[
|
||||||
{
|
{
|
||||||
"role": "system",
|
"role": "system",
|
||||||
"content": "You are a comedy writer at a satirical newspaper. Improve on the following satirical headline. Your new headline is funny, can involve current political events and has an edge to it. Print only the suggestions, with one suggestion on each line.",
|
"content": prompt,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"role": "user",
|
"role": "user",
|
||||||
|
|
@ -34,16 +69,34 @@ class GroqClient(LLMClient):
|
||||||
raise ValueError
|
raise ValueError
|
||||||
return suggestions_str
|
return suggestions_str
|
||||||
|
|
||||||
|
@override
|
||||||
def rewrite_title(
|
def rewrite_title(
|
||||||
self, original_content: str, suggestions: str | None = None
|
self,
|
||||||
|
original_content: str,
|
||||||
|
suggestions: str | None = None,
|
||||||
|
custom_prompt: str | None = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
|
prompt = (
|
||||||
|
custom_prompt
|
||||||
|
if custom_prompt
|
||||||
|
else """
|
||||||
|
You are an editor at a satirical newspaper. Improve on the following
|
||||||
|
satirical headline. For a given headline, you diligently evaluate: (1)
|
||||||
|
Whether the headline is funny; (2) Whether the headline follows a clear
|
||||||
|
satirical goal; (3) Whether the headline has sufficient substance and
|
||||||
|
bite; (4) Whether the headline is roughly the length of the original
|
||||||
|
suggestion. Based on the outcomes of your review, you pick your
|
||||||
|
favorite headline from the given suggestions and you make targeted
|
||||||
|
revisions to it. Your output consists solely of the revised headline.
|
||||||
|
"""
|
||||||
|
)
|
||||||
if not suggestions:
|
if not suggestions:
|
||||||
suggestions = self.get_alternative_title_suggestions(original_content)
|
suggestions = self.get_alternative_title_suggestions(original_content)
|
||||||
winner = self.client.chat.completions.create(
|
winner = self.client.chat.completions.create(
|
||||||
messages=[
|
messages=[
|
||||||
{
|
{
|
||||||
"role": "system",
|
"role": "system",
|
||||||
"content": "You are an editor at a satirical newspaper. Improve on the following satirical headline. For a given headline, you diligently evaluate: (1) Whether the headline is funny; (2) Whether the headline follows a clear satirical goal; (3) Whether the headline has sufficient substance and bite. Based on the outcomes of your review, you pick your favorite headline from the given suggestions and you make targeted revisions to it. Keep the length roughly to that of the original suggestions. Your output consists solely of the revised headline.",
|
"content": prompt,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"role": "user",
|
"role": "user",
|
||||||
|
|
@ -58,21 +111,26 @@ class GroqClient(LLMClient):
|
||||||
raise ValueError
|
raise ValueError
|
||||||
return winner_str.strip(" \"'")
|
return winner_str.strip(" \"'")
|
||||||
|
|
||||||
|
@override
|
||||||
def rewrite_summary(
|
def rewrite_summary(
|
||||||
self, original: Original, improved_title: str | None = None
|
self,
|
||||||
|
original: Original,
|
||||||
|
improved_title: str | None = None,
|
||||||
|
custom_prompt: str | None = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
|
prompt = (
|
||||||
|
custom_prompt
|
||||||
|
if custom_prompt
|
||||||
|
else f""" Below there is an original title and an original summary. Then follows an improved title. Write an improved summary based on the original summary which fits to the improved title. {"Do not use the phrase: 'in a surprising turn of events' or 'in a shocking turn of events.'" if AVOID_SHOCKING_TURN_OF_EVENTS else ""} Only output the improved summary.\n\nTitle:{original.title}\nSummary:{original.summary}\n---\nTitle:{improved_title}\nSummary:"""
|
||||||
|
)
|
||||||
if not improved_title:
|
if not improved_title:
|
||||||
improved_title = self.rewrite_title(original.title)
|
improved_title = self.rewrite_title(original.title)
|
||||||
|
|
||||||
no_shocking_turn: bool = True
|
|
||||||
summary = self.client.chat.completions.create(
|
summary = self.client.chat.completions.create(
|
||||||
messages=[
|
messages=[
|
||||||
{
|
{
|
||||||
"role": "user",
|
"role": "user",
|
||||||
"content": f"""
|
"content": prompt,
|
||||||
Below there is an original title and an original summary. Then follows an improved title. Write an improved summary based on the original summary which fits to the improved title.
|
|
||||||
{"Do not use the phrase: 'in a surprising turn of events' or 'in a shocking turn of events.'" if no_shocking_turn else ""}
|
|
||||||
Only output the improved summary.\n\nTitle:{original.title}\nSummary:{original.summary}\n---\nTitle:{improved_title}\nSummary:""",
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
model="llama-3.3-70b-versatile",
|
model="llama-3.3-70b-versatile",
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,14 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<span class="fab-label">Lefty Bee</span>
|
||||||
|
<div class="option-btn fab-icon-holder">
|
||||||
|
<div class="icon">
|
||||||
|
<i class="fas fa-arrow-left"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<span class="fab-label">About</span>
|
<span class="fab-label">About</span>
|
||||||
<div class="option-btn fab-icon-holder">
|
<div class="option-btn fab-icon-holder">
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue