msz-archive commited on
Commit
28295d6
·
verified ·
1 Parent(s): 29e9a9b

Upload folder using huggingface_hub

Browse files
.github/workflows/update_space.yml ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Run Python script
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+
8
+ jobs:
9
+ build:
10
+ runs-on: ubuntu-latest
11
+
12
+ steps:
13
+ - name: Checkout
14
+ uses: actions/checkout@v2
15
+
16
+ - name: Set up Python
17
+ uses: actions/setup-python@v2
18
+ with:
19
+ python-version: '3.9'
20
+
21
+ - name: Install Gradio
22
+ run: python -m pip install gradio
23
+
24
+ - name: Log in to Hugging Face
25
+ run: python -c 'import huggingface_hub; huggingface_hub.login(token="${{ secrets.hf_token }}")'
26
+
27
+ - name: Deploy to Spaces
28
+ run: gradio deploy
README.md CHANGED
@@ -1,12 +1,6 @@
1
  ---
2
- title: Src
3
- emoji: 🏢
4
- colorFrom: red
5
- colorTo: pink
6
  sdk: gradio
7
  sdk_version: 5.20.1
8
- app_file: app.py
9
- pinned: false
10
  ---
11
-
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: src
3
+ app_file: gradio_app.py
 
 
4
  sdk: gradio
5
  sdk_version: 5.20.1
 
 
6
  ---
 
 
__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ # This file can be empty - it just marks the directory as a Python package
__pycache__/__init__.cpython-312.pyc ADDED
Binary file (153 Bytes). View file
 
__pycache__/__init__.cpython-313.pyc ADDED
Binary file (153 Bytes). View file
 
__pycache__/langgraph_workflow.cpython-313.pyc ADDED
Binary file (7.82 kB). View file
 
__pycache__/main.cpython-312.pyc ADDED
Binary file (9.96 kB). View file
 
__pycache__/main.cpython-313.pyc ADDED
Binary file (11.6 kB). View file
 
__pycache__/tools.cpython-312.pyc ADDED
Binary file (13.8 kB). View file
 
__pycache__/tools.cpython-313.pyc ADDED
Binary file (14 kB). View file
 
app.py ADDED
@@ -0,0 +1,177 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import streamlit as st
3
+ import requests
4
+ import uuid
5
+ import time
6
+ from datetime import datetime, timedelta
7
+
8
+ # Get the API URL from environment variable or use default
9
+ API_URL = os.getenv('API_URL', 'http://localhost:8000')
10
+
11
+ # Set session timeout to 15 minutes
12
+ SESSION_TIMEOUT = 15 * 60 # 15 minutes in seconds
13
+ # Set API request timeout to 60 seconds for local development
14
+ API_TIMEOUT = 60 # seconds
15
+
16
+ st.title("Longevity Assistant")
17
+
18
+ # Initialize session state
19
+ if "messages" not in st.session_state:
20
+ st.session_state.messages = []
21
+ st.session_state.session_id = str(uuid.uuid4())
22
+ st.session_state.last_activity = datetime.now()
23
+
24
+ # Get welcome message from API
25
+ try:
26
+ welcome_response = requests.get(f"{API_URL}/welcome", timeout=API_TIMEOUT)
27
+ if welcome_response.status_code == 200:
28
+ welcome_data = welcome_response.json()
29
+
30
+ # Add assistant welcome message to chat history
31
+ st.session_state.messages.append({
32
+ "role": "assistant",
33
+ "content": welcome_data["welcome_message"],
34
+ "links": []
35
+ })
36
+
37
+ # Store suggested questions
38
+ st.session_state.suggested_questions = welcome_data.get("suggested_questions", [])
39
+ else:
40
+ # Fallback welcome message if API fails
41
+ st.session_state.messages.append({
42
+ "role": "assistant",
43
+ "content": "Welcome to the Longevity Assistant! How can I help you today?",
44
+ "links": []
45
+ })
46
+ st.session_state.suggested_questions = []
47
+ except Exception as e:
48
+ st.error(f"Error connecting to the server: {str(e)}")
49
+ # Fallback welcome message if API fails
50
+ st.session_state.messages.append({
51
+ "role": "assistant",
52
+ "content": "Welcome to the Longevity Assistant! How can I help you today?",
53
+ "links": []
54
+ })
55
+ st.session_state.suggested_questions = []
56
+
57
+ elif "last_activity" in st.session_state:
58
+ # Check if session has expired
59
+ time_inactive = (datetime.now() - st.session_state.last_activity).total_seconds()
60
+ if time_inactive > SESSION_TIMEOUT:
61
+ # Reset session
62
+ st.session_state.messages = []
63
+ st.session_state.session_id = str(uuid.uuid4())
64
+
65
+ # Update last activity time
66
+ st.session_state.last_activity = datetime.now()
67
+
68
+ # Display chat messages
69
+ for message in st.session_state.messages:
70
+ with st.chat_message(message["role"]):
71
+ st.write(message["content"])
72
+ #if "links" in message and message["links"]:
73
+ #for link in message["links"]:
74
+ #st.markdown(f"[{link['name']}]({link['url']})")
75
+
76
+ # Display suggested questions as buttons (only if no messages from user yet)
77
+ if len(st.session_state.messages) == 1 and hasattr(st.session_state, 'suggested_questions'):
78
+ st.write("Try asking about:")
79
+ cols = st.columns(2)
80
+ for i, question in enumerate(st.session_state.suggested_questions):
81
+ with cols[i % 2]:
82
+ if st.button(question, key=f"suggested_{i}"):
83
+ # Add user message to chat history
84
+ st.session_state.messages.append({"role": "user", "content": question})
85
+
86
+ # Display user message immediately
87
+ with st.chat_message("user"):
88
+ st.write(question)
89
+
90
+ # Process the question (reusing the chat input logic)
91
+ with st.chat_message("assistant"):
92
+ with st.spinner("Thinking..."):
93
+ try:
94
+ response = requests.post(
95
+ f"{API_URL}/chat",
96
+ json={"session_id": st.session_state.session_id, "message": question},
97
+ timeout=API_TIMEOUT
98
+ )
99
+
100
+ if response.status_code == 200:
101
+ response_data = response.json()
102
+ # Store response for history
103
+ st.session_state.messages.append({
104
+ "role": "assistant",
105
+ "content": response_data["response"],
106
+ "links": response_data["links"]
107
+ })
108
+ # Display the response
109
+ st.write(response_data["response"])
110
+ #if response_data["links"]:
111
+ # for link in response_data["links"]:
112
+ # st.markdown(f"[{link['name']}]({link['url']})")
113
+ else:
114
+ st.error(f"Error: {response.status_code}")
115
+ st.session_state.messages.append({
116
+ "role": "assistant",
117
+ "content": "Sorry, I encountered an error while processing your request.",
118
+ "links": []
119
+ })
120
+ except Exception as e:
121
+ st.error(f"Error connecting to the server: {str(e)}")
122
+ st.session_state.messages.append({
123
+ "role": "assistant",
124
+ "content": "Sorry, I'm having trouble connecting to the server.",
125
+ "links": []
126
+ })
127
+
128
+ # Force a rerun to update the UI
129
+ st.rerun()
130
+
131
+ # Chat input
132
+ if prompt := st.chat_input():
133
+ # Add user message to chat history
134
+ st.session_state.messages.append({"role": "user", "content": prompt})
135
+
136
+ # Display user message immediately
137
+ with st.chat_message("user"):
138
+ st.write(prompt)
139
+
140
+ # Display assistant "thinking" message with spinner
141
+ with st.chat_message("assistant"):
142
+ with st.spinner("Thinking..."):
143
+ # Get bot response
144
+ try:
145
+ response = requests.post(
146
+ f"{API_URL}/chat",
147
+ json={"session_id": st.session_state.session_id, "message": prompt},
148
+ timeout=API_TIMEOUT
149
+ )
150
+
151
+ if response.status_code == 200:
152
+ response_data = response.json()
153
+ # Store response for history
154
+ st.session_state.messages.append({
155
+ "role": "assistant",
156
+ "content": response_data["response"],
157
+ "links": response_data["links"]
158
+ })
159
+ # Display the response
160
+ st.write(response_data["response"])
161
+ #if response_data["links"]:
162
+ # for link in response_data["links"]:
163
+ # st.markdown(f"[{link['name']}]({link['url']})")
164
+ else:
165
+ st.error(f"Error: {response.status_code}")
166
+ st.session_state.messages.append({
167
+ "role": "assistant",
168
+ "content": "Sorry, I encountered an error while processing your request.",
169
+ "links": []
170
+ })
171
+ except Exception as e:
172
+ st.error(f"Error connecting to the server: {str(e)}")
173
+ st.session_state.messages.append({
174
+ "role": "assistant",
175
+ "content": "Sorry, I'm having trouble connecting to the server.",
176
+ "links": []
177
+ })
classes/__pycache__/labReportAnalyzer.cpython-313.pyc ADDED
Binary file (4.5 kB). View file
 
