2023年8月11日

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

本笔记本重点介绍如何整合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功能的使用情况。

用户输入也会作为消息发送给模型,函数call_google_places_apifunctions参数中定义,供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.