AI&GameDev

AI와 게임개발에 관련된 이야기

OpenAI Assistants API와 Retrieval로 챗봇 만들기: 5단계 완전 가이드

openai, chatgpt, assistants api, retrieval, 어시스턴트 api, 공공데이터플랫폼

이전 글 “OpenAI의 Assistants API 인공지능 기반 챗봇 개발의 새로운 지평“에서 살펴본 기본 개념과 사용 방법을 바탕으로 이번 글에서는 Assistants API의 도구 중 하나인 Retrieval을 활용한 챗봇 개발에 초점을 맞춰보겠습니다. 특히 공공 데이터를 이용해 금천구 내 착한가격 업소 정보를 제공하는 챗봇 개발 과정을 자세히 소개하며 Retrieval 도구를 활용한 챗봇 개발의 실질적 방법론에 대해 알아보겠습니다.

Assistants API와 Retrieval을 사용한 챗봇

Assistants API의 Retrieval 도구를 사용하여 사용자에게 유용한 정보를 제공하는 챗봇 구축 방법을 알아봅니다. 공공데이터포털에서 서울특별시_금천구_착한가격업소 정보 데이터를 다운로드하여 Retrieval Tool을 사용하는 Assistant를 만들어, 동네의 착한가격 업소를 추천하는 챗봇을 개발해 보겠습니다.

개발을 위한 기본 설정 및 준비는 “OpenAI의 Assistants API: 인공지능 기반 ChatGPT 챗봇 개발의 새로운 지평“의 “Python으로 Assistants API 실행” 부분을 참조하세요.

공공데이터 활용

챗봇 개발 (Python)

Assistant 생성

  • 모델은 gpt-4-1106-preview를 사용했습니다.
  • tools에서 retrieval을 활성화해줍니다.
  • 자료를 먼저 생성하고, 이를 Assistant 생성 시 포함시킵니다.
# Assistant에서 사용할 파일을 업로드 합니다.
file = client.files.create(
  file=open("PriceInfo_20230310.json", "rb"),
  purpose='assistants'
)

# Assistant를 생성합니다.
assistant = client.beta.assistants.create(
    name="Good Prices in Geumcheon (Python)",
    instructions="당신은 예의 바르고 전문적인 금천구에 있는 착한 가격 업체를 알려주는 챗봇입니다. 제공된 문서를 지식 기반으로 사용하여 질문에 답하세요",
    model="gpt-4-1106-preview",
    tools=[{"type": "retrieval"}],
    file_ids=[file.id]
)
print(assistant)

file_id를 포함하여 Assistant가 생성되었습니다.

Assistant(id='asst_zZbmvHH3wMiaGXWVMTtmxIs9', created_at=1704937337, description=None, file_ids=['file-cHwSWlDXnkijtPmjGsiMHBis'], instructions='당신은 예의 바르고 전문적인 금천구에 있는 착한 가격 업체를 알려주는 챗봇입니다. 제공된 문서를 지식 기반으로 사용하여 질문에 답하세요', metadata={}, model='gpt-4-1106-preview', name='Good Prices in Geumcheon (Python)', object='assistant', tools=[ToolRetrieval(type='retrieval')])

OpenAI Platform의 Assistants 탭을 확인해 보면 정상적으로 생성된 것을 알 수 있습니다.

openai chatgpt assistants api retrieval  api

Retrieval이 활성화 되어 있고 PriceInfo_20230310.json파일이 업로드 되어 있습니다.

Assistants API Retrieval Assistant

Thread 생성

Thread를 하나 생성해 줍니다.

thread = client.beta.threads.create()
print(thread)

정상적으로 생성되었습니다.

Thread(id='thread_9YL2ZOLyJPdlp3aAGcAMRUcU', created_at=1704937436, metadata={}, object='thread')

Message 생성

생성한 Thread에 Message를 생성합니다.

question = """순대국을 파는 읍식점을 모두 알려주세요.
답변은 다음과 같은 형식으로 해주세요:
1. 음식점이름
- 전화번호: 02-123-4567
- 메뉴: 메뉴1 5000원, ...
...
"""
message = client.beta.threads.messages.create(
    thread_id="thread_9YL2ZOLyJPdlp3aAGcAMRUcU",
    role="user",
    content=question
)
print(message)

Thead에 Message가 추가된 것을 확인할 수 있습니다.

ThreadMessage(id='msg_WeCjVt3d3XoIs7VC4sgjS5Dv', assistant_id=None, content=[MessageContentText(text=Text(annotations=[], value='순대국을 파는 읍식점을 모두 알려주세요.\n답변은 다음과 같은 형식으로 해주세요:\n1. 음식점이름\n- 전화번호: 02-123-4567\n- 메뉴: 메뉴1 5000원, ...\n...\n'), type='text')], created_at=1704937483, file_ids=[], metadata={}, object='thread.message', role='user', run_id=None, thread_id='thread_9YL2ZOLyJPdlp3aAGcAMRUcU')

