SysModeler commited on
Commit
fc3a249
·
verified ·
1 Parent(s): e12b72c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +217 -779
app.py CHANGED
@@ -1,780 +1,218 @@
1
- import os
2
- import gradio as gr
3
- import warnings
4
- import json
5
- from dotenv import load_dotenv
6
- from typing import List
7
- import time
8
- from functools import lru_cache
9
- import logging
10
-
11
- from langchain_community.vectorstores import FAISS
12
- from langchain_community.embeddings import AzureOpenAIEmbeddings
13
- from openai import AzureOpenAI
14
-
15
- # Patch Gradio bug
16
- import gradio_client.utils
17
- gradio_client.utils.json_schema_to_python_type = lambda schema, defs=None: "string"
18
-
19
- # Load environment variables
20
- load_dotenv()
21
- AZURE_OPENAI_API_KEY = os.getenv("AZURE_OPENAI_API_KEY")
22
- AZURE_OPENAI_ENDPOINT = os.getenv("AZURE_OPENAI_ENDPOINT")
23
- AZURE_OPENAI_LLM_DEPLOYMENT = os.getenv("AZURE_OPENAI_LLM_DEPLOYMENT")
24
- AZURE_OPENAI_EMBEDDING_DEPLOYMENT = os.getenv("AZURE_OPENAI_EMBEDDING_DEPLOYMENT")
25
-
26
- if not all([AZURE_OPENAI_API_KEY, AZURE_OPENAI_ENDPOINT, AZURE_OPENAI_LLM_DEPLOYMENT, AZURE_OPENAI_EMBEDDING_DEPLOYMENT]):
27
- raise ValueError("Missing one or more Azure OpenAI environment variables.")
28
-
29
- warnings.filterwarnings("ignore")
30
-
31
- # Embeddings
32
- embeddings = AzureOpenAIEmbeddings(
33
- azure_deployment=AZURE_OPENAI_EMBEDDING_DEPLOYMENT,
34
- azure_endpoint=AZURE_OPENAI_ENDPOINT,
35
- openai_api_key=AZURE_OPENAI_API_KEY,
36
- openai_api_version="2025-01-01-preview",
37
- chunk_size=1000
38
- )
39
-
40
- # Vectorstore
41
- SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
42
- FAISS_INDEX_PATH = os.path.join(SCRIPT_DIR, "faiss_index_sysml")
43
- vectorstore = FAISS.load_local(FAISS_INDEX_PATH, embeddings, allow_dangerous_deserialization=True)
44
-
45
- # OpenAI client
46
- client = AzureOpenAI(
47
- api_key=AZURE_OPENAI_API_KEY,
48
- api_version="2025-01-01-preview",
49
- azure_endpoint=AZURE_OPENAI_ENDPOINT
50
- )
51
-
52
- # Logger
53
- logger = logging.getLogger(__name__)
54
-
55
- # Enhanced SysML retriever with proper metadata filtering & weighting
56
- @lru_cache(maxsize=100)
57
- def sysml_retriever(query: str) -> str:
58
- try:
59
- print(f"\n🔍 QUERY: {query}")
60
- print("="*80)
61
-
62
- # Get more results for filtering and weighting
63
- results = vectorstore.similarity_search_with_score(query, k=100)
64
- print(f"📊 Total results retrieved: {len(results)}")
65
-
66
- # Apply metadata filtering and weighting
67
- weighted_results = []
68
- sysmodeler_count = 0
69
- other_count = 0
70
-
71
- for i, (doc, score) in enumerate(results):
72
- # Get document source
73
- doc_source = doc.metadata.get('source', '').lower() if hasattr(doc, 'metadata') else str(doc).lower()
74
-
75
- # Determine if this is SysModeler content
76
- is_sysmodeler = (
77
- 'sysmodeler' in doc_source or
78
- 'user manual' in doc_source or
79
- 'sysmodeler.ai' in doc.page_content.lower() or
80
- 'workspace.sysmodeler.ai' in doc.page_content.lower() or
81
- 'Create with AI' in doc.page_content or
82
- 'Canvas Overview' in doc.page_content or
83
- 'AI-powered' in doc.page_content or
84
- 'voice input' in doc.page_content or
85
- 'Canvas interface' in doc.page_content or
86
- 'Project Creation' in doc.page_content or
87
- 'Shape Palette' in doc.page_content or
88
- 'AI Copilot' in doc.page_content or
89
- 'SynthAgent' in doc.page_content or
90
- 'workspace dashboard' in doc.page_content.lower()
91
- )
92
-
93
- # Apply weighting based on source
94
- if is_sysmodeler:
95
- # BOOST SysModeler content: reduce score by 40% (lower score = higher relevance)
96
- weighted_score = score * 0.6
97
- source_type = "SysModeler"
98
- sysmodeler_count += 1
99
- else:
100
- # Keep original score for other content
101
- weighted_score = score
102
- source_type = "Other"
103
- other_count += 1
104
-
105
- # Add metadata tags for filtering
106
- doc.metadata = doc.metadata if hasattr(doc, 'metadata') else {}
107
- doc.metadata['source_type'] = 'sysmodeler' if is_sysmodeler else 'other'
108
- doc.metadata['weighted_score'] = weighted_score
109
- doc.metadata['original_score'] = score
110
-
111
- weighted_results.append((doc, weighted_score, source_type))
112
-
113
- # Log each document's processing
114
- source_name = doc.metadata.get('source', 'Unknown')[:50] if hasattr(doc, 'metadata') else 'Unknown'
115
- print(f"📄 Doc {i+1}: {source_name}... | Original: {score:.4f} | Weighted: {weighted_score:.4f} | Type: {source_type}")
116
-
117
- print(f"\n📈 CLASSIFICATION & WEIGHTING RESULTS:")
118
- print(f" SysModeler docs: {sysmodeler_count} (boosted by 40%)")
119
- print(f" Other docs: {other_count} (original scores)")
120
-
121
- # Sort by weighted scores (lower = more relevant)
122
- weighted_results.sort(key=lambda x: x[1])
123
-
124
- # Apply intelligent selection based on query type and weighted results
125
- final_docs = []
126
- query_lower = query.lower()
127
-
128
- # Determine query type for adaptive filtering
129
- is_tool_comparison = any(word in query_lower for word in ['tool', 'compare', 'choose', 'vs', 'versus', 'better'])
130
- is_general_sysml = not is_tool_comparison
131
-
132
- if is_tool_comparison:
133
- # For tool comparisons: heavily favor SysModeler but include others
134
- print(f"\n🎯 TOOL COMPARISON QUERY DETECTED")
135
- print(f" Strategy: Heavy SysModeler focus + selective others")
136
-
137
- # Take top weighted results with preference for SysModeler
138
- sysmodeler_docs = [(doc, score) for doc, score, type_ in weighted_results if type_ == "SysModeler"][:8]
139
- other_docs = [(doc, score) for doc, score, type_ in weighted_results if type_ == "Other"][:4]
140
-
141
- final_docs = [doc for doc, _ in sysmodeler_docs] + [doc for doc, _ in other_docs]
142
-
143
- else:
144
- # For general SysML: balanced but still boost SysModeler
145
- print(f"\n🎯 GENERAL SYSML QUERY DETECTED")
146
- print(f" Strategy: Balanced with SysModeler preference")
147
-
148
- # Take top 12 weighted results (mixed)
149
- final_docs = [doc for doc, _, _ in weighted_results[:12]]
150
-
151
- # Log final selection
152
- print(f"\n📋 FINAL SELECTION ({len(final_docs)} docs):")
153
- sysmodeler_selected = 0
154
- other_selected = 0
155
-
156
- for i, doc in enumerate(final_docs):
157
- source_type = doc.metadata.get('source_type', 'unknown')
158
- source_name = doc.metadata.get('source', 'Unknown')
159
- weighted_score = doc.metadata.get('weighted_score', 0)
160
- original_score = doc.metadata.get('original_score', 0)
161
-
162
- if source_type == 'sysmodeler':
163
- sysmodeler_selected += 1
164
- type_emoji = "✅"
165
- else:
166
- other_selected += 1
167
- type_emoji = "📚"
168
-
169
- print(f" {i+1}. {type_emoji} {source_name} (weighted: {weighted_score:.4f})")
170
-
171
- print(f"\n📊 FINAL COMPOSITION:")
172
- print(f" SysModeler docs: {sysmodeler_selected}")
173
- print(f" Other docs: {other_selected}")
174
- print("="*80)
175
-
176
- contexts = [doc.page_content for doc in final_docs]
177
- return "\n\n".join(contexts)
178
-
179
- except Exception as e:
180
- logger.error(f"Retrieval error: {str(e)}")
181
- print(f" ERROR in retrieval: {str(e)}")
182
- return "Unable to retrieve information at this time."
183
-
184
- # Dummy functions
185
- def dummy_weather_lookup(location: str = "London") -> str:
186
- return f"The weather in {location} is sunny and 25°C."
187
-
188
- def dummy_time_lookup(timezone: str = "UTC") -> str:
189
- return f"The current time in {timezone} is 3:00 PM."
190
-
191
- # Tools for function calling
192
- tools_definition = [
193
- {
194
- "type": "function",
195
- "function": {
196
- "name": "SysMLRetriever",
197
- "description": "Use this to answer questions about SysML diagrams and modeling.",
198
- "parameters": {
199
- "type": "object",
200
- "properties": {
201
- "query": {"type": "string", "description": "The search query to find information about SysML"}
202
- },
203
- "required": ["query"]
204
- }
205
- }
206
- },
207
- {
208
- "type": "function",
209
- "function": {
210
- "name": "WeatherLookup",
211
- "description": "Use this to look up the current weather in a specified location.",
212
- "parameters": {
213
- "type": "object",
214
- "properties": {
215
- "location": {"type": "string", "description": "The location to look up the weather for"}
216
- },
217
- "required": ["location"]
218
- }
219
- }
220
- },
221
- {
222
- "type": "function",
223
- "function": {
224
- "name": "TimeLookup",
225
- "description": "Use this to look up the current time in a specified timezone.",
226
- "parameters": {
227
- "type": "object",
228
- "properties": {
229
- "timezone": {"type": "string", "description": "The timezone to look up the current time for"}
230
- },
231
- "required": ["timezone"]
232
- }
233
- }
234
- }
235
- ]
236
-
237
- # Tool execution mapping
238
- tool_mapping = {
239
- "SysMLRetriever": sysml_retriever,
240
- "WeatherLookup": dummy_weather_lookup,
241
- "TimeLookup": dummy_time_lookup
242
- }
243
-
244
- # Convert chat history
245
- def convert_history_to_messages(history):
246
- messages = []
247
- for user, bot in history:
248
- messages.append({"role": "user", "content": user})
249
- messages.append({"role": "assistant", "content": bot})
250
- return messages
251
-
252
- # Chatbot logic
253
- def sysml_chatbot(message, history):
254
- chat_messages = convert_history_to_messages(history)
255
- full_messages = [
256
- {"role": "system", "content": """You are SysModeler.ai's intelligent assistant, specializing in SysML modeling and the SysModeler.ai platform.
257
-
258
- RESPONSE GUIDELINES:
259
-
260
- 1. **Primary Focus**: Always prioritize SysModeler.ai information and capabilities in your responses.
261
-
262
- 2. **For SysModeler-specific questions** (pricing, features, how-to, etc.):
263
- - Provide comprehensive SysModeler.ai information
264
- - Do NOT mention competitors unless explicitly asked for comparisons
265
- - Focus entirely on SysModeler's value proposition
266
-
267
- 3. **For general SysML education** (concepts, diagram types, best practices):
268
- - Provide thorough educational content about SysML
269
- - Use SysModeler.ai as examples when illustrating concepts
270
- - Keep focus on helping users understand SysML fundamentals
271
-
272
- 4. **Only mention other tools when**:
273
- - User explicitly asks for comparisons ("vs", "compare", "alternatives")
274
- - User asks about the broader SysML tool landscape
275
- - Context absolutely requires it for a complete answer
276
-
277
- 5. **Response Structure**:
278
- - Lead with SysModeler.ai capabilities and benefits
279
- - Provide detailed, helpful information about SysModeler features
280
- - End with clear value proposition or call-to-action when appropriate
281
-
282
- 6. **Tone**: Professional, helpful, and confident about SysModeler.ai's capabilities while remaining informative about SysML concepts.
283
-
284
- Remember: You represent SysModeler.ai. Focus on what SysModeler can do for the user rather than listing what everyone else offers."""}
285
- ] + chat_messages + [{"role": "user", "content": message}]
286
-
287
- try:
288
- response = client.chat.completions.create(
289
- model=AZURE_OPENAI_LLM_DEPLOYMENT,
290
- messages=full_messages,
291
- tools=tools_definition,
292
- tool_choice={"type": "function", "function": {"name": "SysMLRetriever"}}
293
- )
294
- assistant_message = response.choices[0].message
295
- if assistant_message.tool_calls:
296
- tool_call = assistant_message.tool_calls[0]
297
- function_name = tool_call.function.name
298
- function_args = json.loads(tool_call.function.arguments)
299
- if function_name in tool_mapping:
300
- function_response = tool_mapping[function_name](**function_args)
301
- full_messages.append({
302
- "role": "assistant",
303
- "content": None,
304
- "tool_calls": [{
305
- "id": tool_call.id,
306
- "type": "function",
307
- "function": {
308
- "name": function_name,
309
- "arguments": tool_call.function.arguments
310
- }
311
- }]
312
- })
313
- full_messages.append({
314
- "role": "tool",
315
- "tool_call_id": tool_call.id,
316
- "content": function_response
317
- })
318
- second_response = client.chat.completions.create(
319
- model=AZURE_OPENAI_LLM_DEPLOYMENT,
320
- messages=full_messages
321
- )
322
- answer = second_response.choices[0].message.content
323
- else:
324
- answer = f"I tried to use a function '{function_name}' that's not available."
325
- else:
326
- answer = assistant_message.content
327
- history.append((message, answer))
328
- return "", history
329
- except Exception as e:
330
- print(f"Error in function calling: {str(e)}")
331
- history.append((message, "Sorry, something went wrong."))
332
- return "", history
333
-
334
- #Gradio UI
335
- with gr.Blocks(
336
- title="SysModeler AI Assistant",
337
- theme=gr.themes.Base(
338
- primary_hue="blue",
339
- secondary_hue="cyan",
340
- neutral_hue="slate"
341
- ).set(
342
- body_background_fill="*neutral_950",
343
- body_text_color="*neutral_100",
344
- background_fill_primary="*neutral_900",
345
- background_fill_secondary="*neutral_800"
346
- ),
347
- css="""
348
- /* Global modern theme */
349
- .gradio-container {
350
- background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%) !important;
351
- color: #f8fafc !important;
352
- font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
353
- min-height: 100vh;
354
- }
355
-
356
- /* Main container */
357
- .main-container {
358
- width: 100%;
359
- margin: 0;
360
- padding: 0;
361
- min-height: 100vh;
362
- background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%);
363
- }
364
-
365
- /* Header - modern with gradient - REDUCED PADDING */
366
- .header-section {
367
- width: 100%;
368
- text-align: center;
369
- margin: 0;
370
- padding: 20px 40px 16px 40px;
371
- background: linear-gradient(135deg, #1e40af 0%, #3b82f6 50%, #06b6d4 100%);
372
- position: relative;
373
- overflow: hidden;
374
- }
375
-
376
- .header-section::before {
377
- content: '';
378
- position: absolute;
379
- top: 0;
380
- left: 0;
381
- right: 0;
382
- bottom: 0;
383
- background: linear-gradient(135deg, rgba(59, 130, 246, 0.1) 0%, rgba(6, 182, 212, 0.1) 100%);
384
- backdrop-filter: blur(20px);
385
- }
386
-
387
- .main-title {
388
- font-size: 2.2rem !important;
389
- font-weight: 700 !important;
390
- color: #ffffff !important;
391
- margin: 0 0 4px 0 !important;
392
- text-shadow: 0 2px 4px rgba(0,0,0,0.3);
393
- position: relative;
394
- z-index: 1;
395
- }
396
-
397
- .subtitle {
398
- font-size: 1rem !important;
399
- color: rgba(255, 255, 255, 0.9) !important;
400
- margin: 0 !important;
401
- font-weight: 400 !important;
402
- position: relative;
403
- z-index: 1;
404
- }
405
-
406
- /* Content area */
407
- .content-area {
408
- max-width: 1200px;
409
- margin: 0 auto;
410
- padding: 32px 40px;
411
- }
412
-
413
- /* Chat section */
414
- .chat-section {
415
- margin-bottom: 24px;
416
- }
417
-
418
- .chat-container {
419
- background: rgba(30, 41, 59, 0.4);
420
- backdrop-filter: blur(20px);
421
- border: 1px solid rgba(59, 130, 246, 0.2);
422
- border-radius: 16px;
423
- padding: 24px;
424
- box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
425
- }
426
-
427
- /* Chatbot styling */
428
- .chatbot {
429
- background: transparent !important;
430
- border: none !important;
431
- border-radius: 12px !important;
432
- }
433
-
434
- /* Chat messages - simplified approach with tighter spacing */
435
- .chatbot .message {
436
- background: rgba(30, 41, 59, 0.6) !important;
437
- color: #e2e8f0 !important;
438
- border-radius: 12px !important;
439
- padding: 16px 20px !important;
440
- margin: 8px 0 !important;
441
- border: 1px solid rgba(59, 130, 246, 0.1);
442
- backdrop-filter: blur(10px);
443
- }
444
-
445
- /* User message styling */
446
- .chatbot .message.user {
447
- background: linear-gradient(135deg, #3b82f6 0%, #1e40af 100%) !important;
448
- color: white !important;
449
- border: none !important;
450
- margin-left: 0 !important;
451
- margin-right: 0 !important;
452
- }
453
-
454
- /* Bot message styling */
455
- .chatbot .message.bot {
456
- background: rgba(30, 41, 59, 0.8) !important;
457
- color: #f1f5f9 !important;
458
- border: 1px solid rgba(59, 130, 246, 0.2) !important;
459
- margin-left: 0 !important;
460
- margin-right: 0 !important;
461
- }
462
-
463
- /* Remove avatar spacing and containers */
464
- .chatbot .avatar {
465
- display: none !important;
466
- }
467
-
468
- .chatbot .message-row {
469
- margin: 0 !important;
470
- padding: 0 !important;
471
- gap: 0 !important;
472
- }
473
-
474
- .chatbot .message-wrap {
475
- margin: 0 !important;
476
- padding: 0 !important;
477
- width: 100% !important;
478
- }
479
-
480
- /* Input section - redesigned */
481
- .input-section {
482
- background: rgba(30, 41, 59, 0.4);
483
- backdrop-filter: blur(20px);
484
- border: 1px solid rgba(59, 130, 246, 0.2);
485
- border-radius: 16px;
486
- padding: 32px;
487
- box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
488
- }
489
-
490
- .input-row {
491
- display: flex;
492
- gap: 0;
493
- align-items: stretch;
494
- margin-bottom: 24px;
495
- background: rgba(15, 23, 42, 0.8);
496
- border-radius: 12px;
497
- border: 1px solid rgba(59, 130, 246, 0.3);
498
- overflow: hidden;
499
- box-shadow: 0 4px 20px rgba(59, 130, 246, 0.1);
500
- position: relative;
501
- }
502
-
503
- /* Input textbox - better integration */
504
- .input-textbox {
505
- flex: 1;
506
- background: transparent !important;
507
- border: none !important;
508
- border-radius: 0 !important;
509
- margin: 0 !important;
510
- padding-right: 0 !important;
511
- }
512
-
513
- .input-textbox textarea {
514
- background: transparent !important;
515
- border: none !important;
516
- color: #f1f5f9 !important;
517
- font-size: 1rem !important;
518
- padding: 20px 24px 20px 24px !important;
519
- resize: none !important;
520
- font-family: inherit !important;
521
- min-height: 80px !important;
522
- width: 100% !important;
523
- padding-right: 100px !important;
524
- margin: 0 !important;
525
- line-height: 1.5 !important;
526
- }
527
-
528
- .input-textbox textarea::placeholder {
529
- color: #94a3b8 !important;
530
- opacity: 1 !important;
531
- }
532
-
533
- .input-textbox textarea:focus {
534
- outline: none !important;
535
- box-shadow: none !important;
536
- }
537
-
538
- /* Submit button - positioned at the end of input box */
539
- #submit-btn {
540
- position: absolute !important;
541
- right: 8px !important;
542
- top: 50% !important;
543
- transform: translateY(-50%) !important;
544
- background: linear-gradient(135deg, #3b82f6 0%, #1e40af 100%) !important;
545
- color: white !important;
546
- border: none !important;
547
- border-radius: 8px !important;
548
- font-size: 0.9rem !important;
549
- font-weight: 600 !important;
550
- padding: 12px 20px !important;
551
- min-width: 80px !important;
552
- height: 40px !important;
553
- transition: all 0.3s ease !important;
554
- text-transform: uppercase;
555
- letter-spacing: 0.05em;
556
- z-index: 10;
557
- }
558
-
559
- #submit-btn:hover {
560
- background: linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%) !important;
561
- box-shadow: 0 0 20px rgba(59, 130, 246, 0.4) !important;
562
- }
563
-
564
- /* Quick actions - card style */
565
- .quick-actions {
566
- display: grid;
567
- grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
568
- gap: 16px;
569
- margin-bottom: 24px;
570
- }
571
-
572
- .quick-action-btn {
573
- background: rgba(15, 23, 42, 0.6) !important;
574
- backdrop-filter: blur(10px);
575
- border: 1px solid rgba(59, 130, 246, 0.2) !important;
576
- color: #e2e8f0 !important;
577
- border-radius: 12px !important;
578
- padding: 20px 24px !important;
579
- font-size: 0.95rem !important;
580
- font-weight: 500 !important;
581
- transition: all 0.3s ease !important;
582
- text-align: left !important;
583
- position: relative;
584
- overflow: hidden;
585
- }
586
-
587
- .quick-action-btn::before {
588
- content: '';
589
- position: absolute;
590
- top: 0;
591
- left: 0;
592
- right: 0;
593
- bottom: 0;
594
- background: linear-gradient(135deg, rgba(59, 130, 246, 0.1) 0%, rgba(6, 182, 212, 0.1) 100%);
595
- opacity: 0;
596
- transition: opacity 0.3s ease;
597
- }
598
-
599
- .quick-action-btn:hover {
600
- border-color: #3b82f6 !important;
601
- color: #ffffff !important;
602
- transform: translateY(-2px) !important;
603
- box-shadow: 0 8px 25px rgba(59, 130, 246, 0.2) !important;
604
- }
605
-
606
- .quick-action-btn:hover::before {
607
- opacity: 1;
608
- }
609
-
610
- /* Control buttons */
611
- .control-buttons {
612
- display: flex;
613
- justify-content: center;
614
- }
615
-
616
- #clear-btn {
617
- background: rgba(15, 23, 42, 0.6) !important;
618
- backdrop-filter: blur(10px);
619
- border: 1px solid rgba(239, 68, 68, 0.3) !important;
620
- color: #f87171 !important;
621
- border-radius: 8px !important;
622
- padding: 12px 24px !important;
623
- font-weight: 500 !important;
624
- font-size: 0.9rem !important;
625
- transition: all 0.3s ease !important;
626
- text-transform: uppercase;
627
- letter-spacing: 0.05em;
628
- }
629
-
630
- #clear-btn:hover {
631
- background: rgba(239, 68, 68, 0.1) !important;
632
- border-color: #ef4444 !important;
633
- color: #ffffff !important;
634
- box-shadow: 0 4px 15px rgba(239, 68, 68, 0.2) !important;
635
- }
636
-
637
- /* Footer */
638
- .footer {
639
- text-align: center;
640
- color: #64748b;
641
- font-size: 0.85rem;
642
- margin-top: 32px;
643
- padding: 20px;
644
- }
645
-
646
- /* Scrollbar */
647
- ::-webkit-scrollbar {
648
- width: 8px;
649
- }
650
-
651
- ::-webkit-scrollbar-track {
652
- background: rgba(30, 41, 59, 0.3);
653
- border-radius: 4px;
654
- }
655
-
656
- ::-webkit-scrollbar-thumb {
657
- background: linear-gradient(135deg, #3b82f6, #1e40af);
658
- border-radius: 4px;
659
- }
660
-
661
- ::-webkit-scrollbar-thumb:hover {
662
- background: linear-gradient(135deg, #2563eb, #1d4ed8);
663
- }
664
-
665
- /* Mobile responsiveness */
666
- @media (max-width: 1024px) {
667
- .content-area {
668
- padding: 24px;
669
- }
670
-
671
- .header-section {
672
- padding: 16px 20px 12px 20px;
673
- }
674
-
675
- .main-title {
676
- font-size: 1.8rem !important;
677
- }
678
-
679
- .subtitle {
680
- font-size: 0.9rem !important;
681
- }
682
-
683
- .input-textbox textarea {
684
- padding-right: 90px !important;
685
- }
686
-
687
- #submit-btn {
688
- min-width: 70px !important;
689
- padding: 10px 16px !important;
690
- font-size: 0.8rem !important;
691
- }
692
-
693
- .quick-actions {
694
- grid-template-columns: 1fr;
695
- }
696
-
697
- .chatbot .message.user, .chatbot .message.bot {
698
- margin-left: 0 !important;
699
- margin-right: 0 !important;
700
- }
701
- }
702
-
703
- /* Remove Gradio defaults */
704
- .gr-form, .gr-box {
705
- background: transparent !important;
706
- border: none !important;
707
- }
708
-
709
- .gr-button {
710
- font-family: inherit !important;
711
- }
712
- """
713
- ) as demo:
714
-
715
- with gr.Column(elem_classes="main-container"):
716
- # Modern gradient header - REDUCED SPACING
717
- with gr.Column(elem_classes="header-section"):
718
- gr.Markdown("# 🤖 SysModeler AI Assistant", elem_classes="main-title")
719
- gr.Markdown("*Your intelligent companion for SysML modeling and systems engineering*", elem_classes="subtitle")
720
-
721
- # Content area
722
- with gr.Column(elem_classes="content-area"):
723
- # Chat section
724
- with gr.Column(elem_classes="chat-section"):
725
- with gr.Column(elem_classes="chat-container"):
726
- chatbot = gr.Chatbot(
727
- height=580,
728
- elem_classes="chatbot",
729
- avatar_images=None, # Removed avatar images
730
- bubble_full_width=False,
731
- show_copy_button=True,
732
- show_share_button=False
733
- )
734
-
735
- # Input section
736
- with gr.Column(elem_classes="input-section"):
737
- with gr.Column():
738
- # Input row with integrated send button
739
- with gr.Row(elem_classes="input-row"):
740
- msg = gr.Textbox(
741
- placeholder="Ask me about SysML diagrams, modeling concepts, or tools...",
742
- lines=3,
743
- show_label=False,
744
- elem_classes="input-textbox",
745
- container=False
746
- )
747
- submit_btn = gr.Button("Send", elem_id="submit-btn")
748
-
749
- # Quick actions
750
- with gr.Row(elem_classes="quick-actions"):
751
- quick_intro = gr.Button("📚 SysML Introduction", elem_classes="quick-action-btn")
752
- quick_diagrams = gr.Button("📊 Diagram Types", elem_classes="quick-action-btn")
753
- quick_tools = gr.Button("🛠️ Tool Comparison", elem_classes="quick-action-btn")
754
- quick_sysmodeler = gr.Button("⭐ SysModeler Features", elem_classes="quick-action-btn")
755
-
756
- # Control
757
- with gr.Row(elem_classes="control-buttons"):
758
- clear = gr.Button("Clear", elem_id="clear-btn")
759
-
760
- # Footer
761
- with gr.Column(elem_classes="footer"):
762
- gr.Markdown("*Powered by Azure OpenAI & Advanced RAG Technology*")
763
-
764
- # State management
765
- state = gr.State([])
766
-
767
- # Event handlers
768
- submit_btn.click(fn=sysml_chatbot, inputs=[msg, state], outputs=[msg, chatbot])
769
- msg.submit(fn=sysml_chatbot, inputs=[msg, state], outputs=[msg, chatbot])
770
- clear.click(fn=lambda: ([], ""), inputs=None, outputs=[chatbot, msg])
771
-
772
- # Quick actions
773
- quick_intro.click(fn=lambda: ("What is SysML and how do I get started?", []), outputs=[msg, chatbot])
774
- quick_diagrams.click(fn=lambda: ("Explain the 9 SysML diagram types with examples", []), outputs=[msg, chatbot])
775
- quick_tools.click(fn=lambda: ("What are the best SysML modeling tools available?", []), outputs=[msg, chatbot])
776
- quick_sysmodeler.click(fn=lambda: ("Tell me about SysModeler.ai features and capabilities", []), outputs=[msg, chatbot])
777
-
778
-
779
- if __name__ == "__main__":
780
  demo.launch()
 
