| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195 |
- from __future__ import annotations
- """地图研究Agent:调用高德地图MCP工具搜索目的地周边景点、餐饮与天气,返回结构化POI数据。"""
- from typing import Any
- from langchain.agents import create_agent
- from langchain_core.language_models.chat_models import (
- BaseChatModel,
- )
- from langchain_core.tools import BaseTool
- from app.llm import get_chat_model
- from app.schemas.travel_request import TravelRequest
- # MAP_AGENT_PROMPT:地图研究的系统提示词,定义高德工具调用规则、候选数量约束与输出JSON结构。
- MAP_AGENT_PROMPT = """
- 你是旅行规划系统中的地图研究Agent。
- 你的职责:
- 1. 使用高德地图MCP查询真实景点。
- 2. 根据用户兴趣查询相关地点。
- 3. 查询适合游客的当地餐饮地点。
- 4. 查询目的城市天气。
- 5. 返回结构化候选结果。
- 6. 不负责查询机票和酒店。
- 7. 不负责生成最终行程。
- 8. 不负责计算完整路线。
- 工具使用规则:
- 1. 必须调用 maps_text_search 查询真实POI。
- 2. 必须调用 maps_weather 查询目的城市天气。
- 3. maps_search_detail 只在需要补充POI详情时调用。
- 4. 不允许根据模型知识编造地点、地址或坐标。
- 5. 地点名称、POI ID、地址和坐标必须来自工具结果。
- 6. 查询景点时,需要覆盖:
- - 目的城市热门景点;
- - 用户明确表达的兴趣。
- 7. 如果用户喜欢美食,需要额外查询当地特色餐饮。
- 8. 最多执行5次关键词搜索,避免无意义重复调用。
- 候选数量与多样性:
- - 景点保留6至10个;
- - 餐饮地点保留4至6个;
- - 同一地点不能重复。
- - 景点必须覆盖不同类型,同类景点最多选1个
- (例如:动物园/熊猫类只选1个、寺庙/道观类只选1个、
- 古街/古镇类只选1个、自然山水类分散到不同山脉);
- - 餐饮必须优先选择不同品牌/餐厅,
- 同一连锁品牌只选1家分店,
- 避免同一品牌出现两次(如饕林餐厅各分店只选1家);
- - 优先覆盖目的城市不同行政区,
- 避免所有候选集中在同一区域。
- 天气规则:
- 1. 如果旅行日期超过天气工具可查询范围,
- 不得编造未来天气。
- 2. 此时weather返回空列表,并在notes说明
- "旅行日期超过当前天气预报范围"。
- 3. 天气数据必须来自maps_weather。
- match_reason只能描述该地点与用户兴趣的匹配关系,
- 不能声称"距离酒店近"或"交通最方便",
- 因为路线尚未计算。
- 完成工具查询后,请将所有结果整理为以下JSON格式,
- 放在最终消息的最末尾,用```json代码块包裹:
- ```json
- {
- "city": "城市名称",
- "attractions": [
- {
- "poi_id": "高德POI ID或null",
- "name": "景点名称",
- "category": "类别",
- "address": "地址",
- "district": "区",
- "location": "经度,纬度",
- "telephone": "电话或null",
- "source_query": "使用的搜索关键词",
- "match_reason": "匹配用户兴趣的原因"
- }
- ],
- "restaurants": [
- {
- "poi_id": "高德POI ID或null",
- "name": "餐厅名称",
- "category": "类别",
- "address": "地址",
- "district": "区",
- "location": "经度,纬度",
- "telephone": "电话或null",
- "source_query": "使用的搜索关键词",
- "match_reason": "匹配用户兴趣的原因"
- }
- ],
- "weather": [
- {
- "date": "日期 YYYY-MM-DD",
- "week": "星期几",
- "day_weather": "白天天气",
- "night_weather": "夜间天气",
- "day_temperature": "白天温度",
- "night_temperature": "夜间温度",
- "day_wind_direction": "白天风向",
- "night_wind_direction": "夜间风向"
- }
- ],
- "notes": ["备注1", "备注2"]
- }
- ```
- """
- class MapResearchAgent:
- """查询景点、餐饮和天气的地图Agent。"""
- def __init__(
- self,
- tools: list[BaseTool],
- model: BaseChatModel | None = None,
- ) -> None:
- """初始化地图研究Agent,校验必需的高德工具是否已加载。"""
- allowed_names = {
- "maps_text_search",
- "maps_weather",
- "maps_search_detail",
- }
- selected_tools = [
- tool
- for tool in tools
- if tool.name in allowed_names
- ]
- loaded_names = {
- tool.name
- for tool in selected_tools
- }
- required_names = {
- "maps_text_search",
- "maps_weather",
- }
- missing_names = required_names - loaded_names
- if missing_names:
- raise RuntimeError(
- "地图Agent缺少高德工具:"
- + "、".join(sorted(missing_names))
- )
- self._agent = create_agent(
- model=model or get_chat_model(timeout=180.0),
- tools=selected_tools,
- system_prompt=MAP_AGENT_PROMPT,
- )
- async def research(
- self,
- request: TravelRequest,
- ) -> dict[str, Any]:
- """执行目的地地图研究。"""
- request_json = request.model_dump_json(
- indent=2
- )
- user_message = f"""
- 请根据下面的结构化旅行需求,
- 查询真实景点、餐饮地点和天气:
- {request_json}
- 必须实际调用高德地图工具。
- 查询结束后,输出 JSON 结构化结果。
- """
- return await self._agent.ainvoke(
- {
- "messages": [
- {
- "role": "user",
- "content": user_message,
- }
- ]
- }
- )
|