career_chatbots / core /chatbot.py
liuyuelintop's picture
Upload folder using huggingface_hub
8e7f687 verified
# Main chatbot class extracted from app.py
import os
from openai import OpenAI
from content import ContentStore
from notifications.pushover import PushoverService
from tools.definitions import TOOLS
from tools.handler import ToolHandler
from core.router import MessageRouter
from config.prompts import build_system_prompt
class Chatbot:
"""Main chatbot orchestration class"""
def __init__(self, name: str = "Yuelin Liu"):
self.name = name
# Initialize OpenAI client
self.openai = OpenAI(
api_key=os.getenv("GOOGLE_API_KEY"),
base_url="https://generativelanguage.googleapis.com/v1beta/openai/"
)
# Initialize services
self.pushover = PushoverService(
token=os.getenv("PUSHOVER_TOKEN"),
user=os.getenv("PUSHOVER_USER")
)
self.tool_handler = ToolHandler(pushover_service=self.pushover)
self.router = MessageRouter(self.openai)
# Initialize content store
self.content = ContentStore()
# Put career.pdf + summary.txt here (and any other work docs)
self.content.load_folder("me/career", "career")
# Merge everything else (hobby/life/projects/education) into personal/
self.content.load_folder("me/personal", "personal")
# Optional: quick startup log (comment out if noisy)
self._log_loaded_docs()
def build_context_for_mode(self, mode: str) -> str:
"""Build document context for the given mode"""
domain = "career" if mode == "career" else "personal"
return self.content.join_domain_text([domain])
def system_prompt(self, mode: str) -> str:
"""Generate system prompt for the given mode"""
domain_text = self.build_context_for_mode(mode)
return build_system_prompt(self.name, domain_text, mode)
def chat(self, message: str, history: list) -> str:
"""Main chat entrypoint with guarded execution"""
try:
# 1) Route message
route = self.router.classify(message)
intent = route.get("intent", "career")
# Determine mode
if intent == "contact_exchange":
mode = "career" # keep professional context for contact flows
else:
mode = "career" if intent == "career" else "personal"
# 2) Check for immediate responses (boundaries, contact collection, pitch)
immediate_response = self.router.get_response_for_route(message, route, mode)
if immediate_response:
return immediate_response
# 3) Regular chat with tools enabled
messages = [{"role": "system", "content": self.system_prompt(mode)}] \
+ history + [{"role": "user", "content": message}]
while True:
response = self.openai.chat.completions.create(
model="gemini-2.5-flash",
messages=messages,
tools=TOOLS,
temperature=0.2,
top_p=0.9
)
choice = response.choices[0]
if choice.finish_reason == "tool_calls":
results = self.tool_handler.handle_tool_calls(choice.message.tool_calls)
messages.append(choice.message)
messages.extend(results)
continue
return choice.message.content or "Thanks—I've noted that."
except Exception as e:
# Fail-closed, keep UI stable
print(f"[FATAL] Chat turn failed: {e}", flush=True)
return "Oops, something went wrong on my side. Please ask that again—I've reset my context."
def _log_loaded_docs(self):
"""Optional: log loaded documents at startup"""
by_domain = self.content.by_domain
for domain, docs in by_domain.items():
print(f"[LOAD] Domain '{domain}': {len(docs)} document(s)")
for d in docs:
print(f" - {d.title}")