旅荐网

您现在的位置是:首页 > 国内旅游目的推荐 > 正文

国内旅游目的推荐

Langchain + Neo4j + DeepSeek 实现旅游行程规划

admin2026年03月23日 19:30:49国内旅游目的推荐1
Langchain + Neo4j + DeepSeek 实现旅游行程规划

前些时间,家人准备计划完成今年的出游任务,但只提出了几个旅游景点,剩下的就是我的了,家里小孩比较小,所以就放弃了跟团的打算,准备自由行,为了规划行程,各种查询......

最近根据前段时间的“痛苦”经历,也为了了解点新东西,就使用python做了个旅游行程规划的简单版。


1、准备各种数据,我在准备时把景点、交通枢纽、城市、交通方式、票务等作为节点,然后创建关联关系,以备后期查询。

CREATE (tm_bus:TransportMode {mode_id'bus', name'公交', avg_speed_kmh30});
CREATE (gys:City {code'GYS', name'贵阳市', province'贵州'});
CREATE (gy_east:TransportHub {hub_id'gy_east_rail', name'贵阳东站', type'train_station', city_code'GYS'});
CREATE (qygz:Attraction {id'gys_qygz', name'青岩古镇', city'贵阳市', district'青岩古镇', category: ['古镇','文化'], description'青岩古镇'});
CREATE (tr_qygz:TicketRule {rule_id'qygz_all_09_17', start_time'09:00', end_time'17:00', valid_days'1-7', price0.0, currency'CNY', ticket_type'门票', is_peakfalse});
MATCH (a:Attraction {id: 'gys_qygz'}), (c:City {code'GYS'}) CREATE (a)-[:LOCATED_IN]->(c);
MATCH (a:Attraction {id: 'gys_qygz'}), (tr:TicketRule {rule_id: 'qygz_all_09_17'}) CREATE (a)-[:HAS_TICKET_RULE {booking_lead_hours: 0}]->(tr);
MATCH (a:Attraction {id: 'gys_qygz'}), (h:TransportHub {hub_id: 'gy_east_rail'}) CREATE (a)-[:TO_HUB {duration_min: 80, distance_km: 38.5, mode_ref: 'bus'}]->(h),(h)-[:TO_ATT {duration_min: 80, distance_km: 38.5, mode_ref: 'bus'}]->(a), (a)-[:TO_HUB {duration_min: 30, distance_km: 38.5, mode_ref: 'car'}]->(h),(h)-[:TO_ATT {duration_min: 30, distance_km: 38.5, mode_ref: 'car'}]->(a), (a)-[:TO_HUB {duration_min: 50, distance_km: 38.5, mode_ref: 'subway'}]->(h),(h)-[:TO_ATT {duration_min: 50, distance_km: 38.5, mode_ref: 'subway'}]->(a);
// 贵阳东站 ↔ 贵阳北站 (地铁,双向)MATCH (h1:TransportHub {hub_id: 'gy_east_rail'}), (h2:TransportHub {hub_id: 'gy_north_rail'})CREATE (h1)-[:INTER_CITY {  mode_ref: 'subway', duration_min: 20, price: 10,  freq_min: 5, departure_station: '贵阳东站', arrival_station: '贵阳北站'}]->(h2),(h2)-[:INTER_CITY {  mode_ref: 'subway', duration_min: 20, price: 10,  freq_min: 5, departure_station: '贵阳北站', arrival_station: '贵阳东站'}]->(h1);
最终在neo4j中的效果如下:

2、编写相关查询工具:

