Skip to content

Commit

Permalink
Merge branch 'v0.1.1'
Browse files Browse the repository at this point in the history
  • Loading branch information
ifTNT committed Apr 14, 2024
2 parents 5ede83e + 802eb6d commit d055606
Show file tree
Hide file tree
Showing 128 changed files with 3,124 additions and 2,067 deletions.
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@
To run this application, please make sure the following packages are installed on your system:

- Node.js v20.11.1 & npm
- PHP 8.1.27 & php-fpm & Composer
- Python 3.9.5 & pip
- PHP 8.1 & php-fpm & Composer
- Python 3.10 & pip
- Nginx or Apache
- Redis 6.0.20
- CUDA
Expand All @@ -84,6 +84,7 @@ Alternatively, you can refer to the following steps to install the entire system
./production_update.sh
cd ../kernel
pip install -r requirement.txt
sudo chown -R $(whoami):www-data /var/www/html
```

- For Windows:
Expand Down
5 changes: 3 additions & 2 deletions README_TW.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@
為了執行此應用,請確保您的系統上安裝了以下套件:

- Node.js v20.11.1 & npm
- PHP 8.1.27 & php-fpm & Composer
- Python 3.9.5 & pip
- PHP 8.1 & php-fpm & Composer
- Python 3.10 & pip
- Nginx 或 Apache
- Redis 6.0.20
- CUDA
Expand All @@ -84,6 +84,7 @@
./production_update.sh
cd ../kernel
pip install -r requirement.txt
sudo chown -R $(whoami):www-data /var/www/html
```

- 對於 Windows:
Expand Down
21 changes: 21 additions & 0 deletions docker/dbqa.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
services:
dbqa-executor:
build:
context: ../
dockerfile: docker/executor/Dockerfile
image: kuwa-executor
environment:
CUSTOM_EXECUTOR_PATH: ./docqa/docqa.py
EXECUTOR_ACCESS_CODE: db-qa
EXECUTOR_NAME: DB QA
volumes: [ "</path/to/vector-database>:/var/database" ]
depends_on:
- kernel
- multi-chat
command: [
"--api_base_url", "http://web/",
"--access_code", "db-qa"
"--database", "/var/database"
]
restart: unless-stopped
networks: ["backend", "frontend"]
20 changes: 20 additions & 0 deletions docker/docqa_webqa.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
services:
docqa-executor:
build:
context: ../
dockerfile: docker/executor/Dockerfile
image: kuwa-executor
environment:
CUSTOM_EXECUTOR_PATH: ./docqa/docqa.py
EXECUTOR_ACCESS_CODE: doc-qa;web-qa
EXECUTOR_NAME: DocQA;WebQA
depends_on:
- kernel
- multi-chat
command: [
"--api_base_url", "http://web/",
"--access_code", "doc-qa",
"--alt_access_code", "web-qa"
]
restart: unless-stopped
networks: ["backend", "frontend"]
2 changes: 2 additions & 0 deletions docker/executor/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ COPY src/executor/requirements.txt ./
RUN sed -i '/\.[/]*/d' ./requirements.txt &&\
sed -i '/torch.*/d' ./requirements.txt
RUN pip install --no-cache-dir -r requirements.txt
COPY src/executor/docqa/requirements.txt ./docqa/requirements.txt
RUN pip install --no-cache-dir -r ./docqa/requirements.txt

# Install the executor framework
COPY src/executor/. .
Expand Down
24 changes: 21 additions & 3 deletions docker/executor/docker-entrypoint
Original file line number Diff line number Diff line change
@@ -1,19 +1,37 @@
#!/bin/sh
#!/bin/bash
set -xeu

add_executor(){
if [ -z "${EXECUTOR_NAME:-}" ] || [ -z "${EXECUTOR_ACCESS_CODE:-}" ]
then
return
fi
multi-chat-client add-executor "${EXECUTOR_ACCESS_CODE}" "${EXECUTOR_NAME}" || return 0
IFS=';' read -r -a access_code_arr <<< "${EXECUTOR_ACCESS_CODE}"
IFS=';' read -r -a name_arr <<< "${EXECUTOR_NAME}"
for idx in "${!name_arr[@]}"
do
access_code="${access_code_arr[idx]}"
name="${name_arr[idx]}"
multi-chat-client add-executor "${access_code}" "${name}" || true
done
}

if [ "${ADD_EXECUTOR_TO_MULTI_CHAT}" = true ]
then
add_executor
fi

