最佳实践-基于Assistant API的旅游助手

简介

本文介绍一个基于Assistant API实现的旅游助手最佳实践,通过智能调用各类函数实现,可以动态地实现各类旅游查询。在本示例代码中通过虚拟函数查询接口实现,支持查询多个旅游问题。

开发者可以替换成自己真实的API接口或复写其中的各类函数。

依赖功能

  • Assistant API的基础API参数生成能力。

  • Assistant APIFunction calling能力。

实现代码

概述

通过下文的三个代码文件,运行tour-assistant.py 即可测试相关代码(配置api-key后)。

目前虚拟的旅游相关函数支持问“丽江“ 和”北京“相关的问题。

前期准备

设置您的API-KEY,替换YOUR_DASHCOPE_API_KEY为您自己的API key

export DASHSCOPE_API_KEY=YOUR_DASHSCOPE_API_KEY

主函数(tour_assistant.py)

# -*- encoding: utf-8 -*-

import json
import sys
from http import HTTPStatus

from dashscope import Assistants, Messages, Runs, Threads
from function_utils import *


def create_assistant():
    # create assistant with information
    assistant = Assistants.create(
        model='qwen-max',
        name='smart helper',
        description='一个旅游助手,可以通过用户诉求,调用天气查询,路径推荐,当地餐厅推荐等帮助用户。',
        instructions='你是一个旅游助手,可以通过调用插件解决问题。插件例如,天气查询,路径推荐,当地餐厅推荐等,当你无法回答问题时应当结合插件回复进行回答。请根据插件结果适当丰富回复内容。',
        tools=[{
            'type': 'function',
            'function': {
                'name': '天气查询',
                'description': '用于查询天气的插件和函数',
                'parameters': {
                    'type': 'object',
                    'properties': {
                        'location': {
                            'type': 'str',
                            'description': '待查询的地点'
                        },
                        'date': {
                            'type': 'str',
                            'description': '待查询的具体日期'
                        }
                    },
                    'required': ['location', 'date']
                }
            }
        },
            {
                'type': 'function',
                'function': {
                    'name': '路径规划',
                    'description': '用于推荐出行和旅游的路径规划,包含查询路线,规划起点到终点的路线。也用于推荐最近最热门的旅游行程。',
                    'parameters': {
                        'type': 'object',
                        'properties': {
                            'start': {
                                'type': 'str',
                                'description': '待查询路径规划的出发点'
                            },
                            'destination': {
                                'type': 'str',
                                'description': '待查询路径规划的终点.'
                            },
                            'recommendation': {
                                'type': 'int',
                                'description': '用于控制是否随机推荐,用于在无法判断有路径规划终点时,设置为1,其他情况为0。此时会推荐最热门的旅游路线.'
                            }
                        },
                        'required': ['destination', 'start', 'recommendation']
                    }
                }
            },
            {
                'type': 'function',
                'function': {
                    'name': '获取目的地建议',
                    'description': '用于推荐最近热门的旅游目的地。',
                    'parameters': {
                        'type': 'object',
                        'properties': {
                            'query': {
                                'type': 'str',
                                'description': '可能需要的信息'
                            },
                        },
                        'required': ['query']
                    }
                }
            },
            {
                'type': 'function',
                'function': {
                    'name': '获取景点推荐',
                    'description': '用于推荐指定城市的旅游景点。',
                    'parameters': {
                        'type': 'object',
                        'properties': {
                            'city': {
                                'type': 'str',
                                'description': '城市名称'
                            },
                        },
                        'required': ['city']
                    }
                }
            },
            {
                'type': 'function',
                'function': {
                    'name': '获取餐饮推荐',
                    'description': '用于推荐指定城市的餐饮。',
                    'parameters': {
                        'type': 'object',
                        'properties': {
                            'city': {
                                'type': 'str',
                                'description': '城市名称'
                            },
                        },
                        'required': ['city']
                    }
                }
            },
            {
                'type': 'function',
                'function': {
                    'name': '获取旅行提示',
                    'description': '用于获取指定城市的旅行注意事项。',
                    'parameters': {
                        'type': 'object',
                        'properties': {
                            'city': {
                                'type': 'str',
                                'description': '城市名称'
                            },
                        },
                        'required': ['city']
                    }
                }
            },
            {
                'type': 'function',
                'function': {
                    'name': '获取当地风俗',
                    'description': '用于获取指定城市的当地风俗。',
                    'parameters': {
                        'type': 'object',
                        'properties': {
                            'city': {
                                'type': 'str',
                                'description': '城市名称'
                            },
                        },
                        'required': ['city']
                    }
                }
            },
        ],
    )

    return assistant