from langchain_neo4j import Neo4jGraphfrom neo4j import GraphDatabasefrom agent.utils.env_utils import NEO4J_URI, NEO4J_USERNAME, NEO4J_PASSWORD, NEO4J_DATABASE# 创建Neo4jGraph实例(用于简单查询)# refresh_schema=False:无 APOC 插件时也能连接;安装 APOC 后可改为 True 或之后调用 refresh_schema()graph = Neo4jGraph(    url=NEO4J_URI,    username=NEO4J_USERNAME,    password=NEO4J_PASSWORD,    database=NEO4J_DATABASE,    refresh_schema=False,)# 原生驱动(用于复杂查询)driver = GraphDatabase.driver(    NEO4J_URI,    auth=(NEO4J_USERNAME, NEO4J_PASSWORD),    database=NEO4J_DATABASE)
# tools.pyfrom langchain_core.tools import toolfrom agent.utils.neo4j_config import driverimport re@tooldef route_planner_tool(start_attraction: str, end_attraction: str):    """    规划跨城行程(含市内+跨城+市内)    参数:      start_attraction: 起点景点(如"深圳")      end_attraction: 终点景点(如"黄果树瀑布")    """    cypher = """    OPTIONAL MATCH (startA:Attraction {name: $start})    OPTIONAL MATCH (startH:TransportHub {name: $start})    WITH coalesce(startA, startH) AS start    WHERE start IS NOT NULL    OPTIONAL MATCH (endA:Attraction {name: $end})    OPTIONAL MATCH (endH:TransportHub {name: $end})    WITH start, coalesce(endA, endH) AS end    WHERE end IS NOT NULL    MATCH path = allShortestPaths((start)-[*1..5]->(end))    // 格式化输出    WITH path,         [r IN relationships(path) | {           从: startNode(r).name,           到: endNode(r).name,           方式: r.mode_ref,           耗时: r.duration_min+'分钟',           距离: r.distance_km +'千米',           价格: r.price +'元'         }] AS rels_detail    RETURN       rels_detail AS 交通详情    """    try:        with driver.session() as session:            result = session.run(                cypher,                start=start_attraction,                end=end_attraction            )            record = list(result)            if not record:                return f"「{start_attraction}」无法到达 「{end_attraction}」 "            resp = f"*两地之间通行方式如下" + "\n"            for i, r in enumerate(record, 1):                resp += f"\n{i}{r['交通详情']}\n  "            return resp    except Exception as e:        print(e)        return f"规划「{start_attraction}{end_attraction}」路线时出错:{str(e)[:100]}"@tooldef city_attractions_tool(city_name: str):    """    查询某城市景点列表(支持按类别筛选)    参数:      city_name: 城市名称(如"贵州、铜仁")    """    cypher = """        MATCH (a:Attraction)-[:LOCATED_IN]->(c:City)    WHERE c.name CONTAINS $city or c.province CONTAINS $city    RETURN a.name AS 景点,a.city as 所在城市, a.description AS 描述,            [(a)-[:HAS_TICKET_RULE]->(tr) | tr.price][0] AS 门票    ORDER BY 门票 ASC    LIMIT 10    """    try:        with driver.session() as session:            results = session.run(cypher, city=city_name)            records = list(results)            if not records:                return f"「{city_name}」暂无收录景点数据"            resp = f"**{city_name}推荐景点**" + "\n"            for i, r in enumerate(records, 1):                resp += f"\n{i}. **{r['景点']}**\n   {r['所在城市']}\n   门票:{r['门票']}元"            return resp    except Exception as e:        return f"查询「{city_name}」景点时出错:{str(e)[:100]}"@tooldef city_transport_hub_tool(city_name: str):    """    查询某个城市的交通枢纽    参数:      city_name: 城市名称(如"贵州、铜仁")    """    cypher = """            MATCH (c:City)<-[:IN_CITY]-(t:TransportHub)        WHERE c.name CONTAINS $city or c.province CONTAINS $city        RETURN t.name AS 交通枢纽,c.name as 所在城市        LIMIT 10        """    try:        with driver.session() as session:            results = session.run(cypher, city=city_name)            records = list(results)            if not records:                return f"「{city_name}」暂无收录交通枢纽数据"            resp = f"{city_name}的交通枢纽" + "\n"            for i, r in enumerate(records, 1):                resp += f"\n{i}. **{r['交通枢纽']}**\n   {r['所在城市']}"            return resp    except Exception as e:        return f"查询「{city_name}」交通枢纽时出错:{str(e)[:100]}"if __name__ == '__main__':    #print(city_attractions_tool.invoke("贵州"))    # print(route_planner_tool.invoke({"start_attraction": "深圳北站", "end_attraction": "黄果树瀑布"}))    print(city_transport_hub_tool.invoke("贵州"))

