# 채팅 API 사용

## 보안

### 로그인

* 채팅 API에 사용할 JWT (access token) 을 얻기 위해 로그인 합니다.

```python
import requests

genos_url = 'GenOS 주소'

url = f"{genos_url}/app/api/chat"
body = {
    "user_id": "genos_user_01",
    "password": "<PASSWD>"
}
res = requests.post(f"{url}/auth/login",json=body)
print(res.json())
```

* 정상 출력은 아래와 같습니다.

```json
{
    "code": 0,
    "errMsg": "success",
    "data": {
        "access_token": "<JWT 1>",
        "refresh_token": "<JWT 2>",
        "user": {
            "id": 1,
            "user_id": "genos_user_01",
            "name": "GenOS 사용자  01",
            "email": "genos_user_01@mnc.ai",
            "image": "string",
            "group": null,
            "reset_required": null,
            "consent_date": null,
            "consent_required": false,
            "last_login_time": "2025-02-18T14:25:00",
            "is_active": true,
            "reg_date": "2025-01-03T14:24:58",
            "mod_date": "2025-02-18T14:25:00",
            "group_id": null,
            "group_name": null
        },
        "current_login_ip": "123.123.123.123",
        "last_login_ip": "192.168.74.172",
        "last_login_date": "2025-02-18 14:25:00"
    }
}
```

* access\_token, refresh token의 값을 저장합니다.

### (Opt.) Access 토큰 재발급

* Access 토큰 만료 시, 모든 요청에서 405 Unauthorized 에러가 발생합니다.
* refresh token을 갖고 있다면, access token의 재발급을 요청할 수 있습니다.

```python
import requests

genos_url = 'GenOS 주소'

url = f"{genos_url}/app/api/chat"
body = {
    "refresh_token": refresh_token
}
res = requests.post(f"{url}/auth/refresh",json=body)
print(res.json())
```

* 정상 출력은 아래와 같습니다.

```json
{
    "code": 0,
    "errMsg": "success",
    "data": {
        "access_token": "<JWT 3>"
    }
}
```

## 채팅

* 사용할 채팅 어플리케이션의 endpoint (`jlig_i3hi_i743` 형식) 가 필요합니다.

### 정보 조회

* 해당 채팅의 주요 정보를 조회합니다.

```python
import requests

genos_url = 'GenOS 주소'
endpoint = 'jlig_i3hi_i743'

url = f"{genos_url}/app/api/chat"
headers = {
    "Authorization": "Bearer <JWT 1>"
}
res = requests.get(f"{url}/chat/info?chat_endpoint={endpoint}", headers=headers)
print(res.json())
```

* 정상 출력은 아래와 같습니다.

```json
{
    "code": 0,
    "errMsg": "success",
    "data": {
        "chat_id": 112,      // 채팅 서비스 ID
        "chat_rev_id": 187,  // 채팅 서비스 리비전ID
        "chat_pub_id": 172,  // 채팅 서비스 배포ID
        "approval_result": "AP0001",
        "memo": null,
        "service_rev_id": 187,
        "icon": null,        // 지정되었을 경우 base64
        "reg_user": {
            "id": 1,
            "user_id": "administrator",
            "name": "관리자",
            "email": "admin@mnc.ai",
            "image": "string",
            "group": null,
            "reset_required": null,
            "consent_date": null,
            "is_active": true,
            "reg_date": "2025-01-03T14:24:58",
            "mod_date": "2025-02-18T14:25:00",
            "group_id": null,
            "group_name": null
        },
        "service_status": "CS0003",
        "title": "검색 UI",       // 채팅 서비스 이름
        "chat_name": "검색 UI",
        "description": "마크다운테스트",
        "workflow_id": 226,       // 연결된 워크플로우 ID
        "ui_template_id": 3,      // UI 타입 (1: 채팅, 2: PDF Viewer, 3: 검색)
        "session_id": "fecaa1a2-bedd-4558-a3eb-8f69035fe059",  // deprecated
        "greeting": "안녕하세요. 무엇을 도와드릴까요?",             // 인사말
        "chat_samples": [],                                    // 샘플 질문
        "feedback_type": "FILL_IN",                            // 피드백 설정
        "feedback_questions": [],
        "allow_other_feedback": true,
        "workflow_plugins": []     // 파일 기반 대화 등 플러그인 정보
    }
}
```

