Skip to content

Commit

Permalink
Merge pull request #69 from leozqin/llm-provider
Browse files Browse the repository at this point in the history
Rename summarization to LLM
  • Loading branch information
leozqin authored Oct 30, 2024
2 parents 11424f3 + 1a354dc commit 90d1623
Show file tree
Hide file tree
Showing 19 changed files with 74 additions and 83 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
Precis (properly Précis, pronounced "pray-see") is a extensibility-oriented RSS reader that can use LLMs to summarize and synthesize information from numerous different sources, with an emphasis on timely delivery of information via notifications.

The following components of the app are extensible:
1. Summarization - LLMs including Ollama and OpenAI
1. LLMs - LLMs including Ollama and OpenAI, used for functions such as summarization
2. Content Retrieval - `requests` or `playwright`
3. Notification - `matrix`, `slack`, `jira`, and `ntfy`
4. Storage - At this time, we support two reasonable embedded DBs - `tinydb` or `lmdb` - defaults to `tinydb`. You can add support for your database of choice if you can implement about 20 shared transactions.

The Summarization and Notification handlers also support a `null` handler that does nothing. Good for testing or if you don't care about notifications and summaries. The null handler is the default.
The LLM and Notification handlers also support a `null` handler that does nothing. Good for testing or if you don't care about notifications and summaries. The null handler is the default.

Precis also supports themes.

