---
html:
toc: true
offline: true
export_on_save:
html: true
---
# REST API를 파이프라인하여 새로운 융합 REST API 만들기 (ConvergedServingEndToEndExample)
---
본 예제에서는 사용자가 프로그래밍한 Python 함수를 KSB Dockerize를 이용하여 REST API로 만들고, 이러한 API 들을 연결하여 서빙하는 융합서빙 예제를 설명합니다.
KSB Dockerize 라이브러리는 사용자가 프로그래밍한 Python 함수를 Docker 이미지로 자동 변환할 뿐만 아니라 REST API 기능을 자동으로 추가하여, 사용자 Python 함수로 입력 데이터를 보내고 결과를 받을 수 있게 합니다.
## 입력 데이터 준비하기
본 예제에서는 다음과 같은 챗봇 시나리오를 가정합니다.
1. 입력한 문장의 topic 분류
2. topic이 일상적 대화일 경우, 일상적 대화들을 학습한 모델로 응답 문장 생성
topic이 여행과 관련된 대화일 경우, 여행 관련 대화들을 학습한 모델로 응답 문장 생성
따라서 topic 분류 모델(이하 classify로 칭함), 일상적 대화들을 학습한 모델(이하 chitchat으로 칭함), 여행 관련 대화들을 학습한 모델(이하 travel로 칭함)이 필요합니다. 이러한 모델들을 KSB Dockerize를 이용하여 프레임워크에서 서빙하기 위해 Python 모듈 사용하기 (KSB Dockerize) 매뉴얼에서 제공하는 방법대로 `base.py` 와 `main_func`을 생성해야 합니다.
### 사용자 파이썬 코드 업로드
Host PC의 /home/csle/ksb-csle/examples/pyModules/ChatbotServing 폴더에 있는 사용자가 프로그래밍한 파이썬 함수를 HDFS repository에 웹툴킷을 이용하여 업로드 합니다.
dataset/pyModules 위치에 ChatbotServing 폴더를 업로드 합니다.
![코드 업로드](./images/2.5.13_codeUpload.png)
파이썬 코드가 아래 폴더 구조로 업로드 되어야 합니다. KSB Dockerize를 이용하기 위해서 `__init.py` 파일과 `base.py` 파일을 작성합니다. `base.py` 는 `main_func` 이라는 함수를 포함하고 있어야 합니다. KSB Dockerize 라이브러리는 이 `main_func`을 불러 수행합니다.
![코드 업로드](./images/2.5.13_codeUpload2.png)
다음은 chitchat 폴더 내의 `base.py` 의 `main_func` 을 보여줍니다.
```python
def main_func(x):
# get input
input = x
text = tune_text(input)
# call your function
output_text = predict(text)
return str(output_text)
```
## 워크플로우 생성하기
워크플로우 편집 화면에서 워크플로우를 작성합니다. 본 예제에서는 네 개의 엔진을 생성합니다.
- 워크플로우 속성
속성 | 값 | 비고
--|---|--
name | ConvergedServingEndToEndExample | 워크플로우 이름
description | 융합서빙 예제 | 워크플로우를 설명하는 글
isBatch | false | 융합서빙을 위한 워크플로우 이므로, false 로 지정
verbose | false | 디버깅을 위해 로그정보를 보고자할 경우, true 로 지정
- 엔진 속성
순번 | 엔진 Type | NickName | RunType | 설명
--|---|---|---|--
1 | OnDemandExternalServing | ClassifyDockerizeEngine | 즉시실행 | classify 함수 Dockerize
2 | OnDemandExternalServing | ChitchatDockerizeEngine | 즉시실행 | chitchat 함수 Dockerize
3 | OnDemandExternalServing | TravelDockerizeEngine | 즉시실행 | travel 함수 Dockerize
4 | OnDemandPipeServing | ChatbotServingEngine | 즉시실행 | API들을 연결하여 챗봇 서빙
### 첫 번째 엔진 작성하기
topic 분류 모델(classify) 을 구현한 파이썬 코드를 Dockerize 하고 REST API 로 만들기 위해 OnDemandExternalServing엔진을 선택합니다. 본 예제의 OnDemandExternalServing 엔진은 Runner 만 가집니다.
#### Runner
PyContainerRunner를 선택하고 아래표와 같은 속성을 지정합니다.
field |value | 설명
--|---|--
port | 18001 | REST API 포트 번호
imgName | classify_image | 생성할 도커 이미지 이름
codePath | dataset/pyModules/ChatbotServing/classify | 파이썬 코드가 있는 경로
### 두 번째 엔진 작성하기
일상적 대화들을 학습한 모델(chitchat) 을 구현한 파이썬 코드를 Dockerize 하고 REST API 로 만들기 위해 OnDemandExternalServing엔진을 선택합니다. 본 예제의 OnDemandExternalServing 엔진은 Runner 만 가집니다.
#### Runner
PyContainerRunner를 선택하고 아래표와 같은 속성을 지정합니다.
field |value | 설명
--|---|--
port | 18002 | REST API 포트 번호
imgName | chitchat_image | 생성할 도커 이미지 이름
codePath | dataset/pyModules/ChatbotServing/chitchat | 파이썬 코드가 있는 경로
### 세 번째 엔진 작성하기
여행 관련 대화들을 학습한 모델(travel) 을 구현한 파이썬 코드를 Dockerize 하고 REST API 로 만들기 위해 OnDemandExternalServing엔진을 선택합니다. 본 예제의 OnDemandExternalServing 엔진은 Runner 만 가집니다.
#### Runner
PyContainerRunner를 선택하고 아래표와 같은 속성을 지정합니다.
field |value | 설명
--|---|--
port | 18003 | REST API 포트 번호
imgName | travel_image | 생성할 도커 이미지 이름
codePath | dataset/pyModules/ChatbotServing/travel | 파이썬 코드가 있는 경로
### 네 번째 엔진 작성하기
여기에서는 하나 이상의 REST API들을 연결하여 상위의 REST API를 제공하기 위한 엔진을 정의합니다. 이를 위해서 융합서빙엔진을 정의하기 위한 과정을 설명합니다.
먼저 융합서빙엔진을 구성하기 위한 엔진컨터이너로서 OnDemandPipeServing 엔진컨테이너를 선택합니다. 참고로 OnDemandPipeServing 엔진은 REST API를 통해서 request를 받아 일련의 오퍼레이터를 거치면서 처리한 결과를 response에 실어서 보냅니다. 따라서 데이터를 입출력하기 위한 Reader 와 Writer는 별도로 필요로 하지 않습니다.
#### Controller
하나 이상의 REST API를 하나의 로직으로 연결하여 서빙하기 위한, 하나 이상의 오퍼레이터들의 호출흐름을 제어하기 위한 컨트롤러로서 OnDemandCompositeServingRestfulController를 선택합니다.
(OnDemandCompositeServingRestfulController는 restfulActorName과 uri정보를 설정할 수 있으며, 본 예제에서는 별도의 설정없이 기본값을 사용합니다. 요청 uri를 변경하고자 할 경우, 값을 입력하면 됩니다.)
#### Runner
REST 서비스를 실행하기 위한 Runner로써 ServingPipeRunner를 선택합니다. 사용자가 챗봇 서비스를 받기 위한 REST API에 대한 접근 주소와 포트번호를 설정합니다. (아래 표 참조)
field | value | 설명
--|---|--
host | 0.0.0.0 | 융합 서빙 주소
port | 18080 | 융합 서빙 포트 번호
#### Operator
본 챗봇 예제에서는 문장으로부터 topic을 분류하기 위한 REST API와 분류된 topic 내에서 문장에 대한 응답을 생성하는 REST API를 엮기 위해 총 3개의 오퍼레이터를 사용합니다.
각각의 오퍼레이터의 역할은 다음과 같습니다:
- **RouteRestfulContextQueryPipeOperator**
- topic 분류기 서비스 REST API (상기 첫번째 엔진을 통해서 생성될 API)를 호출하여 문장에 대한 topic 분류값을 얻어내기위한 질의 오퍼레이터
- **RouteMappingPipeOperator**
- 얻어낸 topic 분류값을 응답을 질의할 REST 서비스의 주소로 변환하는 매핑 오퍼레이터
- **OutputRestfulContextQueryPipeOperator**
- 매핑된 REST API 주소로 질의하여 최종 응답문구를 얻어내기위한 질의 오퍼레이터
1. **RouteRestfulContextQueryPipeOperator**
field |value | 설명
--|---|--
url | http://SERVER_IP:18001 | REST API 주소
method | POST | REST API 방식
header | | 아래의 표 참고
header 설정은 다음과 같이 합니다.
field |value | 설명
--|---|--
paramValue | Content-Type |
paramName | text/plain; charset=utf-8 |
2. **RouteMappingPipeOperator**
field |value | 설명
--|---|--
routeMap | | 아래의 표 참고
routeMap 설정은 [+] 버튼을 두 번 클릭하여 다음과 같이 합니다.
field |value | 설명
--|---|--
idx | 0 |
route | http://SERVER_IP:18002 | '0' 인덱스값에 대한 호출할 REST API 주소
field |value | 설명
--|---|--
idx | 1 |
route | http://SERVER_IP:18003 | '1' 인덱스값에 대한 호출할 REST API 주소
3. **OutputRestfulContextQueryPipeOperator**
field |value | 설명
--|---|--
url | http://SERVER_IP:18002 | route 정보가 넘어오지 않을 경우 디폴트로 호출할 REST API 주소
method | POST | REST API 방식
header | | 아래의 표 참고
header 설정은 다음과 같이 합니다.
field |value | 설명
--|---|--
paramValue | Content-Type |
paramName | text/plain; charset=utf-8 |
![워크플로우 완성 화면](./images/2.5.13_workflow.png)
ksbuser@etri.re.kr 계정으로 로그인하면, 본 예제 워크플로우가 이미 만들어져 있는 것을 확인할 수 있습니다. 불러오기 메뉴를 통해 본 예제를 실행할 수 있습니다.
## 워크플로우 실행 및 모니터링하기
### 워크플로우 실행하기
워크플로우 편집기 화면 상단의 실행 버튼을 눌러서, 위에서 작성한 워크플로우를 실행합니다. REST API를 서빙하는 워크플로우 이므로 Batch 체크박스를 해제하고 Run 버튼을 클릭합니다.
### 워크플로우 모니터링 하기
KSB 웹툴킷 상단 메뉴의 Monitoring 탭을 클릭하면 Workflow 탭이 선택되어 있습니다. Workflow 탭 화면에서 실행한 워크플로우와 엔진의 동작 상태를 확인할 수 있습니다. 정상적으로 실행되어 Status 값이 Inprogress(실행중)인 것을 확인할 수 있습니다.
![워크플로우 동작 상태 확인](./images/2.5.13_monitoring.png)
Workflow History 탭을 선택하면 워크플로우가 동작하며 발생시킨 각 엔진의 로그 정보를 확인할 수 있습니다.
![워크플로우 히스토리](./images/2.5.13_monitoring_history.png)
## 클라이언트에서 융합 REST API 로 쿼리 요청하기
프레임워크에서 서빙하고 있는 융합 REST API (`http://0.0.0.0:18080`) 로 클라이언트에서 쿼리를 요청하는 방법은 다음과 같습니다.
**◼ 약속된 Json 형태 : {"input" : "???"}**
"input" 키워드와 함께 보내고자 하는 데이터를 `{"input" : "???"}` 형태의 Json 으로 보냅니다. 이 경우 응답도 json 으로 옵니다. 약속된 Json 형태로 쿼리를 요청하는 것을 권장합니다.
- **문자열을 보내고자 하는 경우**:
보내고자 하는 데이터 (예: `aaa`) 를 다음과 같은 형태로 보냅니다.
```Json
{"input" : "aaa"}
```
이 경우 내부적으로 쿼리를 해석하여 KSB Dockerize 라이브러리의 `base.py` 의 `main_func` 으로 데이터를 문자열 형태로 보냅니다. 즉 문자열 `'aaa'`를 보냅니다.
따라서 사용자 파이썬 코드에서는 아래와 같이 입력데이터를 읽을 수 있습니다.
```Python
def main_func(x):
# In this example, x is Python string.
input = x
...
```
- **Json 문자열을 보내고자 하는 경우**:
보내고자 하는 데이터 (예: `{"key1":"aaa", "key2":"bbb", "key3":"ccc"}`) 를 다음과 같은 형태로 보냅니다.
```JSON
{"input": "{\"key1\":\"aaa\", \"key2\":\"bbb\", \"key3\":\"ccc\"}"}
```
이 경우 내부적으로 쿼리를 해석하여 KSB Dockerize 라이브러리의 `base.py` 의 `main_func` 으로 Json 문자열 데이터를 Json 문자열 형태로 보냅니다. 즉 `'{"key1":"aaa", "key2":"bbb", "key3":"ccc"}'` 를 보냅니다.
따라서 사용자 파이썬 코드에서는 아래와 같이 입력데이터를 읽을 수 있습니다.
```Python
import json
def main_func(x):
# In this example, x is Python string containing Json data.
x = json.loads(x)
input1 = x['key1']
input2 = x['key2']
input3 = x['key3']
...
```
**◼ 문자열 형태 **
보내고자 하는 데이터를 문자열로 보냅니다 (예를 들면, `"aaa"`). 이 경우 응답도 문자열로 옵니다.
문자열 형태로 쿼리를 보낼 경우, 사용자가 작성한 그대로의 문자열을 KSB Dockerize 라이브러리의 `base.py` 의 `main_func` 으로 보냅니다. (예를 들면, `'aaa'` )
따라서 사용자 파이썬 코드에서는 아래와 같이 입력데이터를 읽을 수 있습니다.
```Python
def main_func(x):
# In this example, x is Python string.
input = x
...
```
### 클라이언트 프로그램 구현 가이드 (파이썬)
융합 REST API 로 쿼리를 보내는 클라이언트 프로그램을 파이썬으로 구현한 예제 코드입니다.
**◼ 약속된 Json 형태 : {"input" : "???"}**
```Python
import requests
url = 'http://0.0.0.0:18080'
userId = 'ksbuser@etri.re.kr'
query_str = '{0}/postQuery?userId={1}&password="pw"&key="key"'
query_str = query_str.format(url, userId)
# data_str = {"input": "hellow"}
# data_str = {"input": "{\"key1\":\"aaa\", \"key2\":\"bbb\", \"key3\":\"ccc\"}"}
data_str = {"input": '{"key1":"aaa", "key2":"bbb", "key3":"ccc"}'}
r = requests.post(query_str, json=data_str)
print('Status: ', r.status_code)
print('Response: ', r.text)
```
**◼ 문자열 형태 **
```Python
import requests
url = 'http://0.0.0.0:18080'
userId = 'ksbuser@etri.re.kr'
query_str = '{0}/postQuery?userId={1}&password="pw"&key="key"'
query_str = query_str.format(url, userId)
data_str = 'I want to go LA'
r = requests.post(query_str, data=data_str)
print('Status: ', r.status_code)
print('Response: ', r.text)
```
## 융합서빙 서비스 확인하기
본 예제에서는 클라이언트로 Postman 프로그램을 활용하여 융합서빙 엔진이 제공하는 챗봇 서비스(융합 REST API)를 테스트 합니다. Postman을 실행하여 아래와 같이 설정합니다. (Postman 설명 바로가기)
- 좌측 상단의 콤보 박스에서 **POST** 를 선택하고, 질의 선택할 URL을 입력합니다. 본 예제에서의 URL은 융합 서빙 엔진이 제공하는 URL (``http://0.0.0.0:18080/postQuery?userId=ksbuser@etri.re.kr&password="pw"&key="key"``)을 사용합니다.
- **Headers** 메뉴에서 Key 필드에 "Content-Type"을, Value 필드에 "text/plain; charset=utf-8" 을 입력합니다.
![Postman 설정](./images/2.5.13_postmanSetting.png)
- **Body** 메뉴에서 **raw** 라디오 버튼을 선택한 후, ``{"input": "TEXT"}`` 형태로 입력 문장을 작성합니다. 약속된 Json 형태로 융합 REST API 에 쿼리를 요청합니다.
- 우측 상단의 **Send** 버튼을 클릭하면 쿼리 요청이 제출되고, 결과가 화면 하단에 표시됩니다. 입력 문장에 대한 응답 문장이 표시 됩니다.
![챗봇 서비스 확인하기](./images/2.5.13_servingResult1.png)
![챗봇 서비스 확인하기](./images/2.5.13_servingResult2.png)
## 생성된 Docker 이미지 확인하기
커맨드 창에서 아래 명령어를 실행합니다.
```sh
docker container ls
```
아래 그림과 같이 Docker 이미지가 생성된 것을 확인할 수 있습니다.
![Docker 이미지 생성](./images/2.5.13_dockerImage.png)
## KSB Dockerize를 통해 생성된 REST API 동작 확인하기
커맨드 창에서 아래 명령어를 실행하여 KSB Dockerize를 통해 생성된 REST API 가 잘 동작하는 지 테스트할 수 있습니다.
```sh
curl -d '{"input": "TEXT"}' http://0.0.0.0:PORT
```
![Docker 이미지 테스트](./images/2.5.13_dockerTest.png)
- topic 분류 모델(classify)
- PORT 번호 : 18001
- 입력 문장의 topic을 분류한 결과 리턴 (0: chitchat, 1: travel)
- 본 예제에서는 dummy 코드로 입력 문장에 'go' 가 포함될 경우 1, 그 밖에는 0을 리턴함
- 일상적 대화들을 학습한 모델(chitchat)
- PORT 번호 : 18002
- 입력 문장에 대응하는 문장 리턴
- 본 예제에서는 dummy 코드로 'Hi, I am chitchat'을 리턴함
- 여행 관련 대화들을 학습한 모델(travel)
- PORT 번호 : 18003
- 입력 문장에 대응하는 문장 리턴
- 본 예제에서는 dummy 코드로 'Hi, I am travel agency'를 리턴함
## 워크플로우 종료하기
KSB 웹툴킷 Monitoring 화면의 Workflow 탭에서, 현재 Status가 Inprogress인 ConvergedServingEndToEndExample 워크플로우의 정지(◼) 버튼을 눌러 종료 시킵니다.
## 워크플로우 재실행 시 유의사항
동일한 워크플로우를 재실행하기 위해서 기존 생성된 chitchat_image, classify_image, travel_image 도커 이미지와 컨테이너를 지운 후 워크플로우를 재실행 합니다.
아래 명령어를 이용하여 Docker 이미지 목록을 확인합니다.
```sh
docker images
```
아래 명령어를 이용하여 Docker 컨테이너를 삭제합니다.
```sh
docker rm -f {IMAGE_ID}
```
아래 명령어를 이용하여 Docker 이미지를 삭제합니다.
```sh
docker rmi {IMAGE_ID}
```
```sh
csle@csle1:~$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
chitchat_image latest 054e56309341 2 hours ago 221MB
classify_image latest a4fd33bd0ba6 2 hours ago 221MB
travel_image latest 6c3bff27b0f5 2 hours ago 221MB
csle@csle1:~$ docker rm -f chitchat_image classify_image travel_image
csle@csle1:~$ docker rmi chitchat_image classify_image travel_image
```