config.py ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Global configuration settings
2
+
3
+ CONSULTATION_SETTINGS = {
4
+ "np_consultation_fee_usd": 50.00, # Base consultation fee as shown in the plan example
5
+ "consultation_description": "Medical consultation with our Nurse Practitioner",
6
+ "consultation_link": "https://longevityclinic.com/consultation",
7
+ "consultation_disclaimers": [
8
+ "Required for all prescription medications",
9
+ "One-time fee covers review of all requested medications",
10
+ "Refundable if no prescriptions are approved"
11
+ ]
12
+ }
gradio_app.py ADDED
@@ -0,0 +1,146 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import requests
3
+ import uuid
4
+ from datetime import datetime
5
+ import os
6
+
7
+ # Get the API URL from environment variable or use default
8
+ API_URL = os.getenv('API_URL', 'http://localhost:8000')
9
+ API_TIMEOUT = 60 # seconds
10
+
11
+ def create_demo():
12
+ # Initialize state
13
+ session_id = str(uuid.uuid4())
14
+ messages = []
15
+ suggested_questions = []
16
+
17
+ def get_welcome_message():
18
+ try:
19
+ welcome_response = requests.get(f"{API_URL}/welcome", timeout=API_TIMEOUT)
20
+ if welcome_response.status_code == 200:
21
+ welcome_data = welcome_response.json()
22
+ messages = [{"role": "assistant", "content": welcome_data["welcome_message"]}]
23
+ suggested_questions = welcome_data.get("suggested_questions", [])
24
+ return messages, gr.update(visible=True), suggested_questions
25
+ else:
26
+ fallback_message = "Welcome to the Longevity Assistant! How can I help you today?"
27
+ return [{"role": "assistant", "content": fallback_message}], gr.update(visible=False), []
28
+ except Exception as e:
29
+ print(f"Error connecting to server: {str(e)}")
30
+ fallback_message = "Welcome to the Longevity Assistant! How can I help you today?"
31
+ return [{"role": "assistant", "content": fallback_message}], gr.update(visible=False), []
32
+
33
+ def handle_chat(message, files, history):
34
+ # Add user message to history
35
+ history.append({"role": "user", "content": message})
36
+
37
+ # If files were uploaded, add them to the message with distinct formatting
38
+ if files:
39
+ file_names = [f.name for f in files]
40
+ file_message = f"\n\n📎 *Files attached:* {', '.join(file_names)} 📎"
41
+ history[-1]["content"] += file_message
42
+
43
+ try:
44
+ response = requests.post(
45
+ f"{API_URL}/chat",
46
+ json={"session_id": session_id, "message": message},
47
+ timeout=API_TIMEOUT
48
+ )
49
+
50
+ if response.status_code == 200:
51
+ response_data = response.json()
52
+ history.append({"role": "assistant", "content": response_data["response"]})
53
+ else:
54
+ error_message = "Sorry, I encountered an error while processing your request."
55
+ history.append({"role": "assistant", "content": error_message})
56
+
57
+ except Exception as e:
58
+ error_message = "Sorry, I'm having trouble connecting to the server."
59
+ history.append({"role": "assistant", "content": error_message})
60
+
61
+ return "", None, history
62
+
63
+ def handle_suggested_question(question, history):
64
+ return handle_chat(question, None, history)
65
+
66
+ def update_question_buttons(questions):
67
+ updates = []
68
+ for i, button in enumerate(question_buttons):
69
+ if i < len(questions):
70
+ updates.append(gr.update(visible=True, value=questions[i]))
71
+ else:
72
+ updates.append(gr.update(visible=False))
73
+ return updates
74
+
75
+ with gr.Blocks(css="#chatbot { height: 500px; } .upload-box { height: 40px; min-height: 40px; }") as demo:
76
+ gr.Markdown("# 🧬 Longevity Assistant")
77
+
78
+ chatbot = gr.Chatbot(
79
+ [],
80
+ elem_id="chatbot",
81
+ height=500,
82
+ type="messages"
83
+ )
84
+
85
+ with gr.Row():
86
+ txt = gr.Textbox(
87
+ scale=4,
88
+ show_label=False,
89
+ placeholder="Enter text and press enter",
90
+ container=False
91
+ )
92
+ file_output = gr.File(
93
+ file_count="multiple",
94
+ label="",
95
+ scale=1,
96
+ container=False,
97
+ elem_classes="upload-box"
98
+ )
99
+ btn = gr.Button("Send", scale=1)
100
+
101
+ question_list = gr.State([])
102
+ suggested_questions_container = gr.Column(visible=False)
103
+
104
+ with suggested_questions_container:
105
+ gr.Markdown("Try asking about:")
106
+ question_buttons = []
107
+
108
+ # Create 4 rows with 2 buttons each (maximum 8 suggested questions)
109
+ for i in range(4):
110
+ with gr.Row():
111
+ for j in range(2):
112
+ question_btn = gr.Button("", visible=False)
113
+ question_btn.click(
114
+ handle_suggested_question,
115
+ [question_btn, chatbot],
116
+ [txt, chatbot]
117
+ ).then(
118
+ lambda: gr.update(visible=False),
119
+ None,
120
+ suggested_questions_container
121
+ )
122
+ question_buttons.append(question_btn)
123
+
124
+ # Update event handlers
125
+ txt.submit(handle_chat, [txt, file_output, chatbot], [txt, file_output, chatbot])
126
+ btn.click(handle_chat, [txt, file_output, chatbot], [txt, file_output, chatbot])
127
+
128
+ # Initialize welcome message and suggested questions
129
+ demo.load(
130
+ get_welcome_message,
131
+ outputs=[chatbot, suggested_questions_container, question_list]
132
+ ).then(
133
+ update_question_buttons,
134
+ inputs=[question_list],
135
+ outputs=question_buttons
136
+ )
137
+
138
+ return demo
139
+
140
+ if __name__ == "__main__":
141
+ demo = create_demo()
142
+ demo.launch(
143
+ server_name="0.0.0.0",
144
+ server_port=7860,
145
+ share=True
146
+ )
main.py ADDED
@@ -0,0 +1,255 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, HTTPException
2
+ from pydantic import BaseModel
3
+ from typing import List, Dict, Optional
4
+ import os
5
+ from langchain_openai import ChatOpenAI
6
+ from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
7
+ from langchain.schema import HumanMessage, SystemMessage
8
+ from langchain.agents import AgentExecutor, create_openai_tools_agent
9
+ from langchain.memory import ConversationBufferMemory
10
+ from dotenv import load_dotenv
11
+ import openai
12
+ import stripe # Add stripe import
13
+
14
+ # Project imports
15
+ from src.tools import get_tools, get_supplements, get_prescription_drugs
16
+ from src.payments.agent import process_purchase_request
17
+
18
+ # Force reload environment variables from .env file
19
+ load_dotenv(override=True)
20
+
21
+ # Set OpenAI API key globally for all OpenAI services
22
+ api_key = os.getenv("OPENAI_API_KEY")
23
+ if not api_key:
24
+ raise ValueError("OPENAI_API_KEY environment variable is not set")
25
+ elif not api_key.startswith("sk-"):
26
+ raise ValueError(f"Invalid API key format: {api_key[:10]}...")
27
+
28
+ # Set Stripe API key
29
+ stripe_key = os.getenv("STRIPE_API_KEY")
30
+ if not stripe_key:
31
+ raise ValueError("STRIPE_API_KEY environment variable is not set")
32
+ stripe.api_key = stripe_key
33
+
34
+ # Set for the OpenAI library directly
35
+ openai.api_key = api_key
36
+ os.environ["OPENAI_API_KEY"] = api_key # Ensure it's in environment variables
37
+
38
+ # Print first few characters of API key for debugging (remove in production)
39
+ print(f"Using API key starting with: {api_key[:10]}...")
40
+
41
+ app = FastAPI()
42
+
43
+ # Get data from tools module
44
+ SUPPLEMENTS = get_supplements()
45
+ PRESCRIPTION_DRUGS = get_prescription_drugs()
46
+
47
+ class ChatMessage(BaseModel):
48
+ session_id: str
49
+ message: str
50
+
51
+ class ChatResponse(BaseModel):
52
+ response: str
53
+ links: List[Dict[str, str]] = []
54
+
55
+ # Update the in-memory storage structure
56
+ class ChatSession:
57
+ def __init__(self, agent_executor: AgentExecutor, memory: ConversationBufferMemory, summary: str = ""):
58
+ self.agent_executor = agent_executor
59
+ self.memory = memory
60
+ self.summary = summary
61
+
62
+ # Update the storage dictionary
63
+ chat_sessions = {} # Replace chat_histories and chat_summaries
64
+
65
+ # Get all tools from the tools module
66
+ tools = get_tools()
67
+
68
+ @app.post("/chat", response_model=ChatResponse)
69
+ async def handle_chat(chat_message: ChatMessage):
70
+ session_id = chat_message.session_id
71
+
72
+ # Initialize or retrieve chat session
73
+ if session_id not in chat_sessions:
74
+ # Create system prompt with empty summary
75
+ system_message = """You are an expert longevity assistant specializing in evidence-based health optimization, supplementation, and prescription medications.
76
+
77
+ ## YOUR ROLE:
78
+ - Provide concise, scientifically-grounded advice on supplements, prescription medications, and longevity interventions
79
+ - Focus on personalization based on the user's specific health concerns and goals
80
+ - Maintain a friendly, supportive tone while being direct and efficient
81
+
82
+ ## CONVERSATION FLOW:
83
+ 1. Begin by asking clarifying questions about the user's specific health concerns
84
+ 2. Once you understand their needs, recommend a focused set of solutions
85
+ 3. For supplements or medications, use the get_product_details tool to provide information
86
+ 4. If the user expresses interest in prescription products, recommend pre-paying for the product with a consultation
87
+ 5. End with a brief encouraging remark related to their health goals
88
+
89
+ ## PRESCRIPTION RECOMMENDATIONS:
90
+ - When a user presents with a health concern, first ask clarifying questions
91
+ - Based on their responses, recommend ONE or at most TWO prescription medications that would be most effective
92
+ - Always clearly state that prescriptions require a medical consultation
93
+ - Emphasize that prescription medications require a consultation with our nurse practitioner (additional fee applies)
94
+ - Include this disclaimer with ANY prescription recommendation: "This medication requires a prescription from our nurse practitioner. Please schedule a consultation (additional fee applies)."
95
+
96
+ ## COMPLEMENTARY APPROACHES:
97
+ - When recommending a prescription medication, briefly mention 2-3 complementary supplements that may support the same health goal
98
+ - Present these as educational information only, not as additional purchase suggestions
99
+ - Use phrases like "Some people also find benefit from..." or "For a holistic approach, you might research..."
100
+ - Do not include purchase links for these complementary supplements unless specifically requested
101
+
102
+ ## PAYMENT HANDLING:
103
+ - When a user expresses interest in purchasing prescription products, ask if they would like to pre-pay with a consultation
104
+ - Generate ONE payment link for the entire purchase using the create_payment_link tool
105
+ - Use the create_payment_link tool with a clear description of products and quantities
106
+ (e.g., "I want to buy 1 tretinoin and 1 consultation")
107
+ - After generating the payment link, ask for the user's email and phone number
108
+ - Explain that payments will be refunded if the Nurse Practitioner determines the user doesn't meet the criteria
109
+ - If a user requests additional products, ALWAYS check availability using get_available_products or check_product_availability before generating a payment link
110
+ - If a user only wants to purchase a subset of recommended products, generate a payment link only for the products they want to purchase
111
+
112
+ ## PRODUCT RESTRICTIONS:
113
+ - ONLY recommend products available in our catalog
114
+ - Use the get_available_products tool to check which products are available and use the exact names of the products in the catalog
115
+ - DO NOT suggest products not in our catalog
116
+ - Remember that NP_consultation is a valid product in our catalog
117
+ - When a user asks to buy additional products, ALWAYS verify their availability before proceeding
118
+
119
+ ## TOOLS AVAILABLE:
120
+ - For supplements: search_supplements_by_condition, search_supplements_by_name, get_supplement_details, list_all_supplements
121
+ - For prescription drugs: search_prescription_drugs_by_condition, search_prescription_drugs_by_name, get_prescription_drug_details, list_all_prescription_drugs
122
+ - Combined searches: search_all_products_by_condition, search_all_products_by_name, get_product_details, list_all_products
123
+ - Catalog verification: get_available_products
124
+ - Payment: create_payment_link. This tool takes a string describing what products and quantities to purchase in a single transaction
125
+ (e.g., "I want to buy 1 tretinoin and 1 consultation", "I want to buy 1 finasteride, 1 minoxidil and 1 consultation")
126
+
127
+ ## GUIDELINES:
128
+ - Keep responses under 150 words unless detailed information is requested
129
+ - Use bullet points for clarity when listing multiple recommendations
130
+ - Cite evidence categories (strong, moderate, preliminary) for recommendations
131
+ - If a user asks for a product not in our catalog, politely explain we don't carry it and suggest available alternatives
132
+ - If a user declines prescription options, gracefully recommend supplements or lifestyle changes instead
133
+
134
+ ## TONE:
135
+ - Professional but conversational
136
+ - Evidence-based but accessible
137
+ - Supportive without overpromising
138
+
139
+ Remember to use the appropriate tool for each query type, and always prioritize user safety and scientific accuracy.
140
+ """
141
+
142
+ # Create the prompt template
143
+ prompt = ChatPromptTemplate.from_messages(
144
+ [
145
+ ("system", system_message),
146
+ MessagesPlaceholder(variable_name="chat_history", optional=True),
147
+ ("human", "{input}"),
148
+ MessagesPlaceholder(variable_name="agent_scratchpad"),
149
+ ]
150
+ )
151
+
152
+ # Initialize LLM and memory
153
+ llm = ChatOpenAI(temperature=0.2, model="gpt-4o-mini", api_key=api_key)
154
+ memory = ConversationBufferMemory(memory_key="chat_history", return_messages=True)
155
+
156
+ # Create the agent
157
+ agent = create_openai_tools_agent(
158
+ llm=llm,
159
+ tools=tools,
160
+ prompt=prompt
161
+ )
162
+
163
+ # Create the agent executor
164
+ agent_executor = AgentExecutor(
165
+ agent=agent,
166
+ tools=tools,
167
+ memory=memory,
168
+ verbose=True
169
+ )
170
+
171
+ # Store everything in the session
172
+ chat_sessions[session_id] = ChatSession(
173
+ agent_executor=agent_executor,
174
+ memory=memory
175
+ )
176
+
177
+ # Get the existing session
178
+ session = chat_sessions[session_id]
179
+
180
+ # Get response from agent
181
+ result = await session.agent_executor.ainvoke({"input": chat_message.message})
182
+ response_text = result["output"]
183
+
184
+ # Extract links from the response
185
+ links = []
186
+
187
+ # Check for supplements first
188
+ for supp_id, supp_data in SUPPLEMENTS.items():
189
+ if supp_id in response_text.lower() or supp_data["name"].lower() in response_text.lower():
190
+ links.append({
191
+ "name": supp_data["name"],
192
+ "url": supp_data["affiliate_link"]
193
+ })
194
+
195
+ # Check for prescription drugs
196
+ for drug_id, drug_data in PRESCRIPTION_DRUGS.items():
197
+ if drug_id in response_text.lower() or drug_data["name"].lower() in response_text.lower():
198
+ links.append({
199
+ "name": drug_data["name"] + " (Prescription Required)",
200
+ "url": drug_data["affiliate_link"]
201
+ })
202
+
203
+ # Update the summary using the existing LLM
204
+ summarizer_prompt = """Your role is to summarize the conversation, focusing on the user's medically relevant information,
205
+ concerns, and any specific supplement or medication needs or preferences they express. Ensure the summary is concise and captures
206
+ the essence of the user's health-related queries and the assistant's advice."""
207
+
208
+ # Get the last exchange
209
+ last_exchange = f"User: {chat_message.message}\nAssistant: {response_text}"
210
+
211
+ # Update the summary
212
+ summarizer = ChatOpenAI(temperature=0.2, model="gpt-4o-mini", api_key=api_key)
213
+ summary_update = summarizer.invoke([
214
+ SystemMessage(content=summarizer_prompt),
215
+ HumanMessage(content=last_exchange)
216
+ ]).content
217
+
218
+ # Update the session summary
219
+ session.summary = f"{session.summary} {summary_update}".strip()
220
+
221
+ return ChatResponse(
222
+ response=response_text,
223
+ links=links
224
+ )
225
+
226
+ @app.get("/welcome", response_model=dict)
227
+ async def get_welcome_message():
228
+ """
229
+ Returns a welcome message for new users.
230
+ """
231
+ return {
232
+ "welcome_message": """
233
+ Welcome to the Longevity Assistant! 👋
234
+
235
+ I'm here to help you optimize your health and longevity journey. I can:
236
+
237
+ • Answer questions about supplements and their benefits\n
238
+ • Recommend products based on your specific health goals\n
239
+ • Provide evidence-based information on longevity practices\n
240
+
241
+ What health or longevity goal would you like to focus on today?
242
+ """.strip(),
243
+ "suggested_questions": [
244
+
245
+ "I am having hair loss issues",
246
+ "I am having issues with my sleep",
247
+ "I am finding it difficult to lose weight",
248
+ "I am having memory issues",
249
+ #"What supplements can help with cognitive function?",
250
+ #"I'm looking for something to improve my sleep quality",
251
+ #"What should I take for joint pain?",
252
+ #"What's the best supplement for energy?",
253
+ #"What's the best supplement for skin health?"
254
+ ]
255
+ }
payments/__pycache__/agent.cpython-313.pyc ADDED
Binary file (4.68 kB). View file
 