if [ -z "${CUSTOM_EXECUTOR_PATH:-}" ]
then
exec kuwa-executor ${EXECUTOR_TYPE} \
--kernel_url ${KERNEL_URL} \
--access_code ${EXECUTOR_ACCESS_CODE} \
$@ 2>&1
$@ 2>&1
else
dir_name="$(dirname "${CUSTOM_EXECUTOR_PATH}")"
file_name="$(basename "${CUSTOM_EXECUTOR_PATH}")"
cd ${dir_name}
exec python "${file_name}" \
--kernel_url ${KERNEL_URL} \
$@ 2>&1
fi
2 changes: 1 addition & 1 deletion docker/gemini.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@ services:
depends_on:
- kernel
- multi-chat
command: []
command: ["--api_key", "<YOUR_GLOBAL_API_KEY_HERE>"]
restart: unless-stopped
networks: ["backend"]
5 changes: 3 additions & 2 deletions scripts/translate.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,12 @@ def invoke_model(prompt, model="gemini-pro", base_url="https://chatdev.gai.tw"):
if not resp.ok:
raise RuntimeError(f'Request failed with status {response.status_code}')
for line in resp.iter_lines(decode_unicode=True):
if not line or line == "event: end": break
if line == "event: close": break
elif line.startswith("data: "):
chunk = json.loads(line[len("data: "):])["choices"][0]["delta"]["content"]
yield chunk


