Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/better structure #348

Merged
merged 8 commits into from
Sep 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -169,4 +169,5 @@ application/vectors/

**/yarn.lock

node_modules/
node_modules/
.vscode/settings.json
Empty file added application/api/__init__.py
Empty file.
Empty file.
365 changes: 365 additions & 0 deletions application/api/answer/routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,365 @@
import asyncio
import os
from flask import Blueprint, request, Response
import json
import datetime
import logging
import traceback

from pymongo import MongoClient
from bson.objectid import ObjectId
from transformers import GPT2TokenizerFast



from application.core.settings import settings
from application.llm.openai import OpenAILLM, AzureOpenAILLM
from application.vectorstore.faiss import FaissStore
from application.error import bad_request



logger = logging.getLogger(__name__)

mongo = MongoClient(settings.MONGO_URI)
db = mongo["docsgpt"]
conversations_collection = db["conversations"]
vectors_collection = db["vectors"]
answer = Blueprint('answer', __name__)

if settings.LLM_NAME == "gpt4":
gpt_model = 'gpt-4'

Check warning on line 31 in application/api/answer/routes.py

View check run for this annotation

Codecov / codecov/patch

application/api/answer/routes.py#L31

Added line #L31 was not covered by tests
else:
gpt_model = 'gpt-3.5-turbo'

if settings.SELF_HOSTED_MODEL:
from langchain.llms import HuggingFacePipeline
from transformers import AutoModelForCausalLM, AutoTokenizer, pipeline

Check warning on line 37 in application/api/answer/routes.py

View check run for this annotation

Codecov / codecov/patch

application/api/answer/routes.py#L36-L37

Added lines #L36 - L37 were not covered by tests

model_id = settings.LLM_NAME # hf model id (Arc53/docsgpt-7b-falcon, Arc53/docsgpt-14b)
tokenizer = AutoTokenizer.from_pretrained(model_id)
model = AutoModelForCausalLM.from_pretrained(model_id)
pipe = pipeline(

Check warning on line 42 in application/api/answer/routes.py

View check run for this annotation

Codecov / codecov/patch

application/api/answer/routes.py#L39-L42

Added lines #L39 - L42 were not covered by tests
"text-generation", model=model,
tokenizer=tokenizer, max_new_tokens=2000,
device_map="auto", eos_token_id=tokenizer.eos_token_id
)
hf = HuggingFacePipeline(pipeline=pipe)

Check warning on line 47 in application/api/answer/routes.py

View check run for this annotation

Codecov / codecov/patch

application/api/answer/routes.py#L47

Added line #L47 was not covered by tests

# load the prompts
current_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
with open(os.path.join(current_dir, "prompts", "combine_prompt.txt"), "r") as f:
template = f.read()

with open(os.path.join(current_dir, "prompts", "combine_prompt_hist.txt"), "r") as f:
template_hist = f.read()

with open(os.path.join(current_dir, "prompts", "question_prompt.txt"), "r") as f:
template_quest = f.read()

with open(os.path.join(current_dir, "prompts", "chat_combine_prompt.txt"), "r") as f:
chat_combine_template = f.read()

with open(os.path.join(current_dir, "prompts", "chat_reduce_prompt.txt"), "r") as f:
chat_reduce_template = f.read()

api_key_set = settings.API_KEY is not None
embeddings_key_set = settings.EMBEDDINGS_KEY is not None


async def async_generate(chain, question, chat_history):
result = await chain.arun({"question": question, "chat_history": chat_history})
return result

Check warning on line 72 in application/api/answer/routes.py

View check run for this annotation

Codecov / codecov/patch

application/api/answer/routes.py#L71-L72

Added lines #L71 - L72 were not covered by tests


def count_tokens(string):
tokenizer = GPT2TokenizerFast.from_pretrained('gpt2')
return len(tokenizer(string)['input_ids'])

Check warning on line 77 in application/api/answer/routes.py

View check run for this annotation

Codecov / codecov/patch

application/api/answer/routes.py#L76-L77

Added lines #L76 - L77 were not covered by tests


