12. OpenAI Assistants API로 AI 비서 만들기

이 장에서는 OpenAI의 강력한 Assistants API를 사용하여 한 단계 더 발전된 AI 애플리케이션을 구축합니다. 단순한 일회성 질의응답을 넘어, 대화의 맥락을 기억하고, 외부 도구를 사용하여 정보를 가져오거나 작업을 수행하며, 심지어 사용자가 제공한 파일을 읽고 그 내용을 기반으로 답변하는 ‘AI 비서’를 만드는 전체 과정을 학습합니다.

이 장에서 배울 내용

  • Assistants API의 핵심 개념: Assistant, Thread, Message, Run 등 Assistants API를 구성하는 핵심 객체들의 역할과 관계를 이해합니다.
  • Tool-Using Assistant: AI 비서가 웹 검색, 데이터베이스 조회 등 외부 세계와 상호작용할 수 있도록 ‘도구(Tool)’를 정의하고 연결하는 방법을 배웁니다.
  • 상태 관리(Stateful Conversation): Thread를 사용하여 여러 차례의 질문과 답변에 걸쳐 대화의 맥락을 유지하는 방법을 익힙니다.
  • 비동기 처리 및 Function Calling: AI가 도구 사용을 요청할 때(requires_action), 해당 함수를 실행하고 그 결과를 다시 AI에게 전달하는 비동기 상호작용 루프를 구현합니다.
  • RAG (검색 증강 생성): AI 비서에게 파일을 업로드하여 그 내용을 학습시키고, 파일 기반의 질문에 대해 출처를 인용하며 답변하는 RAG(Retrieval-Augmented Generation) 기능을 구현합니다.

첫 번째 AI 비서(Assistant) 생성하기

가장 먼저, AI 비서의 정체성, 지침, 그리고 능력을 정의하는 Assistant 객체를 생성합니다. 이는 우리가 만들 AI 에이전트의 청사진과 같습니다.

학습 목표

  • OpenAI Assistants API의 기본 개념과 구성 요소(Assistant, Thread, Message, Run) 이해하기
  • AI 비서가 사용할 수 있는 ‘도구(Tool)’를 JSON 스키마 형식으로 정의하는 방법 배우기
  • openai 라이브러리를 사용하여 새로운 Assistant를 생성하고 설정하는 방법 익히기

변경된 파일: notebook.ipynb

이번 장의 모든 개발 과정은 notebook.ipynb 파일 내에서 진행됩니다.

단계 1: OpenAI Assistants API 소개

Assistants API는 대화형 AI를 더 쉽고 강력하게 만들 수 있도록 설계되었습니다. 주요 구성 요소는 다음과 같습니다.

  • Assistant: 특정 목적(예: 투자 분석)을 위해 설정된 AI. 지침, 모델, 사용할 도구 등을 가집니다.
  • Thread: 사용자와 Assistant 간의 대화 세션입니다. 하나의 대화와 관련된 모든 메시지를 담습니다.
  • Message: Thread에 포함된 각 대화 내용입니다. 사용자 또는 Assistant가 생성할 수 있습니다.
  • Run: Assistant가 Thread를 읽고 대화를 진행시키는 작업 단위입니다. Assistant는 메시지를 읽고, 도구를 사용할지, 아니면 텍스트로 답변할지 결정합니다.

단계 2: 도구(Tool) 정의하기

