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") | |