def run_async_chain(chain, question, chat_history):
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
result = {}
try:
answer = loop.run_until_complete(async_generate(chain, question, chat_history))

Check warning on line 85 in application/api/answer/routes.py

View check run for this annotation

Codecov / codecov/patch

application/api/answer/routes.py#L81-L85

Added lines #L81 - L85 were not covered by tests
finally:
loop.close()
result["answer"] = answer
return result

Check warning on line 89 in application/api/answer/routes.py

View check run for this annotation

Codecov / codecov/patch

application/api/answer/routes.py#L87-L89

Added lines #L87 - L89 were not covered by tests


def get_vectorstore(data):
if "active_docs" in data:
if data["active_docs"].split("/")[0] == "local":
if data["active_docs"].split("/")[1] == "default":
vectorstore = ""

Check warning on line 96 in application/api/answer/routes.py

View check run for this annotation

Codecov / codecov/patch

application/api/answer/routes.py#L93-L96

Added lines #L93 - L96 were not covered by tests
else:
vectorstore = "indexes/" + data["active_docs"]

Check warning on line 98 in application/api/answer/routes.py

View check run for this annotation

Codecov / codecov/patch

application/api/answer/routes.py#L98

Added line #L98 was not covered by tests
else:
vectorstore = "vectors/" + data["active_docs"]
if data["active_docs"] == "default":
vectorstore = ""

Check warning on line 102 in application/api/answer/routes.py

View check run for this annotation

Codecov / codecov/patch

application/api/answer/routes.py#L100-L102

Added lines #L100 - L102 were not covered by tests
else:
vectorstore = ""
vectorstore = os.path.join("application", vectorstore)
return vectorstore

Check warning on line 106 in application/api/answer/routes.py

View check run for this annotation

Codecov / codecov/patch

application/api/answer/routes.py#L104-L106

Added lines #L104 - L106 were not covered by tests


# def get_docsearch(vectorstore, embeddings_key):
# if settings.EMBEDDINGS_NAME == "openai_text-embedding-ada-002":
# if is_azure_configured():
# os.environ["OPENAI_API_TYPE"] = "azure"
# openai_embeddings = OpenAIEmbeddings(model=settings.AZURE_EMBEDDINGS_DEPLOYMENT_NAME)
# else:
# openai_embeddings = OpenAIEmbeddings(openai_api_key=embeddings_key)
# docsearch = FAISS.load_local(vectorstore, openai_embeddings)
# elif settings.EMBEDDINGS_NAME == "huggingface_sentence-transformers/all-mpnet-base-v2":
# docsearch = FAISS.load_local(vectorstore, HuggingFaceHubEmbeddings())
# elif settings.EMBEDDINGS_NAME == "huggingface_hkunlp/instructor-large":
# docsearch = FAISS.load_local(vectorstore, HuggingFaceInstructEmbeddings())
# elif settings.EMBEDDINGS_NAME == "cohere_medium":
# docsearch = FAISS.load_local(vectorstore, CohereEmbeddings(cohere_api_key=embeddings_key))
# return docsearch


def is_azure_configured():
return settings.OPENAI_API_BASE and settings.OPENAI_API_VERSION and settings.AZURE_DEPLOYMENT_NAME

Check warning on line 127 in application/api/answer/routes.py

View check run for this annotation

Codecov / codecov/patch

application/api/answer/routes.py#L127

Added line #L127 was not covered by tests


def complete_stream(question, docsearch, chat_history, api_key, conversation_id):
if is_azure_configured():
llm = AzureOpenAILLM(

Check warning on line 132 in application/api/answer/routes.py

View check run for this annotation

Codecov / codecov/patch

application/api/answer/routes.py#L131-L132

Added lines #L131 - L132 were not covered by tests
openai_api_key=api_key,
openai_api_base=settings.OPENAI_API_BASE,
openai_api_version=settings.OPENAI_API_VERSION,
deployment_name=settings.AZURE_DEPLOYMENT_NAME,
)
else:
logger.debug("plain OpenAI")
llm = OpenAILLM(api_key=api_key)

