bookworm_genai 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bookworm_genai-0.1.0/PKG-INFO +111 -0
- bookworm_genai-0.1.0/README.md +89 -0
- bookworm_genai-0.1.0/bookworm_genai/__init__.py +17 -0
- bookworm_genai-0.1.0/bookworm_genai/__main__.py +70 -0
- bookworm_genai-0.1.0/bookworm_genai/commands/__init__.py +0 -0
- bookworm_genai-0.1.0/bookworm_genai/commands/ask.py +86 -0
- bookworm_genai-0.1.0/bookworm_genai/commands/sync.py +39 -0
- bookworm_genai-0.1.0/bookworm_genai/integrations.py +26 -0
- bookworm_genai-0.1.0/bookworm_genai/models.py +36 -0
- bookworm_genai-0.1.0/bookworm_genai/storage.py +43 -0
- bookworm_genai-0.1.0/pyproject.toml +33 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: bookworm_genai
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Bookworm - A LLM-powered bookmark search engine
|
|
5
|
+
Author: kiran94
|
|
6
|
+
Requires-Python: >=3.9,<4.0
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
+
Requires-Dist: duckdb (>=1.0.0,<2.0.0)
|
|
13
|
+
Requires-Dist: jq (>=1.7.0,<2.0.0)
|
|
14
|
+
Requires-Dist: langchain (>=0.2.12,<0.3.0)
|
|
15
|
+
Requires-Dist: langchain-community (>=0.2.11,<0.3.0)
|
|
16
|
+
Requires-Dist: langchain-openai (>=0.1.20,<0.2.0)
|
|
17
|
+
Requires-Dist: pandas (>=2.2.2,<3.0.0)
|
|
18
|
+
Requires-Dist: platformdirs (>=4.2.2,<5.0.0)
|
|
19
|
+
Requires-Dist: rich (>=13.7.1,<14.0.0)
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
# bookworm
|
|
23
|
+
|
|
24
|
+
[](https://github.com/kiran94/bookworm/actions/workflows/main.yml)
|
|
25
|
+
|
|
26
|
+
### Processes
|
|
27
|
+
|
|
28
|
+
*`bookworm sync`*
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
python -m bookworm sync
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
```mermaid
|
|
35
|
+
graph LR
|
|
36
|
+
|
|
37
|
+
subgraph Bookmarks
|
|
38
|
+
Chrome(Chrome Bookmarks)
|
|
39
|
+
Brave(Brave Bookmarks)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
Bookworm(bookworm sync)
|
|
43
|
+
|
|
44
|
+
EmbeddingsService(Embeddings Service e.g OpenAIEmbeddings)
|
|
45
|
+
|
|
46
|
+
VectorStore(Vector Store e.g DuckDB)
|
|
47
|
+
|
|
48
|
+
Chrome -->|load bookmarks|Bookworm
|
|
49
|
+
Brave -->|load bookmarks|Bookworm
|
|
50
|
+
|
|
51
|
+
Bookworm -->|vectorize bookmarks|EmbeddingsService-->|store embeddings|VectorStore
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
*`bookworm ask`*
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
python -m bookworm ask
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
```mermaid
|
|
63
|
+
graph LR
|
|
64
|
+
|
|
65
|
+
query
|
|
66
|
+
Bookworm(bookworm ask)
|
|
67
|
+
|
|
68
|
+
subgraph _
|
|
69
|
+
LLM(LLM e.g OpenAI)
|
|
70
|
+
VectorStore(Vector Store e.g DuckDB)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
query -->|user queries for information|Bookworm
|
|
74
|
+
|
|
75
|
+
Bookworm -->|simularity search|VectorStore -->|send similar docs + user query|LLM
|
|
76
|
+
LLM -->|send back response|Bookworm
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
### Developer Setup
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
# LLMs
|
|
86
|
+
export OPENAI_API_KEY=
|
|
87
|
+
|
|
88
|
+
# Langchain (optional, but useful for debugging)
|
|
89
|
+
export LANGCHAIN_API_KEY=
|
|
90
|
+
export LANGCHAIN_TRACING_V2=true
|
|
91
|
+
export LANGCHAIN_PROJECT=bookworm
|
|
92
|
+
|
|
93
|
+
# Misc (optional)
|
|
94
|
+
export LOGGING_LEVEL=INFO
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Recommendations:
|
|
98
|
+
|
|
99
|
+
- Install [`pyenv`](https://github.com/pyenv/pyenv?tab=readme-ov-file#installation) and ensure [build dependencies are installed](https://github.com/pyenv/pyenv?tab=readme-ov-file#install-python-build-dependencies) for your OS.
|
|
100
|
+
- Install [Poetry](https://python-poetry.org/docs/) we will be using [environment management](https://python-poetry.org/docs/managing-environments/) below.
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
poetry env use 3.9 # or path to your 3.9 installation
|
|
105
|
+
|
|
106
|
+
poetry shell
|
|
107
|
+
poetry install
|
|
108
|
+
|
|
109
|
+
bookworm --help
|
|
110
|
+
```
|
|
111
|
+
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# bookworm
|
|
2
|
+
|
|
3
|
+
[](https://github.com/kiran94/bookworm/actions/workflows/main.yml)
|
|
4
|
+
|
|
5
|
+
### Processes
|
|
6
|
+
|
|
7
|
+
*`bookworm sync`*
|
|
8
|
+
|
|
9
|
+
```python
|
|
10
|
+
python -m bookworm sync
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
```mermaid
|
|
14
|
+
graph LR
|
|
15
|
+
|
|
16
|
+
subgraph Bookmarks
|
|
17
|
+
Chrome(Chrome Bookmarks)
|
|
18
|
+
Brave(Brave Bookmarks)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
Bookworm(bookworm sync)
|
|
22
|
+
|
|
23
|
+
EmbeddingsService(Embeddings Service e.g OpenAIEmbeddings)
|
|
24
|
+
|
|
25
|
+
VectorStore(Vector Store e.g DuckDB)
|
|
26
|
+
|
|
27
|
+
Chrome -->|load bookmarks|Bookworm
|
|
28
|
+
Brave -->|load bookmarks|Bookworm
|
|
29
|
+
|
|
30
|
+
Bookworm -->|vectorize bookmarks|EmbeddingsService-->|store embeddings|VectorStore
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
*`bookworm ask`*
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
python -m bookworm ask
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
```mermaid
|
|
42
|
+
graph LR
|
|
43
|
+
|
|
44
|
+
query
|
|
45
|
+
Bookworm(bookworm ask)
|
|
46
|
+
|
|
47
|
+
subgraph _
|
|
48
|
+
LLM(LLM e.g OpenAI)
|
|
49
|
+
VectorStore(Vector Store e.g DuckDB)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
query -->|user queries for information|Bookworm
|
|
53
|
+
|
|
54
|
+
Bookworm -->|simularity search|VectorStore -->|send similar docs + user query|LLM
|
|
55
|
+
LLM -->|send back response|Bookworm
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
### Developer Setup
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
# LLMs
|
|
65
|
+
export OPENAI_API_KEY=
|
|
66
|
+
|
|
67
|
+
# Langchain (optional, but useful for debugging)
|
|
68
|
+
export LANGCHAIN_API_KEY=
|
|
69
|
+
export LANGCHAIN_TRACING_V2=true
|
|
70
|
+
export LANGCHAIN_PROJECT=bookworm
|
|
71
|
+
|
|
72
|
+
# Misc (optional)
|
|
73
|
+
export LOGGING_LEVEL=INFO
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Recommendations:
|
|
77
|
+
|
|
78
|
+
- Install [`pyenv`](https://github.com/pyenv/pyenv?tab=readme-ov-file#installation) and ensure [build dependencies are installed](https://github.com/pyenv/pyenv?tab=readme-ov-file#install-python-build-dependencies) for your OS.
|
|
79
|
+
- Install [Poetry](https://python-poetry.org/docs/) we will be using [environment management](https://python-poetry.org/docs/managing-environments/) below.
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
poetry env use 3.9 # or path to your 3.9 installation
|
|
84
|
+
|
|
85
|
+
poetry shell
|
|
86
|
+
poetry install
|
|
87
|
+
|
|
88
|
+
bookworm --help
|
|
89
|
+
```
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
from rich.logging import RichHandler
|
|
5
|
+
|
|
6
|
+
LOGGING_LEVEL = os.environ.get("LOGGING_LEVEL", logging.INFO)
|
|
7
|
+
LOGGING_FORMAT = os.environ.get("LOGGING_FORMAT", "%(message)s")
|
|
8
|
+
|
|
9
|
+
_is_debug = logging.getLevelName(LOGGING_LEVEL) == logging.DEBUG
|
|
10
|
+
|
|
11
|
+
logging.basicConfig(
|
|
12
|
+
level=LOGGING_LEVEL,
|
|
13
|
+
format=LOGGING_FORMAT,
|
|
14
|
+
handlers=[RichHandler(markup=True, show_path=_is_debug, show_time=_is_debug, show_level=_is_debug)],
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
logging.getLogger("httpx").setLevel(logging.WARNING)
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import logging
|
|
3
|
+
import argparse
|
|
4
|
+
|
|
5
|
+
from bookworm_genai.commands.sync import sync
|
|
6
|
+
from bookworm_genai.commands.ask import BookmarkChain
|
|
7
|
+
|
|
8
|
+
logger = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def main():
|
|
12
|
+
logger.info("[bold green]Starting Bookworm 📖")
|
|
13
|
+
logger.debug("Running on platform '%s'", sys.platform)
|
|
14
|
+
|
|
15
|
+
argparser = argparse.ArgumentParser(description="Bookworm - A LLM-powered bookmark search engine")
|
|
16
|
+
|
|
17
|
+
sub_parsers = argparser.add_subparsers(dest="command", help="Available commands")
|
|
18
|
+
sub_parsers.add_parser("sync", help="Sync the bookmark database with the latest changes")
|
|
19
|
+
sub_parsers.add_parser("ask", help="Search for a bookmark")
|
|
20
|
+
|
|
21
|
+
args = argparser.parse_args()
|
|
22
|
+
|
|
23
|
+
logger.debug("Arguments: %s", args)
|
|
24
|
+
|
|
25
|
+
if args.command == "sync":
|
|
26
|
+
sync()
|
|
27
|
+
|
|
28
|
+
elif args.command == "ask":
|
|
29
|
+
logger.info("What would you like to search for?")
|
|
30
|
+
query = input("> ")
|
|
31
|
+
|
|
32
|
+
logger.debug("query: %s", query)
|
|
33
|
+
|
|
34
|
+
with BookmarkChain() as bookmark_chain:
|
|
35
|
+
if not bookmark_chain.is_valid():
|
|
36
|
+
logger.debug("bookmark chain is not valid, exiting early.")
|
|
37
|
+
return
|
|
38
|
+
|
|
39
|
+
bookmarks = bookmark_chain.ask(query)
|
|
40
|
+
|
|
41
|
+
if not bookmarks.bookmarks:
|
|
42
|
+
logger.info("""
|
|
43
|
+
No bookmarks found for the query 🙁. Please ensure you have performed a "bookworm sync" to update the database
|
|
44
|
+
and the query is relevant to the bookmarks stored.
|
|
45
|
+
""")
|
|
46
|
+
return
|
|
47
|
+
|
|
48
|
+
for index, bookmark in enumerate(bookmarks.bookmarks):
|
|
49
|
+
if logger.isEnabledFor(logging.DEBUG):
|
|
50
|
+
# also shows the source of the bookmark
|
|
51
|
+
logger.info(f"[green][{index}] [/] {bookmark.title} - [link={bookmark.url}]{bookmark.url}[/link] ({bookmark.source})")
|
|
52
|
+
else:
|
|
53
|
+
logger.info(f"[green][{index}] [/] {bookmark.title} - [link={bookmark.url}]{bookmark.url}[/link]")
|
|
54
|
+
|
|
55
|
+
logger.info("Press a number to open the bookmark:")
|
|
56
|
+
while True:
|
|
57
|
+
try:
|
|
58
|
+
raw_input = input("> ")
|
|
59
|
+
selected_index = int(raw_input)
|
|
60
|
+
bookmarks.bookmarks[selected_index].open()
|
|
61
|
+
|
|
62
|
+
break
|
|
63
|
+
except ValueError:
|
|
64
|
+
logger.warning(f"Invalid input: '{raw_input}'. Please enter a number.")
|
|
65
|
+
except IndexError:
|
|
66
|
+
logger.warning(f"Invalid index: '{selected_index}'. Please select a valid index.")
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
if __name__ == "__main__":
|
|
70
|
+
main()
|
|
File without changes
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import logging
|
|
3
|
+
|
|
4
|
+
import duckdb
|
|
5
|
+
from langchain_community.vectorstores import DuckDB as DuckDBVectorStore
|
|
6
|
+
from langchain_openai import ChatOpenAI, AzureChatOpenAI
|
|
7
|
+
from langchain_core.prompts import ChatPromptTemplate
|
|
8
|
+
from langchain_core.runnables import RunnablePassthrough
|
|
9
|
+
from langchain_core.language_models.chat_models import BaseChatModel
|
|
10
|
+
|
|
11
|
+
from bookworm_genai.models import Bookmarks
|
|
12
|
+
from bookworm_genai.storage import full_database_path, _get_embedding_store
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
_system_message = """
|
|
18
|
+
You have knowledge about all the browser bookmarks stored by an individual.
|
|
19
|
+
When a user asks a question, you should be able to search the bookmarks and return the most relevant bookmark title and URL.
|
|
20
|
+
It could be multiple bookmarks.
|
|
21
|
+
If you don't have anything in the context then return empty list
|
|
22
|
+
|
|
23
|
+
The bookmarks available are from the context:
|
|
24
|
+
{context}
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class BookmarkChain:
|
|
29
|
+
def __init__(self):
|
|
30
|
+
logger.debug("Connecting to vector database at: %s", full_database_path)
|
|
31
|
+
self._duckdb_connection = duckdb.connect(full_database_path, read_only=False)
|
|
32
|
+
self.vector_store = DuckDBVectorStore(connection=self._duckdb_connection, embedding=_get_embedding_store())
|
|
33
|
+
|
|
34
|
+
llm = _get_llm()
|
|
35
|
+
llm = llm.with_structured_output(Bookmarks)
|
|
36
|
+
|
|
37
|
+
prompt = ChatPromptTemplate.from_messages([("system", _system_message), ("human", "{query}")])
|
|
38
|
+
|
|
39
|
+
self.chain = {"context": self.vector_store.as_retriever(), "query": RunnablePassthrough()} | prompt | llm
|
|
40
|
+
|
|
41
|
+
def ask(self, query: str) -> Bookmarks:
|
|
42
|
+
logger.debug("Searching for bookmarks with query: %s", query)
|
|
43
|
+
|
|
44
|
+
return self.chain.invoke(query)
|
|
45
|
+
|
|
46
|
+
def is_valid(self) -> bool:
|
|
47
|
+
res = self._duckdb_connection.execute("SELECT COUNT(*) FROM embeddings").fetchall()
|
|
48
|
+
|
|
49
|
+
try:
|
|
50
|
+
res = res[0][0]
|
|
51
|
+
except IndexError:
|
|
52
|
+
logger.warning("validation check failed due to unexpected response from the database.")
|
|
53
|
+
logger.debug("Raw DuckDB Response: %s", res)
|
|
54
|
+
|
|
55
|
+
return False
|
|
56
|
+
|
|
57
|
+
if res == 0:
|
|
58
|
+
logger.warning("No bookmarks were found in database. Please ensure you run 'bookworm sync' before asking questions")
|
|
59
|
+
return False
|
|
60
|
+
else:
|
|
61
|
+
return True
|
|
62
|
+
|
|
63
|
+
def __enter__(self):
|
|
64
|
+
return self
|
|
65
|
+
|
|
66
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
67
|
+
logger.debug("Closing DuckDB connection")
|
|
68
|
+
|
|
69
|
+
self._duckdb_connection.close()
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _get_llm() -> BaseChatModel:
|
|
73
|
+
kwargs = {
|
|
74
|
+
"temperature": 0.0,
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if os.environ.get("OPENAI_API_KEY"):
|
|
78
|
+
# https://api.python.langchain.com/en/latest/chat_models/langchain_openai.chat_models.base.ChatOpenAI.html
|
|
79
|
+
return ChatOpenAI(**kwargs)
|
|
80
|
+
|
|
81
|
+
elif os.environ.get("AZURE_OPENAI_API_KEY"):
|
|
82
|
+
# https://api.python.langchain.com/en/latest/chat_models/langchain_openai.chat_models.azure.AzureChatOpenAI.html
|
|
83
|
+
return AzureChatOpenAI(**kwargs)
|
|
84
|
+
|
|
85
|
+
else:
|
|
86
|
+
raise ValueError("No OpenAI API key found in environment variables")
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
from langchain_community.document_loaders import JSONLoader
|
|
6
|
+
|
|
7
|
+
from bookworm_genai.integrations import browsers
|
|
8
|
+
from bookworm_genai.storage import store_documents
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def sync():
|
|
14
|
+
docs = []
|
|
15
|
+
|
|
16
|
+
for browser, config in browsers.items():
|
|
17
|
+
try:
|
|
18
|
+
platform_config = config[sys.platform]
|
|
19
|
+
except KeyError:
|
|
20
|
+
logger.warning(f"Platform {sys.platform} not supported for browser {browser}")
|
|
21
|
+
continue
|
|
22
|
+
else:
|
|
23
|
+
expanded_path = os.path.expanduser(platform_config["bookmark_file_path"])
|
|
24
|
+
|
|
25
|
+
jq_command = """
|
|
26
|
+
[.roots.bookmark_bar.children, .roots.other.children] |
|
|
27
|
+
flatten |
|
|
28
|
+
.. |
|
|
29
|
+
objects |
|
|
30
|
+
select(.type == "url")
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
logger.info("Loading bookmarks from %s", expanded_path)
|
|
34
|
+
bookmark_bar = JSONLoader(expanded_path, jq_command, text_content=False)
|
|
35
|
+
docs.extend(bookmark_bar.lazy_load())
|
|
36
|
+
|
|
37
|
+
logger.debug(f"{len(docs)} Bookmarks loaded")
|
|
38
|
+
|
|
39
|
+
store_documents(docs)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Browser(str, Enum):
|
|
5
|
+
BRAVE = "brave"
|
|
6
|
+
CHROME = "chrome"
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
# Configuration for various browsers and details about them
|
|
10
|
+
# The bookmark_file_path is the path to the bookmarks file for the browsers, in order for it to be used it must be used in conjunction with
|
|
11
|
+
# os.path.expanduser as it may contain environment variables
|
|
12
|
+
#
|
|
13
|
+
# The platform configuration is keyed off the values from https://docs.python.org/3/library/sys.html#sys.platform
|
|
14
|
+
#
|
|
15
|
+
browsers = {
|
|
16
|
+
Browser.BRAVE: {
|
|
17
|
+
"linux": {"bookmark_file_path": "~/.config/BraveSoftware/Brave-Browser/Default/Bookmarks"},
|
|
18
|
+
"win32": {},
|
|
19
|
+
"darwin": {},
|
|
20
|
+
},
|
|
21
|
+
Browser.CHROME: {
|
|
22
|
+
"linux": {"bookmark_file_path": "~/.config/google-chrome/Default/Bookmarks"},
|
|
23
|
+
"win32": {},
|
|
24
|
+
"darwin": {},
|
|
25
|
+
},
|
|
26
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import subprocess
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
from langchain_core.pydantic_v1 import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
logger = logging.getLogger(__name__)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Bookmark(BaseModel):
|
|
11
|
+
"""
|
|
12
|
+
A bookmark to a website
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
title: str = Field(description="The title of the bookmark")
|
|
16
|
+
url: str = Field(description="The URL of the bookmark")
|
|
17
|
+
source: str = Field(description="The source of the bookmark")
|
|
18
|
+
|
|
19
|
+
def open(self):
|
|
20
|
+
if sys.platform == "win32":
|
|
21
|
+
subprocess.Popen(["start", self.url], shell=True)
|
|
22
|
+
elif sys.platform == "darwin":
|
|
23
|
+
subprocess.Popen(["open", self.url])
|
|
24
|
+
elif sys.platform == "linux":
|
|
25
|
+
subprocess.Popen(["xdg-open", self.url])
|
|
26
|
+
else:
|
|
27
|
+
logger.warning(f'Platform "{sys.platform}" not supported. Printing URL instead')
|
|
28
|
+
logger.info(self.url)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class Bookmarks(BaseModel):
|
|
32
|
+
"""
|
|
33
|
+
A list of bookmarks
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
bookmarks: list[Bookmark] = Field(description="A list of bookmarks")
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import duckdb
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
from platformdirs import PlatformDirs
|
|
6
|
+
from langchain_community.vectorstores import DuckDB as DuckDBVectorStore
|
|
7
|
+
from langchain_core.documents import Document
|
|
8
|
+
from langchain_core.embeddings.embeddings import Embeddings
|
|
9
|
+
from langchain_openai.embeddings import OpenAIEmbeddings, AzureOpenAIEmbeddings
|
|
10
|
+
|
|
11
|
+
appdirs = PlatformDirs("bookworm", "bookworm")
|
|
12
|
+
database_name = "bookmarks.duckdb"
|
|
13
|
+
full_database_path = os.path.join(appdirs.user_data_dir, database_name)
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def store_documents(docs: list[Document]):
|
|
19
|
+
logger.debug(f"creating folder {appdirs.user_data_dir}")
|
|
20
|
+
os.makedirs(appdirs.user_data_dir, exist_ok=True)
|
|
21
|
+
|
|
22
|
+
embeddings = _get_embedding_store()
|
|
23
|
+
|
|
24
|
+
logger.info(f"vectorizing and storing {len(docs)} documents into {full_database_path}")
|
|
25
|
+
with duckdb.connect(full_database_path) as conn:
|
|
26
|
+
logger.debug("dropping existing embeddings table if exists")
|
|
27
|
+
conn.execute("DROP TABLE IF EXISTS embeddings")
|
|
28
|
+
|
|
29
|
+
logger.debug("loading documents")
|
|
30
|
+
DuckDBVectorStore.from_documents(docs, embeddings, connection=conn)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _get_embedding_store() -> Embeddings:
|
|
34
|
+
if os.environ.get("AZURE_OPENAI_API_KEY"):
|
|
35
|
+
logger.debug("Using Azure OpenAI Embeddings")
|
|
36
|
+
return AzureOpenAIEmbeddings()
|
|
37
|
+
|
|
38
|
+
elif os.environ.get("OPENAI_API_KEY"):
|
|
39
|
+
logger.debug("Using OpenAI Embeddings")
|
|
40
|
+
return OpenAIEmbeddings()
|
|
41
|
+
|
|
42
|
+
else:
|
|
43
|
+
raise ValueError("No OpenAI API key found in environment variables")
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "bookworm_genai"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Bookworm - A LLM-powered bookmark search engine"
|
|
5
|
+
authors = ["kiran94"]
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
|
|
8
|
+
[tool.poetry.dependencies]
|
|
9
|
+
python = "^3.9"
|
|
10
|
+
langchain = "^0.2.12"
|
|
11
|
+
langchain-community = "^0.2.11"
|
|
12
|
+
langchain-openai = "^0.1.20"
|
|
13
|
+
jq = "^1.7.0"
|
|
14
|
+
duckdb = "^1.0.0"
|
|
15
|
+
rich = "^13.7.1"
|
|
16
|
+
platformdirs = "^4.2.2"
|
|
17
|
+
pandas = "^2.2.2"
|
|
18
|
+
|
|
19
|
+
[tool.poetry.group.dev.dependencies]
|
|
20
|
+
pytest = "^8.3.2"
|
|
21
|
+
ruff = "^0.5.6"
|
|
22
|
+
pytest-cov = "^5.0.0"
|
|
23
|
+
pytest-github-actions-annotate-failures = "^0.2.0"
|
|
24
|
+
|
|
25
|
+
[build-system]
|
|
26
|
+
requires = ["poetry-core"]
|
|
27
|
+
build-backend = "poetry.core.masonry.api"
|
|
28
|
+
|
|
29
|
+
[tool.poetry.scripts]
|
|
30
|
+
bookworm = 'bookworm_genai.__main__:main'
|
|
31
|
+
|
|
32
|
+
[tool.ruff]
|
|
33
|
+
line-length = 160
|