payments/__pycache__/payment_tools.cpython-313.pyc ADDED
Binary file (3.78 kB). View file
 
payments/agent.py ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from dotenv import dotenv_values
3
+ from langchain_openai import ChatOpenAI
4
+ from langgraph.prebuilt import ToolNode
5
+ from langchain_core.tools import tool
6
+
7
+ import stripe
8
+ import json
9
+ # Load values directly from .env file without setting environment variables
10
+ env_path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), '.env')
11
+ env_vars = dotenv_values(env_path)
12
+
13
+
14
+ stripe.api_key = env_vars.get("STRIPE_API_KEY")
15
+ #product_name is the name of the product in the payments/products.json file
16
+ def generate_payment_link(product_quantities_str: str) -> str:
17
+ """Generate a Stripe payment link for specified products.
18
+
19
+ Args:
20
+ product_quantities_str: String with format "(product_1,k_1),(product_2,k_2),...,(product_n,k_n)" where product_i must match a key in products.json and k_i is the quantity. Example: "(finasteride,1),(NP_consultation,1)"
21
+ """
22
+ # Parse the input string
23
+ product_quantities = []
24
+ pairs = product_quantities_str.strip().split("),(")
25
+
26
+ for pair in pairs:
27
+ # Clean up the pair string
28
+ pair = pair.replace("(", "").replace(")", "")
29
+ if not pair:
30
+ continue
31
+
32
+ parts = pair.split(",")
33
+ if len(parts) != 2:
34
+ raise ValueError(f"Invalid format in pair: {pair}. Expected 'product,quantity'")
35
+
36
+ product_id, quantity_str = parts
37
+ try:
38
+ quantity = int(quantity_str)
39
+ except ValueError:
40
+ raise ValueError(f"Quantity must be an integer: {quantity_str}")
41
+
42
+ product_quantities.append((product_id, quantity))
43
+
44
+ # Load the product catalog - adjust path based on script location
45
+ import os
46
+ script_dir = os.path.dirname(os.path.abspath(__file__))
47
+ catalog_path = os.path.join(script_dir, "products.json")
48
+ catalog = json.load(open(catalog_path))
49
+
50
+ # Create line items for the payment link
51
+ # Use a dictionary to track and combine items with the same price_id
52
+ price_quantities = {}
53
+ for product_id, quantity in product_quantities:
54
+ if product_id not in catalog:
55
+ raise ValueError(f"Product '{product_id}' not found in catalog")
56
+
57
+ price_id = catalog[product_id]["price_id"]
58
+ if price_id in price_quantities:
59
+ price_quantities[price_id] += quantity
60
+ else:
61
+ price_quantities[price_id] = quantity
62
+
63
+ # Create line items from the consolidated price_quantities
64
+ line_items = [
65
+ {"price": price_id, "quantity": quantity}
66
+ for price_id, quantity in price_quantities.items()
67
+ ]
68
+
69
+ # Create the payment link
70
+ payment_link = stripe.PaymentLink.create(
71
+ line_items=line_items,
72
+ phone_number_collection={"enabled": True},
73
+ after_completion={
74
+ "type": "hosted_confirmation",
75
+ "hosted_confirmation": {
76
+ "custom_message": "Thank you for your order! 🎉.\n A nurse practitioner will review your order and contact you shortly."
77
+ }
78
+ }
79
+ )
80
+
81
+ return payment_link.url
82
+
83
+
84
+ def process_purchase_request(user_input):
85
+ """
86
+ Process a purchase request from a user input string.
87
+
88
+ Args:
89
+ user_input (str): A string containing purchase intent like
90
+ "I want to buy 3 items of product_i and 2 of product_k"
91
+
92
+ Returns:
93
+ dict: The response from the tool node containing payment information
94
+ """
95
+ # Use the API key directly from the loaded .env values
96
+ llm = ChatOpenAI(model='gpt-4o-mini', api_key=env_vars.get("OPENAI_API_KEY"), temperature=0)
97
+
98
+ # Tool creation
99
+ tools = [generate_payment_link]
100
+ tool_node = ToolNode(tools)
101
+
102
+ # Tool binding
103
+ model_with_tools = llm.bind_tools(tools)
104
+ # Get the LLM to understand the request and format it for tool calling
105
+ llm_response = model_with_tools.invoke(user_input)
106
+ print(llm_response)
107
+ # Pass the LLM's response to the tool node for execution
108
+ tool_response = tool_node.invoke({"messages": [llm_response]})
109
+ print(tool_response)
110
+ return tool_response
111
+
112
+ # Example usage
113
+ if __name__ == "__main__":
114
+ sample_request = '1 Finasteride consultation, 1 Minoxidil consultation'
115
+ result = process_purchase_request(sample_request)
116
+ print(result)
117
+
118
+ # You can test with other examples
119
+ # result2 = process_purchase_request("I want to buy 3 items of product_i and 2 of product_k")
120
+ # print(result2)
payments/create_price.py ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import stripe
2
+ stripe.api_key = "sk_test_51QzLWD2MnbfibIToo1GmNncuEXXHn0qS8JZ1FMQNB1CWbUuTUt1XP3Q4Plv3imagCoOMtQVmWgc8vHPXGemAdZa900k20pOvcM"
3
+
4
+ item = stripe.Product.create(
5
+ name="newTestItem",
6
+ description="$12/Month subscription",
7
+ default_price_data={
8
+ "unit_amount": 1000,
9
+ "currency": "usd",
10
+ },
11
+ expand=["default_price"]
12
+ )
13
+
14
+
15
+
16
+ # Save these identifiers
17
+ print(f"Success! Here is your starter subscription product id: {item.id}")
18
+ print(f"Success! Here is your starter subscription price id: {item.default_price.id}")
19
+ #print(f"Success! Here is your starter subscription price id: {item_price.id}")
payments/payment_tools.py ADDED
@@ -0,0 +1,135 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ import stripe
4
+ from typing import List, Tuple
5
+
6
+ def parse_product_quantities(product_quantities_str: str) -> List[Tuple[str, int]]:
7
+ """Parse a product quantities string into a list of (product_id, quantity) tuples.
8
+
9
+ Args:
10
+ product_quantities_str: String with format "(product_1,k_1),(product_2,k_2),...,(product_n,k_n)"
11
+ where product_i must match a key in products.json and k_i is the quantity.
12
+ Example: "(finasteride,1),(NP_consultation,1)"
13
+
14
+ Returns:
15
+ List of tuples containing (product_id, quantity)
16
+
17
+ Raises:
18
+ ValueError: If the input string format is invalid
19
+ """
20
+ product_quantities = []
21
+ pairs = product_quantities_str.strip().split("),(")
22
+
23
+ for pair in pairs:
24
+ # Clean up the pair string
25
+ pair = pair.replace("(", "").replace(")", "")
26
+ if not pair:
27
+ continue
28
+
29
+ parts = pair.split(",")
30
+ if len(parts) != 2:
31
+ raise ValueError(f"Invalid format in pair: {pair}. Expected 'product,quantity'")
32
+
33
+ product_id, quantity_str = parts
34
+ try:
35
+ quantity = int(quantity_str)
36
+ except ValueError:
37
+ raise ValueError(f"Quantity must be an integer: {quantity_str}")
38
+
39
+ product_quantities.append((product_id, quantity))
40
+
41
+ return product_quantities
42
+
43
+ def generate_payment_link(product_quantities_str: str) -> str:
44
+ """Generate a Stripe payment link for specified products.
45
+
46
+ Args:
47
+ product_quantities_str: String describing products and quantities
48
+ Format: "(product_1,k_1),(product_2,k_2),...,(product_n,k_n)"
49
+ Example: "(finasteride,1),(NP_consultation,1)"
50
+
51
+ Returns:
52
+ Stripe payment link URL
53
+
54
+ Raises:
55
+ ValueError: If products are not found or quantities are invalid
56
+ stripe.error.StripeError: If there's an error with the Stripe API
57
+ """
58
+ try:
59
+ # Parse the product quantities
60
+ product_quantities = parse_product_quantities(product_quantities_str)
61
+
62
+ # Load the product catalog
63
+ script_dir = os.path.dirname(os.path.abspath(__file__))
64
+ catalog_path = os.path.join(script_dir, "products.json")
65
+
66
+ try:
67
+ with open(catalog_path, 'r') as f:
68
+ catalog = json.load(f)
69
+ except (FileNotFoundError, json.JSONDecodeError) as e:
70
+ raise ValueError(f"Error loading product catalog: {str(e)}")
71
+
72
+ # Create line items for the payment link
73
+ price_quantities = {}
74
+ for product_id, quantity in product_quantities:
75
+ if product_id not in catalog:
76
+ raise ValueError(f"Product '{product_id}' not found in catalog")
77
+
78
+ price_id = catalog[product_id]["price_id"]
79
+ price_quantities[price_id] = price_quantities.get(price_id, 0) + quantity
80
+
81
+ # Create line items from the consolidated price_quantities
82
+ line_items = [
83
+ {"price": price_id, "quantity": quantity}
84
+ for price_id, quantity in price_quantities.items()
85
+ ]
86
+
87
+ # Create the payment link
88
+ payment_link = stripe.PaymentLink.create(
89
+ line_items=line_items,
90
+ phone_number_collection={"enabled": True},
91
+ after_completion={
92
+ "type": "hosted_confirmation",
93
+ "hosted_confirmation": {
94
+ "custom_message": "Thank you for your order! 🎉\nA nurse practitioner will review your order and contact you shortly."
95
+ }
96
+ }
97
+ )
98
+
99
+ return payment_link.url
100
+
101
+ except stripe.error.StripeError as e:
102
+ raise ValueError(f"Stripe API error: {str(e)}")
103
+
104
+ def create_non_recurring_item_with_price(product_name: str, price: int, description: str = "Default description") -> stripe.Product:
105
+ """Create a new Stripe product with a non-recurring price.
106
+
107
+ Args:
108
+ product_name: Name of the product
109
+ price: Price in cents
110
+ description: Product description
111
+
112
+ Returns:
113
+ Stripe Product object
114
+
115
+ Raises:
116
+ stripe.error.StripeError: If there's an error with the Stripe API
117
+ """
118
+ try:
119
+ item = stripe.Product.create(
120
+ name=product_name,
121
+ description=description,
122
+ default_price_data={
123
+ "unit_amount": price,
124
+ "currency": "usd",
125
+ },
126
+ expand=["default_price"]
127
+ )
128
+
129
+ print(f"Created product with id: {item.id}")
130
+ print(f"Created price with id: {item.default_price.id}")
131
+
132
+ return item
133
+
134
+ except stripe.error.StripeError as e:
135
+ raise ValueError(f"Error creating Stripe product: {str(e)}")
payments/products.json ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "NP_consultation": {
3
+ "product_id": "prod_Rt9PWRucM6Wadt",
4
+ "price_id": "price_1QzN752MnbfibITojfGIs66e"
5
+ },
6
+ "finasteride": {
7
+ "product_id": "prod_Rt9Qwj9pJN7j4c",
8
+ "price_id": "price_1QzN842MnbfibIToWvjmIgYg"
9
+ },
10
+ "tretinoin": {
11
+ "product_id": "prod_RvI4MdmYKy0bL3",
12
+ "price_id": "price_1R1RU52MnbfibIToeUUQXMIZ"
13
+ },
14
+ "metformin": {
15
+ "product_id": "prod_RvI43hy7caoRpD",
16
+ "price_id": "price_1R1RU52MnbfibIToRjvABsFs"
17
+ },
18
+ "semaglutide": {
19
+ "product_id": "prod_RvI4MY1TSllOQe",
20
+ "price_id": "price_1R1RU52MnbfibIToq1zatJNe"
21
+ },
22
+ "minoxidil": {
23
+ "product_id": "prod_RvI4ZwJmyr6ORy",
24
+ "price_id": "price_1R1RTy2MnbfibITov5hHr6bb"
25
+ },
26
+ "berberine": {
27
+ "product_id": "prod_RvI48b6paTkrvD",
28
+ "price_id": "price_1R1RTy2MnbfibIToriPHZWQJ"
29
+ },
30
+ "uber minoxidil": {
31
+ "product_id": "prod_RvI4jg0L500y6V",
32
+ "price_id": "price_1R1RTz2MnbfibIToEO7cIrXS"
33
+ },
34
+ "biotin": {
35
+ "product_id": "prod_RvI4dlIslPSgVA",
36
+ "price_id": "price_1R1RTz2MnbfibIToerYafEud"
37
+ },
38
+ "protein": {
39
+ "product_id": "prod_RvI4aIcp3Tt4sB",
40
+ "price_id": "price_1R1RU02MnbfibIToH4TGN4wd"
41
+ },
42
+ "creatine": {
43
+ "product_id": "prod_RvI4jJV2ga5kCU",
44
+ "price_id": "price_1R1RU02MnbfibITomNTvzECN"
45
+ },
46
+ "vitamin_c": {
47
+ "product_id": "prod_RvI4L8EV1gDFxl",
48
+ "price_id": "price_1R1RU12MnbfibITozq17jxTw"
49
+ },
50
+ "omega_3": {
51
+ "product_id": "prod_RvI4tUkYisZgIi",
52
+ "price_id": "price_1R1RU12MnbfibITo0YJFeuJT"
53
+ },
54
+ "vitamin_d": {
55
+ "product_id": "prod_RvI4qDpZsYAgGf",
56
+ "price_id": "price_1R1RU12MnbfibITofaWnibCd"
57
+ },
58
+ "magnesium": {
59
+ "product_id": "prod_RvI4fWkWSE04e4",
60
+ "price_id": "price_1R1RU22MnbfibITogOteYaYi"
61
+ },
62
+ "zinc": {
63
+ "product_id": "prod_RvI4AotK9kH2v9",
64
+ "price_id": "price_1R1RU32MnbfibITozho1pSua"
65
+ },
66
+ "ashwagandha": {
67
+ "product_id": "prod_RvI4zrDFVPKp4I",
68
+ "price_id": "price_1R1RU32MnbfibITo8ICxb94X"
69
+ },
70
+ "lions_mane": {
71
+ "product_id": "prod_RvI4KkOiQIxaey",
72
+ "price_id": "price_1R1RU32MnbfibIToPKuTKQXv"
73
+ },
74
+ "nac": {
75
+ "product_id": "prod_RvI4YVD1c9l136",
76
+ "price_id": "price_1R1RU42MnbfibITo4eApTmet"
77
+ },
78
+ "minoxidil_prescription": {
79
+ "product_id": "prod_RvI4Xp1CM2qCSs",
80
+ "price_id": "price_1R1RU42MnbfibIToU0k8SWlF"
81
+ }
82
+ }
payments/test_payment_link.py ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import payment_tools as payment_tools
2
+
3
+ # Example 1: Single product
4
+ print("Example 1: Single finasteride product")
5
+ link = payment_tools.generate_payment_link("(finasteride,1)")
6
+ print(f"Payment link: {link}\n")
7
+
8
+ # Example 2: Multiple products
9
+ print("Example 2: Finasteride and consultation")
10
+ link = payment_tools.generate_payment_link("(finasteride,1),(NP_consultation,1)")
11
+ print(f"Payment link: {link}\n")
12
+
13
+ # Example 3: Hair loss combo
14
+ print("Example 3: Hair loss combo")
15
+ link = payment_tools.generate_payment_link("(finasteride,1),(minoxidil,2),(biotin,1)")
16
+ print(f"Payment link: {link}\n")
17
+
18
+ # Example 4: Weight management combo
19
+ print("Example 4: Weight management combo")
20
+ link = payment_tools.generate_payment_link("(metformin,1),(semaglutide,1),(berberine,2)")
21
+ print(f"Payment link: {link}\n")
22
+
23
+ # Example 5: Supplement stack
24
+ print("Example 5: Supplement stack")
25
+ link = payment_tools.generate_payment_link("(vitamin_d,1),(omega_3,1),(magnesium,1),(zinc,1)")
26
+ print(f"Payment link: {link}\n")
27
+
28
+ # Example 6: Cognitive enhancement
29
+ print("Example 6: Cognitive enhancement")
30
+ link = payment_tools.generate_payment_link("(lions_mane,2),(ashwagandha,1),(nac,1)")
31
+ print(f"Payment link: {link}\n")
32
+
33
+ # Example 7: Fitness stack
34
+ print("Example 7: Fitness stack")
35
+ link = payment_tools.generate_payment_link("(protein,2),(creatine,1)")
36
+ print(f"Payment link: {link}\n")
37
+
38
+ # Example 8: Premium hair treatment
39
+ print("Example 8: Premium hair treatment")
40
+ link = payment_tools.generate_payment_link("(uber minoxidil,1),(finasteride,1),(biotin,2),(NP_consultation,1)")
41
+ print(f"Payment link: {link}\n")
42
+
43
+ # Example 9: Multiple quantities
44
+ print("Example 9: Multiple quantities")
45
+ link = payment_tools.generate_payment_link("(vitamin_c,3),(vitamin_d,2),(zinc,2)")
46
+ print(f"Payment link: {link}\n")
47
+
48
+ # Example 10: Complete wellness package
49
+ print("Example 10: Complete wellness package")
50
+ link = payment_tools.generate_payment_link("(NP_consultation,1),(vitamin_d,1),(omega_3,1),(magnesium,1),(zinc,1),(protein,1),(creatine,1)")
51
+ print(f"Payment link: {link}\n")
payments/workflow_graph.png ADDED
render_app.py ADDED
@@ -0,0 +1,183 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import streamlit as st
3
+ import requests
4
+ import uuid
5
+ import time
6
+ from datetime import datetime, timedelta
7
+
8
+ # Get the API URL from environment variable
9
+ API_URL = os.getenv('API_URL')
10
+
11
+ if not API_URL:
12
+ st.error("API_URL environment variable is not set. Please configure the API URL.")
13
+ st.stop()
14
+
15
+ # Set session timeout to 15 minutes
16
+ SESSION_TIMEOUT = 15 * 60 # 15 minutes in seconds
17
+ # Set API request timeout to 2 minutes for Render (cold starts can be slow)
18
+ API_TIMEOUT = 120 # seconds
19
+
20
+ st.title("Longevity Assistant")
21
+
22
+ # Initialize session state
23
+ if "messages" not in st.session_state:
24
+ st.session_state.messages = []
25
+ st.session_state.session_id = str(uuid.uuid4())
26
+ st.session_state.last_activity = datetime.now()
27
+
28
+ # Get welcome message from API
29
+ try:
30
+ with st.spinner("Connecting to server..."):
31
+ welcome_response = requests.get(f"{API_URL}/welcome", timeout=API_TIMEOUT)
32
+ if welcome_response.status_code == 200:
33
+ welcome_data = welcome_response.json()
34
+
35
+ # Add assistant welcome message to chat history
36
+ st.session_state.messages.append({
37
+ "role": "assistant",
38
+ "content": welcome_data["welcome_message"],
39
+ "links": []
40
+ })
41
+
42
+ # Store suggested questions
43
+ st.session_state.suggested_questions = welcome_data.get("suggested_questions", [])
44
+ else:
45
+ raise Exception(f"Server returned status code: {welcome_response.status_code}")
46
+ except requests.Timeout:
47
+ st.error("Server is taking too long to respond. This might be due to a cold start. Please refresh the page.")
48
+ st.session_state.messages.append({
49
+ "role": "assistant",
50
+ "content": "The server is warming up. Please wait a moment and refresh the page.",
51
+ "links": []
52
+ })
53
+ st.session_state.suggested_questions = []
54
+ except Exception as e:
55
+ st.error(f"Error connecting to the server: {str(e)}")
56
+ st.session_state.messages.append({
57
+ "role": "assistant",
58
+ "content": "Welcome to the Longevity Assistant! How can I help you today?",
59
+ "links": []
60
+ })
61
+ st.session_state.suggested_questions = []
62
+
63
+ elif "last_activity" in st.session_state:
64
+ # Check if session has expired
65
+ time_inactive = (datetime.now() - st.session_state.last_activity).total_seconds()
66
+ if time_inactive > SESSION_TIMEOUT:
67
+ # Reset session
68
+ st.session_state.messages = []
69
+ st.session_state.session_id = str(uuid.uuid4())
70
+
71
+ # Update last activity time
72
+ st.session_state.last_activity = datetime.now()
73
+
74
+ # Display chat messages
75
+ for message in st.session_state.messages:
76
+ with st.chat_message(message["role"]):
77
+ st.write(message["content"])
78
+ if "links" in message and message["links"]:
79
+ for link in message["links"]:
80
+ st.markdown(f"[{link['name']}]({link['url']})")
81
+
82
+ # Display suggested questions as buttons (only if no messages from user yet)
83
+ if len(st.session_state.messages) == 1 and hasattr(st.session_state, 'suggested_questions'):
84
+ st.write("Try asking about:")
85
+ cols = st.columns(2)
86
+ for i, question in enumerate(st.session_state.suggested_questions):
87
+ with cols[i % 2]:
88
+ if st.button(question, key=f"suggested_{i}"):
89
+ # Add user message to chat history
90
+ st.session_state.messages.append({"role": "user", "content": question})
91
+
92
+ # Display user message immediately
93
+ with st.chat_message("user"):
94
+ st.write(question)
95
+
96
+ # Process the question (reusing the chat input logic)
97
+ with st.chat_message("assistant"):
98
+ with st.spinner("Thinking..."):
99
+ try:
100
+ response = requests.post(
101
+ f"{API_URL}/chat",
102
+ json={"session_id": st.session_state.session_id, "message": question},
103
+ timeout=API_TIMEOUT
104
+ )
105
+
106
+ if response.status_code == 200:
107
+ response_data = response.json()
108
+ # Store response for history
109
+ st.session_state.messages.append({
110
+ "role": "assistant",
111
+ "content": response_data["response"],
112
+ "links": response_data["links"]
113
+ })
114
+ # Display the response
115
+ st.write(response_data["response"])
116
+ if response_data["links"]:
117
+ for link in response_data["links"]:
118
+ st.markdown(f"[{link['name']}]({link['url']})")
119
+ else:
120
+ st.error(f"Error: {response.status_code}")
121
+ st.session_state.messages.append({
122
+ "role": "assistant",
123
+ "content": "Sorry, I encountered an error while processing your request.",
124
+ "links": []
125
+ })
126
+ except Exception as e:
127
+ st.error(f"Error connecting to the server: {str(e)}")
128
+ st.session_state.messages.append({
129
+ "role": "assistant",
130
+ "content": "Sorry, I'm having trouble connecting to the server.",
131
+ "links": []
132
+ })
133
+
134
+ # Force a rerun to update the UI
135
+ st.rerun()
136
+
137
+ # Chat input
138
+ if prompt := st.chat_input():
139
+ # Add user message to chat history
140
+ st.session_state.messages.append({"role": "user", "content": prompt})
141
+
142
+ # Display user message immediately
143
+ with st.chat_message("user"):
144
+ st.write(prompt)
145
+
146
+ # Display assistant "thinking" message with spinner
147
+ with st.chat_message("assistant"):
148
+ with st.spinner("Thinking..."):
149
+ # Get bot response
150
+ try:
151
+ response = requests.post(
152
+ f"{API_URL}/chat",
153
+ json={"session_id": st.session_state.session_id, "message": prompt},
154
+ timeout=API_TIMEOUT
155
+ )
156
+
157
+ if response.status_code == 200:
158
+ response_data = response.json()
159
+ # Store response for history
160
+ st.session_state.messages.append({
161
+ "role": "assistant",
162
+ "content": response_data["response"],
163
+ "links": response_data["links"]
164
+ })
165
+ # Display the response
166
+ st.write(response_data["response"])
167
+ if response_data["links"]:
168
+ for link in response_data["links"]:
169
+ st.markdown(f"[{link['name']}]({link['url']})")
170
+ else:
171
+ st.error(f"Error: {response.status_code}")
172
+ st.session_state.messages.append({
173
+ "role": "assistant",
174
+ "content": "Sorry, I encountered an error while processing your request.",
175
+ "links": []
176
+ })
177
+ except Exception as e:
178
+ st.error(f"Error connecting to the server: {str(e)}")
179
+ st.session_state.messages.append({
180
+ "role": "assistant",
181
+ "content": "Sorry, I'm having trouble connecting to the server.",
182
+ "links": []
183
+ })
repositories/__pycache__/__init__.cpython-313.pyc ADDED
Binary file (166 Bytes). View file
 
