|
|
|
@ -1,4 +1,6 @@ |
|
|
|
|
import os, openai, aiomysql |
|
|
|
|
import random |
|
|
|
|
|
|
|
|
|
from openai import OpenAI |
|
|
|
|
from dotenv import load_dotenv |
|
|
|
|
import redis.asyncio as redis |
|
|
|
@ -83,20 +85,73 @@ EXTRACT_PROMPT = """你是一名景区名称精准匹配助手。用户的问题 |
|
|
|
|
大慈阁""" |
|
|
|
|
|
|
|
|
|
# 客流查询后回答的提示词 |
|
|
|
|
ANSWER_PROMPT = """生成一份人性化的出行指南。输出结构要求如下,使用生活化语言表达,注意不可依据想象生成未经查询的客流数据: |
|
|
|
|
首行为标题(如:**景区名称**游览建议,景区名称要显示正确、完整), |
|
|
|
|
**实时客流**(展示在园人数,=进入人数-离开人数,若为负取绝对值,超出最大承载量时不显示具体人数,仅显示舒适度等级;展示舒适度等级,进入和离开人数、瞬时承载量不展示,承载率仅以“舒适度等级”汉字形式体现,不展示百分比,并用简短1-2句话描述舒适度等级代表的游览体验,如游客较少,游览体验轻松。根据承载率范围输出等级,并用 HTML 字体颜色标签展示,舒适度等级:<30% 为舒适(绿色)(热门景点基本不排队,通道畅通,拍照无遮挡),30%-50% 为较舒适(蓝色)(热门景点排队 5-10 分钟,通道不挤,拍照等待短),50%-70% 为一般(黄色)(热门景点排队 15-30 分钟,部分通道挤,拍照需等),70%-90% 为较拥挤(橙色)**(热门景点排队 30-60 分钟,通道拥挤,拍照需错峰,座位紧张),>90% 为拥挤(红色)(热门景点排队超 60 分钟,通道拥堵,拍照困难,可能限流),如 <font color="green">舒适</font> <font color="blue">较舒适</font> <font color="yellow">一般</font> <font color="orange">较拥挤</font> <font color="red">拥挤</font>,不要加括号或冗余说明,仅展示彩色词语), |
|
|
|
|
**周边停车指南**(展示停车场名称,距景区距离,步行所需时间,当前余位,收费标准;每项数据为一行,收费标准需提示“具体以现场公示为准”), |
|
|
|
|
**出行与游览建议**(是否前往:强烈推荐/推荐/谨慎考虑/暂缓前往,用生活化建议替代机械的等级描述,如:强烈推荐可表述为现在正是游览的好时机!馆内人少舒适,无需排队等候,可以随到随游,享受宁静的观展氛围;当较拥挤或拥挤时显示错峰建议:如当日16:00后或次日8:30前;交通方式:停车场充足时推荐自驾,紧张建议打车或备用停车场,已满建议使用公共交通如XX路公交;路线建议:当较拥挤或拥挤时则推荐反常规路线或人少区域;建议游览时长:较舒适多10%,一般多20%,较拥挤/拥挤多30%-50%;不要直接显示百分比,需提出具体时长,如1小时以上;替代方案:当较拥挤或拥挤时推荐1-2个周边承载率低的景区并说明优势), |
|
|
|
|
**温馨提示**(如安全提醒、天气影响等影响游客游览的内容),语言风格亲切如朋友交谈,用"您""记得""推荐"等暖心词汇,禁用机械术语,用**加粗**突出关键建议,加入表情符号增加亲和力,不展示原始字段数据,结尾不输出数据时间,仅加提示“动态信息请以现场为准,祝您旅途愉快!”。 |
|
|
|
|
若未找到景区的信息时,生成景区通用游览建议,使用以下输出结构, |
|
|
|
|
首行为标题(如:**景区名称** 游览建议,景区名称要显示正确、完整), |
|
|
|
|
**实时客流**(仅显示提示“暂时无法获取实时客流数据,您仍可参考以下出行建议。”), |
|
|
|
|
**出行与游览建议**(交通方式:列出几种常规交通方式,并推荐大众选择较多的出行方式,简单描述优势和注意事项; |
|
|
|
|
游览路线:描述游览景区常规路线和游览重点,突出表现景区特色; |
|
|
|
|
游览时间:显示常规参观时间,旺季需多预留30%,不要直接显示百分比,需提出具体时长,如1小时以上; |
|
|
|
|
附近景区推荐:推荐1-2个周边景区并说明景区特点), |
|
|
|
|
**温馨提示**(如安全提醒、天气影响等影响游客游览的内容),语言风格亲切如朋友交谈,用"您""记得""推荐"等暖心词汇,禁用机械术语,用**加粗**突出关键建议,加入表情符号增加亲和力,不展示原始字段数据,结尾不输出数据时间,仅加提示“动态信息请以现场为准,祝您旅途愉快!”。""" |
|
|
|
|
ANSWER_PROMPT = """ |
|
|
|
|
**景区游览指南生成要求** |
|
|
|
|
|
|
|
|
|
输入数据格式: |
|
|
|
|
- 用户问题:{msg} |
|
|
|
|
- 查询到的数据:{data}(包含景区名称、在园人数、最大承载量、进出人数等) |
|
|
|
|
- 输出语言:{language} |
|
|
|
|
|
|
|
|
|
输出要求: |
|
|
|
|
|
|
|
|
|
1. 标题格式: |
|
|
|
|
**完整景区名称**游览建议 |
|
|
|
|
|
|
|
|
|
2. 实时客流展示: |
|
|
|
|
- 计算当前在园人数(进入人数-离开人数的绝对值) |
|
|
|
|
- 计算承载率(在园人数/最大承载量) |
|
|
|
|
- 按以下标准显示舒适度等级: |
|
|
|
|
<30%:<font color="green">舒适</font> |
|
|
|
|
30%-50%:<font color="blue">较舒适</font> |
|
|
|
|
50%-70%:<font color="blue">一般</font> |
|
|
|
|
70%-90%:<font color="blue">较拥挤</font> |
|
|
|
|
>90%:<font color="red">拥挤</font> |
|
|
|
|
- 附加简短体验描述(1-2句) |
|
|
|
|
|
|
|
|
|
3. 周边停车指南: |
|
|
|
|
- 停车场名称 |
|
|
|
|
- 距离(米) |
|
|
|
|
- 步行时间(分钟) |
|
|
|
|
- 当前余位/总车位 |
|
|
|
|
- 收费标准(注明"具体以现场公示为准") |
|
|
|
|
|
|
|
|
|
4. 出行与游览建议: |
|
|
|
|
- 推荐程度(强烈推荐/推荐/谨慎考虑/暂缓前往) |
|
|
|
|
- 具体建议(包含错峰时间、交通方式、路线建议) |
|
|
|
|
- 建议游览时长(根据舒适度调整) |
|
|
|
|
- 替代景区推荐(当拥挤时) |
|
|
|
|
|
|
|
|
|
5. 温馨提示: |
|
|
|
|
- 安全提醒 |
|
|
|
|
- 天气影响 |
|
|
|
|
- 其他注意事项 |
|
|
|
|
|
|
|
|
|
6. 数据缺失时的通用建议: |
|
|
|
|
- 标题:**景区名称**游览建议 |
|
|
|
|
- 实时客流提示 |
|
|
|
|
- 常规交通建议 |
|
|
|
|
- 游览路线说明 |
|
|
|
|
- 附近景区推荐 |
|
|
|
|
|
|
|
|
|
语言风格要求: |
|
|
|
|
- 使用生动形象的语言描述,适当添加emoji表情符号增强表现力 |
|
|
|
|
- 采用"您""记得""推荐"等暖心词汇 |
|
|
|
|
- 禁用专业术语 |
|
|
|
|
- 使用**加粗**强调关键信息 |
|
|
|
|
- 结尾提示:"动态信息请以现场为准,祝您旅途愉快!" |
|
|
|
|
|
|
|
|
|
数据要求: |
|
|
|
|
- 仅使用查询到的数据 |
|
|
|
|
- 不虚构未提供的信息 |
|
|
|
|
- 数字数据仅展示计算后的在园人数,不显示例如进入人数、离开人数、承载量和承载率等原始数据字段 |
|
|
|
|
- 不输出数据更新时间 |
|
|
|
|
- 标题字体大小为 18–20px,加粗显示 |
|
|
|
|
- 正文内容字体大小为 15–16px |
|
|
|
|
- 行间距为 1.6–1.8 倍字体大小 |
|
|
|
|
- 段落与模块之间上下边距应为10–16px |
|
|
|
|
- 不能使用小于 14px 的字体 |
|
|
|
|
""" |
|
|
|
|
|
|
|
|
|
async def classify(msg: str) -> str: |
|
|
|
|
print(f"Starting classification for message: {msg}") |
|
|
|
@ -113,10 +168,24 @@ async def classify(msg: str) -> str: |
|
|
|
|
raise |
|
|
|
|
|
|
|
|
|
async def ai_chat_stream(inp: ChatIn, conversation_history: list) -> AsyncGenerator[str, None]: |
|
|
|
|
chat_prompt = ( |
|
|
|
|
f"你是一个河北省保定市的旅游助手,为游客提供保定市文旅相关问答服务,包括但不限于行程规划、" |
|
|
|
|
f"景点推荐等,不提供其他任何非保定文旅相关信息。输出语言为:{inp.language}" |
|
|
|
|
) |
|
|
|
|
chat_prompt = f""" |
|
|
|
|
你是一个专门服务河北省保定市旅游的AI助手,主要提供以下精准服务: |
|
|
|
|
1. 行程规划:根据游客的停留天数(1-7天)、预算范围(经济型/中档/豪华)、兴趣偏好(历史文化/自然风光/美食体验)提供定制化行程方案 |
|
|
|
|
2. 景点推荐:详细介绍保定市3A级以上旅游景区(如白洋淀、野三坡、清西陵等)的开放时间、门票价格、最佳游览季节和交通方式 |
|
|
|
|
3. 特色推荐:提供保定驴肉火烧、槐茂酱菜等地方特色美食的具体店铺地址和人均消费 |
|
|
|
|
4. 实用信息:提供保定市区及周边县市的公共交通线路、出租车参考价格、天气情况等实用旅行信息 |
|
|
|
|
|
|
|
|
|
**服务要求**: |
|
|
|
|
- 使用生动形象的语言描述,适当添加emoji表情符号增强表现力 |
|
|
|
|
- 采用Markdown语法组织内容,合理使用**加粗**、*斜体*等格式 |
|
|
|
|
- 分点说明时使用清晰的列表格式 |
|
|
|
|
- 重要信息使用高亮标记 |
|
|
|
|
|
|
|
|
|
**服务限制**: |
|
|
|
|
- 地理范围:仅限保定市行政区划内(含下辖县市)的文旅信息 |
|
|
|
|
- 语言输出:严格使用用户指定的{inp.language}语言回复 |
|
|
|
|
- 问题边界:对非保定文旅相关问题统一回复"我是您的保定旅行助手,专注于解答有关保定市的旅行问题哦~" |
|
|
|
|
""" |
|
|
|
|
messages = [{"role": "system", "content": chat_prompt}] + conversation_history |
|
|
|
|
messages.append({"role": "user", "content": inp.message}) |
|
|
|
|
|
|
|
|
@ -150,9 +219,12 @@ async def ai_chat_stream(inp: ChatIn, conversation_history: list) -> AsyncGenera |
|
|
|
|
conversation_history.append({"role": "assistant", "content": full_response}) |
|
|
|
|
print("AI chat stream finished.") |
|
|
|
|
|
|
|
|
|
def get_formatted_prompt(user_language,msg,data): |
|
|
|
|
# 使用format方法替换占位符 |
|
|
|
|
return ANSWER_PROMPT.format(language=user_language, msg=msg, data=data) |
|
|
|
|
|
|
|
|
|
async def gen_markdown_stream(msg: str, data: str, language: str, conversation_history: list) -> AsyncGenerator[str, None]: |
|
|
|
|
prompt = f"""你是一位智能旅游助手,结合用户问题:{msg} 和查询内容:{data},"""+ANSWER_PROMPT + f"""输出语言为:{language}""" |
|
|
|
|
prompt = get_formatted_prompt(language,msg,data) # 将变量替换到提示词中 |
|
|
|
|
|
|
|
|
|
messages = conversation_history + [{"role": "user", "content": prompt}] |
|
|
|
|
|
|
|
|
@ -235,8 +307,11 @@ async def query_flow(request: Request, spot: str) -> str: |
|
|
|
|
print(f"No data found for spot: {spot}") |
|
|
|
|
return f"**未找到景区【{spot}】的信息,请检查名称是否正确。\n\n(内容仅供参考)" |
|
|
|
|
|
|
|
|
|
#随机生成剩余车位数量进行测试 |
|
|
|
|
car_num = random.randint(0, 100) |
|
|
|
|
jl_num = random.randint(50, 200) |
|
|
|
|
# 修改结果拼接部分,使用新的列名 |
|
|
|
|
result = f"**{spot} 客流**\n\n进入人数: {row[0]}\n离开人数: {row[1]}\n\n景区瞬时承载量:{row[2]},暂无停车场数据(内容仅供参考)" |
|
|
|
|
result = f"**{spot} 客流**\n\n进入人数: {row[0]}\n离开人数: {row[1]}\n\n景区瞬时承载量:{row[2]};停车场名称:临时地上停车场 ,距离:{jl_num}米,空余车位:{car_num},总车位:100,收费标准:30分钟内免费,超出后3元/小时。(内容仅供参考)" |
|
|
|
|
|
|
|
|
|
# Step 3: 写入 Redis 缓存 |
|
|
|
|
print(f"Writing data to Redis cache for key: {cache_key}") |
|
|
|
|