1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012 |
- import json
- import requests
- from django.conf import settings
- from django.db.models import Q
- from openai import OpenAI
- from .models import City, Attraction, TravelPlan, DayPlan, DayPlanAttraction
- from django.core.cache import cache
- import logging
- logger = logging.getLogger(__name__)
- import json
- from django.conf import settings
- from openai import OpenAI
- from .models import City, Attraction
- from django.core.cache import cache
- from datetime import datetime, timedelta
- import random
- # class MoonshotAIService:
- # @staticmethod
- # def generate_travel_plan(preference, is_regeneration=False, previous_plan=None):
- # """
- # 增强版红色旅游路线规划AI服务
- # """
- # # 处理输入参数
- # if isinstance(preference, dict):
- # city_ids = preference.get('city_ids', [])
- # cities = City.objects.filter(id__in=city_ids)
- # days = preference.get('days', 3)
- # interests = preference.get('interests', [])
- # transport = preference.get('transport', 'public')
- # custom_requirements = preference.get('custom_requirements', '')
- # else:
- # cities = preference.cities.all()
- # days = preference.days
- # interests = preference.interests
- # transport = preference.get_transport_display()
- # custom_requirements = preference.custom_requirements or '无'
- #
- # # 获取相关景点(优先红色旅游景点)
- # attractions = Attraction.objects.filter(
- # city__in=cities,
- # tags__contains="红色旅游"
- # ).select_related('city')
- #
- # # 如果没有足够红色景点,补充其他景点
- # if len(attractions) < days * 3:
- # additional_attractions = Attraction.objects.filter(
- # city__in=cities
- # ).exclude(tags__contains="红色旅游")[:10]
- # attractions = list(attractions) + list(additional_attractions)
- #
- # # 构建提示词
- # prompt = MoonshotAIService._build_enhanced_prompt(
- # cities, days, interests, transport,
- # custom_requirements, attractions,
- # is_regeneration, previous_plan
- # )
- #
- # # 调用Moonshot AI API
- # client = OpenAI(
- # api_key=settings.MOONSHOT_API_KEY,
- # base_url="https://api.moonshot.cn/v1"
- # )
- #
- # try:
- # response = client.chat.completions.create(
- # model="moonshot-v1-8k",
- # messages=[
- # {
- # "role": "system",
- # "content": "你是一个红色旅游规划专家,熟悉山东省所有红色景点。请按照指定格式输出,包含详细路线、时间安排和景点介绍。"
- # },
- # {"role": "user", "content": prompt}
- # ],
- # response_format={"type": "json_object"},
- # temperature=0.7 if is_regeneration else 0.5
- # )
- #
- # # 解析并处理返回数据
- # plan_data = json.loads(response.choices[0].message.content.strip())
- # return MoonshotAIService._process_ai_response(plan_data, attractions)
- #
- # except Exception as e:
- # print(f"AI服务异常: {str(e)}")
- # return {
- # "error": "行程生成失败",
- # "details": str(e)
- # }
- #
- # @staticmethod
- # def _build_enhanced_prompt(cities, days, interests, transport, requirements, attractions, is_regeneration,
- # previous_plan):
- # """构建增强版红色旅游提示词"""
- # city_names = [city.name for city in cities]
- # attraction_list = [
- # f"{att.id}:{att.name}[标签:{','.join(att.tags)}][城市:{att.city.name}][描述:{att.short_desc}]"
- # for att in attractions
- # ]
- #
- # base_prompt = f"""
- # 你是一个专业的红色旅游规划AI助手,请根据以下信息为游客规划山东省红色旅游行程:
- #
- # 基本要求:
- # - 城市:{city_names}
- # - 天数:{days}天
- # - 兴趣偏好:{interests or '无特别偏好'}
- # - 交通方式:{transport}
- # - 特殊要求:{requirements or '无'}
- #
- # 可选景点信息(ID:名称[标签][城市][简短描述]):
- # {attraction_list}
- #
- # 请设计一个富有教育意义的红色旅游路线,要求:
- # 1. 每天安排3-4个景点,包含至少2个红色景点
- # 2. 合理安排景点间的交通时间和午餐时间
- # 3. 每个景点提供详细的参观建议和背景介绍
- # 4. 路线设计要连贯,避免来回奔波
- # 5. 包含早中晚餐的推荐地点(尽量选择红色教育基地附近的餐馆)
- # """
- #
- # if is_regeneration:
- # base_prompt += f"""
- # 这是之前的行程计划,请重新设计不同的路线:
- # {json.dumps(previous_plan, ensure_ascii=False, indent=2)}
- # """
- #
- # base_prompt += f"""
- # 请返回JSON格式的行程计划,结构如下:
- # {{
- # "title": "行程标题(突出红色主题)",
- # "description": "行程整体描述(200字左右)",
- # "suitable_for": "适合人群(如'亲子家庭'、'学生团体'等)",
- # "days": [
- # {{
- # "day": 1,
- # "theme": "当日主题(如'革命精神传承之旅')",
- # "description": "当日详细描述",
- # "transport": "交通安排建议",
- # "morning": {{
- # "start_time": "08:00",
- # "attraction": 景点ID,
- # "visit_time": "建议参观时间(如'1.5小时')",
- # "description": "景点详细介绍(300字左右)",
- # "recommendation": "参观建议(如'建议先参观纪念馆主展厅')"
- # }},
- # "lunch": {{
- # "time": "12:00",
- # "recommendation": "午餐推荐地点和特色菜",
- # "description": "餐馆简介(100字左右)"
- # }},
- # "afternoon": [
- # {{
- # "start_time": "13:30",
- # "attraction": 景点ID,
- # "visit_time": "建议参观时间",
- # "description": "景点详细介绍",
- # "recommendation": "参观建议"
- # }},
- # ...
- # ],
- # "dinner": {{
- # "time": "18:00",
- # "recommendation": "晚餐推荐",
- # "description": "餐馆简介"
- # }},
- # "evening_activity": {{
- # "description": "晚间活动建议(如'观看红色主题演出')",
- # "recommendation": "活动详情"
- # }}
- # }},
- # ...
- # ],
- # "travel_tips": [
- # "穿着建议:...",
- # "注意事项:...",
- # "红色教育重点:..."
- # ]
- # }}
- # """
- # return base_prompt
- #
- # @staticmethod
- # def _process_ai_response(plan_data, attractions):
- # """处理AI返回数据,补充完整景点信息"""
- # attraction_map = {att.id: att for att in attractions}
- #
- # for day in plan_data.get('days', []):
- # # 处理上午景点
- # if 'morning' in day and day['morning']['attraction'] in attraction_map:
- # att = attraction_map[day['morning']['attraction']]
- # day['morning'].update({
- # 'attraction_data': {
- # 'name': att.name,
- # 'image': att.image.url if att.image else '',
- # 'address': att.address,
- # 'open_hours': att.open_hours,
- # 'ticket_price': str(att.ticket_price)
- # }
- # })
- #
- # # 处理下午景点
- # if 'afternoon' in day:
- # for item in day['afternoon']:
- # if item['attraction'] in attraction_map:
- # att = attraction_map[item['attraction']]
- # item.update({
- # 'attraction_data': {
- # 'name': att.name,
- # 'image': att.image.url if att.image else '',
- # 'address': att.address,
- # 'open_hours': att.open_hours,
- # 'ticket_price': str(att.ticket_price)
- # }
- # })
- #
- # return plan_data
- # services.py
- class MoonshotAIService:
- RED_TOURISM_TAGS = ['红色旅游', '革命', '烈士', '纪念馆', '党史']
- @staticmethod
- def generate_travel_plan(preference):
- """
- 红色旅游专用生成方法
- """
- try:
- # 1. 获取城市和景点
- cities = City.objects.filter(id__in=preference['city_ids'])
- if not cities.exists():
- return {'error': '未找到指定城市'}
- # 2. 优先获取红色景点
- attractions = Attraction.objects.filter(
- city__in=cities,
- tags__overlap=MoonshotAIService.RED_TOURISM_TAGS
- )
- # 如果红色景点不足,补充其他景点
- if len(attractions) < preference['days'] * 2:
- extra_attractions = Attraction.objects.filter(
- city__in=cities
- ).exclude(tags__overlap=MoonshotAIService.RED_TOURISM_TAGS)[:10]
- attractions = list(attractions) + list(extra_attractions)
- # 3. 构建AI提示
- prompt = MoonshotAIService._build_red_tourism_prompt(
- cities,
- preference['days'],
- preference['interests'],
- preference['transport'],
- preference.get('custom_requirements', ''),
- attractions
- )
- # 4. 调用AI接口
- client = OpenAI(
- api_key=settings.MOONSHOT_API_KEY,
- base_url="https://api.moonshot.cn/v1"
- )
- response = client.chat.completions.create(
- model="moonshot-v1-8k",
- messages=[
- {
- "role": "system",
- "content": "你是红色旅游专家,专门规划革命教育路线。必须包含党史学习内容。"
- },
- {"role": "user", "content": prompt}
- ],
- response_format={"type": "json_object"},
- temperature=0.5
- )
- # 5. 解析响应
- plan_data = json.loads(response.choices[0].message.content)
- return MoonshotAIService._process_red_tourism_response(plan_data, attractions)
- except Exception as e:
- return {'error': str(e)}
- @staticmethod
- def _build_red_tourism_prompt(cities, days, interests, transport, requirements, attractions):
- """构建红色旅游专用提示词"""
- city_names = [city.name for city in cities]
- attraction_list = "\n".join([
- f"{att.id}:{att.name}[标签:{','.join(att.tags)}][城市:{att.city.name}]"
- for att in attractions
- ])
- return f"""
- 请规划一个{days}天的红色旅游路线,要求:
- - 城市:{city_names}
- - 必须包含至少{max(2, days)}个红色教育基地
- - 交通方式:{transport}
- - 特殊要求:{requirements or '无'}
- 可选景点:
- {attraction_list}
- 返回JSON格式,包含:
- - title: 行程标题(必须含"红色"或"革命")
- - description: 行程描述(突出教育意义)
- - days: 每日安排(必须包含educational_points教育要点)
- - 每个景点标注is_red_tourism是否为红色景点
- """
- @staticmethod
- def _process_red_tourism_response(plan_data, attractions):
- """处理AI返回的红色旅游数据"""
- attraction_map = {att.id: att for att in attractions}
- for day in plan_data.get('days', []):
- for attraction in day.get('attractions', []):
- att_id = attraction.get('id')
- if att_id in attraction_map:
- att = attraction_map[att_id]
- attraction.update({
- 'image': att.image.url if att.image else '',
- 'address': att.address,
- 'open_hours': att.open_hours,
- 'is_red_tourism': any(tag in att.tags for tag in MoonshotAIService.RED_TOURISM_TAGS)
- })
- return plan_data
- @staticmethod
- def regenerate_travel_plan(params):
- """
- 增强版重新生成方法,确保返回完整景点数据
- """
- try:
- # 1. 参数验证和转换
- city_ids = params.get('city_ids', [])
- if isinstance(city_ids, int): # 处理单个城市ID的情况
- city_ids = [city_ids]
- cities = City.objects.filter(id__in=city_ids)
- if not cities.exists():
- return {'error': '未找到指定城市'}
- days = int(params.get('days', 3))
- # 2. 获取所有相关景点(原行程景点 + 新红色景点)
- previous_attractions = []
- for day in params.get('previous_plan', {}).get('days', []):
- for attr in day.get('attractions', []):
- if attr.get('id'):
- previous_attractions.append(attr['id'])
- # 查询数据库获取完整景点对象
- attractions = Attraction.objects.filter(
- Q(id__in=previous_attractions) |
- Q(city__in=cities, tags__overlap=MoonshotAIService.RED_TOURISM_TAGS)
- ).distinct()
- # 3. 构建更明确的提示词
- prompt = f"""
- 请基于原行程重新规划{days}天红色旅游路线,要求:
- **必须包含以下元素**:
- 1. 至少{max(2, days)}个红色教育基地
- 2. 保留原行程中评分高的景点
- 3. 每日主题明确(如"革命精神传承")
- **城市范围**:{[city.name for city in cities]}
- **可选景点**:
- {[f"{a.id}:{a.name}[红色:{any(tag in a.tags for tag in MoonshotAIService.RED_TOURISM_TAGS)}]" for a in attractions]}
- **返回格式示例**:
- {{
- "title": "新行程标题",
- "days": [
- {{
- "day": 1,
- "attractions": [
- {{
- "id": 景点ID,
- "name": "景点名称",
- "is_red_tourism": true/false,
- "educational_value": "高/中/低"
- }}
- ]
- }}
- ]
- }}
- """
- # 4. 调用AI接口(示例代码,实际需要替换为您的AI调用)
- client = OpenAI(api_key=settings.MOONSHOT_API_KEY, base_url="https://api.moonshot.cn/v1")
- response = client.chat.completions.create(
- model="moonshot-v1-8k",
- messages=[
- {"role": "system", "content": "你是红色旅游专家,严格按照要求生成行程"},
- {"role": "user", "content": prompt}
- ],
- response_format={"type": "json_object"},
- temperature=0.6
- )
- # 5. 处理响应数据
- plan_data = json.loads(response.choices[0].message.content)
- # 补充景点完整信息
- attraction_map = {a.id: a for a in attractions}
- for day in plan_data.get('days', []):
- for attr in day.get('attractions', []):
- if attr['id'] in attraction_map:
- a = attraction_map[attr['id']]
- attr.update({
- 'image': a.image.url if a.image else '',
- 'address': a.address,
- 'open_hours': a.open_hours,
- 'is_red_tourism': any(tag in a.tags for tag in MoonshotAIService.RED_TOURISM_TAGS)
- })
- return plan_data
- except Exception as e:
- logger.error(f"重新生成失败: {str(e)}", exc_info=True)
- return {'error': str(e)}
- # class MoonshotAIService:
- # @staticmethod
- # def generate_travel_plan(preference, is_regeneration=False, previous_plan=None):
- # """
- # 调用Moonshot AI API生成旅行计划
- # :param preference: 偏好设置(字典或Django模型对象)
- # :param is_regeneration: 是否为重新生成请求
- # :param previous_plan: 之前的行程计划(仅重新生成时使用)
- # :return: 生成的行程计划字典
- # """
- # # 处理输入参数(支持字典或Django模型对象)
- # if isinstance(preference, dict):
- # city_ids = preference.get('city_ids', [])
- # cities = City.objects.filter(id__in=city_ids)
- # days = preference.get('days', 3)
- # interests = preference.get('interests', [])
- # transport = preference.get('transport', 'public')
- # custom_requirements = preference.get('custom_requirements', '')
- # else:
- # cities = preference.cities.all()
- # days = preference.days
- # interests = preference.interests
- # transport = preference.get_transport_display()
- # custom_requirements = preference.custom_requirements or '无'
- #
- # # 获取相关景点
- # attractions = Attraction.objects.filter(city__in=cities)
- #
- # # 构建不同的提示词基于是否是重新生成
- # if is_regeneration:
- # prompt = MoonshotAIService._build_regeneration_prompt(
- # cities, days, interests, transport,
- # custom_requirements, attractions, previous_plan
- # )
- # else:
- # prompt = MoonshotAIService._build_initial_prompt(
- # cities, days, interests, transport,
- # custom_requirements, attractions
- # )
- #
- # # 调用Moonshot AI API
- # client = OpenAI(
- # api_key=settings.MOONSHOT_API_KEY,
- # base_url="https://api.moonshot.cn/v1"
- # )
- #
- # try:
- # response = client.chat.completions.create(
- # model="moonshot-v1-8k",
- # messages=[
- # {
- # "role": "system",
- # "content": "你是一个旅行规划专家,严格按照用户指定的JSON格式输出,不添加额外解释。"
- # },
- # {"role": "user", "content": prompt}
- # ],
- # response_format={"type": "json_object"},
- # temperature=0.7 if is_regeneration else 0.5 # 重新生成时增加一点随机性
- # )
- #
- # # 解析并返回JSON
- # return json.loads(response.choices[0].message.content.strip())
- #
- # except json.JSONDecodeError:
- # print("API返回的不是有效JSON,尝试修复...")
- # raw_content = response.choices[0].message.content
- # start_idx = raw_content.find("{")
- # end_idx = raw_content.rfind("}") + 1
- # return json.loads(raw_content[start_idx:end_idx])
- #
- # except Exception as e:
- # print(f"Moonshot AI API调用异常: {str(e)}")
- # return {
- # "error": "行程生成失败",
- # "details": str(e)
- # }
- #
- # @staticmethod
- # def _build_initial_prompt(cities, days, interests, transport, custom_requirements, attractions):
- # """构建初始行程提示词"""
- # return f"""
- # 你是一个专业的旅行规划AI助手,请根据以下信息规划行程:
- # - 城市:{[city.name for city in cities]}
- # - 天数:{days}天
- # - 兴趣:{', '.join(interests) if interests else '无特别偏好'}
- # - 交通方式:{transport}
- # - 特殊要求:{custom_requirements if custom_requirements else '无'}
- #
- # 可选景点(格式:名称[标签]):
- # {[f"{att.name}[{', '.join(att.tags)}]" for att in attractions]}
- #
- # 请返回JSON格式的行程计划,结构如下:
- # {{
- # "title": "行程标题",
- # "description": "行程描述",
- # "suitable_for": "适合人群描述",
- # "days": [
- # {{
- # "day": 1,
- # "theme": "当日主题",
- # "description": "当日描述",
- # "transport": "交通方式描述",
- # "attractions": [
- # {{
- # "id": 景点ID,
- # "name": "景点名称",
- # "visit_time": "建议参观时间",
- # "notes": "备注信息"
- # }}
- # ]
- # }}
- # ]
- # }}
- # """
- #
- # @staticmethod
- # def _build_regeneration_prompt(cities, days, interests, transport, custom_requirements, attractions, previous_plan):
- # """构建重新生成行程提示词"""
- # previous_plan_str = json.dumps(previous_plan, ensure_ascii=False, indent=2) if previous_plan else "无"
- #
- # return f"""
- # 你是一个专业的旅行规划AI助手,请基于相同的偏好但不同的安排重新规划行程:
- #
- # 原行程计划:
- # {previous_plan_str}
- #
- # 偏好设置:
- # - 城市:{[city.name for city in cities]}
- # - 天数:{days}天
- # - 兴趣:{', '.join(interests) if interests else '无特别偏好'}
- # - 交通方式:{transport}
- # - 特殊要求:{custom_requirements if custom_requirements else '无'}
- #
- # 可选景点(格式:名称[标签]):
- # {[f"{att.name}[{', '.join(att.tags)}]" for att in attractions]}
- #
- # 请返回一个不同的JSON格式行程计划,要求:
- # 1. 使用不同的景点组合
- # 2. 调整每天的行程顺序
- # 3. 创建新的每日主题
- # 4. 保持相同的天数和城市
- #
- # 结构如下:
- # {{
- # "title": "新行程标题(与之前不同)",
- # "description": "新行程描述",
- # "suitable_for": "适合人群描述",
- # "days": [
- # {{
- # "day": 1,
- # "theme": "新当日主题",
- # "description": "当日描述",
- # "transport": "交通方式描述",
- # "attractions": [
- # {{
- # "id": 景点ID,
- # "name": "景点名称",
- # "visit_time": "建议参观时间",
- # "notes": "备注信息"
- # }}
- # ]
- # }}
- # ]
- # }}
- # """
- #
- # import json
- # from django.db.models import Q
- # from django.conf import settings
- # from openai import OpenAI
- # from .models import City, Attraction
- #
- #
- # class MoonshotAIService:
- # """
- # 红色文化旅游规划服务类
- # 严格限定只选择烈士陵园、革命纪念馆、红色博物馆等红色文化景点
- # """
- #
- # # 定义合法的红色景点类型标签
- # RED_ATTRACTION_TAGS = ['martyrs', 'memorial', 'museum', 'revolution', '红色旅游']
- #
- # @staticmethod
- # def generate_travel_plan(preference, is_regeneration=False, previous_plan=None):
- # """
- # 生成红色文化主题旅行计划
- # :param preference: 偏好设置(dict或Django模型)
- # :param is_regeneration: 是否重新生成
- # :param previous_plan: 原行程(重新生成时用)
- # :return: 行程计划(dict)或错误信息
- # """
- # # 1. 参数解析
- # params = MoonshotAIService._parse_preferences(preference)
- # if 'error' in params:
- # return params
- #
- # cities, days, interests, transport, custom_reqs = params
- #
- # # 2. 获取红色文化景点
- # attractions = MoonshotAIService._get_red_attractions(cities)
- # if isinstance(attractions, dict) and 'error' in attractions:
- # return attractions
- #
- # # 3. 构建AI提示词
- # prompt = MoonshotAIService._build_prompt(
- # cities, days, interests, transport,
- # custom_reqs, attractions, is_regeneration, previous_plan
- # )
- #
- # # 4. 调用AI接口
- # plan = MoonshotAIService._call_moonshot_api(prompt, is_regeneration)
- # if 'error' in plan:
- # return plan
- #
- # # 5. 验证结果
- # if not MoonshotAIService._validate_red_plan(plan):
- # return {
- # "error": "行程验证失败",
- # "details": "生成的行程不符合红色文化主题要求"
- # }
- #
- # return plan
- #
- # @staticmethod
- # def _parse_preferences(preference):
- # """解析偏好参数"""
- # try:
- # if isinstance(preference, dict):
- # city_ids = preference.get('city_ids', [])
- # cities = City.objects.filter(id__in=city_ids)
- # days = preference.get('days', 3)
- # interests = preference.get('interests', [])
- # transport = preference.get('transport', 'public')
- # custom_reqs = preference.get('custom_requirements', '')
- # else:
- # cities = preference.cities.all()
- # days = preference.days
- # interests = preference.interests
- # transport = preference.get_transport_display()
- # custom_reqs = preference.custom_requirements or '无'
- #
- # if not cities.exists():
- # return {"error": "未选择有效城市", "details": "请至少选择一个城市"}
- #
- # return cities, days, interests, transport, custom_reqs
- #
- # except Exception as e:
- # return {"error": "参数解析失败", "details": str(e)}
- #
- # @staticmethod
- # def _get_red_attractions(cities):
- # """获取红色文化景点"""
- # try:
- # # 精确查询
- # attractions = Attraction.objects.filter(
- # city__in=cities,
- # tags__overlap=MoonshotAIService.RED_ATTRACTION_TAGS
- # ).distinct()
- #
- # # 放宽条件查询
- # if not attractions.exists():
- # attractions = Attraction.objects.filter(
- # city__in=cities,
- # name__iregex=r'革命|烈士|党史|红色|纪念馆'
- # )
- #
- # if not attractions.exists():
- # red_cities = ["北京", "延安", "井冈山", "遵义", "韶山"]
- # return {
- # "error": "未找到红色景点",
- # "details": "当前城市未找到红色文化景点",
- # "suggestion": f"建议选择{','.join(red_cities)}等红色旅游城市"
- # }
- #
- # return attractions
- #
- # except Exception as e:
- # return {"error": "景点查询失败", "details": str(e)}
- #
- # @staticmethod
- # def _build_prompt(cities, days, interests, transport, custom_reqs, attractions, is_regeneration, previous_plan):
- # """构建AI提示词"""
- # if is_regeneration:
- # return MoonshotAIService._build_red_regeneration_prompt(
- # cities, days, interests, transport,
- # custom_reqs, attractions, previous_plan
- # )
- # return MoonshotAIService._build_red_initial_prompt(
- # cities, days, interests, transport,
- # custom_reqs, attractions
- # )
- #
- # @staticmethod
- # def _build_red_initial_prompt(cities, days, interests, transport, custom_reqs, attractions):
- # """红色文化初始行程提示词"""
- # city_names = [city.name for city in cities]
- # attraction_list = [
- # f"{att.name}[{'烈士纪念' if 'martyrs' in att.tags else '革命遗址' if 'revolution' in att.tags else '红色博物馆'}]"
- # for att in attractions
- # ]
- #
- # return f"""
- # 你是一个红色文化旅行规划专家,请严格按照以下要求规划行程:
- #
- # **硬性要求**:
- # 1. 所有景点必须为以下类型:
- # - 烈士陵园/纪念碑(含martyrs标签)
- # - 革命纪念馆/遗址(含memorial/revolution标签)
- # - 红色博物馆(含museum标签)
- # 2. 每日主题必须包含党史/革命史教育内容
- # 3. 景点顺序应符合历史时间线
- #
- # **行程信息**:
- # - 城市:{city_names}
- # - 天数:{days}天
- # - 兴趣偏好:{interests if interests else '红色文化教育'}
- # - 交通方式:{transport}
- # - 特殊要求:{custom_reqs if custom_reqs else '无'}
- #
- # **可选红色景点**:
- # {attraction_list}
- #
- # **输出格式**:
- # {{
- # "title": "红色主题标题(必须含'红色'或'革命')",
- # "description": "突出爱国主义教育的描述",
- # "suitable_for": "适合人群(如:党员干部、学生等)",
- # "days": [
- # {{
- # "day": 1,
- # "theme": "教育主题(如:'井冈山革命精神')",
- # "description": "当日教育重点",
- # "transport": "{transport}",
- # "attractions": [
- # {{
- # "id": 景点ID,
- # "name": "景点名称",
- # "visit_time": "建议时长(如:2小时)",
- # "notes": "具体教育意义(如:该景点展示了XX历史)"
- # }}
- # ]
- # }}
- # ]
- # }}
- # """
- #
- # @staticmethod
- # def _build_red_regeneration_prompt(cities, days, interests, transport, custom_reqs, attractions, previous_plan):
- # """红色文化重新生成提示词"""
- # prev_plan = json.dumps(previous_plan, ensure_ascii=False, indent=2) if previous_plan else "无"
- #
- # return f"""
- # 请基于相同偏好重新规划不同的红色文化行程:
- #
- # **原行程**:
- # {prev_plan}
- #
- # **新行程要求**:
- # 1. 使用不同类型的红色景点(如原行程以纪念馆为主,新行程改为以博物馆为主)
- # 2. 按新的历史视角安排(如按时间倒序)
- # 3. 创建新的教育主题(如从"革命历程"改为"英雄人物")
- #
- # **偏好设置**:
- # - 城市:{[city.name for city in cities]}
- # - 天数:{days}天
- # - 交通:{transport}
- # - 特殊要求:{custom_reqs or '无'}
- #
- # **请返回新行程**:
- # {{
- # "title": "新红色主题(区别于原行程)",
- # "days": [
- # {{
- # "attractions": [
- # {{
- # "educational_focus": "新的教育侧重点"
- # }}
- # ]
- # }}
- # ]
- # }}
- # """
- #
- # @staticmethod
- # def _call_moonshot_api(prompt, is_regeneration):
- # """调用Moonshot AI接口"""
- # try:
- # client = OpenAI(
- # api_key=settings.MOONSHOT_API_KEY,
- # base_url="https://api.moonshot.cn/v1"
- # )
- #
- # response = client.chat.completions.create(
- # model="moonshot-v1-8k",
- # messages=[
- # {
- # "role": "system",
- # "content": "你是一个红色文化专家,严格按用户要求生成行程,不添加无关内容"
- # },
- # {"role": "user", "content": prompt}
- # ],
- # response_format={"type": "json_object"},
- # temperature=0.7 if is_regeneration else 0.5
- # )
- #
- # content = response.choices[0].message.content
- # return json.loads(content.strip())
- #
- # except json.JSONDecodeError:
- # try:
- # content = response.choices[0].message.content
- # start = content.find('{')
- # end = content.rfind('}') + 1
- # return json.loads(content[start:end])
- # except:
- # return {"error": "响应解析失败", "details": "无法解析AI返回的JSON"}
- #
- # except Exception as e:
- # return {"error": "API调用失败", "details": str(e)}
- #
- # @staticmethod
- # def _validate_red_plan(plan):
- # """验证行程是否符合红色主题"""
- # required_phrases = ['红色', '革命', '烈士', '党史', '爱国主义']
- # plan_str = json.dumps(plan, ensure_ascii=False)
- #
- # # 检查关键词
- # if not any(phrase in plan_str for phrase in required_phrases):
- # return False
- #
- # # 检查景点备注
- # for day in plan.get('days', []):
- # for attr in day.get('attractions', []):
- # if not any(phrase in attr.get('notes', '') for phrase in required_phrases):
- # return False
- #
- # return True
- class TravelPlanService:
- @staticmethod
- def create_travel_plan(plan_data):
- """
- 根据输入数据创建旅行计划(无需登录版)
- :param plan_data: 包含 city_ids, days, interests, transport 等
- :return: 生成的旅行计划对象
- """
- try:
- # 1. 验证城市是否存在
- cities = City.objects.filter(id__in=plan_data['city_ids'])
- if not cities.exists():
- raise ValueError("选择的城市不存在")
- # 2. 调用AI生成计划数据
- moonshot_data = {
- 'city_ids': plan_data['city_ids'],
- 'days': plan_data['days'],
- 'interests': plan_data['interests'],
- 'transport': plan_data['transport'],
- 'custom_requirements': plan_data.get('custom_requirements', '')
- }
- plan_data = MoonshotAIService.generate_travel_plan(moonshot_data)
- if not plan_data or 'error' in plan_data:
- return None
- # 3. 创建旅行计划(无用户关联)
- travel_plan = TravelPlan.objects.create(
- title=plan_data['title'],
- description=plan_data['description'],
- days=len(plan_data['days']),
- suitable_for=plan_data.get('suitable_for', ''),
- status='completed'
- )
- # 4. 创建每日计划
- for day_info in plan_data['days']:
- day_plan = DayPlan.objects.create(
- travel_plan=travel_plan,
- day=day_info['day'],
- theme=day_info['theme'],
- description=day_info.get('description', ''),
- transport=day_info.get('transport', plan_data.get('transport', ''))
- )
- # 5. 添加每日景点
- for attraction_info in day_info['attractions']:
- try:
- attraction = Attraction.objects.get(id=attraction_info['id'])
- DayPlanAttraction.objects.create(
- day_plan=day_plan,
- attraction=attraction,
- order=attraction_info.get('order', 1),
- visit_time=attraction_info.get('visit_time', ''),
- notes=attraction_info.get('notes', '')
- )
- except Attraction.DoesNotExist:
- continue
- return travel_plan
- except Exception as e:
- print(f"创建旅行计划失败: {str(e)}")
- return None
- @staticmethod
- def create_travel_plan_from_preference(preference):
- """
- 根据用户偏好创建旅行计划(需要登录)
- """
- # 调用AI生成计划
- plan_data = MoonshotAIService.generate_travel_plan({
- 'city_ids': list(preference.cities.values_list('id', flat=True)),
- 'days': preference.days,
- 'interests': preference.interests,
- 'transport': preference.transport,
- 'custom_requirements': preference.custom_requirements
- })
- if not plan_data or 'error' in plan_data:
- return None
- # 创建旅行计划
- travel_plan = TravelPlan.objects.create(
- user=preference.user,
- preference=preference,
- title=plan_data['title'],
- description=plan_data['description'],
- days=preference.days,
- suitable_for=plan_data.get('suitable_for', ''),
- status='completed'
- )
- # 创建每日计划
- for day_info in plan_data['days']:
- day_plan = DayPlan.objects.create(
- travel_plan=travel_plan,
- day=day_info['day'],
- theme=day_info['theme'],
- description=day_info.get('description', ''),
- transport=day_info.get('transport', '')
- )
- # 添加每日景点
- for attraction_info in day_info['attractions']:
- try:
- attraction = Attraction.objects.get(id=attraction_info['id'])
- DayPlanAttraction.objects.create(
- day_plan=day_plan,
- attraction=attraction,
- order=attraction_info.get('order', 1),
- visit_time=attraction_info.get('visit_time', ''),
- notes=attraction_info.get('notes', '')
- )
- except Attraction.DoesNotExist:
- continue
- return travel_plan
- class CacheService:
- @staticmethod
- def get_cities():
- """
- 获取城市列表(带缓存)
- """
- cache_key = 'all_cities'
- cities = cache.get(cache_key)
- if not cities:
- cities = list(City.objects.filter(is_hot=True).values('id', 'name', 'code', 'image'))
- cache.set(cache_key, cities, timeout=3600) # 缓存1小时
- return cities
- @staticmethod
- def get_attractions_by_city(city_id):
- """
- 获取城市景点列表(带缓存)
- """
- cache_key = f'attractions_city_{city_id}'
- attractions = cache.get(cache_key)
- if not attractions:
- attractions = list(Attraction.objects.filter(city_id=city_id).values(
- 'id', 'name', 'short_desc', 'image', 'tags'
- ))
- cache.set(cache_key, attractions, timeout=3600) # 缓存1小时
- return attractions
|