Challenge/Inflearn

[Do it LLM] 챌린지 2주차 - 스트림 출력

graph-dev 2025. 10. 4. 19:19
728x90

 

GPT API로 스트림 방식으로 출력하기 위해, stream 파라미터를 사용합니다.

 

stream=False : 결과를 한 번에 반환

stream=True : yield로 반환합니다. 중간마다 값을 반환합니다.

def get_ai_response(messages, tools=None, stream=True):
    response = client.chat.completions.create(
        model="gpt-4o",  # 응답 생성에 사용할 모델을 지정합니다.
        stream=stream, # (1) 스트리밍 출력을 위해 설정
        messages=messages,  # 대화 기록을 입력으로 전달합니다.
        tools=tools,  # 사용 가능한 도구 목록을 전달합니다.
    )

    if stream: 
        for chunk in response:
            yield chunk  # 생성된 응답의 내용을 yield로 순차적으로 반환합니다.
    else:
        return response  # 생성된 응답의 내용을 반환합니다.

 

 

stock_info.py 파일을 실행하겠습니다.

ai_response 함수로 한번에 답변하지 않고, 응답을 쪼개어 답변하게 되므로, for 문을 사용하게 됩니다.

 

    with st.chat_message("assistant").empty(): # 스트림릿 챗 메시지 초기화
        for chunk in ai_response:
            content_chunk = chunk.choices[0].delta.content # 청크 속 content 추출
            if content_chunk: # 만약 content_chunk가 있다면, 
                print(content_chunk, end="")	 # 터미널에 줄바꿈 없이 이어서 출력
                content += content_chunk # content에 덧붙이기
                st.markdown(content) # 스트림릿 챗 메시지에 마크다운으로 출력
            
            # print(chunk) # 임시로 청크 출력
            if chunk.choices[0].delta.tool_calls:	# tool_calls가 있는 경우
                tool_calls_chunk += chunk.choices[0].delta.tool_calls # tool_calls_chunk에 추가

    tool_obj = tool_list_to_tool_obj(tool_calls_chunk)
    tool_calls = tool_obj["tool_calls"]

 

이 코드를 실행하면 타이핑하듯이 출력됩니다. 물론 위에서 stream 파라미터가 True인 경우에 한합니다.

 

이제, 스트림 방식에서 Function Calling을 사용합니다. 이 결과도 조각 단위로 반환됩니다.

 

"불러도 대답없는 챗봇 고치기"

빈칸만 존재하네요

 

 

이제 함수를 좀 더 수정합니다. 메시지 안에 함수 정보를 넣어주고, 답변하게 합니다. 여기서 핵심은, tool_calls 안에 들어간 function 내용을 담아서 출력하는 것입니다.

 

    with st.chat_message("assistant").empty(): # 스트림릿 챗 메시지 초기화
        for chunk in ai_response:
            content_chunk = chunk.choices[0].delta.content # 청크 속 content 추출
            if content_chunk: # 만약 content_chunk가 있다면, 
                print(content_chunk, end="")	 # 터미널에 줄바꿈 없이 이어서 출력
                content += content_chunk # content에 덧붙이기
                st.markdown(content) # 스트림릿 챗 메시지에 마크다운으로 출력
            
            # print(chunk) # 임시로 청크 출력
            if chunk.choices[0].delta.tool_calls:	# tool_calls가 있는 경우
                tool_calls_chunk += chunk.choices[0].delta.tool_calls # tool_calls_chunk에 추가

    tool_obj = tool_list_to_tool_obj(tool_calls_chunk)
    tool_calls = tool_obj["tool_calls"]   

    if len(tool_calls) > 0: # 만약 tool_calls가 존재하면, st.write로 tool_call 내용 출력
        print(tool_calls)
        # tool_calls에서 function 정보만 모아서 출력
        tool_call_msg = [tool_call["function"] for tool_call in tool_calls]
        st.write(tool_call_msg) 

    print('\n===========')
    print(content)

 

 

이렇게 출력하면 아래와 같은데,

[{'id': '**', 'function': {'arguments': '{"ticker": "TSLA", "period": "1d"}', 'name': 'get_yf_stock_history'}, 'type': 'function'}, {'id': 'call_0My0oR1YFDqASvDfhwREoAE9', 'function': {'arguments': '{"timezone": "America/Los_Angeles"}', 'name': 'get_current_time'}, 'type': 'function'}]

===========

[{'id': 'call_bZc69QZwdTk33jB4CIs8Zeh8', 'function': {'arguments': '{"ticker": "TSLA", "period": "1d"}', 'name': 'get_yf_stock_history'}, 'type': 'function'}, {'id': '**', 'function': {'arguments': '{"timezone": "America/Los_Angeles"}', 'name': 'get_current_time'}, 'type': 'function'}]

 

여기서 function으로 된 키 값을 추출하면 다음과 같다.

 

'function': {'arguments': '{"ticker": "TSLA", "period": "1d"}',
'name': 'get_yf_stock_history'}

...

'function': {'arguments': '{"timezone": "America/Los_Angeles"}',
'name': 'get_current_time'}

 

자 tabulate 모듈을 설치합니다.

 

이제 tabulate 설치가 완료되었고, 다시 실행합니다.

 

아래와 같이, 펑션 콜링을 수행할 때, 함수가 잘 나오고 그 뒤에 답변이 나타나는 것을 알 수 있습니다. 어느 함수를 사용했는지 이 빈 칸에 넣게 됩니다.

 

 

결과

 

이렇게 잘 함수가 출력되고, 그 펑션 콜링을 바탕으로 아래 답변이 나타나는 것을 알 수 있습니다.