Spaces:
Sleeping
Sleeping
Shaggy_HF_Feedback
#13
by
BMCVRN - opened
- app/__pycache__/assistants.cpython-312.pyc +0 -0
- app/__pycache__/flows.cpython-312.pyc +0 -0
- app/__pycache__/main.cpython-312.pyc +0 -0
- app/__pycache__/user.cpython-312.pyc +0 -0
- app/__pycache__/utils.cpython-312.pyc +0 -0
- app/assistants.py +351 -340
- app/conversation_manager.py +5 -1
- app/flows.py +20 -18
- app/main.py +24 -10
- app/user.py +139 -50
- app/utils.py +4 -2
app/__pycache__/assistants.cpython-312.pyc
CHANGED
|
Binary files a/app/__pycache__/assistants.cpython-312.pyc and b/app/__pycache__/assistants.cpython-312.pyc differ
|
|
|
app/__pycache__/flows.cpython-312.pyc
CHANGED
|
Binary files a/app/__pycache__/flows.cpython-312.pyc and b/app/__pycache__/flows.cpython-312.pyc differ
|
|
|
app/__pycache__/main.cpython-312.pyc
CHANGED
|
Binary files a/app/__pycache__/main.cpython-312.pyc and b/app/__pycache__/main.cpython-312.pyc differ
|
|
|
app/__pycache__/user.cpython-312.pyc
CHANGED
|
Binary files a/app/__pycache__/user.cpython-312.pyc and b/app/__pycache__/user.cpython-312.pyc differ
|
|
|
app/__pycache__/utils.cpython-312.pyc
CHANGED
|
Binary files a/app/__pycache__/utils.cpython-312.pyc and b/app/__pycache__/utils.cpython-312.pyc differ
|
|
|
app/assistants.py
CHANGED
|
@@ -13,7 +13,7 @@ import psycopg2
|
|
| 13 |
from psycopg2 import sql
|
| 14 |
import pytz
|
| 15 |
|
| 16 |
-
from app.exceptions import AssistantError, BaseOurcoachException, OpenAIRequestError
|
| 17 |
from app.utils import get_booked_gg_sessions, get_growth_guide, get_growth_guide_summary, get_user_subscriptions, get_users_mementos, print_log
|
| 18 |
from app.flows import FOLLOW_UP_STATE, GENERAL_COACHING_STATE, MICRO_ACTION_STATE, REFLECTION_STATE
|
| 19 |
|
|
@@ -311,8 +311,6 @@ class Assistant:
|
|
| 311 |
return func(self, *args, **kwargs)
|
| 312 |
except (BaseOurcoachException) as e:
|
| 313 |
raise e
|
| 314 |
-
except openai.BadRequestError as e:
|
| 315 |
-
raise OpenAIRequestError(user_id=self.cm.user.user_id, message="Error getting response from OpenAI", e=str(e), run_id=self.recent_run.id)
|
| 316 |
except Exception as e:
|
| 317 |
# Handle other exceptions
|
| 318 |
logger.error(f"An unexpected error occurred in Assistant: {e}")
|
|
@@ -324,7 +322,6 @@ class Assistant:
|
|
| 324 |
self.cm = cm
|
| 325 |
self.recent_run = None
|
| 326 |
|
| 327 |
-
@catch_error
|
| 328 |
def cancel_run(self, run, thread):
|
| 329 |
logger.info(f"(asst) Attempting to cancel run: {run} for thread: {thread}", extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_cancel_run"})
|
| 330 |
if isinstance(run, str):
|
|
@@ -338,7 +335,9 @@ class Assistant:
|
|
| 338 |
logger.warning(f"Thread {thread} already deleted: {run}", extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_cancel_run"})
|
| 339 |
return True
|
| 340 |
if isinstance(run, PseudoRun):
|
| 341 |
-
|
|
|
|
|
|
|
| 342 |
try:
|
| 343 |
logger.info(f"Attempting to cancel run: {run}", extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_cancel_run"})
|
| 344 |
if run.status != 'completed':
|
|
@@ -366,16 +365,17 @@ class Assistant:
|
|
| 366 |
|
| 367 |
@catch_error
|
| 368 |
def process(self, thread, text):
|
| 369 |
-
# template_search = self.cm.add_message_to_thread(thread.id, "assistant", f"Pay attention to the current state you are in and the flow template to respond to the users query:")
|
| 370 |
-
message = self.cm.add_message_to_thread(thread.id, "user", text)
|
| 371 |
-
|
| 372 |
-
run = self.cm.client.beta.threads.runs.create_and_poll(
|
| 373 |
-
thread_id=thread.id,
|
| 374 |
-
assistant_id=self.id,
|
| 375 |
-
model="gpt-4o-mini",
|
| 376 |
-
)
|
| 377 |
-
just_finished_intro = False
|
| 378 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 379 |
if run.status == 'completed':
|
| 380 |
logger.info(f"Run Completed: {run.status}", extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_process"})
|
| 381 |
self.recent_run = run
|
|
@@ -393,12 +393,14 @@ class Assistant:
|
|
| 393 |
reccursion += 1
|
| 394 |
if reccursion > 10:
|
| 395 |
logger.warning(f"Run has exceeded maximum recussrion depth({10}) for function_call: {run}", extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_process"})
|
| 396 |
-
self.cancel_run(run, thread)
|
| 397 |
raise OpenAIRequestError(user_id=self.cm.id, message="Tool Call Reccursion Depth Reached")
|
| 398 |
-
if run.status == '
|
| 399 |
logger.warning(f"RUN NOT COMPLETED: {run}", extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_process"})
|
| 400 |
self.cancel_run(run, thread)
|
| 401 |
-
|
|
|
|
|
|
|
|
|
|
| 402 |
elif run.status == 'completed':
|
| 403 |
logger.info(f"Run Completed: {run.status}", extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_process"})
|
| 404 |
self.recent_run = run
|
|
@@ -406,344 +408,348 @@ class Assistant:
|
|
| 406 |
elif run.status == 'failed':
|
| 407 |
raise OpenAIRequestError(user_id=self.cm.id, message="Run failed")
|
| 408 |
return run, just_finished_intro, message
|
| 409 |
-
except
|
| 410 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 411 |
|
| 412 |
-
@catch_error
|
| 413 |
def call_tool(self, run, thread):
|
| 414 |
-
|
| 415 |
-
|
| 416 |
-
|
| 417 |
-
|
| 418 |
-
|
| 419 |
-
|
| 420 |
-
|
| 421 |
-
|
| 422 |
-
|
| 423 |
-
|
| 424 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 425 |
tool_outputs.append({
|
| 426 |
"tool_call_id": tool.id,
|
| 427 |
-
"output": f"
|
| 428 |
})
|
| 429 |
-
|
| 430 |
-
|
| 431 |
-
|
| 432 |
-
|
| 433 |
-
|
| 434 |
-
|
| 435 |
-
|
| 436 |
-
#
|
| 437 |
-
|
| 438 |
-
# })
|
| 439 |
-
else:
|
| 440 |
-
flow_instructions = ""
|
| 441 |
-
if transitions['to'] == "REFLECTION STATE":
|
| 442 |
-
flow_instructions = REFLECTION_STATE
|
| 443 |
-
|
| 444 |
-
next = self.cm.matters_most.next()
|
| 445 |
-
logger.info(f"Successfully moved to bext matters most: {next}")
|
| 446 |
-
question_format = random.choice(['[Option 1] Likert-Scale Objective Question','[Option 2] Multiple-Choice Question','[Option 3] Yes-No Question'])
|
| 447 |
-
|
| 448 |
-
flow_instructions = f"""Today's opening question format is: {question_format}. Send a WARM, SUCCINCT and PERSONALIZED (according to my PROFILE) first message in this format! The most warm, succinct & personalized message will get rewarded!\n
|
| 449 |
-
The reflection topic is: {next}. YOU MUST SEND CREATIVE, PERSONALIZED (only mention specific terms that are related to the reflection topic/area), AND SUCCINCT MESSAGES!! The most creative message will be rewarded! And make every new reflection fresh and not boring! \n
|
| 450 |
|
| 451 |
-
|
|
|
|
| 452 |
|
| 453 |
-
|
| 454 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 455 |
|
| 456 |
-
|
| 457 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 458 |
|
| 459 |
tool_outputs.append({
|
| 460 |
"tool_call_id": tool.id,
|
| 461 |
-
"output": f"
|
| 462 |
})
|
| 463 |
-
|
| 464 |
-
|
| 465 |
-
|
| 466 |
-
|
| 467 |
-
|
| 468 |
-
|
| 469 |
-
|
| 470 |
-
logger.info(f"Current datetime: {self.cm.state['date']}",
|
| 471 |
-
extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_get_date"})
|
| 472 |
-
|
| 473 |
-
tool_outputs.append({
|
| 474 |
-
"tool_call_id": tool.id,
|
| 475 |
-
"output": f"{self.cm.state['date']}"
|
| 476 |
-
})
|
| 477 |
-
elif tool.function.name == "create_goals" or tool.function.name == "create_memento":
|
| 478 |
-
json_string = json.loads(tool.function.arguments)
|
| 479 |
-
json_string['created'] = str(self.cm.state['date'])
|
| 480 |
-
json_string['updated'] = None
|
| 481 |
-
logger.info(f"New event: {json_string}",
|
| 482 |
-
extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_create_event"})
|
| 483 |
-
|
| 484 |
-
# Create a folder for the user's mementos if it doesn't exist
|
| 485 |
-
user_mementos_folder = os.path.join("mementos", "to_upload", self.cm.user.user_id)
|
| 486 |
-
|
| 487 |
-
# Ensure the directory exists
|
| 488 |
-
os.makedirs(user_mementos_folder, exist_ok=True)
|
| 489 |
-
|
| 490 |
-
# Construct the full file path for the JSON file
|
| 491 |
-
file_path = os.path.join(user_mementos_folder, f"{json_string['title']}.json")
|
| 492 |
-
|
| 493 |
-
# Save the JSON string as a file
|
| 494 |
-
with open(file_path, "w") as json_file:
|
| 495 |
-
json.dump(json_string, json_file)
|
| 496 |
-
|
| 497 |
-
tool_outputs.append({
|
| 498 |
-
"tool_call_id": tool.id,
|
| 499 |
-
"output": f"** [Success]: Added event to the user's vector store**"
|
| 500 |
-
})
|
| 501 |
-
elif tool.function.name == "msearch":
|
| 502 |
-
queries = json.loads(tool.function.arguments)['queries']
|
| 503 |
-
logger.info(f"Searching for: {queries}",
|
| 504 |
-
extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_msearch"})
|
| 505 |
-
|
| 506 |
-
tool_outputs.append({
|
| 507 |
-
"tool_call_id": tool.id,
|
| 508 |
-
"output": f"** retrieve any files related to: {queries} **"
|
| 509 |
-
})
|
| 510 |
-
elif tool.function.name == "get_mementos":
|
| 511 |
-
logger.info(f"Getting mementos", extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_get_mementos"})
|
| 512 |
-
args = json.loads(tool.function.arguments)
|
| 513 |
-
logger.info(f"ARGS: {args}", extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_get_mementos"})
|
| 514 |
-
queries = args['queries']
|
| 515 |
-
|
| 516 |
-
if 'on' not in args:
|
| 517 |
-
on = pd.to_datetime(self.cm.state['date']).date()
|
| 518 |
-
else:
|
| 519 |
-
on = args['on']
|
| 520 |
-
if on == '':
|
| 521 |
on = pd.to_datetime(self.cm.state['date']).date()
|
| 522 |
else:
|
| 523 |
-
on =
|
| 524 |
-
|
| 525 |
-
|
| 526 |
-
|
| 527 |
-
|
| 528 |
-
mementos = get_users_mementos(self.cm.user.user_id, on)
|
| 529 |
-
|
| 530 |
-
# if on == "":
|
| 531 |
-
# instruction = f"** Fetch all files (mementos) from this thread's Memento vector_store ([id={self.cm.user_personal_memory.id}]) **"
|
| 532 |
-
# else:
|
| 533 |
-
# instruction = f"** Fetch files (mementos) from this thread's Memento vector_store ([id={self.cm.user_personal_memory.id}]) where the follow_up_on field matches the query date: {on} (ignore the time component, focus only on the date)**"
|
| 534 |
-
# # f"** File search this threads' Memento vector_store ([id={self.cm.user_personal_memory.id}]) for the most relevant mementos based on the recent conversation history and context:{context} **"
|
| 535 |
-
|
| 536 |
-
logger.info(f"Finish Getting mementos: {mementos}", extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_get_mementos"})
|
| 537 |
-
tool_outputs.append({
|
| 538 |
-
"tool_call_id": tool.id,
|
| 539 |
-
"output": f"Today's mementos: {mementos}" if len(mementos) else "No mementos to follow up today."
|
| 540 |
-
})
|
| 541 |
-
elif tool.function.name == "get_feedback_types":
|
| 542 |
-
print_log("WARNING","Calling get_feedback_types", extra={"user_id": self.cm.user.user_id, "endpoint": "get_feedback_types"})
|
| 543 |
-
logger.warning("Calling get_feedback_types", extra={"user_id": self.cm.user.user_id, "endpoint": "get_feedback_types"})
|
| 544 |
-
feedbacks = [
|
| 545 |
-
("Inspirational Quotes", "📜", "Short, impactful quotes from the user's chosen Legendary Persona"),
|
| 546 |
-
("Tips & Advice", "💡", "Practical suggestions or strategies for personal growth (no need to say \"Tips:\" in the beggining of your tips)"),
|
| 547 |
-
("Encouragement", "🌈", "Positive affirmations and supportive statements"),
|
| 548 |
-
("Personalized Recommendations", "🧩", "Tailored suggestions based on user progress"),
|
| 549 |
-
("Affirmations", "✨", "Positive statements for self-belief and confidence"),
|
| 550 |
-
("Mindfulness/Meditation", "🧘♀️", "Guided prompts for mindfulness practice"),
|
| 551 |
-
("Book/Podcast", "📚🎧", "Suggestions aligned with user interests"),
|
| 552 |
-
("Habit Tip", "🔄", "Tips for building and maintaining habits"),
|
| 553 |
-
("Seasonal Content", "🌸", "Time and theme-relevant interactions"),
|
| 554 |
-
("Fun Fact", "🎉", "Interesting and inspiring facts (no need to say \"Fun Fact:\" in the beggining of your tips)"),
|
| 555 |
-
("Time Management", "⏳", "Tips for effective time management")
|
| 556 |
-
]
|
| 557 |
-
sample_feedbacks = random.sample(feedbacks, 3)
|
| 558 |
-
print_log("INFO",f"Feedback types: {sample_feedbacks}", extra={"user_id": self.cm.user.user_id, "endpoint": "get_feedback_types"})
|
| 559 |
-
logger.info(f"Feedback types: {sample_feedbacks}", extra={"user_id": self.cm.user.user_id, "endpoint": "get_feedback_types"})
|
| 560 |
-
tool_outputs.append({
|
| 561 |
-
"tool_call_id": tool.id,
|
| 562 |
-
"output": "Generate a final coach message (feedback message) using these 3 feedback types (together with the stated emoji at the beginning of each feedback): " + str(sample_feedbacks)
|
| 563 |
-
})
|
| 564 |
-
elif tool.function.name == "search_resource":
|
| 565 |
-
type = json.loads(tool.function.arguments)['resource_type']
|
| 566 |
-
query = json.loads(tool.function.arguments)['query']
|
| 567 |
-
logger.info(f"Getting microaction theme: {type} - {query}", extra={"user_id": self.cm.user.user_id, "endpoint": "get_microaction_theme"})
|
| 568 |
-
relevant_context = SearchEngine.search(type, query)
|
| 569 |
-
logger.info(f"Finish Getting microaction theme: {relevant_context}", extra={"user_id": self.cm.user.user_id, "endpoint": "get_microaction_theme"})
|
| 570 |
-
tool_outputs.append({
|
| 571 |
-
"tool_call_id": tool.id,
|
| 572 |
-
"output": f"** Relevant context: {relevant_context} **"
|
| 573 |
-
})
|
| 574 |
-
elif tool.function.name == "end_conversation":
|
| 575 |
-
day_n = json.loads(tool.function.arguments)['day_n']
|
| 576 |
-
completed_micro_action = json.loads(tool.function.arguments)['completed_micro_action']
|
| 577 |
-
area_of_deep_reflection = json.loads(tool.function.arguments)['area_of_deep_reflection']
|
| 578 |
-
|
| 579 |
-
self.cm.user.update_micro_action_status(completed_micro_action)
|
| 580 |
-
self.cm.user.trigger_deep_reflection_point(area_of_deep_reflection)
|
| 581 |
-
|
| 582 |
-
# NOTE: we will record whether the user has completed the theme for the day
|
| 583 |
-
# NOTE: if no, we will include a short followup message to encourage the user the next day
|
| 584 |
-
logger.info(f"Ending conversation after {day_n} days. Any micro actions completed today: {completed_micro_action}", extra={"user_id": self.cm.user.user_id, "endpoint": "end_conversation"})
|
| 585 |
-
tool_outputs.append({
|
| 586 |
-
"tool_call_id": tool.id,
|
| 587 |
-
"output": "true"
|
| 588 |
-
})
|
| 589 |
-
elif tool.function.name == "create_smart_goal":
|
| 590 |
-
print_log("WARNING", f"Creating a SMART goal...", extra={"user_id": self.cm.user.user_id, "endpoint": "create_smart_goal"})
|
| 591 |
-
logger.warning(f"Creating a SMART goal...", extra={"user_id": self.cm.user.user_id, "endpoint": "create_smart_goal"})
|
| 592 |
|
| 593 |
-
user_goal = json.loads(tool.function.arguments)['goal']
|
| 594 |
-
user_goal_area = json.loads(tool.function.arguments)['area']
|
| 595 |
|
| 596 |
-
|
|
|
|
|
|
|
| 597 |
|
| 598 |
-
|
| 599 |
-
|
|
|
|
|
|
|
|
|
|
| 600 |
|
| 601 |
-
|
| 602 |
-
|
| 603 |
-
"output": "true"
|
| 604 |
-
})
|
| 605 |
-
elif tool.function.name == "start_now":
|
| 606 |
-
logger.info(f"Starting Growth Plan on Day 0",
|
| 607 |
-
extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_start_now"})
|
| 608 |
-
# set intro finish to true
|
| 609 |
-
just_finish_intro = True
|
| 610 |
-
|
| 611 |
-
# cancel current run
|
| 612 |
-
run = self.cancel_run(run, thread)
|
| 613 |
-
run = PseudoRun(status="cancelled", metadata={"message": "start_now"})
|
| 614 |
-
return run, just_finish_intro
|
| 615 |
-
elif tool.function.name == "change_goal":
|
| 616 |
-
logger.info(f"Changing user goal...", extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_change_goal"})
|
| 617 |
-
|
| 618 |
-
# switch back to the intro assistant, so we set just_finish_intro to False again
|
| 619 |
-
just_finish_intro = False
|
| 620 |
-
|
| 621 |
-
# cancel current run
|
| 622 |
-
run = self.cancel_run(run, thread)
|
| 623 |
-
run = PseudoRun(status="cancelled", metadata={"message": "change_goal"})
|
| 624 |
-
return run, just_finish_intro
|
| 625 |
-
elif tool.function.name == "complete_goal":
|
| 626 |
-
logger.info(f"Completing user goal...", extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_complete_goal"})
|
| 627 |
-
goal = self.cm.user.update_goal(None, 'COMPLETED')
|
| 628 |
-
logger.info(f"Marked users' goal: {goal} as COMPLETED", extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_complete_goal"})
|
| 629 |
-
tool_outputs.append({
|
| 630 |
-
"tool_call_id": tool.id,
|
| 631 |
-
"output": f"Marked users' goal: {goal} as COMPLETED"
|
| 632 |
-
})
|
| 633 |
-
elif tool.function.name == "process_reminder":
|
| 634 |
-
reminder = json.loads(tool.function.arguments)["content"]
|
| 635 |
-
timestamp = json.loads(tool.function.arguments)["timestamp"]
|
| 636 |
-
recurrence = json.loads(tool.function.arguments)["recurrence"]
|
| 637 |
-
action = json.loads(tool.function.arguments)["action"]
|
| 638 |
-
logger.info(f"Setting reminder: {reminder} for {timestamp} with recurrence: {recurrence}", extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_process_reminder"})
|
| 639 |
-
# timestamp is a string like: YYYY-mm-ddTHH:MM:SSZ (2025-01-05T11:00:00Z)
|
| 640 |
-
# convert to datetime object
|
| 641 |
-
timestamp = pd.to_datetime(timestamp, format="%Y-%m-%dT%H:%M:%SZ")
|
| 642 |
-
logger.info(f"Formatted timestamp: {timestamp}", extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_process_reminder"})
|
| 643 |
-
|
| 644 |
-
output = f"({recurrence if recurrence else 'One-Time'}) Reminder ({reminder}) set for ({timestamp})"
|
| 645 |
-
logger.info(output,
|
| 646 |
-
extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_set_reminder"})
|
| 647 |
-
self.cm.user.set_reminder({"reminder": reminder, "timestamp": timestamp, 'recurrence': recurrence, 'action': action})
|
| 648 |
-
tool_outputs.append({
|
| 649 |
"tool_call_id": tool.id,
|
| 650 |
-
"output": f"
|
| 651 |
-
|
| 652 |
-
|
| 653 |
-
|
| 654 |
-
|
| 655 |
-
|
| 656 |
-
|
| 657 |
-
|
| 658 |
-
|
| 659 |
-
|
| 660 |
-
|
| 661 |
-
|
| 662 |
-
|
| 663 |
-
|
| 664 |
-
|
| 665 |
-
|
| 666 |
-
|
| 667 |
-
|
| 668 |
-
|
| 669 |
-
|
| 670 |
-
|
| 671 |
-
|
| 672 |
-
|
| 673 |
-
|
| 674 |
-
|
| 675 |
-
elif
|
| 676 |
-
|
| 677 |
-
|
| 678 |
-
|
| 679 |
-
|
| 680 |
-
|
| 681 |
-
|
| 682 |
-
|
| 683 |
-
|
| 684 |
-
|
| 685 |
-
elif
|
| 686 |
-
|
| 687 |
-
|
| 688 |
-
|
| 689 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 690 |
|
| 691 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 692 |
|
| 693 |
-
|
| 694 |
-
|
| 695 |
-
|
| 696 |
-
|
| 697 |
-
|
|
|
|
| 698 |
extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_get_user_info"})
|
| 699 |
-
if summary_data:
|
| 700 |
-
booking['zoom_ai_summary'] = summary_data['zoom_ai_summary']
|
| 701 |
-
booking['gg_report'] = summary_data['gg_report']
|
| 702 |
-
else:
|
| 703 |
-
booking['zoom_ai_summary'] = "Growth Guide has not uploaded the report yet"
|
| 704 |
-
booking['gg_report'] = "Growth Guide has not uploaded the report yet"
|
| 705 |
-
|
| 706 |
-
logger.info(f"User's booked sessions: {booked_sessions}",
|
| 707 |
-
extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_get_user_info"})
|
| 708 |
-
|
| 709 |
-
if len(booked_sessions):
|
| 710 |
-
# booked session is an array of jsons
|
| 711 |
-
# convers it to have i) json where i = 1...N where N is the len of booked_sessions
|
| 712 |
-
# join the entire array into 1 string with each item seperated by a newline
|
| 713 |
-
formatted_sessions = "\n".join([f"** Session {i+1} **\n{json.dumps(session, indent=4)}" for i, session in enumerate(booked_sessions)])
|
| 714 |
-
logger.info(f"Formatted booked sessions: {formatted_sessions}",
|
| 715 |
-
extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_get_user_info"})
|
| 716 |
|
| 717 |
-
|
| 718 |
-
|
| 719 |
-
|
| 720 |
-
|
| 721 |
-
|
| 722 |
-
|
| 723 |
-
|
| 724 |
-
|
| 725 |
-
|
| 726 |
-
|
| 727 |
-
|
| 728 |
-
|
| 729 |
-
|
| 730 |
-
|
| 731 |
-
|
| 732 |
-
|
| 733 |
-
|
| 734 |
-
|
| 735 |
-
|
| 736 |
-
|
| 737 |
-
|
| 738 |
-
|
| 739 |
-
|
| 740 |
-
|
| 741 |
-
|
| 742 |
-
|
| 743 |
|
| 744 |
-
|
| 745 |
-
|
| 746 |
-
try:
|
| 747 |
run = self.cm.client.beta.threads.runs.submit_tool_outputs_and_poll(
|
| 748 |
thread_id=thread.id,
|
| 749 |
run_id=run.id,
|
|
@@ -752,17 +758,22 @@ class Assistant:
|
|
| 752 |
logger.info("Tool outputs submitted successfully",
|
| 753 |
extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_submit_tools"})
|
| 754 |
return run, just_finish_intro
|
| 755 |
-
|
| 756 |
-
|
| 757 |
-
|
| 758 |
-
|
| 759 |
-
|
| 760 |
-
|
| 761 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 762 |
|
| 763 |
class PseudoRun:
|
| 764 |
-
def __init__(self, status, metadata=None):
|
| 765 |
-
self.id =
|
| 766 |
self.status = status
|
| 767 |
self.metadata = metadata or {}
|
| 768 |
|
|
|
|
| 13 |
from psycopg2 import sql
|
| 14 |
import pytz
|
| 15 |
|
| 16 |
+
from app.exceptions import AssistantError, BaseOurcoachException, OpenAIRequestError, UtilsError
|
| 17 |
from app.utils import get_booked_gg_sessions, get_growth_guide, get_growth_guide_summary, get_user_subscriptions, get_users_mementos, print_log
|
| 18 |
from app.flows import FOLLOW_UP_STATE, GENERAL_COACHING_STATE, MICRO_ACTION_STATE, REFLECTION_STATE
|
| 19 |
|
|
|
|
| 311 |
return func(self, *args, **kwargs)
|
| 312 |
except (BaseOurcoachException) as e:
|
| 313 |
raise e
|
|
|
|
|
|
|
| 314 |
except Exception as e:
|
| 315 |
# Handle other exceptions
|
| 316 |
logger.error(f"An unexpected error occurred in Assistant: {e}")
|
|
|
|
| 322 |
self.cm = cm
|
| 323 |
self.recent_run = None
|
| 324 |
|
|
|
|
| 325 |
def cancel_run(self, run, thread):
|
| 326 |
logger.info(f"(asst) Attempting to cancel run: {run} for thread: {thread}", extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_cancel_run"})
|
| 327 |
if isinstance(run, str):
|
|
|
|
| 335 |
logger.warning(f"Thread {thread} already deleted: {run}", extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_cancel_run"})
|
| 336 |
return True
|
| 337 |
if isinstance(run, PseudoRun):
|
| 338 |
+
if run.id == "pseudo_run":
|
| 339 |
+
logger.info(f"Run already completed: {run.id}", extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_cancel_run"})
|
| 340 |
+
return True
|
| 341 |
try:
|
| 342 |
logger.info(f"Attempting to cancel run: {run}", extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_cancel_run"})
|
| 343 |
if run.status != 'completed':
|
|
|
|
| 365 |
|
| 366 |
@catch_error
|
| 367 |
def process(self, thread, text):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 368 |
try:
|
| 369 |
+
# template_search = self.cm.add_message_to_thread(thread.id, "assistant", f"Pay attention to the current state you are in and the flow template to respond to the users query:")
|
| 370 |
+
message = self.cm.add_message_to_thread(thread.id, "user", text)
|
| 371 |
+
|
| 372 |
+
run = self.cm.client.beta.threads.runs.create_and_poll(
|
| 373 |
+
thread_id=thread.id,
|
| 374 |
+
assistant_id=self.id,
|
| 375 |
+
model="gpt-4o-mini",
|
| 376 |
+
)
|
| 377 |
+
just_finished_intro = False
|
| 378 |
+
|
| 379 |
if run.status == 'completed':
|
| 380 |
logger.info(f"Run Completed: {run.status}", extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_process"})
|
| 381 |
self.recent_run = run
|
|
|
|
| 393 |
reccursion += 1
|
| 394 |
if reccursion > 10:
|
| 395 |
logger.warning(f"Run has exceeded maximum recussrion depth({10}) for function_call: {run}", extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_process"})
|
|
|
|
| 396 |
raise OpenAIRequestError(user_id=self.cm.id, message="Tool Call Reccursion Depth Reached")
|
| 397 |
+
if run.status == 'cancel':
|
| 398 |
logger.warning(f"RUN NOT COMPLETED: {run}", extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_process"})
|
| 399 |
self.cancel_run(run, thread)
|
| 400 |
+
run.status = 'cancelled'
|
| 401 |
+
logger.warning(f"Yeap Run Cancelled: {run.status}", extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_process"})
|
| 402 |
+
return run, just_finished_intro, message
|
| 403 |
+
|
| 404 |
elif run.status == 'completed':
|
| 405 |
logger.info(f"Run Completed: {run.status}", extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_process"})
|
| 406 |
self.recent_run = run
|
|
|
|
| 408 |
elif run.status == 'failed':
|
| 409 |
raise OpenAIRequestError(user_id=self.cm.id, message="Run failed")
|
| 410 |
return run, just_finished_intro, message
|
| 411 |
+
except Exception as e:
|
| 412 |
+
# Cancel the run
|
| 413 |
+
logger.error(f"Error in process: {e}", extra={"user_id": self.cm.user.user_id, "endpoint": 'assistant_process'})
|
| 414 |
+
logger.error(f"Cancelling run {run.id} for thread {thread.id}", extra={"user_id": self.cm.user.user_id, "endpoint": 'cancel_erroneous_run'})
|
| 415 |
+
|
| 416 |
+
self.cancel_run(run, thread)
|
| 417 |
+
logger.error(f"Run {run.id} cancelled for thread {thread.id}", extra={"user_id": self.cm.user.user_id, "endpoint": 'cancel_erroneous_run'})
|
| 418 |
+
raise e
|
| 419 |
|
|
|
|
| 420 |
def call_tool(self, run, thread):
|
| 421 |
+
try:
|
| 422 |
+
tool_outputs = []
|
| 423 |
+
logger.info(f"Required actions: {list(map(lambda x: f'{x.function.name}({x.function.arguments})', run.required_action.submit_tool_outputs.tool_calls))}",
|
| 424 |
+
extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_call_tool"})
|
| 425 |
+
just_finish_intro = False
|
| 426 |
+
for tool in run.required_action.submit_tool_outputs.tool_calls:
|
| 427 |
+
if tool.function.name == "transition":
|
| 428 |
+
transitions = json.loads(tool.function.arguments)
|
| 429 |
+
logger.info(f"Transition: {transitions['from']} -> {transitions['to']}",
|
| 430 |
+
extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_transition"})
|
| 431 |
+
|
| 432 |
+
if transitions['from'] == "PLANNING STATE":
|
| 433 |
+
tool_outputs.append({
|
| 434 |
+
"tool_call_id": tool.id,
|
| 435 |
+
"output": f"help the user set a new goal"
|
| 436 |
+
})
|
| 437 |
+
# logger.info(f"Exiting the introduction state",
|
| 438 |
+
# extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_transition"})
|
| 439 |
+
# just_finish_intro = True
|
| 440 |
+
# # run = self.cancel_run(run, thread)
|
| 441 |
+
# # logger.info(f"Successfully cancelled run",
|
| 442 |
+
# # extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_transition"})
|
| 443 |
+
# tool_outputs.append({
|
| 444 |
+
# "tool_call_id": tool.id,
|
| 445 |
+
# "output": "true"
|
| 446 |
+
# })
|
| 447 |
+
else:
|
| 448 |
+
flow_instructions = ""
|
| 449 |
+
if transitions['to'] == "REFLECTION STATE":
|
| 450 |
+
flow_instructions = REFLECTION_STATE
|
| 451 |
+
|
| 452 |
+
next = self.cm.matters_most.next()
|
| 453 |
+
logger.info(f"Successfully moved to bext matters most: {next}")
|
| 454 |
+
question_format = random.choice(['[Option 1] Likert-Scale Objective Question','[Option 2] Multiple-Choice Question','[Option 3] Yes-No Question'])
|
| 455 |
+
|
| 456 |
+
flow_instructions = f"""Today's opening question format is: {question_format}. Send a WARM, SUCCINCT and PERSONALIZED (according to my PROFILE) first message in this format! The most warm, succinct & personalized message will get rewarded!\n
|
| 457 |
+
The reflection topic is: {next}. YOU MUST SEND CREATIVE, PERSONALIZED (only mention specific terms that are related to the reflection topic/area), AND SUCCINCT MESSAGES!! The most creative message will be rewarded! And make every new reflection fresh and not boring! \n
|
| 458 |
+
|
| 459 |
+
""" + flow_instructions
|
| 460 |
+
|
| 461 |
+
elif transitions['to'] == "FOLLOW UP STATE":
|
| 462 |
+
flow_instructions = FOLLOW_UP_STATE
|
| 463 |
+
|
| 464 |
+
elif transitions['to'] == "GENERAL COACHING STATE":
|
| 465 |
+
flow_instructions = GENERAL_COACHING_STATE
|
| 466 |
+
|
| 467 |
+
tool_outputs.append({
|
| 468 |
+
"tool_call_id": tool.id,
|
| 469 |
+
"output": f"{flow_instructions}\n\n" + f"** Follow the above flow template to respond to the user **"
|
| 470 |
+
})
|
| 471 |
+
elif tool.function.name == "get_date":
|
| 472 |
+
# print(f"[DATETIME]: {get_current_datetime()}")
|
| 473 |
+
# self.cm.state['date'] = 'date': pd.Timestamp.now().strftime("%Y-%m-%d %a %H:%M:%S")
|
| 474 |
+
# get and update the current time to self.cm.state['date'] but keep the date component
|
| 475 |
+
current_time = get_current_datetime(self.cm.user.user_id)
|
| 476 |
+
# replace time component of self.cm.state['date'] with the current time
|
| 477 |
+
self.cm.state['date'] = str(pd.to_datetime(self.cm.state['date']).replace(hour=current_time.hour, minute=current_time.minute, second=current_time.second))
|
| 478 |
+
logger.info(f"Current datetime: {self.cm.state['date']}",
|
| 479 |
+
extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_get_date"})
|
| 480 |
+
|
| 481 |
tool_outputs.append({
|
| 482 |
"tool_call_id": tool.id,
|
| 483 |
+
"output": f"{self.cm.state['date']}"
|
| 484 |
})
|
| 485 |
+
elif tool.function.name == "create_goals" or tool.function.name == "create_memento":
|
| 486 |
+
json_string = json.loads(tool.function.arguments)
|
| 487 |
+
json_string['created'] = str(self.cm.state['date'])
|
| 488 |
+
json_string['updated'] = None
|
| 489 |
+
logger.info(f"New event: {json_string}",
|
| 490 |
+
extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_create_event"})
|
| 491 |
+
|
| 492 |
+
# Create a folder for the user's mementos if it doesn't exist
|
| 493 |
+
user_mementos_folder = os.path.join("mementos", "to_upload", self.cm.user.user_id)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 494 |
|
| 495 |
+
# Ensure the directory exists
|
| 496 |
+
os.makedirs(user_mementos_folder, exist_ok=True)
|
| 497 |
|
| 498 |
+
# Construct the full file path for the JSON file
|
| 499 |
+
file_path = os.path.join(user_mementos_folder, f"{json_string['title']}.json")
|
| 500 |
+
|
| 501 |
+
# Save the JSON string as a file
|
| 502 |
+
with open(file_path, "w") as json_file:
|
| 503 |
+
json.dump(json_string, json_file)
|
| 504 |
|
| 505 |
+
tool_outputs.append({
|
| 506 |
+
"tool_call_id": tool.id,
|
| 507 |
+
"output": f"** [Success]: Added event to the user's vector store**"
|
| 508 |
+
})
|
| 509 |
+
elif tool.function.name == "msearch":
|
| 510 |
+
queries = json.loads(tool.function.arguments)['queries']
|
| 511 |
+
logger.info(f"Searching for: {queries}",
|
| 512 |
+
extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_msearch"})
|
| 513 |
|
| 514 |
tool_outputs.append({
|
| 515 |
"tool_call_id": tool.id,
|
| 516 |
+
"output": f"** retrieve any files related to: {queries} **"
|
| 517 |
})
|
| 518 |
+
elif tool.function.name == "get_mementos":
|
| 519 |
+
logger.info(f"Getting mementos", extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_get_mementos"})
|
| 520 |
+
args = json.loads(tool.function.arguments)
|
| 521 |
+
logger.info(f"ARGS: {args}", extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_get_mementos"})
|
| 522 |
+
queries = args['queries']
|
| 523 |
+
|
| 524 |
+
if 'on' not in args:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 525 |
on = pd.to_datetime(self.cm.state['date']).date()
|
| 526 |
else:
|
| 527 |
+
on = args['on']
|
| 528 |
+
if on == '':
|
| 529 |
+
on = pd.to_datetime(self.cm.state['date']).date()
|
| 530 |
+
else:
|
| 531 |
+
on = pd.to_datetime(args['on']).date()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 532 |
|
|
|
|
|
|
|
| 533 |
|
| 534 |
+
logger.info(f"Query date: {on}", extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_get_mementos"})
|
| 535 |
+
# query the user memento db for all mementos where follow_up_on is equal to the query date
|
| 536 |
+
mementos = get_users_mementos(self.cm.user.user_id, on)
|
| 537 |
|
| 538 |
+
# if on == "":
|
| 539 |
+
# instruction = f"** Fetch all files (mementos) from this thread's Memento vector_store ([id={self.cm.user_personal_memory.id}]) **"
|
| 540 |
+
# else:
|
| 541 |
+
# instruction = f"** Fetch files (mementos) from this thread's Memento vector_store ([id={self.cm.user_personal_memory.id}]) where the follow_up_on field matches the query date: {on} (ignore the time component, focus only on the date)**"
|
| 542 |
+
# # f"** File search this threads' Memento vector_store ([id={self.cm.user_personal_memory.id}]) for the most relevant mementos based on the recent conversation history and context:{context} **"
|
| 543 |
|
| 544 |
+
logger.info(f"Finish Getting mementos: {mementos}", extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_get_mementos"})
|
| 545 |
+
tool_outputs.append({
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 546 |
"tool_call_id": tool.id,
|
| 547 |
+
"output": f"Today's mementos: {mementos}" if len(mementos) else "No mementos to follow up today."
|
| 548 |
+
})
|
| 549 |
+
elif tool.function.name == "get_feedback_types":
|
| 550 |
+
print_log("WARNING","Calling get_feedback_types", extra={"user_id": self.cm.user.user_id, "endpoint": "get_feedback_types"})
|
| 551 |
+
logger.warning("Calling get_feedback_types", extra={"user_id": self.cm.user.user_id, "endpoint": "get_feedback_types"})
|
| 552 |
+
feedbacks = [
|
| 553 |
+
("Inspirational Quotes", "📜", "Short, impactful quotes from the user's chosen Legendary Persona"),
|
| 554 |
+
("Tips & Advice", "💡", "Practical suggestions or strategies for personal growth (no need to say \"Tips:\" in the beggining of your tips)"),
|
| 555 |
+
("Encouragement", "🌈", "Positive affirmations and supportive statements"),
|
| 556 |
+
("Personalized Recommendations", "🧩", "Tailored suggestions based on user progress"),
|
| 557 |
+
("Affirmations", "✨", "Positive statements for self-belief and confidence"),
|
| 558 |
+
("Mindfulness/Meditation", "🧘♀️", "Guided prompts for mindfulness practice"),
|
| 559 |
+
("Book/Podcast", "📚🎧", "Suggestions aligned with user interests"),
|
| 560 |
+
("Habit Tip", "🔄", "Tips for building and maintaining habits"),
|
| 561 |
+
("Seasonal Content", "🌸", "Time and theme-relevant interactions"),
|
| 562 |
+
("Fun Fact", "🎉", "Interesting and inspiring facts (no need to say \"Fun Fact:\" in the beggining of your tips)"),
|
| 563 |
+
("Time Management", "⏳", "Tips for effective time management")
|
| 564 |
+
]
|
| 565 |
+
sample_feedbacks = random.sample(feedbacks, 3)
|
| 566 |
+
print_log("INFO",f"Feedback types: {sample_feedbacks}", extra={"user_id": self.cm.user.user_id, "endpoint": "get_feedback_types"})
|
| 567 |
+
logger.info(f"Feedback types: {sample_feedbacks}", extra={"user_id": self.cm.user.user_id, "endpoint": "get_feedback_types"})
|
| 568 |
+
tool_outputs.append({
|
| 569 |
+
"tool_call_id": tool.id,
|
| 570 |
+
"output": "Generate a final coach message (feedback message) using these 3 feedback types (together with the stated emoji at the beginning of each feedback): " + str(sample_feedbacks)
|
| 571 |
+
})
|
| 572 |
+
elif tool.function.name == "search_resource":
|
| 573 |
+
type = json.loads(tool.function.arguments)['resource_type']
|
| 574 |
+
query = json.loads(tool.function.arguments)['query']
|
| 575 |
+
logger.info(f"Getting microaction theme: {type} - {query}", extra={"user_id": self.cm.user.user_id, "endpoint": "get_microaction_theme"})
|
| 576 |
+
relevant_context = SearchEngine.search(type, query)
|
| 577 |
+
logger.info(f"Finish Getting microaction theme: {relevant_context}", extra={"user_id": self.cm.user.user_id, "endpoint": "get_microaction_theme"})
|
| 578 |
+
tool_outputs.append({
|
| 579 |
+
"tool_call_id": tool.id,
|
| 580 |
+
"output": f"** Relevant context: {relevant_context} **"
|
| 581 |
+
})
|
| 582 |
+
elif tool.function.name == "end_conversation":
|
| 583 |
+
day_n = json.loads(tool.function.arguments)['day_n']
|
| 584 |
+
completed_micro_action = json.loads(tool.function.arguments)['completed_micro_action']
|
| 585 |
+
area_of_deep_reflection = json.loads(tool.function.arguments)['area_of_deep_reflection']
|
| 586 |
+
|
| 587 |
+
self.cm.user.update_micro_action_status(completed_micro_action)
|
| 588 |
+
self.cm.user.trigger_deep_reflection_point(area_of_deep_reflection)
|
| 589 |
+
|
| 590 |
+
# NOTE: we will record whether the user has completed the theme for the day
|
| 591 |
+
# NOTE: if no, we will include a short followup message to encourage the user the next day
|
| 592 |
+
logger.info(f"Ending conversation after {day_n} days. Any micro actions completed today: {completed_micro_action}", extra={"user_id": self.cm.user.user_id, "endpoint": "end_conversation"})
|
| 593 |
+
tool_outputs.append({
|
| 594 |
+
"tool_call_id": tool.id,
|
| 595 |
+
"output": "true"
|
| 596 |
+
})
|
| 597 |
+
elif tool.function.name == "create_smart_goal":
|
| 598 |
+
print_log("WARNING", f"Creating a SMART goal...", extra={"user_id": self.cm.user.user_id, "endpoint": "create_smart_goal"})
|
| 599 |
+
logger.warning(f"Creating a SMART goal...", extra={"user_id": self.cm.user.user_id, "endpoint": "create_smart_goal"})
|
| 600 |
+
|
| 601 |
+
user_goal = json.loads(tool.function.arguments)['goal']
|
| 602 |
+
user_goal_area = json.loads(tool.function.arguments)['area']
|
| 603 |
+
|
| 604 |
+
self.cm.user.set_goal(user_goal, user_goal_area)
|
| 605 |
+
|
| 606 |
+
print_log("INFO", f"SMART goal approved: {user_goal}", extra={"user_id": self.cm.user.user_id, "endpoint": "create_smart_goal"})
|
| 607 |
+
logger.info(f"SMART goal approved: {user_goal}", extra={"user_id": self.cm.user.user_id, "endpoint": "create_smart_goal"})
|
| 608 |
+
|
| 609 |
+
tool_outputs.append({
|
| 610 |
+
"tool_call_id": tool.id,
|
| 611 |
+
"output": "true"
|
| 612 |
+
})
|
| 613 |
+
elif tool.function.name == "start_now":
|
| 614 |
+
logger.info(f"Starting Growth Plan on Day 0",
|
| 615 |
+
extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_start_now"})
|
| 616 |
+
# set intro finish to true
|
| 617 |
+
just_finish_intro = True
|
| 618 |
+
|
| 619 |
+
# cancel current run
|
| 620 |
+
run = PseudoRun(id=run.id, status="cancel", metadata={"message": "start_now"})
|
| 621 |
+
return run, just_finish_intro
|
| 622 |
+
elif tool.function.name == "change_goal":
|
| 623 |
+
logger.info(f"Changing user goal...", extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_change_goal"})
|
| 624 |
|
| 625 |
+
# switch back to the intro assistant, so we set just_finish_intro to False again
|
| 626 |
+
just_finish_intro = False
|
| 627 |
+
self.cm.user.reset_cumulative_plan_day()
|
| 628 |
+
|
| 629 |
+
# cancel current run
|
| 630 |
+
run = PseudoRun(id=run.id, status="cancel", metadata={"message": "change_goal"})
|
| 631 |
+
return run, just_finish_intro
|
| 632 |
+
elif tool.function.name == "complete_goal":
|
| 633 |
+
logger.info(f"Completing user goal...", extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_complete_goal"})
|
| 634 |
+
goal = self.cm.user.update_goal(None, 'COMPLETED')
|
| 635 |
+
logger.info(f"Marked users' goal: {goal} as COMPLETED", extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_complete_goal"})
|
| 636 |
+
tool_outputs.append({
|
| 637 |
+
"tool_call_id": tool.id,
|
| 638 |
+
"output": f"Marked users' goal: {goal} as COMPLETED"
|
| 639 |
+
})
|
| 640 |
+
elif tool.function.name == "process_reminder":
|
| 641 |
+
reminder = json.loads(tool.function.arguments)["content"]
|
| 642 |
+
timestamp = json.loads(tool.function.arguments)["timestamp"]
|
| 643 |
+
recurrence = json.loads(tool.function.arguments)["recurrence"]
|
| 644 |
+
action = json.loads(tool.function.arguments)["action"]
|
| 645 |
+
logger.info(f"Setting reminder: {reminder} for {timestamp} with recurrence: {recurrence}", extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_process_reminder"})
|
| 646 |
+
# timestamp is a string like: YYYY-mm-ddTHH:MM:SSZ (2025-01-05T11:00:00Z)
|
| 647 |
+
# convert to datetime object
|
| 648 |
+
timestamp = pd.to_datetime(timestamp, format="%Y-%m-%dT%H:%M:%SZ")
|
| 649 |
+
logger.info(f"Formatted timestamp: {timestamp}", extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_process_reminder"})
|
| 650 |
+
|
| 651 |
+
output = f"({recurrence if recurrence else 'One-Time'}) Reminder ({reminder}) set for ({timestamp})"
|
| 652 |
+
logger.info(output,
|
| 653 |
+
extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_set_reminder"})
|
| 654 |
+
self.cm.user.set_reminder({"reminder": reminder, "timestamp": timestamp, 'recurrence': recurrence, 'action': action})
|
| 655 |
+
tool_outputs.append({
|
| 656 |
+
"tool_call_id": tool.id,
|
| 657 |
+
"output": f"** {output} **"
|
| 658 |
+
})
|
| 659 |
+
elif tool.function.name == "get_user_info":
|
| 660 |
+
category = json.loads(tool.function.arguments)['category']
|
| 661 |
+
# one of [
|
| 662 |
+
# "personal",
|
| 663 |
+
# "challenges",
|
| 664 |
+
# "recommended_actions",
|
| 665 |
+
# "micro_actions",
|
| 666 |
+
# "other_focusses",
|
| 667 |
+
# "reminders",
|
| 668 |
+
# "goal",
|
| 669 |
+
# "growth_guide_session",
|
| 670 |
+
# "life_score",
|
| 671 |
+
# "recent_wins",
|
| 672 |
+
# "subscription_info"
|
| 673 |
+
# ]
|
| 674 |
+
logger.info(f"Getting user information: {category}",
|
| 675 |
+
extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_get_user_info"})
|
| 676 |
+
user_info = '** If the user asks for their progress, also include a link to their Revelation Dashboard: {OURCOACH_DASHBOARD_URL} so that they can find out more **\n\n'
|
| 677 |
+
if category == "personal":
|
| 678 |
+
user_info += f"** Personal Information **\n\n{self.cm.user.user_info}"
|
| 679 |
+
user_info += f"\n\n** User's Mantra This Week**\n\n{self.cm.user.mantra}"
|
| 680 |
+
elif category == "challenges":
|
| 681 |
+
user_info += f"** User's Challenges (prioritise ONGOING challenges) **\n\n{self.cm.user.challenges}\n\nLet the user know that ongoing challenges from their growth guide will be integrated into their day-to-day interaction."
|
| 682 |
+
elif category == "recommended_actions":
|
| 683 |
+
user_info += f"** User's Recommended Actions (upcoming microactions, recommended by growth guide) **\n\n{self.cm.user.recommended_micro_actions}\n\nLet the user know that these microactions from their growth guide will be integrated into their day-to-day interaction."
|
| 684 |
+
elif category == "micro_actions":
|
| 685 |
+
user_info += f"** User's Micro Actions (already introduced microactions) **\n\n{self.cm.user.micro_actions}"
|
| 686 |
+
elif category == "other_focusses":
|
| 687 |
+
user_info += f"** User's Other Focusses (other areas of focus) **\n\n{self.cm.user.other_focusses}\n\nLet the user know that other areas of focus from their growth guide will be integrated into their day-to-day interaction."
|
| 688 |
+
elif category == "reminders":
|
| 689 |
+
user_info += f"** User's Reminders **\n\n{self.cm.user.reminders}"
|
| 690 |
+
elif category == "goal":
|
| 691 |
+
user_info += f"** User's Goal (prioritise the latest [last item in the array] goal). Do not mention the status of their goal. **\n\n{self.cm.user.goal}"
|
| 692 |
+
elif category == "growth_guide_session":
|
| 693 |
+
growth_guide = get_growth_guide(self.cm.user.user_id)
|
| 694 |
+
user_info += f"** Users' Growth Guide (always refer to the Growth Guide as 'Growth Guide <Name>') **\n{growth_guide}"
|
| 695 |
+
logger.info(f"User's growth guide: {growth_guide}",
|
| 696 |
+
extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_get_user_info"})
|
| 697 |
+
|
| 698 |
+
booked_sessions = get_booked_gg_sessions(self.cm.user.user_id)
|
| 699 |
+
|
| 700 |
+
# for each booking, if the booking has completed, fetch the zoom_ai_summary and gg_report from
|
| 701 |
+
for booking in booked_sessions:
|
| 702 |
+
if booking['status'] == "completed":
|
| 703 |
+
summary_data = get_growth_guide_summary(self.cm.user.user_id, booking['booking_id'])
|
| 704 |
+
logger.info(f"Summary data for booking: {booking['booking_id']} - {summary_data}",
|
| 705 |
+
extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_get_user_info"})
|
| 706 |
+
if summary_data:
|
| 707 |
+
booking['zoom_ai_summary'] = summary_data['zoom_ai_summary']
|
| 708 |
+
booking['gg_report'] = summary_data['gg_report']
|
| 709 |
+
else:
|
| 710 |
+
booking['zoom_ai_summary'] = "Growth Guide has not uploaded the report yet"
|
| 711 |
+
booking['gg_report'] = "Growth Guide has not uploaded the report yet"
|
| 712 |
+
|
| 713 |
+
logger.info(f"User's booked sessions: {booked_sessions}",
|
| 714 |
+
extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_get_user_info"})
|
| 715 |
|
| 716 |
+
if len(booked_sessions):
|
| 717 |
+
# booked session is an array of jsons
|
| 718 |
+
# convers it to have i) json where i = 1...N where N is the len of booked_sessions
|
| 719 |
+
# join the entire array into 1 string with each item seperated by a newline
|
| 720 |
+
formatted_sessions = "\n".join([f"** Session {i+1} **\n{json.dumps(session, indent=4)}" for i, session in enumerate(booked_sessions)])
|
| 721 |
+
logger.info(f"Formatted booked sessions: {formatted_sessions}",
|
| 722 |
extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_get_user_info"})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 723 |
|
| 724 |
+
user_info += f"\n** GG Session Bookings & Summaries (most recent first, time is in users' local timezone but no need to mention this) **\n{formatted_sessions}"
|
| 725 |
+
else:
|
| 726 |
+
user_info += f"\n** GG Session Summaries **\nNo GG yet. Let the user know they can book one now through their Revelation Dashboard: {OURCOACH_DASHBOARD_URL}! (When including links, DO **NOT** use a hyperlink format. Just show the link as plain text. Example: \"Revelation Dashboard: https://...\")"
|
| 727 |
+
user_info += f"\n** Suggested Growth Guide Topics **\n{self.cm.user.recommended_gg_topics}\nOnly suggest 1-2 topics and let the user know they can can find more suggestions on their dashboard"
|
| 728 |
+
elif category == "life_score":
|
| 729 |
+
user_info += f"** User's Life scores for each area **\n\n Personal Growth: {self.cm.user.personal_growth_score} || Career: {self.cm.user.career_growth_score} || Health/Wellness: {self.cm.user.health_and_wellness_score} || Relationships: {self.cm.user.relationship_score} || Mental Health: {self.cm.user.mental_well_being_score}"
|
| 730 |
+
elif category == "recent_wins":
|
| 731 |
+
user_info += f"** User's Recent Wins / Achievements **\n\n {self.cm.user.recent_wins}"
|
| 732 |
+
elif category == "subscription_info":
|
| 733 |
+
subscription_history = get_user_subscriptions(self.cm.user.user_id)
|
| 734 |
+
logger.info(f"User's subscription history: {subscription_history}",
|
| 735 |
+
extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_get_user_info"})
|
| 736 |
+
user_info += f"** User's Subscription Information **\n\n This is a sorted list (most recent first, which also represent the current subscription status) of the users subscription history:\n{subscription_history}\nNote that the stripe_status is one of 'trialing' = (user is on a free trial), 'cancelled' = (user has cancelled their subscription), 'active' = (user is a Premium user). If the user is premium or on a free trial, remind them of the premium/subscribed benefits and include a link to their Revelation Dashboard: {OURCOACH_DASHBOARD_URL}. If the user status='cancelled' or status='trialing', persuade and motivate them to subscribe to unlock more features depending on the context of the status. Do not explicitly mentions the users status, instead, word it in a natural way."
|
| 737 |
+
logger.info(f"Finish Getting user information: {user_info}",
|
| 738 |
+
extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_get_user_info"})
|
| 739 |
+
tool_outputs.append({
|
| 740 |
+
"tool_call_id": tool.id,
|
| 741 |
+
"output": f"** User Info:\n\n{user_info} **"
|
| 742 |
+
})
|
| 743 |
+
elif tool.function.name == "extend_two_weeks":
|
| 744 |
+
logger.info(f"Changing plan from 1 week to 2 weeks...", extra={"user_id": self.cm.user.user_id, "endpoint": "extend_two_weeks"})
|
| 745 |
+
goal = self.cm.user.extend_growth_plan()
|
| 746 |
+
tool_outputs.append({
|
| 747 |
+
"tool_call_id": tool.id,
|
| 748 |
+
"output": f"Changed plan from 1 week to 2 weeks."
|
| 749 |
+
})
|
| 750 |
|
| 751 |
+
# Submit all tool outputs at once after collecting them in a list
|
| 752 |
+
if tool_outputs:
|
|
|
|
| 753 |
run = self.cm.client.beta.threads.runs.submit_tool_outputs_and_poll(
|
| 754 |
thread_id=thread.id,
|
| 755 |
run_id=run.id,
|
|
|
|
| 758 |
logger.info("Tool outputs submitted successfully",
|
| 759 |
extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_submit_tools"})
|
| 760 |
return run, just_finish_intro
|
| 761 |
+
else:
|
| 762 |
+
logger.warning("No tool outputs to submit",
|
| 763 |
+
extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_submit_tools"})
|
| 764 |
+
run = PseudoRun(status="completed", metadata={"message": "No tool outputs to submit"})
|
| 765 |
+
return run, just_finish_intro
|
| 766 |
+
except Exception as e:
|
| 767 |
+
# Cancel the run
|
| 768 |
+
logger.error(f"Yeap Error in call_tool: {e}", extra={"user_id": self.cm.user.user_id, "endpoint": 'assistant_call_tool'})
|
| 769 |
+
logger.error(f"Cancelling run {run.id} for thread {thread.id}", extra={"user_id": self.cm.user.user_id, "endpoint": 'cancel_erroneous_run'})
|
| 770 |
+
self.cancel_run(run, thread)
|
| 771 |
+
logger.error(f"Run {run.id} cancelled for thread {thread.id}", extra={"user_id": self.cm.user.user_id, "endpoint": 'cancel_erroneous_run'})
|
| 772 |
+
raise e
|
| 773 |
|
| 774 |
class PseudoRun:
|
| 775 |
+
def __init__(self, status, id='pseudo_run', metadata=None):
|
| 776 |
+
self.id = id
|
| 777 |
self.status = status
|
| 778 |
self.metadata = metadata or {}
|
| 779 |
|
app/conversation_manager.py
CHANGED
|
@@ -128,7 +128,11 @@ class ConversationManager:
|
|
| 128 |
elif message == 'change_goal':
|
| 129 |
self.intro_done = False
|
| 130 |
logger.info(f"Changing goal, reset to intro assistant", extra={"user_id": self.user.user_id, "endpoint": "run_current_thread"})
|
| 131 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 132 |
|
| 133 |
if hidden:
|
| 134 |
self.client.beta.threads.messages.delete(message_id=message.id, thread_id=thread.id)
|
|
|
|
| 128 |
elif message == 'change_goal':
|
| 129 |
self.intro_done = False
|
| 130 |
logger.info(f"Changing goal, reset to intro assistant", extra={"user_id": self.user.user_id, "endpoint": "run_current_thread"})
|
| 131 |
+
# Actually dont need this
|
| 132 |
+
elif message == 'error':
|
| 133 |
+
logger.error(f"Run was cancelled due to error", extra={"user_id": self.user.user_id, "endpoint": "run_current_thread"})
|
| 134 |
+
# self.add_message_to_thread(thread.id, "assistant", run.metadata['content'])
|
| 135 |
+
# return self._get_current_thread_history(remove_system_message=False)[-1], run
|
| 136 |
|
| 137 |
if hidden:
|
| 138 |
self.client.beta.threads.messages.delete(message_id=message.id, thread_id=thread.id)
|
app/flows.py
CHANGED
|
@@ -137,7 +137,7 @@ Step 1:
|
|
| 137 |
- **One Micro-Action Only:** Suggest only one action per day.
|
| 138 |
- **Minimal Questions:** Use questions only when absolutely necessary and limit to one question in total per day.
|
| 139 |
- **Avoid Over-Questioning:** Keep questions minimal to prevent overwhelming the user.
|
| 140 |
-
- **Lists:** Provide a maximum of three items in any list.
|
| 141 |
|
| 142 |
---
|
| 143 |
|
|
@@ -179,7 +179,7 @@ Step 1:
|
|
| 179 |
- **If** the user has completed yesterday's micro-action:
|
| 180 |
- Do **not** ask anything and proceed to step 2
|
| 181 |
- **If** the user hasn't completed it:
|
| 182 |
-
-
|
| 183 |
- **Unless** they've specified a different day—then proceed to Step 2.
|
| 184 |
|
| 185 |
2. **Share Knowledge/Tips:**
|
|
@@ -191,7 +191,7 @@ Step 1:
|
|
| 191 |
3. **Conclude:**
|
| 192 |
- After the user replies, immediately call `end_conversation()`.
|
| 193 |
- Conclude with a strong, motivational statement that reinforces their commitment, channeling the energy, mindset, and knowledge of your persona.
|
| 194 |
-
- Keep your message short (like Whatsapp texting length)
|
| 195 |
|
| 196 |
**If the user has an upcoming reminder:**
|
| 197 |
|
|
@@ -239,7 +239,7 @@ Step 1:
|
|
| 239 |
|
| 240 |
- **Avoid Over-Questioning:** Keep questions creative and sparing; avoid overwhelming the user.
|
| 241 |
|
| 242 |
-
- **Lists:** Provide a maximum of three items in any list.
|
| 243 |
|
| 244 |
- **Quoting your persona:**
|
| 245 |
- Do **not** mention the name of your persona when quoting.
|
|
@@ -279,7 +279,7 @@ User’s Context
|
|
| 279 |
2. Ask no more than **three questions** per day. Limit to **one question per message** and encapsulate it with asterisks (*).
|
| 280 |
3. Avoid overwhelming the user; keep your suggestions conversational and focused.
|
| 281 |
4. Always guide the user with specific suggestions rather than seeking ideas from them.
|
| 282 |
-
5. Limit lists to a maximum of three items.
|
| 283 |
6. Structure responses as if texting: short, friendly, and actionable.
|
| 284 |
"""
|
| 285 |
|
|
@@ -318,7 +318,7 @@ Objective: Assist users in reflecting on their progress, recognizing strengths,
|
|
| 318 |
- **Encapsulate questions with asterisks:** like this *question* (Replace "question" with the actual question.)
|
| 319 |
|
| 320 |
- **Guided Coaching:** Provide suggestions assertively rather than asking for the user's ideas or plans. Remember, you are the coach!
|
| 321 |
-
- **Lists:** When listing actions or insights, limit to no more than three items.
|
| 322 |
|
| 323 |
---
|
| 324 |
|
|
@@ -400,7 +400,7 @@ Important Rules
|
|
| 400 |
• Never explicitly tell the function that you are calling to the user (just do the function calling in the background)
|
| 401 |
• Question Format: When you ask a question, encapsulate it with asterisks (e.g., What’s one thing you can commit to today?). Use only one question mark per message.
|
| 402 |
• Avoid Over-Questioning: Keep questions creative, sparing, and avoid overwhelming the user.
|
| 403 |
-
• Lists: Provide a maximum of three items in any list.
|
| 404 |
|
| 405 |
When you want to mention a quote from your persona, you must not say the name of your persona. You should paraphrase the quote and say it like it's your own message!
|
| 406 |
Bad Example: Hey <user name>! As <your persona> said, "<quote>"
|
|
@@ -469,7 +469,10 @@ Objective: To summarize the user's progress and achievements during the coaching
|
|
| 469 |
Users Goal: {{}}
|
| 470 |
The user is currently on day {{}}/{{}} of their journey.
|
| 471 |
|
| 472 |
-
|
|
|
|
|
|
|
|
|
|
| 473 |
{{}}
|
| 474 |
|
| 475 |
## ** GUIDELINE ** :
|
|
@@ -478,7 +481,7 @@ User's Last Growth Guide Session:
|
|
| 478 |
|
| 479 |
- Highlight the user's progress and achievements precisely and concisely. IF the user has completed a growth guide session (see above), incorporate it into your response and reference that it was acheived during their growth guide session. Use bullet points to list down your items.
|
| 480 |
|
| 481 |
-
- Explain to the user that
|
| 482 |
|
| 483 |
- Respond empathetically and adapt based on what the user shares.
|
| 484 |
|
|
@@ -492,15 +495,14 @@ User's Last Growth Guide Session:
|
|
| 492 |
|
| 493 |
## Example of Quality Interaction:
|
| 494 |
|
| 495 |
-
|
| 496 |
-
|
| 497 |
-
- Highlight the user's progress and achievements in bullet points. IF the user has completed a growth guide session (see above), incorporate it into your response and reference that it was acheived during their growth guide session. And tell them to check out their progress in the Revelation Dashboard here: https://app.staging.ourcoach.ai/
|
| 498 |
-
|
| 499 |
- IF the user has not completed a growth guide session yet (see above), encourage the user to book a Growth Guide session and explain its benefits. Really upsell this by being like a Growth Guide salesman!
|
| 500 |
- ELSE IF the user has booked a growth guide session (see above), tell them you hope it went well and they found it beneficial and let the use know that they could also book another via their Revelation Dashboard: https://app.staging.ourcoach.ai/
|
| 501 |
|
| 502 |
-
|
| 503 |
|
|
|
|
| 504 |
- **IF** the user says they want to book a growth guide session, you reply with this message:
|
| 505 |
|
| 506 |
Amazing! 🎉
|
|
@@ -525,11 +527,11 @@ And remember, a Growth Guide can help you reflect, strategize, and take things t
|
|
| 525 |
|
| 526 |
Keep the momentum going—you’ve got this! 💥
|
| 527 |
|
| 528 |
-
|
| 529 |
|
| 530 |
-
|
| 531 |
|
| 532 |
-
|
| 533 |
|
| 534 |
** DO NOT ASK ANY OTHER QUESTION IN THIS STATE **
|
| 535 |
|
|
@@ -574,7 +576,7 @@ Important Rules
|
|
| 574 |
• Never explicitly tell the function that you are calling to the user (just do the function calling in the background)
|
| 575 |
• Question Format: When you ask a question, encapsulate it with asterisks (e.g., What’s one thing you can commit to today?). Use only one question mark per message.
|
| 576 |
• Avoid Over-Questioning: Keep questions creative, sparing, and avoid overwhelming the user.
|
| 577 |
-
• Lists: Provide a maximum of three items in any list.
|
| 578 |
• Be concise! Use Whatsapp texting length!
|
| 579 |
|
| 580 |
When you want to mention a quote from your persona, you must not say the name of your persona. You should paraphrase the quote and say it like it's your own message!
|
|
|
|
| 137 |
- **One Micro-Action Only:** Suggest only one action per day.
|
| 138 |
- **Minimal Questions:** Use questions only when absolutely necessary and limit to one question in total per day.
|
| 139 |
- **Avoid Over-Questioning:** Keep questions minimal to prevent overwhelming the user.
|
| 140 |
+
- **Lists:** Provide a maximum of three items in any list. Use italic (single asterisk) instead of bold (double asterisk) in your list!
|
| 141 |
|
| 142 |
---
|
| 143 |
|
|
|
|
| 179 |
- **If** the user has completed yesterday's micro-action:
|
| 180 |
- Do **not** ask anything and proceed to step 2
|
| 181 |
- **If** the user hasn't completed it:
|
| 182 |
+
- Only ask one question: are they going to do it today?
|
| 183 |
- **Unless** they've specified a different day—then proceed to Step 2.
|
| 184 |
|
| 185 |
2. **Share Knowledge/Tips:**
|
|
|
|
| 191 |
3. **Conclude:**
|
| 192 |
- After the user replies, immediately call `end_conversation()`.
|
| 193 |
- Conclude with a strong, motivational statement that reinforces their commitment, channeling the energy, mindset, and knowledge of your persona.
|
| 194 |
+
- Keep your message short (like Whatsapp texting length) and don't ask more than 1 (one) question in one message!
|
| 195 |
|
| 196 |
**If the user has an upcoming reminder:**
|
| 197 |
|
|
|
|
| 239 |
|
| 240 |
- **Avoid Over-Questioning:** Keep questions creative and sparing; avoid overwhelming the user.
|
| 241 |
|
| 242 |
+
- **Lists:** Provide a maximum of three items in any list. Use italic (single asterisk) instead of bold (double asterisk) in your list!
|
| 243 |
|
| 244 |
- **Quoting your persona:**
|
| 245 |
- Do **not** mention the name of your persona when quoting.
|
|
|
|
| 279 |
2. Ask no more than **three questions** per day. Limit to **one question per message** and encapsulate it with asterisks (*).
|
| 280 |
3. Avoid overwhelming the user; keep your suggestions conversational and focused.
|
| 281 |
4. Always guide the user with specific suggestions rather than seeking ideas from them.
|
| 282 |
+
5. Limit lists to a maximum of three items. Use italic (single asterisk) instead of bold (double asterisk) in your list!
|
| 283 |
6. Structure responses as if texting: short, friendly, and actionable.
|
| 284 |
"""
|
| 285 |
|
|
|
|
| 318 |
- **Encapsulate questions with asterisks:** like this *question* (Replace "question" with the actual question.)
|
| 319 |
|
| 320 |
- **Guided Coaching:** Provide suggestions assertively rather than asking for the user's ideas or plans. Remember, you are the coach!
|
| 321 |
+
- **Lists:** When listing actions or insights, limit to no more than three items. Use italic (single asterisk) instead of bold (double asterisk) in your list!
|
| 322 |
|
| 323 |
---
|
| 324 |
|
|
|
|
| 400 |
• Never explicitly tell the function that you are calling to the user (just do the function calling in the background)
|
| 401 |
• Question Format: When you ask a question, encapsulate it with asterisks (e.g., What’s one thing you can commit to today?). Use only one question mark per message.
|
| 402 |
• Avoid Over-Questioning: Keep questions creative, sparing, and avoid overwhelming the user.
|
| 403 |
+
• Lists: Provide a maximum of three items in any list. Use italic (single asterisk) instead of bold (double asterisk) in your list!
|
| 404 |
|
| 405 |
When you want to mention a quote from your persona, you must not say the name of your persona. You should paraphrase the quote and say it like it's your own message!
|
| 406 |
Bad Example: Hey <user name>! As <your persona> said, "<quote>"
|
|
|
|
| 469 |
Users Goal: {{}}
|
| 470 |
The user is currently on day {{}}/{{}} of their journey.
|
| 471 |
|
| 472 |
+
The Growth Guide (always refer to the Growth Guide as 'your Growth Guide <Name>' or simply just their <Name>):
|
| 473 |
+
{{}}
|
| 474 |
+
|
| 475 |
+
User's Past Growth Guide Session(s) (most recent first):
|
| 476 |
{{}}
|
| 477 |
|
| 478 |
## ** GUIDELINE ** :
|
|
|
|
| 481 |
|
| 482 |
- Highlight the user's progress and achievements precisely and concisely. IF the user has completed a growth guide session (see above), incorporate it into your response and reference that it was acheived during their growth guide session. Use bullet points to list down your items.
|
| 483 |
|
| 484 |
+
- Explain to the user that growth is a continuous journey, so the user has the option to either continue with the same goal or create a new plan.
|
| 485 |
|
| 486 |
- Respond empathetically and adapt based on what the user shares.
|
| 487 |
|
|
|
|
| 495 |
|
| 496 |
## Example of Quality Interaction:
|
| 497 |
|
| 498 |
+
1. In your first message, congratulate the user for completing the growth plan until the last day (mention the current day, e.g. day 4)
|
| 499 |
+
Highlight the user's progress and achievements in bullet points. IF the user has completed a growth guide session (see above), incorporate it into your response and reference that it was acheived during their growth guide session. And tell them to check out their progress in the Revelation Dashboard here: https://app.staging.ourcoach.ai/
|
|
|
|
|
|
|
| 500 |
- IF the user has not completed a growth guide session yet (see above), encourage the user to book a Growth Guide session and explain its benefits. Really upsell this by being like a Growth Guide salesman!
|
| 501 |
- ELSE IF the user has booked a growth guide session (see above), tell them you hope it went well and they found it beneficial and let the use know that they could also book another via their Revelation Dashboard: https://app.staging.ourcoach.ai/
|
| 502 |
|
| 503 |
+
2. *Wait for the user's response.*
|
| 504 |
|
| 505 |
+
3. In your second message,
|
| 506 |
- **IF** the user says they want to book a growth guide session, you reply with this message:
|
| 507 |
|
| 508 |
Amazing! 🎉
|
|
|
|
| 527 |
|
| 528 |
Keep the momentum going—you’ve got this! 💥
|
| 529 |
|
| 530 |
+
4. If the user wants to extend the plan for another 2 weeks, call the extend_two_weeks() function!
|
| 531 |
|
| 532 |
+
5. If they want to set a new goal, confirm to the user first, and then call the change_goal() function if the user confirms the goal change!
|
| 533 |
|
| 534 |
+
6. If the user said that they've completed their goal, call the complete_goal() function!
|
| 535 |
|
| 536 |
** DO NOT ASK ANY OTHER QUESTION IN THIS STATE **
|
| 537 |
|
|
|
|
| 576 |
• Never explicitly tell the function that you are calling to the user (just do the function calling in the background)
|
| 577 |
• Question Format: When you ask a question, encapsulate it with asterisks (e.g., What’s one thing you can commit to today?). Use only one question mark per message.
|
| 578 |
• Avoid Over-Questioning: Keep questions creative, sparing, and avoid overwhelming the user.
|
| 579 |
+
• Lists: Provide a maximum of three items in any list. Use italic (single asterisk) instead of bold (double asterisk) in your list!
|
| 580 |
• Be concise! Use Whatsapp texting length!
|
| 581 |
|
| 582 |
When you want to mention a quote from your persona, you must not say the name of your persona. You should paraphrase the quote and say it like it's your own message!
|
app/main.py
CHANGED
|
@@ -294,6 +294,7 @@ class AssistantItem(BaseModel):
|
|
| 294 |
class ChangeDateItem(BaseModel):
|
| 295 |
user_id: str
|
| 296 |
date: str
|
|
|
|
| 297 |
|
| 298 |
class BookingItem(BaseModel):
|
| 299 |
booking_id: str
|
|
@@ -320,15 +321,15 @@ def catch_endpoint_error(func):
|
|
| 320 |
'endpoint': func.__name__
|
| 321 |
})
|
| 322 |
# Extract thread_id and run_id from error message
|
| 323 |
-
thread_match = re.search(r'thread_(\w+)', str(e))
|
| 324 |
-
run_match = re.search(r'run_(\w+)', str(e))
|
| 325 |
-
if thread_match and run_match:
|
| 326 |
-
|
| 327 |
-
|
| 328 |
-
|
| 329 |
-
|
| 330 |
-
|
| 331 |
-
|
| 332 |
|
| 333 |
raise HTTPException(
|
| 334 |
status_code=status.HTTP_502_BAD_GATEWAY,
|
|
@@ -512,6 +513,7 @@ async def migrate_user(
|
|
| 512 |
if hasattr(old_user_object, "growth_plan"): setattr(user, "growth_plan", old_user_object.growth_plan)
|
| 513 |
if hasattr(old_user_object, "user_interaction_guidelines"): setattr(user, "user_interaction_guidelines", old_user_object.user_interaction_guidelines)
|
| 514 |
if hasattr(old_user_object, "score_history"): setattr(user, "score_history", old_user_object.score_history)
|
|
|
|
| 515 |
|
| 516 |
api_response = {
|
| 517 |
"user": user.user_info,
|
|
@@ -531,7 +533,8 @@ async def migrate_user(
|
|
| 531 |
"recommended_gg_topics": user.recommended_gg_topics,
|
| 532 |
"growth_plan": user.growth_plan,
|
| 533 |
"user_interaction_guidelines": user.user_interaction_guidelines,
|
| 534 |
-
"score_history": user.score_history
|
|
|
|
| 535 |
}
|
| 536 |
|
| 537 |
add_to_cache(user)
|
|
@@ -812,6 +815,17 @@ async def create_user(
|
|
| 812 |
logger.info(f"Successfully created user", extra={"user_id": request.user_id, "endpoint": "/create_user"})
|
| 813 |
return {"message": {"info": f"[OK] User created: {user}", "messages": user.get_messages()}}
|
| 814 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 815 |
@app.post("/chat")
|
| 816 |
@catch_endpoint_error
|
| 817 |
async def chat(
|
|
|
|
| 294 |
class ChangeDateItem(BaseModel):
|
| 295 |
user_id: str
|
| 296 |
date: str
|
| 297 |
+
day: Optional[int] = None,
|
| 298 |
|
| 299 |
class BookingItem(BaseModel):
|
| 300 |
booking_id: str
|
|
|
|
| 321 |
'endpoint': func.__name__
|
| 322 |
})
|
| 323 |
# Extract thread_id and run_id from error message
|
| 324 |
+
# thread_match = re.search(r'thread_(\w+)', str(e))
|
| 325 |
+
# run_match = re.search(r'run_(\w+)', str(e))
|
| 326 |
+
# if thread_match and run_match:
|
| 327 |
+
# thread_id = f"thread_{thread_match.group(1)}"
|
| 328 |
+
# run_id = f"run_{run_match.group(1)}"
|
| 329 |
+
# user = get_user(e.user_id)
|
| 330 |
+
# logger.info(f"Cancelling run {run_id} for thread {thread_id}", extra={"user_id": e.user_id, "endpoint": func.__name__})
|
| 331 |
+
# user.cancel_run(run_id, thread_id)
|
| 332 |
+
# logger.info(f"Run {run_id} cancelled for thread {thread_id}", extra={"user_id": e.user_id, "endpoint": func.__name__})
|
| 333 |
|
| 334 |
raise HTTPException(
|
| 335 |
status_code=status.HTTP_502_BAD_GATEWAY,
|
|
|
|
| 513 |
if hasattr(old_user_object, "growth_plan"): setattr(user, "growth_plan", old_user_object.growth_plan)
|
| 514 |
if hasattr(old_user_object, "user_interaction_guidelines"): setattr(user, "user_interaction_guidelines", old_user_object.user_interaction_guidelines)
|
| 515 |
if hasattr(old_user_object, "score_history"): setattr(user, "score_history", old_user_object.score_history)
|
| 516 |
+
if hasattr(old_user_object, "cumulative_plan_day"): setattr(user, "cumulative_plan_day", old_user_object.cumulative_plan_day)
|
| 517 |
|
| 518 |
api_response = {
|
| 519 |
"user": user.user_info,
|
|
|
|
| 533 |
"recommended_gg_topics": user.recommended_gg_topics,
|
| 534 |
"growth_plan": user.growth_plan,
|
| 535 |
"user_interaction_guidelines": user.user_interaction_guidelines,
|
| 536 |
+
"score_history": user.score_history,
|
| 537 |
+
"cumulative_plan_day": user.cumulative_plan_day
|
| 538 |
}
|
| 539 |
|
| 540 |
add_to_cache(user)
|
|
|
|
| 815 |
logger.info(f"Successfully created user", extra={"user_id": request.user_id, "endpoint": "/create_user"})
|
| 816 |
return {"message": {"info": f"[OK] User created: {user}", "messages": user.get_messages()}}
|
| 817 |
|
| 818 |
+
@app.post("/fetch_daily_alert")
|
| 819 |
+
@catch_endpoint_error
|
| 820 |
+
async def fetch_daily_alert(
|
| 821 |
+
request: ChangeDateItem,
|
| 822 |
+
api_key: str = Depends(get_api_key) # Change Security to Depends
|
| 823 |
+
):
|
| 824 |
+
logger.info(f"Upselling GG for day: {request.day}", extra={"user_id": request.user_id, "endpoint": "/upsell_gg"})
|
| 825 |
+
user = get_user(request.user_id)
|
| 826 |
+
response = user.get_alerts(request.date, request.day)
|
| 827 |
+
return {"response": response}
|
| 828 |
+
|
| 829 |
@app.post("/chat")
|
| 830 |
@catch_endpoint_error
|
| 831 |
async def chat(
|
app/user.py
CHANGED
|
@@ -19,10 +19,11 @@ from app.flows import FINAL_SUMMARY_STATE, FINAL_SUMMARY_STATE, MICRO_ACTION_STA
|
|
| 19 |
from pydantic import BaseModel
|
| 20 |
from datetime import datetime
|
| 21 |
|
| 22 |
-
from app.utils import generate_uuid, get_booked_gg_sessions, get_growth_guide_summary, update_growth_guide_summary
|
| 23 |
|
| 24 |
import dotenv
|
| 25 |
import re
|
|
|
|
| 26 |
dotenv.load_dotenv()
|
| 27 |
|
| 28 |
OURCOACH_DASHBOARD_URL = os.getenv("OURCOACH_DASHBOARD_URL")
|
|
@@ -126,6 +127,7 @@ class User:
|
|
| 126 |
self.conversations = ConversationManager(client, self, asst_id)
|
| 127 |
|
| 128 |
self.score_history = []
|
|
|
|
| 129 |
|
| 130 |
@catch_error
|
| 131 |
def extend_growth_plan(self):
|
|
@@ -195,6 +197,11 @@ class User:
|
|
| 195 |
logger.info(f"Success.", extra={"user_id": self.user_id, "endpoint": "extend_growth_plan"})
|
| 196 |
return True
|
| 197 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 198 |
@catch_error
|
| 199 |
def add_recent_wins(self, wins, context = None):
|
| 200 |
prompt = f"""
|
|
@@ -612,10 +619,109 @@ class User:
|
|
| 612 |
def set_intro_done(self):
|
| 613 |
self.conversations.intro_done = True
|
| 614 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 615 |
@catch_error
|
| 616 |
def do_theme(self, theme, date, day, last_msg_is_answered = True):
|
| 617 |
logger.info(f"Doing theme: {theme}", extra={"user_id": self.user_id, "endpoint": "do_theme"})
|
| 618 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 619 |
if self.reminders is not None and len(self.reminders):
|
| 620 |
logger.info(f"ALL Upcoming Reminders: {self.reminders}", extra={"user_id": self.user_id, "endpoint": "do_theme"})
|
| 621 |
reminders = list(filter(lambda x : x['recurrence'] == 'postponed', self.reminders))
|
|
@@ -635,40 +741,59 @@ class User:
|
|
| 635 |
logger.info(f"No reminders found for today ({pd.to_datetime(date).date()})", extra={"user_id": self.user_id, "endpoint": "do_theme"})
|
| 636 |
|
| 637 |
if theme == "MOTIVATION_INSPIRATION_STATE":
|
| 638 |
-
formatted_message = MOTIVATION_INSPIRATION_STATE.format(self.get_current_goal(),
|
| 639 |
elif theme == "PROGRESS_REFLECTION_STATE":
|
| 640 |
-
formatted_message = PROGRESS_REFLECTION_STATE.format(self.get_current_goal(),
|
| 641 |
if len(self.challenges):
|
| 642 |
challenge = self.challenges.pop(0)
|
| 643 |
formatted_message += f"\n\n** IMPORTANT: Today, reflect on the users' challenge of: {challenge.content}, which they brought up during their growth guide session (let the user know we are bringing it up because of this) **"
|
| 644 |
elif theme == "MICRO_ACTION_STATE":
|
| 645 |
reminder_message = "\n".join([f"{i+1}. {reminder}" for i, reminder in enumerate(reminders)]) if reminders else "User has no postponed micro-actions"
|
| 646 |
|
| 647 |
-
formatted_message = MICRO_ACTION_STATE.format(self.get_current_goal(),
|
| 648 |
if len(self.recommended_micro_actions):
|
| 649 |
todays_micro_action = self.recommended_micro_actions.pop(0)
|
| 650 |
formatted_message += f"\n\n** IMPORTANT: Today's Micro Action is: {todays_micro_action.content}, which was recommended during their growth guide session (let the user know we are bringing it up because of this) **"
|
| 651 |
elif theme == "OPEN_DISCUSSION_STATE":
|
| 652 |
-
formatted_message = OPEN_DISCUSSION_STATE.format(self.get_current_goal(),
|
| 653 |
if len(self.other_focusses):
|
| 654 |
focus = self.other_focusses.pop(0)
|
| 655 |
formatted_message += f"\n\n** IMPORTANT: Today, focus the discussion on: {focus.content}, which they discussed during their growth guide session (let the user know we are bringing it up because of this) **"
|
| 656 |
elif theme == "PROGRESS_SUMMARY_STATE":
|
| 657 |
-
formatted_message = PROGRESS_SUMMARY_STATE.format(self.get_current_goal(),
|
| 658 |
elif theme == "FINAL_SUMMARY_STATE":
|
| 659 |
-
|
| 660 |
-
|
| 661 |
-
|
| 662 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 663 |
elif theme == "EDUCATION_STATE":
|
| 664 |
-
formatted_message = EDUCATION_STATE.format(self.get_current_goal(),
|
| 665 |
elif theme == "FOLLUP_ACTION_STATE":
|
| 666 |
reminder_message = "\n".join([f"{i+1}. {reminder}" for i, reminder in enumerate(reminders)]) if reminders else "User has no postponed micro-actions"
|
| 667 |
-
formatted_message = FOLLUP_ACTION_STATE.format(self.get_current_goal(),
|
| 668 |
elif theme == "FUNFACT_STATE":
|
| 669 |
topics = ["Fun Fact about the User's Goal", "How Personality Type is affecting/shaping their behaviour towards the goal", "How Love Language may impact and be relevant toward the goal"]
|
| 670 |
randomized_topic = random.choice(topics)
|
| 671 |
-
formatted_message = FUNFACT_STATE.format(self.get_current_goal(),
|
| 672 |
|
| 673 |
# prompt = f"""** It is a new day: {date} **
|
| 674 |
# Additional System Instruction:
|
|
@@ -709,44 +834,8 @@ class User:
|
|
| 709 |
If the answer above is "False", you must immediately ask something like (but warmer) "Hey <user name>, I've noticed that you haven't answered my latest question. Do you still want to continue the growth plan?". If the user says "no", then ask if they want to set a new goal (therefore later, call the change_goal() function)
|
| 710 |
But if the user says "yes", then proceed to do the instruction below.
|
| 711 |
|
| 712 |
-
|
| 713 |
-
** Always remember to incorporate your personality (based on your persona) into all of your responses. **
|
| 714 |
-
1. Focus on giving more wisdom than questions:
|
| 715 |
-
- Provide assertive guidance, encouragement, validation, and different viewpoints.
|
| 716 |
-
- Be critical and challenge the user's arguments as needed.
|
| 717 |
-
- Avoid asking too many questions.
|
| 718 |
-
- Offer strategies instead of asking for the user's strategies.
|
| 719 |
|
| 720 |
-
2. Keep messages concise & use Whatsapp texting length:
|
| 721 |
-
- Use natural, casual language that's easy to understand.
|
| 722 |
-
|
| 723 |
-
3. Limit interaction to three messages per day (until the user says "it is a new day"):
|
| 724 |
-
- Send each message one at a time.
|
| 725 |
-
- Warmly end the daily interaction in the third message.
|
| 726 |
-
|
| 727 |
-
4. Include zero or one question per message (optional):
|
| 728 |
-
- If including a question, place it after a line break and encapsulate with one asterisk: *question*
|
| 729 |
-
|
| 730 |
-
5. Ensure messages are personalized and creative:
|
| 731 |
-
- Enrich content with user information.
|
| 732 |
-
- Avoid being a "boring" coach with generic questions or advice.
|
| 733 |
-
|
| 734 |
-
6. Channel the energy, wisdom, and mindset of your persona:
|
| 735 |
-
- Reiterate your famous sayings.
|
| 736 |
-
- Explain your thinking philosophy in a natural, casual conversational tone (not like a lecture).
|
| 737 |
-
|
| 738 |
-
7. You may greet the user by name on a new day, but no need to use their name in every message.
|
| 739 |
-
|
| 740 |
-
8. You **MUST** call the process_reminder() function if the user wants to postpone their micro-action
|
| 741 |
-
|
| 742 |
-
9. When talking about Growth Guide, you must try to upsell the growth guide. Meaning that you are always encouraging the user to book a Growth Guide session and explain it benefits. Be like the Growth Guide's salesman!
|
| 743 |
-
|
| 744 |
-
10. If the user asks a question to you, you must answer the user's question directly & concisely, without asking a follow-up question.
|
| 745 |
-
|
| 746 |
-
11. If you want to bold your message, always use one (1) asterisk (do not use the markdown bolding method or double asterisk)
|
| 747 |
-
|
| 748 |
-
12. When your message includes a link, you **must** always mention the link directly without using hyperlinks (example: [link](link.com) or [link.com](link.com) are wrong). Hence, provide the website URL as plain text! (example: "Hi there, you can access it in this link: link.com", is correct)
|
| 749 |
-
|
| 750 |
Today's Theme:
|
| 751 |
{formatted_message}
|
| 752 |
"""
|
|
|
|
| 19 |
from pydantic import BaseModel
|
| 20 |
from datetime import datetime
|
| 21 |
|
| 22 |
+
from app.utils import generate_uuid, get_booked_gg_sessions, get_growth_guide, get_growth_guide_summary, get_user_subscriptions, update_growth_guide_summary
|
| 23 |
|
| 24 |
import dotenv
|
| 25 |
import re
|
| 26 |
+
import math
|
| 27 |
dotenv.load_dotenv()
|
| 28 |
|
| 29 |
OURCOACH_DASHBOARD_URL = os.getenv("OURCOACH_DASHBOARD_URL")
|
|
|
|
| 127 |
self.conversations = ConversationManager(client, self, asst_id)
|
| 128 |
|
| 129 |
self.score_history = []
|
| 130 |
+
self.cumulative_plan_day = 0
|
| 131 |
|
| 132 |
@catch_error
|
| 133 |
def extend_growth_plan(self):
|
|
|
|
| 197 |
logger.info(f"Success.", extra={"user_id": self.user_id, "endpoint": "extend_growth_plan"})
|
| 198 |
return True
|
| 199 |
|
| 200 |
+
@catch_error
|
| 201 |
+
def reset_cumulative_plan_day(self):
|
| 202 |
+
logger.info(f"Reseting cumulative_plan_day", extra={"user_id": self.user_id, "endpoint": "reset_cumulative_plan_day"})
|
| 203 |
+
self.cumulative_plan_day = 0
|
| 204 |
+
|
| 205 |
@catch_error
|
| 206 |
def add_recent_wins(self, wins, context = None):
|
| 207 |
prompt = f"""
|
|
|
|
| 619 |
def set_intro_done(self):
|
| 620 |
self.conversations.intro_done = True
|
| 621 |
|
| 622 |
+
@catch_error
|
| 623 |
+
def get_alerts(self, date, day=None):
|
| 624 |
+
# responses = []
|
| 625 |
+
if day is None:
|
| 626 |
+
day = self.cumulative_plan_day
|
| 627 |
+
if day == 2 or day == 5:
|
| 628 |
+
# upsell the GG
|
| 629 |
+
growth_guide = get_growth_guide(self.user_id)
|
| 630 |
+
|
| 631 |
+
upsell_prompt = "introduce WHO their grwoth guide is and how a GG can help them" if day == 2 else "Let the user know that their Growth Guide <Name> (no need to re-introduce them) is available to enhance their current growth journey with you and based on the converstaion history so far and the users personal information, challenges and goals suggest WHAT they can discuss with their growth guide."
|
| 632 |
+
|
| 633 |
+
prompt = f"""You are an expert ambassador/salesman of Growth Guide sessions.
|
| 634 |
+
The users' growth guide is {growth_guide}.
|
| 635 |
+
|
| 636 |
+
Respond with a enthusiatic hello! Then, based on the current day ({day}), succintly:
|
| 637 |
+
{upsell_prompt}
|
| 638 |
+
Frame your response like a you are telling the user a fun fact, but dont explicitly mention "fun fact".
|
| 639 |
+
"""
|
| 640 |
+
|
| 641 |
+
# send upsell gg alert at 7pm
|
| 642 |
+
timestamp = pd.Timestamp.now().replace(hour=19, minute=0, second=0, microsecond=0).strftime("%d-%m-%Y %a %H:%M:%S")
|
| 643 |
+
elif day == 8:
|
| 644 |
+
# alert the user that we are always collecting feedback in order to improve. Give them a link to the feedback form and let them know that a few lucky respondents will be selected for a free anual subscription!
|
| 645 |
+
prompt = f"""You are an expert ambassador/salesman of ourcoach whose objective is to upsell the ourcoach subscription based on the following context:
|
| 646 |
+
We are always collecting feedback in order to improve our services. Please take a moment to fill out our feedback form: http://feedback_form. A few lucky respondents will be selected for a free anual subscription!"""
|
| 647 |
+
timestamp = pd.Timestamp.now().replace(hour=19, minute=0, second=0, microsecond=0).strftime("%d-%m-%Y %a %H:%M:%S")
|
| 648 |
+
elif day == 12:
|
| 649 |
+
growth_guide = get_growth_guide(self.user_id)['full_name']
|
| 650 |
+
|
| 651 |
+
subscription = get_user_subscriptions(self.user_id)[0]
|
| 652 |
+
|
| 653 |
+
subscription_end_date = pd.to_datetime(subscription['subscription_end_date'])
|
| 654 |
+
|
| 655 |
+
# get difference between subscription end date and date
|
| 656 |
+
date = pd.to_datetime(date)
|
| 657 |
+
days_left = (subscription_end_date - date).days
|
| 658 |
+
logger.info(f"{subscription_end_date} - {date} = Days left: {days_left}", extra={"user_id": self.user_id, "endpoint": "get_alerts"})
|
| 659 |
+
if days_left <= 2:
|
| 660 |
+
subscription_alert = f"""** User's Subscription Information **
|
| 661 |
+
Users current subscription:
|
| 662 |
+
{subscription}
|
| 663 |
+
|
| 664 |
+
Users growth guide:
|
| 665 |
+
{growth_guide}
|
| 666 |
+
|
| 667 |
+
Alert the user that their free trial is ending in {days_left} days and Sell them to subscribe via their Revelation Dashboard: {OURCOACH_DASHBOARD_URL} to continue chatting with you, receiving personalized advice and guidance and access to Growth Guide sessions. Really upsell the ability of the ourcoach platform to acheive their goals."
|
| 668 |
+
"""
|
| 669 |
+
|
| 670 |
+
prompt = f"""You are an expert ambassador/salesman of ourcoach (product) whose objective is to upsell the ourcoach subscription based on the following context:
|
| 671 |
+
{subscription_alert}
|
| 672 |
+
"""
|
| 673 |
+
# send reminder alert at 7pm
|
| 674 |
+
timestamp = pd.Timestamp.now().replace(hour=19, minute=0, second=0, microsecond=0).strftime("%d-%m-%Y %a %H:%M:%S")
|
| 675 |
+
else:
|
| 676 |
+
return []
|
| 677 |
+
elif day == 14:
|
| 678 |
+
growth_guide = get_growth_guide(self.user_id)['full_name']
|
| 679 |
+
|
| 680 |
+
subscription = get_user_subscriptions(self.user_id)[0]
|
| 681 |
+
|
| 682 |
+
subscription_end_date = pd.to_datetime(subscription['subscription_end_date'])
|
| 683 |
+
|
| 684 |
+
# get difference between subscription end date and date
|
| 685 |
+
date = pd.to_datetime(date)
|
| 686 |
+
days_left = (subscription_end_date - date).days
|
| 687 |
+
logger.info(f"{subscription_end_date} - {date} = Days left: {days_left}", extra={"user_id": self.user_id, "endpoint": "get_alerts"})
|
| 688 |
+
if days_left <= 0:
|
| 689 |
+
subscription_alert = f"""** User's Subscription Information **
|
| 690 |
+
Users current subscription:
|
| 691 |
+
{subscription}
|
| 692 |
+
|
| 693 |
+
Users growth guide:
|
| 694 |
+
{growth_guide}
|
| 695 |
+
|
| 696 |
+
OMG the users subscription is ending today! If you lose this user you and your family will not be able to survive!
|
| 697 |
+
You have to persuade the user to stay so use the best of your salesman abilities and sell them to continue to subscribe to ourcoach, otherwise, how will you put food on the table???"
|
| 698 |
+
"""
|
| 699 |
+
prompt = f"""You are an expert ambassador/salesman of ourcoach whose objective is to upsell the ourcoach subscription based on the following context:
|
| 700 |
+
{subscription_alert}
|
| 701 |
+
"""
|
| 702 |
+
# send reminder alert at 7pm
|
| 703 |
+
timestamp = pd.Timestamp.now().replace(hour=19, minute=0, second=0, microsecond=0).strftime("%d-%m-%Y %a %H:%M:%S")
|
| 704 |
+
else:
|
| 705 |
+
return []
|
| 706 |
+
else:
|
| 707 |
+
return []
|
| 708 |
+
|
| 709 |
+
response, run = self.conversations._run_current_thread(prompt, hidden=True)
|
| 710 |
+
message = run.metadata.get("message", "No message")
|
| 711 |
+
logger.info(f"Message: {message}", extra={"user_id": self.user_id, "endpoint": "upsell_gg"})
|
| 712 |
+
|
| 713 |
+
# make timestamp ISO
|
| 714 |
+
response['timestamp'] = pd.to_datetime(timestamp).isoformat()
|
| 715 |
+
return [response]
|
| 716 |
+
|
| 717 |
@catch_error
|
| 718 |
def do_theme(self, theme, date, day, last_msg_is_answered = True):
|
| 719 |
logger.info(f"Doing theme: {theme}", extra={"user_id": self.user_id, "endpoint": "do_theme"})
|
| 720 |
|
| 721 |
+
# Add 1 day to cumulative_plan_day
|
| 722 |
+
self.cumulative_plan_day += 1
|
| 723 |
+
final_day = (math.ceil((self.cumulative_plan_day+7)/14) * 14) - 7
|
| 724 |
+
|
| 725 |
if self.reminders is not None and len(self.reminders):
|
| 726 |
logger.info(f"ALL Upcoming Reminders: {self.reminders}", extra={"user_id": self.user_id, "endpoint": "do_theme"})
|
| 727 |
reminders = list(filter(lambda x : x['recurrence'] == 'postponed', self.reminders))
|
|
|
|
| 741 |
logger.info(f"No reminders found for today ({pd.to_datetime(date).date()})", extra={"user_id": self.user_id, "endpoint": "do_theme"})
|
| 742 |
|
| 743 |
if theme == "MOTIVATION_INSPIRATION_STATE":
|
| 744 |
+
formatted_message = MOTIVATION_INSPIRATION_STATE.format(self.get_current_goal(), self.cumulative_plan_day, final_day)
|
| 745 |
elif theme == "PROGRESS_REFLECTION_STATE":
|
| 746 |
+
formatted_message = PROGRESS_REFLECTION_STATE.format(self.get_current_goal(), self.cumulative_plan_day, final_day)
|
| 747 |
if len(self.challenges):
|
| 748 |
challenge = self.challenges.pop(0)
|
| 749 |
formatted_message += f"\n\n** IMPORTANT: Today, reflect on the users' challenge of: {challenge.content}, which they brought up during their growth guide session (let the user know we are bringing it up because of this) **"
|
| 750 |
elif theme == "MICRO_ACTION_STATE":
|
| 751 |
reminder_message = "\n".join([f"{i+1}. {reminder}" for i, reminder in enumerate(reminders)]) if reminders else "User has no postponed micro-actions"
|
| 752 |
|
| 753 |
+
formatted_message = MICRO_ACTION_STATE.format(self.get_current_goal(), self.cumulative_plan_day, final_day, reminder_message)
|
| 754 |
if len(self.recommended_micro_actions):
|
| 755 |
todays_micro_action = self.recommended_micro_actions.pop(0)
|
| 756 |
formatted_message += f"\n\n** IMPORTANT: Today's Micro Action is: {todays_micro_action.content}, which was recommended during their growth guide session (let the user know we are bringing it up because of this) **"
|
| 757 |
elif theme == "OPEN_DISCUSSION_STATE":
|
| 758 |
+
formatted_message = OPEN_DISCUSSION_STATE.format(self.get_current_goal(), self.cumulative_plan_day, final_day)
|
| 759 |
if len(self.other_focusses):
|
| 760 |
focus = self.other_focusses.pop(0)
|
| 761 |
formatted_message += f"\n\n** IMPORTANT: Today, focus the discussion on: {focus.content}, which they discussed during their growth guide session (let the user know we are bringing it up because of this) **"
|
| 762 |
elif theme == "PROGRESS_SUMMARY_STATE":
|
| 763 |
+
formatted_message = PROGRESS_SUMMARY_STATE.format(self.get_current_goal(), self.cumulative_plan_day, final_day)
|
| 764 |
elif theme == "FINAL_SUMMARY_STATE":
|
| 765 |
+
past_gg_summary = "<User has not had a Growth Guide session yet>"
|
| 766 |
+
growth_guide = get_growth_guide(self.user_id)
|
| 767 |
+
|
| 768 |
+
booked_sessions = get_booked_gg_sessions(self.user_id)
|
| 769 |
+
|
| 770 |
+
# filter out only completed (past) sessions
|
| 771 |
+
past_sessions = [session for session in booked_sessions if session['status'] == "completed"]
|
| 772 |
+
|
| 773 |
+
# for each past booking, fetch the zoom_ai_summary and gg_report from
|
| 774 |
+
for booking in past_sessions:
|
| 775 |
+
summary_data = get_growth_guide_summary(self.user_id, booking['booking_id'])
|
| 776 |
+
logger.info(f"Summary data for booking: {booking['booking_id']} - {summary_data}",
|
| 777 |
+
extra={"user_id": self.user_id, "endpoint": "assistant_get_user_info"})
|
| 778 |
+
if summary_data:
|
| 779 |
+
booking['zoom_ai_summary'] = summary_data['zoom_ai_summary']
|
| 780 |
+
booking['gg_report'] = summary_data['gg_report']
|
| 781 |
+
else:
|
| 782 |
+
booking['zoom_ai_summary'] = "Growth Guide has not uploaded the report yet"
|
| 783 |
+
booking['gg_report'] = "Growth Guide has not uploaded the report yet"
|
| 784 |
+
if len(past_sessions):
|
| 785 |
+
past_gg_summary = "\n".join([f"** Session {i+1} **\n{json.dumps(session, indent=4)}" for i, session in enumerate(past_sessions)])
|
| 786 |
+
|
| 787 |
+
formatted_message = FINAL_SUMMARY_STATE.format(self.get_current_goal(), day, len(self.growth_plan.array), growth_guide, past_gg_summary)
|
| 788 |
elif theme == "EDUCATION_STATE":
|
| 789 |
+
formatted_message = EDUCATION_STATE.format(self.get_current_goal(), self.cumulative_plan_day, final_day)
|
| 790 |
elif theme == "FOLLUP_ACTION_STATE":
|
| 791 |
reminder_message = "\n".join([f"{i+1}. {reminder}" for i, reminder in enumerate(reminders)]) if reminders else "User has no postponed micro-actions"
|
| 792 |
+
formatted_message = FOLLUP_ACTION_STATE.format(self.get_current_goal(), self.cumulative_plan_day, final_day, reminder_message)
|
| 793 |
elif theme == "FUNFACT_STATE":
|
| 794 |
topics = ["Fun Fact about the User's Goal", "How Personality Type is affecting/shaping their behaviour towards the goal", "How Love Language may impact and be relevant toward the goal"]
|
| 795 |
randomized_topic = random.choice(topics)
|
| 796 |
+
formatted_message = FUNFACT_STATE.format(self.get_current_goal(), self.cumulative_plan_day, final_day, randomized_topic)
|
| 797 |
|
| 798 |
# prompt = f"""** It is a new day: {date} **
|
| 799 |
# Additional System Instruction:
|
|
|
|
| 834 |
If the answer above is "False", you must immediately ask something like (but warmer) "Hey <user name>, I've noticed that you haven't answered my latest question. Do you still want to continue the growth plan?". If the user says "no", then ask if they want to set a new goal (therefore later, call the change_goal() function)
|
| 835 |
But if the user says "yes", then proceed to do the instruction below.
|
| 836 |
|
| 837 |
+
Today is day {self.cumulative_plan_day} of the user's growth journey (out of {final_day} days). You may (or may not) mention this occasionally in your first message of the day.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 838 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 839 |
Today's Theme:
|
| 840 |
{formatted_message}
|
| 841 |
"""
|
app/utils.py
CHANGED
|
@@ -425,13 +425,14 @@ def get_user_summary(user_id, update_rec_topics=False):
|
|
| 425 |
### **2. User's Growth Guide Preparation Brief**
|
| 426 |
|
| 427 |
**Objective**: Guide the user on what to discuss with the Growth Guide, providing actionable advice and highlighting key areas to focus on during their session, covering the five key areas.
|
| 428 |
-
You must use the user's current **challenges** and **life goal** to make the preparation brief **personalized**!
|
| 429 |
|
| 430 |
Important Rules:
|
| 431 |
1. **ALWAYS** be succinct, valuable and personalized! Do **NOT** ask generic question. Ask a personalized question! And bold the key parts of the user brief!
|
| 432 |
2. **Session Length Awareness**: Be realistic about what can be effectively discussed in a 30-minute session. Prioritize the areas that are most pressing or offer the greatest opportunity for positive change.
|
| 433 |
3. **Guidance for Interaction**: Provide specific suggestions for topics to discuss with the **Growth Guide**, you are encouraged to use phrases like "Discuss with your Growth Guide how to...".
|
| 434 |
4. And for the second time, please be succinct and concise!!!
|
|
|
|
| 435 |
|
| 436 |
**Format**:
|
| 437 |
|
|
@@ -1087,7 +1088,7 @@ def get_user_info(user_id):
|
|
| 1087 |
try:
|
| 1088 |
with psycopg2.connect(**db_params) as conn:
|
| 1089 |
with conn.cursor() as cursor:
|
| 1090 |
-
query = sql.SQL("SELECT
|
| 1091 |
cursor.execute(query, (user_id,))
|
| 1092 |
row = cursor.fetchone()
|
| 1093 |
if (row):
|
|
@@ -1103,6 +1104,7 @@ def get_user_info(user_id):
|
|
| 1103 |
### USER PROFILE ###
|
| 1104 |
|
| 1105 |
Name: {user_data_clean.get('firstName', '')}
|
|
|
|
| 1106 |
{user_data_clean.get('firstName', '')}'s challenges (You **must** use this information for the PLANNING STATE):
|
| 1107 |
{challenges}
|
| 1108 |
Persona:
|
|
|
|
| 425 |
### **2. User's Growth Guide Preparation Brief**
|
| 426 |
|
| 427 |
**Objective**: Guide the user on what to discuss with the Growth Guide, providing actionable advice and highlighting key areas to focus on during their session, covering the five key areas.
|
| 428 |
+
You must use the user's current **challenges** and **life goal** to make the preparation brief **personalized**! You **must** bold some words that you think is important! but it does **not** have to be the first few words!
|
| 429 |
|
| 430 |
Important Rules:
|
| 431 |
1. **ALWAYS** be succinct, valuable and personalized! Do **NOT** ask generic question. Ask a personalized question! And bold the key parts of the user brief!
|
| 432 |
2. **Session Length Awareness**: Be realistic about what can be effectively discussed in a 30-minute session. Prioritize the areas that are most pressing or offer the greatest opportunity for positive change.
|
| 433 |
3. **Guidance for Interaction**: Provide specific suggestions for topics to discuss with the **Growth Guide**, you are encouraged to use phrases like "Discuss with your Growth Guide how to...".
|
| 434 |
4. And for the second time, please be succinct and concise!!!
|
| 435 |
+
5. You **must** bold some words that you think is important! but it does **not** have to be the first few words!
|
| 436 |
|
| 437 |
**Format**:
|
| 438 |
|
|
|
|
| 1088 |
try:
|
| 1089 |
with psycopg2.connect(**db_params) as conn:
|
| 1090 |
with conn.cursor() as cursor:
|
| 1091 |
+
query = sql.SQL("SELECT left(onboarding,length(onboarding)-1)||',\"growth_guide_name\":\"'||coalesce(b.full_name,'')||'\"}}' onboarding FROM {table} a LEFT JOIN {coach_tbl} b ON a.assign_coach_id = b.id WHERE a.id = %s").format(table=sql.Identifier('public', 'users'), coach_tbl = sql.Identifier('public','coach'))
|
| 1092 |
cursor.execute(query, (user_id,))
|
| 1093 |
row = cursor.fetchone()
|
| 1094 |
if (row):
|
|
|
|
| 1104 |
### USER PROFILE ###
|
| 1105 |
|
| 1106 |
Name: {user_data_clean.get('firstName', '')}
|
| 1107 |
+
Growth Guide Name: {user_data_clean.get('growth_guide_name', '')}
|
| 1108 |
{user_data_clean.get('firstName', '')}'s challenges (You **must** use this information for the PLANNING STATE):
|
| 1109 |
{challenges}
|
| 1110 |
Persona:
|