digital-twin / app.py
Sunkat's picture
Upload app.py
b1194c6 verified
Raw
History Blame Contribute Delete
19.9 kB
import os
from openai import OpenAI
import gradio as gr
import uuid
from pprint import pprint
import chromadb
import json
import requests
import random
#-----------------------
# Setup
#-----------------------
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
if OPENAI_API_KEY is None:
raise Exception("API key is missing")
client = OpenAI()
#-----------------------
# Documents
#-----------------------
document_overview = """
Kateryna is a data enablement and governance expert at Henkel, where she drives initiatives that empower organizations to leverage data effectively
, while ensuring strong governance frameworks. She is recognised for her expertise in data governance, data analytics and responsible data usage, helping bridge the gap between business and regulatory requirements.
With a solid academic foundation that includes focus on computer science, data management and digital transformation, Kateryna combines technical understanding with strategic thinking.
She is an AI enthusiast and continuos lerner, actively expanding her knowledge in artificial inteligence and it's practical applications while advocating for responsible and well-governed adoption.
Actively involved in diverse initiatives and communities, she contributes to the data and AI ecosystem by sharing insights, best practices and fostering collaboration among professionals in the field.
Overall, Kateryna stands out as a professional who blends strong technical background, governance expertise, data-driven thinking and a passion for AI and technology,
driving future-ready data strategies and fostering a culture of responsible data use within organizations.
Happy to share what she's learned and help others on their data and AI journey. Also still learning and growing,
so open to new ideas and perspectives as this field moves fast.
Make sure that you only use factual infomration about Kateryna that is provided
in the system message. Do not make up any information about her, and if you don't have enough information
to answer a question, say that you don't have enough information to answer as Kateryna and share what information you are missing.
Additional info:
- Kateryna enjoys cooking and trying out new recipes in her free time.,
- personal interests:loves and teaches yoga, follow healthy lifestyle, since childhood enjoyed swimming, curious about investment, loves traveling and exploring new cultures, exploring new technologies and how they changing our life.,
- Kateryna speaks ukrainian, german, english and russian languages.,
- A dog lover, she has a dog named Coockie, who is a 3 year old border collie.,
- friendly, approachable, clear and concise, with a touch of empathy. She enjoys engaging in meaningful conversations and sharing insights investments and technology topics.,
- she loves learning and exploring new technologies, especially in the AI space. She is passionate about helping organizations leverage data effectively while ensuring responsible and ethical use. ,
- believes that technology, especially AI, has the potential to transform industries and improve lives, and she is excited to be part of this journey.
"""
document_education = """Education, studies and internships:
RWTH Aachen University
Master of Science (M.Sc.), Media Informatics
2010 – 2013
Master thesis: "Requirements detection within end-user mailing lists in OSS"
Fraunhofer Institute of Applied Information Technology (FIT)
Internship, User-oriented system design and personalized information services
2011 – 2011
Design and development of cutting-edge software.
"""
document_work = """Work Experience:
Working now as a Data Governance Expert & Enablement Manager
at Henkel
Oct 2023 - Present · 2 yrs 7 mos
Enable federated governance by onboarding teams, documenting policy, and providing coaching.
Establish AI and Data Governance frameworks, setting clear guidelines and technical controls.
Define oversight mechanisms and support data domain implementation in the enterprise data catalog.
Set up a centralized Data Governance Hub and Data Management Council working group for sharing best practices and key contacts.
Data Governance, Data Management and +6 skills
Generali Deutschland AG
Governance of Data Management & Analytics
Generali Deutschland AG · Full-time
Jun 2021 - Sep 2023 · 2 yrs 4 mos
Aachen, North Rhine-Westphalia, Germany
Implementation of data governance programs based on international policy, including business analysis and coordination of AI/Analytics use cases.
Expansion and collaboration on the information platform for data topics and Alation Data Catalog.
Data Governance, Business Analysis and +2 skills
e.GO Digital GmbH
Smart Data Expert
e.GO Digital GmbH · Full-time
Dec 2018 - Jun 2021 · 2 yrs 7 mos
Hybrid
Creating Dashboards (Tableau, DataStudio), organizing data (Python, Azure, SQL)
Moderation of workshops for the introduction of business intelligence solutions
Creation of requirements and needs analyzes for business intelligence and data science
Integration of data in a data warehouse
Microsoft Azure, Google Analytics and +3 skills
equensWorldline
Software Engineer
equensWorldline · Full-time
Oct 2017 - Dec 2018 · 1 yr 3 mos
Aachen, North Rhine-Westphalia, Germany · Hybrid
Software development for online transaction processing at the financial sector. Based on Unix, Stratus, Tandem platforms using technologies C/C++, Jira.
Shell Scripting, C (Programming Language) and +3 skills
RWTH Aachen University
Research Assistant
RWTH Aachen University
Jan 2014 - Sep 2017 · 3 yrs 9 mos
Aachen, North Rhine-Westphalia, Germany
Research and development activities at the chair "Databases and information systems".
Data mining and social network analysis, with emphasis on mining and modeling OSS communities, text and web mining. """
document_certification = """ Certifications:
German Federal Association of the Digital Economy (BVDW e.V.)
AI Officer/KI-Beauftragte
Issued Feb 2025
Skills: Artificial Intelligence (AI), EU AI Act, AI Ethics, Responsible AI
AI Officer Certification
Azure AI Foundry
LinkedIn
Issued Aug 2025
Skills: Azure AI Studio, Azure AI Foundry
Gen AI Foundations
Henkel
Issued Jul 2025
Skills: Artificial Intelligence (AI)
Certified Yoga Instructor
Together-Yoga-Zumba
Issued Jan 2023
"""
document_recommedations = """This is past recomendations from here colleagues: Business Change Manager & Coach
Katya was on a project with me in Ukraine in late 2008 & was a strong asset to the team. Working in an extremely unsavoury environment which had many areas of potential risk, she was able to conduct her studies & analysis with ease, engaging well with critical client personnel in her areas of responsibility.
Creating relationships with clients undergoing change projects can be somewhat difficult sometimes although this had been made even tougher by the hard financial situation this client had found itself in. In spite of this Katya was able to form effective relationships with ease & gain the trust & confidence both of clients & her co-workers alike. Her manner & operating styles are light & understanding of those she works with, which makes connecting with others easy for her, something that's an essential skill for those involved in change. Her ability to do this in 3 languages is an additional & obvious asset.
I have been happy to have recommended Katya to colleagues since we worked together & also consider her a friend. I therefore have no hesitation in recommending her as a thoroughly capable & trustworthy consultant to anyone looking to crew a project team in countries where English, Russian or Ukrainian language is required.
"""
document_publications = """ This is a list of
Publications during her academic career:
A Longitudinal Study of Community-oriented Open Source Software Development
Published in Proceedings of the 28th International Conference on Advanced Information Systems Engineering (CAiSE'16) Jun 13, 2016
A Longitudinal Study of Community oriented Open Source Software Development
CAiSE 2016
Boosting informal workplace learning in small enterprises
Proceedings of the 4th Workshop on ARTEL. Sep 16, 2014
Supporting Workplace Learning in Small Enterprises by Personal Learning Environments
UMAP 2014 Extended Proceedings. Jul 7, 2014
"""
#-----------------------
# Chunking Function
#-----------------------
def split_text_into_chunks(text: str, chunk_size: int = 200, overlap: int = 50) -> list[str]:
BOUNDARIES = ["\n\n", "\n", ". ", "? ", "! ", " "]
def find_natural_boundary(start: int, end: int) -> int:
midpoint = start + (chunk_size // 2)
for boundary in BOUNDARIES:
pos = text.rfind(boundary, midpoint, end)
return end
chunks = []
start = 0
while start < len(text):
end = min(start + chunk_size, len(text))
if end < len(text):
end = find_natural_boundary(start,end)
chunks.append(text[start:end])
if end >= len(text):
break
start = max(start + 1, end - overlap)
return chunks
#-----------------------
# RAG: Chunk, Embed and Store in ChromaDB
#-----------------------
documents = [
{"text": document_overview, "source":"Overview"},
{"text": document_work, "source":"Professional Experience"},
{"text": document_education, "source":"Education"},
{"text": document_certification, "source":"Certification"},
{"text": document_publications, "source":"Publications"},
{"text": document_recommedations, "source":"Recommendations"}
]
chunks = []
ids = []
metadatas = []
for doc in documents:
#Prepare the lists
chunks_ = split_text_into_chunks(doc["text"],chunk_size=200, overlap=50)
ids_= [str(uuid.uuid4()) for _ in range(len(chunks_))]
metadatas_= [{"source": doc["source"], "chunk_index":i} for i in range(len(chunks_))]
#Add to main lists
chunks.extend(chunks_)
ids.extend(ids_)
metadatas.extend(metadatas_)
#Print for logs:
print(f"Created {len(chunks)} chunks")
for i, chunk in enumerate(chunks):
print(f"Chunk {i+1} (ID: {ids[i]}, Source: {metadatas[i]['source']}, Index: {metadatas[i]['chunk_index']}):")
print(chunk[:100])
print()
#Generate embeddings for all chunks
response = client.embeddings.create(
model = "text-embedding-3-small",
input = chunks
)
embeddings = [item.embedding for item in response.data]
#verify embeddings for logs
print(f"Generated {len(embeddings)} embeddings")
print(f"Each embedding has {len(embeddings[0])} dimensions")
#initialize ChromaDB client (persistent storage)
chroma_client = chromadb.PersistentClient(path="./chroma_db_twin")
#initialize ChromaDB client (in-memory storage)
#chroma_client = chromadb.Client()
#Get or Create + Empty the collection before adding new data (for testing purpose)
collection = chroma_client.get_or_create_collection(name="digital_twin") # a db chroma_db is created on my disk, it's empty.next we will put data in the storage.
if collection.get()["ids"]:
collection.delete(collection.get()["ids"])
#Adding data to chromaDB
collection.add(
ids=ids,
embeddings=embeddings,
documents=chunks,
metadatas=metadatas
)
pprint(collection.get())
#-----------------------
# Tools
#-----------------------
tools = []
#### 1. Import pushover keys
pushover_user = os.getenv("PUSHOVER_USER")
pushover_token = os.getenv("PUSHOVER_TOKEN")
pushover_url = "https://api.pushover.net/1/messages.json" # Pushover API endpoint for sending messages
#create a function to send a notification using Pushover API
def send_notification(message:str):
if pushover_user is None or pushover_token is None: #handle the case when credentials are not set, to avoid errors when LLM calls the tool without proper configuration
return "Notification failed: Pushover credentials not configured."
payload = {"user": pushover_user, "token": pushover_token, "message": message}
requests.post(pushover_url, data=payload)
return f"Sent notification with message: {message}"
### 3. Describe Pushover as an LLM tool
#creating a function to explain LLM when to use the tool and how to call it, including the expected input and output formats. This will help the LLM understand how to use the tool effectively and provide the necessary information for successful execution.
send_notification_function = {
#creating dictionary
"name": "send_notification",
"description": "Sends a notification message to the real Kateryna. Use this when: \
1) Someone wants to get in touch with Kateryna, hire, or collaborate ask for their name and contact details first,\
then send notification to Kateryna with the message that includes the user's name and contact details, and a brief reason for reaching out.\
2) When you don't know the answer to a question about Kateryna - send AUTOMATICLY without asking, include question so she can add this info later.",
"parameters": {
# way LLM should format the input when calling the function / JSON theobject with key-value pairs corresponding to the function's parameters
"type": "object",
"properties": {
"message": {
"type": "string",
"description": "The content of the notification message to be sent to the user's device."
}
},
"required": ["message"]
}
}
### 3. Add Pushover to the list of tools for the LLM
# This is the list of tools that the LLM can use to interact with the environment. Each tool is defined by a dictionary that includes its name, description, and parameters. The LLM will use this information to determine when and how to call the tool during the conversation.
# we have just one tool in our list, but we could add more tools in the future by appending additional dictionaries to this list.
tools.append({"type": "function", "function": send_notification_function})
#simulates rolling a single six-sided die
def dice_roll():
result = random.randint(1,6)
return result
#describe function for LLM
#dice role functionality
roll_dice_function = {
"name": "dice_roll",
"description": "Simulates rolling a single six-sided die and returns the result. Use this when the user wants to roll a die for games, decisions or random number generation",
"parameters": {
# way LLM should format the input when calling the function / JSON theobject with key-value pairs corresponding to the function's parameters
"type": "object",
"properties": {},
"required": []
}
}
#Add function to list of tools of LLM
tools.append({"type": "function", "function": roll_dice_function})
#-----------------------
# Tool Call Handler
#-----------------------
def handle_tool_call(tool_calls):
tool_results = []
for tool_call in tool_calls:
function_name = tool_call.function.name
args = json.loads(tool_call.function.arguments)
#print(f"Calling funvtion {function_name}") #for future debugging
#Route to the appropriate function based on function name
if function_name == "send_notification":
#acctually send the notification, i.e. call the tool
content = send_notification(args["message"])
elif function_name == "dice_roll":
content = f"Rolled: {dice_roll()}"
#elif function_name == "insert_function_name_3":
# content = insert_function_name_3(args["message"])
else:
content = f"Uknown function: {function_name}"
#print(f"Sent notification with message: {args['message']}")
tool_call_result = {
"role": "tool",
"content": content,
"tool_call_id" : tool_call.id
}
tool_results.append(tool_call_result)
#return what to add to our "context (about tool call results), a dictionary "
return tool_results
#-----------------------
# System Message
#-----------------------
system_message = """You are a digital twin add emoji and assistant of Kateryna Neulinger. Make initial introduction short and polite\
Important: do not make things up about Kateryna. If you don't know the answer, say you don't know. The only factual information available to you is what's in this system message.
You can not get any more facts about Kateryna from the internet or make them up.
Important: whenever you don't know something about Kateryna, always use the send_notification tool to alert the real Kateryna about the question, so she can update the system message with the missing information for future conversations.
Do this automatically, without asking the user.
AI Engineering experience: as Kateryna has build me, she has a good understanding of how I work and what I can do. She has also given me access to some tools that I can use to interact with the world, such as sending notifications to her.
For the publications, certifications and recommendations, you can only share the information that is provided in the documents."""
#-----------------------
# Main Response. Function
#-----------------------
def respond_ai(message, history):
#RAG
#Embed the query using the same model we used for the chunks to ensure compability
response = client.embeddings.create(
model = "text-embedding-3-small",
input = [message]
)
query_embedding = response.data[0].embedding
#Search ChromaDB
results = collection.query(
query_embeddings=[query_embedding],
n_results=3
)
#Sititch retrieved chunks together to create the context for the response
context = "\n---\n".join(results["documents"][0])
print("\n==============================\n")
print(f"User message: \n{message}\n")
print("***Retrieved Chunks:")
for a, b in zip(results["documents"][0], results["metadatas"][0]):
print("-------------------------------\n")
print(f"<<Document {b['source']}---Chunk {b['chunk_index']}>>\n{a}\n")
#Update system message with context (for this conversation turn)
system_message_enhanced= system_message + "\n\nContext:\n" + context
#Build messages for LLM with updated system message
messages = [{"role": "system", "content": system_message_enhanced}] + history + [{"role": "user", "content": message}]
#Call LLM
response = client.chat.completions.create(
model="gpt-4.1-mini",
messages=messages,
tools=tools
)
message = response.choices[0].message
#check if model wants to call a tool
while message.tool_calls:
pprint(message.tool_calls)
#..handle tool call
tool_result = handle_tool_call(message.tool_calls) #whole list of tool calls on purpose
#..add message to "context", i.e. messages
messages.append(message)
#..add information about the tool call to the context for the LLM to learn from i.e. messages
messages.extend(tool_result) #cheanged from append() to extend() when we switched to multiple tool call handling
# ..invoke the LLM one more time to get it's updated response and provide context with tool calling
response = client.chat.completions.create(
model="gpt-4.1-mini",
messages=messages,
tools=tools # consecutive tool call will be considered by LLM
)
message = response.choices[0].message
return(message.content)
#-----------------------
# Launch Gradio
#-----------------------
gr.ChatInterface(
fn=respond_ai,
title="Kateryna's Digital Assistant",
chatbot=gr.Chatbot(avatar_images=(None, "avatar.jpeg")),
description="Hi I am an AI version of Kateryna. Ask me anything about her professional background and interests?",
examples=[["Hi, what can you do?"], ["Tell me more about Kateryna's AI Engineering experience?"],["Get in touch with the real Kateryna"]]
).launch(inbrowser=True)