Assistant가 웹 검색이나 데이터 조회 같은 특정 작업을 수행하게 하려면, 먼저 어떤 도구를 사용할 수 있는지 알려줘야 합니다. 이는 두 부분으로 구성됩니다.

  1. 실제 함수 구현: 작업을 수행하는 Python 함수를 만듭니다. 여기서는 DuckDuckGo 검색을 통해 회사의 티커 심볼을 찾는 get_ticker 함수를 예시로 정의합니다.

    1
    2
    3
    4
    5
    6
    from langchain.utilities.duckduckgo_search import DuckDuckGoSearchAPIWrapper

    def get_ticker(inputs):
    ddg = DuckDuckGoSearchAPIWrapper()
    company_name = inputs["company_name"]
    return ddg.run(f"Ticker symbol of {company_name}")
  2. 함수 스키마 정의: AI 모델이 함수를 이해할 수 있도록, 함수의 이름, 설명, 필요한 인자 등을 정해진 JSON 형식으로 설명해 줍니다. 아직 구현되지 않은 함수들도 미리 스키마로 정의하여 Assistant에게 능력을 알려줄 수 있습니다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    functions = [
    {
    "type": "function",
    "function": {
    "name": "get_ticker",
    "description": "Given the name of a company returns its ticker symbol",
    "parameters": {
    "type": "object",
    "properties": {
    "company_name": {
    "type": "string",
    "description": "The name of the company",
    }
    },
    "required": ["company_name"],
    },
    },
    },
    # get_income_statement, get_balance_sheet 등 다른 함수 스키마들...
    ]

단계 3: Assistant 생성하기

이제 openai 클라이언트를 사용하여 위에서 정의한 내용을 바탕으로 Assistant를 생성합니다.

1
2
3
4
5
6
7
8
import openai as client

assistant = client.beta.assistants.create(
name="Investor Assistant",
instructions="You help users do research on publicly traded companies and you help users decide if they should buy the stock or not.",
model="gpt-4-1106-preview",
tools=functions,
)
  • name: Assistant의 이름입니다.
  • instructions: Assistant의 역할과 행동 지침을 정의하는 가장 중요한 부분입니다. 일종의 시스템 프롬프트입니다.
  • model: 사용할 GPT 모델을 지정합니다.
  • tools: 위에서 정의한 함수 스키마 리스트를 전달하여 Assistant가 사용할 수 있는 도구들을 알려줍니다.

단계 4: 생성 결과

이 코드를 실행하면 OpenAI 서버에 Investor Assistant라는 설정값을 가진 Assistant 객체가 영구적으로 생성됩니다. 이 Assistant는 고유한 ID를 가지게 되며, 우리는 앞으로 이 ID를 사용하여 Assistant를 호출하고 대화를 나눌 수 있습니다.

체크리스트

  • Assistant, Thread, Message, Run의 기본 개념을 이해했나요?
  • Assistant에게 ‘도구’를 제공하는 것이 어떤 의미인지 이해했나요?
  • 함수의 기능을 설명하는 JSON 스키마를 작성할 수 있나요?
  • client.beta.assistants.create를 사용하여 새로운 Assistant를 만들 수 있나요?

AI 비서의 도구 상자 채우기

이전 단계에서 AI 비서가 무엇을 할 수 있는지 ‘설명’했다면, 이제는 그 설명을 실제로 수행할 Python 코드를 구현할 차례입니다. yfinance 라이브러리를 사용하여 주식 시장 데이터를 가져오는 실제 도구 함수들을 만듭니다.

학습 목표

  • yfinance 라이브러리를 사용하여 주식 티커로 실제 재무 데이터를 가져오는 방법 배우기
  • 도구 스키마에 정의된 함수들을 실제 Python 코드로 구현하는 방법 익히기
  • Pandas DataFrame을 AI가 처리할 수 있는 JSON 문자열로 변환하는 이유와 방법 이해하기

변경된 파일: notebook.ipynb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# notebook.ipynb

import yfinance
import json

# ... (get_ticker 함수 생략)

def get_income_statement(inputs):
ticker = inputs["ticker"]
stock = yfinance.Ticker(ticker)
return json.dumps(stock.income_stmt.to_json())

def get_balance_sheet(inputs):
ticker = inputs["ticker"]
stock = yfinance.Ticker(ticker)
return json.dumps(stock.balance_sheet.to_json())

