附近地点的函数调用:利用 Google Places API 和客户资料

2023 年 8 月 11 日
在 Github 中打开

此笔记本的核心是整合 Google Places API 和自定义用户资料,以增强基于位置的搜索。我们的方法包括结合用户偏好使用 Google Places API,旨在使位置发现更具个性化和相关性。请注意,虽然我们在此实例中专注于 Google Places API,但您可以探索并以类似方式应用许多其他 API。

我们将探讨三个主要组件的应用

  • 客户资料:此模拟资料捕捉了个人对地点类型的偏好(例如,餐厅、公园、博物馆)、预算、首选评分和其他特定要求。

  • Google Places API:此 API 提供有关附近地点的实时数据。它考虑了来自您周围地点的各种数据点,例如评分、场地类型、费用等。

  • 函数调用:诸如“我饿了”或“我想参观博物馆”之类的单个命令激活该函数,该函数结合用户资料数据和 Google Places API 来识别合适的场所。

此笔记本介绍了两个主要用例

  • 基于资料的推荐:了解如何创建用户资料并根据个人偏好进行地点推荐。

  • API 与函数调用集成:了解如何有效集成和调用 Google Places API,以使用函数调用获取各种地点的实时数据。

请注意,虽然此系统用途广泛,但其有效性可能会因用户偏好和可用的地点数据而异。就本笔记本而言,客户数据是虚假的,位置是硬编码的。

设置

Google Places API

要使用 Google Places API,您需要两样东西

  • Google 帐户:如果您还没有帐户,则需要创建一个 Google 帐户。

  • Google Places API 密钥:API 密钥是一个唯一标识符,用于验证与您的项目关联的请求,以便进行使用和计费。您可以从 Google Cloud Console 获取您的 API 密钥。

请注意,Google Places API 是一项付费服务,费用与 API 调用次数相关。请跟踪您的使用情况,以避免任何意外费用。

还需要 requests 库,您可以使用以下命令下载它

pip install requests
import json
from openai import OpenAI
import os
import requests

client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY", "<your OpenAI API key if not set as env var>"))

在此代码片段中,我们定义了一个函数 fetch_customer_profile,它接受一个 user_id 并返回一个模拟用户资料。

此函数模拟从数据库中获取用户数据的 API 调用。对于此演示,我们使用的是硬编码数据。用户资料包含各种详细信息,例如用户的位置(在本示例中设置为金门大桥的坐标)、食物和活动的偏好、应用使用指标、最近的互动和用户排名。

在生产环境中,您应该将此硬编码数据替换为对用户数据库的真实 API 调用。

def fetch_customer_profile(user_id):
    # You can replace this with a real API call in the production code
    if user_id == "user1234":
        return {
            "name": "John Doe",
            "location": {
                "latitude": 37.7955,
                "longitude": -122.4026,
            },
            "preferences": {
                "food": ["Italian", "Sushi"],
                "activities": ["Hiking", "Reading"],
            },
            "behavioral_metrics": {
                "app_usage": {
                    "daily": 2,  # hours
                    "weekly": 14  # hours
                },
                "favourite_post_categories": ["Nature", "Food", "Books"],
                "active_time": "Evening",
            },
            "recent_searches": ["Italian restaurants nearby", "Book clubs"],
            "recent_interactions": ["Liked a post about 'Best Pizzas in New York'", "Commented on a post about 'Central Park Trails'"],
            "user_rank": "Gold",  # based on some internal ranking system
        }
    else:
        return None

从 Google Places API 请求和处理数据

函数 call_google_places_api 用于从 Google Places API 请求信息,并根据给定的 place_type 和可选的 food_preference 提供前两个地点的列表。由于这是一项付费服务,我们已将此函数限制为最多两个结果以管理使用量。但是,您可以修改它以根据您的需求检索任意数量的结果。

