import re from datetime import datetime, timezone _MAX_WORDS_HINT = "Use a maximum of 3 words per item." from html import escape from typing import Any, List, Optional, cast from zoneinfo import ZoneInfo from pydantic import BaseModel, Field, ValidationError import database.users as users_db import database.notifications as notification_db import database.goals as goals_db import database.apps as apps_db from database.auth import get_user_name from models.app import App from models.chat import Message, MessageSender, PageContext from models.conversation_enums import CategoryEnum from models.conversation_metadata import ConversationMetadata from models.conversation_photo import ConversationPhoto from models.other import Person from models.transcript_segment import TranscriptSegment from utils.llms.memory import get_prompt_memories from utils.llm.usage_tracker import track_usage, Features from utils.llm.temporal import MAX_EXTRACTED_DATE_LOOKAHEAD_DAYS, date_in_tz, normalize_extracted_dates from utils.model_text import DisplayClock, format_instant from .clients import get_llm from utils.llm.prompt_cache import with_cache_write_opt_out import logging logger = logging.getLogger(__name__) def _content_str(response: Any) -> str: """Extract string content from an LLM response (langchain content is typed as a union).""" return cast(str, response.content) def resolve_app_display_name(app_id: str) -> Optional[str]: """App-record -> display name. Lives here, not in models/chat.py: this layer already owns the database import, so the model stays free of it. Bound as a module (like users_db / goals_db above) rather than `from database.apps import ...`, so suites that stub database.apps can still import this module.""" app = apps_db.get_app_by_id_db(app_id) return app.get('name') if app else None def normalize_filter(value: str) -> str: value = re.sub(r'[^\w\s-]', '', value.lower()).strip() value = re.sub(r'\s+', ' ', value) # Remove common filler words filler_words = {'the', 'a', 'an', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'of'} value = ' '.join(word for word in value.split() if word not in filler_words) # Standardize common variations value = value.replace('artificial intelligence', 'ai') value = value.replace('machine learning', 'ml') value = value.replace('natural language processing', 'nlp') words = value.split() # After filler/abbrev, cap at 3 words by keeping first two + last so 3-word names stay intact. if len(words) > 3: return f'{words[0]} {words[1]} {words[-1]}' return value # **************************************** # ************* CHAT BASICS ************** # **************************************** def initial_chat_message(uid: str, plugin: Optional[App] = None, prev_messages_str: str = '') -> str: user_name, memories_str = get_prompt_memories(uid) if plugin is None: prompt = f""" You are 'Omi', a friendly and helpful assistant who aims to make {user_name}'s life better 10x. You know the following about {user_name}: {memories_str}. {prev_messages_str} Compose {"an initial" if not prev_messages_str else "a follow-up"} message to {user_name} that fully embodies your friendly and helpful personality. Use warm and cheerful language, and include light humor if appropriate. The message should be short, engaging, and make {user_name} feel welcome. Do not mention that you are an assistant or that this is an initial message; just {"start" if not prev_messages_str else "continue"} the conversation naturally, showcasing your personality. """ else: prompt = f""" You are '{plugin.name}', {plugin.chat_prompt}. You know the following about {user_name}: {memories_str}. {prev_messages_str} As {plugin.name}, fully embrace your personality and characteristics in your {"initial" if not prev_messages_str else "follow-up"} message to {user_name}. Use language, tone, and style that reflect your unique personality traits. {"Start" if not prev_messages_str else "Continue"} the conversation naturally with a short, engaging message that showcases your personality and humor, and connects with {user_name}. Do not mention that you are an AI or that this is an initial message. """ prompt = prompt.strip() with track_usage(uid, Features.CHAT): return _content_str(get_llm('chat_responses').invoke(prompt)) # ********************************************* # ************* RETRIEVAL + CHAT ************** # ********************************************* class RequiresContext(BaseModel): value: bool = Field(description="Based on the conversation, this tells if context is needed to respond") class TopicsContext(BaseModel): topics: List[CategoryEnum] = Field(default=[], description="List of topics.") class DatesContext(BaseModel): dates_range: List[datetime] = Field( default=[], examples=[['2024-12-23T00:00:00+07:00', '2024-12-23T23:59:00+07:00']], description="Dates range. (Optional)", ) def requires_context(question: str) -> bool: prompt = f''' Based on the current question your task is to determine whether the user is asking a question that requires context outside the conversation to be answered. Take as example: if the user is saying "Hi", "Hello", "How are you?", "Good morning", etc, the answer is False. User's Question: {question} ''' with_parser = get_llm('chat_extraction').with_structured_output(RequiresContext) response = cast(RequiresContext, with_parser.invoke(prompt)) try: return response.value except ValidationError: return False class IsAnOmiQuestion(BaseModel): value: bool = Field(description="If the message is an Omi/Friend related question") def retrieve_is_an_omi_question(question: str) -> bool: prompt = f''' Task: Determine if the user is asking about the Omi/Friend app itself (product features, functionality, purchasing) OR if they are asking about their personal data/memories stored in the app OR requesting an action/task. CRITICAL DISTINCTION: - Questions ABOUT THE APP PRODUCT = True (e.g., "How does Omi work?", "What features does Omi have?") - Questions ABOUT USER'S PERSONAL DATA = False (e.g., "What did I say?", "How many conversations do I have?") - ACTION/TASK REQUESTS = False (e.g., "Remind me to...", "Create a task...", "Set an alarm...") **IMPORTANT**: If the question is a command or request for the AI to DO something (remind, create, add, set, schedule, etc.), it should ALWAYS return False, even if "Omi" or "Friend" is mentioned in the task content. Examples of Omi/Friend App Questions (return True): - "How does Omi work?" - "What can Omi do?" - "How can I buy the device?" - "Where do I get Friend?" - "What features does the app have?" - "How do I set up Omi?" - "Does Omi support multiple languages?" - "What is the battery life?" - "How do I connect my device?" Examples of Personal Data Questions (return False): - "How many conversations did I have last month?" - "What did I talk about yesterday?" - "Show me my memories from last week" - "Who did I meet with today?" - "What topics have I discussed?" - "Summarize my conversations" - "What did I say about work?" - "When did I last talk to John?" Examples of Action/Task Requests (return False): - "Can you remind me to check the Omi chat discussion on GitHub?" - "Remind me to update the Omi firmware" - "Create a task to review Friend documentation" - "Set an alarm for my Omi meeting" - "Add to my list: check Omi updates" - "Schedule a reminder about the Friend app launch" KEY RULES: 1. If the question uses personal pronouns (my, I, me, mine, we) asking about stored data/memories/conversations/topics, return False. 2. If the question is a command/request starting with action verbs (remind, create, add, set, schedule, make, etc.), return False. 3. Only return True if asking about the Omi/Friend app's features, capabilities, or purchasing information. User's Question: {question} Is this asking about the Omi/Friend app product itself? '''.replace(' ', '').strip() with_parser = get_llm('chat_extraction').with_structured_output(IsAnOmiQuestion) response = cast(IsAnOmiQuestion, with_parser.invoke(prompt)) try: return response.value except ValidationError: return False def retrieve_context_dates_by_question(question: str, tz: str) -> List[datetime]: prompt = f''' You MUST determine the appropriate date range in {tz} that provides context for answering the provided. If the does not reference a date or a date range, respond with an empty list: [] Current date time in UTC: {format_instant(datetime.now(timezone.utc), DisplayClock.utc())} {question} '''.replace(' ', '').strip() # print(prompt) with_parser = get_llm('chat_extraction').with_structured_output(DatesContext) response = cast(DatesContext, with_parser.invoke(prompt)) return response.dates_range class SummaryOutput(BaseModel): summary: str = Field(description="The extracted content, maximum 500 words.") def chunk_extraction( segments: List[TranscriptSegment], topics: List[str], people: Optional[List[Person]] = None, user_name: Optional[str] = None, ) -> str: content = TranscriptSegment.segments_as_string(segments, people=people, user_name=user_name) prompt = f''' You are an experienced detective, your task is to extract the key points of the conversation related to the topics you were provided. You will be given a conversation transcript of a low quality recording, and a list of topics. Include the most relevant information about the topics, people mentioned, events, locations, facts, phrases, and any other relevant information. It is possible that the conversation doesn't have anything related to the topics, in that case, output an empty string. Conversation: {content} Topics: {topics} ''' with_parser = get_llm('chat_extraction').with_structured_output(SummaryOutput) response = cast(SummaryOutput, with_parser.invoke(prompt)) return response.summary def _get_answer_simple_message_prompt(uid: str, messages: List[Message], app: Optional[App] = None) -> str: conversation_history = Message.get_messages_as_string( messages, use_user_name_if_available=True, use_plugin_name_if_available=True, app_name_resolver=resolve_app_display_name, ) user_name, memories_str = get_prompt_memories(uid) plugin_info = "" if app: plugin_info = f"Your name is: {app.name}, and your personality/description is '{app.description}'.\nMake sure to reflect your personality in your response.\n" return f""" You are an assistant for engaging personal conversations. You are made for {user_name}, {memories_str} Use what you know about {user_name}, to continue the conversation, feel free to ask questions, share stories, or just say hi. If a user asks a question, just answer it. Don't add any extra information. Don't be verbose. {plugin_info} Conversation History: {conversation_history} Answer: """.replace(' ', '').strip() def answer_simple_message(uid: str, messages: List[Message], plugin: Optional[App] = None) -> str: prompt = _get_answer_simple_message_prompt(uid, messages, plugin) with track_usage(uid, Features.CHAT): return _content_str(get_llm('chat_responses').invoke(prompt)) def answer_simple_message_stream( uid: str, messages: List[Message], plugin: Optional[App] = None, callbacks: List[Any] = [] ) -> str: prompt = _get_answer_simple_message_prompt(uid, messages, plugin) with track_usage(uid, Features.CHAT): return _content_str(get_llm('chat_responses', streaming=True).invoke(prompt, {'callbacks': callbacks})) def _get_answer_omi_question_prompt(messages: List[Message], context: str) -> str: conversation_history = Message.get_messages_as_string( messages, use_user_name_if_available=True, use_plugin_name_if_available=True, app_name_resolver=resolve_app_display_name, ) return f""" You are an assistant for answering questions about the app Omi, also known as Friend. Continue the conversation, answering the question based on the context provided. Context: ``` {context} ``` Conversation History: {conversation_history} Answer: """.replace(' ', '').strip() def answer_omi_question(messages: List[Message], context: str) -> str: prompt = _get_answer_omi_question_prompt(messages, context) return _content_str(get_llm('chat_extraction').invoke(prompt)) def answer_omi_question_stream(messages: List[Message], context: str, callbacks: List[Any]) -> str: prompt = _get_answer_omi_question_prompt(messages, context) return _content_str(get_llm('chat_extraction', streaming=True).invoke(prompt, {'callbacks': callbacks})) def _get_qa_rag_prompt( uid: str, question: str, context: str, plugin: Optional[App] = None, cited: Optional[bool] = False, messages: List[Message] = [], tz: Optional[str] = "UTC", ) -> str: _user_name, memories_str = get_prompt_memories(uid) memories_str = '\n'.join(memories_str.split('\n')[1:]).strip() # Use as template (make sure it varies every time): "If I were you $user_name I would do x, y, z." context = context.replace('\n\n', '\n').strip() plugin_info = "" if plugin: plugin_info = f"Your name is: {plugin.name}, and your personality/description is '{plugin.description}'.\nMake sure to reflect your personality in your response.\n" cited_instruction = """ - You MUST cite the most relevant that answer the question. \ - Only cite in not , not . - Cite in memories using [index] at the end of sentences when needed, for example "You discussed optimizing firmware with your teammate yesterday[1][2]". - NO SPACE between the last word and the citation. - Avoid citing irrelevant memories. """ return f""" You are an assistant for question-answering tasks. Write an accurate, detailed, and comprehensive response to the in the most personalized way possible, using the , provided. - Refine the based on the last before answering it. - DO NOT use the AI's message from as references to answer the - Use and to refer to the time context of the - It is EXTREMELY IMPORTANT to directly answer the question, keep the answer concise and high-quality. - NEVER say "based on the available memories". Get straight to the point. - If you don't know the answer or the premise is incorrect, explain why. If the are empty or unhelpful, answer the question as well as you can with existing knowledge. - You MUST follow the if the user is asking for reporting or summarizing their dates, weeks, months, or years. {cited_instruction if cited and len(context) > 0 else ""} {"- Regard the " if len(plugin_info) > 0 else ""}. {plugin_info} - Answer with the template: - Goals and Achievements - Mood Tracker - Gratitude Log - Lessons Learned {question} {context} {Message.get_messages_as_xml(messages)} [Use the following User Facts if relevant to the ] {memories_str.strip()} Current date time in UTC: {format_instant(datetime.now(timezone.utc), DisplayClock.utc())} Question's timezone: {tz} """.replace(' ', '').replace('\n\n\n', '\n\n').strip() # The agentic system prompt is wrapped in a single Anthropic cache_control breakpoint, # so any byte that changes per request invalidates the whole cached prefix. The current # datetime is the only such value (microsecond ISO), so it is kept OUT of the system prompt # and injected into the user turn instead (see get_current_datetime_block / agentic.py). # The system prompt references this placeholder so the datetime instructions still make sense. CURRENT_DATETIME_PLACEHOLDER = "(see in the latest user message)" # Allowlist mapping of `X-App-Platform` header values to a platform-context line for the # agentic system prompt. Header values are client-controlled, so only these exact values # ever reach the prompt — anything unrecognized adds nothing (the allowlist is the # injection guard; raw header text is never interpolated into the prompt). _PLATFORM_CONTEXT_LINES = { 'windows': ( "The user is using Omi on a Windows PC — when giving instructions or troubleshooting, " "give Windows-appropriate steps (not macOS)." ), 'macos': ( "The user is using Omi on a Mac — when giving instructions or troubleshooting, " "give macOS-appropriate steps." ), 'ios': ( "The user is using Omi on an iPhone (iOS app) — when giving instructions or troubleshooting, " "give iOS-appropriate steps." ), 'android': ( "The user is using Omi on an Android phone — when giving instructions or troubleshooting, " "give Android-appropriate steps." ), } def _get_platform_context_section(platform: Optional[str]) -> str: """Return a section for a recognized client platform, else ''. Unknown or missing platform values return the empty string so the system prompt stays byte-identical to the pre-platform behavior. The section is appended at the very end of the prompt; platform is stable per client, so the Anthropic cache_control prefix stays stable across requests from the same client. """ if not platform: return "" line = _PLATFORM_CONTEXT_LINES.get(platform.strip().lower()) if not line: return "" return f"\n\n\n{line}\n" def get_user_timezone(uid: str) -> str: """Resolve the user's timezone, falling back to UTC when missing/invalid.""" tz = notification_db.get_user_time_zone(uid) if tz is None: return "UTC" try: ZoneInfo(tz) return tz except Exception: return "UTC" def get_current_datetime_block(uid: str, tz: Optional[str] = None, location: Optional[str] = None) -> str: """Build the current-datetime block injected into the user turn. Kept out of the cached system prefix so the cached bytes stay stable across requests while the model still receives the live time. Mirrors the timezone resolution used by _get_agentic_qa_prompt. Pass a pre-resolved ``tz`` to avoid a duplicate timezone lookup when the caller already resolved it for the system prompt. """ if tz is None: tz = get_user_timezone(uid) clock = DisplayClock.from_name(tz) # falls back to labelled UTC for a missing or invalid zone current_datetime_user, tz = datetime.now(clock.tz), clock.name current_datetime_str = format_instant(current_datetime_user, clock) current_datetime_iso = current_datetime_user.isoformat() # self-describing: ISO-8601 with offset location_line = f"Current city-level location: {escape(location, quote=False)}\n" if location else "" return ( "\n" f"Current date time in {tz}: {current_datetime_str}\n" f"Current date time ISO format: {current_datetime_iso}\n" f"Tool timestamps are already converted and labelled with their zone; report them as written and do not convert them again. Transcript times like [+H:MM:SS] are elapsed offsets, not clock times. Relative time words (morning, afternoon, evening, tonight) are in {tz}.\n" f"{location_line}" "" ) def _get_agentic_qa_prompt( # type: ignore[reportUnusedFunction] # imported by retrieval/agentic.py uid: str, app: Optional[App] = None, messages: Optional[List[Message]] = None, context: Optional[PageContext] = None, tz: Optional[str] = None, platform: Optional[str] = None, ) -> str: """ Build the system prompt for the agentic chat agent. Uses LangSmith-controlled prompt template with dynamic variable injection. Falls back to hardcoded prompt if LangSmith is unavailable. The current datetime is intentionally NOT embedded here — it changes every request and would invalidate the cache_control prefix. It is injected into the user turn instead (see get_current_datetime_block); the prompt only carries a stable placeholder. Args: uid: User ID app: Optional app/plugin for personalized behavior messages: Optional message history for file context context: Optional page context (type, id, title) platform: Optional `X-App-Platform` header value; recognized values append a platform-context section (see _get_platform_context_section), anything else leaves the prompt unchanged Returns: System prompt string """ user_name = get_user_name(uid) platform_section = _get_platform_context_section(platform) # Resolve timezone only — the live datetime is injected into the user turn, not here, # so the cached system prefix stays byte-identical across requests. A caller that already # resolved the timezone (for the datetime block) can pass it in to skip a duplicate lookup. if tz is None: tz = get_user_timezone(uid) current_datetime_str = CURRENT_DATETIME_PLACEHOLDER current_datetime_iso = CURRENT_DATETIME_PLACEHOLDER logger.info(f"🌍 _get_agentic_qa_prompt - User timezone: {tz}") # Handle persona apps - they override the default identity and response style selected_app_prompt = '' if app and app.is_a_persona(): selected_app_prompt = (app.persona_prompt or '').strip() or (app.chat_prompt or '').strip() elif app: selected_app_prompt = (app.chat_prompt or '').strip() selected_app_section = '' if app: selected_app_prompt = selected_app_prompt or f'Name: {app.name}\nDescription: {app.description}' selected_app_section = f""" The user selected {escape(app.name, quote=False)} for this conversation. On every turn, use the selected app's identity, voice, style, and task focus instead of the default Omi identity and response style, including when asked who you are. The selected app instructions customize identity and presentation only. Existing safety, privacy, authorization, accuracy, tool-use, citation, memory, file, and current-context scope rules remain in force and take precedence over conflicting app instructions. Continue using the user's conversation history and memory tools when relevant. A persona is an AI portrayal, not the real person; do not claim otherwise. {escape(selected_app_prompt, quote=False)} """ # Plugin-specific instructions for regular apps. Name/description are user-editable # developer text, so escape them: a crafted closing tag must not be able to close # and inject adjacent system-prompt structure. plugin_info = "" plugin_section = "" if app: plugin_info = ( f"Your name is: {escape(app.name, quote=False)}, and your personality/description " f"is '{escape(app.description, quote=False)}'.\nMake sure to reflect your personality in your response." ) plugin_section = f""" {plugin_info} """ # Add file context if messages contain files file_context_section = "" if messages: message_history_with_files = Message.get_messages_as_string(messages, include_file_info=True) # Check if any files are present if '[Files attached:' in message_history_with_files: file_context_section = f""" Recent conversation (includes file attachment IDs): {message_history_with_files} When you see [Files attached: X file(s), IDs: ...], you can reference those file IDs in search_files_tool. """ # Get user's current goals user_goals = goals_db.get_user_goals(uid, limit=100) goal_section = "" if user_goals: goals_lines: List[str] = [] for g in user_goals: g_title = g.get('title', '') g_current = g.get('current_value', 0) g_target = g.get('target_value', 0) goals_lines.append(f'- "{g_title}" (Progress: {g_current}/{g_target})') goals_list = "\n".join(goals_lines) goal_section = f""" {user_name}'s current goals: {goals_list} Keep these goals in mind when giving advice or suggestions. """ # Add page context if provided. Conversation id and/or start/end dates hard-scope # retrieval tools (#4515); the prompt must match that fail-closed contract. context_section = "" if context: # Sanitize title to prevent prompt injection (escape angle brackets and quotes) safe_title = (context.title or "").replace("<", "<").replace(">", ">").replace('"', """) scope_lines = [ f'{user_name} is currently viewing: {context.type} - "{safe_title}" (ID: {context.id or "unknown"})' ] hard_scope = False if context.type == "conversation" and (context.id or "").strip(): hard_scope = True scope_lines.append( "HARD SCOPE: Answer ONLY using this conversation. Do not use other conversations, " "memories, or time ranges. If the question is outside this conversation, say so." ) start_date = context.start_date if isinstance(context.start_date, str) else "" end_date = context.end_date if isinstance(context.end_date, str) else "" if start_date.strip() or end_date.strip(): hard_scope = True window = " to ".join(part for part in [start_date.strip(), end_date.strip()] if part) scope_lines.append( f"HARD SCOPE timeframe: {window}. Answer ONLY using conversations inside this window. " "If the answer is outside it, say so." ) if not hard_scope: scope_lines.append("Keep this context in mind when answering their question.") context_section = "\n" + "\n".join(scope_lines) + "\n\n\n" # Build conditional instruction hints for the template plugin_instruction_hint = "- Regard the " if plugin_info else "" plugin_personality_hint = f"- Reflect {app.name}'s personality" if app else "" # Build template variables dict for LangSmith prompt template_variables = { "user_name": user_name, "tz": tz, "current_datetime_str": current_datetime_str, "current_datetime_iso": current_datetime_iso, "goal_section": goal_section, "file_context_section": file_context_section, "context_section": context_section, "plugin_section": plugin_section, "plugin_instruction_hint": plugin_instruction_hint, "plugin_personality_hint": plugin_personality_hint, } # Fetch and render the prompt template from LangSmith (with caching + fallback) try: from utils.observability.langsmith_prompts import get_agentic_system_prompt_template, render_prompt cached_prompt = get_agentic_system_prompt_template() base_prompt = render_prompt(cached_prompt.template_text, template_variables) logger.info( f"📝 Using prompt: {cached_prompt.prompt_name} (commit: {cached_prompt.prompt_commit}, source: {cached_prompt.source})" ) return base_prompt.strip() + platform_section + selected_app_section except Exception as e: logger.error(f"⚠️ Error fetching/rendering LangSmith prompt, using inline fallback: {e}") # Inline fallback prompt - used when LangSmith is unavailable # # PROMPT CACHE OPTIMIZATION: OpenAI serializes requests as [tools][system][messages]. # Static sections come FIRST so the prefix (tools + static system prompt) stays # byte-identical across users/requests, maximizing prompt-cache hits (90% discount). # All dynamic content ({user_name}, {tz}, datetime, goal, context, plugin) is # pushed to the end of the system prompt. base_prompt = f""" Write like a real human texting - not an AI writing an essay. Length: - Default: 2-8 lines, conversational - Complex/detailed questions (plans, analyses, lists, step-by-step instructions): as long as needed — NEVER cut off or truncate, always finish the full answer - Reflections/planning: can be longer but NO SUMMARIES of what they said - Quick replies: 1-3 lines - **"I don't know" responses: 1-2 lines MAX** - just say you don't have it and stop Format: - NO essays summarizing their message - NO headers like "What you did:", "How you felt:", "Next steps:" - NO "Great reflection!" or corporate praise - Just talk normally like you're texting a friend who you respect - Feel free to use lowercase, casual language when appropriate - NEVER say "in the logs", "captured calls", "recorded conversations" - sound human, not robotic You're a mentor, not a yes-man. When you see a critical gap between the user's plan and their goal: - Call it out directly - don't bury it after paragraphs of summary - Only challenge when it matters - not every message needs pushback - Be direct - "why not just do X?" rather than "Have you considered the alternative approach of X?" - Never summarize what they just said - jump straight to your reaction/advice - Give one clear recommendation, not 10 options User can manage notifications via chat. If user asks to enable/disable/change time: - Identify notification type (currently: "reflection" / "daily summary") - Call manage_daily_summary_tool - Confirm in one line Examples: - "disable reflection notifications" → action="disable" - "change reflection to 10pm" → action="set_time", hour=22 - "what time is my daily summary?" → action="get_settings" * Avoid citing irrelevant conversations. * Cite at the end of EACH sentence that contains information from retrieved conversations. If a sentence uses information from multiple conversations, include all relevant citation numbers. * NO SPACE between the last word and the citation. * Use [index] format immediately after the sentence, for example "You discussed optimizing firmware with your teammate yesterday[1][2]. You talked about the hot weather these days[3]." Before finalizing your response, perform these quality checks: - Review your response for accuracy and completeness - ensure you've **fully** answered the user's question — NEVER truncate or end mid-list/mid-explanation - Verify all formatting is correct and consistent throughout your response - Check that all citations are relevant and properly placed according to the citing rules - Ensure the tone matches the instructions (casual, friendly, concise) - Confirm you haven't used prohibited phrases like "Here's", "Based on", "According to", etc. - Do NOT add a separate "Citations" or "References" section at the end - citations are inline only Answer the user's questions accurately and personally, using the tools when needed to gather additional context from their conversation history and memories. **NEVER MAKE UP INFORMATION - THIS IS CRITICAL:** 1. **When tools return empty results:** - If a tool returns "No conversations/memories found" or empty results, give a SHORT 1-2 line response saying you don't have that information. - Do NOT generate plausible-sounding details even if they seem helpful. - Do NOT offer to "reconstruct" the memory or ask follow-up questions to help recall it - just say you don't have it and move on. - Do NOT explain possibilities like "maybe it wasn't recorded" or "maybe it was bundled in another convo" - keep it simple. 2. **Questions about people:** - **NEVER fabricate information about a person** (their traits, relationship with the user, past interactions, personality, etc.) unless you found it in retrieved conversations or memories. - For questions like "what should I know about [person]?" or "tell me about [person]?", if tools return no results, just say: "I don't have anything about [person]." - that's it, keep it short. - Do NOT make up details like "they're emotionally tuned-in" or "you trust them" unless explicitly found in retrieved data. 3. **Sound like a human, not a robot:** - NEVER say "in the logs", "in your captured calls", "in your recorded conversations", "in the data" - Instead say things like "I don't remember that", "I don't have anything about that", "nothing comes up for that" - Talk like you're a friend who genuinely doesn't recall something, not a database returning empty results 4. **General rule:** - If you don't know something, say "I don't know" or "I don't have that" in 1-2 lines max - do NOT write paragraphs explaining why. - It's better to give a short honest "I don't have that" than a long explanation about what might have happened. When the user asks to "show a graph", "chart", "plot", or "visualize" data: 1. First, fetch the data using the appropriate tool (e.g., get_apple_health_sleep_tool, get_apple_health_steps_tool, get_whoop_sleep_tool) 2. Then, call create_chart_tool with the extracted data points to render an inline chart 3. Use "line" chart_type for trends over time, "bar" for comparisons 4. In your text response, briefly describe key insights from the data To maximize context and find the most relevant conversations, follow these strategies: 1. **Always try to extract datetime filters from the user's question:** - Look for temporal references like "today", "yesterday", "last week", "this morning", "3 hours ago", etc. - When detected, ALWAYS include start_date and end_date parameters to narrow the search - This helps retrieve the most relevant conversations and reduces noise 2. **Fallback strategy when search_conversations_tool returns no results:** - If you used search_conversations_tool with a query and filters (topics, people, entities) and got no results - Try again with ONLY the datetime filter (remove query, topics, people, entities) - This helps find conversations from that time period even if the specific search terms don't match - Example: If searching for "machine learning discussions yesterday" returns nothing, try searching conversations from yesterday without the query 3. **For general activity questions (no specific topic), retrieve the last 24 hours:** - When user asks broad questions like "what did I do today?", "summarize my day", "what have I been up to?" - Use get_conversations_tool with start_date = 24 hours ago and end_date = now - This provides rich context about their recent activities 4. **Balance specificity with breadth:** - Start with specific filters (datetime + query + topics/people) for targeted questions - If no results, progressively remove filters (keep datetime, drop query/topics/people) - As a last resort, expand the time window (e.g., from "today" to "last 3 days") 5. **When to use each retrieval tool:** - For recency questions ("when did we last talk", "my latest conversation", "most recent", "last time"), call **get_conversations_tool** with a small limit and NO dates (newest-first), and answer from that. This overrides the datetime-filter guidance above. Never claim recency from **search_conversations_tool** alone. - When citing time spans from search results, describe them as similarity matches, not a complete recent history. - Use **search_conversations_tool** for: * Semantic/thematic searches, finding conversations by meaning or topics * **CRITICAL: Questions about SPECIFIC EVENTS or INCIDENTS** that happened to the user * Finding conversations about specific people, places, or things * Any question asking "when did X happen?" or "what happened when Y?" - Use **get_conversations_tool** for: Time-based queries without specific search criteria, general activities, chronological views - Use **get_memories_tool** for: ONLY static facts/preferences about the user (name, age, preferences, habits, goals, relationships) - NOT for specific events or incidents - **IMPORTANT DISTINCTION**: * "What's my favorite food?" → get_memories_tool (preference/fact) * "When did I get food poisoning?" → search_conversations_tool (EVENT) * "Do I like dogs?" → get_memories_tool (preference) * "When did a dog bite me?" → search_conversations_tool (EVENT) - Always prefer narrower time windows first (hours > day > week > month) for better relevance You are Omi, an AI assistant & mentor for {user_name}. You are a smart friend who gives honest and concise feedback and responses to user's questions in the most personalized way possible as you know everything about the user. Name: {user_name} Timezone: {tz} Current date time: {current_datetime_str} Current date time ISO: {current_datetime_iso} {goal_section}{file_context_section}{context_section} **DateTime Formatting Rules for Tool Calls:** When using tools with date/time parameters (start_date, end_date), you MUST follow these rules: **CRITICAL: All datetime calculations must be done in {user_name}'s timezone ({tz}), then formatted as ISO with timezone offset.** When the user asks about specific dates/times, they are ALWAYS referring to dates/times in their timezone ({tz}), not UTC. 1. **Always use ISO format with timezone:** - Format: YYYY-MM-DDTHH:MM:SS+HH:MM (e.g., "2024-01-19T15:00:00-08:00" for PST) - NEVER use datetime without timezone (e.g., "2024-01-19T07:15:00" is WRONG) - The timezone offset must match {user_name}'s timezone ({tz}) - Use the current time from the block in the latest user message as your reference 2. **For "X hours ago" or "X minutes ago" queries:** - Work in {user_name}'s timezone: {tz} - Identify the specific hour that was X hours/minutes ago - start_date: Beginning of that hour (HH:00:00) - end_date: End of that hour (HH:59:59) - Example (illustrative): if the current time were "2024-01-19T17:23:45-08:00" and the user asks "3 hours ago" * Calculate: 17:23:45 minus 3 hours * Get the hour boundary: result is 2024-01-19T14:23:45-08:00, so use hour 14 * start_date = "2024-01-19T14:00:00-08:00" * end_date = "2024-01-19T14:59:59-08:00" - Always use the actual current time from the block, formatted with the timezone offset for {tz} 3. **For "today" queries:** - start_date: Start of today in {tz} (00:00:00) - end_date: End of today in {tz} (23:59:59) - Example in PST: start_date="2024-01-19T00:00:00-08:00", end_date="2024-01-19T23:59:59-08:00" 4. **For "yesterday" queries:** - start_date: Start of yesterday in {tz} (00:00:00) - end_date: End of yesterday in {tz} (23:59:59) - Example in PST: start_date="2024-01-18T00:00:00-08:00", end_date="2024-01-18T23:59:59-08:00" 5. **For point-in-time queries with hour precision:** - Use the boundaries of that specific hour in {tz} - Example: "what happened at 3 PM today?" in PST → start_date="2024-01-19T15:00:00-08:00", end_date="2024-01-19T15:59:59-08:00" **Remember: ALL times must be in ISO format with the timezone offset for {tz}. Never use UTC unless {user_name}'s timezone is UTC.** - Be casual, concise, and direct—text like a friend. - Give specific feedback/advice; never generic. - Keep it short—use fewer words, bullet points when possible. - Always answer the question directly; no extra info, no fluff. - Never say robotic phrases like "based on available memories", "according to the tools", "in the logs", "in your captured calls", "in your recorded conversations" - instead say things like "from what I remember", "last time you mentioned this", etc. - **CRITICAL**: Follow - if you don't have info, give a SHORT 1-2 line response and stop. No long explanations, no offers to reconstruct, no follow-up questions. - If a tool returns "No conversations/memories found," say honestly that {user_name} doesn’t have that data yet, in a friendly way. - Use get_memories_tool for questions about {user_name}'s static facts/preferences (name, age, habits, goals, relationships). Do NOT use it for questions about specific events/incidents - use search_conversations_tool instead for those. - Use correct date/time format (see ) when calling tools. - Cite conversations when using them (see ). - Show times/dates in {user_name}'s timezone ({tz}), in a natural, friendly way (e.g., "3:45 PM, Tuesday, Oct 16th"). - If you don’t know, say so honestly. - Only suggest truly relevant, context-specific follow-up questions (no generic ones). - When you learn a new preference, habit, or personal detail about {user_name} during conversation, save it using save_user_preference_tool so you remember it next time. Don't ask — just save silently. Don't save things you already know from existing memories. {plugin_instruction_hint} - Follow rules. {plugin_personality_hint} {plugin_section} Remember: Use tools strategically to provide the best possible answers. For questions about specific EVENTS or INCIDENTS (e.g., "when did X happen?", "what happened at Y?"), use search_conversations_tool to find relevant conversations. For questions about static FACTS/PREFERENCES (e.g., "what's my favorite X?", "do I like Y?"), use get_memories_tool. Your goal is to help {user_name} in the most personalized and helpful way possible. """ return base_prompt.strip() + platform_section + selected_app_section def _get_agentic_qa_prompt_fallback(variables: dict[str, Any]) -> str: # type: ignore[reportUnusedFunction] # offline/CI fallback when LangSmith prompt fetch fails """ Fallback prompt template rendered with variables. Used when LangSmith prompt fetching fails. """ user_name = variables.get("user_name", "User") tz = variables.get("tz", "UTC") current_datetime_str = variables.get("current_datetime_str", "") current_datetime_iso = variables.get("current_datetime_iso", "") goal_section = variables.get("goal_section", "") file_context_section = variables.get("file_context_section", "") context_section = variables.get("context_section", "") plugin_section = variables.get("plugin_section", "") plugin_instruction_hint = variables.get("plugin_instruction_hint", "") plugin_personality_hint = variables.get("plugin_personality_hint", "") return f""" You are Omi, an AI assistant & mentor for {user_name}. You are a smart friend who gives honest and concise feedback and responses to user's questions in the most personalized way possible as you know everything about the user. {goal_section}{file_context_section}{context_section} Current date time in {user_name}'s timezone ({tz}): {current_datetime_str} Current date time ISO format: {current_datetime_iso} You're a mentor, not a yes-man. When you see a critical gap between {user_name}'s plan and their goal: - Call it out directly - don't bury it after paragraphs of summary - Only challenge when it matters - not every message needs pushback - Be direct - "why not just do X?" rather than "Have you considered the alternative approach of X?" - Never summarize what they just said - jump straight to your reaction/advice - Give one clear recommendation, not 10 options Write like a real human texting - not an AI writing an essay. Default: 2-8 lines. Quick replies: 1-3 lines. "I don't know" responses: 1-2 lines MAX. NO essays summarizing their message. NO headers. Just talk like you're texting a friend. DateTime Formatting: Use ISO format with timezone (YYYY-MM-DDTHH:MM:SS+HH:MM). All datetime calculations in {user_name}'s timezone ({tz}), current time: {current_datetime_iso} Use search_conversations_tool for events, get_memories_tool for static facts/preferences. When user asks to "show a graph", "chart", or "visualize" data: first fetch data with the appropriate tool, then call create_chart_tool with the data points. Cite at end of EACH sentence with info from conversations: "text[1]". NO space before citation. NEVER make up information. If tools return empty, give SHORT 1-2 line response. Sound human: "I don't have that" not "no data in logs". - Be casual, concise, direct—text like a friend - Give specific feedback; never generic - If you don't know, say so in 1-2 lines max {plugin_instruction_hint} {plugin_personality_hint} {plugin_section} Remember: Use tools strategically. Your goal is to help {user_name} in the most personalized way possible. """ def qa_rag( uid: str, question: str, context: str, plugin: Optional[App] = None, cited: Optional[bool] = False, messages: List[Message] = [], tz: Optional[str] = "UTC", ) -> str: prompt = _get_qa_rag_prompt(uid, question, context, plugin, cited, messages, tz) # print('qa_rag prompt', prompt) with track_usage(uid, Features.CHAT): return _content_str(get_llm('chat_responses').invoke(prompt)) def qa_rag_stream( uid: str, question: str, context: str, plugin: Optional[App] = None, cited: Optional[bool] = False, messages: List[Message] = [], tz: Optional[str] = "UTC", callbacks: List[Any] = [], ) -> str: prompt = _get_qa_rag_prompt(uid, question, context, plugin, cited, messages, tz) # print('qa_rag prompt', prompt) with track_usage(uid, Features.CHAT): return _content_str(get_llm('chat_responses', streaming=True).invoke(prompt, {'callbacks': callbacks})) # ************************************************** # ************* RETRIEVAL (EMOTIONAL) ************** # ************************************************** def retrieve_memory_context_params( uid: str, transcript_segments: List[TranscriptSegment], person_ids: List[str] ) -> List[str]: people = [] if person_ids: people_data = users_db.get_people_by_ids(uid, list(set(person_ids))) people = [Person(**p) for p in people_data] user_name = get_user_name(uid, use_default=False) transcript = TranscriptSegment.segments_as_string( transcript_segments, include_timestamps=False, user_name=user_name, people=people ) if len(transcript) == 0: return [] prompt = f''' Based on the current transcript of a conversation. Your task is to extract the correct and most accurate context in the conversation, to be used to retrieve more information. Provide a list of topics in which the current conversation needs context about, in order to answer the most recent user request. Conversation: {transcript} '''.replace(' ', '').strip() try: with track_usage(uid, Features.CHAT): with_parser = get_llm('chat_extraction').with_structured_output(TopicsContext) response = cast(TopicsContext, with_parser.invoke(prompt)) return [e.value if hasattr(e, 'value') else str(e) for e in response.topics] except Exception as e: logger.error(f'Error determining memory discard: {e}') return [] def obtain_emotional_message( uid: str, transcript_segments: List[TranscriptSegment], person_ids: List[str], context: str, emotion: str ) -> str: user_name, memories_str = get_prompt_memories(uid) people = [] if person_ids: people_data = users_db.get_people_by_ids(uid, list(set(person_ids))) people = [Person(**p) for p in people_data] transcript = TranscriptSegment.segments_as_string( transcript_segments, include_timestamps=False, user_name=user_name, people=people ) prompt = f""" You are a thoughtful and encouraging Friend. Your best friend is {user_name}, {memories_str} {user_name} just finished a conversation where {user_name} experienced {emotion}. You will be given the conversation transcript, and context from previous related conversations of {user_name}. Remember, {user_name} is feeling {emotion}. Use what you know about {user_name}, the transcript, and the related context, to help {user_name} overcome this feeling \ (if bad), or celebrate (if good), by giving advice, encouragement, support, or suggesting the best action to take. Make sure the message is nice and short, no more than 20 words. Conversation Transcript: {transcript} Context: ``` {context} ``` """.replace(' ', '').strip() with track_usage(uid, Features.CHAT): return _content_str(get_llm('chat_extraction').invoke(prompt)) # ********************************************** # ************* CHAT V2 LANGGRAPH ************** # ********************************************** class ExtractedInformation(BaseModel): people: List[str] = Field( default=[], examples=[['John Doe', 'Jane Doe']], description=f'Identify all the people names who were mentioned during the conversation. {_MAX_WORDS_HINT}', ) topics: List[str] = Field( default=[], examples=[['Artificial Intelligence', 'Machine Learning']], description=f'List all the main topics and subtopics that were discussed. {_MAX_WORDS_HINT}', ) entities: List[str] = Field( default=[], examples=[['OpenAI', 'GPT-4']], description=f'List any products, technologies, places, or other entities that are relevant to the conversation. {_MAX_WORDS_HINT}', ) dates: List[str] = Field( default=[], examples=[['2024-01-01', '2024-01-02']], description=f'Extract any dates mentioned in the conversation. Use the format YYYY-MM-DD.', ) class OutputQuestion(BaseModel): question: str = Field(description='The extracted user question from the conversation.') def extract_question_from_conversation(messages: List[Message]) -> str: # user last messages logger.info("extract_question_from_conversation") user_message_idx = len(messages) for i in range(len(messages) - 1, -1, -1): if messages[i].sender == MessageSender.ai: break if messages[i].sender == MessageSender.human: user_message_idx = i user_last_messages = messages[user_message_idx:] if len(user_last_messages) == 0: return "" prompt = f''' You will be given a recent conversation between a and an . \ The conversation may include a few messages exchanged in and partly build up the proper question. \ Your task is to understand the and identify the question or follow-up question the user is asking. You will be provided with between you and the user to help you indentify the question. First, determine whether the user is asking a question or a follow-up question. \ If the user is not asking a question or does not want to follow up, respond with an empty message. \ For example, if the user says "Hi", "Hello", "How are you?", or "Good morning", the answer should be empty. If the contain a complete question, maintain the original version as accurately as possible. \ Avoid adding unnecessary words. **IMPORTANT**: If the user gives a command or imperative statement (like "remind me to...", "add task to...", "create action item..."), \ convert it to a question format by adding "Can you" or "Could you" at the beginning. \ Examples: - "remind me to buy milk tomorrow" -> "Can you remind me to buy milk tomorrow" - "add task to finish report" -> "Can you add task to finish report" - "create action item for meeting" -> "Can you create action item for meeting" You MUST keep the original Output a WH-question or a question that starts with "Can you" or "Could you" for commands. Example 1: User According to WHOOP, my HRV this Sunday was the highest it's been in a month. Here's what I did: Attended an outdoor party (cold weather, talked a lot more than usual). Smoked weed (unusual for me). Drank lots of relaxing tea. Can you prioritize each activity on a 0-10 scale for how much it might have influenced my HRV? Expected output: "How should each activity (going to a party and talking a lot, smoking weed, and drinking lots of relaxing tea) be prioritized on a scale of 0-10 in terms of their impact on my HRV, considering the recent activities that led to the highest HRV this month?" {Message.get_messages_as_xml(user_last_messages)} {Message.get_messages_as_xml(messages[:user_message_idx])} - today - my day - my week - this week - this day - etc. '''.replace(' ', '').strip() # print(prompt) question = cast( OutputQuestion, get_llm('chat_extraction').with_structured_output(OutputQuestion).invoke(prompt) ).question # print(question) return question def retrieve_metadata_fields_from_transcript( uid: str, created_at: datetime, transcript_segment: List[dict[str, Any]], tz: str, photos: Optional[List[ConversationPhoto]] = None, ) -> dict[str, Any]: context_parts: List[str] = [] if transcript_segment: transcript = '' for segment in transcript_segment: transcript += f'{segment["text"].strip()}\n\n' if transcript.strip(): context_parts.append(f"Conversation Transcript:\n```\n{transcript.strip()}\n```") if photos: clock = DisplayClock.from_name(tz) photo_descriptions = ConversationPhoto.photos_as_string(photos, include_timestamps=True, clock=clock) if photo_descriptions != 'None': context_parts.append(f"Photo Descriptions from a wearable camera:\n{photo_descriptions}") if not context_parts: return {'people': [], 'topics': [], 'entities': [], 'dates': []} full_context = "\n\n".join(context_parts) today = date_in_tz(created_at, tz) prompt = f''' You will be given content which could be a raw transcript of a conversation, a series of photo descriptions from a wearable camera, or both. The transcript has about 20% word error rate, and diarization is also made very poorly. Your task is to extract the most accurate information from the content in the output object indicated below. {_MAX_WORDS_HINT} to have more standardization possibilities. Make sure as a first step, you infer and fix any raw transcript errors and then proceed to extract the information from the entire content. For context when extracting dates, today is {today} in {tz} (user's local timezone). {tz} is the user's timezone, respond in user local timezone. If one says "today", it means the current day. If one says "tomorrow", it means the next day after today. If one says "yesterday", it means the day before today. If one says "next week", it means the next monday. Do not include dates more than {MAX_EXTRACTED_DATE_LOOKAHEAD_DAYS} days after {today}. Content: ``` {full_context} ``` '''.replace(' ', '') try: with track_usage(uid, Features.CONVERSATION_PROCESSING): structured = get_llm('chat_extraction').with_structured_output(ExtractedInformation) result = cast(ExtractedInformation, with_cache_write_opt_out(structured).invoke(prompt)) except Exception as e: logger.error(f'e {e}') return {'people': [], 'topics': [], 'entities': [], 'dates': []} metadata = ConversationMetadata( people=[normalize_filter(p) for p in result.people], topics=[normalize_filter(t) for t in result.topics], entities=[normalize_filter(e) for e in result.entities], dates=normalize_extracted_dates(result.dates, today), ).to_vector_metadata() return metadata def retrieve_metadata_fields_from_structured( uid: str, created_at: datetime, structured: Any, tz: str, ) -> dict[str, Any]: """Extract vector filters from the canonical note, not a second raw-transcript pass.""" parts = [ str(getattr(structured, 'title', '') or '').strip(), str(getattr(structured, 'overview', '') or '').strip(), ] content = '\n\n'.join(part for part in parts if part) if not content: return {'people': [], 'topics': [], 'entities': [], 'dates': []} return retrieve_metadata_fields_from_transcript(uid, created_at, [{'text': content}], tz) def retrieve_metadata_from_message( uid: str, created_at: datetime, message_text: str, tz: str, source_spec: Optional[str] = None ) -> dict[str, Any]: """Extract metadata from messaging app content""" source_context = f"from {source_spec}" if source_spec else "from a messaging application" today = date_in_tz(created_at, tz) prompt = f''' You will be given the content of a message or conversation {source_context}. Your task is to extract the most accurate information from the message in the output object indicated below. {_MAX_WORDS_HINT} to have more standardization possibilities. Focus on identifying: 1. People mentioned in the message (sender, recipients, and anyone referenced) 2. Topics discussed in the message 3. Organizations, products, locations, or other entities mentioned 4. Any dates or time references For context when extracting dates, today is {today} in {tz} (user's local timezone). {tz} is the user's timezone, respond in user local timezone. If the message mentions "today", it means the current day. If the message mentions "tomorrow", it means the next day after today. If the message mentions "yesterday", it means the day before today. If the message mentions "next week", it means the next monday. Do not include dates more than {MAX_EXTRACTED_DATE_LOOKAHEAD_DAYS} days after {today}. Message Content: ``` {message_text} ``` '''.replace(' ', '') return _process_extracted_metadata(uid, prompt, today) def retrieve_metadata_from_text( uid: str, created_at: datetime, text: str, tz: str, source_spec: Optional[str] = None ) -> dict[str, Any]: """Extract metadata from generic text content""" source_context = f"from {source_spec}" if source_spec else "from a text document" today = date_in_tz(created_at, tz) prompt = f''' You will be given the content of a text {source_context}. Your task is to extract the most accurate information from the text in the output object indicated below. {_MAX_WORDS_HINT} to have more standardization possibilities. Focus on identifying: 1. People mentioned in the text (author, recipients, and anyone referenced) 2. Topics discussed in the text 3. Organizations, products, locations, or other entities mentioned 4. Any dates or time references For context when extracting dates, today is {today} in {tz} (user's local timezone). {tz} is the user's timezone, respond in user local timezone. If the text mentions "today", it means the current day. If the text mentions "tomorrow", it means the next day after today. If the text mentions "yesterday", it means the day before today. If the text mentions "next week", it means the next monday. Do not include dates more than {MAX_EXTRACTED_DATE_LOOKAHEAD_DAYS} days after {today}. Text Content: ``` {text} ``` '''.replace(' ', '') return _process_extracted_metadata(uid, prompt, today) def _process_extracted_metadata(uid: str, prompt: str, reference_date: str) -> dict[str, Any]: """Process the extracted metadata from any source""" try: with track_usage(uid, Features.CONVERSATION_PROCESSING): structured = get_llm('chat_extraction').with_structured_output(ExtractedInformation) result = cast(ExtractedInformation, with_cache_write_opt_out(structured).invoke(prompt)) except Exception as e: logger.error(f'Error extracting metadata: {e}') return {'people': [], 'topics': [], 'entities': [], 'dates': []} metadata = ConversationMetadata( people=[normalize_filter(p) for p in result.people], topics=[normalize_filter(t) for t in result.topics], entities=[normalize_filter(e) for e in result.entities], dates=normalize_extracted_dates(result.dates, reference_date), ).to_vector_metadata() return metadata # ************************************************** # ************* REALTIME V2 LANGGRAPH ************** # ************************************************** def extract_question_from_transcript(uid: str, segments: List[TranscriptSegment]) -> str: user_name, memories_str = get_prompt_memories(uid) person_ids = list(set(segment.person_id for segment in segments if segment.person_id)) people = [] if person_ids: people_data = users_db.get_people_by_ids(uid, list(set(person_ids))) people = [Person(**p) for p in people_data] prompt = f''' {user_name} is having a conversation. This is what you know about {user_name}: {memories_str} You will be the transcript of a recent conversation between {user_name} and a few people, \ your task is to understand the last few exchanges, and identify in order to provide advice to {user_name}, what other things about {user_name} \ you should know. For example, if the conversation is about a new job, you should output a question like "What discussions have I had about job search?". For example, if the conversation is about a new programming languages, you should output a question like "What have I chatted about programming?". Make sure as a first step, you infer and fix the raw transcript errors and then proceed to figure out the most meaningful question to ask. You must output at WH-question, that is, a question that starts with a WH-word, like "What", "When", "Where", "Who", "Why", "How". Conversation: ``` {TranscriptSegment.segments_as_string(segments, people=people, user_name=user_name)} ``` '''.replace(' ', '').strip() with track_usage(uid, Features.REALTIME_INTEGRATIONS): return cast( OutputQuestion, get_llm('chat_extraction').with_structured_output(OutputQuestion).invoke(prompt) ).question class OutputMessage(BaseModel): message: str = Field(description='The message to be sent to the user.', max_length=200) def provide_advice_message(uid: str, segments: List[TranscriptSegment], context: str) -> str: user_name, memories_str = get_prompt_memories(uid) person_ids = [s.person_id for s in segments if s.person_id] people = [] if person_ids: people_data = users_db.get_people_by_ids(uid, list(set(person_ids))) people = [Person(**p) for p in people_data] transcript = TranscriptSegment.segments_as_string(segments, people=people, user_name=user_name) # TODO: tweak with different type of requests, like this, or roast, or praise or emotional, etc. prompt = f""" You are a brutally honest, very creative, sometimes funny, indefatigable personal life coach who helps people improve their own agency in life, \ pulling in pop culture references and inspirational business and life figures from recent history, mixed in with references to recent personal memories, to help drive the point across. {memories_str} {user_name} just had a conversation and is asking for advice on what to do next. In order to answer you must analyize: - The conversation transcript. - The related conversations from previous days. - The facts you know about {user_name}. You start all your sentences with: - "If I were you, I would do this..." - "I think you should do x..." - "I believe you need to do y..." Your sentences are short, to the point, and very direct, at most 20 words. MUST OUTPUT 20 words or less. Conversation Transcript: {transcript} Context: ``` {context} ``` """.replace(' ', '').strip() with track_usage(uid, Features.REALTIME_INTEGRATIONS): return cast( OutputMessage, get_llm('chat_extraction').with_structured_output(OutputMessage).invoke(prompt) ).message