Chat_with_Evison / logic /profile.py
SoniAI's picture
Upload folder using huggingface_hub
5884015 verified
Raw
History Blame Contribute Delete
6.17 kB
import json
from pathlib import Path
from dotenv import load_dotenv
from openai import OpenAI
from pypdf import PdfReader
from logic.tools import FUNCTION_MAP, TOOLS
load_dotenv(override=True)
BASE_DIR = Path(__file__).resolve().parent.parent
class Me:
def __init__(self):
self.openai = OpenAI()
self.name = "Evison Ndoni"
pdf_path = BASE_DIR / "me" / "evison.pdf"
self.linkedin = ""
try:
if pdf_path.exists():
reader = PdfReader(str(pdf_path))
for page in reader.pages:
text = page.extract_text()
if text:
self.linkedin += text
else:
self.linkedin = "(LinkedIn PDF not found on server.)"
except Exception as e:
self.linkedin = f"(Error reading LinkedIn PDF: {e})"
summary_path = BASE_DIR / "me" / "summary.txt"
try:
with open(summary_path, "r", encoding="utf-8") as f:
self.summary = f.read()
except Exception as e:
self.summary = f"(Summary file not found or unreadable: {e})"
self.extra_context = """
- 26-year-old software engineer from Albania.
- 3+ years experience with React, Next.js, TypeScript, Tailwind CSS and Flutter.
- Currently learning Agentic AI and aiming for AI Engineer roles.
- Values clean, fluid UI/UX and likes to build useful products and SaaS.
- Tries to stay grounded, responsible, and future-oriented while putting God first.
""".strip()
def handle_tool_call(self, tool_calls):
results = []
for tool_call in tool_calls:
tool_name = tool_call.function.name
arguments = json.loads(tool_call.function.arguments)
print(f"Tool called: {tool_name}", flush=True)
tool = FUNCTION_MAP.get(tool_name)
result = tool(**arguments) if tool else {}
results.append(
{
"role": "tool",
"content": json.dumps(result),
"tool_call_id": tool_call.id,
}
)
return results
def system_prompt(self):
system_prompt = (
f"You are acting as {self.name}. You are answering questions on "
f"{self.name}'s personal website, particularly questions related to his "
f"career, background, skills, experience, and values.\n\n"
f"Your responsibility is to represent {self.name} as faithfully as possible. "
f"Be professional, warm, confident, and grounded, as if talking to a "
f"potential client, hiring manager, or collaborator.\n\n"
f"{self.name} is a follower of the Orthodox Christian faith and tries to put "
f"God first while being ambitious and disciplined in his work. When faith "
f"or values come up, you can mention this naturally, but keep the focus on "
f"respectful and professional conversation.\n\n"
f"If you don't know the answer to any question, use your "
f"'record_unknown_question' tool to record the question, even if it's "
f"trivial or unrelated to career.\n\n"
f"If the user seems like a potential employer, client, or collaborator, "
f"gently encourage them to share their email so {self.name} can follow up, "
f"and record it using your 'record_user_details' tool.\n"
)
system_prompt += f"\n## Short Summary\n{self.summary}\n"
system_prompt += f"\n## LinkedIn-style Profile\n{self.linkedin}\n"
system_prompt += f"\n## Additional Personal Context\n{self.extra_context}\n"
system_prompt += (
"\nWith this context, please chat with the user, always staying in "
f"character as {self.name}."
)
return system_prompt
def _run_conversation(self, message, history_messages):
"""
Internal helper that runs the full tool-calling loop and returns
the final assistant message content as a string.
`history_messages` is a list of OpenAI-style dicts: [{role, content}, ...]
(no system message inside; we add it here).
"""
if history_messages is None:
history_messages = []
messages = [
{"role": "system", "content": self.system_prompt()}
] + history_messages + [{"role": "user", "content": message}]
done = False
while not done:
response = self.openai.chat.completions.create(
model="gpt-4o-mini",
messages=messages,
tools=TOOLS,
)
choice = response.choices[0]
if choice.finish_reason == "tool_calls":
message_tool = choice.message
tool_calls = message_tool.tool_calls
results = self.handle_tool_call(tool_calls)
messages.append(message_tool)
messages.extend(results)
else:
done = True
final_message = choice.message
return final_message.content or ""
return ""
def chat_stream(self, message, history_messages):
"""
Generator version for streaming.
Yields *partial assistant content* as a plain string.
Gradio can wrap this to update the Chatbot incrementally.
"""
full_content = self._run_conversation(message, history_messages)
partial = ""
for ch in full_content:
partial += ch
yield partial
def chat(self, message, history_messages):
"""
Non-streaming wrapper kept for compatibility with existing code.
It internally uses `chat_stream` and just returns the final string.
"""
last_chunk = ""
for chunk in self.chat_stream(message, history_messages):
last_chunk = chunk
return last_chunk