Check warning on line 140 in application/api/answer/routes.py

View check run for this annotation

Codecov / codecov/patch

application/api/answer/routes.py#L139-L140

Added lines #L139 - L140 were not covered by tests

docs = docsearch.search(question, k=2)

Check warning on line 142 in application/api/answer/routes.py

View check run for this annotation

Codecov / codecov/patch

application/api/answer/routes.py#L142

Added line #L142 was not covered by tests
# join all page_content together with a newline
docs_together = "\n".join([doc.page_content for doc in docs])
p_chat_combine = chat_combine_template.replace("{summaries}", docs_together)
messages_combine = [{"role": "system", "content": p_chat_combine}]
source_log_docs = []
for doc in docs:
if doc.metadata:
data = json.dumps({"type": "source", "doc": doc.page_content, "metadata": doc.metadata})
source_log_docs.append({"title": doc.metadata['title'].split('/')[-1], "text": doc.page_content})

Check warning on line 151 in application/api/answer/routes.py

View check run for this annotation

Codecov / codecov/patch

application/api/answer/routes.py#L144-L151

Added lines #L144 - L151 were not covered by tests
else:
data = json.dumps({"type": "source", "doc": doc.page_content})
source_log_docs.append({"title": doc.page_content, "text": doc.page_content})
yield f"data:{data}\n\n"

Check warning on line 155 in application/api/answer/routes.py

View check run for this annotation

Codecov / codecov/patch

application/api/answer/routes.py#L153-L155

Added lines #L153 - L155 were not covered by tests

if len(chat_history) > 1:
tokens_current_history = 0

Check warning on line 158 in application/api/answer/routes.py

View check run for this annotation

Codecov / codecov/patch

application/api/answer/routes.py#L157-L158

Added lines #L157 - L158 were not covered by tests
# count tokens in history
chat_history.reverse()
for i in chat_history:
if "prompt" in i and "response" in i:
tokens_batch = count_tokens(i["prompt"]) + count_tokens(i["response"])
if tokens_current_history + tokens_batch < settings.TOKENS_MAX_HISTORY:
tokens_current_history += tokens_batch
messages_combine.append({"role": "user", "content": i["prompt"]})
messages_combine.append({"role": "system", "content": i["response"]})
messages_combine.append({"role": "user", "content": question})

Check warning on line 168 in application/api/answer/routes.py

View check run for this annotation

Codecov / codecov/patch

application/api/answer/routes.py#L160-L168

Added lines #L160 - L168 were not covered by tests

response_full = ""
completion = llm.gen_stream(model=gpt_model, engine=settings.AZURE_DEPLOYMENT_NAME,

Check warning on line 171 in application/api/answer/routes.py

View check run for this annotation

Codecov / codecov/patch

application/api/answer/routes.py#L170-L171

Added lines #L170 - L171 were not covered by tests
messages=messages_combine)
for line in completion:
data = json.dumps({"answer": str(line)})
response_full += str(line)
yield f"data: {data}\n\n"

Check warning on line 176 in application/api/answer/routes.py

View check run for this annotation

Codecov / codecov/patch

application/api/answer/routes.py#L173-L176

Added lines #L173 - L176 were not covered by tests

# save conversation to database
if conversation_id is not None:
conversations_collection.update_one(

Check warning on line 180 in application/api/answer/routes.py

View check run for this annotation

Codecov / codecov/patch

application/api/answer/routes.py#L179-L180

Added lines #L179 - L180 were not covered by tests
{"_id": ObjectId(conversation_id)},
{"$push": {"queries": {"prompt": question, "response": response_full, "sources": source_log_docs}}},
)

else:
# create new conversation
# generate summary
messages_summary = [{"role": "assistant", "content": "Summarise following conversation in no more than 3 "

Check warning on line 188 in application/api/answer/routes.py

View check run for this annotation

Codecov / codecov/patch

application/api/answer/routes.py#L188

Added line #L188 was not covered by tests
"words, respond ONLY with the summary, use the same "
"language as the system \n\nUser: " + question + "\n\n" +
"AI: " +
response_full},
{"role": "user", "content": "Summarise following conversation in no more than 3 words, "
"respond ONLY with the summary, use the same language as the "
"system"}]

