출장 자동화 시스템
Agent-s engine.py
myeongjaechoi
2025. 3. 30. 15:16
기본 모듈 임포트
import os # 운영 체제 인터페이스를 제공하는 모듈 임포트
import backoff # API 호출 실패 시 재시도를 관리하는 라이브러리 임포트
import numpy as np # 수치 계산을 위한 NumPy 라이브러리 임포트
from anthropic import Anthropic # Anthropic AI API 클라이언트 임포트
from openai import APIConnectionError, APIError, AzureOpenAI, OpenAI, RateLimitError # OpenAI 관련 클래스와 에러 타입 임포트
기본 LMM 엔진 클래스
class LMMEngine:
# LLM 엔진의 기본 클래스 - 모든 LLM 엔진 클래스의 부모 클래스
pass
OpenAI API 클래스 -> GPT 모델을 사용하여 텍스트 생성 ->이전 대화 내용을 바탕으로 다음 대화를 생성
- 예 : "안녕하세요?" -> 적절한 답변 생성
class LMMEngineOpenAI(LMMEngine):
# OpenAI API를 사용하는 LLM 엔진 클래스
def __init__(self, api_key=None, model=None, rate_limit=-1, **kwargs):
"""
LMMEngineOpenAI 클래스의 초기화 메서드
매개변수:
api_key (str, 선택적): OpenAI API 키. 없으면 환경 변수에서 가져옴
model (str, 필수): 사용할 OpenAI 모델명
rate_limit (int, 선택적): 분당 API 요청 제한 수. -1이면 제한 없음
**kwargs: 추가 매개변수
"""
assert model is not None, "model must be provided" # 모델명이 제공되지 않으면 오류 발생
self.model = model # 모델명 저장
api_key = api_key or os.getenv("OPENAI_API_KEY") # API 키가 제공되지 않으면 환경 변수에서 가져옴
if api_key is None: # API 키가 여전히 없으면 오류 발생
raise ValueError(
"An API Key needs to be provided in either the api_key parameter or as an environment variable named OPENAI_API_KEY"
)
self.api_key = api_key # API 키 저장
self.request_interval = 0 if rate_limit == -1 else 60.0 / rate_limit # API 요청 간격 계산 (초 단위)
self.llm_client = OpenAI(api_key=self.api_key) # OpenAI 클라이언트 초기화
@backoff.on_exception(
backoff.expo, (APIConnectionError, APIError, RateLimitError), max_time=60
) # API 오류 발생 시 지수 백오프로 60초까지 재시도
def generate(self, messages, temperature=0.0, max_new_tokens=None, **kwargs):
"""
이전 메시지를 기반으로 다음 메시지 생성
매개변수:
messages (list): 대화 메시지 목록
temperature (float, 선택적): 생성 결과의 무작위성 정도. 기본값 0.0 (결정적)
max_new_tokens (int, 선택적): 생성할 최대 토큰 수. 기본값 4096
**kwargs: 추가 매개변수
반환값:
str: 생성된 응답 텍스트
"""
return (
self.llm_client.chat.completions.create(
model=self.model, # 사용할 모델 지정
messages=messages, # 대화 메시지 목록 전달
max_tokens=max_new_tokens if max_new_tokens else 4096, # 최대 토큰 수 설정
temperature=temperature, # 온도(무작위성) 설정
**kwargs, # 추가 매개변수 전달
)
.choices[0] # 첫 번째 생성 결과 선택
.message.content # 생성된 메시지 내용 반환
)
Anthropic API 클래스 -> Claude 모델을 사용하여 텍스트 생성 -> 답변을 생성
class LMMEngineAnthropic(LMMEngine):
# Anthropic API를 사용하는 LLM 엔진 클래스
def __init__(self, api_key=None, model=None, **kwargs):
"""
LMMEngineAnthropic 클래스의 초기화 메서드
매개변수:
api_key (str, 선택적): Anthropic API 키. 없으면 환경 변수에서 가져옴
model (str, 필수): 사용할 Anthropic 모델명
**kwargs: 추가 매개변수
"""
assert model is not None, "model must be provided" # 모델명이 제공되지 않으면 오류 발생
self.model = model # 모델명 저장
api_key = api_key or os.getenv("ANTHROPIC_API_KEY") # API 키가 제공되지 않으면 환경 변수에서 가져옴
if api_key is None: # API 키가 여전히 없으면 오류 발생
raise ValueError(
"An API Key needs to be provided in either the api_key parameter or as an environment variable named ANTHROPIC_API_KEY"
)
self.api_key = api_key # API 키 저장
self.llm_client = Anthropic(api_key=self.api_key) # Anthropic 클라이언트 초기화
@backoff.on_exception(
backoff.expo, (APIConnectionError, APIError, RateLimitError), max_time=60
) # API 오류 발생 시 지수 백오프로 60초까지 재시도
def generate(self, messages, temperature=0.0, max_new_tokens=None, **kwargs):
"""
이전 메시지를 기반으로 다음 메시지 생성
매개변수:
messages (list): 대화 메시지 목록 (첫 메시지는 시스템 프롬프트로 사용)
temperature (float, 선택적): 생성 결과의 무작위성 정도. 기본값 0.0 (결정적)
max_new_tokens (int, 선택적): 생성할 최대 토큰 수. 기본값 4096
**kwargs: 추가 매개변수
반환값:
str: 생성된 응답 텍스트
"""
return (
self.llm_client.messages.create(
system=messages[0]["content"][0]["text"], # 첫 번째 메시지를 시스템 프롬프트로 설정
model=self.model, # 사용할 모델 지정
messages=messages[1:], # 시스템 프롬프트 이후의 메시지들 전달
max_tokens=max_new_tokens if max_new_tokens else 4096, # 최대 토큰 수 설정
temperature=temperature, # 온도(무작위성) 설정
**kwargs, # 추가 매개변수 전달
)
.content[0] # 첫 번째 콘텐츠 항목 선택
.text # 생성된 텍스트 반환
)
@backoff.on_exception(
backoff.expo, (APIConnectionError, APIError, RateLimitError), max_time=60
) # API 오류 발생 시 지수 백오프로 60초까지 재시도
# Claude-3.7 Sonnet 사고 모드와 호환되는 메서드
def generate_with_thinking(
self, messages, temperature=0.0, max_new_tokens=None, **kwargs
):
"""
사고 과정이 포함된 응답 생성 (Claude-3.7 Sonnet 사고 모드 지원)
매개변수:
messages (list): 대화 메시지 목록 (첫 메시지는 시스템 프롬프트로 사용)
temperature (float, 선택적): 생성 결과의 무작위성 정도. 기본값 0.0 (결정적)
max_new_tokens (int, 선택적): 생성할 최대 토큰 수
**kwargs: 추가 매개변수
반환값:
str: 생성된 최종 응답 텍스트
"""
full_response = self.llm_client.messages.create(
system=messages[0]["content"][0]["text"], # 첫 번째 메시지를 시스템 프롬프트로 설정
model=self.model, # 사용할 모델 지정
messages=messages[1:], # 시스템 프롬프트 이후의 메시지들 전달
max_tokens=8192, # 최대 토큰 수 설정 (8192 고정)
thinking={"type": "enabled", "budget_tokens": 4096}, # 사고 모드 활성화 및 토큰 예산 설정
**kwargs, # 추가 매개변수 전달
)
thoughts = full_response.content[0].thinking # 사고 과정 추출
print("CLAUDE 3.7 THOUGHTS:", thoughts) # 사고 과정 출력
final_response = full_response.content[1].text # 최종 응답 추출
return final_response # 최종 응답 반환
OpenAI 임베딩 엔진 클래스 -> 텍스트를 숫자로 된 벡터로 변환
class OpenAIEmbeddingEngine(LMMEngine):
# OpenAI 임베딩 API를 사용하는 엔진 클래스
def __init__(
self,
api_key=None,
rate_limit: int = -1,
display_cost: bool = True,
):
"""
OpenAI 임베딩 엔진 초기화
매개변수:
api_key (str, 선택적): OpenAI API 키. 없으면 환경 변수에서 가져옴
rate_limit (int, 선택적): 분당 API 요청 제한 수. -1이면 제한 없음. 기본값 -1
display_cost (bool, 선택적): API 호출 비용 표시 여부. 기본값 True
"""
self.model = "text-embedding-3-small" # 사용할 임베딩 모델 지정
self.cost_per_thousand_tokens = 0.00002 # 1000 토큰당 비용 설정
api_key = api_key or os.getenv("OPENAI_API_KEY") # API 키가 제공되지 않으면 환경 변수에서 가져옴
if api_key is None: # API 키가 여전히 없으면 오류 발생
raise ValueError(
"An API Key needs to be provided in either the api_key parameter or as an environment variable named OPENAI_API_KEY"
)
self.api_key = api_key # API 키 저장
self.display_cost = display_cost # 비용 표시 여부 저장
self.request_interval = 0 if rate_limit == -1 else 60.0 / rate_limit # API 요청 간격 계산 (초 단위)
@backoff.on_exception(
backoff.expo,
(
APIError,
RateLimitError,
APIConnectionError,
),
) # API 오류 발생 시 지수 백오프로 재시도
def get_embeddings(self, text: str) -> np.ndarray:
"""
텍스트의 임베딩 벡터 가져오기
매개변수:
text (str): 임베딩할 텍스트
반환값:
np.ndarray: 임베딩 벡터 배열
"""
client = OpenAI(api_key=self.api_key) # OpenAI 클라이언트 초기화
response = client.embeddings.create(model=self.model, input=text) # 임베딩 생성 요청
if self.display_cost: # 비용 표시 옵션이 활성화된 경우
total_tokens = response.usage.total_tokens # 사용된 총 토큰 수 추출
cost = self.cost_per_thousand_tokens * total_tokens / 1000 # 비용 계산
# print(f"Total cost for this embedding API call: {cost}") # 비용 출력 (주석 처리됨)
return np.array([data.embedding for data in response.data]) # 임베딩 벡터 배열 반환
Azure OpenAI API 클래스 -> 비용 추적 기능
class LMMEngineAzureOpenAI(LMMEngine):
# Azure OpenAI API를 사용하는 LLM 엔진 클래스
def __init__(
self,
api_key=None,
azure_endpoint=None,
model=None,
api_version=None,
rate_limit=-1,
**kwargs
):
"""
LMMEngineAzureOpenAI 클래스의 초기화 메서드
매개변수:
api_key (str, 선택적): Azure OpenAI API 키. 없으면 환경 변수에서 가져옴
azure_endpoint (str, 선택적): Azure OpenAI 엔드포인트. 없으면 환경 변수에서 가져옴
model (str, 필수): 사용할 Azure OpenAI 모델명
api_version (str, 필수): 사용할 Azure OpenAI API 버전
rate_limit (int, 선택적): 분당 API 요청 제한 수. -1이면 제한 없음. 기본값 -1
**kwargs: 추가 매개변수
"""
assert model is not None, "model must be provided" # 모델명이 제공되지 않으면 오류 발생
self.model = model # 모델명 저장
assert api_version is not None, "api_version must be provided" # API 버전이 제공되지 않으면 오류 발생
self.api_version = api_version # API 버전 저장
api_key = api_key or os.getenv("AZURE_OPENAI_API_KEY") # API 키가 제공되지 않으면 환경 변수에서 가져옴
if api_key is None: # API 키가 여전히 없으면 오류 발생
raise ValueError(
"An API Key needs to be provided in either the api_key parameter or as an environment variable named AZURE_OPENAI_API_KEY"
)
self.api_key = api_key # API 키 저장
azure_endpoint = azure_endpoint or os.getenv("AZURE_OPENAI_API_BASE") # Azure 엔드포인트가 제공되지 않으면 환경 변수에서 가져옴
if azure_endpoint is None: # Azure 엔드포인트가 여전히 없으면 오류 발생
raise ValueError(
"An Azure API endpoint needs to be provided in either the azure_endpoint parameter or as an environment variable named AZURE_OPENAI_API_BASE"
)
self.azure_endpoint = azure_endpoint # Azure 엔드포인트 저장
self.request_interval = 0 if rate_limit == -1 else 60.0 / rate_limit # API 요청 간격 계산 (초 단위)
self.llm_client = AzureOpenAI(
azure_endpoint=self.azure_endpoint, # Azure 엔드포인트 설정
api_key=self.api_key, # API 키 설정
api_version=self.api_version, # API 버전 설정
) # Azure OpenAI 클라이언트 초기화
self.cost = 0.0 # 비용 초기화
# @backoff.on_exception(backoff.expo, (APIConnectionError, APIError, RateLimitError), max_tries=10) # 백오프 재시도 (주석 처리됨)
def generate(self, messages, temperature=0.0, max_new_tokens=None, **kwargs):
"""
이전 메시지를 기반으로 다음 메시지 생성
매개변수:
messages (list): 대화 메시지 목록
temperature (float, 선택적): 생성 결과의 무작위성 정도. 기본값 0.0 (결정적)
max_new_tokens (int, 선택적): 생성할 최대 토큰 수. 기본값 4096
**kwargs: 추가 매개변수
반환값:
str: 생성된 응답 텍스트
"""
completion = self.llm_client.chat.completions.create(
model=self.model, # 사용할 모델 지정
messages=messages, # 대화 메시지 목록 전달
max_tokens=max_new_tokens if max_new_tokens else 4096, # 최대 토큰 수 설정
temperature=temperature, # 온도(무작위성) 설정
**kwargs, # 추가 매개변수 전달
)
total_tokens = completion.usage.total_tokens # 사용된 총 토큰 수 추출
self.cost += 0.02 * ((total_tokens + 500) / 1000) # 비용 계산 및 누적 (토큰당 비용 * 토큰 수)
return completion.choices[0].message.content # 생성된 메시지 내용 반환
vLLM API 클래스 -> 텍스트 생성(무료)
class LMMEnginevLLM(LMMEngine):
# vLLM API를 사용하는 LLM 엔진 클래스
def __init__(
self, base_url=None, api_key=None, model=None, rate_limit=-1, **kwargs
):
"""
LMMEnginevLLM 클래스의 초기화 메서드
매개변수:
base_url (str, 선택적): vLLM 엔드포인트 URL. 없으면 환경 변수에서 가져옴
api_key (str, 선택적): vLLM API 키
model (str, 필수): 사용할 모델명
rate_limit (int, 선택적): 분당 API 요청 제한 수. -1이면 제한 없음. 기본값 -1
**kwargs: 추가 매개변수
"""
assert model is not None, "model must be provided" # 모델명이 제공되지 않으면 오류 발생
self.model = model # 모델명 저장
self.api_key = api_key # API 키 저장 (필수 아님)
self.base_url = base_url or os.getenv("vLLM_ENDPOINT_URL") # 엔드포인트 URL이 제공되지 않으면 환경 변수에서 가져옴
if self.base_url is None: # 엔드포인트 URL이 여전히 없으면 오류 발생
raise ValueError(
"An endpoint URL needs to be provided in either the endpoint_url parameter or as an environment variable named vLLM_ENDPOINT_URL"
)
self.request_interval = 0 if rate_limit == -1 else 60.0 / rate_limit # API 요청 간격 계산 (초 단위)
self.llm_client = OpenAI(base_url=self.base_url, api_key=self.api_key) # vLLM 엔드포인트에 연결하는 OpenAI 호환 클라이언트 초기화
# @backoff.on_exception(backoff.expo, (APIConnectionError, APIError, RateLimitError), max_tries=10) # 백오프 재시도 (주석 처리됨)
# TODO: Qwen 모델용으로 선택된 기본 매개변수
def generate(
self,
messages,
temperature=0.0,
top_p=0.8,
repetition_penalty=1.05,
max_new_tokens=512,
**kwargs
):
"""
이전 메시지를 기반으로 다음 메시지 생성
매개변수:
messages (list): 대화 메시지 목록
temperature (float, 선택적): 생성 결과의 무작위성 정도. 기본값 0.0 (결정적)
top_p (float, 선택적): 상위 확률 샘플링 비율. 기본값 0.8
repetition_penalty (float, 선택적): 반복 패널티. 기본값 1.05
max_new_tokens (int, 선택적): 생성할 최대 토큰 수. 기본값 512
**kwargs: 추가 매개변수
반환값:
str: 생성된 응답 텍스트
"""
completion = self.llm_client.chat.completions.create(
model=self.model, # 사용할 모델 지정
messages=messages, # 대화 메시지 목록 전달
max_tokens=max_new_tokens if max_new_tokens else 4096, # 최대 토큰 수 설정
temperature=temperature, # 온도(무작위성) 설정
top_p=top_p, # 상위 확률 샘플링 비율 설정
extra_body={"repetition_penalty": repetition_penalty}, # 반복 패널티 설정 (추가 매개변수)
)
return completion.choices[0].message.content # 생성된 메시지 내용 반환
HuggingFace API 클래스 -> 텍스트 생성(무료)
class LMMEngineHuggingFace(LMMEngine):
# HuggingFace API를 사용하는 LLM 엔진 클래스
def __init__(self, api_key=None, endpoint_url=None, rate_limit=-1, **kwargs):
"""
LMMEngineHuggingFace 클래스의 초기화 메서드
매개변수:
api_key (str, 선택적): HuggingFace 토큰. 없으면 환경 변수에서 가져옴
endpoint_url (str, 필수): HuggingFace 엔드포인트 URL
rate_limit (int, 선택적): 분당 API 요청 제한 수. -1이면 제한 없음. 기본값 -1
**kwargs: 추가 매개변수
"""
assert endpoint_url is not None, "HuggingFace endpoint must be provided" # 엔드포인트 URL이 제공되지 않으면 오류 발생
self.endpoint_url = endpoint_url # 엔드포인트 URL 저장
api_key = api_key or os.getenv("HF_TOKEN") # API 키가 제공되지 않으면 환경 변수에서 가져옴
if api_key is None: # API 키가 여전히 없으면 오류 발생
raise ValueError(
"A HuggingFace token needs to be provided in either the api_key parameter or as an environment variable named HF_TOKEN"
)
self.api_key = api_key # API 키 저장
self.request_interval = 0 if rate_limit == -1 else 60.0 / rate_limit # API 요청 간격 계산 (초 단위)
self.llm_client = OpenAI(base_url=self.endpoint_url, api_key=self.api_key) # HuggingFace 엔드포인트에 연결하는 OpenAI 호환 클라이언트 초기화
@backoff.on_exception(
backoff.expo, (APIConnectionError, APIError, RateLimitError), max_time=60
) # API 오류 발생 시 지수 백오프로 60초까지 재시도
def generate(self, messages, temperature=0.0, max_new_tokens=None, **kwargs):
"""
이전 메시지를 기반으로 다음 메시지 생성
매개변수:
messages (list): 대화 메시지 목록
temperature (float, 선택적): 생성 결과의 무작위성 정도. 기본값 0.0 (결정적)
max_new_tokens (int, 선택적): 생성할 최대 토큰 수. 기본값 4096
**kwargs: 추가 매개변수
반환값:
str: 생성된 응답 텍스트
"""
return (
self.llm_client.chat.completions.create(
model="tgi", # 모델로 "tgi" 고정 사용
messages=messages, # 대화 메시지 목록 전달
max_tokens=max_new_tokens if max_new_tokens else 4096, # 최대 토큰 수 설정
temperature=temperature, # 온도(무작위성) 설정
**kwargs, # 추가 매개변수 전달
)
.choices[0] # 첫 번째 생성 결과 선택
.message.content # 생성된 메시지 내용 반환
)
모든 클래스가 필요한 건 아니다. vLLM API랑 임베딩 엔진만으로도 가능