3、使用langchainGraph+smith在本地运行

# agent.pyfrom langchain.agents import create_agentfrom langchain_openai import ChatOpenAIfrom agent.tools.tools import attraction_info_tool, route_planner_tool, city_attractions_tool, city_transport_hub_toolfrom agent.utils.env_utils import DEEPSEEK_MODEL_NAME, DEEPSEEK_API_KEY, DEEPSEEK_BASE_URL# 初始化LLM(替换为你的模型)llm = ChatOpenAI(    model_name=DEEPSEEK_MODEL_NAME,    api_key=DEEPSEEK_API_KEY,    base_url=DEEPSEEK_BASE_URL)# 精心设计的系统提示词(关键!)AGENT_PROMPT = """你是一名专业的**文旅智能助手**,精通贵州省及周边景点、票务、交通信息。请严格遵守以下规则:## 知识范围- 仅回答与景点、门票、行程规划相关的问题- 数据覆盖:贵阳、安顺、铜仁等城市景点(黔灵山公园、黄果树瀑布、梵净山)- 交通方式:高铁(highspeed)、飞机(plane)、公交(bus)、自驾(car)、步行(walk)、地铁(subway)## 可用工具说明1. `route_planner_tool`:   - 用途:规划跨城行程(自动计算市内+跨城+市内总耗时)   - 参数:start_attraction(必填), end_attraction(必填)   - 示例:start_attraction="灵隐寺", end_attraction="拙政园"2. `city_attractions_tool`:   - 用途:查询某城市景点列表   - 参数:city_name(必填)   - 示例:city_name="贵州"3. `city_transport_hub_tool`:   - 用途:查询某城市交通枢纽列表   - 参数:city_name(必填)   - 示例:city_name="深圳"## 回答规范- 工具返回后,用**自然语言总结**,突出关键信息(耗时/价格/注意事项)- 涉及时间计算时,明确说明"总耗时=市内+跨城+市内"- 若工具返回错误,友好提示用户调整查询(如"请确认景点名称是否正确")- **禁止**编造工具未返回的数据## 示例对话用户:西湖下午2点能进去吗?门票多少?思考:需查询景点票务信息 → 调用attraction_info_tool工具返回:... 回答:西湖下午2点可入园!开放时间09:00-17:00,成人票60元,无需预约。用户:从灵隐寺到拙政园坐高铁要多久?思考:需规划跨城行程 → 调用route_planner_tool工具返回:...回答:灵隐寺→拙政园(高铁)总耗时110分钟:灵隐寺→杭州东站(25min)→苏州北站(70min ¥120)→拙政园(15min)现在请开始服务用户:Question: {input}{agent_scratchpad}"""# 创建Agenttools = [attraction_info_tool, route_planner_tool, city_attractions_tool, city_transport_hub_tool]# agent = create_agent(llm, tools, AGENT_PROMPT)agent = create_agent(    llm,    tools=tools,    system_prompt=AGENT_PROMPT)
运行本地测试
langgraph dev 
测试结果如下:

大模型根据输入,自动分析需要查询哪些工具,然后调用相关工具,最后根据查询结果组织返回

问题1:我在深圳,有10天假期,想去贵州游玩,请推荐几个景点,顺便安排下行程
问题2:贵阳有哪些景区,交通方便吗

源码已放在github:https://github.com/awbqty/TourismTransportAgent

发表评论

评论列表

  • 这篇文章还没有收到评论,赶紧来抢沙发吧~