function_mapper = {
    "天气查询": get_weather,
    "路径规划": get_path_recommendation,
    "获取目的地建议": get_destination_recommendation,
    "获取景点推荐": get_attraction_recommendation,
    "获取餐饮推荐": get_dining_recommendation,
    "获取旅行提示": get_life_tips,
    "获取当地风俗": get_local_customs,
}


def verify_status_code(res):
    if res.status_code != HTTPStatus.OK:
        sys.exit(res.status_code)


def send_message(assistant, message='查询杭州天气'):
    print(f"Query: {message}")

    # create thread.
    # create a thread.
    thread = Threads.create()

    print(thread)

    # create a message.
    message = Messages.create(thread.id, content=message)
    # create run

    run = Runs.create(thread.id, assistant_id=assistant.id)
    print(run)

    # # get run statue
    # run_status = Runs.get(run.id, thread_id=thread.id)
    # print(run_status)

    # wait for run completed or requires_action
    run_status = Runs.wait(run.id, thread_id=thread.id)
    # print(run_status)

    # if prompt input tool result, submit tool result.
    if run_status.required_action:

        f = run_status.required_action.submit_tool_outputs.tool_calls[0].function
        func_name = f['name']
        param = json.loads(f['arguments'])
        print(f)
        if func_name in function_mapper:
            output = function_mapper[func_name](**param)
        else:
            output = ""

        tool_outputs = [{
            'output':
                output
        }]
        run = Runs.submit_tool_outputs(run.id,
                                       thread_id=thread.id,
                                       tool_outputs=tool_outputs)

        # should wait for run completed
        run_status = Runs.wait(run.id, thread_id=thread.id)
        # print(run_status)
        verify_status_code(run_status)

    run_status = Runs.get(run.id, thread_id=thread.id)
    print(run_status)
    # verify_status_code(run_status)

    # get the thread messages.
    msgs = Messages.list(thread.id)
    # print(msgs)
    # print(json.dumps(msgs, default=lambda o: o.__dict__, sort_keys=True, indent=4))

    print("运行结果:")
    for message in msgs['data'][::-1]:
        print("content: ", message['content'][0]['text']['value'])
    print("\n")


if __name__ == '__main__':
    assistant = create_assistant()
    send_message(assistant=assistant, message="丽江天气怎么样?")

    # send_message(assistant=assistant,message="年假打算出去玩,有什么地点推荐吗")
    # send_message(assistant=assistant,message="从北京去丽江怎么出行方便?")

    # send_message(assistant=assistant,message="丽江有什么好玩的地方?")
    # send_message(assistant=assistant,message="我喜欢吃,丽江有什么美食推荐吗")
    # send_message(assistant=assistant,message="去丽江还应该注意什么?")
    # send_message(assistant=assistant,message="能告诉我丽江当地有什么风俗吗")
    # send_message(assistant=assistant,message='从杭州到北京的出行推荐')

自定义函数(function_utils.py)

tour assistant 中用到了多个自定义函数,开发者也可以根据自己的需求改写和增加相关函数。

from mocked_information import *


def get_destination_recommendation(query):
    return destination


def get_attraction_recommendation(city):
    return attraction


def get_dining_recommendation(city):
    return dining


def get_life_tips(city):
    return life_tips


