Spaces:
Runtime error
Runtime error
from dotenv import load_dotenv | |
import os | |
import re | |
import json | |
from datetime import datetime | |
from pathlib import Path | |
from openai import AzureOpenAI | |
from typing import List | |
load_dotenv() | |
class TourGuideGenerator: | |
def __init__(self): | |
self.client = AzureOpenAI( | |
api_key=os.getenv("AZURE_OPENAI_KEY_2"), | |
azure_endpoint=os.getenv("AZURE_OPENAI_ENDPOINT_2"), | |
api_version=os.getenv("AZURE_OPENAI_VERSION_2") | |
) | |
self.deployment_name = os.getenv("AZURE_OPENAI_DEPLOYMENT_2") | |
self.output_dir = "./outputs" | |
def generate( | |
self, | |
prompt_type: str, | |
context: str, | |
exhibit_chunks: List[str], | |
survey_id: str = "unknown", | |
tour_length_minutes: int = None, | |
major: str = "", | |
age_group: str = "", | |
class_subject: str = "", | |
topics_of_interest: List[str] = [], | |
exhibit_name: str = "", | |
additional_notes: str = "" | |
) -> dict: | |
if not isinstance(exhibit_chunks, list): | |
raise TypeError("exhibit_chunks must be a list of strings") | |
formatted_chunk = "\n\n".join( | |
f"# Chunk {i+1}\n{chunk.strip()}" for i, chunk in enumerate(exhibit_chunks) if chunk.strip() | |
) | |
prompt = self.build_prompt( | |
prompt_type, | |
context, | |
formatted_chunk, | |
tour_length_minutes, | |
major, | |
age_group, | |
class_subject, | |
topics_of_interest, | |
exhibit_name, | |
additional_notes | |
) | |
temperature = { | |
"talking_points": 0.3, | |
"itinerary": 0.3, | |
"engagement_tips": 0.7 | |
}.get(prompt_type, 0.3) | |
response = self.client.chat.completions.create( | |
model=self.deployment_name, | |
messages=[{"role": "user", "content": prompt}], | |
temperature=temperature, | |
) | |
raw_response = response.choices[0].message.content.strip() | |
raw_response = self._clean_json(raw_response) | |
try: | |
output_json = json.loads(raw_response) | |
except json.JSONDecodeError as e: | |
print("❌ Failed to parse JSON response. Saving raw output instead.") | |
print("Error:", e) | |
output_json = {"error": raw_response} | |
# Save generated output | |
filename = f"{survey_id}_{prompt_type}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json" | |
os.makedirs(self.output_dir, exist_ok=True) | |
save_path = os.path.join(self.output_dir, filename) | |
with open(save_path, "w", encoding="utf-8") as f: | |
json.dump(output_json, f, indent=2, ensure_ascii=False) | |
print(f"✅ Output saved to: {save_path}") | |
# ✅ Only save survey JSON once (during "talking_points" step) | |
if prompt_type == "talking_points": | |
survey_data = { | |
"survey_id": survey_id, | |
"tour_length_minutes": tour_length_minutes, | |
"major": major, | |
"age_group": age_group, | |
"class_subject": class_subject, | |
"topics_of_interest": topics_of_interest, | |
"exhibit_name": exhibit_name, | |
"additional_notes": additional_notes | |
} | |
survey_filename = f"{survey_id}_survey_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json" | |
survey_save_path = os.path.join(self.output_dir, survey_filename) | |
with open(survey_save_path, "w", encoding="utf-8") as f: | |
json.dump(survey_data, f, indent=2, ensure_ascii=False) | |
print(f"📝 Survey saved to: {survey_save_path}") | |
return output_json | |
def _clean_json(self, raw: str) -> str: | |
if raw.startswith("```json"): | |
raw = raw[len("```json"):].strip() | |
if raw.endswith("```"): | |
raw = raw[:-len("```")].strip() | |
raw = raw.replace("\u201c", '"').replace("\u201d", '"').replace("\u2019", "'") | |
raw = re.sub(r",(\s*[}\]])", r"\1", raw) | |
return raw | |
def build_prompt( | |
self, | |
prompt_type: str, | |
context: str, | |
exhibit_chunk: str, | |
tour_length_minutes: int = None, | |
major: str = "", | |
age_group: str = "", | |
class_subject: str = "", | |
topics_of_interest: List[str] = [], | |
exhibit_name: str = "", | |
additional_notes: str = "" | |
) -> str: | |
base_prompt = f""" | |
# Role and Objective | |
You are a helpful assistant for museum tour planning. Your job is to generate content that helps a volunteer guide lead an educational and engaging tour based on exhibit materials and survey context. | |
# Instructions | |
- Read the tour context carefully | |
- Use details from the survey (such as tour guide's major, student age group, class subject, and interests) | |
- Tailor language and content based on the intended audience | |
- Prioritize clarity, cultural relevance, and hands-on engagement | |
""".strip() | |
survey_info = f""" | |
# Survey Information | |
- Tour Guide Major: {major} | |
- Age Group: {age_group} | |
- Class Subject: {class_subject} | |
- Topics of Interest: {", ".join(topics_of_interest)} | |
- Exhibit Name: {exhibit_name} | |
- Tour Length: {tour_length_minutes} minutes | |
- Additional Notes: {additional_notes} | |
""".strip() | |
if prompt_type == "talking_points": | |
return base_prompt + "\n\n" + survey_info + f""" | |
# Talking Points Instructions | |
- Identify recurring themes across the exhibit | |
- Include technical details (symbolism, techniques, materials) | |
- Incorporate the tour guide’s academic background (major) | |
- Relate to the class subject and topics of interest | |
- Use bullet points with short headers | |
- Refer to specific artworks titles | |
# Output Format | |
{{ | |
"themes": [ | |
{{ | |
"title": "Theme Title", | |
"points": [ | |
"First bullet under this theme", | |
"Second bullet under this theme" | |
] | |
}} | |
] | |
}} | |
# Context | |
{context} | |
# Exhibit Chunk | |
{exhibit_chunk} | |
""".strip() | |
elif prompt_type == "itinerary": | |
return base_prompt + "\n\n" + survey_info + f""" | |
# Itinerary Instructions | |
Utilize the total tour duration of: {tour_length_minutes} minutes. Break the tour into sequential time blocks that are between 7 to 10 minutes long. Keep it brief with time for personal reflection of the tour guide. Minimal text. | |
Include: | |
1. Introduction | |
2. Context of the exhibit | |
3. Tour Guide’s personal reflection | |
4. Engagement activities | |
5. Wrap-up/conclusion | |
# Output Format | |
{{ | |
"itinerary": [ | |
{{ "time": "0:00–10:00", "activity": "Welcome and introduce the exhibit" }}, | |
{{ "time": "10:00–20:00", "activity": "Overview of the Qing Dynasty and symbolism" }} | |
] | |
}} | |
# Context | |
{context} | |
# Exhibit Chunk | |
{exhibit_chunk} | |
""".strip() | |
elif prompt_type == "engagement_tips": | |
return base_prompt + "\n\n" + survey_info + f""" | |
# Engagement Tips Instructions | |
- Make the tips age appropriate according to the age group | |
- Use the tour guide’s major, the age group, and any additional notes to guide tone and creativity | |
- Include at least one interactive activity | |
- Focus on making the content fun, relevant, and educational | |
# Output Format | |
{{ | |
"tone_framing": ["Frame the tour as a treasure hunt"], | |
"key_takeaways": ["Silk symbolized power in Qing dynasty"], | |
"creative_activities": ["Design your own robe using meaningful colors"] | |
}} | |
# Context | |
{context} | |
# Exhibit Chunk | |
{exhibit_chunk} | |
""".strip() | |
else: | |
raise ValueError("❌ Invalid prompt type") | |