def get_daily_stock_performance(inputs):
ticker = inputs["ticker"]
stock = yfinance.Ticker(ticker)
return json.dumps(stock.history(period="3mo").to_json())

functions_map = {
"get_ticker": get_ticker,
"get_income_statement": get_income_statement,
"get_balance_sheet": get_balance_sheet,
"get_daily_stock_performance": get_daily_stock_performance,
}

# ... (이하 생략)

단계 1: yfinance 라이브러리 소개

yfinance는 Yahoo! Finance에서 제공하는 주식 시장 데이터를 파이썬으로 쉽게 다운로드할 수 있게 해주는 매우 유용한 라이브러리입니다.

단계 2: 재무 데이터 조회 함수 구현

이전 단계에서 스키마로만 정의했던 함수들의 실제 내용을 yfinance를 사용하여 구현합니다.

  • get_income_statement(inputs): yfinance.Ticker(ticker)로 특정 주식 객체를 가져온 뒤, .income_stmt 속성을 통해 손익계산서 정보를 가져옵니다.
  • get_balance_sheet(inputs): 같은 방식으로 .balance_sheet 속성을 통해 대차대조표 정보를 가져옵니다.
  • get_daily_stock_performance(inputs): .history(period="3mo") 메소드를 호출하여 최근 3개월간의 주가 데이터를 가져옵니다.

단계 3: 데이터를 문자열로 반환하기

Assistant API의 도구 함수가 반환하는 값은 반드시 **문자열(string)**이어야 합니다. yfinance가 반환하는 데이터는 주로 Pandas DataFrame이라는 복잡한 테이블 형태의 객체이므로, 이를 그대로 반환할 수 없습니다.

따라서 .to_json() 메소드로 DataFrame을 JSON 형태의 문자열로 변환하고, json.dumps()를 한 번 더 사용하여 전체를 완전한 문자열로 만들어 반환합니다. 이렇게 해야 AI Assistant가 그 결과를 읽고 해석할 수 있습니다.

단계 4: 함수 맵핑

functions_map 딕셔너리는 나중에 AI가 “get_income_statement 함수를 호출해줘” 라고 요청했을 때, 문자열 이름("get_income_statement")을 실제 파이썬 함수(get_income_statement)와 연결해주는 역할을 합니다.

체크리스트

  • yfinance 라이브러리로 주식 정보를 가져올 수 있나요?
  • 도구 함수의 반환값이 왜 반드시 문자열이어야 하는지 이해했나요?
  • Pandas DataFrame을 .to_json()을 사용해 문자열로 변환할 수 있나요?

AI 비서와 대화 시작하기: Thread와 Run

Assistant를 만들었으니, 이제 실제로 대화를 시작해 봅니다. Assistants API에서는 하나의 대화 단위를 ‘Thread’라고 부르며, Assistant가 이 Thread를 읽고 응답을 생성하는 행위를 ‘Run’이라고 합니다.

학습 목표

  • Assistants API의 핵심 객체인 ThreadRun의 개념과 역할을 이해하기
  • 사용자 메시지를 포함하여 새로운 대화(Thread)를 시작하는 방법 배우기
  • 특정 AssistantThread를 처리하도록 Run을 생성하고 실행하는 방법 익히기
  • Run의 비동기적 특성과 상태를 확인해야 하는 이유 이해하기

변경된 파일: notebook.ipynb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# notebook.ipynb

# ... (상단 생략)

assistant_id = "asst_RJHxXodfpEkpDGua4NL2wXAC" # 생성된 Assistant ID 사용

# 1. 사용자의 첫 메시지를 포함하여 새로운 Thread 생성
thread = client.beta.threads.create(
messages=[
{
"role": "user",
"content": "I want to know if the Salesforce stock is a good buy",
}
]
)

# 2. 생성된 Thread ID와 Assistant ID를 사용하여 Run 생성
run = client.beta.threads.runs.create(
thread_id=thread.id,
assistant_id=assistant_id,
)

