Ollama와 LiteLLM으로 파이썬에서 인터랙티브 콘솔 LLM 챗봇 구축하기
로컬 대규모 언어 모델(LLM)에 대한 관심이 커지고 이를 맞춤형 애플리케이션에 통합하는 추세 속에서, 단순히 모델을 실행하는 것을 넘어 완전한 기능을 갖춘 인터랙티브 인터페이스를 만드는 것이 중요합니다. 이 글은 저희 연재의 다음 편으로, 단일 LLM 쿼리를 위한 기본 스크립트를 파이썬에서 기능적인 콘솔 챗봇으로 전환하는 데 중점을 둡니다. Ollama와 LiteLLM의 강력한 조합을 사용하여 코드 구조화, 시스템 프롬프트 구현, 상호작용성 추가, 오류 처리 등 핵심 단계를 다룰 것입니다. 목표는 단순한 명령어 실행에서 실시간 사용자 상호작용이 가능한 견고하고 관리하기 쉬운 애플리케이션을 구축하는 방법과 대화 컨텍스트 관리 등 추가 기능 확장을 위한 기반을 마련하는 방법을 보여주는 것입니다.
단일 실행 스크립트에서 인터랙티브 애플리케이션으로
이전 가이드에서는 Ollama를 통해 호스팅되는 로컬 LLM을 LiteLLM 라이브러리를 사용하여 파이썬 코드와 기본적으로 통합하는 방법을 시연했습니다. 그 결과는 미리 정의된 단일 쿼리를 모델에 보내고 응답을 표시한 후 프로그램이 종료되는 스크립트였습니다. 기술에 대한 초기 소개로는 충분했지만, 이 접근 방식은 실제 애플리케이션에는 비실용적입니다. 진정한 챗봇은 지속적인 상호작용을 제공해야 합니다. 사용자 입력을 받고, 이를 처리하고, 모델로부터 응답을 받은 다음, 사용자가 세션을 종료하기로 결정할 때까지 다음 질문을 기다려야 합니다. 이러한 근본적인 변화가 바로 우리가 구현할 내용이며, 정적인 스크립트를 동적인 콘솔 챗봇으로 바꿀 것입니다.
이번 파트에서 소개할 주요 개선 사항은 다음과 같습니다.
- LLM 쿼리 로직을 별도의 재사용 가능한 함수로 추출합니다.
- 어시스턴트의 역할과 행동을 정의하기 위해 시스템 프롬프트를 활용합니다.
- 사용자가 여러 질문을 연속적으로 할 수 있도록 대화 루프를 구성합니다.
- 성능 평가를 위해 응답 시간 측정을 구현합니다.
- 애플리케이션 견고성을 향상시키기 위한 기본적인 예외 처리를 추가합니다.
LLM API가 작동하는 메시지 형식을 이해하는 것은 효과적인 상호작용을 위해 필수적입니다. 모델은 단일 텍스트 라인으로 작동하지 않고, 각각 특정 역할을 가진 메시지 목록을 처리합니다.
system: 이는 모델에 대한 지시로, 모델의 성격, 응답 스타일, 제약 사항 또는 중요한 컨텍스트 데이터를 정의합니다. 사용자는 이 메시지를 직접 보지 못하며, 코드 수준에서 생성됩니다.user: 사용자로부터 오는 메시지 또는 질문입니다.assistant: 모델 자체에서 생성된 응답입니다. 향후 반복에서는 컨텍스트 유지를 위해 이전 모델 응답이 여기에 추가될 것입니다.
파이썬 콘솔 LLM 챗봇 아키텍처
이제 구현으로 넘어가겠습니다. main.py 파일을 수정하여 LLM 상호작용 로직을 캡슐화하고 사용자 입력 루프를 구성할 것입니다. 아래는 main.py 파일의 내용을 대체할 수 있는 업데이트된 코드입니다.
# -*- coding: utf-8 -*-
import time
from typing import Optional
from litellm import completion
MODEL = "ollama_chat/qwen2.5:3b"
API_BASE = "http://localhost:11434"
SYSTEM_PROMPT = "You are a helpful assistant. Answer concisely and to the point."
def send_request_to_llm(user_message: str) -> Optional[str]:
messages = [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": user_message},
]
try:
start_time = time.time()
response = completion(
model=MODEL,
messages=messages,
api_base=API_BASE,
request_timeout=120,
)
duration = time.time() - start_time
print(f"\n⏱ Generation time: {duration:.2f} sec")
return response.choices[0].message.content
except Exception as e:
print(f"\n❌ Error during request: {e}")
return None
def main() -> None:
print("🤖 Local AI assistant started.")
print("Enter your question or 'exit' to quit.\n")
while True:
user_input = input("👤 You: ").strip()
if user_input.lower() in ("종료", "exit", "quit"):
print("Goodbye!")
break
if not user_input:
print("⚠️ Please enter a question.")
continue
print("\n🤔 Model is thinking...")
answer = send_request_to_llm(user_input)
if answer is not None:
print(f"\n🦙 AI: {answer}\n")
else:
print("\n⚠️ Failed to get a response. Check if Ollama is running.\n")
if __name__ == "__main__":
main()
핵심 코드 구성 요소 분석
이 스크립트의 각 블록이 어떻게 작동하는지 자세히 살펴보겠습니다.
1. 설정 상수:
파일 시작 부분에는 MODEL, API_BASE, SYSTEM_PROMPT와 같은 상수가 정의되어 있습니다. 이 접근 방식은 주 코드의 핵심 로직을 깊이 파고들지 않고도 다른 LLM 모델을 사용하거나 어시스턴트의 행동을 조정하는 등 구성 매개변수를 쉽게 수정할 수 있게 해줍니다. 여기서 SYSTEM_PROMPT는 모델의 전반적인 응답 톤과 스타일에 영향을 미치는 초기 지침을 설정함으로써 중요한 역할을 합니다.
2. send_request_to_llm 함수:
이 함수(def send_request_to_llm(user_message: str) -> Optional[str]:)는 LLM과 상호작용하는 핵심 요소입니다. 사용자 메시지를 받아 완전한 메시지 목록(SYSTEM_PROMPT 및 user_message 포함)을 구성하고, litellm.completion을 통해 로컬 Ollama 모델로 전송합니다.
- 캡슐화: 요청을 보내고 응답을 받는 로직이 완전히 캡슐화되어 있어, 주
main루프를 더 깔끔하고 이해하기 쉽게 만듭니다. - 시간 측정: 내장된 시간 측정(
time.time())은 모델 성능을 평가할 수 있게 해주며, 응답 생성에 걸린 시간을 초 단위로 보여줍니다. 이는 다양한 모델이나 매개변수를 실험할 때 특히 유용합니다. - 오류 처리:
try...except블록은 기본적인 오류 허용 기능을 제공합니다. LLM 요청 중 오류가 발생하면(예: Ollama를 사용할 수 없는 경우), 함수는 치명적인 오류를 발생시키는 대신None을 반환하여main함수가 상황을 우아하게 처리하고 채팅을 계속할 수 있도록 합니다.
3. main 함수와 인터랙티브 루프:
main 함수는 사용자 인터페이스와 전반적인 채팅 흐름을 관리합니다.
- 무한 루프 (
while True): 사용자가 명시적으로 종료를 결정할 때까지 지속되는 연속적인 채팅 세션을 생성합니다. - 사용자 입력:
input("👤 You: ").strip()는 사용자 입력을 기다립니다..strip()메서드는 앞뒤 공백을 제거하여 비어 있거나 잘못된 형식의 쿼리가 전송되는 것을 방지합니다. - 종료 명령:
user_input.lower() in ("종료", "exit", "quit")확인은 사용자가 다양한 종료 명령 옵션을 사용하여 프로그램을 종료할 수 있도록 합니다. - 빈 입력:
if not user_input:확인은 빈 메시지가 모델로 전송되는 것을 방지하고, 사용자에게 질문을 입력하도록 안내합니다. - 상호작용:
main은 사용자 입력으로send_request_to_llm을 호출하고, 수신된 응답을 처리하여 콘솔에 출력하거나 오류를 보고합니다.main(인터페이스 관리)과send_request_to_llm(모델 상호작용) 간의 책임 분리는 좋은 소프트웨어 설계의 기본 원칙입니다.
시스템 프롬프트를 통한 LLM 행동 제어
LLM과 작업할 때 가장 강력한 도구 중 하나는 시스템 프롬프트입니다. 이는 모델 자체나 매개변수를 변경하지 않고도 모델에 특정 역할, 통신 스타일 또는 제약 조건을 할당하여 응답을 크게 변경할 수 있게 해줍니다. SYSTEM_PROMPT를 실험하는 것은 특정 작업에 맞게 어시스턴트의 행동을 얼마나 미묘하게 조정할 수 있는지 이해하는 훌륭한 방법입니다. 단 한 줄의 변경이 대화에 얼마나 큰 영향을 미칠 수 있는지 몇 가지 예를 살펴보겠습니다.
- `
SYSTEM_PROMPT = "You are a sarcastic assistant. Respond with irony."` — 모델은 유머와 재치로 응답할 것입니다. - `
SYSTEM_PROMPT = "You are a strict teacher. Correct errors in questions."` — 어시스턴트는 답변뿐만 아니라 쿼리의 문법적 또는 사실적 부정확성도 지적할 것입니다. - `
SYSTEM_PROMPT = "Respond only in English. Always provide a code example."` — 모델은 영어만 엄격히 준수하고, 가능하다면 응답에 코드 예시를 포함하려고 할 것입니다.
이러한 예시들은 시스템 프롬프트가 정적인 모델을 다양한 사용 사례에 적응할 수 있는 유연한 도구로 어떻게 변화시키는지 보여줍니다. 이는 기술 지원, 창의적인 파트너 또는 교육 도구 등 특화된 챗봇을 만드는 데 매우 중요합니다.
주요 내용 요약
이 개발 단계에서 우리의 콘솔 LLM 챗봇은 다음과 같은 중요한 결과를 달성했습니다.
- 상호작용성: 사용자는 질문을 하고 실시간으로 답변을 받으며 지속적인 대화에 참여할 수 있습니다.
- 제어된 행동: 시스템 프롬프트를 통해 모델의 역할과 통신 스타일을 쉽게 구성할 수 있습니다.
- 코드 모듈성: LLM 상호작용 로직이 사용자 인터페이스 로직과 분리되어 있어, 향후 개발 및 디버깅을 간소화합니다.
- 성능: 내장된 응답 생성 시간 측정은 모델의 속도를 모니터링하는 데 도움이 됩니다.
- 신뢰성: 기본적인 오류 처리는 연결 또는 모델 문제로 인한 프로그램 충돌을 방지합니다.
일반적인 문제와 해결책
로컬 LLM 및 콘솔 애플리케이션을 사용할 때 몇 가지 일반적인 문제가 발생할 수 있습니다.
- 질문 입력 후 프로그램 멈춤: 이는 대부분 Ollama가 실행되고 있지 않거나 모델이 아직 메모리에 완전히 로드되지 않았기 때문에 발생합니다. Ollama가 활성 상태로 실행 중인지 확인하고(터미널에서
ollama list) 모델이 초기화될 시간을 주세요. - 모델의 응답이 너무 간결함:
SYSTEM_PROMPT가 "간결하게 답변하라"고 지정하면 모델은 이 지시를 따를 것입니다. 더 자세한 응답을 원한다면, 이 제약 조건을 제거하거나 "예시와 함께 자세히 답변하라"고 명시적으로 요청하여 시스템 프롬프트를 수정하세요. - KeyboardInterrupt를 눌렀을 때
KeyboardInterrupt발생:** 이는 표준 파이썬 동작입니다. 학습 프로젝트에서는 중요하지 않지만, 프로덕션 환경에서는 더 우아한 종료를 위해main()함수를try/except KeyboardInterrupt블록으로 감쌀 수 있습니다. - 인코딩 문제 (Windows에서 글자가 깨짐): 이는 터미널 인코딩을 UTF-8로 설정하여 해결할 수 있습니다. 예를 들어, PowerShell에서
$OutputEncoding = [System.Text.Encoding]::UTF8명령어를 사용합니다.
다음 단계: 완전한 대화로 나아가기
우리 콘솔 챗봇이 훨씬 더 기능적으로 발전했지만, 현재 "기억"이 부족합니다. 각 요청은 독립적으로 처리되며, 모델은 이전 대화 내용을 기억하지 못합니다. 이는 "같은 것을 더 짧게 해줄 수 있나요?"라고 물었을 때, 모델이 "같은 것"이 무엇을 의미하는지 이해하지 못한다는 뜻입니다. 다음 파트에서는 메시지 기록을 구현하여 새로운 요청마다 전체 대화 컨텍스트를 전달하는 방법을 탐구할 것이며, 이는 챗봇을 진정으로 지능적인 대화 파트너로 변화시킬 것입니다.
— Editorial Team
아직 댓글이 없습니다.