Skip to content

Commit 09c8b45

Browse files
committed
C
C ready for initial publishing
1 parent 343a579 commit 09c8b45

File tree

9 files changed

+190
-185
lines changed

9 files changed

+190
-185
lines changed

.github/workflows/publish_pypi.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ jobs:
1717
- name: Set up Python
1818
uses: actions/setup-python@v2
1919
with:
20-
python-version: "3.11.4"
20+
python-version: "3.11.7"
2121

2222
- name: Build and publish package
2323
env:

pyproject.toml

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ classifiers = [
2121
"Programming Language :: Python",
2222
"Programming Language :: Python :: 3",
2323
]
24-
keywords = ["chatbot", "terminal", "openai", "rag"]
24+
keywords = ["chatbot", "terminal", "openai", "rag", "bumpver"]
2525

2626
[build-system]
2727
requires = ["setuptools>=61", "wheel"]
@@ -34,4 +34,16 @@ dev = ["black", "pytest", "pre-commit", "twine"]
3434
Homepage = "https://github.com/SAMAD101/Chino"
3535

3636
[project.scripts]
37-
chino = "chino.__main__:main"
37+
chino = "chino.__main__:app"
38+
39+
[tool.bumpver]
40+
current_version = "1.0.0"
41+
version_pattern = "MAJOR.MINOR.PATCH"
42+
commit_message = "Bump version {old_version} -> {new_version}"
43+
commit = true
44+
tag = true
45+
push = false
46+
47+
[tool.bumpver.file_patterns]
48+
"pyproject.toml" = ['current_version = "{version}"', 'version = "{version}"']
49+
"src/reader/__init__.py" = ["{version}"]

src/chino/__main__.py

Lines changed: 34 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1,91 +1,42 @@
1-
# !/usr/bin/python
1+
import os
22

3-
import typer
3+
from typer import Typer
44

5-
from typing import List, Union
5+
from .query import Query
6+
from .conversation import Conversation
7+
from .migrations import Migration
68

7-
from rich.console import Console
89

