Skip to content

Commit

Permalink
Add web server
Browse files Browse the repository at this point in the history
  • Loading branch information
gadhagod committed Jul 25, 2023
1 parent 24d12c6 commit a6306e2
Show file tree
Hide file tree
Showing 10 changed files with 246 additions and 54 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/store-embeddings.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
- name: Install requirements
run: pip install -r requirements.txt
- name: Generate links
run: python3 generate_links.py --reset
run: python3 scripts/ingest.py --reset
env:
ROCKSET_API_KEY: ${{ secrets.ROCKSET_API_KEY }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Zelda GPT

## Setup
```bash
export ROCKSET_API_KEY="<rockset api key>"
export ROCKSET_API_SERVER="<rockset api server>"
export OPENAI_API_KEY="<open api key>"
pip3 install -r requirements.txt
```

## Data ingestion
```
python3 scripts/ingest.py
```

## Starting the server
```python3
python3 main.py
```

## Deployment
See [Heroku docs](https://devcenter.heroku.com/articles/github-integration#manual-deploys).
56 changes: 25 additions & 31 deletions constants.py
Original file line number Diff line number Diff line change
@@ -1,45 +1,39 @@
from sys import argv
from time import sleep
from os import getenv
from rockset import RocksetClient, Regions, exceptions
from rockset import RocksetClient, exceptions
from langchain.embeddings import OpenAIEmbeddings
from langchain.vectorstores import Rockset as RocksetStore
from sql import ingest_tranformation

rockset_api_server = getenv("ROCKSET_API_SERVER")
rockset_api_key = getenv("ROCKSET_API_KEY")
openai_api_key = getenv("OPENAI_API_KEY")

rockset = RocksetClient(Regions.rs2, rockset_api_key)
rockset = RocksetClient(rockset_api_server, rockset_api_key)

def collection_exists():
try:
rockset.Collections.get(collection="hyrule-compendium-ai")
except exceptions.NotFoundException:
return False
return True
class Collection:
def __init__(self, workspace, name):
self.workspace = workspace
self.name = name

def exists(self):
try:
rockset.Collections.get(collection=self.name)
except exceptions.NotFoundException:
return False
return True

def collection_is_ready():
return rockset.Collections.get(collection="hyrule-compendium-ai").data.status == "READY"
def is_ready(self):
return rockset.Collections.get(collection=self.name).data.status == "READY"

def delete_collection():
print("Deleting collection \"commons.hyrule-compendium-ai\"")
rockset.Collections.delete(collection="hyrule-compendium-ai")

def create_collection():
print("Creating collection \"commons.hyrule-compendium-ai\"")
rockset.Collections.create_s3_collection(name="hyrule-compendium-ai", field_mapping_query=ingest_tranformation)
def delete(self):
print(f"Deleting collection \"{self.workspace}.{self.name}\"")
rockset.Collections.delete(collection=self.name)
def create(self):
print(f"Creating collection \"{self.workspace}.{self.name}\"")
rockset.Collections.create_s3_collection(name=self.name, field_mapping_query=ingest_tranformation)

if "--reset" in argv:
if collection_exists():
delete_collection()
while collection_exists():
sleep(1)

create_collection()
while not collection_exists():
sleep(1)
while not collection_is_ready():
sleep(1)
collection = Collection("commons", "hyrule-compendium-ai")

openai = OpenAIEmbeddings(
openai_api_key=openai_api_key,
Expand All @@ -48,7 +42,7 @@ def create_collection():
store = RocksetStore(
rockset,
openai,
"hyrule-compendium-ai",
collection.name,
"text",
"embedding"
)
20 changes: 20 additions & 0 deletions main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from flask import Flask, render_template
from flask_socketio import SocketIO, send
from search import ask

app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret!'
socketio = SocketIO(app)

@app.route("/")
def main():
return render_template("index.jinja")

@socketio.on("message")
def handle_message(question):
print('received question: ' + question)
send(ask(question))


if __name__ == '__main__':
socketio.run(app, debug=True)
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,4 @@ typing-inspect==0.9.0
typing_extensions==4.7.1
urllib3==1.26.16
yarl==1.9.2
eventlet==0.30.2
43 changes: 29 additions & 14 deletions generate_links.py → scripts/ingest.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,21 @@
from requests import get, exceptions
from bs4 import BeautifulSoup
from sys import argv
from time import sleep
from requests import get, exceptions
from bs4 import BeautifulSoup
from langchain.text_splitter import RecursiveCharacterTextSplitter
from constants import store, rockset as rs
from constants import store, collection, rockset as rs

if "--reset" in argv:
if collection.exists():
collection.delete()
while collection.exists():
sleep(1)

collection.create()
while not collection.exists():
sleep(1)
while not collection.is_ready():
sleep(1)

text_splitter = RecursiveCharacterTextSplitter(
chunk_size = 1000,
Expand All @@ -22,6 +34,14 @@ def __init__(self, init_value=None):
self.first = LinkNode(init_value, None) if init_value is not None else None
self.last = self.first

def _add(self, link):
node = LinkNode(link)
if self.first is None and self.last is None: # empty queue
self.first = node
else:
self.last.next = node
self.last = node

def remove(self):
if self.first is self.last: # one item in queue
link = self.first.link
Expand All @@ -31,21 +51,13 @@ def remove(self):
prev_first = self.first
self.first = self.first.next
return prev_first.link

def add(self, link):
node = LinkNode(link)
if self.first is None and self.last is None: # empty queue
self.first = node
else:
self.last.next = node
self.last = node

def is_empty(self):
return self.first is None

def add_elem_links(self, a_elems):
for i in a_elems:
self.add(i["href"])
self._add(i["href"])

def __str__(self) -> str:
if self.is_empty():
Expand Down Expand Up @@ -97,11 +109,14 @@ def _is_category(self, link):
def _scrape(self, link):
soup = BeautifulSoup(get(link).text, "html.parser")

if self._is_category(link): # we do not need to generate embeddings for this page
if self._is_category(link):
# we do not need to generate embeddings for this page,
# but we still need to add it to the collection to
# make sure we don't scrape it again
rs.Documents.add_documents(
collection="hyrule-compendium-ai",
data=[{
"source": link, # make sure we do not scrape this page again
"source": link,
"embedding": None
}]
)
Expand Down
20 changes: 12 additions & 8 deletions search.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,25 @@
from langchain.chat_models import ChatOpenAI
from langchain.chains import RetrievalQA

chat_bot = ChatOpenAI(model_name="gpt-3.5-turbo-0613", temperature=0.99)
chat_bot = ChatOpenAI(model_name="gpt-3.5-turbo-0613", temperature=0.8)

chat_bot(
[
SystemMessage(content='''The context I have you is about the "Legend of Zelda" series. The questions I am about to ask you are about this game series. Use the context given and your knowledge about the games to answer my questions. You are a Goddess that knows everything about the fictional world of Hyrule.''')
SystemMessage(content='''The context I have you is about the "Legend of Zelda" series. The questions I am about to ask you are about this game series. Use the context given and your knowledge about the games to answer my questions.''')
]
)
qa_chain = RetrievalQA.from_chain_type(chat_bot, retriever=store.as_retriever())

retriever = store.as_retriever()
retriever.search_kwargs = {"where_str": "embedding IS NOT NULL"}

qa_chain = RetrievalQA.from_chain_type(
chat_bot,
retriever=retriever
)

def ask(question):
if question:
print(
qa_chain({"query": question})["result"]
)
return qa_chain({"query": f"""Answer my question and be sure to make your answer as long as possible. Question: {question}"""})["result"]

if __name__ == "__main__":
while True:
ask(input("question: "))
print(ask(input("question: ")))
55 changes: 55 additions & 0 deletions static/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
var socket = io();

socket.on("connect", () => {
let askBtn = document.getElementById("ask");
let questionBox = document.getElementById("question");
let answerContainer = document.getElementById("answer-container");
let thinkingMsg = document.getElementById("thinking");
let responseBox = document.getElementById("response");

let ask = () => {
let question = document.getElementById("question").value;
socket.send(question);
thinkingMsg.classList.remove("hidden");
responseBox.classList.add("hidden");
answerContainer.classList.remove("hidden");
};

let answer = (response) => {
responseBox.innerText = response;
thinkingMsg.classList.add("hidden");
responseBox.classList.remove("hidden");
};


questionBox.addEventListener("input", () => {
if (questionBox.value) {
askBtn.removeAttribute("disabled")
} else {
askBtn.setAttribute("disabled", "")
}
});

questionBox.addEventListener("keypress", (event) => {
if (event.key === "Enter") {
event.preventDefault();
ask();
}
});

askBtn.addEventListener("click", ask);

socket.on("message", answer);

console.log("Connected!");
});

var ellipses = document.getElementById("ellipses");

setInterval(() => {
if (ellipses.innerText.length == 2) {
ellipses.innerText = "";
} else {
ellipses.innerText += ".";
}
}, 1000);
32 changes: 32 additions & 0 deletions static/styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
.input-group, #answer-container {
max-width: 75%;
}

h1.gold {
color: gold;
}

button.gold {
background-color: gold
}

button.gold:disabled {
background-color: lightgoldenrodyellow
}

button.gold:hover {
background-color: gold
}

.hidden {
display: none;
}

.showing {
display: block;
}

#star {
text-align:right;
margin-right: 8px;
}
49 changes: 49 additions & 0 deletions templates/index.jinja
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<html>
<head>
<title>ZeldaGPT</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
</head>
<body class="bg-secondary">
<div id="star" class="mr-3 mt-3">
<a class="github-button" href="https://github.com/gadhagod/ZeldaGPT" data-icon="octicon-star">Star</a>
</div>

<div class="header mt-5 text-center">
<p class="display-1 text-center text-light">
<span class="text-warning gold">Zelda</span>GPT
</p>
</div>

<div class="input-group input-group-lg mx-auto mt-5">
<button disabled class="btn bg-dark btn-outline-secondary text-light" type="button">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-search" viewBox="0 0 16 16">
<path d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001c.03.04.062.078.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0z"/>
</svg>
</button>
<input type="text" class="bg-dark text-white form-control" placeholder="Ask a question..." type="text" id="question" autofocus>
<button id="ask" class="btn btn-outline-secondary text-dark gold" type="button" disabled>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-return-left" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M14.5 1.5a.5.5 0 0 1 .5.5v4.8a2.5 2.5 0 0 1-2.5 2.5H2.707l3.347 3.346a.5.5 0 0 1-.708.708l-4.2-4.2a.5.5 0 0 1 0-.708l4-4a.5.5 0 1 1 .708.708L2.707 8.3H12.5A1.5 1.5 0 0 0 14 6.8V2a.5.5 0 0 1 .5-.5z"/>
</svg>
</button>
</div>

<div id="answer-container" class="form-control mt-5 mx-auto bg-dark hidden" id="answer">
<p id="thinking" class="hidden text-secondary">
<!-- 🧠🤖 -->Give me a moment to think.<span id="ellipses"></span>
</p>
<p id="response" class="hidden text-white-50"></p>
</div>

<!-- Place this tag where you want the button to render. -->

<footer class="fixed-bottom text-center">
<p class="mb-2">Built with ❤️ by Aarav Borthakur</p>
</footer>

<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.1/socket.io.js" integrity="sha512-q/dWJ3kcmjBLU4Qc47E4A9kTB4m3wuTY7vkFJDTZKjTs8jhyGQnaUrxa0Ytd0ssMZhbNua9hE+E7Qv1j+DyZwA==" crossorigin="anonymous"></script>
<script async defer src="https://buttons.github.io/buttons.js"></script>
<script src="{{ url_for('static', filename='index.js') }}"></script>
</body>
</html>

0 comments on commit a6306e2

Please sign in to comment.