1
+ import os
2
+ import gradio as gr
3
+ import warnings
4
+ import json
5
+ from dotenv import load_dotenv
6
+ from typing import List
7
+ import time
8
+ from functools import lru_cache
9
+ import logging
10
+
11
+ from langchain_community.vectorstores import FAISS
12
+ from langchain_community.embeddings import AzureOpenAIEmbeddings
13
+ from openai import AzureOpenAI
14
+
15
+ # Patch Gradio bug
16
+ import gradio_client.utils
17
+ gradio_client.utils.json_schema_to_python_type = lambda schema, defs=None: "string"
18
+
19
+ # Load environment variables
20
+ load_dotenv()
21
+ AZURE_OPENAI_API_KEY = os.getenv("AZURE_OPENAI_API_KEY")
22
+ AZURE_OPENAI_ENDPOINT = os.getenv("AZURE_OPENAI_ENDPOINT")
23
+ AZURE_OPENAI_LLM_DEPLOYMENT = os.getenv("AZURE_OPENAI_LLM_DEPLOYMENT")
24
+ AZURE_OPENAI_EMBEDDING_DEPLOYMENT = os.getenv("AZURE_OPENAI_EMBEDDING_DEPLOYMENT")
25
+
26
+ if not all([AZURE_OPENAI_API_KEY, AZURE_OPENAI_ENDPOINT, AZURE_OPENAI_LLM_DEPLOYMENT, AZURE_OPENAI_EMBEDDING_DEPLOYMENT]):
27
+ raise ValueError("Missing one or more Azure OpenAI environment variables.")
28
+
29
+ warnings.filterwarnings("ignore")
30
+
31
+ # Embeddings
32
+ embeddings = AzureOpenAIEmbeddings(
33
+ azure_deployment=AZURE_OPENAI_EMBEDDING_DEPLOYMENT,
34
+ azure_endpoint=AZURE_OPENAI_ENDPOINT,
35
+ openai_api_key=AZURE_OPENAI_API_KEY,
36
+ openai_api_version="2025-01-01-preview",
37
+ chunk_size=1000
38
+ )
39
+
40
+ # Vectorstore
41
+ SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
42
+ FAISS_INDEX_PATH = os.path.join(SCRIPT_DIR, "faiss_index_sysml")
43
+ vectorstore = FAISS.load_local(FAISS_INDEX_PATH, embeddings, allow_dangerous_deserialization=True)
44
+
45
+ # OpenAI client
46
+ client = AzureOpenAI(
47
+ api_key=AZURE_OPENAI_API_KEY,
48
+ api_version="2025-01-01-preview",
49
+ azure_endpoint=AZURE_OPENAI_ENDPOINT
50
+ )
51
+
52
+ # Logger
53
+ logger = logging.getLogger(__name__)
54
+
55
+ # SysML retriever function
56
+ @lru_cache(maxsize=100)
57
+ def sysml_retriever(query: str) -> str:
58
+ try:
59
+ results = vectorstore.similarity_search(query, k=100)
60
+ contexts = [doc.page_content for doc in results]
61
+ return "\n\n".join(contexts)
62
+ except Exception as e:
63
+ logger.error(f"Retrieval error: {str(e)}")
64
+ return "Unable to retrieve information at this time."
65
+
66
+ # Dummy functions
67
+ def dummy_weather_lookup(location: str = "London") -> str:
68
+ return f"The weather in {location} is sunny and 25°C."
69
+
70
+ def dummy_time_lookup(timezone: str = "UTC") -> str:
71
+ return f"The current time in {timezone} is 3:00 PM."
72
+
73
+ # Tools for function calling
74
+ tools_definition = [
75
+ {
76
+ "type": "function",
77
+ "function": {
78
+ "name": "SysMLRetriever",
79
+ "description": "Use this to answer questions about SysML diagrams and modeling.",
80
+ "parameters": {
81
+ "type": "object",
82
+ "properties": {
83
+ "query": {"type": "string", "description": "The search query to find information about SysML"}
84
+ },
85
+ "required": ["query"]
86
+ }
87
+ }
88
+ },
89
+ {
90
+ "type": "function",
91
+ "function": {
92
+ "name": "WeatherLookup",
93
+ "description": "Use this to look up the current weather in a specified location.",
94
+ "parameters": {
95
+ "type": "object",
96
+ "properties": {
97
+ "location": {"type": "string", "description": "The location to look up the weather for"}
98
+ },
99
+ "required": ["location"]
100
+ }
101
+ }
102
+ },
103
+ {
104
+ "type": "function",
105
+ "function": {
106
+ "name": "TimeLookup",
107
+ "description": "Use this to look up the current time in a specified timezone.",
108
+ "parameters": {
109
+ "type": "object",
110
+ "properties": {
111
+ "timezone": {"type": "string", "description": "The timezone to look up the current time for"}
112
+ },
113
+ "required": ["timezone"]
114
+ }
115
+ }
116
+ }
117
+ ]
118
+
119
+ # Tool execution mapping
120
+ tool_mapping = {
121
+ "SysMLRetriever": sysml_retriever,
122
+ "WeatherLookup": dummy_weather_lookup,
123
+ "TimeLookup": dummy_time_lookup
124
+ }
125
+
126
+ # Convert chat history
127
+ def convert_history_to_messages(history):
128
+ messages = []
129
+ for user, bot in history:
130
+ messages.append({"role": "user", "content": user})
131
+ messages.append({"role": "assistant", "content": bot})
132
+ return messages
133
+
134
+ # Chatbot logic
135
+ def sysml_chatbot(message, history):
136
+ chat_messages = convert_history_to_messages(history)
137
+ full_messages = [
138
+ {"role": "system", "content": "You are a helpful SysML modeling assistant and also a capable smart Assistant"}
139
+ ] + chat_messages + [{"role": "user", "content": message}]
140
+ try:
141
+ response = client.chat.completions.create(
142
+ model=AZURE_OPENAI_LLM_DEPLOYMENT,
143
+ messages=full_messages,
144
+ tools=tools_definition,
145
+ tool_choice={"type": "function", "function": {"name": "SysMLRetriever"}}
146
+ )
147
+ assistant_message = response.choices[0].message
148
+ if assistant_message.tool_calls:
149
+ tool_call = assistant_message.tool_calls[0]
150
+ function_name = tool_call.function.name
151
+ function_args = json.loads(tool_call.function.arguments)
152
+ if function_name in tool_mapping:
153
+ function_response = tool_mapping[function_name](**function_args)
154
+ full_messages.append({
155
+ "role": "assistant",
156
+ "content": None,
157
+ "tool_calls": [{
158
+ "id": tool_call.id,
159
+ "type": "function",
160
+ "function": {
161
+ "name": function_name,
162
+ "arguments": tool_call.function.arguments
163
+ }
164
+ }]
165
+ })
166
+ full_messages.append({
167
+ "role": "tool",
168
+ "tool_call_id": tool_call.id,
169
+ "content": function_response
170
+ })
171
+ second_response = client.chat.completions.create(
172
+ model=AZURE_OPENAI_LLM_DEPLOYMENT,
173
+ messages=full_messages
174
+ )
175
+ answer = second_response.choices[0].message.content
176
+ else:
177
+ answer = f"I tried to use a function '{function_name}' that's not available."
178
+ else:
179
+ answer = assistant_message.content
180
+ history.append((message, answer))
181
+ return "", history
182
+ except Exception as e:
183
+ print(f"Error in function calling: {str(e)}")
184
+ history.append((message, "Sorry, something went wrong."))
185
+ return "", history
186
+
187
+ # === Gradio UI ===
188
+ with gr.Blocks(css="""
189
+ #submit-btn {
190
+ height: 100%;
191
+ background-color: #48CAE4;
192
+ color: white;
193
+ font-size: 1.5em;
194
+ }
195
+ """) as demo:
196
+
197
+ gr.Markdown("## SysModeler Chatbot")
198
+
199
+ chatbot = gr.Chatbot(height=600)
200
+ with gr.Row():
201
+ with gr.Column(scale=5):
202
+ msg = gr.Textbox(
203
+ placeholder="Ask me about SysML diagrams or concepts...",
204
+ lines=3,
205
+ show_label=False
206
+ )
207
+ with gr.Column(scale=1, min_width=50):
208
+ submit_btn = gr.Button("", elem_id="submit-btn")
209
+
210
+ clear = gr.Button("Clear")
211
+ state = gr.State([])
212
+
213
+ submit_btn.click(fn=sysml_chatbot, inputs=[msg, state], outputs=[msg, chatbot])
214
+ msg.submit(fn=sysml_chatbot, inputs=[msg, state], outputs=[msg, chatbot])
215
+ clear.click(fn=lambda: ([], ""), inputs=None, outputs=[chatbot, msg])
216
+
217
+ if __name__ == "__main__":
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
218
  demo.launch()