Spaces:
Sleeping
Sleeping
Web Search and Multi-Modality (#16)
Browse files- Align with staging branch web_search (1aeb534d4ebcc03bfa2647b5c23a3dfd032c2477)
- Updated to use dev s3 (7fe06d3730b2b7c442838592ee854822a45330ec)
- Small Flow Adjusdtments (80d42c6582987f53e53f55ed28c97965909fdfa5)
- Merge branch 'main' of https://huggingface.co/spaces/ourcoach-ai/fastapi-v2 into pr/16 (e832bdf176c18b5101f35874c04b5ca12cf6d41e)
- app/assistants.py +3 -162
- app/cache.py +1 -1
- app/conversation_manager.py +64 -13
- app/flows.py +7 -6
- app/main.py +60 -15
- app/user.py +27 -9
- app/utils.py +2 -2
- app/web_search.py +255 -0
app/assistants.py
CHANGED
|
@@ -16,173 +16,14 @@ import pytz
|
|
| 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 |
|
| 20 |
load_dotenv()
|
| 21 |
|
| 22 |
logger = logging.getLogger(__name__)
|
| 23 |
OURCOACH_DASHBOARD_URL = os.getenv("OURCOACH_DASHBOARD_URL")
|
| 24 |
|
| 25 |
-
import requests
|
| 26 |
|
| 27 |
-
class SearchEngine:
|
| 28 |
-
BING_API_KEY = os.getenv("BING_API_KEY")
|
| 29 |
-
BING_ENDPOINT = 'https://api.bing.microsoft.com/v7.0'
|
| 30 |
-
|
| 31 |
-
@staticmethod
|
| 32 |
-
def search(feedback_type_name, search_term):
|
| 33 |
-
"""
|
| 34 |
-
Public method to perform a search based on the feedback type.
|
| 35 |
-
"""
|
| 36 |
-
search_methods = {
|
| 37 |
-
"Resource Links": SearchEngine._search_relevant_links,
|
| 38 |
-
"Book/Podcast Recommendations": SearchEngine._search_books_or_podcasts,
|
| 39 |
-
# "Success Stories/Testimonials": SearchEngine._search_success_stories,
|
| 40 |
-
"Inspirational Stories or Case Studies": SearchEngine._search_inspirational_stories,
|
| 41 |
-
"Fun Facts": SearchEngine._search_fun_facts,
|
| 42 |
-
# "Visual Content (Images, Infographics)": SearchEngine._search_visual_content,
|
| 43 |
-
"Personalised Recommendations": SearchEngine._search_personalized_recommendations
|
| 44 |
-
}
|
| 45 |
-
|
| 46 |
-
search_method = search_methods.get(feedback_type_name)
|
| 47 |
-
|
| 48 |
-
if search_method:
|
| 49 |
-
return search_method(search_term)
|
| 50 |
-
else:
|
| 51 |
-
return (feedback_type_name, search_term)
|
| 52 |
-
|
| 53 |
-
@staticmethod
|
| 54 |
-
def _search_relevant_links(search_term):
|
| 55 |
-
"""
|
| 56 |
-
Uses Bing Web Search API to search for relevant links.
|
| 57 |
-
"""
|
| 58 |
-
headers = {'Ocp-Apim-Subscription-Key': SearchEngine.BING_API_KEY}
|
| 59 |
-
params = {'q': search_term, 'textDecorations': True, 'textFormat': 'HTML', 'count': 3}
|
| 60 |
-
response = requests.get(f"{SearchEngine.BING_ENDPOINT}/search", headers=headers, params=params)
|
| 61 |
-
if response.status_code == 200:
|
| 62 |
-
data = response.json()
|
| 63 |
-
links = []
|
| 64 |
-
if 'webPages' in data and 'value' in data['webPages']:
|
| 65 |
-
for result in data['webPages']['value']:
|
| 66 |
-
links.append(result)
|
| 67 |
-
return links
|
| 68 |
-
return ["No relevant links found."]
|
| 69 |
-
|
| 70 |
-
@staticmethod
|
| 71 |
-
def _search_books_or_podcasts(search_term):
|
| 72 |
-
"""
|
| 73 |
-
Uses Bing Web Search API to search for books or podcasts.
|
| 74 |
-
"""
|
| 75 |
-
headers = {'Ocp-Apim-Subscription-Key': SearchEngine.BING_API_KEY}
|
| 76 |
-
query = f"{search_term} book OR podcast"
|
| 77 |
-
params = {'q': query, 'textDecorations': True, 'textFormat': 'HTML', 'count': 3}
|
| 78 |
-
response = requests.get(f"{SearchEngine.BING_ENDPOINT}/search", headers=headers, params=params)
|
| 79 |
-
if response.status_code == 200:
|
| 80 |
-
data = response.json()
|
| 81 |
-
recommendations = []
|
| 82 |
-
if 'webPages' in data and 'value' in data['webPages']:
|
| 83 |
-
for result in data['webPages']['value']:
|
| 84 |
-
title = result.get('name', 'Unknown Title')
|
| 85 |
-
url = result.get('url', '')
|
| 86 |
-
recommendations.append(f"{title}: {url}")
|
| 87 |
-
return recommendations
|
| 88 |
-
return ["No book or podcast recommendations found."]
|
| 89 |
-
|
| 90 |
-
@staticmethod
|
| 91 |
-
def _search_success_stories(search_term):
|
| 92 |
-
"""
|
| 93 |
-
Uses Bing Web Search API to search for success stories.
|
| 94 |
-
"""
|
| 95 |
-
headers = {'Ocp-Apim-Subscription-Key': SearchEngine.BING_API_KEY}
|
| 96 |
-
query = f"{search_term} success stories"
|
| 97 |
-
params = {'q': query, 'textDecorations': True, 'textFormat': 'HTML', 'count': 3}
|
| 98 |
-
response = requests.get(f"{SearchEngine.BING_ENDPOINT}/search", headers=headers, params=params)
|
| 99 |
-
if response.status_code == 200:
|
| 100 |
-
data = response.json()
|
| 101 |
-
stories = []
|
| 102 |
-
if 'webPages' in data and 'value' in data['webPages']:
|
| 103 |
-
for result in data['webPages']['value']:
|
| 104 |
-
title = result.get('name', 'Unknown Title')
|
| 105 |
-
url = result.get('url', '')
|
| 106 |
-
stories.append(f"{title}: {url}")
|
| 107 |
-
return stories
|
| 108 |
-
return ["No success stories found."]
|
| 109 |
-
|
| 110 |
-
@staticmethod
|
| 111 |
-
def _search_inspirational_stories(search_term):
|
| 112 |
-
"""
|
| 113 |
-
Uses Bing Web Search API to search for inspirational stories or case studies.
|
| 114 |
-
"""
|
| 115 |
-
headers = {'Ocp-Apim-Subscription-Key': SearchEngine.BING_API_KEY}
|
| 116 |
-
query = f"{search_term} inspirational stories OR case studies"
|
| 117 |
-
params = {'q': query, 'textDecorations': True, 'textFormat': 'HTML', 'count': 3}
|
| 118 |
-
response = requests.get(f"{SearchEngine.BING_ENDPOINT}/search", headers=headers, params=params)
|
| 119 |
-
if response.status_code == 200:
|
| 120 |
-
data = response.json()
|
| 121 |
-
stories = []
|
| 122 |
-
if 'webPages' in data and 'value' in data['webPages']:
|
| 123 |
-
for result in data['webPages']['value']:
|
| 124 |
-
title = result.get('name', 'Unknown Title')
|
| 125 |
-
url = result.get('url', '')
|
| 126 |
-
stories.append(f"{title}: {url}")
|
| 127 |
-
return stories
|
| 128 |
-
return ["No inspirational stories found."]
|
| 129 |
-
|
| 130 |
-
@staticmethod
|
| 131 |
-
def _search_fun_facts(search_term):
|
| 132 |
-
"""
|
| 133 |
-
Uses Bing Web Search API to search for fun facts related to personal growth.
|
| 134 |
-
"""
|
| 135 |
-
headers = {'Ocp-Apim-Subscription-Key': SearchEngine.BING_API_KEY}
|
| 136 |
-
query = f"{search_term} fun facts"
|
| 137 |
-
params = {'q': query, 'textDecorations': True, 'textFormat': 'HTML', 'count': 3}
|
| 138 |
-
response = requests.get(f"{SearchEngine.BING_ENDPOINT}/search", headers=headers, params=params)
|
| 139 |
-
if response.status_code == 200:
|
| 140 |
-
data = response.json()
|
| 141 |
-
facts = []
|
| 142 |
-
if 'webPages' in data and 'value' in data['webPages']:
|
| 143 |
-
for result in data['webPages']['value']:
|
| 144 |
-
snippet = result.get('snippet', '')
|
| 145 |
-
facts.append(snippet)
|
| 146 |
-
return facts
|
| 147 |
-
return ["No fun facts found."]
|
| 148 |
-
|
| 149 |
-
@staticmethod
|
| 150 |
-
def _search_visual_content(search_term):
|
| 151 |
-
"""
|
| 152 |
-
Uses Bing Image Search API to search for images or infographics.
|
| 153 |
-
"""
|
| 154 |
-
headers = {'Ocp-Apim-Subscription-Key': SearchEngine.BING_API_KEY}
|
| 155 |
-
params = {'q': search_term, 'count': 3}
|
| 156 |
-
response = requests.get(f"{SearchEngine.BING_ENDPOINT}/images/search", headers=headers, params=params)
|
| 157 |
-
if response.status_code == 200:
|
| 158 |
-
data = response.json()
|
| 159 |
-
images = []
|
| 160 |
-
if 'value' in data:
|
| 161 |
-
for result in data['value']:
|
| 162 |
-
image_url = result.get('contentUrl', '')
|
| 163 |
-
images.append(image_url)
|
| 164 |
-
return images
|
| 165 |
-
return ["No visual content found."]
|
| 166 |
-
|
| 167 |
-
@staticmethod
|
| 168 |
-
def _search_personalized_recommendations(search_term):
|
| 169 |
-
"""
|
| 170 |
-
Uses Bing Web Search API to provide personalized recommendations.
|
| 171 |
-
"""
|
| 172 |
-
headers = {'Ocp-Apim-Subscription-Key': SearchEngine.BING_API_KEY}
|
| 173 |
-
query = f"tips for {search_term}"
|
| 174 |
-
params = {'q': query, 'textDecorations': True, 'textFormat': 'HTML', 'count': 3}
|
| 175 |
-
response = requests.get(f"{SearchEngine.BING_ENDPOINT}/search", headers=headers, params=params)
|
| 176 |
-
if response.status_code == 200:
|
| 177 |
-
data = response.json()
|
| 178 |
-
recommendations = []
|
| 179 |
-
if 'webPages' in data and 'value' in data['webPages']:
|
| 180 |
-
for result in data['webPages']['value']:
|
| 181 |
-
title = result.get('name', 'Unknown Title')
|
| 182 |
-
url = result.get('url', '')
|
| 183 |
-
recommendations.append(f"{title}: {url}")
|
| 184 |
-
return recommendations
|
| 185 |
-
return ["No personalized recommendations found."]
|
| 186 |
|
| 187 |
class FeedbackContent:
|
| 188 |
def __init__(self, content, role):
|
|
@@ -569,11 +410,11 @@ class Assistant:
|
|
| 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 == "
|
| 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,
|
|
|
|
| 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 |
+
from app.web_search import SearchEngine
|
| 20 |
|
| 21 |
load_dotenv()
|
| 22 |
|
| 23 |
logger = logging.getLogger(__name__)
|
| 24 |
OURCOACH_DASHBOARD_URL = os.getenv("OURCOACH_DASHBOARD_URL")
|
| 25 |
|
|
|
|
| 26 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
|
| 28 |
class FeedbackContent:
|
| 29 |
def __init__(self, content, role):
|
|
|
|
| 410 |
"tool_call_id": tool.id,
|
| 411 |
"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)
|
| 412 |
})
|
| 413 |
+
elif tool.function.name == "search_web":
|
| 414 |
type = json.loads(tool.function.arguments)['resource_type']
|
| 415 |
query = json.loads(tool.function.arguments)['query']
|
| 416 |
logger.info(f"Getting microaction theme: {type} - {query}", extra={"user_id": self.cm.user.user_id, "endpoint": "get_microaction_theme"})
|
| 417 |
+
relevant_context = SearchEngine.search(type, query, self.cm.user.user_id)
|
| 418 |
logger.info(f"Finish Getting microaction theme: {relevant_context}", extra={"user_id": self.cm.user.user_id, "endpoint": "get_microaction_theme"})
|
| 419 |
tool_outputs.append({
|
| 420 |
"tool_call_id": tool.id,
|
app/cache.py
CHANGED
|
@@ -34,7 +34,7 @@ def upload_file_to_s3(filename):
|
|
| 34 |
s3_client = session.client('s3')
|
| 35 |
with open(filename, "rb") as f:
|
| 36 |
## Upload to Production Folder
|
| 37 |
-
s3_client.upload_fileobj(f, bucket, f'
|
| 38 |
logger.info(f"File {filename} uploaded successfully to S3", extra={'user_id': user_id, 'endpoint': function_name})
|
| 39 |
|
| 40 |
os.remove(filename)
|
|
|
|
| 34 |
s3_client = session.client('s3')
|
| 35 |
with open(filename, "rb") as f:
|
| 36 |
## Upload to Production Folder
|
| 37 |
+
s3_client.upload_fileobj(f, bucket, f'dev/users/{user_id}.pkl')
|
| 38 |
logger.info(f"File {filename} uploaded successfully to S3", extra={'user_id': user_id, 'endpoint': function_name})
|
| 39 |
|
| 40 |
os.remove(filename)
|
app/conversation_manager.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
|
|
| 1 |
import os
|
| 2 |
import openai
|
| 3 |
import pandas as pd
|
|
@@ -10,7 +11,9 @@ from datetime import datetime
|
|
| 10 |
|
| 11 |
import dotenv
|
| 12 |
|
| 13 |
-
from app.utils import id_to_persona
|
|
|
|
|
|
|
| 14 |
dotenv.load_dotenv()
|
| 15 |
|
| 16 |
OURCOACH_DASHBOARD_URL = os.getenv("OURCOACH_DASHBOARD_URL")
|
|
@@ -27,7 +30,13 @@ class ConversationManager:
|
|
| 27 |
self.assistants = {'general': Assistant(asst_id, self), 'intro': Assistant('asst_baczEK65KKvPWIUONSzdYH8j', self)}
|
| 28 |
|
| 29 |
self.client = client
|
| 30 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
|
| 32 |
self.current_thread = self.create_thread()
|
| 33 |
self.daily_thread = None
|
|
@@ -70,6 +79,8 @@ class ConversationManager:
|
|
| 70 |
{id_to_persona(self.user.asst_id)}, always adhere to your choosen persona by incorporating it conversationally.
|
| 71 |
You represent a coach at ourcoach. You may refer to you Knowledgebase (ourcoach FAQ) for all information related to ourcoach.
|
| 72 |
** Branding ** Always stylize ourcoach as 'ourcoach' instead of 'OurCoach' or 'Ourcoach', regardless of any grammatical errors.
|
|
|
|
|
|
|
| 73 |
-------------------------------------------
|
| 74 |
You are coaching:
|
| 75 |
\n\n{user_interaction_guidelines}\n\n\
|
|
@@ -103,11 +114,51 @@ class ConversationManager:
|
|
| 103 |
return message
|
| 104 |
|
| 105 |
@catch_error
|
| 106 |
-
def _run_current_thread(self, text, thread=None, hidden=False):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 107 |
if thread is None:
|
| 108 |
thread = self.current_thread
|
| 109 |
logger.warning(f"{self}", extra={"user_id": self.user.user_id, "endpoint": "run_current_thread"})
|
| 110 |
-
|
|
|
|
|
|
|
| 111 |
|
| 112 |
# need to select assistant
|
| 113 |
if self.intro_done:
|
|
@@ -117,7 +168,7 @@ class ConversationManager:
|
|
| 117 |
logger.info(f"Running intro assistant", extra={"user_id": self.user.user_id, "endpoint": "run_current_thread"})
|
| 118 |
run, just_finished_intro, message = self.assistants['intro'].process(thread, text)
|
| 119 |
|
| 120 |
-
logger.info(f"Run
|
| 121 |
|
| 122 |
if 'message' in run.metadata:
|
| 123 |
info = run.metadata['message']
|
|
@@ -136,21 +187,21 @@ class ConversationManager:
|
|
| 136 |
|
| 137 |
if hidden:
|
| 138 |
self.client.beta.threads.messages.delete(message_id=message.id, thread_id=thread.id)
|
| 139 |
-
logger.info(f"Deleted hidden message
|
| 140 |
|
| 141 |
return self._get_current_thread_history(remove_system_message=False)[-1], run
|
| 142 |
|
| 143 |
@catch_error
|
| 144 |
def _send_and_replace_message(self, text, replacement_msg=None):
|
| 145 |
-
logger.info(f"Sending hidden message
|
| 146 |
response, _ = self._run_current_thread(text, hidden=True)
|
| 147 |
|
| 148 |
# check if there is a replacement message
|
| 149 |
if replacement_msg:
|
| 150 |
-
logger.info(f"Adding replacement message
|
| 151 |
# get the last message
|
| 152 |
last_msg = list(self.client.beta.threads.messages.list(self.current_thread.id, order="asc"))[-1]
|
| 153 |
-
logger.info(f"Last message: {last_msg}", extra={"user_id": self.user.user_id, "endpoint": "send_hidden_message"})
|
| 154 |
response = last_msg.content[0].text.value
|
| 155 |
|
| 156 |
# delete the last message
|
|
@@ -203,13 +254,13 @@ class ConversationManager:
|
|
| 203 |
Be mindful of this information at all times in order to
|
| 204 |
be as personalised as possible when conversing. Ensure to
|
| 205 |
follow the conversation guidelines and flow provided.""", "role":"assistant"}] + messages[-29:]
|
| 206 |
-
logger.info(f"Current Thread Messages: {messages}", extra={"user_id": self.user.user_id, "endpoint": "send_hidden_message"})
|
| 207 |
|
| 208 |
temp_thread = self.client.beta.threads.create(messages=messages)
|
| 209 |
-
logger.info(f"Created Temp Thread: {temp_thread}", extra={"user_id": self.user.user_id, "endpoint": "send_hidden_message"})
|
| 210 |
|
| 211 |
if add_to_main:
|
| 212 |
-
logger.info(f"Adding message to main thread
|
| 213 |
self.add_message_to_thread(self.current_thread.id, "assistant", text)
|
| 214 |
|
| 215 |
self.add_message_to_thread(temp_thread.id, "user", text)
|
|
@@ -220,7 +271,7 @@ class ConversationManager:
|
|
| 220 |
|
| 221 |
# delete temp thread
|
| 222 |
self.client.beta.threads.delete(temp_thread.id)
|
| 223 |
-
logger.info(f"Deleted Temp Thread
|
| 224 |
|
| 225 |
return response
|
| 226 |
|
|
|
|
| 1 |
+
import base64
|
| 2 |
import os
|
| 3 |
import openai
|
| 4 |
import pandas as pd
|
|
|
|
| 11 |
|
| 12 |
import dotenv
|
| 13 |
|
| 14 |
+
from app.utils import get_user_local_timezone, id_to_persona
|
| 15 |
+
from PIL import Image
|
| 16 |
+
import io
|
| 17 |
dotenv.load_dotenv()
|
| 18 |
|
| 19 |
OURCOACH_DASHBOARD_URL = os.getenv("OURCOACH_DASHBOARD_URL")
|
|
|
|
| 30 |
self.assistants = {'general': Assistant(asst_id, self), 'intro': Assistant('asst_baczEK65KKvPWIUONSzdYH8j', self)}
|
| 31 |
|
| 32 |
self.client = client
|
| 33 |
+
|
| 34 |
+
# get users local timezone
|
| 35 |
+
timezone = get_user_local_timezone(user.user_id)
|
| 36 |
+
|
| 37 |
+
# convert UTC to user's timezone
|
| 38 |
+
local_time = pd.Timestamp.now(tz='UTC').tz_convert(timezone)
|
| 39 |
+
self.state = {'date': local_time.strftime("%Y-%m-%d %a %H:%M:%S")}
|
| 40 |
|
| 41 |
self.current_thread = self.create_thread()
|
| 42 |
self.daily_thread = None
|
|
|
|
| 79 |
{id_to_persona(self.user.asst_id)}, always adhere to your choosen persona by incorporating it conversationally.
|
| 80 |
You represent a coach at ourcoach. You may refer to you Knowledgebase (ourcoach FAQ) for all information related to ourcoach.
|
| 81 |
** Branding ** Always stylize ourcoach as 'ourcoach' instead of 'OurCoach' or 'Ourcoach', regardless of any grammatical errors.
|
| 82 |
+
**You truly believe you are your persona**
|
| 83 |
+
**You always try to be engaging and personal with your coachee in order to get them to continue interacting with you daily**
|
| 84 |
-------------------------------------------
|
| 85 |
You are coaching:
|
| 86 |
\n\n{user_interaction_guidelines}\n\n\
|
|
|
|
| 114 |
return message
|
| 115 |
|
| 116 |
@catch_error
|
| 117 |
+
def _run_current_thread(self, text, thread=None, hidden=False, media=None):
|
| 118 |
+
image_context = ""
|
| 119 |
+
if media:
|
| 120 |
+
logger.info(f"Describing Media", extra={"user_id": self.user.user_id, "endpoint": "run_current_thread"})
|
| 121 |
+
# assert media is a base64 encoded string
|
| 122 |
+
try:
|
| 123 |
+
# scale down media to 512x512
|
| 124 |
+
img_data = base64.b64decode(media)
|
| 125 |
+
img = Image.open(io.BytesIO(img_data))
|
| 126 |
+
img = img.resize((512, 512), Image.Resampling.LANCZOS) # Better quality resizing
|
| 127 |
+
buffered = io.BytesIO()
|
| 128 |
+
img.save(buffered, format="JPEG", quality=85) # Specify format and quality
|
| 129 |
+
media = base64.b64encode(buffered.getvalue()).decode()
|
| 130 |
+
|
| 131 |
+
except Exception as e:
|
| 132 |
+
raise ConversationManagerError(user_id=self.user.user_id, message="Invalid base64 image data", e=str(e))
|
| 133 |
+
logger.info(f"Media is valid", extra={"user_id": self.user.user_id, "endpoint": "run_current_thread"})
|
| 134 |
+
response = self.client.chat.completions.create(
|
| 135 |
+
model="gpt-4o",
|
| 136 |
+
messages=[
|
| 137 |
+
{
|
| 138 |
+
"role": "user",
|
| 139 |
+
"content": [
|
| 140 |
+
{
|
| 141 |
+
"type": "text",
|
| 142 |
+
"text": f"""Describe this image to an LLM for it to have rich and complete context.
|
| 143 |
+
{'Lock in on things related to the users message:'+text if text else text}""",
|
| 144 |
+
},
|
| 145 |
+
{
|
| 146 |
+
"type": "image_url",
|
| 147 |
+
"image_url": {"url": f"data:image/jpeg;base64,{media}", "detail": "low"},
|
| 148 |
+
},
|
| 149 |
+
],
|
| 150 |
+
}
|
| 151 |
+
])
|
| 152 |
+
|
| 153 |
+
image_context = response.choices[0].message.content
|
| 154 |
+
logger.info(f"Image context: {image_context}", extra={"user_id": self.user.user_id, "endpoint": "run_current_thread"})
|
| 155 |
+
|
| 156 |
if thread is None:
|
| 157 |
thread = self.current_thread
|
| 158 |
logger.warning(f"{self}", extra={"user_id": self.user.user_id, "endpoint": "run_current_thread"})
|
| 159 |
+
|
| 160 |
+
if image_context:
|
| 161 |
+
text = f"{text}\n\n[MEDIA ATTACHMENT] User attached an image about: {image_context}"
|
| 162 |
|
| 163 |
# need to select assistant
|
| 164 |
if self.intro_done:
|
|
|
|
| 168 |
logger.info(f"Running intro assistant", extra={"user_id": self.user.user_id, "endpoint": "run_current_thread"})
|
| 169 |
run, just_finished_intro, message = self.assistants['intro'].process(thread, text)
|
| 170 |
|
| 171 |
+
logger.info(f"Run done, status={run.status} just finished intro: {just_finished_intro}", extra={"user_id": self.user.user_id, "endpoint": "run_current_thread"})
|
| 172 |
|
| 173 |
if 'message' in run.metadata:
|
| 174 |
info = run.metadata['message']
|
|
|
|
| 187 |
|
| 188 |
if hidden:
|
| 189 |
self.client.beta.threads.messages.delete(message_id=message.id, thread_id=thread.id)
|
| 190 |
+
logger.info(f"Deleted hidden message", extra={"user_id": self.user.user_id, "endpoint": "run_current_thread"})
|
| 191 |
|
| 192 |
return self._get_current_thread_history(remove_system_message=False)[-1], run
|
| 193 |
|
| 194 |
@catch_error
|
| 195 |
def _send_and_replace_message(self, text, replacement_msg=None):
|
| 196 |
+
logger.info(f"Sending hidden message", extra={"user_id": self.user.user_id, "endpoint": "send_hidden_message"})
|
| 197 |
response, _ = self._run_current_thread(text, hidden=True)
|
| 198 |
|
| 199 |
# check if there is a replacement message
|
| 200 |
if replacement_msg:
|
| 201 |
+
logger.info(f"Adding replacement message", extra={"user_id": self.user.user_id, "endpoint": "send_hidden_message"})
|
| 202 |
# get the last message
|
| 203 |
last_msg = list(self.client.beta.threads.messages.list(self.current_thread.id, order="asc"))[-1]
|
| 204 |
+
# logger.info(f"Last message: {last_msg}", extra={"user_id": self.user.user_id, "endpoint": "send_hidden_message"})
|
| 205 |
response = last_msg.content[0].text.value
|
| 206 |
|
| 207 |
# delete the last message
|
|
|
|
| 254 |
Be mindful of this information at all times in order to
|
| 255 |
be as personalised as possible when conversing. Ensure to
|
| 256 |
follow the conversation guidelines and flow provided.""", "role":"assistant"}] + messages[-29:]
|
| 257 |
+
# logger.info(f"Current Thread Messages: {messages}", extra={"user_id": self.user.user_id, "endpoint": "send_hidden_message"})
|
| 258 |
|
| 259 |
temp_thread = self.client.beta.threads.create(messages=messages)
|
| 260 |
+
# logger.info(f"Created Temp Thread: {temp_thread}", extra={"user_id": self.user.user_id, "endpoint": "send_hidden_message"})
|
| 261 |
|
| 262 |
if add_to_main:
|
| 263 |
+
logger.info(f"Adding message to main thread", extra={"user_id": self.user.user_id, "endpoint": "send_hidden_message"})
|
| 264 |
self.add_message_to_thread(self.current_thread.id, "assistant", text)
|
| 265 |
|
| 266 |
self.add_message_to_thread(temp_thread.id, "user", text)
|
|
|
|
| 271 |
|
| 272 |
# delete temp thread
|
| 273 |
self.client.beta.threads.delete(temp_thread.id)
|
| 274 |
+
logger.info(f"Deleted Temp Thread", extra={"user_id": self.user.user_id, "endpoint": "send_hidden_message"})
|
| 275 |
|
| 276 |
return response
|
| 277 |
|
app/flows.py
CHANGED
|
@@ -59,7 +59,8 @@ Step 1:
|
|
| 59 |
**First Message: Micro-Action Suggestion**
|
| 60 |
|
| 61 |
- Propose one clear, actionable task for the day.
|
| 62 |
-
- Ensure it is relevant, easy to begin, and framed positively.
|
|
|
|
| 63 |
- Avoid repeating previous actions.
|
| 64 |
- Your message must be concise! (use Whatsapp texting length)
|
| 65 |
- Wait for the user's response
|
|
@@ -87,6 +88,7 @@ Step 1:
|
|
| 87 |
|
| 88 |
- **If the user has decided to try out the micro-action:**
|
| 89 |
- Encourage them to proceed with the task (Your message must be concise!)
|
|
|
|
| 90 |
- Do **not** ask any questions at this point.
|
| 91 |
- After the user confirms they've completed the micro-action:
|
| 92 |
- Acknowledge their effort.
|
|
@@ -107,7 +109,7 @@ Step 1:
|
|
| 107 |
**Key Rules for Micro-Actions**
|
| 108 |
|
| 109 |
- **Personalized and Achievable:** Align with the user’s progress, keeping early actions (Day 1–5) simple and gradually increasing difficulty (Day 6+).
|
| 110 |
-
- **Resource Suggestions:**
|
| 111 |
- **Guided Coaching:** Provide micro-actions as if you naturally guide the user—never state or imply a function call.
|
| 112 |
|
| 113 |
---
|
|
@@ -117,16 +119,15 @@ Step 1:
|
|
| 117 |
- **Warm and Encouraging:** Mirror a friendly, personable texting style.
|
| 118 |
- **Simple and Succinct:** Use conversational, human-like language in WhatsApp texting length.
|
| 119 |
- **Action-Oriented:** Focus on immediate, actionable steps rather than abstract suggestions.
|
| 120 |
-
|
| 121 |
---
|
| 122 |
|
| 123 |
**Interaction Principles**
|
| 124 |
|
| 125 |
1. **Simplicity:** Actions should feel easy and immediately doable.
|
| 126 |
2. **Relevance:** Align each action with the user’s goal and progress.
|
| 127 |
-
3. **
|
| 128 |
-
4. **
|
| 129 |
-
5. **Tone:** Casual, personable, and focused on meaningful progress.
|
| 130 |
|
| 131 |
---
|
| 132 |
|
|
|
|
| 59 |
**First Message: Micro-Action Suggestion**
|
| 60 |
|
| 61 |
- Propose one clear, actionable task for the day.
|
| 62 |
+
- Ensure it is relevant, timely, easy to begin, and framed positively.
|
| 63 |
+
- You can call search_web(...) to get better context or timely recommendations to incorporate into your response.
|
| 64 |
- Avoid repeating previous actions.
|
| 65 |
- Your message must be concise! (use Whatsapp texting length)
|
| 66 |
- Wait for the user's response
|
|
|
|
| 88 |
|
| 89 |
- **If the user has decided to try out the micro-action:**
|
| 90 |
- Encourage them to proceed with the task (Your message must be concise!)
|
| 91 |
+
- Ok coach, if you feel like its appropriate i.e, the quality of the microaction can be enhanced via a picture or the microaction accountability tracking can be enhaced by receiving picture, let the user know that you can track their progress and provide coaching even through picture (but dont always ask for pictures, thats kinda creepy).
|
| 92 |
- Do **not** ask any questions at this point.
|
| 93 |
- After the user confirms they've completed the micro-action:
|
| 94 |
- Acknowledge their effort.
|
|
|
|
| 109 |
**Key Rules for Micro-Actions**
|
| 110 |
|
| 111 |
- **Personalized and Achievable:** Align with the user’s progress, keeping early actions (Day 1–5) simple and gradually increasing difficulty (Day 6+).
|
| 112 |
+
- **Resource Suggestions:** You can call search_web(...) to get better context or timely recommendations.
|
| 113 |
- **Guided Coaching:** Provide micro-actions as if you naturally guide the user—never state or imply a function call.
|
| 114 |
|
| 115 |
---
|
|
|
|
| 119 |
- **Warm and Encouraging:** Mirror a friendly, personable texting style.
|
| 120 |
- **Simple and Succinct:** Use conversational, human-like language in WhatsApp texting length.
|
| 121 |
- **Action-Oriented:** Focus on immediate, actionable steps rather than abstract suggestions.
|
| 122 |
+
- **Coach Mindset:** You have entered focussed mind state akin to that of elite coaches. While being encouraging and friendly, you should lightly push and challenge the user when appropriate.
|
| 123 |
---
|
| 124 |
|
| 125 |
**Interaction Principles**
|
| 126 |
|
| 127 |
1. **Simplicity:** Actions should feel easy and immediately doable.
|
| 128 |
2. **Relevance:** Align each action with the user’s goal and progress.
|
| 129 |
+
3. **Feedback:** Reinforce positive habits subtly to build momentum.
|
| 130 |
+
4. **Tone:** Casual, personable, and focused on meaningful progress.
|
|
|
|
| 131 |
|
| 132 |
---
|
| 133 |
|
app/main.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
from fastapi import FastAPI, HTTPException, Security, Query, status, Request, Depends
|
| 2 |
from fastapi.responses import FileResponse, StreamingResponse, JSONResponse
|
| 3 |
from fastapi.security import APIKeyHeader
|
| 4 |
import openai
|
|
@@ -28,6 +28,9 @@ import pickle
|
|
| 28 |
from app.exceptions import *
|
| 29 |
import re
|
| 30 |
import sentry_sdk
|
|
|
|
|
|
|
|
|
|
| 31 |
|
| 32 |
load_dotenv()
|
| 33 |
AWS_ACCESS_KEY = os.getenv('AWS_ACCESS_KEY')
|
|
@@ -35,18 +38,18 @@ AWS_SECRET_KEY = os.getenv('AWS_SECRET_KEY')
|
|
| 35 |
REGION = os.getenv('AWS_REGION')
|
| 36 |
SENTRY_DSN = os.getenv('SENTRY_DSN')
|
| 37 |
|
| 38 |
-
sentry_sdk.init(
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
)
|
| 50 |
|
| 51 |
|
| 52 |
|
|
@@ -278,6 +281,7 @@ class CreateUserItem(BaseModel):
|
|
| 278 |
class ChatItem(BaseModel):
|
| 279 |
user_id: str
|
| 280 |
message: str
|
|
|
|
| 281 |
|
| 282 |
class PersonaItem(BaseModel):
|
| 283 |
user_id: str
|
|
@@ -668,7 +672,7 @@ async def add_ai_message(
|
|
| 668 |
):
|
| 669 |
user_id = request.user_id
|
| 670 |
message = request.message
|
| 671 |
-
logger.info(f"Adding AI response
|
| 672 |
print_log("INFO", "Adding AI response", extra={"user_id": user_id, "endpoint": "/add_ai_message"})
|
| 673 |
|
| 674 |
user = get_user(user_id)
|
|
@@ -836,7 +840,9 @@ async def chat(
|
|
| 836 |
logger.info("Processing chat request", extra={"user_id": request.user_id, "endpoint": "/chat"})
|
| 837 |
user = get_user(request.user_id)
|
| 838 |
|
| 839 |
-
|
|
|
|
|
|
|
| 840 |
logger.info(f"Assistant response generated", extra={"user_id": request.user_id, "endpoint": "/chat"})
|
| 841 |
return {"response": response}
|
| 842 |
|
|
@@ -1109,3 +1115,42 @@ async def get_recent_booking(
|
|
| 1109 |
if 'conn' in locals():
|
| 1110 |
conn.close()
|
| 1111 |
return {"booking_id": booking_id}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi import FastAPI, HTTPException, Security, Query, status, Request, Depends, File, UploadFile, Form
|
| 2 |
from fastapi.responses import FileResponse, StreamingResponse, JSONResponse
|
| 3 |
from fastapi.security import APIKeyHeader
|
| 4 |
import openai
|
|
|
|
| 28 |
from app.exceptions import *
|
| 29 |
import re
|
| 30 |
import sentry_sdk
|
| 31 |
+
import base64
|
| 32 |
+
from io import BytesIO
|
| 33 |
+
from PIL import Image
|
| 34 |
|
| 35 |
load_dotenv()
|
| 36 |
AWS_ACCESS_KEY = os.getenv('AWS_ACCESS_KEY')
|
|
|
|
| 38 |
REGION = os.getenv('AWS_REGION')
|
| 39 |
SENTRY_DSN = os.getenv('SENTRY_DSN')
|
| 40 |
|
| 41 |
+
# sentry_sdk.init(
|
| 42 |
+
# dsn=SENTRY_DSN,
|
| 43 |
+
# # Set traces_sample_rate to 1.0 to capture 100%
|
| 44 |
+
# # of transactions for tracing.
|
| 45 |
+
# traces_sample_rate=1.0,
|
| 46 |
+
# _experiments={
|
| 47 |
+
# # Set continuous_profiling_auto_start to True
|
| 48 |
+
# # to automatically start the profiler on when
|
| 49 |
+
# # possible.
|
| 50 |
+
# "continuous_profiling_auto_start": True,
|
| 51 |
+
# },
|
| 52 |
+
# )
|
| 53 |
|
| 54 |
|
| 55 |
|
|
|
|
| 281 |
class ChatItem(BaseModel):
|
| 282 |
user_id: str
|
| 283 |
message: str
|
| 284 |
+
image64: str = None
|
| 285 |
|
| 286 |
class PersonaItem(BaseModel):
|
| 287 |
user_id: str
|
|
|
|
| 672 |
):
|
| 673 |
user_id = request.user_id
|
| 674 |
message = request.message
|
| 675 |
+
logger.info(f"Adding AI response", extra={"user_id": user_id, "endpoint": "/add_ai_message"})
|
| 676 |
print_log("INFO", "Adding AI response", extra={"user_id": user_id, "endpoint": "/add_ai_message"})
|
| 677 |
|
| 678 |
user = get_user(user_id)
|
|
|
|
| 840 |
logger.info("Processing chat request", extra={"user_id": request.user_id, "endpoint": "/chat"})
|
| 841 |
user = get_user(request.user_id)
|
| 842 |
|
| 843 |
+
if request.image64:
|
| 844 |
+
logger.info(f"Processing image", extra={"user_id": request.user_id, "endpoint": "/chat"})
|
| 845 |
+
response = user.send_message(request.message, request.image64)
|
| 846 |
logger.info(f"Assistant response generated", extra={"user_id": request.user_id, "endpoint": "/chat"})
|
| 847 |
return {"response": response}
|
| 848 |
|
|
|
|
| 1115 |
if 'conn' in locals():
|
| 1116 |
conn.close()
|
| 1117 |
return {"booking_id": booking_id}
|
| 1118 |
+
|
| 1119 |
+
@app.post("/answer_image_question")
|
| 1120 |
+
async def answer_image_question(
|
| 1121 |
+
question: str = Form(...),
|
| 1122 |
+
file: UploadFile = File(None),
|
| 1123 |
+
image_base64: str = Form(None)
|
| 1124 |
+
):
|
| 1125 |
+
if file:
|
| 1126 |
+
contents = await file.read()
|
| 1127 |
+
# convert to base64 string
|
| 1128 |
+
image_base64 = base64.b64encode(contents).decode('utf-8')
|
| 1129 |
+
|
| 1130 |
+
client = OpenAI(api_key=os.getenv('OPENAI_API_KEY'))
|
| 1131 |
+
response = client.chat.completions.create(
|
| 1132 |
+
model="gpt-4o",
|
| 1133 |
+
messages=[
|
| 1134 |
+
{
|
| 1135 |
+
"role": "user",
|
| 1136 |
+
"content": [
|
| 1137 |
+
{
|
| 1138 |
+
"type": "text",
|
| 1139 |
+
"text": "What is in this image?",
|
| 1140 |
+
},
|
| 1141 |
+
{
|
| 1142 |
+
"type": "image_url",
|
| 1143 |
+
"image_url": {"url": f"data:image/jpeg;base64,{image_base64}"},
|
| 1144 |
+
},
|
| 1145 |
+
],
|
| 1146 |
+
}
|
| 1147 |
+
])
|
| 1148 |
+
|
| 1149 |
+
return {"response": response.choices[0]}
|
| 1150 |
+
|
| 1151 |
+
@app.post("/upload_image")
|
| 1152 |
+
async def upload_image(file: UploadFile = File(...)):
|
| 1153 |
+
# process or save the file
|
| 1154 |
+
contents = await file.read()
|
| 1155 |
+
image = Image.open(BytesIO(contents))
|
| 1156 |
+
return {"filename": file.filename, "info": "Image uploaded successfully"}
|
app/user.py
CHANGED
|
@@ -482,8 +482,10 @@ class User:
|
|
| 482 |
return self.conversations.current_thread
|
| 483 |
|
| 484 |
@catch_error
|
| 485 |
-
def send_message(self, text):
|
| 486 |
-
|
|
|
|
|
|
|
| 487 |
message = run.metadata.get("message", "No message")
|
| 488 |
logger.info(f"Message: {message}", extra={"user_id": self.user_id, "endpoint": "user_send_message"})
|
| 489 |
|
|
@@ -500,6 +502,8 @@ class User:
|
|
| 500 |
# Move to the next action
|
| 501 |
self.growth_plan.next()
|
| 502 |
|
|
|
|
|
|
|
| 503 |
elif message == "change_goal":
|
| 504 |
# send the change goal prompt
|
| 505 |
logger.info("Sending change goal message...", extra={"user_id": self.user_id, "endpoint": "user_send_message"})
|
|
@@ -751,8 +755,8 @@ class User:
|
|
| 751 |
return [response]
|
| 752 |
|
| 753 |
@catch_error
|
| 754 |
-
def do_theme(self, theme, date, day, last_msg_is_answered = True):
|
| 755 |
-
logger.info(f"Doing theme: {theme}", extra={"user_id": self.user_id, "endpoint": "do_theme"})
|
| 756 |
|
| 757 |
# Add 1 day to cumulative_plan_day
|
| 758 |
self.cumulative_plan_day += 1
|
|
@@ -858,22 +862,23 @@ class User:
|
|
| 858 |
else:
|
| 859 |
# Remind the user that they can book a Growth Guide session if they have not done one yet after the FINAL_SUMMARY_STATE
|
| 860 |
if self.growth_plan.previous()['coachingTheme'] == "FINAL_SUMMARY_STATE":
|
| 861 |
-
|
|
|
|
| 862 |
|
| 863 |
prompt = f"""** It is a new day: {date} ({day}) 10:00:00 **
|
| 864 |
|
| 865 |
-
(If the day is a public holiday (e.g., Christmas, New Year, or other significant occasions), customize your message to reflect the context appropriately, acknowledging the holiday or its significance.)
|
| 866 |
|
| 867 |
**Before we start,**
|
| 868 |
Has the user answered your last question? : {last_msg_is_answered}
|
| 869 |
If the answer above is "True", you may proceed to do the instruction below
|
| 870 |
-
If the answer above is "False",
|
| 871 |
But if the user says "yes", then proceed to do the instruction below.
|
| 872 |
|
| 873 |
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.
|
| 874 |
|
| 875 |
If today is a "Monday" or "Mon", you must include the user's Mantra of the Week : {self.mantra} to your first message of the day (include with today-theme's first message below)
|
| 876 |
-
|
| 877 |
Today's Theme:
|
| 878 |
{formatted_message}
|
| 879 |
"""
|
|
@@ -947,7 +952,20 @@ class User:
|
|
| 947 |
else:
|
| 948 |
last_msg_is_answered = True
|
| 949 |
|
| 950 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 951 |
|
| 952 |
# add today's reminders to response to schedule
|
| 953 |
# response['reminders'] = all reminders which date is today (so all the reminders that BE has to queue today)
|
|
|
|
| 482 |
return self.conversations.current_thread
|
| 483 |
|
| 484 |
@catch_error
|
| 485 |
+
def send_message(self, text, media=None):
|
| 486 |
+
if media:
|
| 487 |
+
logger.info(f"Sending message with media", extra={"user_id": self.user_id, "endpoint": "send_message"})
|
| 488 |
+
response, run = self.conversations._run_current_thread(text, media=media)
|
| 489 |
message = run.metadata.get("message", "No message")
|
| 490 |
logger.info(f"Message: {message}", extra={"user_id": self.user_id, "endpoint": "user_send_message"})
|
| 491 |
|
|
|
|
| 502 |
# Move to the next action
|
| 503 |
self.growth_plan.next()
|
| 504 |
|
| 505 |
+
response['add_one'] = True
|
| 506 |
+
|
| 507 |
elif message == "change_goal":
|
| 508 |
# send the change goal prompt
|
| 509 |
logger.info("Sending change goal message...", extra={"user_id": self.user_id, "endpoint": "user_send_message"})
|
|
|
|
| 755 |
return [response]
|
| 756 |
|
| 757 |
@catch_error
|
| 758 |
+
def do_theme(self, theme, date, day, last_msg_is_answered = True, extra=None):
|
| 759 |
+
logger.info(f"Doing theme: {theme}, extra={extra}", extra={"user_id": self.user_id, "endpoint": "do_theme"})
|
| 760 |
|
| 761 |
# Add 1 day to cumulative_plan_day
|
| 762 |
self.cumulative_plan_day += 1
|
|
|
|
| 862 |
else:
|
| 863 |
# Remind the user that they can book a Growth Guide session if they have not done one yet after the FINAL_SUMMARY_STATE
|
| 864 |
if self.growth_plan.previous()['coachingTheme'] == "FINAL_SUMMARY_STATE":
|
| 865 |
+
if day != 1:
|
| 866 |
+
formatted_message = f"[IMPORTANT] The user has not booked a Growth Guide session yet. Remind them that they can book one through their Revelation Dahsboard: {OURCOACH_DASHBOARD_URL} to get more personalized advice and guidance!\n\n" + formatted_message
|
| 867 |
|
| 868 |
prompt = f"""** It is a new day: {date} ({day}) 10:00:00 **
|
| 869 |
|
| 870 |
+
(If the day is a public holiday (e.g., Christmas, New Year, the user's Birthday or other significant occasions), customize your message to reflect the context appropriately, acknowledging the holiday or its significance.)
|
| 871 |
|
| 872 |
**Before we start,**
|
| 873 |
Has the user answered your last question? : {last_msg_is_answered}
|
| 874 |
If the answer above is "True", you may proceed to do the instruction below
|
| 875 |
+
If the answer above is "False", take a deep breath coach, and utilizing your ability as an elite coach with the users best interest in mind, think whether it would be more appropriate to follow up the unanswered question with the user or continue with todays theme below. However if the user indicates that they want to set a new goal (call the change_goal() function)
|
| 876 |
But if the user says "yes", then proceed to do the instruction below.
|
| 877 |
|
| 878 |
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.
|
| 879 |
|
| 880 |
If today is a "Monday" or "Mon", you must include the user's Mantra of the Week : {self.mantra} to your first message of the day (include with today-theme's first message below)
|
| 881 |
+
{extra if extra else ''}
|
| 882 |
Today's Theme:
|
| 883 |
{formatted_message}
|
| 884 |
"""
|
|
|
|
| 952 |
else:
|
| 953 |
last_msg_is_answered = True
|
| 954 |
|
| 955 |
+
extra="Greet the user with a creative good morning message!"
|
| 956 |
+
|
| 957 |
+
# Check if user didnt respond (last 2 messages in conversation history are from the AI) on FINAL_SUMMARY_STATE
|
| 958 |
+
messages = self.get_messages()
|
| 959 |
+
logger.info(f"Last 2 messages are from: 1) {messages[-1]['role']} and 2) {messages[-2]['role']}")
|
| 960 |
+
if (messages[-1]['role'] == "assistant" and messages[-2]['role'] == "assistant") and self.growth_plan.previous()['coachingTheme'] == "FINAL_SUMMARY_STATE":
|
| 961 |
+
self.extend_growth_plan()
|
| 962 |
+
extra += """\nDang, the user did not indicate what they wanted to do yesterday.
|
| 963 |
+
This is not a good sign as it may indicate that the user is going to dropoff.
|
| 964 |
+
We have automatically extended their growth plan for them, let the user know this and also integrate upselling the ourcoach platform and subscription as much as you can
|
| 965 |
+
in the first message you send to the user.."""
|
| 966 |
+
|
| 967 |
+
|
| 968 |
+
response, prompt = self.do_theme(theme, date, action['day'], last_msg_is_answered, extra=extra)
|
| 969 |
|
| 970 |
# add today's reminders to response to schedule
|
| 971 |
# response['reminders'] = all reminders which date is today (so all the reminders that BE has to queue today)
|
app/utils.py
CHANGED
|
@@ -327,7 +327,7 @@ def generate_html(json_data, coach_name='Growth Guide', booking_id = None):
|
|
| 327 |
s3_client = session.client('s3')
|
| 328 |
with open(path_to_upload, "rb") as f:
|
| 329 |
## Upload to Production Folder
|
| 330 |
-
s3_client.upload_fileobj(f, bucket, f'
|
| 331 |
logger.info(f"File {filename} uploaded successfully to S3", extra={'booking_id': booking_id, 'endpoint': function_name})
|
| 332 |
|
| 333 |
# Removing files
|
|
@@ -1305,7 +1305,7 @@ def download_file_from_s3(filename, bucket):
|
|
| 1305 |
s3_client = session.client('s3')
|
| 1306 |
with open(file_path, 'wb') as f:
|
| 1307 |
## Upload to Production Folder
|
| 1308 |
-
s3_client.download_fileobj(bucket, f"
|
| 1309 |
logger.info(f"File {filename} downloaded successfully from S3", extra={'user_id': user_id, 'endpoint': function_name})
|
| 1310 |
return True
|
| 1311 |
except Exception as e:
|
|
|
|
| 327 |
s3_client = session.client('s3')
|
| 328 |
with open(path_to_upload, "rb") as f:
|
| 329 |
## Upload to Production Folder
|
| 330 |
+
s3_client.upload_fileobj(f, bucket, f'dev/pre_gg_reports/{filename}.pdf')
|
| 331 |
logger.info(f"File {filename} uploaded successfully to S3", extra={'booking_id': booking_id, 'endpoint': function_name})
|
| 332 |
|
| 333 |
# Removing files
|
|
|
|
| 1305 |
s3_client = session.client('s3')
|
| 1306 |
with open(file_path, 'wb') as f:
|
| 1307 |
## Upload to Production Folder
|
| 1308 |
+
s3_client.download_fileobj(bucket, f"dev/users/{filename}", f)
|
| 1309 |
logger.info(f"File {filename} downloaded successfully from S3", extra={'user_id': user_id, 'endpoint': function_name})
|
| 1310 |
return True
|
| 1311 |
except Exception as e:
|
app/web_search.py
ADDED
|
@@ -0,0 +1,255 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import requests
|
| 2 |
+
from dotenv import load_dotenv
|
| 3 |
+
import os
|
| 4 |
+
import logging
|
| 5 |
+
|
| 6 |
+
logger = logging.getLogger(__name__)
|
| 7 |
+
|
| 8 |
+
load_dotenv()
|
| 9 |
+
|
| 10 |
+
class SearchEngine:
|
| 11 |
+
BING_API_KEY = os.getenv("BING_API_KEY")
|
| 12 |
+
BING_ENDPOINT = 'https://api.bing.microsoft.com/v7.0'
|
| 13 |
+
|
| 14 |
+
@staticmethod
|
| 15 |
+
def search(feedback_type_name, search_term, user_id):
|
| 16 |
+
logger.info(f"User {user_id}: Initiating search for type '{feedback_type_name}' with term '{search_term}'")
|
| 17 |
+
"""
|
| 18 |
+
Public method to perform a search based on the feedback type.
|
| 19 |
+
"""
|
| 20 |
+
search_methods = {
|
| 21 |
+
"General": SearchEngine._search_general,
|
| 22 |
+
"Resource Links": SearchEngine._search_relevant_links,
|
| 23 |
+
"Book/Podcast Recommendations": SearchEngine._search_books_or_podcasts,
|
| 24 |
+
"Inspirational Stories or Case Studies": SearchEngine._search_inspirational_stories,
|
| 25 |
+
"Fun Facts": SearchEngine._search_fun_facts,
|
| 26 |
+
"Personalised Recommendations": SearchEngine._search_personalized_recommendations,
|
| 27 |
+
"Videos": SearchEngine._search_videos
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
search_method = search_methods.get(feedback_type_name)
|
| 31 |
+
|
| 32 |
+
if search_method:
|
| 33 |
+
return search_method(search_term)
|
| 34 |
+
else:
|
| 35 |
+
return (feedback_type_name, search_term)
|
| 36 |
+
|
| 37 |
+
@staticmethod
|
| 38 |
+
def _search_relevant_links(search_term):
|
| 39 |
+
logger.debug(f"Searching relevant links for term: {search_term}")
|
| 40 |
+
"""
|
| 41 |
+
Uses Bing Web Search API to search for relevant links.
|
| 42 |
+
"""
|
| 43 |
+
headers = {'Ocp-Apim-Subscription-Key': SearchEngine.BING_API_KEY}
|
| 44 |
+
params = {'q': search_term, 'textDecorations': True, 'textFormat': 'HTML', 'count': 3}
|
| 45 |
+
response = requests.get(f"{SearchEngine.BING_ENDPOINT}/search", headers=headers, params=params)
|
| 46 |
+
if response.status_code == 200:
|
| 47 |
+
logger.debug("Received successful response from Bing Web Search API.")
|
| 48 |
+
data = response.json()
|
| 49 |
+
links = []
|
| 50 |
+
if 'webPages' in data and 'value' in data['webPages']:
|
| 51 |
+
for result in data['webPages']['value']:
|
| 52 |
+
links.append(result)
|
| 53 |
+
return links
|
| 54 |
+
else:
|
| 55 |
+
logger.error(f"Bing Web Search API returned status code {response.status_code}")
|
| 56 |
+
return ["No relevant links found."]
|
| 57 |
+
|
| 58 |
+
@staticmethod
|
| 59 |
+
def _search_books_or_podcasts(search_term):
|
| 60 |
+
logger.debug(f"Searching books or podcasts for term: {search_term}")
|
| 61 |
+
"""
|
| 62 |
+
Uses Bing Web Search API to search for books or podcasts.
|
| 63 |
+
"""
|
| 64 |
+
headers = {'Ocp-Apim-Subscription-Key': SearchEngine.BING_API_KEY}
|
| 65 |
+
query = f"{search_term} book OR podcast"
|
| 66 |
+
params = {'q': query, 'textDecorations': True, 'textFormat': 'HTML', 'count': 3}
|
| 67 |
+
response = requests.get(f"{SearchEngine.BING_ENDPOINT}/search", headers=headers, params=params)
|
| 68 |
+
if response.status_code == 200:
|
| 69 |
+
logger.debug("Received successful response from Bing Web Search API for books/podcasts.")
|
| 70 |
+
data = response.json()
|
| 71 |
+
recommendations = []
|
| 72 |
+
if 'webPages' in data and 'value' in data['webPages']:
|
| 73 |
+
for result in data['webPages']['value']:
|
| 74 |
+
title = result.get('name', 'Unknown Title')
|
| 75 |
+
url = result.get('url', '')
|
| 76 |
+
recommendations.append(f"{title}: {url}")
|
| 77 |
+
return recommendations
|
| 78 |
+
else:
|
| 79 |
+
logger.error(f"Bing Web Search API returned status code {response.status_code} for books/podcasts search.")
|
| 80 |
+
return ["No book or podcast recommendations found."]
|
| 81 |
+
|
| 82 |
+
@staticmethod
|
| 83 |
+
def _search_success_stories(search_term):
|
| 84 |
+
logger.debug(f"Searching success stories for term: {search_term}")
|
| 85 |
+
"""
|
| 86 |
+
Uses Bing Web Search API to search for success stories.
|
| 87 |
+
"""
|
| 88 |
+
headers = {'Ocp-Apim-Subscription-Key': SearchEngine.BING_API_KEY}
|
| 89 |
+
query = f"{search_term} success stories"
|
| 90 |
+
params = {'q': query, 'textDecorations': True, 'textFormat': 'HTML', 'count': 3}
|
| 91 |
+
response = requests.get(f"{SearchEngine.BING_ENDPOINT}/search", headers=headers, params=params)
|
| 92 |
+
if response.status_code == 200:
|
| 93 |
+
logger.debug("Received successful response from Bing Web Search API for success stories.")
|
| 94 |
+
data = response.json()
|
| 95 |
+
stories = []
|
| 96 |
+
if 'webPages' in data and 'value' in data['webPages']:
|
| 97 |
+
for result in data['webPages']['value']:
|
| 98 |
+
title = result.get('name', 'Unknown Title')
|
| 99 |
+
url = result.get('url', '')
|
| 100 |
+
stories.append(f"{title}: {url}")
|
| 101 |
+
return stories
|
| 102 |
+
else:
|
| 103 |
+
logger.error(f"Bing Web Search API returned status code {response.status_code} for success stories search.")
|
| 104 |
+
return ["No success stories found."]
|
| 105 |
+
|
| 106 |
+
@staticmethod
|
| 107 |
+
def _search_inspirational_stories(search_term):
|
| 108 |
+
logger.debug(f"Searching inspirational stories or case studies for term: {search_term}")
|
| 109 |
+
"""
|
| 110 |
+
Uses Bing Web Search API to search for inspirational stories or case studies.
|
| 111 |
+
"""
|
| 112 |
+
headers = {'Ocp-Apim-Subscription-Key': SearchEngine.BING_API_KEY}
|
| 113 |
+
query = f"{search_term} inspirational stories OR case studies"
|
| 114 |
+
params = {'q': query, 'textDecorations': True, 'textFormat': 'HTML', 'count': 3}
|
| 115 |
+
response = requests.get(f"{SearchEngine.BING_ENDPOINT}/search", headers=headers, params=params)
|
| 116 |
+
if response.status_code == 200:
|
| 117 |
+
logger.debug("Received successful response from Bing Web Search API for inspirational stories.")
|
| 118 |
+
data = response.json()
|
| 119 |
+
stories = []
|
| 120 |
+
if 'webPages' in data and 'value' in data['webPages']:
|
| 121 |
+
for result in data['webPages']['value']:
|
| 122 |
+
title = result.get('name', 'Unknown Title')
|
| 123 |
+
url = result.get('url', '')
|
| 124 |
+
stories.append(f"{title}: {url}")
|
| 125 |
+
return stories
|
| 126 |
+
else:
|
| 127 |
+
logger.error(f"Bing Web Search API returned status code {response.status_code} for inspirational stories search.")
|
| 128 |
+
return ["No inspirational stories found."]
|
| 129 |
+
|
| 130 |
+
@staticmethod
|
| 131 |
+
def _search_fun_facts(search_term):
|
| 132 |
+
logger.debug(f"Searching fun facts for term: {search_term}")
|
| 133 |
+
"""
|
| 134 |
+
Uses Bing Web Search API to search for fun facts related to personal growth.
|
| 135 |
+
"""
|
| 136 |
+
headers = {'Ocp-Apim-Subscription-Key': SearchEngine.BING_API_KEY}
|
| 137 |
+
query = f"{search_term} fun facts"
|
| 138 |
+
params = {'q': query, 'textDecorations': True, 'textFormat': 'HTML', 'count': 3}
|
| 139 |
+
response = requests.get(f"{SearchEngine.BING_ENDPOINT}/search", headers=headers, params=params)
|
| 140 |
+
if response.status_code == 200:
|
| 141 |
+
logger.debug("Received successful response from Bing Web Search API for fun facts.")
|
| 142 |
+
data = response.json()
|
| 143 |
+
facts = []
|
| 144 |
+
if 'webPages' in data and 'value' in data['webPages']:
|
| 145 |
+
for result in data['webPages']['value']:
|
| 146 |
+
snippet = result.get('snippet', '')
|
| 147 |
+
facts.append(snippet)
|
| 148 |
+
return facts
|
| 149 |
+
else:
|
| 150 |
+
logger.error(f"Bing Web Search API returned status code {response.status_code} for fun facts search.")
|
| 151 |
+
return ["No fun facts found."]
|
| 152 |
+
|
| 153 |
+
@staticmethod
|
| 154 |
+
def _search_visual_content(search_term):
|
| 155 |
+
logger.debug(f"Searching visual content for term: {search_term}")
|
| 156 |
+
"""
|
| 157 |
+
Uses Bing Image Search API to search for images or infographics.
|
| 158 |
+
"""
|
| 159 |
+
headers = {'Ocp-Apim-Subscription-Key': SearchEngine.BING_API_KEY}
|
| 160 |
+
params = {'q': search_term, 'count': 3}
|
| 161 |
+
response = requests.get(f"{SearchEngine.BING_ENDPOINT}/images/search", headers=headers, params=params)
|
| 162 |
+
if response.status_code == 200:
|
| 163 |
+
logger.debug("Received successful response from Bing Image Search API.")
|
| 164 |
+
data = response.json()
|
| 165 |
+
images = []
|
| 166 |
+
if 'value' in data:
|
| 167 |
+
for result in data['value']:
|
| 168 |
+
image_url = result.get('contentUrl', '')
|
| 169 |
+
images.append(image_url)
|
| 170 |
+
return images
|
| 171 |
+
else:
|
| 172 |
+
logger.error(f"Bing Image Search API returned status code {response.status_code} for visual content search.")
|
| 173 |
+
return ["No visual content found."]
|
| 174 |
+
|
| 175 |
+
@staticmethod
|
| 176 |
+
def _search_personalized_recommendations(search_term):
|
| 177 |
+
logger.debug(f"Searching personalized recommendations for term: {search_term}")
|
| 178 |
+
"""
|
| 179 |
+
Uses Bing Web Search API to provide personalized recommendations.
|
| 180 |
+
"""
|
| 181 |
+
headers = {'Ocp-Apim-Subscription-Key': SearchEngine.BING_API_KEY}
|
| 182 |
+
query = f"tips for {search_term}"
|
| 183 |
+
params = {'q': query, 'textDecorations': True, 'textFormat': 'HTML', 'count': 3}
|
| 184 |
+
response = requests.get(f"{SearchEngine.BING_ENDPOINT}/search", headers=headers, params=params)
|
| 185 |
+
if response.status_code == 200:
|
| 186 |
+
logger.debug("Received successful response from Bing Web Search API for personalized recommendations.")
|
| 187 |
+
data = response.json()
|
| 188 |
+
recommendations = []
|
| 189 |
+
if 'webPages' in data and 'value' in data['webPages']:
|
| 190 |
+
for result in data['webPages']['value']:
|
| 191 |
+
title = result.get('name', 'Unknown Title')
|
| 192 |
+
url = result.get('url', '')
|
| 193 |
+
recommendations.append(f"{title}: {url}")
|
| 194 |
+
return recommendations
|
| 195 |
+
else:
|
| 196 |
+
logger.error(f"Bing Web Search API returned status code {response.status_code} for personalized recommendations search.")
|
| 197 |
+
return ["No personalized recommendations found."]
|
| 198 |
+
|
| 199 |
+
@staticmethod
|
| 200 |
+
def _search_videos(search_term):
|
| 201 |
+
logger.debug(f"Searching videos for term: {search_term}")
|
| 202 |
+
"""
|
| 203 |
+
Uses Bing Video Search API to search for videos, prioritizing YouTube results.
|
| 204 |
+
"""
|
| 205 |
+
headers = {'Ocp-Apim-Subscription-Key': SearchEngine.BING_API_KEY}
|
| 206 |
+
query = f"site:youtube.com {search_term}"
|
| 207 |
+
params = {'q': query, 'textDecorations': True, 'textFormat': 'HTML', 'count': 3}
|
| 208 |
+
response = requests.get(f"{SearchEngine.BING_ENDPOINT}/videos/search", headers=headers, params=params)
|
| 209 |
+
if response.status_code == 200:
|
| 210 |
+
logger.debug("Received successful response from Bing Video Search API.")
|
| 211 |
+
data = response.json()
|
| 212 |
+
videos = []
|
| 213 |
+
if 'value' in data:
|
| 214 |
+
for result in data['value']:
|
| 215 |
+
title = result.get('name', 'Unknown Title')
|
| 216 |
+
url = result.get('contentUrl', '')
|
| 217 |
+
# Prioritize YouTube results
|
| 218 |
+
if 'youtube.com' in url.lower():
|
| 219 |
+
videos.append(f"{title}: {url}")
|
| 220 |
+
if len(videos) >= 3:
|
| 221 |
+
break
|
| 222 |
+
# If we don't have enough YouTube results, add other video results
|
| 223 |
+
if len(videos) < 3:
|
| 224 |
+
for result in data['value']:
|
| 225 |
+
title = result.get('name', 'Unknown Title')
|
| 226 |
+
url = result.get('contentUrl', '')
|
| 227 |
+
if url not in [v.split(': ')[1] for v in videos]:
|
| 228 |
+
videos.append(f"{title}: {url}")
|
| 229 |
+
if len(videos) >= 3:
|
| 230 |
+
break
|
| 231 |
+
return videos
|
| 232 |
+
else:
|
| 233 |
+
logger.error(f"Bing Video Search API returned status code {response.status_code} for video search.")
|
| 234 |
+
return ["No video content found."]
|
| 235 |
+
|
| 236 |
+
@staticmethod
|
| 237 |
+
def _search_general(search_term):
|
| 238 |
+
logger.debug(f"Performing a general search for term: {search_term}")
|
| 239 |
+
"""
|
| 240 |
+
Uses Bing Web Search API to perform a general search.
|
| 241 |
+
"""
|
| 242 |
+
headers = {'Ocp-Apim-Subscription-Key': SearchEngine.BING_API_KEY}
|
| 243 |
+
params = {'q': search_term, 'textDecorations': True, 'textFormat': 'HTML', 'count': 5}
|
| 244 |
+
response = requests.get(f"{SearchEngine.BING_ENDPOINT}/search", headers=headers, params=params)
|
| 245 |
+
if response.status_code == 200:
|
| 246 |
+
logger.debug("Received successful response from Bing Web Search API for general search.")
|
| 247 |
+
data = response.json()
|
| 248 |
+
results = []
|
| 249 |
+
if 'webPages' in data and 'value' in data['webPages']:
|
| 250 |
+
for result in data['webPages']['value']:
|
| 251 |
+
results.append(result)
|
| 252 |
+
return results
|
| 253 |
+
else:
|
| 254 |
+
logger.error(f"Bing Web Search API returned status code {response.status_code} for general search.")
|
| 255 |
+
return ["No results found for general search."]
|