@ -124,7 +124,7 @@ ANSWER_PROMPT = """
5. 温馨提示 :
- 安全提醒
- 天气影响
- 介绍景区特色
- 其他注意事项
6. 数据缺失时的通用建议 :
@ -144,13 +144,14 @@ ANSWER_PROMPT = """
数据要求 :
- 仅使用查询到的数据
- 不虚构未提供的信息
- 数字数据仅展示计算后的在园人数 , 不显示例如进入人数 、 离开人数 、 承载量和承载率等原始数据字段
- 数字数据仅展示计算后的在园人数 , 不显示例如进入人数 、 离开人数 、 承载量和承载率等原始数据字段 , 如果承载率超过100 % 则只输出舒适度等级而不输出实际的在园人数
- 不输出数据更新时间
- 标题字体大小为 18 – 20 px , 加粗显示
- 正文内容字体大小为 15 – 16 px
- 行间距为 1.6 – 1.8 倍字体大小
- 段落与模块之间上下边距应为10 – 16 px
- 不能使用小于 14 px 的字体
- 不要解释排版等内容
"""
async def classify ( msg : str ) - > str :
@ -208,6 +209,16 @@ async def ai_chat_stream(inp: ChatIn, conversation_history: list) -> AsyncGenera
sys . stdout . flush ( )
# 短暂让出控制权,避免阻塞
await asyncio . sleep ( 0 )
# 生成推荐问题
if full_response :
recommended_questions = await generate_recommended_questions ( inp . message , full_response )
if recommended_questions :
# 添加分隔符和标题
yield " \n \n ### 您可能还想了解: "
# 逐个返回推荐问题
for i , question in enumerate ( recommended_questions , 1 ) :
yield f " \n { i } . { question } "
# 添加结束标记
yield " "
except Exception as e :
@ -247,6 +258,15 @@ async def gen_markdown_stream(msg: str, data: str, language: str, conversation_h
sys . stdout . flush ( )
# 短暂让出控制权,避免阻塞
await asyncio . sleep ( 0 )
# 生成推荐问题
if full_response :
recommended_questions = await generate_recommended_questions ( msg , full_response )
if recommended_questions :
# 添加分隔符和标题
yield " \n \n ### 您可能还想了解: "
# 逐个返回推荐问题
for i , question in enumerate ( recommended_questions , 1 ) :
yield f " \n { i } . { question } "
# 添加结束标记
yield " "
except Exception as e :
@ -295,7 +315,7 @@ async def query_flow(request: Request, spot: str) -> str:
async with pool . acquire ( ) as conn :
async with conn . cursor ( ) as cur :
# 查询景区客流信息
query = " SELECT SUM(t1.init_num) AS init_num, SUM(t1.out_num) AS out_num,t3.realtime_load_capacity FROM equipment_passenger_flow.flow_current_video t1 LEFT JOIN cyjcpt_bd.zhly_video_manage t2 ON t1.mac_address = t2.mac_address LEFT JOIN cyjcpt_bd.zhly_scenic_basic t3 ON t2.video_scenic_id = t3.id WHERE t3.`name` LIKE %s "
query = " SELECT t3.id AS id, SUM(t1.init_num) AS init_num, SUM(t1.out_num) AS out_num,t3.realtime_load_capacity FROM equipment_passenger_flow.flow_current_video t1 LEFT JOIN cyjcpt_bd.zhly_video_manage t2 ON t1.mac_address = t2.mac_address LEFT JOIN cyjcpt_bd.zhly_scenic_basic t3 ON t2.video_scenic_id = t3.id WHERE t3.`name` LIKE %s "
search_spot = f " % { spot } % "
await cur . execute ( query , ( search_spot , ) )
row = await cur . fetchone ( )
@ -309,7 +329,7 @@ async def query_flow(request: Request, spot: str) -> str:
return f " **未找到景区【 { spot } 】的信息,请检查名称是否正确。 \n \n (内容仅供参考) "
result = " "
if row :
result = f " { spot } 客流 \n \n 进入人数: { row [ 0 ] } \n 离开人数: { row [ 1 ] } \n \n 景区瞬时承载量: { row [ 2 ] } ;"
result = f " { spot } 客流 \n \n 进入人数: { row [ 1 ] } \n 离开人数: { row [ 2 ] } \n \n 景区瞬时承载量: { row [ 3 ] } ;注:全部内容输出完以后,最后输出一段固定内容,内容为:<p data-type= \" keliu \" data-id= \" { row [ 0 ] } \" ></p> ;"
else :
result = f " 未找到景区【 { spot } 】的客流相关信息 "
if park_row :
@ -362,4 +382,241 @@ async def handle_quick_question(inp: ChatIn, question_content: str) -> AsyncGene
yield error_msg
# 不保存快捷问题的对话历史
print ( " Quick question handling finished. " )
print ( " Quick question handling finished. " )
# 在chat_service.py中添加推荐问题生成函数
async def generate_recommended_questions ( user_msg : str , ai_response : str ) - > list :
""" 基于用户问题和AI回答生成1-3个纵向延伸的推荐问题 """
prompt = f """
基于用户的问题和AI的回答 , 生成1 - 3 个相关的纵向延伸问题 , 帮助用户深入了解相关内容 。
用户问题 : { user_msg }
AI回答 : { ai_response }
要求 :
1. 问题需与当前话题高度相关 , 具有延续性
2. 问题应具有探索性 , 引导用户深入了解
3. 用简洁明了的中文表达
4. 只返回问题列表 , 每个问题占一行 , 不添加编号和其他内容
5. 每个问题使用固定的格式 : < p class = " anser-tuijian " > { { 问题 } } < / p >
"""
try :
response = client . chat . completions . create (
model = " deepseek-chat " ,
messages = [
{ " role " : " system " , " content " : " 你是一个问题推荐助手,擅长基于对话内容生成相关的延伸问题 " } ,
{ " role " : " user " , " content " : prompt }
]
)
questions = response . choices [ 0 ] . message . content . strip ( ) . split ( ' \n ' )
# 过滤空行并限制数量为1-3个
return [ q for q in questions if q ] [ : 3 ]
except Exception as e :
print ( f " 生成推荐问题失败: { e } " )
return [ ]
# 添加新函数用于获取所有景区客流数据
async def get_all_scenic_flow_data ( request : Request ) - > list :
"""
查询所有景区的客流数据 , 计算承载率 , 并按承载率倒序排列
"""
try :
pool = request . app . state . mysql_pool
async with pool . acquire ( ) as conn :
async with conn . cursor ( ) as cur :
# 查询所有景区客流信息
query = """
SELECT
t3 . id AS id ,
t3 . ` name ` AS scenic_name ,
SUM ( t1 . init_num ) AS enter_num ,
SUM ( t1 . out_num ) AS leave_num ,
t3 . realtime_load_capacity AS max_capacity
FROM
equipment_passenger_flow . flow_current_video t1
LEFT JOIN
cyjcpt_bd . zhly_video_manage t2 ON t1 . mac_address = t2 . mac_address
LEFT JOIN
cyjcpt_bd . zhly_scenic_basic t3 ON t2 . video_scenic_id = t3 . id
WHERE t3 . ` name ` IS NOT NULL
GROUP BY
t3 . ` name ` , t3 . realtime_load_capacity
ORDER BY
( SUM ( t1 . init_num ) - SUM ( t1 . out_num ) ) / t3 . realtime_load_capacity DESC
"""
await cur . execute ( query )
rows = await cur . fetchall ( )
# 处理结果
result = [ ]
for row in rows :
id , scenic_name , enter_num , leave_num , max_capacity = row
in_park_num = abs ( enter_num - leave_num ) # 确保是正数
# 避免除以零的情况
if max_capacity > 0 :
capacity_rate = in_park_num / max_capacity
else :
capacity_rate = 0
result . append ( {
" id " : id ,
" scenic_name " : scenic_name ,
" enter_num " : enter_num ,
" leave_num " : leave_num ,
" in_park_num " : in_park_num ,
" max_capacity " : max_capacity ,
" capacity_rate " : capacity_rate
} )
return result
except Exception as e :
print ( f " [MySQL] 查询所有景区客流数据失败: { e } " )
return [ ]
# 在文件末尾添加新函数
async def get_scenic_detail_data ( request : Request , id : int ) - > dict :
"""
查询单个景区的详细信息 , 包含舒适度判断
"""
try :
pool = request . app . state . mysql_pool
async with pool . acquire ( ) as conn :
async with conn . cursor ( ) as cur :
# 查询单个景区客流信息
query = """
SELECT
t3 . ` name ` AS scenic_name ,
COALESCE ( SUM ( t1 . init_num ) , 0 ) AS enter_num ,
COALESCE ( SUM ( t1 . out_num ) , 0 ) AS leave_num ,
COALESCE ( t3 . realtime_load_capacity , 0 ) AS max_capacity
FROM
equipment_passenger_flow . flow_current_video t1
LEFT JOIN
cyjcpt_bd . zhly_video_manage t2 ON t1 . mac_address = t2 . mac_address
LEFT JOIN
cyjcpt_bd . zhly_scenic_basic t3 ON t2 . video_scenic_id = t3 . id
WHERE
t3 . id = % s
GROUP BY
t3 . ` name ` , t3 . realtime_load_capacity
"""
await cur . execute ( query , ( id , ) )
row = await cur . fetchone ( )
if not row :
return None
scenic_name , enter_num , leave_num , max_capacity = row
# 计算在园人数
in_park_num = max ( 0 , enter_num - leave_num )
# 计算承载率和舒适度
if max_capacity > 0 :
capacity_rate = in_park_num / max_capacity
capacity_percentage = round ( capacity_rate * 100 , 1 )
# 根据承载率判断舒适度
if capacity_rate < 0.3 :
comfort_level = " 舒适 "
elif capacity_rate < 0.5 :
comfort_level = " 较舒适 "
elif capacity_rate < 0.7 :
comfort_level = " 一般 "
elif capacity_rate < 0.9 :
comfort_level = " 较拥挤 "
else :
comfort_level = " 拥挤 "
else :
capacity_rate = 0.0
capacity_percentage = 0.0
return {
" scenic_name " : scenic_name ,
" enter_num " : enter_num or 0 ,
" leave_num " : leave_num or 0 ,
" in_park_num " : in_park_num ,
" max_capacity " : max_capacity or 0 ,
" capacity_rate " : round ( capacity_rate , 4 ) ,
" comfort_level " : comfort_level
}
except Exception as e :
print ( f " [MySQL] 查询景区详情数据失败: { e } " )
return None
# 在chat_service.py末尾添加新的停车场查询函数
async def get_scenic_parking_data ( request : Request , scenic_name : str , distance : int ) - > list :
"""
查询景区附近的停车场信息
Args :
request : FastAPI请求对象
scenic_name : 景区名称
distance : 查询距离 ( 米 ) , > = 1000 时查询全部
Returns :
list : 停车场信息列表 , 按距离排序
"""
try :
pool = request . app . state . mysql_pool
async with pool . acquire ( ) as conn :
async with conn . cursor ( ) as cur :
# 构建基础查询
base_query = """
SELECT
t3 . park_name AS park_name ,
t3 . total_count AS total_parking_spaces ,
COALESCE ( t4 . space , 0 ) AS available_spaces ,
t1 . distance_value AS distance_meters
FROM
cyjcpt_bd . scenic_pack_distance t1
LEFT JOIN
cyjcpt_bd . zhly_scenic_basic t2 ON t1 . scenic_id = t2 . id
LEFT JOIN
cyjcpt_bd . park_info t3 ON t1 . park_code = t3 . park_code
LEFT JOIN
equipment_passenger_flow . park_current t4 ON t1 . park_code = t4 . park_code
WHERE
t2 . ` name ` LIKE % s
AND t3 . total_count > 0
"""
# 根据距离参数构建WHERE条件
if distance > = 1000 :
# 距离>=1000米,查询全部停车场
where_condition = " "
params = ( f " % { scenic_name } % " , )
else :
# 距离<1000米,按指定距离查询
where_condition = " AND t1.distance_value <= %s "
params = ( f " % { scenic_name } % " , distance )
# 完整的查询语句
query = base_query + where_condition + " ORDER BY t1.distance_value ASC "
await cur . execute ( query , params )
rows = await cur . fetchall ( )
# 处理结果
result = [ ]
for row in rows :
park_name , total_spaces , available_spaces , distance_meters = row
result . append ( {
" park_name " : park_name ,
" total_parking_spaces " : total_spaces or 0 ,
" available_spaces " : available_spaces or 0 ,
" distance_meters " : distance_meters or 0
} )
return result
except Exception as e :
print ( f " [MySQL] 查询景区停车场数据失败: { e } " )
return [ ]