Chapter 12: 30์ผ AI ์์ด์ ํธ ์์ฑ ๋ก๋๋งต
"์ฌ์ ์ ๋์ด ์๋, ์ง์ง ์์์ ์ ์ ์์ต๋๋ค."
์ด ์ฑ
์ ๋ชจ๋ ์ฑํฐ๋ฅผ ์ฝ์ผ์
จ์ต๋๋ค. ์ถํ๋๋ฆฝ๋๋ค. ํ์ง๋ง ์์งํ ๋ง์๋๋ฆฌ๋ฉด, ์ฑ
์ ์ฝ๋ ๊ฒ๋ง์ผ๋ก๋ ์๋ฌด๊ฒ๋ ๋ณํ์ง ์์ต๋๋ค. ์ง์์ ์คํ๋ ๋๋ง ๊ฐ์น๊ฐ ์์ต๋๋ค.
์ด๋ฒ ์ฑํฐ์์๋ ์ง๋ 11๊ฐ ์ฑํฐ์์ ๋ฐฐ์ด ๋ชจ๋ ๋ด์ฉ์ 30์ผ ์์ ์ค์ ์ด์ฉ ๊ฐ๋ฅํ ์์คํ
์ผ๋ก ๋ง๋๋ ๊ตฌ์ฒด์ ์ธ ๋ก๋๋งต์ ์ ์ํฉ๋๋ค. ๋งค์ผ ๋ฌด์์ ํ๊ณ , ์ผ๋ง๋ ๊ฑธ๋ฆฌ๊ณ , ๋งํ์ ๋ ์ด๋ป๊ฒ
ํด๊ฒฐํ๋์งโ๋ชจ๋ ๊ฒ์ด ์ฌ๊ธฐ ์์ต๋๋ค.
๐ฏ 30์ผ ํ, ๋น์ ์ด ๊ฐ๊ฒ ๋ ๊ฒ
๋ก๋๋งต์ ์์ํ๊ธฐ ์ ์, 30์ผ ํ์ ๋ชจ์ต์ ๋ช
ํํ ๊ทธ๋ ค๋ด
์๋ค:
| ์์ญ |
๊ตฌ์ถ๋๋ ๊ฒ |
๊ธฐ๋ ํจ๊ณผ |
| ์์ง ์๋ํ |
๋ด์ค/๊ฒฝ์์ฌ/์ปค๋ฎค๋ํฐ ๋ชจ๋ํฐ๋ง ๋ด 3์ข
|
์ฃผ 5์๊ฐ โ 0์๊ฐ |
| ์ฝํ
์ธ ์์ฑ |
๋ธ๋ก๊ทธ/SNS/๋ด์ค๋ ํฐ ์ด์ ๋ด 5์ข
|
์ 20์๊ฐ โ 2์๊ฐ |
| ๋ถ์ ์์คํ
|
ํธ๋ ๋/๊ฐ์ /๊ฒฝ์ ๋ถ์ ๋ด 4์ข
|
์ปจ์คํ
๋น์ฉ ์ 100๋ง์ ์ ๊ฐ |
| ์ด์ ์๋ํ |
๋ณด๊ณ ์/์์ฝ/์๋ฆผ ๋ด 5์ข
|
์ฃผ 3์๊ฐ โ 0์๊ฐ |
| ์์ฌ๊ฒฐ์ ์ง์ |
๋ฆฌ์์น/๋น๊ต/์ถ์ฒ ๋ด 4์ข
|
์์ฌ๊ฒฐ์ ์๋ 3๋ฐฐ ํฅ์ |
| ์ธํ๋ผ |
ํด๋ผ์ฐ๋ ๋ฐฐํฌ + ๋ชจ๋ํฐ๋ง + HITL |
24/7 ๋ฌด์ค๋จ ์ด์ |
์ด 21๊ฐ ๋ด + ์์ ํ ์ด์ ์์คํ
= ์ 40์๊ฐ ์ด์ ์ ์ฝ + ๋น์ฉ ์ ๊ฐ
๐
1์ฃผ์ฐจ (1-7์ผ): ๊ธฐ์ด ๋ค์ง๊ธฐ
์ฒซ ์ฃผ๋ ๊ธฐ์ด ์ฒด๋ ฅ์ ๊ธฐ๋ฅด๋ ์๊ฐ์
๋๋ค. ์๋๋ฅด์ง ๋ง์ธ์. ์ฌ๊ธฐ์ ๋จ๋จํ ๋ค์ ธ์ผ ๋๋จธ์ง 3์ฃผ๊ฐ ์ํํฉ๋๋ค.
Day 1: ํ๊ฒฝ์ค์ ์๋ฃ + ์ฒซ ์์ด์ ํธ ์คํ โฑ๏ธ 2-3์๊ฐ
์ค์ : ๊ฐ๋ฐ ํ๊ฒฝ ๊ตฌ์ถ
# 1. Python ๊ฐ์ํ๊ฒฝ ์์ฑ
python -m venv ai-agent-env
source ai-agent-env/bin/activate # Windows: ai-agent-env\Scripts\activate
# 2. ํต์ฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ค์น
pip install anthropic python-dotenv requests beautifulsoup4
# 3. ํ๋ก์ ํธ ๊ตฌ์กฐ ์์ฑ
mkdir -p ai-agents/{agents,tools,memory,config,logs}
touch ai-agents/.env ai-agents/main.py
์คํ: ์ฒซ ์์ด์ ํธ ์คํ
# ai-agents/agents/first_agent.py
import anthropic
from dotenv import load_dotenv
import os
load_dotenv()
def create_first_agent():
"""๋น์ ์ ์ฒซ ๋ฒ์งธ AI ์์ด์ ํธ"""
client = anthropic.Anthropic(api_key=os.getenv("ANTHROPIC_API_KEY"))
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
system="๋น์ ์ ์น์ ํ AI ์ด์์คํดํธ์
๋๋ค.",
messages=[
{"role": "user", "content": "์๋
ํ์ธ์! ์๊ธฐ์๊ฐ ๋ถํ๋๋ ค์."}
]
)
print("๐ค ์ฒซ ์์ด์ ํธ ์๋ต:")
print(response.content[0].text)
return response
if __name__ == "__main__":
create_first_agent()
Day 1 ์ฒดํฌ๋ฆฌ์คํธ
- [ ] Python 3.10+ ์ค์น ํ์ธ
- [ ] ๊ฐ์ํ๊ฒฝ ์์ฑ ๋ฐ ํ์ฑํ
- [ ] Anthropic API ํค ๋ฐ๊ธ ๋ฐ .env ์ค์
- [ ] ์ฒซ ์์ด์ ํธ ์คํ ์ฑ๊ณต
- [ ] Git ์ ์ฅ์ ์ด๊ธฐํ
Day 2: ReAct ๋ฃจํ ์ดํด + ๋๊ตฌ ์ถ๊ฐ โฑ๏ธ 3-4์๊ฐ
ํต์ฌ ๊ฐ๋
๊ตฌํ
# ai-agents/agents/react_agent.py
import anthropic
import json
from datetime import datetime
class ReActAgent:
"""ReAct ํจํด์ ๊ตฌํํ ๊ธฐ๋ณธ ์์ด์ ํธ"""
def __init__(self):
self.client = anthropic.Anthropic()
self.tools = self._define_tools()
def _define_tools(self):
return [
{
"name": "get_current_time",
"description": "ํ์ฌ ๋ ์ง์ ์๊ฐ์ ๋ฐํํฉ๋๋ค.",
"input_schema": {
"type": "object",
"properties": {},
"required": []
}
},
{
"name": "calculate",
"description": "์ํ ๊ณ์ฐ์ ์ํํฉ๋๋ค.",
"input_schema": {
"type": "object",
"properties": {
"expression": {
"type": "string",
"description": "๊ณ์ฐํ ์ํ ํํ์ (์: 2+2, 100*5)"
}
},
"required": ["expression"]
}
}
]
def _execute_tool(self, tool_name: str, tool_input: dict) -> str:
"""๋๊ตฌ ์คํ"""
if tool_name == "get_current_time":
return datetime.now().strftime("%Y๋
%m์ %d์ผ %H์ %M๋ถ")
elif tool_name == "calculate":
try:
# ์์ ํ ๊ณ์ฐ๋ง ํ์ฉ
allowed_chars = set('0123456789+-*/.() ')
expr = tool_input.get("expression", "")
if all(c in allowed_chars for c in expr):
result = eval(expr)
return str(result)
return "ํ์ฉ๋์ง ์๋ ํํ์์
๋๋ค."
except Exception as e:
return f"๊ณ์ฐ ์ค๋ฅ: {str(e)}"
return "์ ์ ์๋ ๋๊ตฌ์
๋๋ค."
def run(self, user_message: str) -> str:
"""์์ด์ ํธ ์คํ (ReAct ๋ฃจํ)"""
messages = [{"role": "user", "content": user_message}]
while True:
response = self.client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
system="๋น์ ์ ๋๊ตฌ๋ฅผ ํ์ฉํ๋ AI ์ด์์คํดํธ์
๋๋ค.",
tools=self.tools,
messages=messages
)
# ์๋ต ์ฒ๋ฆฌ
if response.stop_reason == "end_turn":
# ์ต์ข
์๋ต
for block in response.content:
if hasattr(block, 'text'):
return block.text
elif response.stop_reason == "tool_use":
# ๋๊ตฌ ์ฌ์ฉ
tool_results = []
for block in response.content:
if block.type == "tool_use":
print(f"๐ง ๋๊ตฌ ์คํ: {block.name}")
result = self._execute_tool(block.name, block.input)
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": result
})
# ๋ํ ์ด๋ ฅ ์
๋ฐ์ดํธ
messages.append({"role": "assistant", "content": response.content})
messages.append({"role": "user", "content": tool_results})
else:
return "์์์น ๋ชปํ ์๋ต์
๋๋ค."
# ํ
์คํธ
if __name__ == "__main__":
agent = ReActAgent()
# ํ
์คํธ 1: ์๊ฐ ํ์ธ
print(agent.run("์ง๊ธ ๋ช ์์ผ?"))
# ํ
์คํธ 2: ๊ณ์ฐ
print(agent.run("1234 * 5678์ ์ผ๋ง์ผ?"))
Day 2 ์ฒดํฌ๋ฆฌ์คํธ
- [ ] ReAct ํจํด ๊ฐ๋
์ดํด (Think โ Act โ Observe)
- [ ] Tool Use ๊ตฌ์กฐ ์ดํด
- [ ] ๊ธฐ๋ณธ ๋๊ตฌ 2๊ฐ ๊ตฌํ (์๊ฐ, ๊ณ์ฐ)
- [ ] ์์ด์ ํธ ๋ฃจํ ๋์ ํ์ธ
Day 3-5: ์์ง๋ด 3์ข
์์ฑ โฑ๏ธ ๊ฐ 3-4์๊ฐ
Day 3: ๋ด์ค ์์ง๋ด
# ai-agents/agents/news_collector.py
import requests
from bs4 import BeautifulSoup
from datetime import datetime
import json
class NewsCollector:
"""๋ค์ด๋ฒ ๋ด์ค ์์ง๋ด"""
def __init__(self, keywords: list):
self.keywords = keywords
self.headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
}
def collect(self, keyword: str, max_articles: int = 5) -> list:
"""ํค์๋๋ก ๋ด์ค ์์ง"""
url = f"https://search.naver.com/search.naver?where=news&query={keyword}"
try:
response = requests.get(url, headers=self.headers, timeout=10)
soup = BeautifulSoup(response.text, 'html.parser')
articles = []
news_items = soup.select('.news_tit')[:max_articles]
for item in news_items:
articles.append({
"title": item.get_text(),
"url": item.get('href'),
"keyword": keyword,
"collected_at": datetime.now().isoformat()
})
return articles
except Exception as e:
print(f"์์ง ์ค๋ฅ: {e}")
return []
def collect_all(self) -> list:
"""๋ชจ๋ ํค์๋ ์์ง"""
all_articles = []
for keyword in self.keywords:
articles = self.collect(keyword)
all_articles.extend(articles)
print(f"โ
'{keyword}' ์์ง ์๋ฃ: {len(articles)}๊ฑด")
return all_articles
def save_results(self, articles: list, filename: str = "news_results.json"):
"""๊ฒฐ๊ณผ ์ ์ฅ"""
with open(filename, 'w', encoding='utf-8') as f:
json.dump(articles, f, ensure_ascii=False, indent=2)
print(f"๐พ ์ ์ฅ ์๋ฃ: {filename}")
# ์ฌ์ฉ ์์
if __name__ == "__main__":
collector = NewsCollector(["AI ์์ด์ ํธ", "ChatGPT", "Claude AI"])
articles = collector.collect_all()
collector.save_results(articles)
Day 4: ๊ฒฝ์์ฌ ๋ชจ๋ํฐ๋ง๋ด
# ai-agents/agents/competitor_monitor.py
import requests, hashlib, json, os
from bs4 import BeautifulSoup
from datetime import datetime
class CompetitorMonitor:
"""๊ฒฝ์์ฌ ์น์ฌ์ดํธ ๋ณ๊ฒฝ ๊ฐ์ง๋ด"""
def __init__(self, targets: list):
"""targets: [{"name": "ํ์ฌ๋ช
", "url": "URL", "selector": "CSS์ ํ์"}]"""
self.targets = targets
self.headers = {"User-Agent": "Mozilla/5.0"}
self.cache_file = "competitor_cache.json"
self.cache = self._load_cache()
def _load_cache(self) -> dict:
if os.path.exists(self.cache_file):
with open(self.cache_file, 'r') as f:
return json.load(f)
return {}
def _save_cache(self):
with open(self.cache_file, 'w') as f:
json.dump(self.cache, f)
def _get_content_hash(self, url: str, selector: str) -> tuple:
try:
resp = requests.get(url, headers=self.headers, timeout=10)
soup = BeautifulSoup(resp.text, 'html.parser')
el = soup.select_one(selector)
text = el.get_text(strip=True) if el else ''
return hashlib.md5(text.encode()).hexdigest(), text
except Exception as e:
return None, str(e)
def check_all(self) -> list:
"""๋ณ๊ฒฝ๋ ๊ฒฝ์์ฌ ํ์ด์ง ๋ฐํ"""
changed = []
for t in self.targets:
new_hash, content = self._get_content_hash(t["url"], t["selector"])
old_hash = self.cache.get(t["url"])
if new_hash and new_hash != old_hash:
changed.append({"name": t["name"], "url": t["url"],
"content_preview": content[:200],
"detected_at": datetime.now().isoformat()})
self.cache[t["url"]] = new_hash
print(f"๐ด ๋ณ๊ฒฝ ๊ฐ์ง: {t['name']}")
else:
print(f"โ
๋ณ๊ฒฝ ์์: {t['name']}")
self._save_cache()
return changed
if __name__ == "__main__":
monitor = CompetitorMonitor([
{"name": "๊ฒฝ์์ฌA", "url": "https://competitor-a.com/pricing", "selector": ".price-table"},
{"name": "๊ฒฝ์์ฌB", "url": "https://competitor-b.com/blog", "selector": ".post-list"},
])
changes = monitor.check_all()
if changes:
print(f"\n๐จ {len(changes)}๊ฐ ๋ณ๊ฒฝ ๊ฐ์ง โ ํ
๋ ๊ทธ๋จ ๋ณด๊ณ ํ์")
Day 5: SNS ํค์๋ ํธ๋ํน๋ด
# ai-agents/agents/sns_keyword_tracker.py
import anthropic, os, json
from datetime import datetime
client = anthropic.Anthropic(api_key=os.getenv("ANTHROPIC_API_KEY"))
KEYWORDS = ["AI ์์ด์ ํธ", "Claude API", "์๋ํ ๋ด", "n8n"]
def analyze_trend_with_claude(keyword: str, sample_posts: list) -> dict:
"""Claude๋ก ํค์๋ ํธ๋ ๋ ๋ถ์"""
posts_text = "\n".join([f"- {p}" for p in sample_posts[:10]])
response = client.messages.create(
model="claude-haiku-4-5-20251001",
max_tokens=500,
messages=[{
"role": "user",
"content": f"""ํค์๋ '{keyword}'์ ๋ํ ์์
๋ฐ์์ ๋ถ์ํ์ธ์.
๊ฒ์๋ฌผ ์ํ:
{posts_text}
์๋ JSON ํ์์ผ๋ก๋ง ์๋ตํ์ธ์:
{{"sentiment": "๊ธ์ /์ค๋ฆฝ/๋ถ์ ", "trend": "์์น/์ ์ง/ํ๋ฝ",
"key_topics": ["์ฃผ์ ํ ํฝ1", "์ฃผ์ ํ ํฝ2"],
"action_suggestion": "์ฝํ
์ธ ์ ๋ต ์ ์ 1์ค"}}"""
}]
)
try:
return json.loads(response.content[0].text)
except:
return {"sentiment": "๋ถ์๋ถ๊ฐ", "trend": "๋ถ์๋ถ๊ฐ",
"key_topics": [], "action_suggestion": "์ฌ์๋ ํ์"}
def save_report(results: list):
filename = f"sns_trend_{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}")
if __name__ == "__main__":
results = []
for kw in KEYWORDS:
# ์ค์ ์ด์์์๋ Twitter/Naver API๋ก ๊ฒ์๋ฌผ ์์ง
sample = [f"{kw} ๊ด๋ จ ํฌ์คํธ {i}" for i in range(5)]
analysis = analyze_trend_with_claude(kw, sample)
results.append({"keyword": kw, **analysis})
print(f"'{kw}': {analysis['sentiment']} / {analysis['trend']}")
save_report(results)
Day 6-7: ๋ธ๋ก๊ทธ ์๋์์ฑ๋ด (์์ง โ ์์ฑ ํ์ดํ๋ผ์ธ)
# ai-agents/agents/blog_generator.py
import anthropic, os, json
from datetime import datetime
from pathlib import Path
client = anthropic.Anthropic(api_key=os.getenv("ANTHROPIC_API_KEY"))
def generate_blog_post(topic: str, keywords: list, tone: str = "์ ๋ฌธ์ ์ด๊ณ ์ค์ฉ์ ์ธ") -> dict:
"""Claude๋ก SEO ์ต์ ํ ๋ธ๋ก๊ทธ ํฌ์คํธ ์์ฑ"""
kw_str = ", ".join(keywords)
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=3000,
system=f"๋น์ ์ {tone} ๋ธ๋ก๊ทธ ์๊ฐ์
๋๋ค. SEO๋ฅผ ๊ณ ๋ คํด ํ๊ตญ์ด๋ก ์์ฑํฉ๋๋ค.",
messages=[{
"role": "user",
"content": f"""์ฃผ์ : {topic}
ํ์ ํค์๋: {kw_str}
์๋ ๊ตฌ์กฐ๋ก ๋ธ๋ก๊ทธ ํฌ์คํธ๋ฅผ ์์ฑํ์ธ์ (1500~2000์):
1. ์ ๋ชฉ (ํด๋ฆญ๋ฅ ๋์ ์ ๋ชฉ 3๊ฐ ์ ์)
2. ๋์
๋ถ (๋
์ ๊ณ ํต ํฌ์ธํธ ์ธ๊ธ, 3~4๋ฌธ์ฅ)
3. ๋ณธ๋ฌธ (H2 3๊ฐ, ๊ฐ ์น์
์ฝ๋/์์ ํฌํจ)
4. ๊ฒฐ๋ก (ํต์ฌ ์์ฝ + CTA)
5. ๋ฉํ ๋์คํฌ๋ฆฝ์
(160์ ์ดํ)
JSON ํ์์ผ๋ก ์๋ต:
{{"titles": ["์ ๋ชฉ1", "์ ๋ชฉ2", "์ ๋ชฉ3"],
"intro": "๋์
๋ถ",
"sections": [{{"h2": "์์ ๋ชฉ", "content": "๋ด์ฉ"}}],
"conclusion": "๊ฒฐ๋ก ",
"meta_description": "๋ฉํ"}}"""
}]
)
try:
return json.loads(response.content[0].text)
except:
return {"raw": response.content[0].text}
def save_blog_html(post: dict, topic: str):
"""๋ธ๋ก๊ทธ ํฌ์คํธ๋ฅผ HTML ํ์ผ๋ก ์ ์ฅ"""
output_dir = Path("blog_output")
output_dir.mkdir(exist_ok=True)
title = post.get("titles", [topic])[0]
date = datetime.now().strftime("%Y-%m-%d")
filename = output_dir / f"{date}_{topic[:20].replace(' ', '_')}.html"
sections_html = ""
for s in post.get("sections", []):
sections_html += f"{s['h2']}
\n{s['content']}
\n"
html = f"""
{title}
{title}
{post.get('intro', '')}
{sections_html}
๋ง๋ฌด๋ฆฌ
{post.get('conclusion', '')}
"""
filename.write_text(html, encoding='utf-8')
print(f"โ
๋ธ๋ก๊ทธ ์ ์ฅ: {filename}")
return str(filename)
if __name__ == "__main__":
post = generate_blog_post(
topic="AI ์์ด์ ํธ๋ก ์
๋ฌด ์๋ํํ๋ ๋ฒ",
keywords=["AI ์์ด์ ํธ", "Claude API", "์๋ํ", "ํ์ด์ฌ"]
)
save_blog_html(post, "AI์์ด์ ํธ_์๋ํ")
Week 2 (Day 8~14): ์์ด์ ํธ + HITL ํ
๋ ๊ทธ๋จ โฑ๏ธ ๊ฐ 3-4์๊ฐ
Day 8-9: ReAct ์์ด์ ํธ ์์ฑ
ReAct(Reasoning + Acting) ํจํด์ AI๊ฐ ์๊ฐํ๊ณ โ ํ๋ํ๊ณ โ ๊ด์ฐฐํ๊ณ โ ๋ค์ ์๊ฐํ๋ ๋ฃจํ๋ค. Claude์ Tool Use์ ์๋ฒฝํ ๋ง์๋จ์ด์ง๋ ๊ตฌ์กฐ๋ค.
# ai-agents/react_agent.py
import anthropic, os, json, requests
from datetime import datetime
client = anthropic.Anthropic(api_key=os.getenv("ANTHROPIC_API_KEY"))
# Tool ์ ์ 3์ข
TOOLS = [
{
"name": "web_search",
"description": "์ธํฐ๋ท์์ ์ต์ ์ ๋ณด๋ฅผ ๊ฒ์ํฉ๋๋ค",
"input_schema": {
"type": "object",
"properties": {"query": {"type": "string", "description": "๊ฒ์์ด"}},
"required": ["query"]
}
},
{
"name": "save_file",
"description": "ํ
์คํธ ๋ด์ฉ์ ํ์ผ๋ก ์ ์ฅํฉ๋๋ค",
"input_schema": {
"type": "object",
"properties": {
"filename": {"type": "string"},
"content": {"type": "string"}
},
"required": ["filename", "content"]
}
},
{
"name": "send_telegram",
"description": "ํ
๋ ๊ทธ๋จ์ผ๋ก ๋ฉ์์ง๋ฅผ ์ ์กํฉ๋๋ค",
"input_schema": {
"type": "object",
"properties": {"message": {"type": "string", "description": "์ ์กํ ๋ฉ์์ง"}},
"required": ["message"]
}
}
]
def web_search(query: str) -> str:
"""์ค์ ๊ฒ์ ๋์ ์๋ฎฌ๋ ์ด์
(์ด์ ์ SerpAPI ์ฐ๊ฒฐ)"""
return f"'{query}' ๊ฒ์ ๊ฒฐ๊ณผ: ๊ด๋ จ ๊ธฐ์ฌ 5๊ฑด ๋ฐ๊ฒฌ (์๋ฎฌ๋ ์ด์
)"
def save_file(filename: str, content: str) -> str:
with open(filename, 'w', encoding='utf-8') as f:
f.write(content)
return f"ํ์ผ ์ ์ฅ ์๋ฃ: {filename} ({len(content)}์)"
def send_telegram(message: str) -> str:
token = os.getenv("TELEGRAM_BOT_TOKEN")
chat_id = os.getenv("TELEGRAM_CHAT_ID")
if not token:
return "ํ
๋ ๊ทธ๋จ ๋ฏธ์ค์ (์๋ฎฌ๋ ์ด์
)"
url = f"https://api.telegram.org/bot{token}/sendMessage"
requests.post(url, json={"chat_id": chat_id, "text": message})
return "ํ
๋ ๊ทธ๋จ ์ ์ก ์๋ฃ"
TOOL_MAP = {"web_search": web_search, "save_file": save_file, "send_telegram": send_telegram}
def run_react_agent(task: str, max_iterations: int = 5) -> str:
"""ReAct ๋ฃจํ ์คํ"""
messages = [{"role": "user", "content": task}]
print(f"\n๐ค ํ์คํฌ: {task}")
for i in range(max_iterations):
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=2000,
tools=TOOLS,
messages=messages
)
if response.stop_reason == "end_turn":
result = response.content[0].text
print(f"\nโ
์๋ฃ: {result}")
return result
if response.stop_reason == "tool_use":
messages.append({"role": "assistant", "content": response.content})
tool_results = []
for block in response.content:
if block.type == "tool_use":
print(f" ๐ง [{i+1}] {block.name}({block.input})")
result = TOOL_MAP[block.name](**block.input)
print(f" ๐ค ๊ฒฐ๊ณผ: {result}")
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": result
})
messages.append({"role": "user", "content": tool_results})
return "์ต๋ ๋ฐ๋ณต ํ์ ์ด๊ณผ"
if __name__ == "__main__":
run_react_agent(
"AI ์์ด์ ํธ ํธ๋ ๋๋ฅผ ๊ฒ์ํ๊ณ , ์์ฝ ํ์ผ์ ์ ์ฅํ ๋ค ํ
๋ ๊ทธ๋จ์ผ๋ก ๋ณด๊ณ ํ์ธ์."
)
Day 10-11: Tool Use 3๊ฐ ์ค์ ์ฐ๊ฒฐ
# ai-agents/tools/tool_registry.py
# ์ค๋ฌด์์ ์ฐ๋ Tool 3์ข
์์ฑํ
import anthropic, os, json, smtplib
from email.mime.text import MIMEText
from pathlib import Path
client = anthropic.Anthropic(api_key=os.getenv("ANTHROPIC_API_KEY"))
# Tool 1: ํ์ผ ์ฝ๊ธฐ/์ฐ๊ธฐ
def read_file(path: str) -> str:
p = Path(path)
if not p.exists():
return f"ํ์ผ ์์: {path}"
return p.read_text(encoding='utf-8')
def write_file(path: str, content: str) -> str:
Path(path).write_text(content, encoding='utf-8')
return f"์ ์ฅ ์๋ฃ: {path}"
# Tool 2: ์ด๋ฉ์ผ ๋ฐ์ก (Gmail SMTP)
def send_email(to: str, subject: str, body: str) -> str:
gmail = os.getenv("GMAIL_ADDRESS")
password = os.getenv("GMAIL_APP_PASSWORD")
if not gmail:
return "Gmail ๋ฏธ์ค์ (์๋ฎฌ๋ ์ด์
)"
msg = MIMEText(body, 'plain', 'utf-8')
msg['Subject'] = subject
msg['From'] = gmail
msg['To'] = to
with smtplib.SMTP_SSL('smtp.gmail.com', 465) as smtp:
smtp.login(gmail, password)
smtp.send_message(msg)
return f"์ด๋ฉ์ผ ๋ฐ์ก: {to}"
# Tool 3: JSON ๋ฐ์ดํฐ ์ ์ฅ/์กฐํ
def save_data(key: str, value: str, store_file: str = "agent_store.json") -> str:
store = {}
if Path(store_file).exists():
store = json.loads(Path(store_file).read_text())
store[key] = {"value": value, "updated": __import__('datetime').datetime.now().isoformat()}
Path(store_file).write_text(json.dumps(store, ensure_ascii=False, indent=2))
return f"์ ์ฅ: {key}"
def get_data(key: str, store_file: str = "agent_store.json") -> str:
if not Path(store_file).exists():
return "๋ฐ์ดํฐ ์์"
store = json.loads(Path(store_file).read_text())
return store.get(key, {}).get("value", f"ํค ์์: {key}")
Day 12-14: HITL (Human-in-the-Loop) ํ
๋ ๊ทธ๋จ ์น์ธ
HITL์ AI๊ฐ ์ํํ ํ๋์ ํ๊ธฐ ์ ์ ์ฌ๋ ์น์ธ์ ๋ฐ๋ ํจํด์ด๋ค. "AI ํ์ฅ" ๊ตฌ์กฐ์ ํต์ฌ์ด๋ค.
# ai-agents/hitl_agent.py
import anthropic, os, requests, time, json
from datetime import datetime
client = anthropic.Anthropic(api_key=os.getenv("ANTHROPIC_API_KEY"))
TELEGRAM_TOKEN = os.getenv("TELEGRAM_BOT_TOKEN")
TELEGRAM_CHAT = os.getenv("TELEGRAM_CHAT_ID")
def telegram_send(text: str) -> int:
"""๋ฉ์์ง ์ ์ก ํ message_id ๋ฐํ"""
r = requests.post(
f"https://api.telegram.org/bot{TELEGRAM_TOKEN}/sendMessage",
json={"chat_id": TELEGRAM_CHAT, "text": text, "parse_mode": "HTML"}
)
return r.json().get("result", {}).get("message_id", 0)
def telegram_ask_approval(action_name: str, details: str, timeout: int = 300) -> bool:
"""ํ
๋ ๊ทธ๋จ์ผ๋ก ์น์ธ ์์ฒญ โ 5๋ถ ๋ด /yes ๋๋ /no ์๋ต ๋๊ธฐ"""
msg = f"""๐ค ์น์ธ ์์ฒญ
โโโโโโโโโโโโโโ
์์
: {action_name}
๋ด์ฉ: {details}
์๊ฐ: {datetime.now().strftime('%H:%M:%S')}
โโโโโโโโโโโโโโ
โ
/yes โ ์น์ธ โ /no โ ๊ฑฐ๋ถ
โฑ {timeout//60}๋ถ ๋ด ์๋ต ์์ผ๋ฉด ์๋ ๊ฑฐ๋ถ"""
msg_id = telegram_send(msg)
print(f"๐จ ์น์ธ ์์ฒญ ์ ์ก (msg_id={msg_id})")
# ํด๋ง: /yes ๋๋ /no ๋๊ธฐ
deadline = time.time() + timeout
last_update = 0
while time.time() < deadline:
r = requests.get(
f"https://api.telegram.org/bot{TELEGRAM_TOKEN}/getUpdates",
params={"offset": last_update + 1, "timeout": 10}
).json()
for update in r.get("result", []):
last_update = update["update_id"]
text = update.get("message", {}).get("text", "").strip().lower()
if text == "/yes":
telegram_send("โ
์น์ธ๋์์ต๋๋ค. ์คํํฉ๋๋ค.")
return True
if text == "/no":
telegram_send("โ ๊ฑฐ๋ถ๋์์ต๋๋ค. ์ค๋จํฉ๋๋ค.")
return False
telegram_send("โฐ ์๊ฐ ์ด๊ณผ โ ์๋ ๊ฑฐ๋ถ")
return False
def run_hitl_pipeline(task: str):
"""์ํ ์์
์ ๋ฐ๋์ ์ฌ๋ ์น์ธ ๋ฐ๋ ํ์ดํ๋ผ์ธ"""
print(f"๐ ํ์คํฌ ๋ถ์ ์ค: {task}")
# 1๋จ๊ณ: Claude๋ก ์คํ ๊ณํ ์๋ฆฝ
plan_resp = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1000,
messages=[{"role": "user",
"content": f"๋ค์ ํ์คํฌ์ ์คํ ๊ณํ์ 3๋จ๊ณ๋ก ์์ฝํ์ธ์: {task}"}]
)
plan = plan_resp.content[0].text
# 2๋จ๊ณ: ์ฌ๋ ์น์ธ ์์ฒญ
approved = telegram_ask_approval(
action_name=task[:50],
details=f"์คํ ๊ณํ:\n{plan[:300]}"
)
if not approved:
print("โ ์ฌ์ฉ์๊ฐ ๊ฑฐ๋ถ โ ์คํ ์ค๋จ")
return
# 3๋จ๊ณ: ์น์ธ ํ ์คํ
print("โ
์น์ธ ์๋ฃ โ ์คํ ์์")
result_resp = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=2000,
messages=[{"role": "user", "content": f"๋ค์ ํ์คํฌ๋ฅผ ์คํํ๊ณ ๊ฒฐ๊ณผ๋ฅผ ๋ณด๊ณ ํ์ธ์: {task}"}]
)
result = result_resp.content[0].text
telegram_send(f"๐ ์คํ ์๋ฃ\n\n{result[:500]}")
print(f"๊ฒฐ๊ณผ: {result}")
if __name__ == "__main__":
run_hitl_pipeline("๊ฒฝ์์ฌ 3๊ณณ ๊ฐ๊ฒฉ ๋ณ๋ ๋ถ์ ํ ์ด๋ฉ์ผ ๋ฐ์ก")
Week 3 (Day 15~21): ํ๋ก๋์
ํ๋ค์ค ๊ตฌ์ถ โฑ๏ธ ๊ฐ 2-3์๊ฐ
์์ด์ ํธ๊ฐ ๋ง๋ค์ด์ก๋ค. ์ด์ 24์๊ฐ ๋์๊ฐ๋ ์ ํฐ์ง๋ ๊ตฌ์กฐ๋ฅผ ๋ง๋ ๋ค.
Day 15-16: ์๋ฌ ํธ๋ค๋ง + ์๋ ์ฌ์๋
# ai-agents/utils/resilient_caller.py
import time, functools, logging
from typing import Callable, Any
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s [%(levelname)s] %(message)s',
handlers=[
logging.FileHandler("agent.log", encoding='utf-8'),
logging.StreamHandler()
]
)
logger = logging.getLogger(__name__)
def retry(max_attempts: int = 3, delay: float = 2.0, backoff: float = 2.0,
exceptions: tuple = (Exception,)):
"""์ง์ ๋ฐฑ์คํ ์ฌ์๋ ๋ฐ์ฝ๋ ์ดํฐ"""
def decorator(func: Callable) -> Callable:
@functools.wraps(func)
def wrapper(*args, **kwargs) -> Any:
wait = delay
for attempt in range(1, max_attempts + 1):
try:
return func(*args, **kwargs)
except exceptions as e:
if attempt == max_attempts:
logger.error(f"โ {func.__name__} ์ต์ข
์คํจ (์๋ {attempt}/{max_attempts}): {e}")
raise
logger.warning(f"โ ๏ธ {func.__name__} ์คํจ (์๋ {attempt}/{max_attempts}): {e} โ {wait:.1f}์ด ํ ์ฌ์๋")
time.sleep(wait)
wait *= backoff
return wrapper
return decorator
# ์ฌ์ฉ ์์
import anthropic, os
client = anthropic.Anthropic(api_key=os.getenv("ANTHROPIC_API_KEY"))
@retry(max_attempts=3, delay=2.0, exceptions=(anthropic.APIError, anthropic.RateLimitError))
def safe_claude_call(prompt: str, model: str = "claude-sonnet-4-6") -> str:
response = client.messages.create(
model=model, max_tokens=1000,
messages=[{"role": "user", "content": prompt}]
)
return response.content[0].text
# Day 17-18: ๊ตฌ์กฐํ ๋ก๊น
class AgentLogger:
"""์์ด์ ํธ ์คํ ๊ธฐ๋ก ์ ์ฉ ๋ก๊ฑฐ"""
def __init__(self, agent_name: str, log_file: str = "agent_runs.jsonl"):
self.agent_name = agent_name
self.log_file = log_file
def log(self, event: str, data: dict):
entry = {
"ts": __import__('datetime').datetime.now().isoformat(),
"agent": self.agent_name,
"event": event,
**data
}
with open(self.log_file, 'a', encoding='utf-8') as f:
f.write(__import__('json').dumps(entry, ensure_ascii=False) + '\n')
logger.info(f"[{self.agent_name}] {event}: {data}")
# ์ฌ์ฉ๋ฒ
# alog = AgentLogger("blog_generator")
# alog.log("start", {"task": "AI ํธ๋ ๋ ๋ธ๋ก๊ทธ ์์ฑ"})
# alog.log("complete", {"tokens_used": 1200, "file": "blog_2026.html"})
Day 19-21: 20ํญ๋ชฉ ๋ฐฐํฌ ์ ์ฒดํฌ๋ฆฌ์คํธ ์๋ํ
# ai-agents/utils/deployment_harness.py
import os, sys, subprocess
from pathlib import Path
CHECKS = [
# (์ค๋ช
, ๊ฒ์ฆ ํจ์)
("ANTHROPIC_API_KEY ์ค์ ๋จ", lambda: bool(os.getenv("ANTHROPIC_API_KEY"))),
("TELEGRAM_BOT_TOKEN ์ค์ ๋จ", lambda: bool(os.getenv("TELEGRAM_BOT_TOKEN"))),
("TELEGRAM_CHAT_ID ์ค์ ๋จ", lambda: bool(os.getenv("TELEGRAM_CHAT_ID"))),
(".env ํ์ผ ์กด์ฌ", lambda: Path(".env").exists()),
(".gitignore์ .env ํฌํจ", lambda: ".env" in Path(".gitignore").read_text() if Path(".gitignore").exists() else False),
("requirements.txt ์กด์ฌ", lambda: Path("requirements.txt").exists()),
("agent_store.json ์ ๊ทผ ๊ฐ๋ฅ", lambda: True), # ์ฒซ ์คํ ์ ์๋ ์์ฑ
("๋ก๊ทธ ํด๋ ์ฐ๊ธฐ ๊ฐ๋ฅ", lambda: Path("logs").mkdir(exist_ok=True) or True),
("Python ๋ฒ์ 3.9+", lambda: sys.version_info >= (3, 9)),
("anthropic ํจํค์ง ์ค์น๋จ", lambda: __import__('importlib').util.find_spec("anthropic") is not None),
("requests ํจํค์ง ์ค์น๋จ", lambda: __import__('importlib').util.find_spec("requests") is not None),
("bs4 ํจํค์ง ์ค์น๋จ", lambda: __import__('importlib').util.find_spec("bs4") is not None),
("ํ๋์ฝ๋ฉ๋ ํค ์์", lambda: not any("sk-ant" in Path(f).read_text() for f in Path(".").rglob("*.py") if Path(f).is_file())),
("๊ธฐ์กด ๋ก๊ทธ ํ์ผ ์ ์", lambda: True),
("API ์๋ต ์ ์ (ping)", lambda: __import__('anthropic').Anthropic(api_key=os.getenv("ANTHROPIC_API_KEY")).messages.create(model="claude-haiku-4-5-20251001", max_tokens=5, messages=[{"role":"user","content":"hi"}]).content[0].text is not None),
("agent_runs.jsonl ํฌ๋งท ์ ์", lambda: True),
("ํ
๋ ๊ทธ๋จ ๋ด ์ฐ๊ฒฐ ๊ฐ๋ฅ", lambda: bool(os.getenv("TELEGRAM_BOT_TOKEN"))),
("์ถ๋ ฅ ๋๋ ํ ๋ฆฌ ์กด์ฌ", lambda: Path("output").mkdir(exist_ok=True) or True),
("ํ์์์ ์ค์ ํ์ธ", lambda: True),
("๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋ < 80%", lambda: __import__('psutil').virtual_memory().percent < 80 if __import__('importlib').util.find_spec("psutil") else True),
]
def run_harness() -> bool:
print("=" * 50)
print(" ๋ฐฐํฌ ์ ์ฒดํฌ๋ฆฌ์คํธ (20ํญ๋ชฉ)")
print("=" * 50)
passed = failed = 0
for desc, check in CHECKS:
try:
ok = check()
except Exception as e:
ok = False
symbol = "โ
" if ok else "โ"
print(f"{symbol} {desc}")
if ok: passed += 1
else: failed += 1
print("=" * 50)
print(f"๊ฒฐ๊ณผ: {passed}/20 ํต๊ณผ | ์คํจ: {failed}๊ฐ")
if failed > 0:
print("โ ๋ฐฐํฌ ์ค๋จ โ ์คํจ ํญ๋ชฉ ์์ ํ ์ฌ์๋")
return False
print("โ
๋ชจ๋ ์ฒดํฌ ํต๊ณผ โ ๋ฐฐํฌ ์งํ ๊ฐ๋ฅ")
return True
if __name__ == "__main__":
ok = run_harness()
sys.exit(0 if ok else 1)
Week 4 (Day 22~30): ์ค์ผ์คํธ๋ ์ดํฐ + KPI ์๋ ๋ณด๊ณ โฑ๏ธ ์ง์ค ์์ฑ์ฃผ
Day 22-24: ์ค์ผ์คํธ๋ ์ดํฐ ์์ฑ
# ai-agents/daily_orchestrator.py
"""๋งค์ผ 07:00 ์๋ ์คํ โ Windows ์์
์ค์ผ์ค๋ฌ ๋ฑ๋ก"""
import subprocess, os, json, time
from datetime import datetime
from pathlib import Path
LOG_DIR = Path("logs")
LOG_DIR.mkdir(exist_ok=True)
log_file = LOG_DIR / f"orchestrator_{datetime.now().strftime('%Y-%m-%d')}.log"
def log(msg: str):
ts = datetime.now().strftime("%H:%M:%S")
line = f"[{ts}] {msg}"
print(line)
with open(log_file, 'a', encoding='utf-8') as f:
f.write(line + '\n')
def run_step(name: str, script: str, timeout: int = 300) -> bool:
log(f"โถ STEP ์์: {name}")
try:
result = subprocess.run(
["python", script],
capture_output=True, text=True, timeout=timeout, encoding='utf-8'
)
if result.returncode == 0:
log(f"โ
STEP ์๋ฃ: {name}")
return True
else:
log(f"โ STEP ์คํจ: {name}\n{result.stderr[:500]}")
return False
except subprocess.TimeoutExpired:
log(f"โฐ STEP ํ์์์: {name}")
return False
except Exception as e:
log(f"๐ฅ STEP ์ค๋ฅ: {name} โ {e}")
return False
def send_daily_report(results: dict):
"""ํ๋ฃจ ์คํ ๊ฒฐ๊ณผ๋ฅผ ํ
๋ ๊ทธ๋จ์ผ๋ก ๋ณด๊ณ """
import requests
token = os.getenv("TELEGRAM_BOT_TOKEN")
chat = os.getenv("TELEGRAM_CHAT_ID")
if not token: return
icons = {True: "โ
", False: "โ"}
lines = [f"๐ ์ผ์ผ ์ค์ผ์คํธ๋ ์ดํฐ ๋ฆฌํฌํธ",
f"๋ ์ง: {datetime.now().strftime('%Y-%m-%d')}",
"โโโโโโโโโโโโโโ"]
for step, ok in results.items():
lines.append(f"{icons[ok]} {step}")
total = len(results)
passed = sum(results.values())
lines += ["โโโโโโโโโโโโโโ", f"๊ฒฐ๊ณผ: {passed}/{total} ์ฑ๊ณต"]
requests.post(
f"https://api.telegram.org/bot{token}/sendMessage",
json={"chat_id": chat, "text": "\n".join(lines), "parse_mode": "HTML"}
)
PIPELINE = [
("๋ด์ค ์์ง", "agents/news_collector.py"),
("๊ฒฝ์์ฌ ๋ชจ๋ํฐ๋ง", "agents/competitor_monitor.py"),
("SNS ํธ๋ ๋", "agents/sns_keyword_tracker.py"),
("๋ธ๋ก๊ทธ ์์ฑ", "agents/blog_generator.py"),
("์ด๋ฉ์ผ ๋ฐ์ก", "agents/email_sender.py"),
]
if __name__ == "__main__":
log("๐ ์ค์ผ์คํธ๋ ์ดํฐ ์์")
results = {}
for name, script in PIPELINE:
results[name] = run_step(name, script)
time.sleep(2)
send_daily_report(results)
log("๐ ์ค์ผ์คํธ๋ ์ดํฐ ์ข
๋ฃ")
Day 25-27: KPI ๋์๋ณด๋ (HTML)
# ai-agents/kpi_dashboard.py
"""agent_runs.jsonl์ ์ฝ์ด KPI HTML ๋์๋ณด๋ ์์ฑ"""
import json
from pathlib import Path
from datetime import datetime, timedelta
from collections import defaultdict
def load_runs(log_file: str = "agent_runs.jsonl") -> list:
if not Path(log_file).exists():
return []
with open(log_file, 'r', encoding='utf-8') as f:
return [json.loads(line) for line in f if line.strip()]
def calc_kpi(runs: list) -> dict:
"""ํต์ฌ KPI ๊ณ์ฐ"""
last_7 = [r for r in runs
if r.get("ts", "") >= (datetime.now() - timedelta(days=7)).isoformat()]
agents = defaultdict(lambda: {"total": 0, "success": 0, "tokens": 0})
for r in last_7:
a = agents[r.get("agent", "unknown")]
a["total"] += 1
if r.get("event") == "complete":
a["success"] += 1
a["tokens"] += r.get("tokens_used", 0)
return {
"period": "์ต๊ทผ 7์ผ",
"total_runs": len(last_7),
"agents": dict(agents),
"success_rate": sum(a["success"] for a in agents.values()) /
max(sum(a["total"] for a in agents.values()), 1) * 100
}
def format_report(kpi: dict) -> str:
"""ํ
์คํธ KPI ๋ฆฌํฌํธ ์์ฑ (ํฐ๋ฏธ๋ ์ถ๋ ฅ + ํ์ผ ์ ์ฅ์ฉ)"""
sep = "=" * 50
lines = [
sep,
" AI ์์ด์ ํธ KPI ๋ฆฌํฌํธ",
f" ๊ธฐ๊ฐ: {kpi['period']}",
f" ์์ฑ: {datetime.now().strftime('%Y-%m-%d %H:%M')}",
sep,
f" ์ด ์คํ: {kpi['total_runs']}ํ",
f" ์ฑ๊ณต๋ฅ : {kpi['success_rate']:.1f}%",
f" ํ์ฑ ์์ด์ ํธ: {len(kpi['agents'])}๊ฐ",
"-" * 50,
]
for agent, data in kpi["agents"].items():
rate = data["success"] / max(data["total"], 1) * 100
lines.append(f" {agent:<20} {data['total']:>4}ํ {rate:>5.1f}% {data['tokens']:>8,} ํ ํฐ")
lines.append(sep)
return "\n".join(lines)
if __name__ == "__main__":
runs = load_runs()
kpi = calc_kpi(runs)
report = format_report(kpi)
print(report)
# JSON ์ ์ฅ (๋ค๋ฅธ ๋๊ตฌ์์ ์ฝ๊ธฐ ์ฌ์ด ํ์)
out = Path(f"kpi_{datetime.now().strftime('%Y%m%d')}.json")
out.write_text(json.dumps(kpi, ensure_ascii=False, indent=2), encoding='utf-8')
print(f"\nโ
KPI JSON ์ ์ฅ: {out}")
Day 28-30: ์๋ KPI ๋ณด๊ณ + ์ต์ข
๋ฐฐํฌ
๐
์๋ํ ์ค์ผ์ค ์ค๊ณ ์์น
Day 28-30์ ํต์ฌ์ "์ธ์ , ๋ฌด์์, ์ด๋ป๊ฒ ์๋ ์คํํ ์ง" ๊ฒฐ์ ํ๋ ๊ฒ์
๋๋ค.
์ถ์ฒ ์ค์ผ์ค ๊ตฌ์กฐ:
โข ๋งค์ผ 07:00 โ ์์ง๋ด + ์์ฑ๋ด + ์ค์ผ์คํธ๋ ์ดํฐ ์์ฐจ ์คํ
โข ๋งค์ฃผ ์์์ผ 09:00 โ ์ฃผ๊ฐ KPI ํ
๋ ๊ทธ๋จ ๋ณด๊ณ
โข ๋งค์ 1์ผ โ ๋น์ฉ ๊ฒฐ์ฐ + ๋ค์ ๋ฌ ๋ชฉํ ์ค์
์ค์ผ์ค๋ฌ ์ ํ ๊ธฐ์ค:
โข Windows: ์์
์ค์ผ์ค๋ฌ(Task Scheduler) โ GUI๋ก ๋ฑ๋กํ๊ฑฐ๋ schtasks ๋ช
๋ น ํ์ฉ
โข Mac/Linux: cron โ crontab -e๋ก ํธ์ง
โข ํด๋ผ์ฐ๋ ๋ฐฐํฌ ์: GitHub Actions schedule, Railway cron, n8n Schedule trigger ๊ถ์ฅ
KPI ๋ณด๊ณ ๋ kpi_dashboard.py๊ฐ agent_runs.jsonl์ ์ฝ์ด ์ฑ๊ณต๋ฅ ยทํ ํฐ ์ฌ์ฉ๋ยท์์ด์ ํธ๋ณ ํต๊ณ๋ฅผ ๊ณ์ฐํ๊ณ , ํ
๋ ๊ทธ๋จ Bot API(sendMessage)๋ก ์ ์กํ๋ ๊ตฌ์กฐ์
๋๋ค.
Day 30 ์ต์ข
์ ๊ฒ โ 1์ธ AI ์ด์ํ ์์ฑ ํ์ธ:
โ deployment_harness.py ์คํ โ 20ํญ๋ชฉ ์ ์ฒด ํต๊ณผ
โ ์ค์ผ์ค๋ฌ ๋ฑ๋ก ํ์ธ (๋งค์ผ 07:00 ์ค์ผ์คํธ๋ ์ดํฐ ์๋ ์์)
โ ํ
๋ ๊ทธ๋จ์์ KPI ๋ณด๊ณ ์์ ํ์ธ
30์ผ ๋ก๋๋งต ์๋ฃ ์ฒดํฌ๋ฆฌ์คํธ:
โก ์์ง๋ด 3๊ฐ โ ๋งค์ผ ์๋ ์คํ ์ค
โก ์์ฑ๋ด 2๊ฐ โ ๋ธ๋ก๊ทธยทSNS ์ด์ ์๋ ์์ฑ ์ค
โก ์์ด์ ํธ 1๊ฐ โ Tool Use + HITL ํ
๋ ๊ทธ๋จ ์ฐ๊ฒฐ ์๋ฃ
โก ์ค์ผ์คํธ๋ ์ดํฐ โ 07:00 Windows ์์
์ค์ผ์ค๋ฌ ๋ฑ๋ก ์๋ฃ
โก ๋น์ฉ ๋ชจ๋ํฐ๋ง โ ์ ์์ฐ ์๋ฆผ ์ค์ ์๋ฃ
"30์ผ ํ ๋น์ ์ ๋ ์ด์ 'AI๋ฅผ ์จ๋ณด๊ณ ์ถ์ ์ฌ๋'์ด ์๋๋ค. AI ์์คํ
์ ์ด์ฉํ๋ ์ฌ๋์ด๋ค."
โถ
30์ผ ์์ฑ ์ฑ๋ฆฐ์ง โ AI์ฌ๋ฅ๊พผ๊ณผ ํจ๊ป ๋งค์ผ ์ง๋ ์ฒดํฌ
๐ AI์ฌ๋ฅ๊พผ ์ฑ๋์์ ๋ดยท์์ด์ ํธ ์ค์ ์ด์ ์์ ์์ฒญ ๊ฐ๋ฅ