# 3. Run의 상태를 확인하고, Thread의 메시지를 가져오는 헬퍼 함수 정의
def get_run(run_id, thread_id):
return client.beta.threads.runs.retrieve(
run_id=run_id,
thread_id=thread_id,
)

def get_messages(thread_id):
messages = client.beta.threads.messages.list(thread_id=thread_id)
messages = list(messages)
messages.reverse()
for message in messages:
print(f"{message.role}: {message.content[0].text.value}")

# 4. 대화 내용 확인
get_messages(thread.id)

단계 1: 대화의 시작, Thread

Thread는 사용자와 Assistant 간의 대화 그 자체입니다. client.beta.threads.create()를 호출하여 새로운 대화를 시작할 수 있으며, messages 인자를 통해 사용자의 첫 질문을 포함시킬 수 있습니다.

단계 2: Assistant 실행, Run

Run은 특정 Assistant에게 특정 Thread를 처리하라고 지시하는 작업입니다. client.beta.threads.runs.create()를 호출하며, 어떤 Assistant(assistant_id)가 어떤 대화(thread_id)를 읽고 응답해야 하는지 알려줍니다.

단계 3: 비동기적 실행과 상태 확인

Run을 생성하면 API는 즉시 Run 객체를 반환하지만, Assistant가 응답을 생성하는 작업은 백그라운드에서 비동기적으로 수행됩니다. 따라서 우리는 Run의 상태를 주기적으로 확인하여 작업이 완료되었는지, 아니면 도구 사용과 같은 추가 조치가 필요한지 알아내야 합니다. get_run 함수는 바로 이 Run의 현재 상태를 가져오는 역할을 합니다.

단계 4: 대화 내용 가져오기

Run이 완료되면, Assistant는 자신의 응답을 Message 객체로 만들어 해당 Thread에 추가합니다. get_messages 함수는 특정 Thread의 모든 메시지 기록을 시간 순으로 가져와 보여줍니다.

노트북의 실행 결과를 보면, Assistant가 사용자의 질문을 이해하고, 자신이 가진 도구들을 활용하여 어떻게 답변을 할 것인지 체계적인 계획을 세워 사용자에게 먼저 제안하는 것을 볼 수 있습니다. 이는 Assistant가 자신의 instructionstools 정보를 바탕으로 스스로 추론한 결과입니다.

체크리스트

  • Thread가 대화 세션을 의미한다는 것을 이해했나요?
  • Run이 Assistant를 특정 Thread에 대해 실행시키는 작업임을 이해했나요?
  • Run이 비동기적으로 처리된다는 것의 의미를 알고 있나요?
  • get_messages를 통해 대화의 전체 내용을 확인할 수 있나요?

AI 비서의 액션 실행하기: 도구 호출과 결과 제출

Assistant가 도구를 사용하겠다고 결정하면, Run의 상태는 requires_action으로 바뀝니다. 이는 우리 애플리케이션에게 “이제 네가 나설 차례야. 내가 요청한 함수를 실행하고 결과를 알려줘”라고 신호를 보내는 것과 같습니다. 이 단계에서는 그 신호를 받아 실제 도구를 실행하고 결과를 다시 Assistant에게 전달하는 과정을 구현합니다.

학습 목표

  • Run 객체의 requires_action 상태의 의미를 이해하기
  • AI가 요청한 도구 호출(Tool Call) 정보를 파싱하는 방법 배우기
  • 요청된 함수를 동적으로 실행하고, 그 결과를 다시 AI 비서에게 제출하는 전체 흐름 이해하기

변경된 파일: notebook.ipynb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# notebook.ipynb

# ... (상단 생략)