def translate(input_filename, target_filename=None, lang="en", quite=False):
prompt = {
"en": "Translate to English.",
Expand All @@ -35,7 +36,7 @@ def translate(input_filename, target_filename=None, lang="en", quite=False):
with open(input_filename,encoding="utf-8") as f:
input_content = f.read()
prompt += "\n" + input_content

output_content = ''
for chunk in invoke_model(prompt):
output_content += chunk
Expand Down
1 change: 1 addition & 0 deletions src/executor/docqa/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
web_data/
27 changes: 27 additions & 0 deletions src/executor/docqa/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
Doc QA
---
A Retrieval-Augmented Generation Executor.

## Production Deployment

This executor has two configurations.
1. DocQA/WebQA: Retrieve information from the documents or web pages
2. DatabaseQA(DBQA): Retrieve information from a pre-built vector database

### Prerequisite
1. Install the dependency
```sh
cd src/executor/webqa
pip install -r requirements.txt
```
2. You can obtain the KUWA_TOKEN from the website's profile > API Token Management > Kuwa Chat API Token

### DocQA/WebQA
```
python ./docqa.py --access_code doc-qa --api_base_url http://localhost/ --api_key <KUWA_TOKEN> [--model <MODEL_NAME>]
```

### DBQA
```
python ./docqa.py --access_code dbqa --api_base_url http://localhost/ --api_key <KUWA_TOKEN> [--model <MODEL_NAME>] --database /path/to/pre-built/vector-db
```
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Document QA (DocQA)
Web QA
---
Retrieve-Augmented Generation Worker.
A Retrieval-Augmented Generation Executor.

## Production Deployment

Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
154 changes: 154 additions & 0 deletions src/executor/docqa/docqa.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
#!/bin/python3
# -#- coding: UTF-8 -*-

import os
import re
import sys
import logging
import asyncio
import functools
import itertools
import requests
import json
import i18n
sys.path.append(os.path.dirname(os.path.abspath(__file__)))

from typing import Generator
from urllib.parse import urljoin
from kuwa.executor import LLMExecutor

from src.docqa import DocQa
from src.kuwa_llm_client import KuwaLlmClient
from src.document_store import DocumentStore

logger = logging.getLogger(__name__)

class NoUrlException(Exception):
def __init__(self, msg):
self.msg = msg
def __str__(self):
return self.msg

class DocQaExecutor(LLMExecutor):
def __init__(self):
super().__init__()

def _try_register(self):
super()._try_register()
if self.alt_access_code:
resp = requests.post(
url=urljoin(self.kernel_url, f"{self.executor_iface_version}/worker/register"),
data={"name": self.alt_access_code, "endpoint": self.get_reg_endpoint()}
)
if not resp.ok or resp.text == "Failed":
raise RuntimeWarning("The server failed to register to kernel.")

def _shut_down(self):
super()._shut_down()
if self.alt_access_code:
try:
response = requests.post(
urljoin(self.kernel_url, f"{self.executor_iface_version}/worker/unregister"),
data={"name": self.alt_access_code,"endpoint": self.get_reg_endpoint()}
)
if not response.ok or response.text == "Failed":
raise RuntimeWarning()
else:
logger.info("Unregistered from kernel.")
except requests.exceptions.ConnectionError as e:
logger.warning("Failed to unregister from kernel")

def extend_arguments(self, parser):
parser.add_argument('--lang', default="en", help='The language code to internationalize the aplication. See \'lang/\'')
parser.add_argument('--database', default=None, type=str, help='The path the the pre-built database.')
parser.add_argument('--api_base_url', default="http://127.0.0.1/", help='The API base URL of Kuwa multi-chat WebUI')
parser.add_argument('--api_key', default=None, help='The API authentication token of Kuwa multi-chat WebUI')
parser.add_argument('--limit', default=3072, type=int, help='The limit of the LLM\'s context window')
parser.add_argument('--model', default=None, help='The model name (access code) on Kuwa multi-chat WebUI')
parser.add_argument('--mmr_k', default=6, type=int, help='Number of chunk to retrieve after Maximum Marginal Relevance (MMR).')
parser.add_argument('--mmr_fetch_k', default=12, type=int, help='Number of chunk to retrieve before Maximum Marginal Relevance (MMR).')
parser.add_argument('--chunk_size', default=512, type=int, help='The charters in the chunk.')
parser.add_argument('--chunk_overlap', default=128, type=int, help='The overlaps between chunks.')
parser.add_argument('--alt_access_code', default=None, type=str, help='The alternate access code.')

def setup(self):
i18n.load_path.append(f'lang/{self.args.lang}/')
i18n.config.set("error_on_missing_translation", True)
i18n.config.set("locale", self.args.lang)

self.pre_built_db = self.args.database
self.llm = KuwaLlmClient(
base_url = self.args.api_base_url,
kernel_base_url = self.kernel_url,
model=self.args.model,
auth_token=self.args.api_key
)
self.document_store = DocumentStore(
mmr_k = self.args.mmr_k,
mmr_fetch_k = self.args.mmr_fetch_k,
chunk_size = self.args.chunk_size,
chunk_overlap = self.args.chunk_overlap
)
self.docqa = DocQa(
document_store = self.document_store,
vector_db = self.pre_built_db,
llm = self.llm,
lang = self.args.lang
)
self.alt_access_code = self.args.alt_access_code
self.proc = False

def extract_last_url(self, chat_history: list):
"""
Find the latest URL provided by the user and trim the chat history to there.
"""

url = None
begin_index = 0
user_records = list(filter(lambda x: not x["isbot"], chat_history))
for i, record in enumerate(reversed(user_records)):

urls_in_msg = re.findall(r'^(https?://[^\s]+)$', record["msg"])
if len(urls_in_msg) != 0:
url = urls_in_msg[-1]
begin_index = len(chat_history) - i - 1
break

return url, chat_history[begin_index:]

async def llm_compute(self, data):
chat_history = json.loads(data.get("input"))
auth_token = data.get("user_token") or self.args.api_key
url = None

try:
if self.pre_built_db == None:
url, chat_history = self.extract_last_url(chat_history)
if url == None : raise NoUrlException(i18n.t('docqa.no_url_exception'))

chat_history = [{"isbot": False, "msg": None}] + chat_history[1:]
self.proc = True
response_generator = self.docqa.process(urls=[url], chat_history=chat_history, auth_token=auth_token)
async for reply in response_generator:
if not self.proc:
await response_generator.aclose()
yield reply

except NoUrlException as e:
yield str(e)

except Exception as e:
await asyncio.sleep(2) # To prevent SSE error of web page.
logger.exception('Unexpected error')
yield i18n.t("docqa.default_exception_msg")

async def abort(self):
if self.proc:
self.proc = False
logger.debug("aborted")
return "Aborted"
return "No process to abort"

if __name__ == "__main__":
executor = DocQaExecutor()
executor.run()
7 changes: 7 additions & 0 deletions src/executor/docqa/lang/en/docqa.en.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
en:
no_url_exception: No URL found.
default_exception_msg: An error occurred, please try again or contact the administrator.
summary_prompt: "Please provide a summary of this article:"
error_fetching_document: An error occurred while trying to fetch the document {}. Please make sure the submitted document exists and is publicly available.
summary_question: When, where, what, outcome, summary
summary_prefix: "Here is a summary of the article:"
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Please answer the questions based on the content below.{{#ref}}If there are references, please include them at the end of the answer.{{/ref}}

{{#docs}}{{page_content}}

{{/docs}}

Question: {{question}}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Article Content:

{{#docs}}{{page_content}}

{{/docs}}


Please provide a highlight summary of the above article.

Summary:
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Translate to English: {{question}}
Loading

0 comments on commit d055606

Please sign in to comment.