Yaswanth-Bolla commited on
Commit
07be4c8
·
1 Parent(s): fb41f3c

Json returns

Browse files
Files changed (1) hide show
  1. business_continuity.py +110 -217
business_continuity.py CHANGED
@@ -27,70 +27,8 @@ if GROQ_API_KEY:
27
  GROQ_API_KEY = GROQ_API_KEY.strip()
28
 
29
  logger.info(f"GROQ_API_KEY loaded: {'Yes' if GROQ_API_KEY else 'No'}")
30
- if GROQ_API_KEY:
31
- logger.info(f"GROQ_API_KEY preview: {GROQ_API_KEY[:10]}...")
32
 
33
- # Model Setup with improved error handling
34
- def generate_response(system_prompt: str, user_message: str, max_retries: int = 3):
35
- """Generate response from GROQ API with retry logic and comprehensive error handling"""
36
-
37
- if not GROQ_API_KEY:
38
- logger.error("GROQ_API_KEY environment variable is not set")
39
- raise Exception("GROQ_API_KEY environment variable is not set")
40
-
41
- logger.info("Initializing GROQ API client")
42
-
43
- for attempt in range(max_retries):
44
- try:
45
- logger.info(f"Attempt {attempt + 1}/{max_retries} - Making API call to GROQ")
46
-
47
- client = openai.OpenAI(
48
- api_key=GROQ_API_KEY,
49
- base_url="https://api.groq.com/openai/v1",
50
- timeout=60.0 # Increased timeout
51
- )
52
-
53
- response = client.chat.completions.create(
54
- model="llama3-8b-8192",
55
- messages=[
56
- {"role": "system", "content": system_prompt},
57
- {"role": "user", "content": user_message}
58
- ],
59
- temperature=0.2, # Even lower for more consistent responses
60
- max_tokens=3000, # Increased further to avoid truncation
61
- top_p=0.9,
62
- frequency_penalty=0.0,
63
- presence_penalty=0.0
64
- )
65
-
66
- content = response.choices[0].message.content
67
- logger.info(f"API call successful - Response length: {len(content) if content else 0}")
68
-
69
- if not content or content.strip() == "":
70
- logger.warning(f"Empty response on attempt {attempt + 1}")
71
- if attempt == max_retries - 1:
72
- raise Exception("Received empty response from GROQ API after all retries")
73
- continue
74
-
75
- return content
76
-
77
- except openai.APITimeoutError as e:
78
- logger.warning(f"Timeout error on attempt {attempt + 1}: {str(e)}")
79
- if attempt == max_retries - 1:
80
- raise Exception(f"GROQ API timeout after {max_retries} attempts: {str(e)}")
81
-
82
- except openai.APIError as e:
83
- logger.error(f"GROQ API error on attempt {attempt + 1}: {str(e)}")
84
- if attempt == max_retries - 1:
85
- raise Exception(f"GROQ API error after {max_retries} attempts: {str(e)}")
86
-
87
- except Exception as e:
88
- logger.error(f"Unexpected error on attempt {attempt + 1}: {str(e)}")
89
- logger.error(f"Traceback: {traceback.format_exc()}")
90
- if attempt == max_retries - 1:
91
- raise Exception(f"GROQ API connection failed after {max_retries} attempts: {str(e)}")
92
-
93
- # Request Models with validation
94
  class BusinessProcess(BaseModel):
95
  department: str = Field(..., min_length=1, max_length=100)
96
  sub_department: Optional[str] = Field(default="", max_length=100)
@@ -105,7 +43,7 @@ class BusinessContinuityRequest(BaseModel):
105
  business_process: BusinessProcess
106
  analysis_data: AnalysisData = Field(default_factory=lambda: AnalysisData())
107
 
108
- # Response Models
109
  class RecoveryStrategiesResponse(BaseModel):
110
  success: bool
111
  people_unavailability_strategy: str
@@ -119,188 +57,143 @@ class RecoveryStrategiesResponse(BaseModel):
119
  message: str
120
  request_id: str = Field(default_factory=lambda: str(uuid.uuid4()))
121
 