completion = llm.gen(model=gpt_model, engine=settings.AZURE_DEPLOYMENT_NAME,

Check warning on line 197 in application/api/answer/routes.py

View check run for this annotation

Codecov / codecov/patch

application/api/answer/routes.py#L197

Added line #L197 was not covered by tests
messages=messages_summary, max_tokens=30)
conversation_id = conversations_collection.insert_one(

Check warning on line 199 in application/api/answer/routes.py

View check run for this annotation

Codecov / codecov/patch

application/api/answer/routes.py#L199

Added line #L199 was not covered by tests
{"user": "local",
"date": datetime.datetime.utcnow(),
"name": completion,
"queries": [{"prompt": question, "response": response_full, "sources": source_log_docs}]}
).inserted_id

# send data.type = "end" to indicate that the stream has ended as json
data = json.dumps({"type": "id", "id": str(conversation_id)})
yield f"data: {data}\n\n"
data = json.dumps({"type": "end"})
yield f"data: {data}\n\n"

Check warning on line 210 in application/api/answer/routes.py

View check run for this annotation

Codecov / codecov/patch

application/api/answer/routes.py#L207-L210

Added lines #L207 - L210 were not covered by tests


@answer.route("/stream", methods=["POST"])
def stream():
data = request.get_json()

Check warning on line 215 in application/api/answer/routes.py

View check run for this annotation

Codecov / codecov/patch

application/api/answer/routes.py#L215

Added line #L215 was not covered by tests
# get parameter from url question
question = data["question"]
history = data["history"]

Check warning on line 218 in application/api/answer/routes.py

View check run for this annotation

Codecov / codecov/patch

application/api/answer/routes.py#L217-L218

Added lines #L217 - L218 were not covered by tests
# history to json object from string
history = json.loads(history)
conversation_id = data["conversation_id"]

Check warning on line 221 in application/api/answer/routes.py

View check run for this annotation

Codecov / codecov/patch

application/api/answer/routes.py#L220-L221

Added lines #L220 - L221 were not covered by tests

# check if active_docs is set

if not api_key_set:
api_key = data["api_key"]

Check warning on line 226 in application/api/answer/routes.py

View check run for this annotation

Codecov / codecov/patch

application/api/answer/routes.py#L225-L226

Added lines #L225 - L226 were not covered by tests
else:
api_key = settings.API_KEY
if not embeddings_key_set:
embeddings_key = data["embeddings_key"]

Check warning on line 230 in application/api/answer/routes.py

View check run for this annotation

Codecov / codecov/patch

application/api/answer/routes.py#L228-L230

Added lines #L228 - L230 were not covered by tests
else:
embeddings_key = settings.EMBEDDINGS_KEY
if "active_docs" in data:
vectorstore = get_vectorstore({"active_docs": data["active_docs"]})

Check warning on line 234 in application/api/answer/routes.py

View check run for this annotation

Codecov / codecov/patch

application/api/answer/routes.py#L232-L234

Added lines #L232 - L234 were not covered by tests
else:
vectorstore = ""
docsearch = FaissStore(vectorstore, embeddings_key)

Check warning on line 237 in application/api/answer/routes.py

View check run for this annotation

Codecov / codecov/patch

application/api/answer/routes.py#L236-L237

Added lines #L236 - L237 were not covered by tests

return Response(

Check warning on line 239 in application/api/answer/routes.py

View check run for this annotation

Codecov / codecov/patch

application/api/answer/routes.py#L239

Added line #L239 was not covered by tests
complete_stream(question, docsearch,
chat_history=history, api_key=api_key,
conversation_id=conversation_id), mimetype="text/event-stream"
)


@answer.route("/api/answer", methods=["POST"])
def api_answer():
data = request.get_json()
question = data["question"]
history = data["history"]
if "conversation_id" not in data:
conversation_id = None

Check warning on line 252 in application/api/answer/routes.py

View check run for this annotation