def get_tool_outputs(run_id, thread_id):
run = get_run(run_id, thread_id)
outputs = []
# 1. AI가 요청한 모든 도구 호출 목록을 순회
for action in run.required_action.submit_tool_outputs.tool_calls:
action_id = action.id
function = action.function
print(f"Calling function: {function.name} with arg {function.arguments}")

# 2. functions_map을 사용해 실제 Python 함수를 찾아 실행
outputs.append(
{
"output": functions_map[function.name](json.loads(function.arguments)),
"tool_call_id": action_id,
}
)
return outputs

def submit_tool_outputs(run_id, thread_id):
outpus = get_tool_outputs(run_id, thread_id)
# 3. 실행 결과를 다시 Assistant에게 제출
return client.beta.threads.runs.submit_tool_outputs(
run_id=run_id, thread_id=thread_id, tool_outputs=outpus
)

# --- 실행 흐름 ---
# 이전 Run이 requires_action 상태가 된 후...
submit_tool_outputs(run.id, thread.id)

단계 1: requires_action 상태

사용자가 “계속 진행해줘”라고 답하고 새로운 Run을 생성하면, Assistant는 계획에 따라 도구를 사용해야 한다고 판단합니다. 이때 Run의 상태는 completed가 아닌 requires_action이 됩니다. 이는 Assistant가 우리 쪽의 응답을 기다리며 일시정지했음을 의미합니다.

단계 2: 도구 호출(Tool Call) 정보 파싱

run.required_action.submit_tool_outputs.tool_calls 객체에는 Assistant가 호출하려는 함수들의 목록이 들어있습니다. get_tool_outputs 함수는 이 목록을 순회하며 각 함수 호출(action)에 필요한 정보를 추출합니다.

  • action.id: 각 도구 호출을 식별하는 고유 ID. 나중에 결과를 제출할 때 이 ID를 함께 보내야 합니다.
  • action.function.name: 호출할 함수의 이름 (예: "get_ticker")
  • action.function.arguments: 함수에 전달할 인자를 담은 JSON 문자열 (예: '{"company_name": "Salesforce"}')

단계 3: 도구 실행 및 결과 제출

get_tool_outputs 함수는 functions_map 딕셔너리를 사용하여 함수 이름에 해당하는 실제 Python 함수를 찾습니다. json.loads()를 사용해 인자 문자열을 Python 딕셔너리로 변환한 뒤, 함수를 실행합니다.

submit_tool_outputs 함수는 이렇게 실행된 모든 도구의 결과(outputs)를 모아, client.beta.threads.runs.submit_tool_outputs API를 호출하여 Assistant에게 다시 전달합니다. 이로써 상호작용의 한 사이클이 마무리됩니다.

결과를 제출받은 Assistant는 Run을 재개하고, 이제 도구로부터 얻은 실제 데이터를 바탕으로 사용자에게 최종 답변을 생성하여 Thread에 추가합니다.

단계 4: 전체 대화 흐름

이 커밋의 노트북 실행 결과는 이 모든 과정을 보여줍니다.

사용자 질문Assistant의 분석 계획 제시사용자의 진행 승인Assistant의 도구 사용 요청 (requires_action)우리 코드의 도구 실행 및 결과 제출Assistant의 최종 답변 생성 (completed)

체크리스트

  • Runrequires_action 상태가 무엇을 의미하는지 설명할 수 있나요?
  • tool_calls 객체에서 함수 이름과 인자 문자열을 어떻게 추출하는지 알고 있나요?
  • json.loads()를 사용하여 JSON 문자열을 Python 딕셔너리로 변환할 수 있나요?
  • submit_tool_outputs를 사용하여 도구 실행 결과를 Assistant에게 어떻게 전달하는지 이해했나요?

대화 이어가기 및 리팩토링

이번 단계에서는 하나의 대화(Thread) 안에서 여러 번 질문과 답변을 주고받으며 대화의 맥락을 이어가는 방법을 배우고, 코드의 가독성과 안정성을 높이기 위한 작은 리팩토링을 진행합니다.