def get_local_customs(city):
    return local_customs


def get_current_date():
    return "今天"


def get_weather(location, date=""):
    if date == "":
        date = get_current_date()

    return date + "天气是" + "晴天"


def get_current_location():
    return "北京"


def get_path_recommendation(destination, start='', recommendation=False):
    return "建议通过飞机从" + start + "到" + destination + "\n以下是航班信息\n" + flight

虚拟的信息(mocked_information.py)

对于部分函数中用到的信息(如航班信息),我们虚拟的信息置于下述文件。

destination="""
以下是一些推荐的旅游目的地:
1. 杭州(中国)
西湖:享有“人间天堂”的美誉,是中国最著名的自然景观之一。
灵隐寺:杭州最古老的寺庙之一,提供宁静的精神修养场所。
西溪湿地:天然的湿地生态区,可以近距离接触自然和野生生物。
2. 丽江(中国)
古城:保留了完好的历史建筑和纳西族文化,是一个不可多得的步行城市。
玉龙雪山:雄伟的山峰,提供滑雪和登山活动,景色壮观。
束河古镇:比丽江古城更为宁静的地方,适合品茶和静思。
3. 巴黎(法国)
埃菲尔铁塔:法国的象征,提供城市全景视角。
卢浮宫:世界上最大的艺术博物馆之一,收藏有《蒙娜丽莎》等珍贵艺术品。
凡尔赛宫:法国历史上的王宫,代表了欧洲花园和宫殿的顶峰。
4. 开普敦(南非)
桌山:通过缆车访问,顶部可俯瞰开普敦市和罗宾岛的壮丽景色。
好望角:著名的海角,为航海家提供的历史地标。
克尔斯滕博施植物园:世界上生物多样性最丰富的植物园之一。
"""


attraction = """
1. 丽江古城(大研古城)
特色:丽江古城是一个完美保存的古老纳西族城镇,以其石板路、流水和古桥而著名。无需购票即可进入,但维护古城的费用需购买一种维护费证书。
建议时间:建议早上开始您的游览以避开人群。
提示:晚上的古城特别迷人,有许多酒吧和咖啡馆可以享受。
2. 黑龙潭公园
特色:位于丽江古城北边,是欣赏玉龙雪山倒影的绝佳地点。
建议时间:上午或傍晚。
提示:入园免费,是拍照和散步的好地方。
3. 玉龙雪山
特色:这座壮观的雪山是丽江的标志性景点,您可以乘坐索道至冰川公园或牦牛坪。
建议时间:全天,但需留出足够的时间上山下山。
提示:高海拔可能引起高反,建议提前做好准备。
4. 白沙古镇
特色:相对于丽江古城,白沙古镇更加宁静,保留了更多传统纳西族文化。
建议时间:下午。
提示:可以尝试纳西族的传统手工艺品制作体验。
5. 束河古镇
特色:比丽江古城小而更为宁静,是了解纳西族文化的另一个好地点。
建议时间:晚上。
提示:束河古镇的夜生活相对平和,适合享受安静的晚餐。
"""


dining = """
餐饮建议
早餐:在丽江古城的小巷内尝试当地的酸奶和烧饵块。
午餐:在玉龙雪山下的餐厅尝试藏族或纳西族风味的菜肴。
晚餐:束河古镇有许多提供地道纳西菜的餐馆,推荐尝试纳西烤鱼。
"""


life_tips = """
旅行提示
衣着:丽江日夜温差大,即使在夏天也需携带厚外套。
高反:丽江海拔约2400米,部分人可能会有高原反应。建议游客到达后先适应一下高海拔环境,适当休息,多喝水,少进行剧烈运动。前往高海拔地区如玉龙雪山前,建议预防高原反应。
"""


