leedami's picture
Deploy from Team Script
41cc6f7 verified
import os
import json
import openai
from openai import OpenAI # 🦁 Use Sync Client
from dotenv import load_dotenv
from abc import ABC, abstractmethod
# 🦁 Debugging Environment Loading
print(f"🦁 [Brain Init] Current CWD: {os.getcwd()}")
load_dotenv()
load_dotenv(os.path.join(os.path.dirname(__file__), "../../../.env"))
API_KEY = os.getenv("OPENAI_API_KEY")
if API_KEY:
print(f"βœ… [Brain Init] API Key Found: {API_KEY[:5]}***")
client = OpenAI(api_key=API_KEY) # 🦁 Sync Client
else:
print("❌ [Brain Init] API Key NOT FOUND in environment!")
client = None
class PersonaStrategy(ABC):
@abstractmethod
def get_query_gen_prompt(self, history_txt): pass
@abstractmethod
def get_final_answer_prompt(self, context, refined_context, trend, history_txt): pass
@abstractmethod
def get_chat_only_prompt(self, history_txt): pass
@abstractmethod
def get_suggestion_prompt(self, path, page_context): pass
class NyangPersona(PersonaStrategy):
def get_query_gen_prompt(self, history_txt):
return f"당신은 졜고의 μƒν’ˆ 검색 μ „λž΅κ°€ 'λƒ₯이'μž…λ‹ˆλ‹€. [이전 λŒ€ν™”]λ₯Ό μ°Έκ³ ν•˜μ—¬ μ‚¬μš©μžμ˜ ν˜„μž¬ 질문 μ˜λ„λ₯Ό νŒŒμ•…ν•˜μ„Έμš”. 1μ°¨ 검색 결과의 [κ΅°μ§‘ 뢄석] λ‚΄μš©μ„ 보고, 정보가 λΆ€μ‘±ν•˜λ‹€λ©΄ **μ •λ°€ 검색 ν‚€μ›Œλ“œ 3개**λ₯Ό μƒμ„±ν•˜μ„Έμš”. λ§Œμ•½ μž‘λ‹΄μ΄λΌλ©΄ 빈 리슀트 []λ₯Ό λ°˜ν™˜ν•˜μ„Έμš”.\n\n[이전 λŒ€ν™”]\n{history_txt}\n\n[μ œμ•½ 사항]\n좜λ ₯은 였직 JSON 리슀트 ν˜•μ‹μ΄μ–΄μ•Ό ν•©λ‹ˆλ‹€. 예: [\"ν‚€μ›Œλ“œ1\", \"ν‚€μ›Œλ“œ2\"]"
def get_final_answer_prompt(self, context, refined_context, trend, history_txt):
return f"""μ§€λ°°μΈλ‹˜, λ°˜κ°‘λ‹€λƒ₯! λƒ₯이가 졜고의 κΏ€ν…œ 리슀트λ₯Ό κ°€μ Έμ™”λ‹€λƒ₯! 🦁✨🐾
당신은 '닀이따λƒ₯' μ‡Όν•‘λͺ°μ˜ AI λΉ„μ„œ 'λƒ₯이'μž…λ‹ˆλ‹€.
[이전 λŒ€ν™”]
{history_txt}
[μ§€μΉ¨]
1. [데이터]에 μžˆλŠ” μƒν’ˆλ“€μ„ λ°”νƒ•μœΌλ‘œ μΆ”μ²œν•΄μ£Όμ„Έμš”.
2. **μƒν’ˆλͺ…, 가격**을 μ •ν™•ν•˜κ²Œ μ–ΈκΈ‰ν•˜μ„Έμš”.
3. μƒν’ˆλͺ…은 λ°˜λ“œμ‹œ **[μƒν’ˆλͺ…](링크)** ν˜•μ‹μ˜ λ§ˆν¬λ‹€μš΄ 링크둜 μž‘μ„±ν•˜μ—¬ 클릭 μ‹œ 이동할 수 있게 ν•˜μ„Έμš”. (예: [λ§›μžˆλŠ” μΈ„λ₯΄](/product/123))
4. λ§νˆ¬λŠ” 무쑰건 '~λ‹€λƒ₯', '~λƒ₯'μž…λ‹ˆλ‹€.
5. μž¬κ³ κ°€ μžˆλ‹€λ©΄ "μ§€κΈˆ λ°”λ‘œ ꡬ맀 κ°€λŠ₯ν•˜λ‹€λƒ₯!" 이라고 λ§λΆ™μ΄μ„Έμš”.
[데이터]:
{context}
[μ •λ°€ 데이터]:
{refined_context}
[νŠΈλ Œλ“œ]:
{trend}"""
def get_chat_only_prompt(self, history_txt):
return f"μ§€λ°°μΈλ‹˜κ³Ό 즐겁게 μˆ˜λ‹€λ₯Ό λ– λŠ” AI 고양이 'λƒ₯이'μž…λ‹ˆλ‹€. λ§νˆ¬λŠ” '~λ‹€λƒ₯'μž…λ‹ˆλ‹€.\n\n[이전 λŒ€ν™”]\n{history_txt}"
def get_suggestion_prompt(self, path, page_context):
return f"""
당신은 λ°˜λ €λ™λ¬Ό μ‡Όν•‘λͺ°μ˜ AI λΉ„μ„œ 'λƒ₯이'μž…λ‹ˆλ‹€.
μ‚¬μš©μžκ°€ ν˜„μž¬ '{path}' νŽ˜μ΄μ§€λ₯Ό 보고 μžˆμŠ΅λ‹ˆλ‹€.
λ§₯락: {page_context}
이 μƒν™©μ—μ„œ μ‚¬μš©μžκ°€ κΆκΈˆν•΄ν•  λ§Œν•œ **질문 3κ°€μ§€**와 그에 λŒ€ν•œ **μ„ΌμŠ€ μžˆλŠ” λ‹΅λ³€**을 미리 μ€€λΉ„ν•΄μ£Όμ„Έμš”.
닡변은 λƒ₯이 말투(~λ‹€λƒ₯)둜 μ§§κ³  ν•΅μ‹¬λ§Œ 전달해야 ν•©λ‹ˆλ‹€.
[좜λ ₯ ν˜•μ‹]
λ°˜λ“œμ‹œ JSON 리슀트 ν˜•νƒœλ‘œ μž‘μ„±ν•˜μ„Έμš”.
μ˜ˆμ‹œ:
[
{{"question": "배솑은 μ–Έμ œ μΆœλ°œν•΄?", "answer": "μ˜€ν›„ 3μ‹œ μ „ 주문은 당일 μΆœλ°œν•œλ‹€λƒ₯! πŸš€"}},
{{"question": "이거 μœ ν†΅κΈ°ν•œ 넉넉해?", "answer": "κ±±μ •λ§ˆλΌλƒ₯! 졜근 제쑰된 μ‹ μ„ ν•œ μƒν’ˆλ§Œ 보낸닀λƒ₯."}}
]
"""
class BrainHub:
def __init__(self, strategy: PersonaStrategy):
self.strategy = strategy
def generate_search_queries(self, query, nodes, centroids, history=[]):
if not client: return []
history_txt = "\n".join([f"User: {h['user']}\nNyang: {h['assistant']}" for h in history[-7:]])
cluster_txt = "\n".join([f"- κ΅°μ§‘ {cid}: {info.get('summary', '정보 μ—†μŒ')} (크기: {info['size']})" for cid, info in centroids.items()])
context_summary = "\n".join([f"- {n['title']}" for n in nodes[:5]])
messages = [
{"role": "system", "content": self.strategy.get_query_gen_prompt(history_txt)},
{"role": "user", "content": f"질문: {query}\n\n[1μ°¨ 검색 κ²°κ³Ό μš”μ•½]\n{context_summary}\n\n[κ΅°μ§‘ 뢄석 κ²°κ³Ό]\n{cluster_txt}"}
]
try:
response = client.chat.completions.create(model="gpt-4o-mini", messages=messages, temperature=0.3)
content = response.choices[0].message.content
if "```" in content: content = content.replace("```json", "").replace("```", "")
return json.loads(content)
except: return []
def extract_keywords(self, query):
if not client: return []
system_prompt = """
당신은 검색 쿼리 μ΅œμ ν™” μ „λ¬Έκ°€μž…λ‹ˆλ‹€.
μ‚¬μš©μžμ˜ μ§ˆλ¬Έμ—μ„œ **λ°μ΄ν„°λ² μ΄μŠ€ 검색에 μ‚¬μš©ν•  핡심 ν‚€μ›Œλ“œ 3~4개**λ₯Ό μΆ”μΆœν•˜μ„Έμš”.
[μ€‘μš” μ§€μΉ¨]
1. **λ°˜λ“œμ‹œ 볡합λͺ…사λ₯Ό κ°œλ³„ λ‹¨μ–΄λ‘œ λΆ„λ¦¬ν•˜μ„Έμš”.** (예: "μŠ΅μ‹μ‚¬λ£Œ" -> ["μŠ΅μ‹", "μ‚¬λ£Œ"], "고양이간식" -> ["고양이", "간식"])
2. λΈŒλžœλ“œλͺ…, μƒν’ˆ μ’…λ₯˜, 핡심 속성 μœ„μ£Όλ‘œ λ½‘μœΌμ„Έμš”.
3. 띄어쓰기가 없어도 의미 λ‹¨μœ„λ‘œ μͺΌκ°œμ•Ό 검색이 잘 λ©λ‹ˆλ‹€.
[좜λ ₯ ν˜•μ‹]
JSON 리슀트만 λ°˜ν™˜ν•˜μ„Έμš”. 예: ["λ‘œμ–„μΊλ‹Œ", "μŠ΅μ‹", "μ‚¬λ£Œ", "λ‹€μ΄μ–΄νŠΈ"]
"""
messages = [
{"role": "system", "content": system_prompt},
{"role": "user", "content": query}
]
try:
response = client.chat.completions.create(model="gpt-4o-mini", messages=messages, temperature=0.3)
content = response.choices[0].message.content
if "```" in content: content = content.replace("```json", "").replace("```", "").strip()
return json.loads(content)
except: return []
def generate_final_answer(self, query, nodes, refined_nodes, top_tokens, centroids, history=[]):
if not client: return "API ν‚€κ°€ μ—†μ–΄μ„œ λƒ₯이가 말을 λͺ» ν•˜κ² λ‹€λƒ₯... 😿"
history_txt = "\n".join([f"User: {h['user']}\nNyang: {h['assistant']}" for h in history[-7:]])
def format_nodes(n_list):
txt = ""
for i, n in enumerate(n_list):
src = "✨[μžμ‚¬λͺ°]" if n.get('source') == 'homepage' else "[지식]"
price = n.get('price', 0)
brand = n.get('brand', '') or n.get('maker', 'Unknown')
category = n.get('category', '') or n.get('main_category', '')
content = n.get('content', '')
stock = n.get('stock', 0)
reviews = n.get('review_count', 0)
pet = n.get('pet_type', 'all')
rel_link = n.get('link', '')
if rel_link.startswith("/") and not rel_link.startswith("http"):
full_link = f"http://localhost:3000{rel_link}"
else:
full_link = rel_link
txt += f"""
[ID:{n.get('id', '?')}] {i+1}. {src} {n['title']}
- 가격: {price}원 | λΈŒλžœλ“œ: {brand} | μΉ΄ν…Œκ³ λ¦¬: {category}
- λŒ€μƒ: {pet} | 재고: {stock}개 | 리뷰수: {reviews}개
- 상세섀λͺ…: {content[:200]}...
- 링크: <{full_link}>
"""
return txt
context_txt = format_nodes(nodes)
inventory_txt = format_nodes(refined_nodes)
trend_txt = f"ν‚€μ›Œλ“œ: {', '.join([t[0] for t in top_tokens])}\nκ΅°μ§‘: {', '.join([info.get('summary', '') for info in centroids.values()])}"
final_prompt = f"""μ§€λ°°μΈλ‹˜, λ°˜κ°‘λ‹€λƒ₯! λƒ₯이가 μ§‘μ‚¬λ‹˜μ„ μœ„ν•œ λ§žμΆ€ν˜• '프리미엄 리포트'λ₯Ό μ™„μ„±ν–ˆλ‹€λƒ₯! 🦁✨🐾
당신은 λ°˜λ €λ™λ¬Ό μš©ν’ˆμ˜ λͺ¨λ“  것을 꿰뚫고 μžˆλŠ” '슈퍼 점원 λƒ₯이'μž…λ‹ˆλ‹€.
[이전 λŒ€ν™” (졜근 7ν„΄)]
{history_txt}
[μ§€μΉ¨]
1. **[μ‹€μ œ 판맀 μƒν’ˆ]** λͺ©λ‘μ— μžˆλŠ” λͺ¨λ“  μƒν’ˆμ„ μš°μ„ μ μœΌλ‘œ μΆ”μ²œν•˜μ„Έμš”.
2. **[λ°°κ²½ 지식]**μ—μ„œ κ°€μž₯ 적합도가 높은 'λͺ…ν’ˆ/인기 μƒν’ˆ' 1~2개λ₯Ό μΆ”κ°€λ‘œ μ—„μ„ ν•˜μ—¬ μΆ”μ²œν•˜μ„Έμš”.
3. 각 μƒν’ˆμ„ μΆ”μ²œν•  λ•ŒλŠ” λ‹¨μˆœνžˆ λ‚˜μ—΄ν•˜μ§€ 말고, **제곡된 상세섀λͺ…κ³Ό 배경지식을 ν™œμš©ν•΄ μ•„μ£Ό ν’λΆ€ν•˜κ³  μ „λ¬Έμ μœΌλ‘œ μ„€λͺ…**ν•˜μ„Έμš”. (예: μ„±λΆ„μ˜ μž₯점, κΈ°λŒ€ 효과 λ“±)
4. **[ν•„μˆ˜] λͺ¨λ“  μžμ‚¬λͺ° μƒν’ˆμ€ λ°˜λ“œμ‹œ `[μƒν’ˆλͺ…](/product/ID)` ν˜•μ‹μ˜ λ§ˆν¬λ‹€μš΄ 링크λ₯Ό 포함해야 ν•©λ‹ˆλ‹€.**
- 링크가 μ—†λŠ” μΆ”μ²œμ€ λ¬΄νš¨μž…λ‹ˆλ‹€. 데이터에 제곡된 링크 μ£Όμ†Œλ₯Ό 100% ν™œμš©ν•˜μ„Έμš”.
5. μ™ΈλΆ€ μƒν’ˆ(λ°°κ²½ 지식)은 "λƒ₯이 λ„μ„œκ΄€μ—μ„œ 찾은 λͺ…ν’ˆ μƒν’ˆμ΄λ‹€λƒ₯!" 같은 μˆ˜μ‹μ–΄λ₯Ό λΆ™μ—¬μ£Όμ„Έμš”.
6. λ§νˆ¬λŠ” λŠ₯μˆ™ν•˜κ³  λ˜‘λ˜‘ν•œ '~λ‹€λƒ₯', '~λƒ₯'μž…λ‹ˆλ‹€.
[λ°°κ²½ 지식 (지식 ν™•μž₯ 및 μ™ΈλΆ€ μΆ”μ²œμš©)]:
{context_txt}
[μ‹€μ œ 판맀 μƒν’ˆ (우리 κ°€κ²Œ 재고)]:
{inventory_txt}
[νŠΈλ Œλ“œ]:
{trend_txt}"""
messages = [{"role": "system", "content": final_prompt}, {"role": "user", "content": query}]
try:
print("🦁 Calling GPT for Final Answer (Sync Mode)...")
response = client.chat.completions.create(model="gpt-4o-mini", messages=messages, temperature=0.7, timeout=45)
print("🦁 GPT Response Received!")
return response.choices[0].message.content
except Exception as e: return f"μ—λŸ¬λΌλƒ₯: {e}"
def generate_quick_chat(self, query, history=[]):
if not client: return "음... μž μ‹œλ§Œ 기닀렀달라λƒ₯!"
history_txt = "\n".join([f"User: {h['user']}\nNyang: {h['assistant']}" for h in history[-3:]])
system_prompt = """
당신은 λ°˜λ €λ™λ¬Ό μ „λ¬Έ μ‡Όν•‘λͺ° '닀이따λƒ₯'의 AI 점원 'λƒ₯이'μž…λ‹ˆλ‹€.
μ‚¬μš©μžμ˜ μ§ˆλ¬Έμ— λŒ€ν•΄ μ¦‰μ‹œ λ‹΅λ³€ν•˜λ˜, λ‹€μŒ ꡬ쑰λ₯Ό μ§€μΌœμ£Όμ„Έμš”:
1. 곡감과 일반 지식: ν•΄λ‹Ή μ œν’ˆκ΅°(예: μ‚¬λ£Œ, λͺ¨λž˜, μž₯λ‚œκ°)에 λŒ€ν•œ 일반적인 νŠΉμ§• μ„€λͺ….
2. 집사 μ£Όμ˜μ‚¬ν•­: 고양이가 ν•΄λ‹Ή μ œν’ˆμ„ μ‚¬μš©ν•  λ•Œ 집사가 κΌ­ μ•Œμ•„μ•Ό ν•  μœ μ˜μ‚¬ν•­μ΄λ‚˜ κΏ€νŒ 1~2κ°€μ§€.
3. μ „ν™˜ 멘트: λ§ˆμ§€λ§‰μ€ λ°˜λ“œμ‹œ "μ§€λ°°μΈλ‹˜μ„ μœ„ν•΄ 우리 λ§€μž₯에 λ”± λ§žλŠ” 물건이 μžˆλŠ”μ§€ λƒ₯이가 μ–Όλ₯Έ μ°Ύμ•„λ³΄κ² λ‹€μ˜Ή! 🐾"둜 끝낼 것.
λ§νˆ¬λŠ” 무쑰건 '~λ‹€λƒ₯', '~옹'을 μ„žμ–΄ κ·€μ—½κ³  μ „λ¬Έμ μœΌλ‘œ λŒ€λ‹΅ν•˜μ„Έμš”.
"""
messages = [
{"role": "system", "content": system_prompt},
{"role": "user", "content": f"[이전 λŒ€ν™”]\n{history_txt}\n\n[μ‚¬μš©μž 질문]\n{query}"}
]
try:
response = client.chat.completions.create(model="gpt-4o-mini", messages=messages, temperature=0.7, max_tokens=250)
return response.choices[0].message.content
except: return "μ•Œκ² λ‹€λƒ₯! μž μ‹œλ§Œ κΈ°λ‹€λ €μ£Όλ©΄ 찾아보겠닀λƒ₯!"
def generate_suggestions(self, path):
if not client:
return [{"label": "μ•ˆλ…•?", "cached_answer": "λ°˜κ°‘λ‹€λƒ₯!"}]
context = "μ‡Όν•‘λͺ° 메인 λ‘œλΉ„"
if "product" in path: context = "νŠΉμ • μƒν’ˆ 상세 νŽ˜μ΄μ§€. ꡬ맀λ₯Ό κ³ λ―Ό 쀑."
elif "cart" in path: context = "μž₯λ°”κ΅¬λ‹ˆ νŽ˜μ΄μ§€. 결제 직전."
elif "category" in path: context = "μΉ΄ν…Œκ³ λ¦¬ λͺ©λ‘ νŽ˜μ΄μ§€. 아이쇼핑 쀑."
elif "login" in path: context = "둜그인/νšŒμ›κ°€μž… νŽ˜μ΄μ§€."
messages = [
{"role": "system", "content": self.strategy.get_suggestion_prompt(path, context)},
{"role": "user", "content": "질문-λ‹΅λ³€ μ„ΈνŠΈ 3개 μƒμ„±ν•΄μ€˜."}
]
try:
response = client.chat.completions.create(model="gpt-4o-mini", messages=messages, temperature=0.7, max_tokens=300)
content = response.choices[0].message.content
if "```" in content: content = content.replace("```json", "").replace("```", "").strip()
raw_data = json.loads(content)
suggestions = []
for item in raw_data:
suggestions.append({
"label": item['question'],
"cached_answer": item['answer'],
"link": None
})
return suggestions[:3]
except Exception as e:
print(f"Suggestion Gen Error: {e}")
return []