* chat\_id, workflow\_id의 값을 저장합니다.

### 기본 채팅요청

* 채팅 응답을 요청합니다.

```python
import requests

genos_url = 'GenOS 주소'
endpoint = 'jlig_i3hi_i743'

url = f"{genos_url}/app/api/chat"
headers = {
    "Authorization": "Bearer <JWT 1>"
}
body = {
    "question": "Hi, How are you?"
}
res = requests.post(f"{url}/chat/v2/query/{endpoint}", headers=headers, json=body)
print(res.json())
```

* 정상 출력은 아래와 같습니다.

```json
{
    "code": 0,
    "errMsg": "success",
    "data": {
        "text": "Im fine thank you, and you?",
        "question": "Hi, How are you?,
        "chatId": "b3429b0c-de84-4740-acbf-ce53a257c5f4",
        "chatMessageId": "83812818-fc9e-40cc-ade9-8666a3e3f81d",
        "sessionId": "b3429b0c-de84-4740-acbf-ce53a257c5f4"
    }
}
```

### 멀티 턴 대화

* 대화 이력을 기반으로 대화하기 위해서는, 같은 세션에 있다는 정보를 알려주어야 합니다.
* 따라서 새로고침 시나 세션 재시작 등 하나로 묶어줄 대화에서 유지할 무작위 uuid4 값을 생성하고 이를 이용하면 히스토리 기반 대화가 가능합니다.

```python
import requests
from uuid import uuid4

genos_url = 'GenOS 주소'
endpoint = 'jlig_i3hi_i743'
url = f"{genos_url}/app/api/chat"

headers = {
    "Authorization": "Bearer <JWT 1>"
}

session_id = str(uuid4()) # ex) f7a30d14-f9ae-4055-904f-594c0573e7a1
```

#### 첫번째 턴

* body의 chatId는 Flowise에 전달하기 위해서 사용되며, headers의 x-genos-session-id 는 GenOS의로깅을 위해 사용합니다.
* 원활한 로깅 및 피드백 기능을 위해 매 채팅 요청마다 uuid4를 생성하여 x-genos-trace-id 헤더에 전달합니다.

```python
headers['x-genos-session-id'] = session_id
headers['x-genos-trace-id'] = str(uuid4())

body = {
    "question": "My Name is GenOS",
    "chatId": session_id
}
res = requests.post(f"{url}/chat/v2/query/{endpoint}", headers=headers, json=body)
print(res.json())
```

* 정상 출력은 아래와 같습니다.

```json
{
    "code": 0,
    "errMsg": "success",
    "data": {
        "text": "Hi GenOS, How can I help you?",
        "question": "My Name is GenOS,
        "chatId": "f7a30d14-f9ae-4055-904f-594c0573e7a1",
        "chatMessageId": "83812818-fc9e-40cc-ade9-8666a3e3f81d",
        "sessionId": "f7a30d14-f9ae-4055-904f-594c0573e7a1"
    }
}
```

#### 두번째 턴

* 이전 대화 기록을 바탕으로 질의합니다.
* session\_id는 유지하며, trace id는 새로 생성합니다.

```python
headers['x-genos-session-id'] = session_id
headers['x-genos-trace-id'] = str(uuid4())

body = {
    "question": "What is my name?",
    "chatId": session_id
}
res = requests.post(f"{url}/chat/v2/query/{endpoint}", headers=headers, json=body)
print(res.json())
```