local_customs = """
纳西族礼仪:

纳西族是丽江的主要民族,他们非常看重礼仪。与长辈交谈时,说话要礼貌且避免直视对方的眼睛,这是尊重的表现。
在参观纳西族家庭或参与当地活动时,接受主人提供的食物和饮料是礼貌的表现。
传统服饰:

当地人在特定节日或有重要活动时可能会穿着传统服装。游客可以租借纳西民族服饰拍照,但应保持尊重,不要对服饰进行任何不适当的处理。
节日和庆典:

参与当地节日如三月街(每年农历三月举行的大型集市和庆典)、庙会等,需遵守当地的规矩和传统,比如不大声喧哗、不随意涉足祭祀区域等。
"""


flight = """
航班号	机型	起飞时间	起飞机场	降落时间	降落机场	班期	餐食服务	票价
CA1469	32N	06:30	首都国际机场 T2	10:25	三义机场	每天	93%	¥1730 起
ZH1469	32N	06:30	首都国际机场 T2	10:25	三义机场	每天	93%	查看时价
JD5181	32Q	06:55	大兴国际机场	10:15	三义机场	每天	97%	¥630 起
CA1459	32N	16:20	首都国际机场 T2	20:15	三义机场	每天	97%	¥1150 起
ZH1459	32N	16:20	首都国际机场 T2	20:15	三义机场	每天	97%	查看时价
3U5234	737	16:55	大兴国际机场	20:05	三义机场	每天	-	¥2860 起
KN6139	737	16:55	大兴国际机场	20:05	三义机场	每天	-	查看时价
MU5716	737	16:55	大兴国际机场	20:05	三义机场	每天	-	¥900 起
3U5234	737	17:00	大兴国际机场	20:05	三义机场	每天	-	¥2860 起
KN6139	737	17:00	大兴国际机场	20:05	三义机场	每天	-	查看时价
MU5716	737	17:00	大兴国际机场	20:05	三义机场	每天	-	¥900 起
"""

运行结果

开发者可以将上述代码复制到IDE中,通过下面的方法运行。

  1. 运行tour_assistant.py 并修改相关问题为“年假打算出去玩,有什么地点推荐吗”

  2. 运行结果见下,成功调用“recommendation”函数,并生成最终结果:

运行结果:
content:  年假打算出去玩,有什么地点推荐吗
content:  根据当前的热门程度,我为您推荐以下几个旅游目的地:

1. **杭州, 中国**  
   - **西湖**:在这里,您能体验到被誉为“人间天堂”的绝美自然景观。
   - **灵隐寺**:作为杭州最古老的寺庙之一,它提供了宁静的环境,非常适合精神修养。
   - **西溪湿地**:自然爱好者的好去处,您可以在这里享受大自然的宁静与美丽。

2. **丽江, 中国**  
   - **丽江古城**:漫步在历史悠久的街道上,感受纳西族文化的独特魅力。
   - **玉龙雪山**:不论是滑雪还是登山,这里的壮观景色都会让您难以忘怀。
   - **束河古镇**:相比热闹的丽江古城,这里更加宁静,适合慢慢品味一杯茶,享受悠闲时光。

3. **巴黎, 法国**  
   - **埃菲尔铁塔**:作为法国的象征,登顶后可将整个巴黎的美景尽收眼底。
   - **卢浮宫**:艺术爱好者的圣地,亲眼目睹《蒙娜丽莎》的神秘微笑。
   - **凡尔赛宫**:宏伟的宫殿与精致的花园,展示了法国皇室昔日的辉煌。

4. **开普敦, 南非**  
   - **桌山**:乘坐缆车到达山顶,您将被开普敦和罗宾岛的壮丽景色所震撼。
   - **好望角**:作为历史上海洋航行的重要地标,这里的自然风光同样令人赞叹。
   - **克尔斯滕博施植物园**:探索世界上生物多样性最为丰富的植物园,感受自然界的奇妙。

每个地方都有其独特的风情和不可错过的体验,选择一个符合您兴趣和放松需求的目的地,享受您的年假吧!

目前Assistant API使用代码均支持流式输出版本,详细可查阅SDK示例

若有收获,就点个赞吧