Codecov / codecov/patch

application/api/answer/routes.py#L248-L252

Added lines #L248 - L252 were not covered by tests
else:
conversation_id = data["conversation_id"]
print("-" * 5)
if not api_key_set:
api_key = data["api_key"]

Check warning on line 257 in application/api/answer/routes.py

View check run for this annotation

Codecov / codecov/patch

application/api/answer/routes.py#L254-L257

Added lines #L254 - L257 were not covered by tests
else:
api_key = settings.API_KEY
if not embeddings_key_set:
embeddings_key = data["embeddings_key"]

Check warning on line 261 in application/api/answer/routes.py

View check run for this annotation

Codecov / codecov/patch

application/api/answer/routes.py#L259-L261

Added lines #L259 - L261 were not covered by tests
else:
embeddings_key = settings.EMBEDDINGS_KEY

Check warning on line 263 in application/api/answer/routes.py

View check run for this annotation

Codecov / codecov/patch

application/api/answer/routes.py#L263

Added line #L263 was not covered by tests

# use try and except to check for exception
try:

Check warning on line 266 in application/api/answer/routes.py

View check run for this annotation

Codecov / codecov/patch

application/api/answer/routes.py#L266

Added line #L266 was not covered by tests
# check if the vectorstore is set
vectorstore = get_vectorstore(data)

Check warning on line 268 in application/api/answer/routes.py

View check run for this annotation

Codecov / codecov/patch

application/api/answer/routes.py#L268

Added line #L268 was not covered by tests
# loading the index and the store and the prompt template
# Note if you have used other embeddings than OpenAI, you need to change the embeddings
docsearch = FaissStore(vectorstore, embeddings_key)

Check warning on line 271 in application/api/answer/routes.py

View check run for this annotation

Codecov / codecov/patch

application/api/answer/routes.py#L271

Added line #L271 was not covered by tests

if is_azure_configured():
llm = AzureOpenAILLM(

Check warning on line 274 in application/api/answer/routes.py

View check run for this annotation

Codecov / codecov/patch

application/api/answer/routes.py#L273-L274

Added lines #L273 - L274 were not covered by tests
openai_api_key=api_key,
openai_api_base=settings.OPENAI_API_BASE,
openai_api_version=settings.OPENAI_API_VERSION,
deployment_name=settings.AZURE_DEPLOYMENT_NAME,
)
else:
logger.debug("plain OpenAI")
llm = OpenAILLM(api_key=api_key)

Check warning on line 282 in application/api/answer/routes.py

View check run for this annotation

Codecov / codecov/patch

application/api/answer/routes.py#L281-L282

Added lines #L281 - L282 were not covered by tests



docs = docsearch.search(question, k=2)

Check warning on line 286 in application/api/answer/routes.py

View check run for this annotation

Codecov / codecov/patch

application/api/answer/routes.py#L286

Added line #L286 was not covered by tests
# join all page_content together with a newline
docs_together = "\n".join([doc.page_content for doc in docs])
p_chat_combine = chat_combine_template.replace("{summaries}", docs_together)
messages_combine = [{"role": "system", "content": p_chat_combine}]
source_log_docs = []
for doc in docs:
if doc.metadata:
source_log_docs.append({"title": doc.metadata['title'].split('/')[-1], "text": doc.page_content})

Check warning on line 294 in application/api/answer/routes.py

View check run for this annotation

Codecov / codecov/patch

application/api/answer/routes.py#L288-L294

Added lines #L288 - L294 were not covered by tests
else:
source_log_docs.append({"title": doc.page_content, "text": doc.page_content})

Check warning on line 296 in application/api/answer/routes.py

View check run for this annotation

Codecov / codecov/patch

application/api/answer/routes.py#L296

Added line #L296 was not covered by tests
# join all page_content together with a newline


if len(history) > 1:
tokens_current_history = 0

Check warning on line 301 in application/api/answer/routes.py

View check run for this annotation

Codecov / codecov/patch

application/api/answer/routes.py#L300-L301