该函数配置了硬编码位置(设置为泛美金字塔的坐标)、您的 Google API 密钥和特定请求参数。根据 place_type,它制定适当的 API 请求 URL。如果 place_type 是餐厅并且指定了 food_preference,则会将其包含在 API 请求中。

发送 GET 请求后,该函数检查响应状态。如果成功,它会处理 JSON 响应,使用 get_place_details 函数提取相关详细信息,并以人类可读的格式返回它们。如果请求失败,它会打印出错误以进行调试。

get_place_details 函数用于检索有关地点的更详细信息,给定其 place_id。它向 Google Place Details API 发送 GET 请求,并在请求成功时返回结果。如果请求失败,它会打印出错误以进行调试。

这两个函数都处理异常,并在出现问题时返回错误消息。

def get_place_details(place_id, api_key):
    URL = f"https://maps.googleapis.com/maps/api/place/details/json?place_id={place_id}&key={api_key}"
    response = requests.get(URL)
    if response.status_code == 200:
        result = json.loads(response.content)["result"]
        return result
    else:
        print(f"Google Place Details API request failed with status code {response.status_code}")
        print(f"Response content: {response.content}")
        return None
def call_google_places_api(user_id, place_type, food_preference=None):
    try:
        # Fetch customer profile
        customer_profile = fetch_customer_profile(user_id)
        if customer_profile is None:
            return "I couldn't find your profile. Could you please verify your user ID?"

        # Get location from customer profile
        lat = customer_profile["location"]["latitude"]
        lng = customer_profile["location"]["longitude"]

        API_KEY = os.getenv('GOOGLE_PLACES_API_KEY')  # retrieve API key from environment variable
        LOCATION = f"{lat},{lng}"
        RADIUS = 500  # search within a radius of 500 meters
        TYPE = place_type

        # If the place_type is restaurant and food_preference is not None, include it in the API request
        if place_type == 'restaurant' and food_preference:
            URL = f"https://maps.googleapis.com/maps/api/place/nearbysearch/json?location={LOCATION}&radius={RADIUS}&type={TYPE}&keyword={food_preference}&key={API_KEY}"
        else:
            URL = f"https://maps.googleapis.com/maps/api/place/nearbysearch/json?location={LOCATION}&radius={RADIUS}&type={TYPE}&key={API_KEY}"

        response = requests.get(URL)
        if response.status_code == 200:
            results = json.loads(response.content)["results"]
            places = []
            for place in results[:2]:  # limit to top 2 results
                place_id = place.get("place_id")
                place_details = get_place_details(place_id, API_KEY)  # Get the details of the place

                place_name = place_details.get("name", "N/A")
                place_types = next((t for t in place_details.get("types", []) if t not in ["food", "point_of_interest"]), "N/A")  # Get the first type of the place, excluding "food" and "point_of_interest"
                place_rating = place_details.get("rating", "N/A")  # Get the rating of the place
                total_ratings = place_details.get("user_ratings_total", "N/A")  # Get the total number of ratings
                place_address = place_details.get("vicinity", "N/A")  # Get the vicinity of the place

                if ',' in place_address:  # If the address contains a comma
                    street_address = place_address.split(',')[0]  # Split by comma and keep only the first part
                else:
                    street_address = place_address

                # Prepare the output string for this place
                place_info = f"{place_name} is a {place_types} located at {street_address}. It has a rating of {place_rating} based on {total_ratings} user reviews."

                places.append(place_info)

            return places
        else:
            print(f"Google Places API request failed with status code {response.status_code}")
            print(f"Response content: {response.content}")  # print out the response content for debugging
            return []
    except Exception as e:
        print(f"Error during the Google Places API call: {e}")
        return []

使用 GPT-3.5-Turbo 和 Google Places API 生成用户特定推荐

函数 provide_user_specific_recommendations 与 GPT-3.5-Turbo 和 Google Places API 交互,以提供根据用户偏好和位置量身定制的响应。

首先,它使用用户的 user_id 获取客户资料。如果找不到资料,则返回错误消息。