* 정상 출력은 아래와 같습니다.

```json
{
    "code": 0,
    "errMsg": "success",
    "data": {
        "text": "Your name is GenOS.", // 이전 대화 이력을 바탕으로 대답
        "question": "What is my name?,
        "chatId": "f7a30d14-f9ae-4055-904f-594c0573e7a1",
        "chatMessageId": "1c052c1e-52f2-49b3-ad75-b4532251899f",
        "sessionId": "f7a30d14-f9ae-4055-904f-594c0573e7a1"
    }
}
```

### (Advanced) 토큰 별 출력

* LLM 추론은 시간이 많이 걸리기 때문에, 한번에 응답을 받지 않고 토큰 별 응답을 받을 수 있습니다.
* websocket 통신을 기반으로 하는 socket.io를 이용합니다.

#### 워크플로우에 socket.io 연결

* python-socketio 라이브러리가 필요합니다.
* sid를 body에 포함하면, workflow에서 해당 sid를 가진 room에만 socket.io "token" 메시지를 emit하여 동시 요청에도 별개의 응답을 받을 수 있습니다.
* 비동기를 지원하는 예시 코드로 작성되었습니다.

```python
from socketio import AsyncClient
import aiohttp

genos_url = 'GenOS 주소'
endpoint = 'kbps_xddq_wer3'
workflow_id = 226  # 채팅 정보 조회 시 얻은 workflow ID
url = f"{genos_url}/app/api/chat/chat/v2/query/{endpoint}"
headers = 생략

async def genos_chat_query_v2(body):
    sio = AsyncClient()
    
    @sio.event
    async def connect():
        pass

    @sio.event
    async def disconnect():
        pass

    @sio.on('start') # 첫번째 토큰 응답 전에 발생
    async def start(data):
        pass

    @sio.on('end') # 마지막 토큰 응답 후에 발생
    async def end():
        pass

    @sio.on('token')
    async def token(data): # 토큰 별 응답
        print(data)

    @sio.on('sourceDocuments') # flowise vectordb의 참조 문서 정보
    async def sourceDocuments(data):
        print(data)

    await sio.connect(genos_url, socketio_path=f'/workflow/{workflow_id}/socket.io')
    
    sid = sio.get_sid() # 연결된 socketio.id 클라이언트의 sid를 body에 포함
    body['socketIOClientId'] = sid          # Flowise에 요청을 위해 사용
    headers['x-genos-workflow-sid'] = sid   # GenOS 로깅을 위해 사용
    
    async with aiohttp.ClientSession() as session:
        async with session.post(url, json=body, headers=headers) as response:
            data = await response.json()
    
    await sio.disconnect()
    return data
```

* 정상 출력은 위와 동일합니다.

## 피드백

* 채팅 요청 시 사용한 trace\_id를 이용하여 피드백을 추가할 수 있습니다.
* 부정 피드백의 경우, 채팅 정보 조회 시 얻은 피드백 타입에 따라 사양이 달라집니다.

### 긍정 피드백 · 삭제

```python
import requests

genos_url = 'GenOS 주소'
chat_id = 177 # 채팅 정보 조회 시 얻은 채팅 서비스 ID

# 아래는 피드백을 추가할 응답을 특정하기 위해 채팅 전송 요청 헤더의 "x-genos-trace-id"와 "x-genos-session-id"로 각각 지정
trace_id = '4844f357-52e6-483c-8a00-b90f1156d9c4' 
session_id = 'a404552c-d6ac-434b-aad7-4ad65ebeeb57'

url = f"{genos_url}/app/api/chat"
headers = {
    "Authorization": "Bearer <JWT 1>"
}
body = {
    "chat_id": chat_id,
    "session_id": session_id,
    "trace_id": trace_id,
    "thumbs": "up" # 긍정 피드백, "none" 이면 피드백 삭제
}
res = requests.post(f"{url}/chat/feedback/v2", headers=headers, json=body)
print(res.json())
```