Added lines #L300 - L301 were not covered by tests
# count tokens in history
history.reverse()
for i in history:
if "prompt" in i and "response" in i:
tokens_batch = count_tokens(i["prompt"]) + count_tokens(i["response"])
if tokens_current_history + tokens_batch < settings.TOKENS_MAX_HISTORY:
tokens_current_history += tokens_batch
messages_combine.append({"role": "user", "content": i["prompt"]})
messages_combine.append({"role": "system", "content": i["response"]})
messages_combine.append({"role": "user", "content": question})

Check warning on line 311 in application/api/answer/routes.py

View check run for this annotation

Codecov / codecov/patch

application/api/answer/routes.py#L303-L311

Added lines #L303 - L311 were not covered by tests


completion = llm.gen(model=gpt_model, engine=settings.AZURE_DEPLOYMENT_NAME,

Check warning on line 314 in application/api/answer/routes.py

View check run for this annotation

Codecov / codecov/patch

application/api/answer/routes.py#L314

Added line #L314 was not covered by tests
messages=messages_combine)


result = {"answer": completion, "sources": source_log_docs}
logger.debug(result)

Check warning on line 319 in application/api/answer/routes.py

View check run for this annotation

Codecov / codecov/patch

application/api/answer/routes.py#L318-L319

Added lines #L318 - L319 were not covered by tests

# generate conversationId
if conversation_id is not None:
conversations_collection.update_one(

Check warning on line 323 in application/api/answer/routes.py

View check run for this annotation

Codecov / codecov/patch

application/api/answer/routes.py#L322-L323

Added lines #L322 - L323 were not covered by tests
{"_id": ObjectId(conversation_id)},
{"$push": {"queries": {"prompt": question,
"response": result["answer"], "sources": result['sources']}}},
)

else:
# create new conversation
# generate summary
messages_summary = [

Check warning on line 332 in application/api/answer/routes.py

View check run for this annotation

Codecov / codecov/patch

application/api/answer/routes.py#L332

Added line #L332 was not covered by tests
{"role": "assistant", "content": "Summarise following conversation in no more than 3 words, "
"respond ONLY with the summary, use the same language as the system \n\n"
"User: " + question + "\n\n" + "AI: " + result["answer"]},
{"role": "user", "content": "Summarise following conversation in no more than 3 words, "
"respond ONLY with the summary, use the same language as the system"}
]

completion = llm.gen(

Check warning on line 340 in application/api/answer/routes.py

View check run for this annotation

Codecov / codecov/patch

application/api/answer/routes.py#L340

Added line #L340 was not covered by tests
model=gpt_model,
engine=settings.AZURE_DEPLOYMENT_NAME,
messages=messages_summary,
max_tokens=30
)
conversation_id = conversations_collection.insert_one(

Check warning on line 346 in application/api/answer/routes.py

View check run for this annotation

Codecov / codecov/patch

application/api/answer/routes.py#L346

Added line #L346 was not covered by tests
{"user": "local",
"date": datetime.datetime.utcnow(),
"name": completion,
"queries": [{"prompt": question, "response": result["answer"], "sources": source_log_docs}]}
).inserted_id

result["conversation_id"] = str(conversation_id)

Check warning on line 353 in application/api/answer/routes.py

View check run for this annotation

Codecov / codecov/patch

application/api/answer/routes.py#L353

Added line #L353 was not covered by tests

# mock result
# result = {
# "answer": "The answer is 42",
# "sources": ["https://en.wikipedia.org/wiki/42_(number)", "https://en.wikipedia.org/wiki/42_(number)"]
# }
return result
except Exception as e:

Check warning on line 361 in application/api/answer/routes.py

View check run for this annotation

Codecov / codecov/patch

application/api/answer/routes.py#L360-L361

Added lines #L360 - L361 were not covered by tests
# print whole traceback
traceback.print_exc()
print(str(e))
return bad_request(500, str(e))

Check warning on line 365 in application/api/answer/routes.py

View check run for this annotation

Codecov / codecov/patch

application/api/answer/routes.py#L363-L365

Added lines #L363 - L365 were not covered by tests
Empty file.
Loading
Loading