9-
from langchain.prompts.chat import (
10-
ChatPromptTemplate,
11-
HumanMessagePromptTemplate,
12-
SystemMessagePromptTemplate,
10+
app: Typer = Typer(
11+
help="Chino is a chatbot based on OpenAI. It can also provide responses about queries on user-provided data."
1312
)
14-
from langchain.schema import HumanMessage, SystemMessage, BaseMessage
15-
from langchain_openai import ChatOpenAI
1613

17-
from .store.query import query_data
18-
from .store.migrations import generate_data_store
1914

20-
21-
app = typer.Typer()
22-
console = Console()
23-
24-
model: ChatOpenAI = ChatOpenAI()
25-
26-
messages: List[Union[SystemMessage, HumanMessage]] = [
27-
SystemMessage(
28-
content="You are Chino, a chatbot based on ChatGPT. 'Chino' means 'intelligence' in Japanese."
29-
),
30-
]
31-
32-
33-
def get_response(prompt: str) -> None:
34-
global model, messages
35-
36-
with console.status("[i]thinking...[/i]"):
37-
messages.append(HumanMessage(content=prompt))
38-
response: BaseMessage = model.invoke(messages)
39-
messages.append(SystemMessage(content=response.content))
40-
console.print(f"[b blue]Chino:[/b blue] {response.content}")
41-
console.rule()
42-
43-
44-
def run_query(prompt: str) -> None:
45-
global model, messages
46-
47-
with console.status("[i]thinking...[/i]"):
48-
query_text, query_sources = query_data(prompt)
49-
messages.append(HumanMessage(content=query_text))
50-
response: BaseMessage = model.invoke(messages)
51-
messages.append(SystemMessage(content=response.content))
52-
console.print(
53-
f"[b blue]Chino:[/b blue] {response.content}\n\n[i violet]Sources:[/i violet]{query_sources}"
54-
)
55-
console.rule()
56-
57-
58-
def run_conversation(prompt: str, query: bool) -> None:
59-
global model, messages
60-
61-
if prompt:
62-
if query or prompt.lower().startswith("\\q:"):
63-
run_query(prompt)
64-
return
65-
get_response(prompt)
66-
return
67-
68-
while True:
69-
prompt: str = console.input("[b green]You: [/b green]")
70-
if prompt == "quit":
71-
break
72-
elif query or prompt.lower().startswith("\\query:"):
73-
run_query(prompt)
74-
continue
75-
get_response(prompt)
76-
77-
78-
def main(
79-
prompt: str = typer.Option(None, "-p", "--prompt", help="Prompt for ChatGPT"),
80-
query: bool = typer.Option(False, "-q", "--query", help="Query for your data"),
81-
process: bool = typer.Option(False, "--process", help="Process your data"),
15+
@app.command()
16+
def migrate(
17+
chroma_path: str = os.path.expanduser("~/.local/share/chino/chroma/"),
18+
data_path: str = os.path.expanduser("~/.local/share/chino/data/"),
8219
) -> None:
83-
if process:
84-
console.status("Processing your data...")
85-
generate_data_store()
86-
return
87-
run_conversation(prompt, query)
88-
89-
90-
if __name__ == "__main__":
91-
typer.run(main)
20+
"""Migrate the data to the chroma using vector embeddings."""
21+
22+
migration = Migration(chroma_path, data_path)
23+
migration.generate_data_store()
24+
25+
26+
@app.command("start")
27+
def main():
28+
"""Start the main event loop function. A chat interface will be opened."""
29+
30+
conversation: Conversation = Conversation()
31+
try:
32+
while True:
33+
prompt = conversation.console.input("[bold green]You: [/bold green]")
34+
if prompt == "quit":
35+
conversation.console.print("[bold red]Quiting...[/bold red]")
36+
break
37+
elif prompt.lower().startswith("\\q:"):
38+
conversation.run_query(prompt)
39+
continue
40+
conversation.get_response(prompt)
41+
except KeyboardInterrupt:
42+
conversation.console.print("\n[bold red]Quiting...[/bold red]")

src/chino/conversation.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import os
2+
3+
from typing import List, Union
4+
5+
from rich.console import Console
6+
7+
from langchain.schema import HumanMessage, SystemMessage, BaseMessage
8+
from langchain_openai import ChatOpenAI
9+
10+
from .query import Query
11+
12+
13+
class Conversation:
14+
def __init__(self) -> None:
15+
self.model = ChatOpenAI()
16+
self.messages: List[Union[SystemMessage, HumanMessage]] = [
17+
SystemMessage(
18+
content="You are Chino, a chatbot based on ChatGPT. 'Chino' means 'intelligence' in Japanese."
19+
),
20+
]
21+
self.console = Console()
22+
23+
def get_response(self, prompt: str) -> None:
24+
with self.console.status("[i]thinking...[/i]"):
25+
self.messages.append(HumanMessage(content=prompt))
26+
response: BaseMessage = self.model.invoke(self.messages)
27+
self.messages.append(SystemMessage(content=response.content))
28+
self.console.print(f"[b blue]Chino:[/b blue] {response.content}")
29+
self.console.rule()
30+
31+
def run_query(self, prompt: str) -> None:
32+
with self.console.status("[i]thinking...[/i]"):
33+
query_text, query_sources = Query(
34+
prompt, os.path.expanduser("~/.local/share/chino/chroma/")
35+
).query_data()
36+
self.messages.append(HumanMessage(content=query_text))
37+
response: BaseMessage = self.model.invoke(self.messages)
38+
self.messages.append(SystemMessage(content=response.content))
39+
self.console.print(
40+
f"[b blue]Chino:[/b blue] {response.content}\n\n[i violet]Sources:[/i violet]{query_sources}"
41+
)
42+
self.console.rule()

src/chino/migrations.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import os
2+
import shutil
3+
4+
from typing import Any, List
5+
6+
from langchain_community.document_loaders import DirectoryLoader
7+
from langchain.text_splitter import RecursiveCharacterTextSplitter
8+
from langchain.schema import Document
9+
from langchain_openai import OpenAIEmbeddings
10+
from langchain.vectorstores.chroma import Chroma
11+
12+
13+
class Migration:
14+
def __init__(self, chroma_path: str = None, data_path: str = None) -> None:
15+
self.CHROMA_PATH: str = chroma_path
16+
self.DATA_PATH: str = data_path
17+
18+
def generate_data_store(self) -> None:
19+
documents: Any = Migration._load_documents(self.DATA_PATH)
20+
chunks: List[Document] = Migration._split_text(documents)
21+
Migration._save_to_chroma(chunks, self.CHROMA_PATH)
22+
23+
@classmethod
24+
def _load_documents(cls, data_path):
25+
loader: DirectoryLoader = DirectoryLoader(data_path)
26+
documents: Any = loader.load()
27+
return documents
28+
29+
@classmethod
30+
def _split_text(cls, documents: List[Document]) -> List[Document]:
31+
text_splitter: RecursiveCharacterTextSplitter = RecursiveCharacterTextSplitter(
32+
chunk_size=300,
33+
chunk_overlap=100,
34+
length_function=len,
35+
add_start_index=True,
36+
)
37+
chunks: List[Document] = text_splitter.split_documents(documents)
38+
print(f"Split {len(documents)} documents into {len(chunks)} chunks.")
39+
40+
return chunks
41+
42+
@classmethod
43+
def _save_to_chroma(cls, chunks: List[Document], chroma_path: str):
44+
# Clear out the database first.
45+
if os.path.exists(chroma_path):
46+
shutil.rmtree(chroma_path)
47+
48+
# Create a new DB from the documents.
49+
db: Chroma = Chroma.from_documents(
50+
chunks, OpenAIEmbeddings(), persist_directory=chroma_path
51+
)
52+
db.persist()
53+
print(f"Saved {len(chunks)} chunks to {chroma_path}.")

src/chino/query.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import sys
2+
3+
from typing import List, Tuple
4+
5+
from langchain.vectorstores.chroma import Chroma
6+
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
7+
from langchain.prompts import ChatPromptTemplate
8+
9+
10+
class Query:
11+
def __init__(self, prompt: str = None, chroma_path: str = None) -> None:
12+
self.query_text = prompt
13+
self.CHROMA_PATH = chroma_path
14+
15+
def _prepare_db(self) -> Chroma:
16+
return Chroma(
17+
persist_directory=self.CHROMA_PATH, embedding_function=OpenAIEmbeddings()
18+
)
19+
20+
def query_data(self) -> Tuple[str, List[str]]:
21+
# query text will be the prompt - provided by the user - to be processed using the embeddings and the model
22+
PROMPT_TEMPLATE: str = """
23+
Details
24+
{context}
25+
---
26+
Answer this question based on the above details and previous messages: {question}
27+
"""
28+
db: Chroma = self._prepare_db()
29+
30+
# Search the DB.
31+
results: List = db.similarity_search_with_relevance_scores(self.query_text, k=3)
32+
if len(results) == 0 or results[0][1] < 0.7:
33+
print(f"Unable to find matching results.")
34+
sys.exit()
35+
36+
context_text: str = "\n\n---\n\n".join(
37+
[doc.page_content for doc, _score in results]
38+
)
39+
prompt_template = ChatPromptTemplate.from_template(PROMPT_TEMPLATE)
40+
query_prompt = "QUERY:" + prompt_template.format(
41+
context=context_text, question=self.query_text
42+
)
43+
# This prompt will serve as a context for the model to generate a response.
44+
45+
query_sources = [doc.metadata.get("source", None) for doc, _score in results]
46+
return query_prompt, query_sources

src/chino/store/__init__.py

Whitespace-only changes.

src/chino/store/migrations.py

Lines changed: 0 additions & 52 deletions
This file was deleted.

0 commit comments

Comments
 (0)