* 정상 출력은 아래와 같습니다.

```json
{"code":0,"errMsg":"success","data":null}
```

### 부정 피드백

#### 주관식

* 채팅 정보 조회 시 feedback\_type이 `FILL_IN` 이면 주관식 피드백으로 지정된 채팅입니다.

```python
import requests

genos_url = 'GenOS 주소'
chat_id = 177 # 채팅 정보 조회 시 얻은 채팅 서비스 ID

# 아래는 피드백을 추가할 응답을 특정하기 위해 채팅 전송 요청 헤더의 "x-genos-trace-id"와 "x-genos-session-id"로 각각 지정
trace_id = '4844f357-52e6-483c-8a00-b90f1156d9c4' 
session_id = 'a404552c-d6ac-434b-aad7-4ad65ebeeb57'

url = f"{genos_url}/app/api/chat"
headers = {
    "Authorization": "Bearer <JWT 1>"
}
body = {
    "chat_id": chat_id,
    "session_id": session_id,
    "trace_id": trace_id,
    "thumbs": "down" # 부정피드백, "none" 이면 피드백 삭제
    "messages": [{ "comment": "The answer is unnecessarily long." }]
}
res = requests.post(f"{url}/chat/feedback/v2", headers=headers, json=body)
print(res.json())
```

* 정상 출력은 위와 동일합니다.

#### 객관식

* 채팅 정보 조회 시 feedback\_type이 `CHOICE` 이면 객관식 피드백으로 지정된 채팅입니다.
* 채팅 정보 조회 시 얻을 수 있는 기타 피드백 관련 정보는 아래와 같습니다.
  * feedback\_questions: `list[str]`, 객관식 피드백 종류
  * allow\_other\_feedback: `bool`, 정해진 객관식 피드백 외에 다른 의견으로 피드백을 추가할 수 있는지 여부

```json
    "feedback_type": "CHOICE",
    "feedback_questions": [
        "길어요",
        "틀렸어요"
    ],
    "allow_other_feedback": true,
...
```

* 요청 API
  * messages에서 question에는 얻은 채팅 정보 조회 시 얻은 feedback\_questions의 값을 그대로 넣고, allow\_other\_feedback이 true이면 "기타" 를 추가합니다.
  * is\_checked가 실제로 사용자가 해당 피드백을 체크했는지에 대한 여부입니다.
  * comment는 is\_checked가 true일 때만 값이 유효하며, 사용자가 부가적으로 추가한 의견입니다.

```python
import requests

genos_url = 'GenOS 주소'
chat_id = 177 # 채팅 정보 조회 시 얻은 채팅 서비스 ID

# 아래는 피드백을 추가할 응답을 특정하기 위해 채팅 전송 요청 헤더의 "x-genos-trace-id"와 "x-genos-session-id"로 각각 지정
trace_id = '4844f357-52e6-483c-8a00-b90f1156d9c4' 
session_id = 'a404552c-d6ac-434b-aad7-4ad65ebeeb57'

url = f"{genos_url}/app/api/chat"
headers = {
    "Authorization": "Bearer <JWT 1>"
}
body = {
    "chat_id": chat_id,
    "session_id": session_id,
    "trace_id": trace_id,
    "thumbs": "down" # 부정피드백, "none" 이면 피드백 삭제
    "messages": [
        { "question": "길어요", "comment": "불필요해요", "is_checked": True },
        { "question": "틀렸어요", "comment": "", "is_checked": False },
        { "question": "기타", "comment": "", "is_checked": False }
    ]
}
res = requests.post(f"{url}/chat/feedback/v2", headers=headers, json=body)
print(res.json())
```

* 정상 출력은 위와 동일합니다.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://genos-docs.gitbook.io/default/v1.7.5.1/advanced-tutorials/guides/api/chat.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
