基于Assistant API的旅游助手

更新时间:

本次实验是一个基于Assistant API实现的旅游助手最佳实践,通过智能调用各类函数实现,可以动态地实现各类旅游查询。

场景简介

阿里云百炼平台提供了全面的Assistant API支持,用户可在本地进行全代码开发。本实验以实现一个旅游助手为例,将一些服务(如机票查询、天气查询、近期新闻)运行在本地环境,以便更好地实现定制化服务。在本实验代码中通过虚拟函数查询接口实现,支持查询多个旅游问题,用户也可以替换成自己真实的API接口或复写其中的各类函数。

背景知识

本场景主要涉及以下云产品和服务:

  • 阿里云百炼

    基于通义系列大模型和三方大模型的一站式大模型服务平台,提供生成式大模型的全流程应用工具和企业大模型的全链路训练工具。

  • Assistant API

    Assistant API旨在帮助开发者快速构建大模型应用(Assistant),例如个人助理、智能导购、会议助手等。相比文本生成API,Assistant API内置了对话管理、知识检索以及多种工具的调用能力,从而降低了此类应用的开发成本。

您可扫描下方二维码或者钉钉搜索钉钉群号: 77600022533,加入阿里云百炼答疑群。

image

前提条件

云起实验室将在您的账号下开通本次实操资源,资源按量付费,需要您自行承担本次实操的云资源费用。

重要

本实验预计产生费用0.2元。如果您调整了资源规格、使用时长,或执行了本方案以外的操作,可能导致费用发生变化,请以控制台显示的实际价格和最终账单为准。

进入实操前,请确保阿里云账号满足以下条件:

  • 已通过实名认证并且账户余额充足。

  • 云资源产生的费用需您自行承担,云起实验室不会向您征收额外费用。

  • 所有实验操作将保留至您的账号,请谨慎操作。

  • 实操结束后,您可以选择继续付费保留资源,或参考手册自动/手动释放资源。

开通阿里云百炼

本步骤指导您如何开通大模型服务平台百炼,如果您已开通过,请跳过此步骤。

说明

阿里云百炼为首次开通服务的用户提供免费试用额度,开通的阿里云主账号与其RAM子账号共享免费试用额度。免费试用额度从开通阿里云百炼或模型申请通过之日起计算有效期,有效期一般是30~180天不等。

  1. 在实验页面,勾我已阅读并同意《阿里云云起实践平台服务协议》后,单击进入实操

    image

  2. 前往阿里云百炼控制台,如果页面顶部显示以下消息,您需要开通阿里云百炼的模型服务,以获得免费额度。如果未显示该消息,则表示您已经开通,请跳过此步骤。

    image

  3. 在弹出的对话框中,勾选我已阅读并同意《模型管理服务协议》,单击确认开通

    image

准备开发环境

  1. 创建API-KEY。

    1. 前往API-KEY页面。

    2. 我的API-KEY页面,单击创建我的API-KEY

      image

    3. 创建新的API-KET对话框中,归属业务空间选择主账号空间,单击确定

      image

    4. 我的API-KEY页面,单击目标API-KEY右侧操作列下的查看,您即可看到API-KEY的明文信息。

      image

  2. 安装最新版阿里云百炼SDK。

    1. 执行如下命令,查看您的电脑已安装Python的版本。

      说明

      请确保您的电脑已安装Python3.8及以上版本,如何安装Python,详情请参见python.org

      python --version

      image

    2. 在您的电脑中,打开终端,执行以下命令,通过pip安装阿里云百炼SDK。

      说明

      如果运行失败,请使用pip3 install -U dashscope命令安装阿里云百炼SDK。更多关于安装SDK的信息,请参见安装SDK

      pip install -U dashscope

      如需更新SDK包,请执行以下命令。

      pip install dashscope --upgrade
  3. 通过环境变量配置API-KEY。

    SDK安装后,在代码中需要传入对应的用户在阿里云百炼的密钥,用于开启大模型的使用。

    1. 如果您的操作系统为Windows系统,在终端窗口中,请将下方命令中的YOUR_DASHCOPE_API_KEY替换为您自己的API KEY后,并执行命令,添加永久性环境变量。

      说明

      如果您的操作系统为Linux系统或macOS系统,添加永久性环境变量的方法请参见通过环境变量配置API-KEY

      setx DASHSCOPE_API_KEY "YOUR_DASHSCOPE_API_KEY"

      image

    2. 重新打开一个终端窗口,执行如下命令,检查环境变量是否生效。

      echo %DASHSCOPE_API_KEY%

      返回如下结果,表示环境变量已生效。

代码实现

通过本小节的三个代码文件,即可实现基于Assistant API的旅游助手。

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

  • 关于创建和使用Assistant Api的更多信息,详情请参见Assistant API的使用方法

  1. 打开您电脑的代码编译器,例如PyCharm、IDEA等。

  2. 在代码编译器中,添加主函数(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='从杭州到北京的出行推荐')
  3. 在代码编译器中,添加自定义函数(function_utils.py)代码。

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

    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
  4. 在代码编译器中,添加虚拟的信息(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 起
    """

image

运行结果

开发者将上一小节的代码添加到代码编译器后,通过下面的方法运行测试。

  1. tour_assistant.py文件中,将提问的问题修改为“年假打算出去玩,有什么地点推荐吗”。

  2. 在代码编译器中,运行tour_assistant.py文件。

  3. 运行结果如下,成功调用recommendation函数,并生成最终结果。

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

清理资源

  • 在完成实验后,如果无需继续使用资源,请根据以下步骤,先删除API-KEY,再结束实操。

    • 删除API-KEY。

      1. 前往API-KEY页面。

      2. 我的API-KEY页面,单击目标API-KEY右侧操作列下的删除

        image

      3. 删除提示对话框中,单击确认除

        image

    • 删除API-KEY后,选择不保留资源,单击结束实操。在结束实操对话框中,单击确定

      image

  • 在完成实验后,如果需要继续使用资源,选择付费保留资源,单击结束实操。在结束实操对话框中,单击确定。请随时关注账户扣费情况,避免发生欠费。

    image