122
- def validate_strategies_data(data: dict) -> dict:
123
- """Validate and clean strategies data"""
124
- logger.info("Validating strategies data")
125
-
126
- required_fields = [
127
- "people_unavailability_strategy",
128
- "people_reasoning",
129
- "technology_data_unavailability_strategy",
130
- "technology_reasoning",
131
- "site_unavailability_strategy",
132
- "site_reasoning",
133
- "third_party_vendors_unavailability_strategy",
134
- "vendor_reasoning"
135
- ]
136
-
137
- # Ensure all required fields exist and are strings
138
- for field in required_fields:
139
- if field not in data:
140
- logger.warning(f"Missing field: {field}")
141
- data[field] = "Strategy not generated - please retry request"
142
- elif not isinstance(data[field], str):
143
- logger.warning(f"Invalid type for field {field}: {type(data[field])}")
144
- data[field] = str(data[field]) if data[field] is not None else "Strategy not generated"
145
- elif len(data[field].strip()) == 0:
146
- logger.warning(f"Empty field: {field}")
147
- data[field] = "Strategy not generated - please retry request"
148
-
149
- logger.info("Data validation completed")
150
- return data
151
 
152
- def generate_fallback_response(request: BusinessContinuityRequest, error_msg: str = "") -> RecoveryStrategiesResponse:
153
- """Generate fallback response when API fails"""
154
- logger.info("Generating fallback response")
155
-
156
- process_name = request.business_process.process_name
157
- department = request.business_process.department
158
 
159
- return RecoveryStrategiesResponse(
160
- success=True,
161
- people_unavailability_strategy=f"Implement cross-training programs for critical roles in {process_name}, maintain updated emergency contact lists, and establish clear succession planning. Create detailed process documentation and implement job rotation to reduce single points of failure.",
162
- people_reasoning=f"Cross-training and succession planning are essential for {process_name} in {department} to ensure continuity when key personnel are unavailable.",
163
-
164
- technology_data_unavailability_strategy=f"Establish automated backup systems for {process_name} data, implement redundant infrastructure with failover capabilities, and maintain disaster recovery procedures. Conduct regular backup testing and ensure secure off-site data replication.",
165
- technology_reasoning=f"Robust backup and redundancy systems are critical for {process_name} to minimize downtime and ensure rapid recovery from technology failures.",
166
-
167
- site_unavailability_strategy=f"Identify and prepare alternative work locations for {process_name} operations, enable comprehensive remote work capabilities, and establish emergency communication protocols. Maintain emergency supplies and equipment at backup locations.",
168
- site_reasoning=f"Alternative work arrangements ensure {process_name} can continue operating when primary {department} facilities are inaccessible.",
169
 
170
- third_party_vendors_unavailability_strategy=f"Develop relationships with backup vendors for {process_name} critical services, maintain emergency supplier contacts, and establish contingency contracts. Regularly assess vendor reliability and maintain strategic inventory buffers.",
171
- vendor_reasoning=f"Vendor diversification and contingency planning reduce supply chain risks for {process_name} and ensure continuity of essential external services.",
 
 
 
 
 
 
 
172
 
173
- message=f"Fallback strategies generated due to API processing error: {error_msg}" if error_msg else "Fallback strategies generated"
174
- )
 
 
175
 
176
- # Business Continuity Router
177
- business_continuity_router = APIRouter()
 
 
 
 
 
 
 
 
 
 
 
 
 
178
 
179
  @business_continuity_router.post("/api/business-continuity/generate-recovery-strategies", response_model=RecoveryStrategiesResponse)
180
  def generate_recovery_strategies(request: BusinessContinuityRequest):
181
- """
182
- Generate comprehensive business continuity recovery strategies for a business process
183
- """
184
  request_id = str(uuid.uuid4())
185
- logger.info(f"Request {request_id}: Starting recovery strategies generation")
186
- logger.info(f"Request {request_id}: Process - {request.business_process.process_name}")
187
- logger.info(f"Request {request_id}: Department - {request.business_process.department}")
 
 
 
 
188
 
189
  try:
190
- # Validate input
191
- if not request.business_process.process_name.strip():
192
- logger.error(f"Request {request_id}: Empty process name")
193
- raise HTTPException(status_code=400, detail="Process name cannot be empty")
194
-
195
- if not request.business_process.department.strip():
196
- logger.error(f"Request {request_id}: Empty department")
197
- raise HTTPException(status_code=400, detail="Department cannot be empty")
198
-
199
- system_prompt = """You are an expert business continuity consultant. Your task is to generate specific, actionable recovery strategies for business processes.
200
-
201
- **CRITICAL INSTRUCTIONS:**
202
- 1. **MUST** respond with a single, valid JSON object and nothing else.
203
- 2. The JSON object must be the only content in your response. Do not include any text, explanations, or markdown formatting (like ```json) before or after the JSON object.
204
- 3. Ensure the JSON is perfectly formed with correct syntax, including matching curly braces, quoted keys, and properly escaped string values.
205
- 4. Each strategy must be 2-3 detailed sentences with specific, actionable steps.
206
- 5. Each reasoning must be 1-2 sentences explaining why the strategy fits the specific process.
207
 
208
- **Required JSON format (no deviations allowed):**
209
- ```json
210
  {
211
- "people_unavailability_strategy": "Detailed strategy for handling personnel unavailability with specific steps.",
212
- "people_reasoning": "Clear explanation of why this strategy suits this specific process.",
213
- "technology_data_unavailability_strategy": "Comprehensive strategy for technology and data failures with actionable steps.",
214
- "technology_reasoning": "Explanation of why this technology strategy is appropriate for this process.",
215
- "site_unavailability_strategy": "Detailed strategy for site/facility unavailability with specific actions.",
216
- "site_reasoning": "Why this site strategy is suitable for this specific process.",
217
- "third_party_vendors_unavailability_strategy": "Comprehensive strategy for vendor/supplier disruptions.",
218
- "vendor_reasoning": "Why this vendor strategy is appropriate for this process."
219
  }
220
- ```"""
221
-
222
- user_message = f"""Generate business continuity recovery strategies for this specific process:
223
 
224
- PROCESS DETAILS:
225
- - Process Name: {request.business_process.process_name}
226
- - Department: {request.business_process.department}
227
- - Sub-Department: {request.business_process.sub_department or 'Not specified'}
228
- - Description: {request.business_process.process_description}
229
 
230
- ANALYSIS DATA:
231
- - Impact Analysis: {json.dumps(request.analysis_data.impact_analysis) if request.analysis_data.impact_analysis else 'Not provided'}
232
- - Minimum Operating Requirements: {json.dumps(request.analysis_data.minimum_operating_requirements) if request.analysis_data.minimum_operating_requirements else 'Not provided'}
 
 
233
 
234
- Generate comprehensive recovery strategies for these four scenarios:
235
- 1. Key personnel unavailability
236
- 2. Technology/data system failures
237
- 3. Primary site/facility unavailability
238
- 4. Critical third-party vendor/supplier disruptions
239
-
240
- Focus on actionable, specific strategies tailored to this exact process and department context."""
241
-
242
- logger.info(f"Request {request_id}: Making API call to GROQ")
243
-
244
- # Get response from API
245
- api_response = generate_response(system_prompt, user_message)
246
-
247
- if not api_response:
248
- logger.error(f"Request {request_id}: Empty API response")
249
- return generate_fallback_response(request, "Empty API response")
250
-
251
- logger.info(f"Request {request_id}: API response received, length: {len(api_response)}")
252
 
253
- # Clean and parse JSON
254
  try:
255
- logger.info(f"Request {request_id}: Raw API response length: {len(api_response)}")
256
- logger.info(f"Request {request_id}: Raw response first 200 chars: {api_response[:200]}")
257
- logger.info(f"Request {request_id}: Raw response last 200 chars: {api_response[-200:]}")
258
-
259
  strategies_data = json.loads(api_response)
260
- logger.info(f"Request {request_id}: JSON parsing successful")
 
 
261
 
262
- except (json.JSONDecodeError, ValueError) as e:
263
- logger.error(f"Request {request_id}: JSON parsing failed - {str(e)}")
264
- logger.error(f"Request {request_id}: Raw response preview: {api_response[:300]}...")
265
- return generate_fallback_response(request, f"JSON parsing failed: {str(e)}")
 
 
 
 
 
 
 
 
 
 
266
 
267
- # Validate the parsed data
268
- try:
269
- strategies_data = validate_strategies_data(strategies_data)
270
-
271
- response = RecoveryStrategiesResponse(
272
- success=True,
273
- people_unavailability_strategy=strategies_data["people_unavailability_strategy"],
274
- people_reasoning=strategies_data["people_reasoning"],
275
- technology_data_unavailability_strategy=strategies_data["technology_data_unavailability_strategy"],
276
- technology_reasoning=strategies_data["technology_reasoning"],
277
- site_unavailability_strategy=strategies_data["site_unavailability_strategy"],
278
- site_reasoning=strategies_data["site_reasoning"],
279
- third_party_vendors_unavailability_strategy=strategies_data["third_party_vendors_unavailability_strategy"],
280
- vendor_reasoning=strategies_data["vendor_reasoning"],
281
- message="Successfully generated comprehensive recovery strategies",
282
- request_id=request_id
283
- )
284
-
285
- logger.info(f"Request {request_id}: Successfully generated strategies")
286
- return response
287
-
288
- except Exception as e:
289
- logger.error(f"Request {request_id}: Data validation failed - {str(e)}")
290
- return generate_fallback_response(request, f"Data validation failed: {str(e)}")
291
 
