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

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +780 -218
app.py CHANGED
@@ -1,218 +1,780 @@
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()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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()