restructured
Browse files- app.py +76 -62
- chains/diagnoser_chain.py +14 -0
- chains/distractors_chain.py +11 -0
- config/chain_configs.py +18 -0
- config/llm_config.py +14 -0
- config/templates.py +25 -0
- utils/auth.py +13 -0
app.py
CHANGED
|
@@ -1,90 +1,104 @@
|
|
|
|
|
| 1 |
import gradio as gr
|
| 2 |
-
import openai
|
| 3 |
import os
|
|
|
|
|
|
|
| 4 |
|
| 5 |
-
#
|
| 6 |
-
|
| 7 |
-
openai.api_key = os.getenv("OPENAI_API_KEY") # Also store securely
|
| 8 |
|
| 9 |
-
|
| 10 |
|
| 11 |
|
| 12 |
-
|
| 13 |
-
# Define Functions for App Logic
|
| 14 |
-
# -------------------------------
|
| 15 |
-
|
| 16 |
-
def login(password):
|
| 17 |
"""
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
"""
|
| 22 |
-
|
| 23 |
-
#
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
"""
|
| 31 |
-
|
| 32 |
-
|
| 33 |
"""
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
messages=[
|
| 38 |
-
{
|
| 39 |
-
"role": "user",
|
| 40 |
-
"content": (
|
| 41 |
-
"What is wrong with this exercise? In other words, what about it should be improved? "
|
| 42 |
-
"What stands between it and a perfect exercise that couldn't be better? Don't give solutions yet, "
|
| 43 |
-
"only a diagnosis\n "
|
| 44 |
-
f"{user_query}"
|
| 45 |
-
),
|
| 46 |
-
}
|
| 47 |
-
],
|
| 48 |
-
)
|
| 49 |
-
return response.choices[0].message.content
|
| 50 |
|
| 51 |
-
|
|
|
|
| 52 |
"""
|
| 53 |
-
|
| 54 |
-
|
| 55 |
"""
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
)
|
| 61 |
-
return response.choices[0].message.content
|
| 62 |
|
| 63 |
# -------------------------------
|
| 64 |
# Build the Gradio Interface
|
| 65 |
# -------------------------------
|
| 66 |
-
|
| 67 |
with gr.Blocks() as demo:
|
| 68 |
# --- Login Page ---
|
| 69 |
with gr.Column(visible=True, elem_id="login_page") as login_container:
|
| 70 |
gr.Markdown("## 🔒 Please Login")
|
| 71 |
-
password_input = gr.Textbox(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 72 |
login_button = gr.Button("Login")
|
| 73 |
-
login_error = gr.Markdown(value="") #
|
| 74 |
|
| 75 |
# --- Main App (initially hidden) ---
|
| 76 |
with gr.Column(visible=False, elem_id="main_app") as app_container:
|
| 77 |
gr.Markdown("## Core Functionalities")
|
| 78 |
-
# Use gr.Tabs for persistent states between functionalities
|
| 79 |
with gr.Tabs():
|
| 80 |
with gr.TabItem("Diagnoser"):
|
| 81 |
gr.Markdown("### Diagnoser")
|
| 82 |
-
diagnoser_input = gr.Textbox(
|
|
|
|
|
|
|
|
|
|
| 83 |
diagnoser_button = gr.Button("Submit")
|
| 84 |
diagnoser_output = gr.Textbox(label="Response", interactive=False)
|
| 85 |
with gr.TabItem("Distractors brainstorm"):
|
| 86 |
gr.Markdown("### Distractors brainstorm")
|
| 87 |
-
distractors_input = gr.Textbox(
|
|
|
|
|
|
|
|
|
|
| 88 |
distractors_button = gr.Button("Submit")
|
| 89 |
distractors_output = gr.Textbox(label="Response", interactive=False)
|
| 90 |
|
|
@@ -92,26 +106,26 @@ with gr.Blocks() as demo:
|
|
| 92 |
# Set Up Interactions
|
| 93 |
# -------------------------------
|
| 94 |
|
| 95 |
-
#
|
| 96 |
login_button.click(
|
| 97 |
-
fn=
|
| 98 |
inputs=[password_input],
|
| 99 |
outputs=[login_container, app_container, login_error]
|
| 100 |
)
|
| 101 |
|
| 102 |
-
#
|
| 103 |
diagnoser_button.click(
|
| 104 |
-
fn=
|
| 105 |
inputs=[diagnoser_input],
|
| 106 |
outputs=[diagnoser_output]
|
| 107 |
)
|
| 108 |
|
| 109 |
-
#
|
| 110 |
distractors_button.click(
|
| 111 |
-
fn=
|
| 112 |
inputs=[distractors_input],
|
| 113 |
outputs=[distractors_output]
|
| 114 |
)
|
| 115 |
|
| 116 |
-
# Launch the app
|
| 117 |
-
demo.launch()
|
|
|
|
| 1 |
+
# app.py
|
| 2 |
import gradio as gr
|
|
|
|
| 3 |
import os
|
| 4 |
+
import asyncio
|
| 5 |
+
import logging
|
| 6 |
|
| 7 |
+
from utils.auth import login as auth_login # simple login function
|
| 8 |
+
from config.chain_configs import chain_configs # chain configurations dictionary
|
|
|
|
| 9 |
|
| 10 |
+
logger = logging.getLogger(__name__)
|
| 11 |
|
| 12 |
|
| 13 |
+
async def run_chain(chain_name: str, input_variables: dict):
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
"""
|
| 15 |
+
A generic async function to run a given structured chain.
|
| 16 |
+
Handles prompt formatting, invoking the LLM asynchronously, and returning results.
|
| 17 |
+
|
| 18 |
+
Args:
|
| 19 |
+
chain_name (str): The key for the desired chain in the chain_configs.
|
| 20 |
+
input_variables (dict): A dictionary of variables to format the chain's prompt.
|
| 21 |
+
|
| 22 |
+
Returns:
|
| 23 |
+
The result of invoking the chain's LLM with the formatted prompt.
|
| 24 |
"""
|
| 25 |
+
try:
|
| 26 |
+
# Resolve the chain configuration by its name.
|
| 27 |
+
chain_config = chain_configs.get(chain_name)
|
| 28 |
+
if not chain_config:
|
| 29 |
+
raise KeyError(f"Chain '{chain_name}' not found in the chain configuration.")
|
| 30 |
+
|
| 31 |
+
# Generate the prompt using the chain's template.
|
| 32 |
+
prompt_value = chain_config["template"].format_prompt(**input_variables)
|
| 33 |
+
|
| 34 |
+
# Invoke the chain's LLM asynchronously.
|
| 35 |
+
result = await chain_config["chain"].ainvoke(prompt_value.to_messages())
|
| 36 |
+
|
| 37 |
+
logger.info(f"Chain '{chain_name}' executed successfully.")
|
| 38 |
+
return result
|
| 39 |
+
|
| 40 |
+
except KeyError as e:
|
| 41 |
+
logger.error(f"Chain configuration error: {e}")
|
| 42 |
+
raise ValueError(f"Invalid chain structure: missing {e}")
|
| 43 |
+
|
| 44 |
+
except Exception as e:
|
| 45 |
+
logger.error(f"Error running chain '{chain_name}': {e}")
|
| 46 |
+
raise RuntimeError(f"Failed to execute chain: {e}")
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
async def run_diagnoser(user_query: str) -> str:
|
| 50 |
"""
|
| 51 |
+
Async function to run the diagnoser chain.
|
| 52 |
+
Prepares the input variables and awaits the chain's result.
|
| 53 |
"""
|
| 54 |
+
input_vars = {"user_query": user_query}
|
| 55 |
+
result = await run_chain("diagnoser", input_vars)
|
| 56 |
+
return result
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 57 |
|
| 58 |
+
|
| 59 |
+
async def run_distractors(user_query: str) -> str:
|
| 60 |
"""
|
| 61 |
+
Async function to run the distractors brainstorm chain.
|
| 62 |
+
Prepares the input variables and awaits the chain's result.
|
| 63 |
"""
|
| 64 |
+
input_vars = {"user_query": user_query}
|
| 65 |
+
result = await run_chain("distractors", input_vars)
|
| 66 |
+
return result
|
| 67 |
+
|
|
|
|
|
|
|
| 68 |
|
| 69 |
# -------------------------------
|
| 70 |
# Build the Gradio Interface
|
| 71 |
# -------------------------------
|
|
|
|
| 72 |
with gr.Blocks() as demo:
|
| 73 |
# --- Login Page ---
|
| 74 |
with gr.Column(visible=True, elem_id="login_page") as login_container:
|
| 75 |
gr.Markdown("## 🔒 Please Login")
|
| 76 |
+
password_input = gr.Textbox(
|
| 77 |
+
label="Enter Password",
|
| 78 |
+
type="password",
|
| 79 |
+
placeholder="Enter password to access the app"
|
| 80 |
+
)
|
| 81 |
login_button = gr.Button("Login")
|
| 82 |
+
login_error = gr.Markdown(value="") # For error messages
|
| 83 |
|
| 84 |
# --- Main App (initially hidden) ---
|
| 85 |
with gr.Column(visible=False, elem_id="main_app") as app_container:
|
| 86 |
gr.Markdown("## Core Functionalities")
|
|
|
|
| 87 |
with gr.Tabs():
|
| 88 |
with gr.TabItem("Diagnoser"):
|
| 89 |
gr.Markdown("### Diagnoser")
|
| 90 |
+
diagnoser_input = gr.Textbox(
|
| 91 |
+
label="Enter Diagnoser Query",
|
| 92 |
+
placeholder="Type your query here..."
|
| 93 |
+
)
|
| 94 |
diagnoser_button = gr.Button("Submit")
|
| 95 |
diagnoser_output = gr.Textbox(label="Response", interactive=False)
|
| 96 |
with gr.TabItem("Distractors brainstorm"):
|
| 97 |
gr.Markdown("### Distractors brainstorm")
|
| 98 |
+
distractors_input = gr.Textbox(
|
| 99 |
+
label="Enter Brainstorm Query",
|
| 100 |
+
placeholder="Type your query here..."
|
| 101 |
+
)
|
| 102 |
distractors_button = gr.Button("Submit")
|
| 103 |
distractors_output = gr.Textbox(label="Response", interactive=False)
|
| 104 |
|
|
|
|
| 106 |
# Set Up Interactions
|
| 107 |
# -------------------------------
|
| 108 |
|
| 109 |
+
# Login button: if the password is correct, hide the login container and reveal the main app.
|
| 110 |
login_button.click(
|
| 111 |
+
fn=auth_login,
|
| 112 |
inputs=[password_input],
|
| 113 |
outputs=[login_container, app_container, login_error]
|
| 114 |
)
|
| 115 |
|
| 116 |
+
# Run the Diagnoser chain asynchronously.
|
| 117 |
diagnoser_button.click(
|
| 118 |
+
fn=run_diagnoser,
|
| 119 |
inputs=[diagnoser_input],
|
| 120 |
outputs=[diagnoser_output]
|
| 121 |
)
|
| 122 |
|
| 123 |
+
# Run the Distractors brainstorm chain asynchronously.
|
| 124 |
distractors_button.click(
|
| 125 |
+
fn=run_distractors,
|
| 126 |
inputs=[distractors_input],
|
| 127 |
outputs=[distractors_output]
|
| 128 |
)
|
| 129 |
|
| 130 |
+
# Launch the Gradio app.
|
| 131 |
+
demo.launch()
|
chains/diagnoser_chain.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pydantic import BaseModel
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
class DiagnoserChain(BaseModel):
|
| 5 |
+
template: str # In practice, you might accept a ChatPromptTemplate
|
| 6 |
+
llm: any # Type the LLM appropriately (or use typing.Any for now)
|
| 7 |
+
|
| 8 |
+
def run(self, user_query: str) -> str:
|
| 9 |
+
# Here you would plug in your LangChain logic.
|
| 10 |
+
# For demonstration, we mimic a call to an LLM:
|
| 11 |
+
# In production, you could use an LLMChain or a sequence of chains.
|
| 12 |
+
prompt = f"What is wrong with this exercise?\n{user_query}"
|
| 13 |
+
response = self.llm.call(prompt)
|
| 14 |
+
return response
|
chains/distractors_chain.py
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pydantic import BaseModel
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
class DistractorsChain(BaseModel):
|
| 5 |
+
template: str
|
| 6 |
+
llm: any
|
| 7 |
+
|
| 8 |
+
def run(self, user_query: str) -> str:
|
| 9 |
+
prompt = f"Brainstorm some distractors for:\n{user_query}"
|
| 10 |
+
response = self.llm.call(prompt)
|
| 11 |
+
return response
|
config/chain_configs.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from config.llm_config import llms
|
| 2 |
+
from config.templates import dummy_template, initial_classification_template
|
| 3 |
+
from chains.diagnoser_chain import DiagnoserChain
|
| 4 |
+
from chains.distractors_chain import DistractorsChain
|
| 5 |
+
|
| 6 |
+
chain_configs = {
|
| 7 |
+
"diagnoser": {
|
| 8 |
+
"template": dummy_template,
|
| 9 |
+
"class": DiagnoserChain,
|
| 10 |
+
"llm": llms["mini"],
|
| 11 |
+
},
|
| 12 |
+
"distractors": {
|
| 13 |
+
"template": dummy_template, # Replace with a specific template if needed.
|
| 14 |
+
"class": DistractorsChain,
|
| 15 |
+
"llm": llms["mini"],
|
| 16 |
+
},
|
| 17 |
+
# More chains can be added here.
|
| 18 |
+
}
|
config/llm_config.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from langchain_openai import ChatOpenAI
|
| 2 |
+
|
| 3 |
+
ZERO = 0
|
| 4 |
+
LOW = 0.2
|
| 5 |
+
MID = 0.7
|
| 6 |
+
HIGH = 1.2
|
| 7 |
+
|
| 8 |
+
llms = {
|
| 9 |
+
"gpt4o": ChatOpenAI(model_name="gpt-4o", temperature=LOW),
|
| 10 |
+
"mini": ChatOpenAI(model_name="gpt-4o-mini", temperature=LOW),
|
| 11 |
+
"gpt4o_high_temp": ChatOpenAI(model_name="gpt-4o", temperature=HIGH),
|
| 12 |
+
"mini_high_temp": ChatOpenAI(model_name="gpt-4o-mini", temperature=HIGH),
|
| 13 |
+
"o1": ChatOpenAI(model_name="o1"),
|
| 14 |
+
}
|
config/templates.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from langchain_core.prompts import ChatPromptTemplate
|
| 2 |
+
|
| 3 |
+
dummy_template = ChatPromptTemplate([
|
| 4 |
+
("system", """
|
| 5 |
+
SYSTEM MESSAGE
|
| 6 |
+
{bot_name} {weekday} {now}
|
| 7 |
+
"""),
|
| 8 |
+
("human", """
|
| 9 |
+
USER MESSAGE
|
| 10 |
+
{user_message}
|
| 11 |
+
"""),
|
| 12 |
+
])
|
| 13 |
+
|
| 14 |
+
initial_classification_template = ChatPromptTemplate([
|
| 15 |
+
("system", """
|
| 16 |
+
# Answer structure
|
| 17 |
+
1. First pick the user_message_language: choose from Literal['English', 'German', 'Dutch', 'other'].
|
| 18 |
+
Look only at the user message. When multiple languages are present, pick the dominant one.
|
| 19 |
+
2. Then, state your classification: 'Goals', 'Reminders', 'Meta', or 'Other'.
|
| 20 |
+
"""),
|
| 21 |
+
("human", """
|
| 22 |
+
USER MESSAGE
|
| 23 |
+
{user_message}
|
| 24 |
+
"""),
|
| 25 |
+
])
|
utils/auth.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import gradio as gr
|
| 3 |
+
|
| 4 |
+
CORRECT_PASSWORD = os.getenv("APP_PASSWORD") # Ensure this is set in your environment
|
| 5 |
+
|
| 6 |
+
def login(password: str):
|
| 7 |
+
"""
|
| 8 |
+
Verify the password. Returns a tuple to update the Gradio UI.
|
| 9 |
+
"""
|
| 10 |
+
if password == CORRECT_PASSWORD:
|
| 11 |
+
return gr.update(visible=False), gr.update(visible=True), ""
|
| 12 |
+
else:
|
| 13 |
+
return gr.update(visible=True), gr.update(visible=False), "❌ Incorrect password. Please try again or contact Ben."
|