참고로 Message 생성 시에도 파일을 추가할 수 있습니다.

message = client.beta.threads.messages.create(
  thread_id="thread_APFoUJZ8n1KFcje6BwEwDlHk",
  role="user",
  content="돼지고기를 파는 음식점은 모두 알려주세요. 답변은 '업소명, 전화번호, 메뉴'의 형태로 해주세요.",
  file_ids=[file_shop.id]
)

Run 생성

Message가 추가된 Thread에 대해서 Assistant로부터 응답을 받기 위해 Run을 생성합니다.

run = client.beta.threads.runs.create(
  thread_id="thread_9YL2ZOLyJPdlp3aAGcAMRUcU",
  assistant_id="asst_zZbmvHH3wMiaGXWVMTtmxIs9",
)
print(run)

Run이 생성되었습니다.

Run(id='run_clOoeBrOLzbkkcu1Q35VHCCp', assistant_id='asst_zZbmvHH3wMiaGXWVMTtmxIs9', cancelled_at=None, completed_at=None, created_at=1704937531, expires_at=1704938131, failed_at=None, file_ids=['file-cHwSWlDXnkijtPmjGsiMHBis'], instructions='당신은 예의 바르고 전문적인 금천구에 있는 착한 가격 업체를 알려주는 챗봇입니다. 제공된 문서를 지식 기반으로 사용하여 질문에 답하세요', last_error=None, metadata={}, model='gpt-4-1106-preview', object='thread.run', required_action=None, started_at=None, status='queued', thread_id='thread_9YL2ZOLyJPdlp3aAGcAMRUcU', tools=[ToolAssistantToolsRetrieval(type='retrieval')])

Run 상태 확인

처음 Run을 생성하면 status가 queued 상태입니다. 이후 진행 중일 때에는 status가 in_progress가 되고 답변이 완료되면 completed 상태가 됩니다.

run = client.beta.threads.runs.retrieve(
  thread_id="thread_9YL2ZOLyJPdlp3aAGcAMRUcU",
  run_id="run_clOoeBrOLzbkkcu1Q35VHCCp"
)
print(run)

다음은 답변이 완료되었을 때 출력결과 입니다.

Run(id='run_clOoeBrOLzbkkcu1Q35VHCCp', assistant_id='asst_zZbmvHH3wMiaGXWVMTtmxIs9', cancelled_at=None, completed_at=1704937550, created_at=1704937531, expires_at=None, failed_at=None, file_ids=['file-cHwSWlDXnkijtPmjGsiMHBis'], instructions='당신은 예의 바르고 전문적인 금천구에 있는 착한 가격 업체를 알려주는 챗봇입니다. 제공된 문서를 지식 기반으로 사용하여 질문에 답하세요', last_error=None, metadata={}, model='gpt-4-1106-preview', object='thread.run', required_action=None, started_at=1704937531, status='completed', thread_id='thread_9YL2ZOLyJPdlp3aAGcAMRUcU', tools=[ToolAssistantToolsRetrieval(type='retrieval')])

Thread의 Message 리스트 확인

답변만 출력하면 다음과 같습니다.

messages = client.beta.threads.messages.list(
  thread_id="thread_9YL2ZOLyJPdlp3aAGcAMRUcU",
)
for i, message in enumerate(reversed(messages.data), start=1):
  print(f"\n# Message {i}:")
  for content in message.content:
    print(content.text.value)

결과는 다음과 같습니다.

# Message 1:
순대국을 파는 읍식점을 모두 알려주세요.
답변은 다음과 같은 형식으로 해주세요:
1. 음식점이름
- 전화번호: 02-123-4567
- 메뉴: 메뉴1 5000원, ...
...


# Message 2:
금천구에 있는 순대국을 파는 식당은 다음과 같습니다:

1. 가산 순대국
- 전화번호: 02-830-7514
- 메뉴: 순대국 7000원【9†source】.

2. 맛순이
- 전화번호: 02-861-0758
- 메뉴: 순대국 6000원, 뼈해장국 7000원【10†source】.

3. 얼큰순대국
- 전화번호: 02-809-8550
- 메뉴: 순대국 7000원, 뚝불고기 6000원【11†source】.

원하는 정보는 맞는데 【9†source】와 같이 이해할 수 없는 텍스트가 포함되어 있습니다.
print(content.text.value) 부분만 print(content)으로 변경하여 Thread의 전체 내용을 출력해 봅니다.

# Message 1:
MessageContentText(text=Text(annotations=[], value='순대국을 파는 읍식점을 모두 알려주세요.\n답변은 다음과 같은 형식으로 해주세요:\n1. 음식점이름\n- 전화번호: 02-123-4567\n- 메뉴: 메뉴1 5000원, ...\n...\n'), type='text')

