├── handlers ├── delete.py ├── __init__.py ├── compose.py ├── create.py ├── list.py ├── query.py └── upload.py ├── models ├── __init__.py ├── knowledgebase_model.py └── statics_model.py ├── tests ├── __init__.py └── test_basic.py ├── services ├── __init__.py └── environment_service.py ├── requirements.txt ├── img ├── img.png ├── img2.png ├── img3.png └── img4.png ├── faq-service-MVP-app ├── public │ ├── robots.txt │ ├── favicon.ico │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── index.html ├── src │ ├── setupTests.js │ ├── index.js │ ├── index.css │ ├── reportWebVitals.js │ ├── logo.svg │ └── App.js ├── .gitignore ├── package.json └── README.md ├── __pycache__ ├── routes.cpython-310.pyc └── test_basic.cpython-310-pytest-7.2.1.pyc ├── .gitignore ├── README.md ├── routes.py ├── FAQ-service endpoints.postman_collection.json └── testing.txt /handlers/delete.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /models/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /handlers/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /services/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | llama-index==0.5.6 2 | langchain==0.0.130 -------------------------------------------------------------------------------- /img/img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luyaojchen/faq-service/HEAD/img/img.png -------------------------------------------------------------------------------- /img/img2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luyaojchen/faq-service/HEAD/img/img2.png -------------------------------------------------------------------------------- /img/img3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luyaojchen/faq-service/HEAD/img/img3.png -------------------------------------------------------------------------------- /img/img4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luyaojchen/faq-service/HEAD/img/img4.png -------------------------------------------------------------------------------- /faq-service-MVP-app/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /__pycache__/routes.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luyaojchen/faq-service/HEAD/__pycache__/routes.cpython-310.pyc -------------------------------------------------------------------------------- /faq-service-MVP-app/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luyaojchen/faq-service/HEAD/faq-service-MVP-app/public/favicon.ico -------------------------------------------------------------------------------- /faq-service-MVP-app/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luyaojchen/faq-service/HEAD/faq-service-MVP-app/public/logo192.png -------------------------------------------------------------------------------- /faq-service-MVP-app/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luyaojchen/faq-service/HEAD/faq-service-MVP-app/public/logo512.png -------------------------------------------------------------------------------- /__pycache__/test_basic.cpython-310-pytest-7.2.1.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luyaojchen/faq-service/HEAD/__pycache__/test_basic.cpython-310-pytest-7.2.1.pyc -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | .idea 3 | __pycache__/* 4 | indexes/* 5 | handlers/__pycache__/* 6 | models/__pycache__/* 7 | tests/indexes/* 8 | services/__pycache__/* 9 | tests/__pycache__/* 10 | -------------------------------------------------------------------------------- /faq-service-MVP-app/src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /faq-service-MVP-app/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import "./index.css"; 4 | import App from "./App"; 5 | 6 | const root = ReactDOM.createRoot(document.getElementById("root")); 7 | root.render( 8 | 9 | 10 | 11 | ); 12 | -------------------------------------------------------------------------------- /handlers/compose.py: -------------------------------------------------------------------------------- 1 | from models.statics_model import g_index 2 | 3 | 4 | async def compose_handler(knowledgebase_id): 5 | 6 | if not knowledgebase_id: 7 | return False 8 | 9 | # Check if knowledgebase exists 10 | if knowledgebase_id not in g_index: 11 | return False 12 | 13 | tokens = await g_index[knowledgebase_id].build_index() 14 | return tokens 15 | -------------------------------------------------------------------------------- /faq-service-MVP-app/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /faq-service-MVP-app/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /faq-service-MVP-app/src/reportWebVitals.js: -------------------------------------------------------------------------------- 1 | const reportWebVitals = (onPerfEntry) => { 2 | if (onPerfEntry && onPerfEntry instanceof Function) { 3 | import("web-vitals").then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 4 | getCLS(onPerfEntry); 5 | getFID(onPerfEntry); 6 | getFCP(onPerfEntry); 7 | getLCP(onPerfEntry); 8 | getTTFB(onPerfEntry); 9 | }); 10 | } 11 | }; 12 | 13 | export default reportWebVitals; 14 | -------------------------------------------------------------------------------- /handlers/create.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | 3 | from models.knowledgebase_model import Knowledgebase 4 | from models.statics_model import ResponseStatics, g_index 5 | 6 | 7 | def create_handler(): 8 | # Create a unique ID for this creation request 9 | knowledgebase_id = str(uuid.uuid4()) 10 | 11 | # Initialize the entry in g_index. 12 | g_index[knowledgebase_id] = Knowledgebase(knowledgebase_id) 13 | 14 | # Return the generated knowledgebase ID to the user 15 | return knowledgebase_id 16 | -------------------------------------------------------------------------------- /handlers/list.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | from models.statics_model import ResponseStatics 4 | 5 | 6 | # list all indexes from indexes directory with pathlib 7 | async def list_handler(): 8 | 9 | idx = [] 10 | (Path.cwd() / 'indexes').exists() # Make sure that the indexes folder exists first 11 | for index_file in Path("indexes").iterdir(): 12 | # Define the path 13 | path = index_file.stem 14 | idx.append(path) 15 | return ResponseStatics.build_list_response(idx) 16 | 17 | -------------------------------------------------------------------------------- /faq-service-MVP-app/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /services/environment_service.py: -------------------------------------------------------------------------------- 1 | import os 2 | import openai 3 | from dotenv import load_dotenv 4 | 5 | load_dotenv() 6 | class EnvService: 7 | def __init__(self): 8 | pass 9 | 10 | @staticmethod 11 | def get_openai_api_key(): 12 | try: 13 | return os.getenv('OPENAI_API_KEY') 14 | except: 15 | raise PermissionError('Could not get OPENAI_API_KEY from environment') 16 | 17 | @staticmethod 18 | def get_openai_organization(): 19 | try: 20 | return os.getenv('OPENAI_ORGANIZATION') 21 | except: 22 | raise PermissionError('Could not get OPENAI_ORGANIZATION from environment') 23 | 24 | 25 | openai.openai_api_key = EnvService.get_openai_api_key() 26 | -------------------------------------------------------------------------------- /faq-service-MVP-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "my-react-app", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.16.5", 7 | "@testing-library/react": "^13.4.0", 8 | "@testing-library/user-event": "^13.5.0", 9 | "bootstrap": "^5.2.3", 10 | "react": "^18.2.0", 11 | "react-bootstrap": "^2.7.2", 12 | "react-dom": "^18.2.0", 13 | "react-scripts": "5.0.1", 14 | "web-vitals": "^2.1.4" 15 | }, 16 | "scripts": { 17 | "start": "react-scripts start", 18 | "build": "react-scripts build", 19 | "test": "react-scripts test", 20 | "eject": "react-scripts eject" 21 | }, 22 | "eslintConfig": { 23 | "extends": [ 24 | "react-app", 25 | "react-app/jest" 26 | ] 27 | }, 28 | "browserslist": { 29 | "production": [ 30 | ">0.2%", 31 | "not dead", 32 | "not op_mini all" 33 | ], 34 | "development": [ 35 | "last 1 chrome version", 36 | "last 1 firefox version", 37 | "last 1 safari version" 38 | ] 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /handlers/query.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from functools import partial 3 | 4 | import flask 5 | from llama_index import ServiceContext 6 | from llama_index.prompts.chat_prompts import CHAT_REFINE_PROMPT 7 | 8 | from models.statics_model import g_index, ResponseStatics, Models 9 | from models.statics_model import prompt_helper 10 | 11 | 12 | async def query_handler(knowledgebase_id, query, nodes=3, model="gpt-3.5-turbo"): 13 | 14 | # Validate the knowledgebase ID 15 | if not knowledgebase_id: 16 | return ResponseStatics.build_api_error("knowledgebase_id is required") 17 | 18 | # Check if knowledgebase exists 19 | if knowledgebase_id not in g_index: 20 | return ResponseStatics.build_api_error("knowledgebase_id does not exist") 21 | 22 | # Check if knowledgebase has a built index 23 | if not g_index[knowledgebase_id].index: 24 | return ResponseStatics.build_api_error("knowledgebase_id does not have an index, run compose first on this knowledgebase") 25 | 26 | # Validate the query 27 | if not query: 28 | return ResponseStatics.build_api_error("No query provided") 29 | 30 | service_context = ServiceContext.from_defaults(llm_predictor=Models.get_llm_predictor(model) if model else Models.get_llm_predictor("gpt-3.5-turbo"), prompt_helper=prompt_helper) 31 | 32 | # Query the index 33 | resp = await g_index[knowledgebase_id].index.aquery(query, refine_template=CHAT_REFINE_PROMPT, similarity_top_k=nodes, service_context=service_context) 34 | 35 | resp = flask.Response(resp.response, 200) 36 | resp.headers.add('Access-Control-Allow-Origin', '*') 37 | 38 | return resp 39 | -------------------------------------------------------------------------------- /faq-service-MVP-app/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /handlers/upload.py: -------------------------------------------------------------------------------- 1 | import tempfile 2 | 3 | import llama_index 4 | from llama_index import SimpleDirectoryReader 5 | import aiohttp 6 | from llama_index.readers.web import DEFAULT_WEBSITE_EXTRACTOR 7 | 8 | from models.statics_model import ResponseStatics, g_index, file_extensions_mappings 9 | 10 | 11 | def upload_doc_handler(knowledgebase_id, file): 12 | 13 | if not knowledgebase_id: 14 | return False 15 | 16 | # Check if knowledgebase exists 17 | if knowledgebase_id not in g_index: 18 | return False 19 | 20 | # Get the content type of the file 21 | content_type = "" if not file.content_type else file.content_type 22 | suffix = file_extensions_mappings[content_type] 23 | 24 | with tempfile.NamedTemporaryFile(delete=False, suffix=suffix) as fp: 25 | file.save(fp) 26 | doc = SimpleDirectoryReader(input_files=[fp.name]).load_data() 27 | g_index[knowledgebase_id].add_documents(doc) 28 | 29 | return True 30 | 31 | 32 | async def upload_link_handler(knowledgebase_id, url): 33 | 34 | if not knowledgebase_id: 35 | return False 36 | 37 | # Check if knowledgebase exists 38 | if knowledgebase_id not in g_index: 39 | return False 40 | 41 | async with aiohttp.ClientSession() as session: 42 | async with session.get(url) as response: 43 | if response.status != 200: 44 | return False 45 | 46 | if response.headers["Content-Type"] == "application/pdf": 47 | data = await response.read() 48 | f = tempfile.NamedTemporaryFile(suffix=".pdf", delete=False) 49 | f.write(data) 50 | f.close() 51 | doc = SimpleDirectoryReader(input_files=[f.name]).load_data() 52 | g_index[knowledgebase_id].add_documents(doc) 53 | 54 | else: 55 | documents = llama_index.BeautifulSoupWebReader(website_extractor=DEFAULT_WEBSITE_EXTRACTOR).load_data(urls=[url]) 56 | g_index[knowledgebase_id].add_documents(documents) 57 | 58 | return True 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FAQ-Service 2 | 3 | ### Overview 4 | FAQ-Service is a service that creates a RESTful interface for creating, managing, and querying your own custom knowledgebases 5 | 6 | ### How does it work? 7 | FAQ-Service is self-hosted. At its base, it is a flask application that acts as an API-server interface for our custom indexing and querying system. 8 | Upon installing FAQ-Service locally or on docker (see below), you can immediately start creating and querying knowledgebases 9 | 10 | Knowledgebases consist of a collection of files. You first call a creation endpoint to create a knowledgebase, this will give you a knowledge ID. After a knowledgebase ID is retrieved, you can pack the knowledgebase with content, you can load the knowledgebase with content by using files or links, our indexing system (using llama-index under the hood) is automatically able to interpret most popular file types such as PDF, DOCX, TXT, CSV, etc. You can also load a knowledgebase from a link. 11 | 12 | Adding documents to your knowledgebase builds a document store. When you are ready to query your knowledgebase, you can compose your knowledgebase. Compositions are what we call turning the document store into a vector index. Knowledgebase compositions have multiple different parameters that allow you to specify what type of index you want to be created, what LLM to use, and etc. 13 | 14 | After composing your knowledgebase, you will be able to query your knowledgebase. A full-fledged example can be seen below: 15 | 16 | Creating a knowledgebase: 17 | ![img.png](img/img.png) 18 | 19 | Adding documents to a knowledgebase: 20 | ![img.png](img/img2.png) 21 | 22 | Composing the knowledgebase: 23 | ![img.png](img/img3.png) 24 | 25 | Querying the composed knowledgebase: 26 | ![img.png](img/img4.png) 27 | 28 | # Installation & Setup 29 | FAQ-Service is a python flask service that you can install with python >=3.9. 30 | 31 | First install the base requirements: 32 | ``` 33 | python3.9 -m pip install -r requirements.txt 34 | ``` 35 | 36 | Install flask-async 37 | ``` 38 | python3.9 -m pip install "flask[async]" 39 | ``` 40 | 41 | You then need to create a file named `.env` in the root of the project and populate it with your openAI key and organization, it should look as follows: 42 | ```text 43 | OPENAI_API_KEY="sk-....." 44 | OPENAI_ORGANIZATIOn="org-..." 45 | ``` 46 | 47 | Then, in the root directory you can run the routes.py as the entrypoint 48 | ``` 49 | python3.9 routes.py 50 | ``` -------------------------------------------------------------------------------- /faq-service-MVP-app/src/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /models/knowledgebase_model.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from functools import partial 3 | from pathlib import Path 4 | 5 | from llama_index import GPTSimpleVectorIndex, ServiceContext 6 | from models.statics_model import LLMPredictorFAQ, g_index, Models, EmbedModelFAQ 7 | from models.statics_model import prompt_helper 8 | 9 | 10 | class Knowledgebase: 11 | 12 | def __init__(self, id, name=None): 13 | self.knowledgebase_id = id 14 | self.documents = [] 15 | self.index = None 16 | self.name = name 17 | 18 | def add_documents(self, *documents): 19 | for document in documents: 20 | self.documents.append(document) 21 | 22 | async def build_index(self): 23 | #Go through the documents and flatten them 24 | flattened_documents = [] 25 | for document in self.documents: 26 | if isinstance(document, list): 27 | # Flatten the list 28 | flattened_documents.extend(document) 29 | else: 30 | flattened_documents.append(document) 31 | predictor = Models.get_llm_predictor("gpt-3.5-turbo") 32 | embed_model = EmbedModelFAQ.ADA.value 33 | service_context = ServiceContext.from_defaults(llm_predictor=predictor, chunk_size_limit=512, prompt_helper=prompt_helper, embed_model=embed_model) 34 | index = await asyncio.get_event_loop().run_in_executor(None, partial(GPTSimpleVectorIndex.from_documents, documents=flattened_documents, service_context=service_context)) 35 | 36 | self.index = index 37 | 38 | # Save the index to disk 39 | self.index.save_to_disk(f"indexes/{self.knowledgebase_id}.index") 40 | print(EmbedModelFAQ.get_last_token_usage()) 41 | 42 | return EmbedModelFAQ.get_last_token_usage() 43 | 44 | # String repr is just the id 45 | def __repr__(self): 46 | return self.knowledgebase_id 47 | 48 | # Equality is just the knowledgebase ID 49 | def __eq__(self, other): 50 | return self.knowledgebase_id == other.knowledgebase_id 51 | 52 | # Using Pathlib open all the files under the "indexes" folder 53 | Path("indexes").mkdir(parents=True, exist_ok=True) # Make sure that the indexes folder exists first 54 | for index_file in Path("indexes").iterdir(): 55 | # Load the index from disk 56 | # Define the path 57 | path = Path("indexes") / f"{index_file.stem}.index" 58 | index = GPTSimpleVectorIndex.load_from_disk(str(path)) 59 | # Add the index to the global index 60 | g_index[index_file.stem] = Knowledgebase(index_file.stem) 61 | g_index[index_file.stem].index = index -------------------------------------------------------------------------------- /tests/test_basic.py: -------------------------------------------------------------------------------- 1 | import io 2 | import json 3 | import os 4 | from pathlib import Path 5 | from pprint import pprint 6 | 7 | from flask import jsonify 8 | import pytest 9 | from flask.testing import FlaskClient 10 | from routes import app 11 | from werkzeug.datastructures import FileStorage 12 | 13 | 14 | ''' 15 | testflow 16 | 17 | - create knowledge base 18 | - upload 19 | - compose 20 | - ask 21 | ''' 22 | 23 | 24 | @pytest.fixture 25 | def client(): 26 | return app.test_client() 27 | 28 | 29 | def test_doc_add(client: FlaskClient): 30 | resp = client.get('/create') 31 | data = json.loads(resp.data.decode('utf-8')) 32 | knowledgebase_id = data['knowledgebase_id'] 33 | 34 | file_name = '../testing.txt' 35 | file_path = os.path.join(Path(os.path.dirname(__file__)), file_name) 36 | with open(file_path, 'rb') as input_file: 37 | input_file_stream = io.BytesIO(input_file.read()) 38 | data = { 39 | 'knowledgebase_id': knowledgebase_id, 40 | 'file': (input_file_stream, file_name), 41 | } 42 | 43 | resp = client.post('/index/doc/add', content_type='multipart/form-data', data=data) 44 | assert resp.status_code == 200 45 | 46 | data = { 47 | 'knowledgebase_id': knowledgebase_id 48 | } 49 | 50 | resp = client.get('/compose', query_string=data) 51 | 52 | assert resp.status_code == 200 53 | 54 | test_payload = { 55 | 'knowledgebase_id': knowledgebase_id, 56 | 'query': 'The code word for this is:' 57 | } 58 | resp = client.get('/query', query_string=test_payload) 59 | data = resp.data.decode('utf-8') 60 | assert "test" in data 61 | 62 | 63 | def test_link(client: FlaskClient): 64 | resp = client.get('/create') 65 | data = json.loads(resp.data.decode('utf-8')) 66 | knowledgebase_id = data['knowledgebase_id'] 67 | 68 | url = 'https://kaveenk.com/' 69 | data = { 70 | 'knowledgebase_id': knowledgebase_id, 71 | 'url': url, 72 | } 73 | 74 | resp = client.post('/index/link/add', data=data) 75 | assert resp.status_code == 200 76 | 77 | data = { 78 | 'knowledgebase_id': knowledgebase_id 79 | } 80 | 81 | resp = client.get('/compose', query_string=data) 82 | 83 | assert resp.status_code == 200 84 | 85 | test_payload = { 86 | 'knowledgebase_id': knowledgebase_id, 87 | 'query': 'Who is this document about?' 88 | } 89 | resp = client.get('/query', query_string=test_payload) 90 | data = resp.data.decode('utf-8') 91 | assert "Kaveen" in data 92 | 93 | 94 | def test_link_pdf(client: FlaskClient): 95 | resp = client.get('/create') 96 | data = json.loads(resp.data.decode('utf-8')) 97 | knowledgebase_id = data['knowledgebase_id'] 98 | 99 | url = 'https://trentstauffer.ca/resume.pdf' 100 | data = { 101 | 'knowledgebase_id': knowledgebase_id, 102 | 'url': url, 103 | } 104 | 105 | resp = client.post('/index/link/add', data=data) 106 | assert resp.status_code == 200 107 | 108 | data = { 109 | 'knowledgebase_id': knowledgebase_id 110 | } 111 | 112 | resp = client.get('/compose', query_string=data) 113 | 114 | assert resp.status_code == 200 115 | 116 | test_payload = { 117 | 'knowledgebase_id': knowledgebase_id, 118 | 'query': 'Who is this document about?' 119 | } 120 | resp = client.get('/query', query_string=test_payload) 121 | data = resp.data.decode('utf-8') 122 | assert "Trent" in data -------------------------------------------------------------------------------- /faq-service-MVP-app/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started with Create React App 2 | 3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `npm start` 10 | 11 | Runs the app in the development mode.\ 12 | Open [http://localhost:3000](http://localhost:3000) to view it in your browser. 13 | 14 | The page will reload when you make changes.\ 15 | You may also see any lint errors in the console. 16 | 17 | ### `npm test` 18 | 19 | Launches the test runner in the interactive watch mode.\ 20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 21 | 22 | ### `npm run build` 23 | 24 | Builds the app for production to the `build` folder.\ 25 | It correctly bundles React in production mode and optimizes the build for the best performance. 26 | 27 | The build is minified and the filenames include the hashes.\ 28 | Your app is ready to be deployed! 29 | 30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 31 | 32 | ### `npm run eject` 33 | 34 | **Note: this is a one-way operation. Once you `eject`, you can't go back!** 35 | 36 | If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 37 | 38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own. 39 | 40 | You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it. 41 | 42 | ## Learn More 43 | 44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 45 | 46 | To learn React, check out the [React documentation](https://reactjs.org/). 47 | 48 | ### Code Splitting 49 | 50 | This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting) 51 | 52 | ### Analyzing the Bundle Size 53 | 54 | This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size) 55 | 56 | ### Making a Progressive Web App 57 | 58 | This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app) 59 | 60 | ### Advanced Configuration 61 | 62 | This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration) 63 | 64 | ### Deployment 65 | 66 | This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment) 67 | 68 | ### `npm run build` fails to minify 69 | 70 | This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify) 71 | -------------------------------------------------------------------------------- /routes.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | import flask 4 | from flask import Flask 5 | import os 6 | from dotenv import load_dotenv 7 | from langchain import OpenAI 8 | 9 | from handlers.compose import compose_handler 10 | from handlers.create import create_handler 11 | from handlers.query import query_handler 12 | from handlers.list import list_handler 13 | 14 | from handlers.upload import upload_doc_handler, upload_link_handler 15 | from models.knowledgebase_model import Knowledgebase 16 | from models.statics_model import ResponseStatics, g_index, Models 17 | 18 | app = Flask(__name__) 19 | load_dotenv() 20 | OpenAI.openai_api_key = os.getenv('OPENAI_API_KEY') 21 | 22 | 23 | @app.route('/create', methods=['GET']) 24 | async def create(): 25 | """Create a knowledgebase and return a knowlegebase ID""" 26 | # Create a unique ID for this creation request 27 | return ResponseStatics.build_creation_response(create_handler()) 28 | 29 | 30 | 31 | @app.route('/index/doc/add', methods=['POST']) 32 | def upload_doc(): 33 | # Extract the knowledgebase_id from the request 34 | knowledgebase_id = flask.request.form.get('knowledgebase_id') 35 | 36 | # Validate the knowledgebase ID 37 | if not upload_doc_handler(knowledgebase_id, flask.request.files['file']): 38 | return ResponseStatics.build_api_error("Invalid knowledgebase ID or none provided") 39 | return ResponseStatics.build_upload_success_message() 40 | 41 | 42 | @app.route('/index/link/add', methods=['POST']) 43 | async def upload_link(): 44 | # Extract the knowledgebase_id from the request 45 | knowledgebase_id, link = flask.request.form.get('knowledgebase_id'), flask.request.form.get('url') 46 | 47 | # Validate the knowledgebase ID 48 | if not await upload_link_handler(knowledgebase_id, link): 49 | return ResponseStatics.build_api_error("Invalid knowledgebase ID or none provided") 50 | return ResponseStatics.build_upload_success_message() 51 | 52 | 53 | @app.route('/compose', methods=['GET']) 54 | async def compose(): 55 | # Extract the knowledgebase_id from the request 56 | knowledgebase_id = flask.request.args.get('knowledgebase_id') 57 | 58 | # Validate the knowledgebase ID 59 | tokens = await compose_handler(knowledgebase_id) 60 | if not tokens: 61 | return ResponseStatics.build_api_error("Invalid knowledgebase ID or none provided") 62 | 63 | return ResponseStatics.build_compose_success_message(tokens) 64 | 65 | 66 | @app.route('/query', methods=['GET']) 67 | async def query(): 68 | # Extract the query from the request (GET) 69 | query = flask.request.args.get('query') 70 | 71 | nodes = int(flask.request.args.get('nodes')) if flask.request.args.get('nodes') else 3 72 | 73 | # Assert that the number of nodes is valid 74 | if nodes < 1 or nodes > 10: 75 | return ResponseStatics.build_api_error("Invalid number of nodes provided [1,10] is valid") 76 | 77 | model = flask.request.args.get('model') 78 | 79 | # Assert that the model is valid and in the list of models 80 | if model and model not in Models.get_models(): 81 | return ResponseStatics.build_api_error("Invalid model provided") 82 | 83 | # Extract the knowledgebase_id from the request 84 | knowledgebase_id = flask.request.args.get('knowledgebase_id') 85 | 86 | return await query_handler(knowledgebase_id, query, nodes, model) 87 | 88 | 89 | @app.route('/index/list', methods=['GET']) 90 | async def list_index(): 91 | return await list_handler() 92 | 93 | 94 | def init(): 95 | app.run(host="0.0.0.0", port=8182, debug=True) 96 | 97 | 98 | if __name__ == '__main__': 99 | sys.exit(init()) 100 | -------------------------------------------------------------------------------- /models/statics_model.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | from pathlib import Path 3 | 4 | import flask 5 | import openai 6 | from langchain.chat_models import ChatOpenAI 7 | from llama_index import LLMPredictor, GPTSimpleVectorIndex, SimpleDirectoryReader, PromptHelper, Document, \ 8 | OpenAIEmbedding 9 | from services.environment_service import EnvService 10 | 11 | openai.openai_api_key = EnvService.get_openai_api_key() 12 | openai.openai_organization = EnvService.get_openai_organization() 13 | openai.organization = EnvService.get_openai_organization() 14 | 15 | prompt_helper = PromptHelper(3900, 256, 20) 16 | # for now keep in memory of all documents in list 17 | g_index = {} 18 | 19 | 20 | file_extensions_mappings = { 21 | "application/pdf": ".pdf", 22 | "application/msword": ".docx", 23 | "application/vnd.openxmlformats-officedocument.wordprocessingml.document": ".docx", 24 | "application/vnd.oasis.opendocument.text": ".odt", 25 | "text/plain": ".txt", 26 | "text/csv": ".csv", 27 | "text/markdown": ".md", 28 | "text/html": ".html", 29 | # Epub 30 | "application/epub+zip": ".epub", 31 | } 32 | 33 | class LLMPredictorFAQ(Enum): 34 | GPT3 = LLMPredictor(llm=ChatOpenAI(temperature=0, model_name="gpt-3.5-turbo")) 35 | GPT4 = LLMPredictor(llm=ChatOpenAI(temperature=0, model_name="gpt-4")) 36 | 37 | class EmbedModelFAQ(Enum): 38 | ADA = OpenAIEmbedding() 39 | 40 | @staticmethod 41 | def get_last_token_usage(): 42 | return EmbedModelFAQ.ADA.value.last_token_usage 43 | 44 | class Models(Enum): 45 | GPT3 = "gpt-3.5-turbo" 46 | GPT4 = "gpt-4" 47 | 48 | # Map these models to the LLMPredictor ones 49 | @staticmethod 50 | def get_llm_predictor(model: str): 51 | if model == Models.GPT3.value: 52 | return LLMPredictorFAQ.GPT3.value 53 | elif model == Models.GPT4.value: 54 | return LLMPredictorFAQ.GPT4.value 55 | else: 56 | raise ValueError("Invalid model") 57 | 58 | @staticmethod 59 | def get_last_token_usage(model: str): 60 | return Models.get_llm_predictor(model).last_token_usage 61 | 62 | def __str__(self): 63 | return self.value 64 | 65 | @staticmethod 66 | def get_models(): 67 | return [model.value for model in Models] 68 | 69 | class Answer: 70 | def __init__(self, answer_id, answer_text): 71 | self.answer_id = answer_id 72 | self.answer_text = answer_text 73 | 74 | 75 | class ResponseStatics: 76 | 77 | @staticmethod 78 | def build_api_error(error_message): 79 | response = flask.jsonify({"error": error_message}) 80 | response.headers.add('Access-Control-Allow-Origin', '*') 81 | return response 82 | 83 | @staticmethod 84 | def build_compose_success_message(tokens): 85 | response = flask.jsonify({"message": "Successfully composed knowledgebase", "tokens_used": tokens}) 86 | response.headers.add('Access-Control-Allow-Origin', '*') 87 | return response 88 | 89 | @staticmethod 90 | def build_upload_success_message(): 91 | response = flask.jsonify({"message": "Successfully uploaded document"}) 92 | response.headers.add('Access-Control-Allow-Origin', '*') 93 | return response 94 | 95 | @staticmethod 96 | def build_answer_response(answer: Answer): 97 | response = flask.jsonify({"answer_id": answer.answer_id, "answer_text": answer.answer_text}) 98 | response.headers.add('Access-Control-Allow-Origin', '*') 99 | return response 100 | 101 | @staticmethod 102 | def build_creation_response(knowledgebase_id): 103 | response = flask.jsonify({"knowledgebase_id": knowledgebase_id}) 104 | response.headers.add('Access-Control-Allow-Origin', '*') 105 | return response 106 | 107 | @staticmethod 108 | def build_list_response(knowledgebase_ids): 109 | response = flask.jsonify({"knowledgebase_ids": knowledgebase_ids}) 110 | response.headers.add('Access-Control-Allow-Origin', '*') 111 | return response 112 | 113 | -------------------------------------------------------------------------------- /faq-service-MVP-app/src/App.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import Container from "react-bootstrap/Container"; 3 | import Row from "react-bootstrap/Row"; 4 | import Button from "react-bootstrap/Button"; 5 | 6 | function App() { 7 | const [file, setFile] = useState(); 8 | const [query, setQuery] = useState(); 9 | const [submittedValue, setSubmittedValue] = useState(); 10 | const [knowledgebase, setKnowledgebase] = useState(); 11 | 12 | const strUrl = "http://127.0.0.1:8182"; 13 | 14 | const AddDoc = async (file) => { 15 | var kb = ""; 16 | var data = new FormData(); 17 | await fetch(strUrl + "/create", { 18 | method: "GET", 19 | }) 20 | .then((response) => { 21 | return response.json(); 22 | }) 23 | .then((data) => { 24 | console.log(data); 25 | kb = data.knowledgebase_id; 26 | }) 27 | .catch((err) => { 28 | console.log(err.message); 29 | }); 30 | 31 | data.append("file", file); 32 | data.append("knowledgebase_id", kb); 33 | await fetch(strUrl + "/index/doc/add", { 34 | method: "POST", 35 | body: data, 36 | }) 37 | .then((response) => response.json()) 38 | .then((data) => { 39 | console.log(data); 40 | }) 41 | .catch((err) => { 42 | console.log(err.message); 43 | }); 44 | 45 | await fetch(strUrl + "/compose?knowledgebase_id=" + kb, { 46 | method: "GET", 47 | }) 48 | .then((response) => response.json()) 49 | .then((data) => { 50 | console.log(data); 51 | }) 52 | .catch((err) => { 53 | console.log(err.message); 54 | }); 55 | return kb; 56 | }; 57 | 58 | const AskQuestion = async () => { 59 | var text = ""; 60 | await fetch( 61 | strUrl + "/query?knowledgebase_id=" + knowledgebase + "&query=" + query, 62 | { 63 | method: "GET", 64 | } 65 | ) 66 | .then((response) => { 67 | return response.text(); 68 | }) 69 | .then((data) => { 70 | console.log(data); 71 | text = data; 72 | }) 73 | .catch((err) => { 74 | console.log(err.message); 75 | }); 76 | return text; 77 | }; 78 | 79 | // handler for selecting file 80 | function handleChange(event) { 81 | setFile(event.target.files[0]); 82 | } 83 | 84 | // handler for submitting button 85 | const submitFile = async () => { 86 | const kb = await AddDoc(file); 87 | setKnowledgebase(kb); 88 | setSubmittedValue(file); 89 | }; 90 | 91 | // handler for submitting question 92 | const uppdateQuery = () => { 93 | var message = document.getElementById("query").value; 94 | setQuery(message); 95 | }; 96 | 97 | // handler for submitting question 98 | const submitQuestion = async () => { 99 | var message = document.getElementById("query").value; 100 | setQuery(message); 101 | const text = await AskQuestion(); 102 | document.getElementById("ans").value = text; 103 | }; 104 | 105 | return ( 106 | 107 | 113 | header for faq service 114 | 115 | 116 | {!submittedValue ? ( 117 | 118 |

Click to select

119 | 120 | 140 |
141 | )} 142 |
143 |
144 | ); 145 | } 146 | 147 | export default App; 148 | -------------------------------------------------------------------------------- /FAQ-service endpoints.postman_collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "_postman_id": "274b172f-a13e-4741-b1dc-03fd08672429", 4 | "name": "FAQ-service endpoints", 5 | "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" 6 | }, 7 | "item": [ 8 | { 9 | "name": "Ask", 10 | "protocolProfileBehavior": { 11 | "disableBodyPruning": true 12 | }, 13 | "request": { 14 | "method": "GET", 15 | "header": [], 16 | "body": { 17 | "mode": "formdata", 18 | "formdata": [ 19 | { 20 | "key": "knowledgebase_id", 21 | "value": "0bc016f6-36e2-4be7-9588-3ea4d46f6270", 22 | "type": "default", 23 | "disabled": true 24 | }, 25 | { 26 | "key": "", 27 | "value": "", 28 | "type": "default", 29 | "disabled": true 30 | } 31 | ] 32 | }, 33 | "url": { 34 | "raw": "localhost:8182/query?knowledgebase_id=a3f009c8-0159-467a-bbca-067cbe76907b&query=what is this document about?", 35 | "host": [ 36 | "localhost" 37 | ], 38 | "port": "8182", 39 | "path": [ 40 | "query" 41 | ], 42 | "query": [ 43 | { 44 | "key": "knowledgebase_id", 45 | "value": "a3f009c8-0159-467a-bbca-067cbe76907b" 46 | }, 47 | { 48 | "key": "query", 49 | "value": "what is this document about?" 50 | } 51 | ] 52 | } 53 | }, 54 | "response": [] 55 | }, 56 | { 57 | "name": "Compose", 58 | "protocolProfileBehavior": { 59 | "disableBodyPruning": true 60 | }, 61 | "request": { 62 | "method": "GET", 63 | "header": [], 64 | "body": { 65 | "mode": "formdata", 66 | "formdata": [] 67 | }, 68 | "url": { 69 | "raw": "localhost:8182/compose?knowledgebase_id=a3f009c8-0159-467a-bbca-067cbe76907b", 70 | "host": [ 71 | "localhost" 72 | ], 73 | "port": "8182", 74 | "path": [ 75 | "compose" 76 | ], 77 | "query": [ 78 | { 79 | "key": "knowledgebase_id", 80 | "value": "a3f009c8-0159-467a-bbca-067cbe76907b" 81 | } 82 | ] 83 | } 84 | }, 85 | "response": [] 86 | }, 87 | { 88 | "name": "Create", 89 | "request": { 90 | "method": "GET", 91 | "header": [], 92 | "url": { 93 | "raw": "localhost:8182/create", 94 | "host": [ 95 | "localhost" 96 | ], 97 | "port": "8182", 98 | "path": [ 99 | "create" 100 | ], 101 | "query": [ 102 | { 103 | "key": null, 104 | "value": "", 105 | "disabled": true 106 | } 107 | ] 108 | } 109 | }, 110 | "response": [] 111 | }, 112 | { 113 | "name": "Upload document", 114 | "request": { 115 | "method": "POST", 116 | "header": [], 117 | "body": { 118 | "mode": "formdata", 119 | "formdata": [ 120 | { 121 | "key": "file", 122 | "type": "file", 123 | "src": "/Users/luyaochen/PycharmProjects/faq-service/testing.txt" 124 | }, 125 | { 126 | "key": "knowledgebase_id", 127 | "value": "e74c5502-00e3-4ded-8e31-7aa8d03c0572", 128 | "type": "default" 129 | } 130 | ] 131 | }, 132 | "url": { 133 | "raw": "localhost:8182/index/doc/add", 134 | "host": [ 135 | "localhost" 136 | ], 137 | "port": "8182", 138 | "path": [ 139 | "index", 140 | "doc", 141 | "add" 142 | ], 143 | "query": [ 144 | { 145 | "key": "", 146 | "value": "", 147 | "disabled": true 148 | } 149 | ] 150 | } 151 | }, 152 | "response": [] 153 | }, 154 | { 155 | "name": "Upload link (website)", 156 | "request": { 157 | "method": "POST", 158 | "header": [], 159 | "body": { 160 | "mode": "formdata", 161 | "formdata": [ 162 | { 163 | "key": "files", 164 | "type": "file", 165 | "src": [], 166 | "disabled": true 167 | }, 168 | { 169 | "key": "knowledgebase_id", 170 | "value": "d41dc7c4-b4b7-47cd-9bb0-1a44c54113ba", 171 | "type": "default" 172 | }, 173 | { 174 | "key": "url", 175 | "value": "https://kaveenk.com/", 176 | "type": "default" 177 | } 178 | ] 179 | }, 180 | "url": { 181 | "raw": "localhost:8182/index/link/add", 182 | "host": [ 183 | "localhost" 184 | ], 185 | "port": "8182", 186 | "path": [ 187 | "index", 188 | "link", 189 | "add" 190 | ], 191 | "query": [ 192 | { 193 | "key": "", 194 | "value": "", 195 | "disabled": true 196 | } 197 | ] 198 | } 199 | }, 200 | "response": [] 201 | }, 202 | { 203 | "name": "Upload link (website -> pdf)", 204 | "request": { 205 | "method": "POST", 206 | "header": [], 207 | "body": { 208 | "mode": "formdata", 209 | "formdata": [ 210 | { 211 | "key": "files", 212 | "type": "file", 213 | "src": [], 214 | "disabled": true 215 | }, 216 | { 217 | "key": "knowledgebase_id", 218 | "value": "a3f009c8-0159-467a-bbca-067cbe76907b", 219 | "type": "default" 220 | }, 221 | { 222 | "key": "url", 223 | "value": "https://trentstauffer.ca/resume.pdf", 224 | "type": "default" 225 | } 226 | ] 227 | }, 228 | "url": { 229 | "raw": "localhost:8182/index/link/add", 230 | "host": [ 231 | "localhost" 232 | ], 233 | "port": "8182", 234 | "path": [ 235 | "index", 236 | "link", 237 | "add" 238 | ], 239 | "query": [ 240 | { 241 | "key": "", 242 | "value": "", 243 | "disabled": true 244 | } 245 | ] 246 | } 247 | }, 248 | "response": [] 249 | }, 250 | { 251 | "name": "Adjust", 252 | "request": { 253 | "method": "POST", 254 | "header": [], 255 | "url": { 256 | "raw": "localhost:8182/adjust", 257 | "host": [ 258 | "localhost" 259 | ], 260 | "port": "8182", 261 | "path": [ 262 | "adjust" 263 | ] 264 | } 265 | }, 266 | "response": [] 267 | } 268 | ] 269 | } -------------------------------------------------------------------------------- /testing.txt: -------------------------------------------------------------------------------- 1 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 2 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 3 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 4 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 5 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 6 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 7 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 8 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 9 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 10 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 11 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 12 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 13 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 14 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 15 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 16 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 17 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 18 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 19 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 20 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 21 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 22 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 23 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 24 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 25 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 26 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 27 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 28 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 29 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 30 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 31 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 32 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 33 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 34 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 35 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 36 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 37 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 38 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 39 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 40 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 41 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 42 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 43 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 44 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 45 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 46 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 47 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 48 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 49 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 50 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 51 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 52 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 53 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 54 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 55 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 56 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 57 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 58 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 59 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 60 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 61 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 62 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 63 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 64 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 65 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 66 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 67 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 68 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 69 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 70 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 71 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 72 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 73 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 74 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 75 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 76 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 77 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 78 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 79 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 80 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 81 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 82 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 83 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 84 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 85 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 86 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 87 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 88 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 89 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 90 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 91 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 92 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 93 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 94 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 95 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 96 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 97 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 98 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 99 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 100 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 101 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 102 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 103 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 104 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 105 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 106 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 107 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 108 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 109 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 110 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 111 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 112 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 113 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 114 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 115 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 116 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 117 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 118 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 119 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 120 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 121 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 122 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 123 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 124 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 125 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 126 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 127 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 128 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 129 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 130 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 131 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 132 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 133 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 134 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 135 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 136 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 137 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 138 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 139 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 140 | This document is a text file that contains the code word required for faq-service's integration tests to pass. In order to successfully pass the test, the test has to read from this file that the code word is "test". 141 | --------------------------------------------------------------------------------