292
  except HTTPException:
293
  # Re-raise HTTP exceptions
294
  raise
295
-
296
  except Exception as e:
297
  logger.error(f"Request {request_id}: Unexpected error - {str(e)}")
298
- logger.error(f"Request {request_id}: Traceback: {traceback.format_exc()}")
299
-
300
- # Return fallback response instead of raising HTTP error
301
- return generate_fallback_response(request, f"Unexpected error: {str(e)}")
302
 
303
- # Health check endpoint
304
  @business_continuity_router.get("/api/business-continuity/health")
305
  def health_check():
306
  """Health check endpoint"""
 
27
  GROQ_API_KEY = GROQ_API_KEY.strip()
28
 
29
  logger.info(f"GROQ_API_KEY loaded: {'Yes' if GROQ_API_KEY else 'No'}")
 
 
30
 
31
+ # Request Models
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
  class BusinessProcess(BaseModel):
33
  department: str = Field(..., min_length=1, max_length=100)
34
  sub_department: Optional[str] = Field(default="", max_length=100)
 
43
  business_process: BusinessProcess
44
  analysis_data: AnalysisData = Field(default_factory=lambda: AnalysisData())
45
 
46
+ # Response Model
47
  class RecoveryStrategiesResponse(BaseModel):
48
  success: bool
49
  people_unavailability_strategy: str
 
57
  message: str
58
  request_id: str = Field(default_factory=lambda: str(uuid.uuid4()))
59
 
60
+ # Create router
61
+ business_continuity_router = APIRouter()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
 
63
+ def call_groq_api(prompt: str, user_message: str):
64
+ """Simple function to call GROQ API"""
65
+ if not GROQ_API_KEY:
66
+ logger.error("GROQ_API_KEY environment variable is not set")
67
+ raise Exception("GROQ_API_KEY not configured")
 
68
 
69
+ try:
70
+ client = openai.OpenAI(
71
+ api_key=GROQ_API_KEY,
72
+ base_url="https://api.groq.com/openai/v1",
73
+ timeout=60.0
74
+ )
 
 
 
 
75
 
76
+ response = client.chat.completions.create(
77
+ model="llama3-8b-8192",
78
+ messages=[
79
+ {"role": "system", "content": prompt},
80
+ {"role": "user", "content": user_message}
81
+ ],
82
+ temperature=0.1,
83
+ max_tokens=1500
84
+ )
85
 
86
+ return response.choices[0].message.content
87
+ except Exception as e:
88
+ logger.error(f"API call failed: {str(e)}")
89
+ raise Exception(f"API call failed: {str(e)}")
90
 
91
+ def create_error_response(request_id: str, error_message: str) -> RecoveryStrategiesResponse:
92
+ """Create a simple error response"""
93
+ return RecoveryStrategiesResponse(
94
+ success=False,
95
+ people_unavailability_strategy="Error: Strategy not generated",
96
+ people_reasoning="Error: Reasoning not generated",
97
+ technology_data_unavailability_strategy="Error: Strategy not generated",
98
+ technology_reasoning="Error: Reasoning not generated",
99
+ site_unavailability_strategy="Error: Strategy not generated",
100
+ site_reasoning="Error: Reasoning not generated",
101
+ third_party_vendors_unavailability_strategy="Error: Strategy not generated",
102
+ vendor_reasoning="Error: Reasoning not generated",
103
+ message=f"Error: {error_message}",
104
+ request_id=request_id
105
+ )
106
 
107
  @business_continuity_router.post("/api/business-continuity/generate-recovery-strategies", response_model=RecoveryStrategiesResponse)
108
  def generate_recovery_strategies(request: BusinessContinuityRequest):
109
+ """Generate business continuity recovery strategies"""
 
 
110
  request_id = str(uuid.uuid4())
111
+ logger.info(f"Request {request_id}: Starting for {request.business_process.process_name}")
112
+
113
+ # Basic input validation
114
+ if not request.business_process.process_name.strip():
115
+ raise HTTPException(status_code=400, detail="Process name cannot be empty")
116
+ if not request.business_process.department.strip():
117
+ raise HTTPException(status_code=400, detail="Department cannot be empty")
118
 
119
  try:
120
+ # Create system prompt - simplified and direct
121
+ system_prompt = """You are a business continuity expert. Your response must be a valid JSON object only.
122
+ Follow this exact format:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
123
 
 
 
124
  {
125
+ "people_unavailability_strategy": "Strategy for when key staff are unavailable (2 sentences).",
126
+ "people_reasoning": "Why this strategy works for this process (1 sentence).",
127
+ "technology_data_unavailability_strategy": "Strategy for system failures (2 sentences).",
128
+ "technology_reasoning": "Why this technology strategy works (1 sentence).",
129
+ "site_unavailability_strategy": "Strategy for site unavailability (2 sentences).",
130
+ "site_reasoning": "Why this site strategy works (1 sentence).",
131
+ "third_party_vendors_unavailability_strategy": "Strategy for supplier disruptions (2 sentences).",
132
+ "vendor_reasoning": "Why this vendor strategy works (1 sentence)."
133
  }
 
 
 
134
 
135
+ Do not include any other text or formatting in your response."""
 
 
 
 
136
 
137
+ # Create user message - simplified
138
+ user_message = f"""Generate business continuity strategies for:
139
+ Process: {request.business_process.process_name}
140
+ Department: {request.business_process.department}
141
+ Description: {request.business_process.process_description}"""
142
 
143
+ # Call API
144
+ try:
145
+ api_response = call_groq_api(system_prompt, user_message)
146
+ logger.info(f"Request {request_id}: Received API response")
147
+ except Exception as e:
148
+ logger.error(f"Request {request_id}: API call failed - {str(e)}")
149
+ return create_error_response(request_id, "API error")
 
 
 
 
 
 
 
 
 
 
 
150
 
151
+ # Parse JSON response with safer approach
152
  try:
153
+ # First try direct parsing
 
 
 
154
  strategies_data = json.loads(api_response)
155
+ logger.info(f"Request {request_id}: Successfully parsed JSON")
156
+ except json.JSONDecodeError:
157
+ logger.warning(f"Request {request_id}: Direct JSON parsing failed, trying to clean response")
158
 
159
+ # Clean the response - look for JSON inside any markdown or text
160
+ try:
161
+ # Try to find JSON pattern
162
+ json_match = re.search(r'({[\s\S]*?})(?:\s*$|\n)', api_response)
163
+ if json_match:
164
+ json_text = json_match.group(1)
165
+ strategies_data = json.loads(json_text)
166
+ logger.info(f"Request {request_id}: Successfully parsed JSON after cleaning")
167
+ else:
168
+ raise ValueError("Could not find JSON in response")
169
+ except Exception as e:
170
+ logger.error(f"Request {request_id}: JSON parsing failed after cleaning - {str(e)}")
171
+ logger.error(f"Request {request_id}: Raw response preview: {api_response[:300]}")
172
+ return create_error_response(request_id, "Could not parse API response")
173
 
174
+ # Safely get fields with fallbacks
175
+ return RecoveryStrategiesResponse(
176
+ success=True,
177
+ people_unavailability_strategy=strategies_data.get("people_unavailability_strategy", "Strategy not generated"),
178
+ people_reasoning=strategies_data.get("people_reasoning", "Reasoning not generated"),
179
+ technology_data_unavailability_strategy=strategies_data.get("technology_data_unavailability_strategy", "Strategy not generated"),
180
+ technology_reasoning=strategies_data.get("technology_reasoning", "Reasoning not generated"),
181
+ site_unavailability_strategy=strategies_data.get("site_unavailability_strategy", "Strategy not generated"),
182
+ site_reasoning=strategies_data.get("site_reasoning", "Reasoning not generated"),
183
+ third_party_vendors_unavailability_strategy=strategies_data.get("third_party_vendors_unavailability_strategy", "Strategy not generated"),
184
+ vendor_reasoning=strategies_data.get("vendor_reasoning", "Reasoning not generated"),
185
+ message="Successfully generated recovery strategies",
186
+ request_id=request_id
187
+ )
 
 
 
 
 
 
 
 
 
 
188
 
189
  except HTTPException:
190
  # Re-raise HTTP exceptions
191
  raise
 
192
  except Exception as e:
193
  logger.error(f"Request {request_id}: Unexpected error - {str(e)}")
194
+ logger.error(f"Traceback: {traceback.format_exc()}")
195
+ return create_error_response(request_id, "An unexpected error occurred")
 
 
196
 
 
197
  @business_continuity_router.get("/api/business-continuity/health")
198
  def health_check():
199
  """Health check endpoint"""