# Message 2:
MessageContentText(text=Text(annotations=[TextAnnotationFileCitation(end_index=86, file_citation=TextAnnotationFileCitationFileCitation(file_id='file-cHwSWlDXnkijtPmjGsiMHBis', quote='가산 순대국",\n    "주소": "서울특별시 금천구 가산로 147-7",\n    "전화번호": "02-830-7514",\n    "메뉴": "순대국 7000원'), start_index=76, text='【9†source】', type='file_citation'), TextAnnotationFileCitation(end_index=154, file_citation=TextAnnotationFileCitationFileCitation(file_id='file-cHwSWlDXnkijtPmjGsiMHBis', quote='맛순이",\n    "주소": "서울특별시 금천구 가산로5길 72, 103호",\n    "전화번호": "02-861-0758",\n    "메뉴": "순대국 6000원+뼈해장국 7000원'), start_index=143, text='【10†source】', type='file_citation'), TextAnnotationFileCitation(end_index=224, file_citation=TextAnnotationFileCitationFileCitation(file_id='file-cHwSWlDXnkijtPmjGsiMHBis', quote='얼큰순대국",\n    "주소": "서울특별시 금천구 범안로 1201, 1층",\n    "전화번호": "02-809-8550",\n    "메뉴": "순대국 7000원+뚝불고기 6000원'), start_index=213, text='【11†source】', type='file_citation')], value='금천구에 있는 순대국을 파는 식당은 다음과 같습니다:\n\n1. 가산 순대국\n- 전화번호: 02-830-7514\n- 메뉴: 순대국 7000원【9†source】.\n\n2. 맛순이\n- 전화번호: 02-861-0758\n- 메뉴: 순대국 6000원, 뼈해장국 7000원【10†source】.\n\n3. 얼큰순대국\n- 전화번호: 02-809-8550\n- 메뉴: 순대국 7000원, 뚝불고기 6000원【11†source】.'), type='text')

답변의 annotations 부분을 보면 3개의 TextAnnotationFileCitation 이 있는데 이는 【9†source】 형식으로 표시된 답변을 위해 참고한 정보의 위치인 것을 확인할 수 있습니다. 차후 챗봇이나 앱을 개발할 때 해당 정보를 바탕으로 추가적인 처리를 할 수 있습니다.

Playground에서 확인

Playground에서 동일한 작업을 진행했을 때, 참고자료의 내용을 팝업으로 보여주는 것을 확인할 수 있습니다.

Assistants API Retrieval Playground

파일 삭제

더 이상 사용하지 않는 파일은 Assistant에서 제거할 수 있습니다. Assistant에서 파일을 제거하면 검색 인덱스에서 파일이 제거되며 해당 파일에 대해 더 이상 관련 요금이 청구되지 않습니다.

file_deletion_status = client.beta.assistants.files.delete(
  assistant_id="asst_zZbmvHH3wMiaGXWVMTtmxIs9",
  file_id="file-cHwSWlDXnkijtPmjGsiMHBis"
)

Retrieval에서 지원되는 파일 (OpenAI Platform)

다음은 사용 가능한 파일 형식의 목록입니다. ‘text/’ MIME 타입의 파일들은 인코딩이 utf-8, utf-16 또는 ascii 중 하나여야 합니다.

FILE FORMATMIME TYPE
.ctext/x-c
.cpptext/x-c++
.docxapplication/vnd.openxmlformats-officedocument.wordprocessingml.document
.htmltext/html
.javatext/x-java
.jsonapplication/json
.mdtext/markdown
.pdfapplication/pdf
.phptext/x-php
.pptxapplication/vnd.openxmlformats-officedocument.presentationml.presentation
.pytext/x-python
.pytext/x-script.python
.rbtext/x-ruby
.textext/x-tex
.txttext/plain

정리

Assistants API의 Retrieval 도구를 활용해 금천구의 착한가격 업소 정보를 검색하는 챗봇을 개발해 보았습니다. 개발 과정에서 느낀 문제점은, 2024년 1월 11일 현재까지 모델의 상세 파라미터를 설정하는 방법이 없다는 것입니다. 예를 들어, temperature를 별도로 설정할 수 없으며, 이로 인해 동일한 질문에도 답변이 달라질 수 있습니다. 이는 temperature를 0으로 설정하면 개선될 수 있는 부분이며, OpenAI에서도 이를 인지하고 있으니, 조만간 업데이트될 것으로 보입니다. 이번에는 금천구의 착한가격 업소를 찾는 기능을 개발했지만, 제품 사용법이나 신입사원 매뉴얼 등 다양한 데이터를 활용한 챗봇 개발이 가능할 것으로 보입니다.

본 글은 GPTers에 게시되고 있습니다.
https://www.gpters.org/c/llm/openai-assistants-api-retrieval-5

OpenAI Assistants API와 Retrieval로 챗봇 만들기: 5단계 완전 가이드

답글 남기기

Scroll to top