학습 목표

  • 하나의 Thread 내에서 여러 차례 질문과 답변을 주고받으며 대화를 이어가는 방법 배우기
  • Run -> requires_action -> submit_tool_outputs -> Run으로 이어지는 상호작용 루프를 반복하는 방법 이해하기
  • 코드의 작은 버그를 수정하고 워크플로우를 명확하게 만드는 리팩토링의 중요성 파악하기

변경된 파일: notebook.ipynb

단계 1: 버그 수정 및 코드 개선

이전 코드의 get_tool_outputs 함수 안에는 작은 버그가 있었습니다.

  • 기존: run = get_run(run_id, thread.id)
  • 수정: run = get_run(run_id, thread_id)

함수 외부의 전역 변수 thread에 의존하던 것을, 함수에 인자로 전달된 thread_id를 사용하도록 수정했습니다. 이렇게 해야 이 함수를 다른 스레드에서도 재사용할 수 있는 독립적이고 안정적인 코드가 됩니다.

단계 2: 대화 이어가기

Assistant와의 대화를 이어가려면 어떻게 해야 할까요? 새로운 Thread를 만드는 대신, 기존 Thread에 새로운 사용자 메시지를 추가하면 됩니다.

1
2
3
4
5
6
7
8
# 새로운 사용자 메시지를 기존 스레드에 추가
send_message(thread.id, "Now I want to know if Cloudflare is a good buy.")

# 동일한 스레드와 어시스턴트에 대해 새로운 Run을 생성
run = client.beta.threads.runs.create(
thread_id=thread.id,
assistant_id=assistant_id,
)

새로운 메시지를 추가한 뒤, 다시 client.beta.threads.runs.create를 호출하면 Assistant는 이전 대화 내용을 모두 기억한 상태에서 새로운 질문에 대한 응답을 시작합니다.

단계 3: 전체 대화 분석

노트북의 최종 실행 결과를 보면, “Salesforce”에 대한 분석이 끝난 뒤 사용자가 “Cloudflare는 어때?”라고 묻자, Assistant가 이전과 동일한 분석 프로세스(티커 검색, 재무제표 조회 등)를 Cloudflare에 대해 다시 수행하고 상세한 분석 결과를 제공하는 것을 볼 수 있습니다. 이는 Assistant가 Thread 내의 전체 대화 맥락을 이해하고 있음을 보여줍니다.

체크리스트

  • 함수를 작성할 때 전역 변수 대신 인자를 사용해야 하는 이유를 이해했나요?
  • 기존 대화에 새로운 메시지를 추가하는 send_message 함수의 역할을 이해했나요?
  • 하나의 Thread에서 대화를 이어가기 위해 새로운 Run을 생성해야 함을 알고 있나요?

RAG 비서 만들기: 파일 기반 지식으로 답변하기

지금까지 Assistant는 외부 함수(도구)를 호출하여 정보를 얻었습니다. 이번에는 완전히 다른 접근법인 **검색 증강 생성(Retrieval-Augmented Generation, RAG)**을 사용합니다. Assistant에게 파일을 업로드하여 새로운 지식을 학습시키고, 그 파일의 내용을 기반으로 질문에 답변하도록 만들어 봅니다.

학습 목표

  • 검색 증강 생성(RAG)의 개념과 AI 비서에 파일을 업로드하여 지식을 확장하는 방법 이해하기
  • Assistants API의 내장 retrieval 도구를 활성화하는 방법 배우기
  • client.files.create를 사용하여 파일을 업로드하고, 메시지에 file_ids를 포함하여 스레드에 연결하는 방법 익히기
  • AI가 파일 내용을 참조하여 답변하고, 그 출처(citation)를 표시하는 과정 확인하기

변경된 파일: notebook.ipynb

Investor Assistant 관련 코드는 모두 삭제되고, 파일 기반 질의응답을 위한 새로운 코드로 대체됩니다.