使用有效的资料,它会提取客户的食物偏好,然后与 OpenAI 模型交互。它提供了一个初始系统消息,向 AI 模型提供有关其角色、用户偏好以及 Google Places API 函数的使用情况的上下文。

用户输入也作为消息发送到模型,并且在 functions 参数中定义了函数 call_google_places_api,供 AI 模型根据需要调用。

最后,它处理模型的响应。如果模型对 Google Places API 进行函数调用,则会使用适当的参数执行该函数,并返回附近地点的名称。如果没有此类地点或请求无法理解,则会返回适当的错误消息。

def provide_user_specific_recommendations(user_input, user_id):
    customer_profile = fetch_customer_profile(user_id)
    if customer_profile is None:
        return "I couldn't find your profile. Could you please verify your user ID?"

    customer_profile_str = json.dumps(customer_profile)

    food_preference = customer_profile.get('preferences', {}).get('food', [])[0] if customer_profile.get('preferences', {}).get('food') else None


    response = client.chat.completions.create(
        model="gpt-3.5-turbo",
        messages=[
    {
        "role": "system",
        "content": f"You are a sophisticated AI assistant, a specialist in user intent detection and interpretation. Your task is to perceive and respond to the user's needs, even when they're expressed in an indirect or direct manner. You excel in recognizing subtle cues: for example, if a user states they are 'hungry', you should assume they are seeking nearby dining options such as a restaurant or a cafe. If they indicate feeling 'tired', 'weary', or mention a long journey, interpret this as a request for accommodation options like hotels or guest houses. However, remember to navigate the fine line of interpretation and assumption: if a user's intent is unclear or can be interpreted in multiple ways, do not hesitate to politely ask for additional clarification. Make sure to tailor your responses to the user based on their preferences and past experiences which can be found here {customer_profile_str}"
    },
    {"role": "user", "content": user_input}
],
        temperature=0,
        tools=[
            {
                "type": "function",
                "function" : {
                    "name": "call_google_places_api",
                    "description": "This function calls the Google Places API to find the top places of a specified type near a specific location. It can be used when a user expresses a need (e.g., feeling hungry or tired) or wants to find a certain type of place (e.g., restaurant or hotel).",
                    "parameters": {
                        "type": "object",
                        "properties": {
                            "place_type": {
                                "type": "string",
                                "description": "The type of place to search for."
                            }
                        }
                    },
                    "result": {
                        "type": "array",
                        "items": {
                            "type": "string"
                        }
                    }
                }
            }
        ],
    )

    print(response.choices[0].message.tool_calls)

    if response.choices[0].finish_reason=='tool_calls':
        function_call = response.choices[0].message.tool_calls[0].function
        if function_call.name == "call_google_places_api":
            place_type = json.loads(function_call.arguments)["place_type"]
            places = call_google_places_api(user_id, place_type, food_preference)
            if places:  # If the list of places is not empty
                return f"Here are some places you might be interested in: {' '.join(places)}"
            else:
                return "I couldn't find any places of interest nearby."

    return "I am sorry, but I could not understand your request."

执行用户特定推荐

执行后,该函数会获取用户资料,与 AI 模型交互,处理模型的响应,在必要时调用 Google Places API,并最终返回根据用户偏好和位置量身定制的推荐列表。打印的输出将包含这些个性化推荐。

user_id = "user1234"
user_input = "I'm hungry"
output = provide_user_specific_recommendations(user_input, user_id)
print(output)
[ChatCompletionMessageToolCall(id='call_Q1mXIi7D6GhobfE4tkruX7nB', function=Function(arguments='{\n  "place_type": "restaurant"\n}', name='call_google_places_api'), type='function')]
Here are some places you might be interested in: Sotto Mare is a restaurant located at 552 Green Street. It has a rating of 4.6 based on 3765 user reviews. Mona Lisa Restaurant is a restaurant located at 353 Columbus Avenue #3907. It has a rating of 4.4 based on 1888 user reviews.