SysModeler's picture
Update app.py
3659290 verified
raw
history blame
10.5 kB
import os
import gradio as gr
import warnings
import json
from dotenv import load_dotenv
from typing import Dict, Any, List, Optional
import time
from functools import lru_cache
import logging
from langchain.agents import Tool, AgentExecutor
from langchain.tools.retriever import create_retriever_tool
from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_community.vectorstores import FAISS
from langchain_community.embeddings import AzureOpenAIEmbeddings
from langchain_core.messages import HumanMessage, AIMessage, SystemMessage
from openai import AzureOpenAI
# Patch Gradio bug
import gradio_client.utils
gradio_client.utils.json_schema_to_python_type = lambda schema, defs=None: "string"
# Load environment variables
load_dotenv()
AZURE_OPENAI_API_KEY = os.getenv("AZURE_OPENAI_API_KEY")
AZURE_OPENAI_ENDPOINT = os.getenv("AZURE_OPENAI_ENDPOINT")
AZURE_OPENAI_LLM_DEPLOYMENT = os.getenv("AZURE_OPENAI_LLM_DEPLOYMENT")
AZURE_OPENAI_EMBEDDING_DEPLOYMENT = os.getenv("AZURE_OPENAI_EMBEDDING_DEPLOYMENT")
if not all([AZURE_OPENAI_API_KEY, AZURE_OPENAI_ENDPOINT, AZURE_OPENAI_LLM_DEPLOYMENT, AZURE_OPENAI_EMBEDDING_DEPLOYMENT]):
raise ValueError("Missing one or more Azure OpenAI environment variables.")
warnings.filterwarnings("ignore")
# Embeddings for retriever
embeddings = AzureOpenAIEmbeddings(
azure_deployment=AZURE_OPENAI_EMBEDDING_DEPLOYMENT,
azure_endpoint=AZURE_OPENAI_ENDPOINT,
openai_api_key=AZURE_OPENAI_API_KEY,
openai_api_version="2025-01-01-preview",
chunk_size=1000
)
# Get the directory where this script is located
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
# Build the absolute path to the faiss_index_sysml directory relative to this script
FAISS_INDEX_PATH = os.path.join(SCRIPT_DIR, "faiss_index_sysml")
# Load FAISS vectorstore
vectorstore = FAISS.load_local(FAISS_INDEX_PATH, embeddings, allow_dangerous_deserialization=True)
# Initialize Azure OpenAI client directly
client = AzureOpenAI(
api_key=AZURE_OPENAI_API_KEY,
api_version="2025-01-01-preview",
azure_endpoint=AZURE_OPENAI_ENDPOINT
)
logger = logging.getLogger(__name__)
# SysML retriever function
@lru_cache(maxsize=100)
def sysml_retriever(query: str) -> str:
start_time = time.time()
try:
results = vectorstore.similarity_search(query, k=100)
contexts = [doc.page_content for doc in results]
response = "\n\n".join(contexts)
# Log performance metrics
duration = time.time() - start_time
print(f"Retrieval completed in {duration:.2f}s for query: {query[:50]}...")
return response
except Exception as e:
logger.error(f"Retrieval error: {str(e)}")
return "Unable to retrieve information at this time."
# sysml_retriever = create_retriever_tool(
# retriever=vectorstore.as_retriever(),
# name="SysMLRetriever",
# description="Use this to answer questions about SysML diagrams and modeling."
# )
# Dummy functions
def dummy_weather_lookup(location: str = "London") -> str:
return f"The weather in {location} is sunny and 25°C."
def dummy_time_lookup(timezone: str = "UTC") -> str:
return f"The current time in {timezone} is 3:00 PM."
# Tools definition for OpenAI function calling
tools_definition = [
{
"type": "function",
"function": {
"name": "SysMLRetriever",
"description": "Use this to answer questions about SysML diagrams and modeling.",
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "The search query to find information about SysML"
}
},
"required": ["query"]
}
}
},
{
"type": "function",
"function": {
"name": "WeatherLookup",
"description": "Use this to look up the current weather in a specified location.",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "The location to look up the weather for"
}
},
"required": ["location"]
}
},
},
{
"type": "function",
"function": {
"name": "TimeLookup",
"description": "Use this to look up the current time in a specified timezone.",
"parameters": {
"type": "object",
"properties": {
"timezone": {
"type": "string",
"description": "The timezone to look up the current time for"
}
},
"required": ["timezone"]
}
}
}
]
# Tool execution mapping
tool_mapping = {
"SysMLRetriever": sysml_retriever,
"WeatherLookup": dummy_weather_lookup,
"TimeLookup": dummy_time_lookup
}
# Convert chat history
def convert_history_to_messages(history):
messages = []
for user, bot in history:
messages.append({"role": "user", "content": user})
messages.append({"role": "assistant", "content": bot})
return messages
history = []
# Main chatbot function with direct function calling
def sysml_chatbot(message, history):
# Convert history to messages format
chat_messages = convert_history_to_messages(history)
# Add system message at beginning
full_messages = [
{"role": "system", "content": "You are a helpful SysML modeling assistant and also a capable smart Assistant "}
]
full_messages.extend(chat_messages)
# Add current user message
full_messages.append({"role": "user", "content": message})
try:
# First call to get either a direct answer or a function call
response = client.chat.completions.create(
model=AZURE_OPENAI_LLM_DEPLOYMENT,
messages=full_messages,
tools=tools_definition,
tool_choice={"type": "function", "function": {"name": "SysMLRetriever"}}
)
assistant_message = response.choices[0].message
# Check if the model wants to call a function
if assistant_message.tool_calls:
# Get the function call details
tool_call = assistant_message.tool_calls[0]
function_name = tool_call.function.name
function_args = json.loads(tool_call.function.arguments)
print("Attempting function calling...")
# Execute the function
if function_name in tool_mapping:
function_response = tool_mapping[function_name](**function_args)
# Append the assistant's request and the function response to messages
full_messages.append({"role": "assistant", "content": None, "tool_calls": [
{"id": tool_call.id, "type": "function", "function": {"name": function_name, "arguments": tool_call.function.arguments}}
]})
full_messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": function_response
})
# Second call to get the final answer based on the function result
second_response = client.chat.completions.create(
model=AZURE_OPENAI_LLM_DEPLOYMENT,
messages=full_messages
)
answer = second_response.choices[0].message.content
print("Getting final response after function execution...")
#print(f"Function '{function_name}' executed successfully. Response: {answer}")
else:
answer = f"I tried to use a function '{function_name}' that's not available. Let me try again with general knowledge: SysML is a modeling language for systems engineering that helps visualize and analyze complex systems."
else:
# Model provided a direct answer
answer = assistant_message.content
history.append((message, answer))
return answer, history
except Exception as e:
print(f"Error in function calling: {str(e)}")
# Fallback to a direct response without function calling
try:
simple_messages = [
{"role": "system", "content": "You are a helpful SysML modeling assistant."}
]
simple_messages.extend(chat_messages)
simple_messages.append({"role": "user", "content": message})
fallback_response = client.chat.completions.create(
model=AZURE_OPENAI_LLM_DEPLOYMENT,
messages=simple_messages
)
answer = fallback_response.choices[0].message.content
except Exception as fallback_error:
print(f"Error in fallback: {str(fallback_error)}")
answer = "I'm having trouble accessing my tools right now. SysML is a modeling language used in systems engineering to visualize and analyze complex systems through various diagram types."
history.append((message, answer))
return answer, history
# Gradio UI
with gr.Blocks(css="""
#submit-btn {
height: 100%;
background-color: #48CAE4;
color: white;
font-size: 1.5em;
}
""") as demo:
gr.Markdown("## SysModeler Chatbot")
chatbot = gr.Chatbot(height=600)
with gr.Row():
with gr.Column(scale=5):
msg = gr.Textbox(
placeholder="Ask me about SysML diagrams or concepts...",
lines=3,
show_label=False
)
with gr.Column(scale=1, min_width=50):
submit_btn = gr.Button("➤", scale=1, elem_id="submit-btn")
clear = gr.Button("Clear")
state = gr.State(history)
submit_btn.click(fn=sysml_chatbot, inputs=[msg, state], outputs=[gr.Textbox.update(value=""), chatbot])
msg.submit(fn=sysml_chatbot, inputs=[msg, state], outputs=[gr.Textbox.update(value=""), chatbot])
clear.click(fn=lambda: ([], ""), inputs=None, outputs=[chatbot, msg])
if __name__ == "__main__":
demo.launch()