단계 1: RAG(Retrieval-Augmented Generation) 소개

RAG는 AI 모델이 답변을 생성할 때, 외부의 신뢰할 수 있는 지식 소스(예: 우리가 업로드한 문서)를 먼저 ‘검색(Retrieval)’하고, 검색된 관련 정보를 ‘근거’로 삼아 답변을 ‘생성(Generation)’하는 기술입니다. 이를 통해 모델의 지식에만 의존할 때 발생할 수 있는 환각(Hallucination) 현상을 줄이고, 특정 도메인에 대한 정확하고 상세한 답변을 제공할 수 있습니다.

단계 2: retrieval 도구 활성화

Assistant에 RAG 기능을 활성화하는 것은 매우 간단합니다. Assistant를 생성하거나 수정할 때, tools 목록에 {"type": "retrieval"}을 추가하기만 하면 됩니다.

1
2
3
4
5
6
7
# Assistant 생성 시 retrieval 도구 활성화
assistant = client.beta.assistants.create(
name="Book Assistant",
instructions="You help users with their question on the files they upload.",
model="gpt-4-1106-preview",
tools=[{"type": "retrieval"}],
)

단계 3: 파일 업로드 및 스레드에 연결

Assistant가 파일을 사용하게 하려면 두 단계가 필요합니다.

  1. 파일 업로드: client.files.create를 사용하여 로컬 파일을 OpenAI 서버에 업로드합니다. purpose"assistants"로 지정해야 합니다.

    1
    2
    3
    4
    file = client.files.create(
    file=client.file_from_path("./files/chapter_one.txt"),
    purpose="assistants"
    )
  2. 스레드에 파일 연결: 사용자 메시지를 생성할 때 file_ids 인자에 업로드된 파일의 ID를 리스트 형태로 전달합니다. 이렇게 하면 해당 Thread 내에서 Assistant가 이 파일을 참조할 수 있게 됩니다.

    1
    2
    3
    4
    5
    6
    client.beta.threads.messages.create(
    thread_id=thread.id,
    role="user",
    content="", # 파일 첨부 시 content는 비워둘 수 있음
    file_ids=[file.id]
    )

단계 4: 답변 출처 확인 (Citations)

RAG의 가장 강력한 기능 중 하나는 답변의 근거가 된 원본 소스를 명확히 밝혀준다는 것입니다. Assistant가 retrieval 도구를 사용하여 답변을 생성하면, 응답 메시지의 annotations 필드에 인용(Citation) 정보가 포함됩니다.

get_messages 함수를 약간 수정하여 이 인용 정보를 함께 출력하도록 만들 수 있습니다.

1
2
3
4
5
6
7
def get_messages(thread_id):
# ... (기존 코드)
for message in messages:
print(f"{message.role}: {message.content[0].text.value}")
# Annotation(인용) 정보 출력 추가
for annotation in message.content[0].text.annotations:
print(f"Source: {annotation.file_citation}")

노트북의 실행 결과를 보면, “윈스턴은 어디에 사나?”라는 질문에 Assistant가 업로드된 chapter_one.txt 파일의 내용을 정확히 인용(【12†source】)하며 답변하는 것을 볼 수 있습니다. 또한 get_messages를 통해 출력된 Source 정보를 보면, 어떤 파일(file_id)의 어떤 구절(quote)을 근거로 답변했는지 투명하게 확인할 수 있습니다.

체크리스트

  • RAG가 무엇이며 왜 유용한지 설명할 수 있나요?
  • Assistant에 retrieval 도구를 어떻게 활성화하는지 알고 있나요?
  • 파일을 업로드하고 특정 대화(Thread)에 연결하는 과정을 이해했나요?
  • Assistant의 답변에서 출처(Citation)를 확인하는 것이 왜 중요한지 이해했나요?

출처 : https://nomadcoders.co/fullstack-gpt