Spaces:
Runtime error
Runtime error
Fix chat interface (return message dictionaries for Gradio Chatbot) and add fallback to COURSECREATOR_API_KEY for OpenAI API key retrieval.
Browse files- app.py +39 -18
- planner.py +41 -13
app.py
CHANGED
|
@@ -16,26 +16,44 @@ SYSTEM_PROMPT = (
|
|
| 16 |
|
| 17 |
def chat(user_message, chat_history, chat_pairs, sources, plan):
|
| 18 |
"""Handle a user chat message and return updated chat state."""
|
| 19 |
-
# Ensure
|
| 20 |
-
openai.api_key = os.getenv("OPENAI_API_KEY")
|
| 21 |
-
# initialise state variables if they are None
|
| 22 |
if chat_history is None:
|
| 23 |
chat_history = []
|
| 24 |
if chat_pairs is None:
|
| 25 |
chat_pairs = []
|
| 26 |
-
#
|
| 27 |
chat_history.append({"role": "user", "content": user_message})
|
| 28 |
-
#
|
| 29 |
messages = [{"role": "system", "content": SYSTEM_PROMPT}] + chat_history
|
| 30 |
-
#
|
| 31 |
try:
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
except Exception as e:
|
| 40 |
# When the API call fails (e.g. missing API key), return an error message
|
| 41 |
assistant_reply = (
|
|
@@ -43,11 +61,12 @@ def chat(user_message, chat_history, chat_pairs, sources, plan):
|
|
| 43 |
"Please ensure your OpenAI API key is configured in the Space secrets.\n"
|
| 44 |
f"(Error: {e})"
|
| 45 |
)
|
| 46 |
-
#
|
| 47 |
chat_history.append({"role": "assistant", "content": assistant_reply})
|
| 48 |
-
#
|
| 49 |
chat_pairs.append((user_message, assistant_reply))
|
| 50 |
-
|
|
|
|
| 51 |
|
| 52 |
def run_search(query, chat_history, chat_pairs, sources, plan, num_results=5, domain_filter=""):
|
| 53 |
"""Execute a web search and update sources list."""
|
|
@@ -119,7 +138,8 @@ with gr.Blocks() as demo:
|
|
| 119 |
Chat with the assistant to brainstorm your course idea. Use web search to collect resources. When you're ready, click **Finalize Outline** to generate a course plan. Then generate the final course package (ZIP)."""
|
| 120 |
)
|
| 121 |
# Chat interface components
|
| 122 |
-
# Use 'messages' type for Chatbot
|
|
|
|
| 123 |
chatbot = gr.Chatbot(label="Conversation", type="messages")
|
| 124 |
msg_input = gr.Textbox(label="Your message", placeholder="Type your message and press Enter", lines=1)
|
| 125 |
# Search controls
|
|
@@ -133,7 +153,7 @@ Chat with the assistant to brainstorm your course idea. Use web search to collec
|
|
| 133 |
plan_output = gr.Textbox(label="Course outline", interactive=False)
|
| 134 |
generate_btn = gr.Button("Generate Course Package")
|
| 135 |
file_output = gr.File(label="course.zip")
|
| 136 |
-
# State variables to keep track of conversation, display pairs, sources and plan
|
| 137 |
state_chat_history = gr.State([])
|
| 138 |
state_chat_pairs = gr.State([])
|
| 139 |
state_sources = gr.State([])
|
|
@@ -142,6 +162,7 @@ Chat with the assistant to brainstorm your course idea. Use web search to collec
|
|
| 142 |
msg_input.submit(
|
| 143 |
chat,
|
| 144 |
inputs=[msg_input, state_chat_history, state_chat_pairs, state_sources, state_plan],
|
|
|
|
| 145 |
outputs=[chatbot, state_chat_history, state_chat_pairs, state_sources, state_plan],
|
| 146 |
)
|
| 147 |
# Handle search submission: update search results and sources; maintain other states unchanged
|
|
|
|
| 16 |
|
| 17 |
def chat(user_message, chat_history, chat_pairs, sources, plan):
|
| 18 |
"""Handle a user chat message and return updated chat state."""
|
| 19 |
+
# Ensure lists are initialised
|
|
|
|
|
|
|
| 20 |
if chat_history is None:
|
| 21 |
chat_history = []
|
| 22 |
if chat_pairs is None:
|
| 23 |
chat_pairs = []
|
| 24 |
+
# Append the user's message to the conversation history (list of dictionaries for Chatbot)
|
| 25 |
chat_history.append({"role": "user", "content": user_message})
|
| 26 |
+
# Build messages including system prompt for API call
|
| 27 |
messages = [{"role": "system", "content": SYSTEM_PROMPT}] + chat_history
|
| 28 |
+
# Call OpenAI's ChatCompletion to get assistant's reply
|
| 29 |
try:
|
| 30 |
+
model = os.getenv("OPENAI_MODEL", "gpt-5-mini")
|
| 31 |
+
temperature = float(os.getenv("TEMPERATURE", "0.7"))
|
| 32 |
+
max_tokens = int(os.getenv("MAX_OUTPUT_TOKENS", "1024"))
|
| 33 |
+
# Support alternative secret name COURSECREATOR_API_KEY as a fallback for the OpenAI API key
|
| 34 |
+
api_key = os.getenv("OPENAI_API_KEY") or os.getenv("COURSECREATOR_API_KEY")
|
| 35 |
+
if not api_key:
|
| 36 |
+
raise ValueError("OPENAI_API_KEY or COURSECREATOR_API_KEY is not set")
|
| 37 |
+
# Prefer the new OpenAI SDK (>=1.0) if available
|
| 38 |
+
if hasattr(openai, "OpenAI"):
|
| 39 |
+
client = openai.OpenAI(api_key=api_key)
|
| 40 |
+
response = client.chat.completions.create(
|
| 41 |
+
model=model,
|
| 42 |
+
messages=messages,
|
| 43 |
+
temperature=temperature,
|
| 44 |
+
max_tokens=max_tokens,
|
| 45 |
+
)
|
| 46 |
+
assistant_reply = response.choices[0].message.content
|
| 47 |
+
else:
|
| 48 |
+
# Legacy OpenAI SDK (<1.0)
|
| 49 |
+
openai.api_key = api_key
|
| 50 |
+
response = openai.ChatCompletion.create(
|
| 51 |
+
model=model,
|
| 52 |
+
messages=messages,
|
| 53 |
+
temperature=temperature,
|
| 54 |
+
max_tokens=max_tokens,
|
| 55 |
+
)
|
| 56 |
+
assistant_reply = response["choices"][0]["message"]["content"]
|
| 57 |
except Exception as e:
|
| 58 |
# When the API call fails (e.g. missing API key), return an error message
|
| 59 |
assistant_reply = (
|
|
|
|
| 61 |
"Please ensure your OpenAI API key is configured in the Space secrets.\n"
|
| 62 |
f"(Error: {e})"
|
| 63 |
)
|
| 64 |
+
# Append assistant reply to conversation history
|
| 65 |
chat_history.append({"role": "assistant", "content": assistant_reply})
|
| 66 |
+
# Append pair to display history for any other uses (kept for compatibility)
|
| 67 |
chat_pairs.append((user_message, assistant_reply))
|
| 68 |
+
# For Chatbot with type="messages", return the chat_history as the first output
|
| 69 |
+
return chat_history, chat_history, chat_pairs, sources, plan
|
| 70 |
|
| 71 |
def run_search(query, chat_history, chat_pairs, sources, plan, num_results=5, domain_filter=""):
|
| 72 |
"""Execute a web search and update sources list."""
|
|
|
|
| 138 |
Chat with the assistant to brainstorm your course idea. Use web search to collect resources. When you're ready, click **Finalize Outline** to generate a course plan. Then generate the final course package (ZIP)."""
|
| 139 |
)
|
| 140 |
# Chat interface components
|
| 141 |
+
# Use 'messages' type for Chatbot. The Chatbot expects a list of message dictionaries
|
| 142 |
+
# (e.g. {"role": "user", "content": "..."}) when type="messages".
|
| 143 |
chatbot = gr.Chatbot(label="Conversation", type="messages")
|
| 144 |
msg_input = gr.Textbox(label="Your message", placeholder="Type your message and press Enter", lines=1)
|
| 145 |
# Search controls
|
|
|
|
| 153 |
plan_output = gr.Textbox(label="Course outline", interactive=False)
|
| 154 |
generate_btn = gr.Button("Generate Course Package")
|
| 155 |
file_output = gr.File(label="course.zip")
|
| 156 |
+
# State variables to keep track of conversation (messages), display pairs, sources and plan
|
| 157 |
state_chat_history = gr.State([])
|
| 158 |
state_chat_pairs = gr.State([])
|
| 159 |
state_sources = gr.State([])
|
|
|
|
| 162 |
msg_input.submit(
|
| 163 |
chat,
|
| 164 |
inputs=[msg_input, state_chat_history, state_chat_pairs, state_sources, state_plan],
|
| 165 |
+
# First output goes to the Chatbot; we return the updated chat history (list of messages)
|
| 166 |
outputs=[chatbot, state_chat_history, state_chat_pairs, state_sources, state_plan],
|
| 167 |
)
|
| 168 |
# Handle search submission: update search results and sources; maintain other states unchanged
|
planner.py
CHANGED
|
@@ -3,19 +3,47 @@ import openai
|
|
| 3 |
|
| 4 |
|
| 5 |
def plan_course(messages, sources):
|
| 6 |
-
"""Use OpenAI to plan a course based on messages and sources."""
|
| 7 |
-
|
| 8 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
formatted_messages = [{"role": "system", "content": system_prompt}]
|
| 10 |
-
# append conversation messages
|
| 11 |
for msg in messages:
|
| 12 |
formatted_messages.append(msg)
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
|
| 4 |
|
| 5 |
def plan_course(messages, sources):
|
| 6 |
+
"""Use OpenAI to plan a course based on messages and sources. Tries to handle different OpenAI SDK versions."""
|
| 7 |
+
# Ensure API key is available
|
| 8 |
+
# Support alternative secret name COURSECREATOR_API_KEY as a fallback for the OpenAI API key
|
| 9 |
+
api_key = os.getenv("OPENAI_API_KEY") or os.getenv("COURSECREATOR_API_KEY")
|
| 10 |
+
if not api_key:
|
| 11 |
+
raise ValueError(
|
| 12 |
+
"An OpenAI API key is required to plan the course (set OPENAI_API_KEY or COURSECREATOR_API_KEY)"
|
| 13 |
+
)
|
| 14 |
+
system_prompt = (
|
| 15 |
+
"You are an expert course planner. Use the conversation and sources to propose a structured plan."
|
| 16 |
+
)
|
| 17 |
formatted_messages = [{"role": "system", "content": system_prompt}]
|
|
|
|
| 18 |
for msg in messages:
|
| 19 |
formatted_messages.append(msg)
|
| 20 |
+
model = os.getenv("OPENAI_MODEL", "gpt-5-mini")
|
| 21 |
+
temperature = float(os.getenv("TEMPERATURE", "0.7"))
|
| 22 |
+
max_tokens = int(os.getenv("MAX_OUTPUT_TOKENS", "2048"))
|
| 23 |
+
# Try to call OpenAI using v1-style client if available
|
| 24 |
+
try:
|
| 25 |
+
# Newer OpenAI Python SDK (>=1.0) exposes `OpenAI` client
|
| 26 |
+
if hasattr(openai, "OpenAI"):
|
| 27 |
+
client = openai.OpenAI(api_key=api_key)
|
| 28 |
+
response = client.chat.completions.create(
|
| 29 |
+
model=model,
|
| 30 |
+
messages=formatted_messages,
|
| 31 |
+
temperature=temperature,
|
| 32 |
+
max_tokens=max_tokens,
|
| 33 |
+
)
|
| 34 |
+
# response.choices is a list of objects
|
| 35 |
+
plan_text = response.choices[0].message.content
|
| 36 |
+
else:
|
| 37 |
+
# Legacy OpenAI SDK (<1.0)
|
| 38 |
+
openai.api_key = api_key
|
| 39 |
+
response = openai.ChatCompletion.create(
|
| 40 |
+
model=model,
|
| 41 |
+
messages=formatted_messages,
|
| 42 |
+
temperature=temperature,
|
| 43 |
+
max_tokens=max_tokens,
|
| 44 |
+
)
|
| 45 |
+
plan_text = response["choices"][0]["message"]["content"]
|
| 46 |
+
except Exception as e:
|
| 47 |
+
# Propagate error for caller to handle
|
| 48 |
+
raise RuntimeError(f"OpenAI API error: {e}")
|
| 49 |
+
return plan_text
|