LM Studio + DeepSeek-R1으로 구축하는 실전 Agent 시스템: Smolagents와 AGUIApp 완벽 가이드
개요
이 튜토리얼에서는 LM Studio에서 DeepSeek-R1 모델을 활용해 웹 스크래핑과 텍스트 요약 기능을 가진 실전 Agent 시스템을 구축합니다. Smolagents로 Agent 로직을 구현하고, AGUIApp으로 사용자 친화적인 웹 인터페이스까지 제공하는 완전한 솔루션을 단계별로 만들어보겠습니다.
🎯 학습 목표
- LM Studio 설치 및 DeepSeek-R1 모델 설정
- Smolagents를 활용한 멀티 도구 Agent 구현
- 웹 스크래핑과 LLM 요약을 결합한 실용적 도구 개발
- AGUIApp을 통한 웹 UI 구축
- 로컬 환경에서의 완전한 Agent 시스템 운영
1. 환경 설정
1.1 LM Studio 설치
macOS 설치:
# Homebrew를 통한 설치
brew install --cask lm-studio
# 또는 공식 웹사이트에서 다운로드
# https://lmstudio.ai/
Windows/Linux:
- LM Studio 공식 사이트에서 설치 파일 다운로드
- 설치 후 실행
1.2 DeepSeek-R1 모델 다운로드
LM Studio를 실행한 후:
- Search 탭에서
deepseek-r1-0528-qwen3-8b-mlx
검색 - Download 버튼 클릭하여 모델 다운로드
- Chat 탭에서 다운로드한 모델 로드
- Local Server 탭에서 서버 시작 (포트: 1234)
1.3 Python 환경 설정
# 가상환경 생성
python -m venv agent_env
source agent_env/bin/activate # Windows: agent_env\Scripts\activate
# 필수 패키지 설치
pip install smolagents requests beautifulsoup4 lxml
pip install agno ag-ui-protocol
pip install uvicorn fastapi
2. 핵심 Agent 시스템 구현
2.1 프로젝트 구조
agent_project/
├── main.py # 메인 Agent 로직
├── ui_app.py # AGUIApp 웹 인터페이스
├── tools/
│ ├── __init__.py
│ ├── scraper.py # 웹 스크래핑 도구
│ └── summarizer.py # 텍스트 요약 도구
└── config.py # 설정 파일
2.2 설정 파일 (config.py)
# config.py
import os
# LM Studio 서버 설정
LM_STUDIO_BASE_URL = "http://localhost:1234/v1"
LM_STUDIO_API_KEY = "not-needed"
MODEL_ID = "deepseek-r1-0528-qwen3-8b-mlx"
# 스크래핑 대상 사이트 (예시)
TARGET_WEBSITES = {
"tech_news": "https://techcrunch.com/",
"ai_research": "https://arxiv.org/list/cs.AI/recent",
"testing_catalog": "https://www.testingcatalog.com/"
}
# UI 설정
UI_PORT = 8000
UI_HOST = "0.0.0.0"
3. 도구 구현
3.1 웹 스크래핑 도구 (tools/scraper.py)
# tools/scraper.py
import requests
import re
from bs4 import BeautifulSoup
from typing import List, Dict, Optional
from smolagents import tool
@tool
def get_latest_articles(
website: str = "https://www.testingcatalog.com/",
n_articles: int = 3
) -> List[Dict]:
"""
지정된 웹사이트에서 최신 기사를 스크래핑합니다.
Args:
website: 스크래핑할 웹사이트 URL
n_articles: 가져올 기사 수 (기본값: 3)
Returns:
기사 정보가 담긴 딕셔너리 리스트
- title: 기사 제목
- url: 기사 URL
- content: 기사 본문
- summary: 간단한 미리보기
"""
try:
headers = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36'
}
response = requests.get(website, headers=headers, timeout=15)
response.raise_for_status()
soup = BeautifulSoup(response.text, "html.parser")
articles = []
# 일반적인 기사 구조를 찾기 위한 셀렉터들
article_selectors = [
"article",
".post",
".entry",
".article-item",
"h2 a, h3 a"
]
found_articles = []
for selector in article_selectors:
found_articles = soup.select(selector)[:n_articles]
if found_articles:
break
for element in found_articles:
try:
# 링크와 제목 추출
if element.name == 'a':
link_elem = element
title = element.get_text(strip=True)
else:
link_elem = element.find('a', href=True)
title = link_elem.get_text(strip=True) if link_elem else "제목 없음"
if not link_elem:
continue
url = link_elem.get('href', '')
if url.startswith('/'):
url = website.rstrip('/') + url
elif not url.startswith('http'):
continue
# 기사 본문 가져오기
article_response = requests.get(url, headers=headers, timeout=10)
article_soup = BeautifulSoup(article_response.text, "html.parser")
# 본문 추출 시도
content_selectors = [
".post-content", ".entry-content", ".article-content",
".content", "main", ".main-content"
]
content = ""
for selector in content_selectors:
content_elem = article_soup.select_one(selector)
if content_elem:
content = content_elem.get_text(" ", strip=True)
break
if not content:
# 모든 p 태그에서 텍스트 추출
paragraphs = article_soup.find_all('p')
content = " ".join([p.get_text(" ", strip=True) for p in paragraphs])
# 내용이 너무 길면 잘라내기
content = content[:2000] + "..." if len(content) > 2000 else content
summary = content[:200] + "..." if len(content) > 200 else content
articles.append({
"title": title,
"url": url,
"content": content,
"summary": summary
})
except Exception as e:
print(f"기사 처리 중 오류: {e}")
continue
return articles[:n_articles]
except Exception as e:
return [{"error": f"스크래핑 실패: {str(e)}"}]
@tool
def search_web_content(query: str, max_results: int = 5) -> List[Dict]:
"""
검색 쿼리를 기반으로 웹 콘텐츠를 검색합니다.
Args:
query: 검색할 키워드
max_results: 최대 결과 수
Returns:
검색 결과 리스트
"""
# DuckDuckGo 검색 (간단한 구현)
try:
search_url = f"https://duckduckgo.com/html/?q={query}"
headers = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36'
}
response = requests.get(search_url, headers=headers, timeout=10)
soup = BeautifulSoup(response.text, "html.parser")
results = []
result_elements = soup.select(".result")[:max_results]
for element in result_elements:
title_elem = element.select_one(".result__title a")
snippet_elem = element.select_one(".result__snippet")
if title_elem:
title = title_elem.get_text(strip=True)
url = title_elem.get('href', '')
snippet = snippet_elem.get_text(strip=True) if snippet_elem else ""
results.append({
"title": title,
"url": url,
"snippet": snippet
})
return results
except Exception as e:
return [{"error": f"검색 실패: {str(e)}"}]
3.2 텍스트 요약 도구 (tools/summarizer.py)
# tools/summarizer.py
from smolagents import tool, OpenAIServerModel
from config import LM_STUDIO_BASE_URL, LM_STUDIO_API_KEY, MODEL_ID
# LLM 모델 인스턴스
model = OpenAIServerModel(
model_id=MODEL_ID,
api_base=LM_STUDIO_BASE_URL,
api_key=LM_STUDIO_API_KEY,
)
@tool
def summarize_article(text: str, max_words: int = 150, style: str = "balanced") -> str:
"""
기사나 텍스트를 요약합니다.
Args:
text: 요약할 텍스트
max_words: 최대 단어 수
style: 요약 스타일 ("concise", "balanced", "detailed")
Returns:
요약된 텍스트
"""
if len(text.strip()) < 50:
return "텍스트가 너무 짧아서 요약할 수 없습니다."
style_prompts = {
"concise": "핵심 포인트만 간단명료하게",
"balanced": "주요 내용을 균형있게",
"detailed": "중요한 세부사항을 포함하여 상세하게"
}
style_instruction = style_prompts.get(style, style_prompts["balanced"])
prompt = f"""다음 텍스트를 {style_instruction} {max_words}단어 이내로 한국어로 요약해주세요.
텍스트:
{text}
요약:"""
try:
response = model(
[{"role": "user", "content": [{"type": "text", "text": prompt}]}],
stop_sequences=["\n\n", "---"],
max_tokens=300
)
summary = response.content.strip()
return summary if summary else "요약을 생성할 수 없습니다."
except Exception as e:
return f"요약 중 오류가 발생했습니다: {str(e)}"
@tool
def analyze_sentiment(text: str) -> Dict:
"""
텍스트의 감정을 분석합니다.
Args:
text: 분석할 텍스트
Returns:
감정 분석 결과 딕셔너리
"""
prompt = f"""다음 텍스트의 감정을 분석하고 JSON 형식으로 답변해주세요:
텍스트: {text}
답변 형식:
sentiment
"""
try:
response = model(
[{"role": "user", "content": [{"type": "text", "text": prompt}]}],
max_tokens=200
)
# JSON 파싱 시도
import json
result = json.loads(response.content.strip())
return result
except Exception as e:
return {
"sentiment": "neutral",
"confidence": 0.0,
"keywords": [],
"explanation": f"분석 실패: {str(e)}"
}
@tool
def extract_keywords(text: str, max_keywords: int = 10) -> List[str]:
"""
텍스트에서 키워드를 추출합니다.
Args:
text: 키워드를 추출할 텍스트
max_keywords: 최대 키워드 수
Returns:
키워드 리스트
"""
prompt = f"""다음 텍스트에서 가장 중요한 키워드 {max_keywords}개를 추출해주세요. JSON 배열 형태로 답변해주세요.
텍스트: {text}
답변 형식: ["키워드1", "키워드2", ...]
"""
try:
response = model(
[{"role": "user", "content": [{"type": "text", "text": prompt}]}],
max_tokens=150
)
import json
keywords = json.loads(response.content.strip())
return keywords if isinstance(keywords, list) else []
except Exception as e:
# 간단한 키워드 추출 폴백
import re
words = re.findall(r'\b[가-힣a-zA-Z]{2,}\b', text)
return list(set(words))[:max_keywords]
4. 메인 Agent 시스템 (main.py)
# main.py
import json
from smolagents import CodeAgent, OpenAIServerModel
from tools.scraper import get_latest_articles, search_web_content
from tools.summarizer import summarize_article, analyze_sentiment, extract_keywords
from config import LM_STUDIO_BASE_URL, LM_STUDIO_API_KEY, MODEL_ID
class NewsAnalysisAgent:
def __init__(self):
"""뉴스 분석 Agent 초기화"""
self.model = OpenAIServerModel(
model_id=MODEL_ID,
api_base=LM_STUDIO_BASE_URL,
api_key=LM_STUDIO_API_KEY,
)
self.agent = CodeAgent(
name="NewsAnalysisAgent",
model=self.model,
tools=[
get_latest_articles,
search_web_content,
summarize_article,
analyze_sentiment,
extract_keywords
],
max_steps=15,
verbose=True
)
def analyze_website_news(self, website_url: str, num_articles: int = 3) -> str:
"""웹사이트의 뉴스를 분석합니다."""
task = f"""
다음 작업을 순서대로 수행해주세요:
1. get_latest_articles 도구를 사용해 '{website_url}'에서 최신 기사 {num_articles}개를 가져오세요.
2. 각 기사에 대해:
- summarize_article 도구로 요약을 생성하세요
- analyze_sentiment 도구로 감정을 분석하세요
- extract_keywords 도구로 키워드를 추출하세요
3. 모든 분석 결과를 마크다운 형식으로 정리해서 final_answer로 반환하세요.
마크다운 형식:
# 뉴스 분석 결과
## 기사 1: [제목]
- **URL**: [링크]
- **요약**: [요약 내용]
- **감정**: [감정 분석 결과]
- **키워드**: [키워드 리스트]
(다른 기사들도 동일한 형식으로...)
## 전체 분석 요약
- 주요 트렌드
- 전반적인 감정
- 공통 키워드
"""
return self.agent.run(task)
def search_and_analyze(self, query: str, max_results: int = 3) -> str:
"""검색하고 결과를 분석합니다."""
task = f"""
다음 작업을 수행해주세요:
1. search_web_content 도구를 사용해 '{query}' 키워드로 검색하고 {max_results}개 결과를 가져오세요.
2. 각 검색 결과에 대해 감정 분석과 키워드 추출을 수행하세요.
3. 결과를 마크다운으로 정리해서 반환하세요.
"""
return self.agent.run(task)
def main():
"""메인 실행 함수"""
print("🤖 뉴스 분석 Agent를 시작합니다...")
print("📡 LM Studio 서버 연결을 확인해주세요 (http://localhost:1234)")
agent = NewsAnalysisAgent()
while True:
print("\n" + "="*50)
print("1. 웹사이트 뉴스 분석")
print("2. 키워드 검색 및 분석")
print("3. 종료")
print("="*50)
choice = input("선택하세요 (1-3): ").strip()
if choice == "1":
url = input("분석할 웹사이트 URL을 입력하세요: ").strip()
if not url:
url = "https://www.testingcatalog.com/"
num_articles = input("분석할 기사 수 (기본값: 3): ").strip()
num_articles = int(num_articles) if num_articles.isdigit() else 3
print(f"\n🔍 {url}에서 최신 기사 {num_articles}개를 분석 중...")
result = agent.analyze_website_news(url, num_articles)
print("\n" + "="*70)
print(result)
print("="*70)
elif choice == "2":
query = input("검색할 키워드를 입력하세요: ").strip()
if not query:
print("키워드를 입력해주세요.")
continue
max_results = input("검색할 결과 수 (기본값: 3): ").strip()
max_results = int(max_results) if max_results.isdigit() else 3
print(f"\n🔍 '{query}' 키워드로 검색 중...")
result = agent.search_and_analyze(query, max_results)
print("\n" + "="*70)
print(result)
print("="*70)
elif choice == "3":
print("👋 프로그램을 종료합니다.")
break
else:
print("❌ 잘못된 선택입니다. 1-3 중에서 선택해주세요.")
if __name__ == "__main__":
main()
5. 웹 UI 구현 (ui_app.py)
5.1 AGUIApp 기반 웹 인터페이스
# ui_app.py
import asyncio
from typing import Optional
from agno.agent.agent import Agent
from agno.app.agui.app import AGUIApp
from agno.models.openai import OpenAIChat
from smolagents import OpenAIServerModel
from tools.scraper import get_latest_articles, search_web_content
from tools.summarizer import summarize_article, analyze_sentiment, extract_keywords
from config import LM_STUDIO_BASE_URL, LM_STUDIO_API_KEY, MODEL_ID, UI_PORT, UI_HOST
class NewsAnalysisWebAgent:
def __init__(self):
"""웹 기반 뉴스 분석 Agent"""
# LM Studio 모델 설정
self.model = OpenAIChat(
id=MODEL_ID,
api_base=LM_STUDIO_BASE_URL,
api_key=LM_STUDIO_API_KEY
)
# Agno Agent 생성
self.agent = Agent(
name="NewsAnalysisWebAgent",
model=self.model,
instructions="""
당신은 뉴스 분석 전문가입니다. 다음 기능을 제공합니다:
1. **웹사이트 뉴스 분석**: 특정 웹사이트에서 최신 뉴스를 가져와 분석
2. **키워드 검색**: 검색 키워드로 관련 뉴스 찾기 및 분석
3. **텍스트 요약**: 긴 텍스트를 간결하게 요약
4. **감정 분석**: 텍스트의 감정(긍정/부정/중립) 분석
5. **키워드 추출**: 텍스트에서 중요한 키워드 추출
사용자의 요청에 따라 적절한 분석을 수행하고 결과를 마크다운 형식으로 제공하세요.
""",
add_datetime_to_instructions=True,
markdown=True,
)
async def analyze_website(self, website_url: str, num_articles: int = 3) -> str:
"""웹사이트 뉴스 분석"""
try:
# 뉴스 가져오기
articles = get_latest_articles(website_url, num_articles)
if not articles or (len(articles) == 1 and "error" in articles[0]):
return f"❌ 웹사이트에서 뉴스를 가져올 수 없습니다: {website_url}"
analysis_results = []
for i, article in enumerate(articles, 1):
if "error" in article:
continue
# 각 기사 분석
summary = summarize_article(article["content"], max_words=100)
sentiment = analyze_sentiment(article["content"])
keywords = extract_keywords(article["content"], max_keywords=5)
analysis_results.append({
"title": article["title"],
"url": article["url"],
"summary": summary,
"sentiment": sentiment,
"keywords": keywords
})
# 결과 포맷팅
result = f"# 🔍 뉴스 분석 결과\n\n**분석 웹사이트**: {website_url}\n**분석 기사 수**: {len(analysis_results)}개\n\n"
for i, analysis in enumerate(analysis_results, 1):
result += f"## 📰 기사 {i}: {analysis['title']}\n\n"
result += f"**🔗 URL**: [{analysis['url']}]({analysis['url']})\n\n"
result += f"**📝 요약**: {analysis['summary']}\n\n"
result += f"**😊 감정**: {analysis['sentiment'].get('sentiment', 'unknown')} "
result += f"(신뢰도: {analysis['sentiment'].get('confidence', 0):.2f})\n\n"
result += f"**🏷️ 키워드**: {', '.join(analysis['keywords'])}\n\n"
result += "---\n\n"
# 전체 요약
all_keywords = []
sentiments = []
for analysis in analysis_results:
all_keywords.extend(analysis['keywords'])
sentiments.append(analysis['sentiment'].get('sentiment', 'neutral'))
common_keywords = list(set(all_keywords))[:10]
positive_count = sentiments.count('positive')
negative_count = sentiments.count('negative')
neutral_count = sentiments.count('neutral')
result += "## 📊 전체 분석 요약\n\n"
result += f"**주요 키워드**: {', '.join(common_keywords[:8])}\n\n"
result += f"**감정 분포**: 긍정 {positive_count}개, 부정 {negative_count}개, 중립 {neutral_count}개\n\n"
return result
except Exception as e:
return f"❌ 분석 중 오류가 발생했습니다: {str(e)}"
async def search_and_analyze(self, query: str, max_results: int = 3) -> str:
"""검색 및 분석"""
try:
# 검색 수행
search_results = search_web_content(query, max_results)
if not search_results or (len(search_results) == 1 and "error" in search_results[0]):
return f"❌ '{query}' 검색 결과가 없습니다."
result = f"# 🔍 검색 분석 결과\n\n**검색 키워드**: {query}\n**검색 결과**: {len(search_results)}개\n\n"
for i, item in enumerate(search_results, 1):
if "error" in item:
continue
# 감정 분석 및 키워드 추출
sentiment = analyze_sentiment(item.get("snippet", ""))
keywords = extract_keywords(item.get("snippet", ""), max_keywords=3)
result += f"## 🔗 결과 {i}: {item.get('title', '제목 없음')}\n\n"
result += f"**URL**: [{item.get('url', '')}]({item.get('url', '')})\n\n"
result += f"**스니펫**: {item.get('snippet', '')}\n\n"
result += f"**감정**: {sentiment.get('sentiment', 'unknown')}\n\n"
result += f"**키워드**: {', '.join(keywords)}\n\n"
result += "---\n\n"
return result
except Exception as e:
return f"❌ 검색 중 오류가 발생했습니다: {str(e)}"
# AGUI 앱 생성
def create_agui_app():
"""AGUI 앱 생성"""
news_agent = NewsAnalysisWebAgent()
# AGUI 앱 설정
agui_app = AGUIApp(
agent=news_agent.agent,
name="🤖 뉴스 분석 Agent",
app_id="news_analysis_agent",
description="웹사이트 뉴스 분석, 검색, 요약, 감정 분석을 제공하는 AI Agent",
host=UI_HOST,
port=UI_PORT
)
return agui_app
def main():
"""웹 UI 실행"""
print("🚀 뉴스 분석 Agent 웹 UI를 시작합니다...")
print(f"📡 LM Studio 서버가 {LM_STUDIO_BASE_URL}에서 실행 중인지 확인하세요")
print(f"🌐 웹 UI: http://{UI_HOST}:{UI_PORT}")
app = create_agui_app()
app.serve(reload=True)
if __name__ == "__main__":
main()
6. 실행 및 테스트
6.1 단계별 실행
1단계: LM Studio 서버 확인
# LM Studio에서 DeepSeek-R1 모델이 로드되어 있는지 확인
curl http://localhost:1234/v1/models
2단계: CLI 버전 실행
# 가상환경 활성화
source agent_env/bin/activate
# CLI 버전 실행
python main.py
3단계: 웹 UI 실행
# 웹 UI 실행 (별도 터미널)
python ui_app.py
6.2 사용 예시
CLI에서 뉴스 분석:
선택하세요 (1-3): 1
분석할 웹사이트 URL을 입력하세요: https://techcrunch.com
분석할 기사 수 (기본값: 3): 5
웹 UI에서 사용:
웹사이트 https://www.testingcatalog.com/ 에서 최신 뉴스 3개를 분석해줘
7. 고급 기능 확장
7.1 스케줄링 기능 추가
# scheduler.py
import schedule
import time
import json
from datetime import datetime
from main import NewsAnalysisAgent
class NewsScheduler:
def __init__(self):
self.agent = NewsAnalysisAgent()
self.monitored_sites = [
"https://techcrunch.com/",
"https://www.testingcatalog.com/",
"https://arxiv.org/list/cs.AI/recent"
]
def daily_analysis(self):
"""일일 뉴스 분석"""
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
print(f"📅 일일 뉴스 분석 시작: {timestamp}")
results = {}
for site in self.monitored_sites:
try:
result = self.agent.analyze_website_news(site, 2)
results[site] = result
print(f"✅ {site} 분석 완료")
except Exception as e:
print(f"❌ {site} 분석 실패: {e}")
results[site] = f"분석 실패: {e}"
# 결과 저장
filename = f"daily_analysis_{datetime.now().strftime('%Y%m%d')}.json"
with open(filename, 'w', encoding='utf-8') as f:
json.dump(results, f, ensure_ascii=False, indent=2)
print(f"📄 결과 저장: {filename}")
def start_scheduler(self):
"""스케줄러 시작"""
# 매일 오전 9시에 분석 실행
schedule.every().day.at("09:00").do(self.daily_analysis)
print("⏰ 스케줄러 시작 - 매일 오전 9시에 뉴스 분석")
while True:
schedule.run_pending()
time.sleep(60)
if __name__ == "__main__":
scheduler = NewsScheduler()
scheduler.start_scheduler()
7.2 데이터베이스 연동
# database.py
import sqlite3
import json
from datetime import datetime
from typing import List, Dict
class NewsDatabase:
def __init__(self, db_path: str = "news_analysis.db"):
self.db_path = db_path
self.init_database()
def init_database(self):
"""데이터베이스 초기화"""
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS articles (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
url TEXT UNIQUE NOT NULL,
content TEXT,
summary TEXT,
sentiment TEXT,
keywords TEXT,
source_website TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
""")
cursor.execute("""
CREATE TABLE IF NOT EXISTS analysis_logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
website TEXT NOT NULL,
analysis_type TEXT NOT NULL,
result TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
""")
conn.commit()
conn.close()
def save_article(self, article_data: Dict):
"""기사 데이터 저장"""
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
try:
cursor.execute("""
INSERT OR REPLACE INTO articles
(title, url, content, summary, sentiment, keywords, source_website)
VALUES (?, ?, ?, ?, ?, ?, ?)
""", (
article_data.get('title'),
article_data.get('url'),
article_data.get('content'),
article_data.get('summary'),
json.dumps(article_data.get('sentiment', {})),
json.dumps(article_data.get('keywords', [])),
article_data.get('source_website')
))
conn.commit()
finally:
conn.close()
def get_recent_articles(self, limit: int = 10) -> List[Dict]:
"""최근 기사 조회"""
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute("""
SELECT title, url, summary, sentiment, keywords, created_at
FROM articles
ORDER BY created_at DESC
LIMIT ?
""", (limit,))
results = []
for row in cursor.fetchall():
results.append({
'title': row[0],
'url': row[1],
'summary': row[2],
'sentiment': json.loads(row[3]) if row[3] else {},
'keywords': json.loads(row[4]) if row[4] else [],
'created_at': row[5]
})
conn.close()
return results
8. 트러블슈팅
8.1 일반적인 문제와 해결책
문제 1: LM Studio 연결 실패
# 해결 방법
1. LM Studio가 실행 중인지 확인
2. 로컬 서버가 포트 1234에서 실행 중인지 확인
3. 모델이 올바르게 로드되었는지 확인
# 연결 테스트
curl -X POST http://localhost:1234/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{"model": "deepseek-r1-0528-qwen3-8b-mlx", "messages": [{"role": "user", "content": "Hello"}]}'
문제 2: 웹 스크래핑 실패
# 해결 방법: 더 견고한 스크래핑 로직
def robust_scraping(url):
try:
# User-Agent 설정
headers = {
'User-Agent': 'Mozilla/5.0 (compatible; NewsBot/1.0)'
}
response = requests.get(url, headers=headers, timeout=15)
return response
except requests.exceptions.RequestException as e:
print(f"스크래핑 오류: {e}")
return None
문제 3: 메모리 부족
# 해결 방법: 배치 처리 및 메모리 관리
def process_articles_in_batches(articles, batch_size=2):
for i in range(0, len(articles), batch_size):
batch = articles[i:i+batch_size]
yield batch
8.2 성능 최적화
비동기 처리:
import asyncio
import aiohttp
async def async_scrape_articles(urls):
async with aiohttp.ClientSession() as session:
tasks = [scrape_single_article(session, url) for url in urls]
return await asyncio.gather(*tasks)
캐싱 구현:
from functools import lru_cache
import hashlib
@lru_cache(maxsize=100)
def cached_summarize(text_hash, text):
return summarize_article(text)
def get_text_hash(text):
return hashlib.md5(text.encode()).hexdigest()
9. 결론 및 확장 방향
9.1 구축한 시스템의 가치
이 튜토리얼을 통해 구축한 시스템의 주요 특징:
- 완전한 로컬 환경: 외부 API 의존성 없이 LM Studio에서 실행
- 실용적인 기능: 웹 스크래핑, 요약, 감정 분석, 키워드 추출
- 사용자 친화적 인터페이스: CLI와 웹 UI 모두 제공
- 확장 가능한 아키텍처: 새로운 도구와 기능을 쉽게 추가
9.2 향후 확장 계획
멀티모달 기능 추가:
- 이미지 분석 및 OCR
- 음성 인식 및 TTS
- PDF/문서 처리
고급 분석 기능:
- 트렌드 분석 및 예측
- 다국어 지원
- 실시간 모니터링 대시보드
엔터프라이즈 기능:
- 사용자 권한 관리
- API 키 관리
- 상세한 로깅 및 감사
9.3 최종 권장사항
- 점진적 확장: 기본 기능부터 시작해서 필요에 따라 확장
- 안정성 우선: 에러 핸들링과 폴백 메커니즘 구현
- 사용자 피드백: 실제 사용자의 피드백을 바탕으로 개선
- 보안 고려: 웹 스크래핑 시 Rate Limiting 및 Robot.txt 준수
이 시스템을 기반으로 더욱 정교하고 실용적인 AI Agent 솔루션을 구축하시길 바랍니다.
이 튜토리얼의 전체 소스코드는 GitHub에서 확인하실 수 있습니다.
```
이 글은 너무 길어서 계속해서 UI 부분과 마지막 부분을 추가로 작성하겠습니다.