devcool20 commited on
Commit
a44e6f7
·
verified ·
1 Parent(s): 111450a

Upload 6 files

Browse files
Files changed (6) hide show
  1. Dockerfile +49 -0
  2. README.md +3 -3
  3. app.py +332 -0
  4. config.json +3 -0
  5. gitattributes +35 -0
  6. requirements.txt +8 -0
Dockerfile ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Use an official Python 3.11 slim image with Debian Bookworm (newer)
2
+ FROM python:3.11-slim-bookworm
3
+
4
+ # Set working directory in the container
5
+ WORKDIR /app
6
+
7
+ # Install system dependencies required by deepmost (and generally good for ML)
8
+ RUN apt-get update && \
9
+ apt-get install -y --no-install-recommends \
10
+ git \
11
+ git-lfs \
12
+ ffmpeg \
13
+ libsm6 \
14
+ libxext6 \
15
+ cmake \
16
+ rsync \
17
+ libgl1-mesa-glx \
18
+ build-essential \
19
+ && rm -rf /var/lib/apt/lists/* \
20
+ && git lfs install
21
+
22
+ # --- CRITICAL FIX: Create /.deepmost and set permissions ---
23
+ # Docker images usually run as root during RUN steps.
24
+ # Create the directory where deepmost wants to save its files.
25
+ RUN mkdir -p /.deepmost && \
26
+ chmod 777 /.deepmost # Give read/write/execute permissions to all for this specific folder
27
+
28
+ # Set Matplotlib cache directory
29
+ ENV MPLCONFIGDIR=/tmp/.matplotlib
30
+
31
+
32
+ # Copy requirements.txt and install Python dependencies
33
+ COPY requirements.txt .
34
+ RUN pip install --no-cache-dir -r requirements.txt
35
+
36
+ # Copy the rest of your application code
37
+ COPY . .
38
+
39
+ # Set the PORT environment variable. Hugging Face Spaces will
40
+ # inject its own PORT value (e.g., 7860), which will override this
41
+ # if it's set. This ensures the CMD always has a value.
42
+ ENV PORT=7860
43
+
44
+ # Expose the port Flask will run on
45
+ EXPOSE ${PORT}
46
+
47
+ # Use the shell form of CMD to allow $PORT expansion
48
+ # This command will start Gunicorn and bind it to the exposed port.
49
+ CMD gunicorn --bind 0.0.0.0:${PORT} app:app --timeout 300 --workers 1
README.md CHANGED
@@ -1,8 +1,8 @@
1
  ---
2
  title: SalesDocSpace
3
- emoji: 🏃
4
- colorFrom: green
5
- colorTo: gray
6
  sdk: docker
7
  pinned: false
8
  ---
 
1
  ---
2
  title: SalesDocSpace
3
+ emoji: 🌖
4
+ colorFrom: indigo
5
+ colorTo: red
6
  sdk: docker
7
  pinned: false
8
  ---
app.py ADDED
@@ -0,0 +1,332 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import sys
3
+ from flask import Flask, request, jsonify
4
+ from flask_cors import CORS
5
+ import numpy as np
6
+ import json
7
+ import google.api_core.exceptions
8
+
9
+ from dotenv import load_dotenv
10
+
11
+ import google.generativeai as genai
12
+ from google.generativeai.types import GenerationConfig
13
+
14
+ print("--- Script Start: app.py ---")
15
+
16
+ # Load environment variables for local testing (Hugging Face handles secrets directly)
17
+ load_dotenv()
18
+
19
+ app = Flask(__name__)
20
+
21
+ # IMPORTANT: Configure CORS to allow requests from your Vercel frontend
22
+ # Replace 'https://sales-doc.vercel.app' with your actual Vercel URL.
23
+ CORS(app, resources={r"/*": {"origins": "https://sales-doc.vercel.app"}})
24
+
25
+ # --- Global Model Instances ---
26
+ sales_agent = None
27
+ gemini_model = None
28
+ gemini_api_key_status = "Not Set" # Track API key status for logs
29
+
30
+ # --- Configure API Keys & Initialize Models ---
31
+ print("\n--- Starting API Key and Model Initialization ---")
32
+
33
+ GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
34
+
35
+ if GEMINI_API_KEY:
36
+ try:
37
+ genai.configure(api_key=GEMINI_API_KEY)
38
+ gemini_api_key_status = "Configured"
39
+ print("Gemini API Key detected and configured.")
40
+ except Exception as e:
41
+ gemini_api_key_status = f"Configuration Failed: {e}"
42
+ print(f"ERROR: Failed to configure Gemini API: {e}")
43
+ else:
44
+ print("WARNING: GEMINI_API_KEY environment variable not found. Gemini LLM features will be disabled.")
45
+ gemini_api_key_status = "Missing"
46
+
47
+ # --- DEEPMOST IMPORT FIX ---
48
+ try:
49
+ from deepmost import sales
50
+ print("Debug Point: Successfully imported deepmost.sales module.")
51
+ except ImportError as e:
52
+ print(f"CRITICAL ERROR: Failed to import deepmost.sales module: {e}")
53
+ print("This means the 'deepmost' library is not correctly installed or its path is wrong.")
54
+ print("SalesRLAgent core model functionality will be disabled.")
55
+ sales = None # Set sales to None if import fails, to prevent NameError later
56
+
57
+ # DeepMost SalesRLAgent Core Model Initialization
58
+ print("Debug Point: Attempting to instantiate sales.Agent (core RL model).")
59
+ if sales is not None:
60
+ try:
61
+ # --- Relying on Dockerfile to make /.deepmost writable ---
62
+ # NO local_model_path argument here.
63
+ sales_agent = sales.Agent(
64
+ model_path="https://huggingface.co/DeepMostInnovations/sales-conversion-model-reinf-learning/resolve/main/sales_conversion_model.zip",
65
+ auto_download=True,
66
+ use_gpu=False
67
+ )
68
+ if sales_agent is not None:
69
+ print("Debug Point: DeepMost SalesRLAgent core model initialized successfully.")
70
+ else:
71
+ print("ERROR: DeepMost SalesRLAgent core model failed to initialize after constructor call (returned None).")
72
+ except Exception as e:
73
+ print(f"CRITICAL ERROR: DeepMost SalesRLAgent core model loading or instantiation failed.")
74
+ print(f"Error Type: {type(e).__name__}")
75
+ print(f"Error Message: {e}")
76
+ import traceback
77
+ traceback.print_exc()
78
+ sales_agent = None
79
+ print("DeepMost model initialization set to None due to error.")
80
+ else:
81
+ print("DeepMost SalesRLAgent core model instantiation skipped because 'sales' module could not be imported.")
82
+
83
+
84
+ # Gemini LLM (1.5 Flash) Initialization
85
+ print("\nDebug Point: Attempting to initialize Gemini 1.5 Flash model.")
86
+ if GEMINI_API_KEY:
87
+ try:
88
+ gemini_model = genai.GenerativeModel('gemini-1.5-flash-latest')
89
+ # Small test call to ensure connectivity
90
+ test_response = gemini_model.generate_content("Hello.", generation_config=GenerationConfig(max_output_tokens=10))
91
+ print(f"Debug Point: Gemini 1.5 Flash test response: {test_response.text[:50]}...")
92
+ print("Debug Point: Gemini LLM (1.5 Flash) initialized successfully.")
93
+ except Exception as e:
94
+ print(f"CRITICAL ERROR: Gemini LLM (1.5 Flash) initialization failed.")
95
+ print(f"Error Type: {type(e).__name__}")
96
+ print(f"Error Message: {e}")
97
+ print("Ensure your GEMINI_API_KEY is correct and has access to Gemini 1.5 Flash.")
98
+ print("This means LLM chat functionality and enriched metrics will not work.")
99
+ import traceback
100
+ traceback.print_exc()
101
+ gemini_model = None
102
+ print(f"Gemini Model Status: {'Initialized' if gemini_model else 'Failed to Initialize'}")
103
+ else:
104
+ print("Debug Point: Skipping Gemini LLM initialization because GEMINI_API_KEY is not set.")
105
+ print("Gemini Model Status: Disabled (API Key Missing)")
106
+
107
+ print("--- Finished Model Initialization Block ---\n")
108
+
109
+
110
+ # --- Flask Routes (API Endpoints only) ---
111
+ @app.route('/analyze_conversation', methods=['POST'])
112
+ def analyze_conversation():
113
+ if sales_agent is None:
114
+ print("ERROR: API call received for analyze_conversation but sales_agent (core) is None.")
115
+ return jsonify({"error": "SalesRLAgent core model not initialized on backend. Check Space logs for DeepMost initialization errors."}), 500
116
+
117
+ try:
118
+ data = request.get_json()
119
+ if not data or 'conversation' not in data:
120
+ return jsonify({"error": "Invalid request. 'conversation' field is required."}), 400
121
+
122
+ conversation = data['conversation']
123
+ if not isinstance(conversation, list) or not all(isinstance(turn, str) for turn in conversation):
124
+ return jsonify({"error": "'conversation' must be a list of strings."}), 400
125
+
126
+ print(f"Processing /analyze_conversation for: {conversation}")
127
+
128
+ all_analysis_results = []
129
+ full_conversation_so_far = []
130
+
131
+ for i, turn_message in enumerate(conversation):
132
+ full_conversation_so_far.append(turn_message)
133
+
134
+ deepmost_analysis = sales_agent.analyze_conversation_progression(full_conversation_so_far, print_results=False)
135
+
136
+ probability = 0.0
137
+ if deepmost_analysis and len(deepmost_analysis) > 0:
138
+ probability = deepmost_analysis[-1]['probability']
139
+
140
+ llm_metrics = {}
141
+ llm_per_turn_suggestion = ""
142
+
143
+ turn_result = {
144
+ "turn": i + 1,
145
+ "speaker": turn_message.split(":")[0].strip() if ":" in turn_message else "Unknown",
146
+ "message": turn_message,
147
+ "probability": probability,
148
+ "status": "calculated",
149
+ "metrics": llm_metrics,
150
+ "llm_per_turn_suggestion": llm_per_turn_suggestion
151
+ }
152
+ all_analysis_results.append(turn_result)
153
+
154
+ print(f"Successfully processed /analyze_conversation. Returning {len(all_analysis_results)} results.")
155
+ return jsonify({"results": all_analysis_results, "llm_advice_pending": True}), 200
156
+
157
+ except Exception as e:
158
+ print(f"ERROR: Exception during /analyze_conversation: {e}")
159
+ import traceback
160
+ traceback.print_exc()
161
+ return jsonify({"error": f"An error occurred during analysis: {str(e)}"}), 500
162
+
163
+ @app.route('/get_llm_advice', methods=['POST'])
164
+ def get_llm_advice():
165
+ if gemini_model is None:
166
+ print("ERROR: LLM advice requested but Gemini LLM is not initialized or available.")
167
+ return jsonify({"points": ["LLM advice unavailable. Gemini failed to load on backend. Check Space logs or add GEMINI_API_KEY secret."]}), 500
168
+
169
+ try:
170
+ data = request.get_json()
171
+ conversation = data.get('conversation', [])
172
+ if not conversation:
173
+ return jsonify({"points": ["No conversation provided for LLM advice."]}), 400
174
+
175
+ full_convo_text = "\n".join(conversation)
176
+ advice_prompt = (
177
+ f"Analyze the entire following sales conversation:\n\n"
178
+ f"{full_convo_text}\n\n"
179
+ f"As a concise sales coach, provide actionable advice to the salesperson on how to best progress this sales call towards a successful outcome. "
180
+ f"Provide this advice as a JSON object with a single key 'points' which is an array of strings, where each string is a distinct, actionable bullet point. "
181
+ f"Do NOT include any other text outside the JSON object. Ensure the JSON is well-formed and complete."
182
+ )
183
+ print(f"Processing /get_llm_advice. Prompting Gemini: {advice_prompt[:200]}...")
184
+ try:
185
+ gemini_response = gemini_model.generate_content(
186
+ [advice_prompt],
187
+ generation_config=GenerationConfig(
188
+ response_mime_type="application/json",
189
+ response_schema={"type": "OBJECT", "properties": {"points": {"type": "ARRAY", "items": {"type": "STRING"}}}, "required": ["points"]},
190
+ max_output_tokens=300,
191
+ temperature=0.6
192
+ )
193
+ )
194
+ raw_json_string = ""
195
+ if gemini_response and gemini_response.candidates and len(gemini_response.candidates) > 0 and \
196
+ gemini_response.candidates[0].content and gemini_response.candidates[0].content.parts and \
197
+ len(gemini_response.candidates[0].content.parts) > 0:
198
+ raw_json_string = gemini_response.candidates[0].content.parts[0].text.strip()
199
+ print(f"Raw LLM JSON response: {raw_json_string}")
200
+ else:
201
+ print("WARNING: Empty or malformed LLM response for overall advice.")
202
+ return jsonify({"points": ["LLM returned an empty or malformed response. Try again or check conversation length."]}), 200
203
+
204
+ parsed_advice = {}
205
+ try:
206
+ parsed_advice = json.loads(raw_json_string)
207
+ if "points" in parsed_advice and isinstance(parsed_advice["points"], list):
208
+ print(f"Successfully parsed Gemini advice: {parsed_advice}")
209
+ return jsonify(parsed_advice), 200
210
+ else:
211
+ print(f"WARNING: LLM did not return 'points' array in structured advice: {raw_json_string}")
212
+ return jsonify({"points": ["LLM response was not structured as expected (missing 'points' array). Raw: " + raw_json_string[:100] + "..."]}), 200
213
+ except json.JSONDecodeError as json_e:
214
+ print(f"ERROR: JSON parsing error for overall advice: {json_e}. Raw string: {raw_json_string}")
215
+ return jsonify({"points": ["Error parsing LLM JSON advice. This happens with incomplete LLM responses (e.g., due to API rate limits or max tokens). Please try a shorter conversation or wait a moment. Raw response starts with: " + raw_json_string[:100] + "..."]})
216
+ except Exception as parse_e:
217
+ print(f"ERROR: General error parsing LLM JSON advice: {parse_e}. Raw string: {raw_json_string}")
218
+ return jsonify({"points": ["General error with LLM JSON parsing. Raw response starts with: " + raw_json_string[:100] + "..."]})
219
+
220
+ except google.api_core.exceptions.ResourceExhausted as quota_e:
221
+ print(f"ERROR: Quota Exceeded for LLM advice: {quota_e}")
222
+ return jsonify({"points": ["Quota Exceeded: Cannot generate overall LLM advice due to API rate limits. Please try again in a minute or two."]}), 200
223
+ except Exception as e:
224
+ print(f"ERROR: Exception generating structured Gemini advice: {e}")
225
+ import traceback
226
+ traceback.print_exc()
227
+ return jsonify({"points": [f"Error generating LLM advice: {type(e).__name__} - {e}"]}), 200
228
+
229
+ except Exception as e:
230
+ print(f"ERROR: An unexpected error occurred in /get_llm_advice: {e}")
231
+ import traceback
232
+ traceback.print_exc()
233
+ return jsonify({"points": [f"An unexpected error occurred: {type(e).__name__} - {e}"]}), 500
234
+
235
+
236
+ @app.route('/chat_llm', methods=['POST'])
237
+ def chat_llm():
238
+ if gemini_model is None:
239
+ print("ERROR: Gemini LLM instance is not initialized or available for chat.")
240
+ return jsonify({"error": "LLM chat functionality unavailable. Gemini failed to load."}), 500
241
+
242
+ try:
243
+ data = request.get_json()
244
+ user_message = data.get('message', '')
245
+ if not user_message:
246
+ return jsonify({"error": "No message provided."}), 400
247
+
248
+ print(f"Processing /chat_llm. Received message: {user_message}")
249
+
250
+ general_chat_prompt = f"Respond to the following message concisely: '{user_message}'"
251
+ chat_response_obj = gemini_model.generate_content(
252
+ general_chat_prompt,
253
+ generation_config=GenerationConfig(max_output_tokens=150, temperature=0.7)
254
+ )
255
+ chat_response = chat_response_obj.text.strip()
256
+ print(f"Gemini Raw Chat Response: {chat_response}")
257
+
258
+ json_prompt = (
259
+ f"Analyze the following message: '{user_message}'. "
260
+ f"Provide a JSON object with 'summary', 'sentiment' (positive/neutral/negative), "
261
+ f"and 'keywords' (array of strings). Do not include any other text outside the JSON block."
262
+ )
263
+ json_response_obj = gemini_model.generate_content(
264
+ [json_prompt],
265
+ generation_config=GenerationConfig(
266
+ response_mime_type="application/json",
267
+ max_output_tokens=200,
268
+ temperature=0.1
269
+ )
270
+ )
271
+ json_response = json_response_obj.text.strip()
272
+ print(f"Gemini Raw JSON Prompt Response: {json_response}")
273
+
274
+ parsed_json_output = None
275
+ try:
276
+ parsed_json_output = json.loads(json_response)
277
+ print(f"Parsed JSON from Gemini chat: {parsed_json_output}")
278
+
279
+ except json.JSONDecodeError as e:
280
+ print(f"ERROR: JSON parsing error for chat_llm (Gemini): {e}. Raw string: {json_response}")
281
+ except Exception as e:
282
+ print(f"ERROR: General error during JSON parsing attempt for chat_llm (Gemini): {e}. Raw string: {json_response}")
283
+
284
+ return jsonify({
285
+ "user_message": user_message,
286
+ "raw_chat_response": chat_response,
287
+ "raw_json_prompt_response": json_response,
288
+ "parsed_json_metrics": parsed_json_output,
289
+ "status": "success"
290
+ }), 200
291
+
292
+ except Exception as e:
293
+ print(f"ERROR: Error during LLM chat: {e}")
294
+ import traceback
295
+ traceback.print_exc()
296
+ return jsonify({"error": f"An error occurred during LLM chat: {str(e)}"}), 500
297
+
298
+ # Health check endpoint for Hugging Face Spaces (optional, but good practice)
299
+ @app.route('/health', methods=['GET'])
300
+ def health_check():
301
+ status = {
302
+ "status": "up",
303
+ "deepmost_model_initialized": sales_agent is not None,
304
+ "gemini_llm_initialized": gemini_model is not None,
305
+ "gemini_api_key_status": gemini_api_key_status,
306
+ "message": "Application is running"
307
+ }
308
+ # Provide more detail if a component failed
309
+ if sales_agent is None:
310
+ status["message"] = "Application running, but DeepMost model failed to initialize."
311
+ status["status"] = "degraded"
312
+ if gemini_model is None and gemini_api_key_status != "Missing": # Only degraded if API key was provided but init failed
313
+ status["message"] = "Application running, but Gemini LLM failed to initialize."
314
+ status["status"] = "degraded"
315
+ elif gemini_model is None and gemini_api_key_status == "Missing":
316
+ status["message"] = "Application running. Gemini LLM disabled (no API key)."
317
+
318
+
319
+ print(f"Health check requested. Status: {status}")
320
+ return jsonify(status), 200
321
+
322
+ # --- Main Execution Block ---
323
+ if __name__ == '__main__':
324
+ try:
325
+ print("Attempting to start Flask app (this block is primarily for local execution).")
326
+ print("Application setup complete. Expecting Gunicorn to take over.")
327
+
328
+ except Exception as startup_exception:
329
+ print(f"CRITICAL: An unhandled exception occurred during Flask app setup: {startup_exception}")
330
+ import traceback
331
+ traceback.print_exc()
332
+ sys.exit(1) # Exit with error code if startup fails
config.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ {
2
+ "python_version": "3.11"
3
+ }
gitattributes ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tar filter=lfs diff=lfs merge=lfs -text
29
+ *.tflite filter=lfs diff=lfs merge=lfs -text
30
+ *.tgz filter=lfs diff=lfs merge=lfs -text
31
+ *.wasm filter=lfs diff=lfs merge=lfs -text
32
+ *.xz filter=lfs diff=lfs merge=lfs -text
33
+ *.zip filter=lfs diff=lfs merge=lfs -text
34
+ *.zst filter=lfs diff=lfs merge=lfs -text
35
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
requirements.txt ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ Flask
2
+ Flask-Cors
3
+ numpy
4
+ python-dotenv
5
+ google-generativeai
6
+ deepmost
7
+ gunicorn # Add this line!
8
+