Spaces:
Sleeping
Sleeping
Shaggy_HF_Feedback (#13)
Browse files- Growth Guide fix for Final Day (287763fb9556a6340fc1111854a85f53b1aa8365)
- Added Alerts and other HF feedback (24c93b67d1934a620f8d8ff36dcca2cdc4b17575)
- Merge https://huggingface.co/spaces/ourcoach-ai/fastapi-v2 into pr/13 (5c0f3749e4d32534e17765cc1cf4285d8a188f6b)
- Small fix for fetch alerts (b7b7ad3cf26aa91908c2cac6836b1ac774b07ee4)
- 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 -341
- app/conversation_manager.py +5 -1
- app/flows.py +4 -1
- app/main.py +21 -9
- app/user.py +119 -5
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,345 +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 |
-
self.cm.user.reset_cumulative_plan_day()
|
| 621 |
-
|
| 622 |
-
# cancel current run
|
| 623 |
-
run = self.cancel_run(run, thread)
|
| 624 |
-
run = PseudoRun(status="cancelled", metadata={"message": "change_goal"})
|
| 625 |
-
return run, just_finish_intro
|
| 626 |
-
elif tool.function.name == "complete_goal":
|
| 627 |
-
logger.info(f"Completing user goal...", extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_complete_goal"})
|
| 628 |
-
goal = self.cm.user.update_goal(None, 'COMPLETED')
|
| 629 |
-
logger.info(f"Marked users' goal: {goal} as COMPLETED", extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_complete_goal"})
|
| 630 |
-
tool_outputs.append({
|
| 631 |
-
"tool_call_id": tool.id,
|
| 632 |
-
"output": f"Marked users' goal: {goal} as COMPLETED"
|
| 633 |
-
})
|
| 634 |
-
elif tool.function.name == "process_reminder":
|
| 635 |
-
reminder = json.loads(tool.function.arguments)["content"]
|
| 636 |
-
timestamp = json.loads(tool.function.arguments)["timestamp"]
|
| 637 |
-
recurrence = json.loads(tool.function.arguments)["recurrence"]
|
| 638 |
-
action = json.loads(tool.function.arguments)["action"]
|
| 639 |
-
logger.info(f"Setting reminder: {reminder} for {timestamp} with recurrence: {recurrence}", extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_process_reminder"})
|
| 640 |
-
# timestamp is a string like: YYYY-mm-ddTHH:MM:SSZ (2025-01-05T11:00:00Z)
|
| 641 |
-
# convert to datetime object
|
| 642 |
-
timestamp = pd.to_datetime(timestamp, format="%Y-%m-%dT%H:%M:%SZ")
|
| 643 |
-
logger.info(f"Formatted timestamp: {timestamp}", extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_process_reminder"})
|
| 644 |
-
|
| 645 |
-
output = f"({recurrence if recurrence else 'One-Time'}) Reminder ({reminder}) set for ({timestamp})"
|
| 646 |
-
logger.info(output,
|
| 647 |
-
extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_set_reminder"})
|
| 648 |
-
self.cm.user.set_reminder({"reminder": reminder, "timestamp": timestamp, 'recurrence': recurrence, 'action': action})
|
| 649 |
-
tool_outputs.append({
|
| 650 |
"tool_call_id": tool.id,
|
| 651 |
-
"output": f"
|
| 652 |
-
|
| 653 |
-
|
| 654 |
-
|
| 655 |
-
|
| 656 |
-
|
| 657 |
-
|
| 658 |
-
|
| 659 |
-
|
| 660 |
-
|
| 661 |
-
|
| 662 |
-
|
| 663 |
-
|
| 664 |
-
|
| 665 |
-
|
| 666 |
-
|
| 667 |
-
|
| 668 |
-
|
| 669 |
-
|
| 670 |
-
|
| 671 |
-
|
| 672 |
-
|
| 673 |
-
|
| 674 |
-
|
| 675 |
-
|
| 676 |
-
elif
|
| 677 |
-
|
| 678 |
-
|
| 679 |
-
|
| 680 |
-
|
| 681 |
-
|
| 682 |
-
|
| 683 |
-
|
| 684 |
-
|
| 685 |
-
|
| 686 |
-
elif
|
| 687 |
-
|
| 688 |
-
|
| 689 |
-
|
| 690 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 691 |
|
| 692 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 693 |
|
| 694 |
-
|
| 695 |
-
|
| 696 |
-
|
| 697 |
-
|
| 698 |
-
|
|
|
|
| 699 |
extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_get_user_info"})
|
| 700 |
-
if summary_data:
|
| 701 |
-
booking['zoom_ai_summary'] = summary_data['zoom_ai_summary']
|
| 702 |
-
booking['gg_report'] = summary_data['gg_report']
|
| 703 |
-
else:
|
| 704 |
-
booking['zoom_ai_summary'] = "Growth Guide has not uploaded the report yet"
|
| 705 |
-
booking['gg_report'] = "Growth Guide has not uploaded the report yet"
|
| 706 |
-
|
| 707 |
-
logger.info(f"User's booked sessions: {booked_sessions}",
|
| 708 |
-
extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_get_user_info"})
|
| 709 |
-
|
| 710 |
-
if len(booked_sessions):
|
| 711 |
-
# booked session is an array of jsons
|
| 712 |
-
# convers it to have i) json where i = 1...N where N is the len of booked_sessions
|
| 713 |
-
# join the entire array into 1 string with each item seperated by a newline
|
| 714 |
-
formatted_sessions = "\n".join([f"** Session {i+1} **\n{json.dumps(session, indent=4)}" for i, session in enumerate(booked_sessions)])
|
| 715 |
-
logger.info(f"Formatted booked sessions: {formatted_sessions}",
|
| 716 |
-
extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_get_user_info"})
|
| 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 |
-
|
| 747 |
-
try:
|
| 748 |
run = self.cm.client.beta.threads.runs.submit_tool_outputs_and_poll(
|
| 749 |
thread_id=thread.id,
|
| 750 |
run_id=run.id,
|
|
@@ -753,17 +758,22 @@ class Assistant:
|
|
| 753 |
logger.info("Tool outputs submitted successfully",
|
| 754 |
extra={"user_id": self.cm.user.user_id, "endpoint": "assistant_submit_tools"})
|
| 755 |
return run, just_finish_intro
|
| 756 |
-
|
| 757 |
-
|
| 758 |
-
|
| 759 |
-
|
| 760 |
-
|
| 761 |
-
|
| 762 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 763 |
|
| 764 |
class PseudoRun:
|
| 765 |
-
def __init__(self, status, metadata=None):
|
| 766 |
-
self.id =
|
| 767 |
self.status = status
|
| 768 |
self.metadata = metadata or {}
|
| 769 |
|
|
|
|
| 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
|
@@ -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 ** :
|
|
|
|
| 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 ** :
|
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,
|
|
@@ -814,6 +815,17 @@ async def create_user(
|
|
| 814 |
logger.info(f"Successfully created user", extra={"user_id": request.user_id, "endpoint": "/create_user"})
|
| 815 |
return {"message": {"info": f"[OK] User created: {user}", "messages": user.get_messages()}}
|
| 816 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 817 |
@app.post("/chat")
|
| 818 |
@catch_endpoint_error
|
| 819 |
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,
|
|
|
|
| 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,7 +19,7 @@ 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
|
|
@@ -619,6 +619,101 @@ class User:
|
|
| 619 |
def set_intro_done(self):
|
| 620 |
self.conversations.intro_done = True
|
| 621 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 622 |
@catch_error
|
| 623 |
def do_theme(self, theme, date, day, last_msg_is_answered = True):
|
| 624 |
logger.info(f"Doing theme: {theme}", extra={"user_id": self.user_id, "endpoint": "do_theme"})
|
|
@@ -667,10 +762,29 @@ class User:
|
|
| 667 |
elif theme == "PROGRESS_SUMMARY_STATE":
|
| 668 |
formatted_message = PROGRESS_SUMMARY_STATE.format(self.get_current_goal(), self.cumulative_plan_day, final_day)
|
| 669 |
elif theme == "FINAL_SUMMARY_STATE":
|
| 670 |
-
|
| 671 |
-
|
| 672 |
-
|
| 673 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 674 |
elif theme == "EDUCATION_STATE":
|
| 675 |
formatted_message = EDUCATION_STATE.format(self.get_current_goal(), self.cumulative_plan_day, final_day)
|
| 676 |
elif theme == "FOLLUP_ACTION_STATE":
|
|
|
|
| 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
|
|
|
|
| 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"})
|
|
|
|
| 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":
|