Expand Down Expand Up @@ -110,7 +110,7 @@ After initial onboarding, you'll be brought to the feeds page.
You can then view the feed entries for each feed.
![The feed entries page](app/assets/feed_entries.png)
When you read a feed entry you'll get the full text of the article, as well a summary if you have a summarization handler configured.
When you read a feed entry you'll get the full text of the article, as well as a summary if you have a LLM handler configured.
![The read page](app/assets/read.png)
Global settings can be configured in the UI
Expand Down
6 changes: 3 additions & 3 deletions app/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ async def settings(
response = {
"themes": Themes._member_names_,
"content_handler_choices": await bk.list_content_handler_choices(),
"summarization_handler_choices": await bk.list_summarization_handler_choices(),
"llm_handler_choices": await bk.list_llm_handler_choices(),
"notification_handler_choices": await bk.list_notification_handler_choices(),
"settings": await bk.get_settings(),
"notification": bk.get_handlers(),
Expand Down Expand Up @@ -287,7 +287,7 @@ async def update_settings(
send_notification: Annotated[bool, Form()] = False,
notification: Annotated[str, Form()] = None,
content: Annotated[str, Form()] = None,
summarization: Annotated[str, Form()] = None,
llm: Annotated[str, Form()] = None,
reading_speed: Annotated[int, Form()] = None,
finished_onboarding: Annotated[bool, Form()] = False,
recent_hours: Annotated[int, Form()] = None,
Expand All @@ -298,7 +298,7 @@ async def update_settings(
theme=theme,
refresh_interval=refresh_interval,
notification_handler_key=notification,
summarization_handler_key=summarization,
llm_handler_key=llm,
content_retrieval_handler_key=content,
reading_speed=reading_speed,
finished_onboarding=finished_onboarding,
Expand Down
6 changes: 3 additions & 3 deletions app/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,10 +220,10 @@ async def list_content_handler_choices():
return list(content_retrieval_handlers.keys())

@staticmethod
async def list_summarization_handler_choices():
from app.summarization import summarization_handlers
async def list_llm_handler_choices():
from app.llm import llm_handlers

return list(summarization_handlers.keys())
return list(llm_handlers.keys())

@staticmethod
async def list_notification_handler_choices():
Expand Down
22 changes: 10 additions & 12 deletions app/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@
from app.handlers import (
ContentRetrievalHandler,
HandlerBase,
LLMHandler,
NotificationHandler,
SummarizationHandler,
)
from app.llm import llm_handlers
from app.models import *
from app.notification import notification_handlers
from app.summarization import summarization_handlers


class Themes(str, Enum):
Expand All @@ -45,7 +45,7 @@ class GlobalSettings(BaseModel):
reading_speed: int = 238

notification_handler_key: str = "null_notification"
summarization_handler_key: str = "null_summarization"
llm_handler_key: str = "null_llm"
content_retrieval_handler_key: str = "playwright"
recent_hours: int = 36

Expand All @@ -68,11 +68,11 @@ def notification_handler(self) -> NotificationHandler:
return self.db.handler_map[self.notification_handler_key]()

@property
def summarization_handler(self) -> SummarizationHandler:
def llm_handler(self) -> LLMHandler:
try:
return self.db.get_handler(id=self.summarization_handler_key)
return self.db.get_handler(id=self.llm_handler_key)
except IndexError:
return self.db.handler_map[self.summarization_handler_key]()
return self.db.handler_map[self.llm_handler_key]()

@property
def content_retrieval_handler(self) -> ContentRetrievalHandler:
Expand All @@ -87,19 +87,19 @@ class StorageHandler(ABC):
logger = getLogger("uvicorn.error")

handler_map = {
**summarization_handlers,
**llm_handlers,
**notification_handlers,
**content_retrieval_handlers,
}

engine_map = {
"summarization": summarization_handlers,
"llm": llm_handlers,
"notification": notification_handlers,
"content": content_retrieval_handlers,
}

handler_type_map = {
**{k: "summarization" for k in summarization_handlers.keys()},
**{k: "llm" for k in llm_handlers.keys()},
**{k: "notification" for k in notification_handlers.keys()},
**{k: "content" for k in content_retrieval_handlers.keys()},
}
Expand Down Expand Up @@ -304,9 +304,7 @@ def summarize(
feed: Feed, entry: FeedEntry, mk: str, settings: GlobalSettings
) -> str:

summary = settings.summarization_handler.summarize(
feed=feed, entry=entry, mk=mk
)
summary = settings.llm_handler.summarize(feed=feed, entry=entry, mk=mk)

if summary:
return markdown(summary)
8 changes: 4 additions & 4 deletions app/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,14 @@ async def send_notification(self, feed: Feed, entry: FeedEntry):
pass


class SummarizationHandler(HandlerBase):
id: ClassVar[str] = "generic_summarization_handler"
class LLMHandler(HandlerBase):
id: ClassVar[str] = "generic_llm_handler"

@abstractmethod
def summarize(self, feed: Feed, entry: FeedEntry, mk: str):
pass

def get_prompt(self, mk: str):
def get_summarization_prompt(self, mk: str):
prompt = f"""
Summarize this article:
Expand All @@ -57,7 +57,7 @@ def get_prompt(self, mk: str):
return prompt

@property
def system_prompt(self):
def summarization_system_prompt(self):
return """
Your goal is to write a brief but detailed summary of the text given to you.
Only output the summary without any headings or sections.
Expand Down
12 changes: 12 additions & 0 deletions app/llm/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from app.llm.null import NullLLMHandler
from app.llm.ollama import OllamaLLMHandler
from app.llm.openai import OpenAILLMHandler

llm_handlers = {
NullLLMHandler.id: NullLLMHandler,
OllamaLLMHandler.id: OllamaLLMHandler,
OpenAILLMHandler.id: OpenAILLMHandler,
# redirect null summarization handler to null llm
# TODO: Deprecate
"null_summarization": NullLLMHandler,
}
12 changes: 12 additions & 0 deletions app/llm/null.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from typing import ClassVar

from pydantic import BaseModel

from app.handlers import Feed, FeedEntry, LLMHandler


class NullLLMHandler(LLMHandler, BaseModel):
id: ClassVar[str] = "null_llm"

def summarize(self, feed: Feed, entry: FeedEntry, mk: str):
return None
4 changes: 2 additions & 2 deletions app/summarization/ollama.py → app/llm/ollama.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
from ollama import ChatResponse, Client, Message, Options
from pydantic import BaseModel

from app.handlers import SummarizationHandler
from app.handlers import LLMHandler
from app.models import Feed, FeedEntry


class OllamaSummarizationHandler(SummarizationHandler, BaseModel):
class OllamaLLMHandler(LLMHandler, BaseModel):
base_url: str
model: str
system: str = None
Expand Down
4 changes: 2 additions & 2 deletions app/summarization/openai.py → app/llm/openai.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@
from openai import OpenAI
from pydantic import BaseModel

from app.handlers import SummarizationHandler
from app.handlers import LLMHandler
from app.models import Feed, FeedEntry


class OpenAISummarizationHandler(SummarizationHandler, BaseModel):
class OpenAILLMHandler(LLMHandler, BaseModel):
api_key: str = environ.get("OPENAI_API_KEY")
model: str = "gpt-4o-mini"

Expand Down
2 changes: 1 addition & 1 deletion app/storage/lmdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,7 @@ def upsert_settings(self, settings: GlobalSettings) -> None:
)

self.upsert_handler(settings.notification_handler)
self.upsert_handler(settings.summarization_handler)
self.upsert_handler(settings.llm_handler)
self.upsert_handler(settings.content_retrieval_handler)

def delete_feed(self, feed: Feed) -> None:
Expand Down
18 changes: 5 additions & 13 deletions app/storage/tinydb.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,7 @@

from app.constants import DATA_DIR
from app.context import GlobalSettings, StorageHandler
from app.handlers import (
ContentRetrievalHandler,
NotificationHandler,
SummarizationHandler,
)
from app.handlers import ContentRetrievalHandler, LLMHandler, NotificationHandler
from app.models import EntryContent, Feed, FeedEntry

logger = getLogger("uvicorn.error")
Expand Down Expand Up @@ -176,9 +172,7 @@ async def upsert_entry_content(self, content: EntryContent):

def upsert_handler(
self,
handler: Type[
SummarizationHandler | NotificationHandler | ContentRetrievalHandler
],
handler: Type[LLMHandler | NotificationHandler | ContentRetrievalHandler],
) -> None:
table = self.db.table("handler")

Expand All @@ -196,9 +190,7 @@ def _make_handler_obj(self, id: str, config: Mapping):

def get_handlers(
self,
) -> Mapping[
str, Type[SummarizationHandler | NotificationHandler | ContentRetrievalHandler]
]:
) -> Mapping[str, Type[LLMHandler | NotificationHandler | ContentRetrievalHandler]]:
table = self.db.table("handler")

handlers = {i: None for i in self.handler_map.keys()}
Expand All @@ -212,7 +204,7 @@ def get_handlers(

def get_handler(
self, id: str
) -> Type[SummarizationHandler | NotificationHandler | ContentRetrievalHandler]:
) -> Type[LLMHandler | NotificationHandler | ContentRetrievalHandler]:
table = self.db.table("handler")
logger.info(f"requested handler {id}")
query = Query().id.matches(id)
Expand Down Expand Up @@ -246,7 +238,7 @@ def upsert_settings(self, settings: GlobalSettings) -> None:
table.upsert(row, cond=query)

self.upsert_handler(settings.notification_handler)
self.upsert_handler(settings.summarization_handler)
self.upsert_handler(settings.llm_handler)
self.upsert_handler(settings.content_retrieval_handler)

def delete_feed(self, feed: Feed) -> None:
Expand Down
11 changes: 0 additions & 11 deletions app/summarization/__init__.py

This file was deleted.

12 changes: 0 additions & 12 deletions app/summarization/null.py

This file was deleted.

4 changes: 2 additions & 2 deletions app/templates/onboarding.html
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@ <h2 class="my-5 text-4xl lg:text-2xl">
In order to use Precis, you'll have to configure it correctly.
</p>
<p class="my-5">
Most functions in Precis are managed by handlers - namely, summarization, content retrieval,
Most functions in Precis are managed by handlers - namely, LLM functions, content retrieval,
and notification. Press the button below to go to the Settings page to configure handlers.
This step is optional - content retrieval is handled by Playwright out of the box, and we
provide default no-op handlers for summarization and notification. However - of course,
provide default no-op handlers for LLMs and notification. However - of course,
you will get more out of Precis if you configure handlers for these features.
</p>
<a href="{{ url_for('settings') }}" role="button"
Expand Down
8 changes: 4 additions & 4 deletions app/templates/settings.html
Original file line number Diff line number Diff line change
Expand Up @@ -104,10 +104,10 @@ <h2 class="text-4xl lg:text-2xl my-5 justify-center flex">
</select>
</label>
<label class="label flex items-center gap-2 text-2xl lg:text-xl">
Summarization Handler
<select name="summarization" class="select select-bordered w-full max-w-xs my-5 text-2xl lg:text-xl">
{% for choice in summarization_handler_choices %}
<option {% if choice==settings.summarization_handler_key %} selected {% endif %}
LLM Handler
<select name="llm" class="select select-bordered w-full max-w-xs my-5 text-2xl lg:text-xl">
{% for choice in llm_handler_choices %}
<option {% if choice==settings.llm_handler_key %} selected {% endif %}
class="text-2xl lg:text-xl">
{{ choice }}
</option>
Expand Down
2 changes: 1 addition & 1 deletion configs/settings.yml.example
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ send_notification: True
theme: forest
refresh_interval: 5
notification_handler_key: null_notification
summarization_handler_key: null_summarization
llm_handler_key: null_llm
content_retrieval_handler_key: playwright
finished_onboarding: False
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "precis"
version = "0.3.0"
version = "0.3.1"
description = "A framework for automating your media diet"
requires-python = ">=3.11"
license = {file = "LICENSE"}
Expand Down
14 changes: 7 additions & 7 deletions tests/integration/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ type Settings struct {
RefreshInterval int `json:"refresh_interval,omitempty"`
ReadingSpeed int `json:"reading_speed,omitempty"`
NotificationHandlerKey string `json:"notification_handler_key,omitempty"`
SummarizationHandlerKey string `json:"summarization_handler_key,omitempty"`
LLMHandlerKey string `json:"llm_handler_key,omitempty"`
ContentRetrievalHandlerKey string `json:"content_retrieval_handler_key,omitempty"`
RecentHours int `json:"recent_hours,omitempty"`
FinishedOnboarding bool `json:"finished_onboarding,omitempty"`
Expand All @@ -58,12 +58,12 @@ type AboutResponse struct {
}

type SettingsResponse struct {
Themes []string `json:"themes,omitempty"`
ContentHandlerChoices []string `json:"content_handler_choices,omitempty"`
SummarizationHandlerChoices []string `json:"summarization_handler_choices,omitempty"`
NotificationHandlerChoices []string `json:"notification_handler_choices,omitempty"`
UpdateStatus bool `json:"update_status,omitempty"`
UpdateException string `json:"update_exception,omitempty"`
Themes []string `json:"themes,omitempty"`
ContentHandlerChoices []string `json:"content_handler_choices,omitempty"`
LLMHandlerChoices []string `json:"llm_handler_choices,omitempty"`
NotificationHandlerChoices []string `json:"notification_handler_choices,omitempty"`
UpdateStatus bool `json:"update_status,omitempty"`
UpdateException string `json:"update_exception,omitempty"`

Settings Settings `json:"settings,omitempty"`
}
Expand Down
4 changes: 2 additions & 2 deletions tests/integration/web_api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ func TestSettings(t *testing.T) {

assert.NotEmpty(t, settings.Themes)
assert.NotEmpty(t, settings.ContentHandlerChoices)
assert.NotEmpty(t, settings.SummarizationHandlerChoices)
assert.NotEmpty(t, settings.LLMHandlerChoices)
assert.NotEmpty(t, settings.NotificationHandlerChoices)
assert.Equal(t, false, settings.UpdateStatus)
assert.Empty(t, settings.UpdateException)
Expand All @@ -93,7 +93,7 @@ func TestSettings(t *testing.T) {
assert.IsType(t, true, s.FinishedOnboarding)

assert.Contains(t, settings.NotificationHandlerChoices, s.NotificationHandlerKey)
assert.Contains(t, settings.SummarizationHandlerChoices, s.SummarizationHandlerKey)
assert.Contains(t, settings.LLMHandlerChoices, s.LLMHandlerKey)
assert.Contains(t, settings.ContentHandlerChoices, s.ContentRetrievalHandlerKey)
}

Expand Down

0 comments on commit 90d1623

Please sign in to comment.