repositories/__pycache__/promptRepository.cpython-313.pyc ADDED
Binary file (5.87 kB). View file
 
tools.py ADDED
@@ -0,0 +1,396 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import List, Dict, Optional
2
+ from langchain.tools import StructuredTool
3
+ import json
4
+ # Now import from payments module
5
+ from src.payments.agent import process_purchase_request
6
+ # Load datasets
7
+ with open("data/supplements.json", "r") as f:
8
+ SUPPLEMENTS = json.load(f)
9
+
10
+ with open("data/prescription_drugs.json", "r") as f:
11
+ PRESCRIPTION_DRUGS = json.load(f)
12
+
13
+ # Combine both datasets for unified searching
14
+ ALL_PRODUCTS = {**SUPPLEMENTS, **PRESCRIPTION_DRUGS}
15
+
16
+ # Supplement search and retrieval functions
17
+ def search_supplements_by_condition(query: str) -> List[Dict]:
18
+ """
19
+ Search for supplements that are recommended for a specific health condition,
20
+ well-being goal, or enhancement purpose.
21
+
22
+ Args:
23
+ query: The condition, goal, or purpose to search supplements for
24
+ (e.g., 'hair_loss', 'cognitive_function', 'muscle_growth')
25
+
26
+ Returns:
27
+ A list of supplements that match the query
28
+ """
29
+ matching_supplements = []
30
+ query = query.lower()
31
+
32
+ for supp_id, supp_data in SUPPLEMENTS.items():
33
+ # Check conditions
34
+ conditions = [c.lower() for c in supp_data.get("conditions", [])]
35
+ # Check benefits/enhancements
36
+ benefits = [b.lower() for b in supp_data.get("benefits", [])]
37
+ # Check categories
38
+ categories = [cat.lower() for cat in supp_data.get("categories", [])]
39
+
40
+ # Match against all fields
41
+ if (query in conditions or
42
+ query in benefits or
43
+ query in categories or
44
+ query in supp_data["name"].lower() or
45
+ query in supp_data["description"].lower()):
46
+
47
+ matching_supplements.append({
48
+ "id": supp_id,
49
+ "name": supp_data["name"],
50
+ "description": supp_data["description"],
51
+ "link": supp_data["affiliate_link"],
52
+ "conditions": supp_data.get("conditions", []),
53
+ "benefits": supp_data.get("benefits", []),
54
+ "categories": supp_data.get("categories", []),
55
+ "disclaimers": supp_data.get("disclaimers", [])
56
+ })
57
+
58
+ return matching_supplements
59
+
60
+ def search_supplements_by_name(name: str) -> Optional[Dict]:
61
+ """
62
+ Search for a supplement by its name.
63
+
64
+ Args:
65
+ name: The name of the supplement to search for (e.g., 'Vitamin D3', 'Magnesium')
66
+
67
+ Returns:
68
+ Details of the supplement if found, None otherwise
69
+ """
70
+ name = name.lower()
71
+ for supp_id, supp_data in SUPPLEMENTS.items():
72
+ if name in supp_data["name"].lower():
73
+ return {
74
+ "id": supp_id,
75
+ "name": supp_data["name"],
76
+ "description": supp_data["description"],
77
+ "link": supp_data["affiliate_link"],
78
+ "conditions": supp_data.get("conditions", []),
79
+ "benefits": supp_data.get("benefits", []),
80
+ "categories": supp_data.get("categories", []),
81
+ "disclaimers": supp_data.get("disclaimers", [])
82
+ }
83
+ return None
84
+
85
+ def get_supplement_details(supplement_id: str) -> Optional[Dict]:
86
+ """
87
+ Get detailed information about a specific supplement.
88
+
89
+ Args:
90
+ supplement_id: The ID of the supplement to retrieve
91
+
92
+ Returns:
93
+ Detailed information about the supplement or None if not found
94
+ """
95
+ if supplement_id in SUPPLEMENTS:
96
+ supp_data = SUPPLEMENTS[supplement_id]
97
+ return {
98
+ "id": supplement_id,
99
+ "name": supp_data["name"],
100
+ "description": supp_data["description"],
101
+ "link": supp_data["affiliate_link"],
102
+ "conditions": supp_data.get("conditions", []),
103
+ "disclaimers": supp_data.get("disclaimers", [])
104
+ }
105
+ return None
106
+
107
+ def list_all_supplements() -> List[Dict]:
108
+ """
109
+ List all available supplements in the database.
110
+
111
+ Returns:
112
+ A list of all supplements with their basic information
113
+ """
114
+ return [
115
+ {
116
+ "id": supp_id,
117
+ "name": supp_data["name"],
118
+ "description": supp_data["description"],
119
+ "conditions": supp_data.get("conditions", [])
120
+ }
121
+ for supp_id, supp_data in SUPPLEMENTS.items()
122
+ ]
123
+
124
+ # Prescription drug search and retrieval functions
125
+ def search_prescription_drugs_by_condition(query: str) -> List[Dict]:
126
+ """
127
+ Search for prescription drugs that are used for a specific health condition,
128
+ well-being goal, or therapeutic purpose.
129
+
130
+ Args:
131
+ query: The condition, goal, or purpose to search drugs for
132
+ (e.g., 'hypertension', 'depression', 'hair_loss')
133
+
134
+ Returns:
135
+ A list of prescription drugs that match the query
136
+ """
137
+ matching_drugs = []
138
+ query = query.lower()
139
+
140
+ for drug_id, drug_data in PRESCRIPTION_DRUGS.items():
141
+ # Check conditions
142
+ conditions = [c.lower() for c in drug_data.get("conditions", [])]
143
+ # Check benefits
144
+ benefits = [b.lower() for b in drug_data.get("benefits", [])]
145
+ # Check categories
146
+ categories = [cat.lower() for cat in drug_data.get("categories", [])]
147
+
148
+ # Match against all fields
149
+ if (query in conditions or
150
+ query in benefits or
151
+ query in categories or
152
+ query in drug_data["name"].lower() or
153
+ query in drug_data["description"].lower()):
154
+
155
+ matching_drugs.append({
156
+ "id": drug_id,
157
+ "name": drug_data["name"],
158
+ "description": drug_data["description"],
159
+ "link": drug_data["affiliate_link"],
160
+ "conditions": drug_data.get("conditions", []),
161
+ "benefits": drug_data.get("benefits", []),
162
+ "categories": drug_data.get("categories", []),
163
+ "disclaimers": drug_data.get("disclaimers", []),
164
+ "requires_prescription": True
165
+ })
166
+
167
+ return matching_drugs
168
+
169
+ def search_prescription_drugs_by_name(name: str) -> Optional[Dict]:
170
+ """
171
+ Search for a prescription drug by its name.
172
+
173
+ Args:
174
+ name: The name of the drug to search for (e.g., 'Metformin', 'Atorvastatin')
175
+
176
+ Returns:
177
+ Details of the prescription drug if found, None otherwise
178
+ """
179
+ name = name.lower()
180
+ for drug_id, drug_data in PRESCRIPTION_DRUGS.items():
181
+ if name in drug_data["name"].lower():
182
+ return {
183
+ "id": drug_id,
184
+ "name": drug_data["name"],
185
+ "description": drug_data["description"],
186
+ "link": drug_data["affiliate_link"],
187
+ "conditions": drug_data.get("conditions", []),
188
+ "benefits": drug_data.get("benefits", []),
189
+ "categories": drug_data.get("categories", []),
190
+ "disclaimers": drug_data.get("disclaimers", []),
191
+ "requires_prescription": True
192
+ }
193
+ return None
194
+
195
+ def get_prescription_drug_details(drug_id: str) -> Optional[Dict]:
196
+ """
197
+ Get detailed information about a specific prescription drug.
198
+
199
+ Args:
200
+ drug_id: The ID of the prescription drug to retrieve
201
+
202
+ Returns:
203
+ Detailed information about the drug or None if not found
204
+ """
205
+ if drug_id in PRESCRIPTION_DRUGS:
206
+ drug_data = PRESCRIPTION_DRUGS[drug_id]
207
+ return {
208
+ "id": drug_id,
209
+ "name": drug_data["name"],
210
+ "description": drug_data["description"],
211
+ "link": drug_data["affiliate_link"],
212
+ "conditions": drug_data.get("conditions", []),
213
+ "benefits": drug_data.get("benefits", []),
214
+ "categories": drug_data.get("categories", []),
215
+ "disclaimers": drug_data.get("disclaimers", []),
216
+ "requires_prescription": True
217
+ }
218
+ return None
219
+
220
+ def list_all_prescription_drugs() -> List[Dict]:
221
+ """
222
+ List all available prescription drugs in the database.
223
+
224
+ Returns:
225
+ A list of all prescription drugs with their basic information
226
+ """
227
+ return [
228
+ {
229
+ "id": drug_id,
230
+ "name": drug_data["name"],
231
+ "description": drug_data["description"],
232
+ "conditions": drug_data.get("conditions", []),
233
+ "requires_prescription": True
234
+ }
235
+ for drug_id, drug_data in PRESCRIPTION_DRUGS.items()
236
+ ]
237
+
238
+ # Combined search functions
239
+ def search_all_products_by_condition(query: str) -> List[Dict]:
240
+ """
241
+ Search for both supplements and prescription drugs that are recommended for a specific health condition,
242
+ well-being goal, or enhancement purpose.
243
+
244
+ Args:
245
+ query: The condition, goal, or purpose to search for
246
+ (e.g., 'hair_loss', 'cognitive_function', 'depression')
247
+
248
+ Returns:
249
+ A list of supplements and prescription drugs that match the query
250
+ """
251
+ supplements = search_supplements_by_condition(query)
252
+ prescription_drugs = search_prescription_drugs_by_condition(query)
253
+
254
+ return supplements + prescription_drugs
255
+
256
+ def search_all_products_by_name(name: str) -> List[Dict]:
257
+ """
258
+ Search for both supplements and prescription drugs by name.
259
+
260
+ Args:
261
+ name: The name to search for (e.g., 'Vitamin D3', 'Metformin')
262
+
263
+ Returns:
264
+ List of products matching the name query
265
+ """
266
+ results = []
267
+
268
+ supplement = search_supplements_by_name(name)
269
+ if supplement:
270
+ results.append(supplement)
271
+
272
+ prescription = search_prescription_drugs_by_name(name)
273
+ if prescription:
274
+ results.append(prescription)
275
+
276
+ return results
277
+
278
+ def get_product_details(product_id: str) -> Optional[Dict]:
279
+ """
280
+ Get detailed information about a specific product (supplement or prescription drug).
281
+
282
+ Args:
283
+ product_id: The ID of the product to retrieve
284
+
285
+ Returns:
286
+ Detailed information about the product or None if not found
287
+ """
288
+ supplement = get_supplement_details(product_id)
289
+ if supplement:
290
+ return supplement
291
+
292
+ prescription = get_prescription_drug_details(product_id)
293
+ if prescription:
294
+ return prescription
295
+
296
+ return None
297
+
298
+ def list_all_products() -> List[Dict]:
299
+ """
300
+ List all available products (supplements and prescription drugs) in the database.
301
+
302
+ Returns:
303
+ A list of all products with their basic information
304
+ """
305
+ supplements = list_all_supplements()
306
+ prescription_drugs = list_all_prescription_drugs()
307
+
308
+ return supplements + prescription_drugs
309
+
310
+ def create_payment_link(request: str) -> str:
311
+ """
312
+ Create a payment link for products and consultations based on a natural language request.
313
+
314
+ Args:
315
+ request: A string describing what products and quantities to purchase
316
+ (e.g., "I want to buy 1 tretinoin and 1 consultation")
317
+
318
+ Returns:
319
+ The payment link URL
320
+ """
321
+
322
+ # Process the request and get payment link
323
+ response = process_purchase_request(request)
324
+ #response['messages'][0].content
325
+ return response
326
+
327
+ # Create and export Langchain tools
328
+ def get_tools():
329
+ """Return all the tools needed for the agent"""
330
+ return [
331
+ StructuredTool.from_function(search_supplements_by_condition),
332
+ StructuredTool.from_function(search_prescription_drugs_by_condition),
333
+ StructuredTool.from_function(search_all_products_by_condition),
334
+ StructuredTool.from_function(search_supplements_by_name),
335
+ StructuredTool.from_function(search_prescription_drugs_by_name),
336
+ StructuredTool.from_function(search_all_products_by_name),
337
+ StructuredTool.from_function(get_supplement_details),
338
+ StructuredTool.from_function(get_prescription_drug_details),
339
+ StructuredTool.from_function(get_product_details),
340
+ StructuredTool.from_function(list_all_supplements),
341
+ StructuredTool.from_function(list_all_prescription_drugs),
342
+ StructuredTool.from_function(list_all_products),
343
+ StructuredTool.from_function(create_payment_link)
344
+ ]
345
+
346
+ # Export data for use in main.py
347
+ def get_supplements():
348
+ return SUPPLEMENTS
349
+
350
+ def get_prescription_drugs():
351
+ return PRESCRIPTION_DRUGS
352
+
353
+
354
+ def get_available_products() -> Dict:
355
+ """
356
+ Get a list of all available products in the catalog.
357
+
358
+ Returns:
359
+ A dictionary containing available products categorized by type
360
+ """
361
+ # Load the products catalog
362
+ with open("src/payments/products.json", "r") as f:
363
+ products_catalog = json.load(f)
364
+
365
+ # Separate into supplements, prescription drugs, and services
366
+ supplements = []
367
+ prescription_drugs = []
368
+ services = []
369
+
370
+ for product_id in products_catalog.keys():
371
+ if product_id == "NP_consultation":
372
+ services.append(product_id) # Add consultation as a service
373
+ elif product_id in PRESCRIPTION_DRUGS:
374
+ prescription_drugs.append(product_id)
375
+ elif product_id in SUPPLEMENTS:
376
+ supplements.append(product_id)
377
+
378
+ return {
379
+ "supplements": supplements,
380
+ "prescription_drugs": prescription_drugs,
381
+ "services": services,
382
+ "all_products": list(products_catalog.keys())
383
+ }
384
+
385
+ if __name__ == "__main__":
386
+ sample_request = '1 Finasteride consultation, 1 Minoxidil consultation'
387
+ result = process_purchase_request(sample_request)
388
+ print(result)
389
+
390
+ breakpoint()
391
+
392
+ asd = 1
393
+ # You can test with other examples
394
+ # result2 = process_purchase_request("I want to buy 3 items of product_i and 2 of product_k")
395
+ # print(result2)
396
+