FastAPI 기반으로 만든 LLM 보고서 생성 API를 백엔드 서버와 연동하는 과정에서,
정상적으로 동작하던 로컬 테스트와 달리 백엔드 테스트 중 LLM에서 인덱싱 에러가 발생했다.
같은 입력인데도 서버에서는 에러, 로컬에서는 정상 작동하는 상황.
처음엔 백엔드 요청 구조를 의심했지만, 결국 문제는 내 FastAPI 서버 내부의 LLM 호출 부분에 있었다.
에러 내용
로그에 아래와 같은 경고와 함께 500 Internal Server Error 발생:
Token indices sequence length is longer than the specified maximum sequence length for this model (7539 > 1024).
Running this sequence through the model will result in indexing errors del (7539 > 1024).
그리고 아래와 같은 UserWarning도 동반됨:
do_sample is set to False. However, temperature is set to 0.7 -- this flag is only used in sample-based generation modes.
You should set do_sample=True or unset temperature.
원인 분석
- skt/kogpt2-base-v2모델의 최대 입력 토큰 수가 1024로 제한됨
- rag에 들어있던 pdf도 프롬프트로 들어가서 짧은 프롬프트를 작성해도 프롬프트가 길다고 판단되어 에러 발생
→ tokenizer.encode()로 프롬프트를 잘라 해결 시도 - tokenizer는 "gpt2"로 설정되어 있었고,
모델은 "skt/kogpt2-base-v2"였기 때문에 tokenizer와 model이 달라 에러 발생 - 입력 길이 잘라도 여전히 출력이 길어지면 전체 길이 초과됨
→ max_new_tokens를 설정하지 않아 출력 길이 무제한 상태였음
해결 방법
1. tokenizer 모델 일치시키기
tokenizer = AutoTokenizer.from_pretrained("skt/kogpt2-base-v2")
2. 정확한 토큰 기준으로 프롬프트 자르기
token_ids = tokenizer.encode(full_prompt) #tokenizer.tokenize()말고!
if len(token_ids) > 900: # 출력 여유 고려
token_ids = token_ids[:900]
trimmed_prompt = tokenizer.decode(token_ids)
tokenizer.tokenize()는 텍스트를 사람이 보기 좋은 문자열 단위의 토큰으로 나누는 반면,
tokenizer.encode()는 모델이 실제로 처리하는 숫자 토큰 ID로 변환함.
겉보기엔 tokenize()로 자르면 충분해 보이지만, 모델 입장에서의 실제 입력 길이는 encode() 기준으로 계산되기 때문에,
프롬프트가 너무 길어 에러가 나는지 확인하거나 자를 때는 반드시 encode()로 해야 정확함.
모델에 넣기 전, encode()로 자르고 decode()로 다시 문자열로 변환하는 흐름이 가장 안전한 처리 방식.
3. transformers pipeline에서 출력 길이 제한
pipe = pipeline(
"text-generation",
model=model,
tokenizer=tokenizer,
device=device,
max_new_tokens=100, # 출력 길이 제한
do_sample=True,
temperature=0.7,
)
결론
- tokenizer와 모델은 반드시 일치시켜야 한다
- tokenizer.tokenize()보다 encode()를 쓰는 게 진짜 모델 기준으로 잘라짐
- max_new_tokens로 출력 토큰도 잘라줘야함
수정된 report_generator.py
더보기
더보기
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import PromptTemplate
from rag_engine.llm import load_llm
from rag_engine.embedder import get_embedder
from rag_engine.vector_store import load_vector_db, add_to_vector_db
from transformers import AutoTokenizer
from langchain.schema import Document
import uuid
import json
from typing import List
from fastapi import UploadFile
from rag_engine.prompt import get_search_prompt
from rag_engine.loader import load_pdf
import requests
from fastapi import FastAPI, Form
from pydantic import BaseModel
import os
def get_report_prompt():
template = """
다음 문서 내용을 기반으로 사용자가 원하는 주제에 대한 보고서를 작성하세요.
주제: {question}
문서:
{context}
"""
return PromptTemplate.from_template(template)
def generate_report(topic: str, file: UploadFile, references: List[str]):
# 1. PDF 파일 저장
save_dir = os.path.join(os.getcwd(), "data")
os.makedirs(save_dir, exist_ok=True)
filename = f"{uuid.uuid4().hex}_{file.filename}"
file_path = os.path.join(save_dir, filename)
with open(file_path, "wb") as f:
f.write(file.file.read())
# 2. PDF → Document 청크
pages = load_pdf(file_path)
docs = [
Document(page_content=page.page_content, metadata={"source": filename, "page": i})
for i, page in enumerate(pages)
]
# 3. 벡터 DB에 임베딩
embedder = get_embedder()
vectordb = load_vector_db(embedder)
add_to_vector_db(docs, vectordb)
# 4. 관련 문서 검색
retriever = vectordb.as_retriever()
relevant_docs = retriever.get_relevant_documents(topic)
# 6. 컨텍스트 구성
context = "\n".join([doc.page_content for doc in relevant_docs])
# 5. references가 있다면 문맥 뒷부분에 사용자 요구사항으로 붙이기
if references:
requirements_text = "\n\n[사용자 요구사항]\n" + "\n".join(references)
context += requirements_text
prompt_template = get_search_prompt()
full_prompt = prompt_template.format(context=context, question=topic)
# 7. 토큰 길이 제한
tokenizer = AutoTokenizer.from_pretrained("skt/kogpt2-base-v2")
tokens = tokenizer.encode(full_prompt)
if len(tokens) > 800:
tokens = tokens[:800]
trimmed_prompt = tokenizer.decode(tokens)
# 8. LLM 호출
llm = load_llm()
output = llm.invoke(trimmed_prompt)
return output
# API 요청/응답 스키마
class GenerateReportRequest(BaseModel):
prompt: str
class GenerateReportResponse(BaseModel):
prompt: str
report: str
# FastAPI 라우터
app = FastAPI(title="AI Report Generator API")
@app.post("/generate_report", response_model=GenerateReportResponse)
async def generate_report_api(prompt: str = Form(...)):
report = generate_report(prompt)
return GenerateReportResponse(prompt=prompt, report=report)
if __name__ == "__main__":
topic = input(" 보고서 주제를 입력하세요: ")
report = generate_report(topic)
print("\n 보고서 결과:\n")
print(report)