利用Google Places API和客户画像进行地点搜索:函数调用实战指南

本教程面向有编程基础的开发者,深入讲解如何将Google Places API与自定义客户画像结合,通过OpenAI函数调用实现个性化地点推荐。文章涵盖客户画像构建、API集成、函数调用机制,并提供完整代码示例、环境配置、常见错误排查及生产环境优化建议,帮助开发者掌握AI工程实践中的关键技能。

技术背景

在AI驱动的应用开发中,个性化推荐是提升用户体验的核心。传统地点搜索仅基于位置和关键词,缺乏对用户偏好的理解。通过引入客户画像(如食物偏好、活动兴趣、预算等),结合Google Places API的实时数据,并利用大语言模型(如GPT-3.5-Turbo)的函数调用能力,可以自动解析用户意图(如“我饿了”),动态调用API并返回定制化结果。此方案适用于旅游推荐、本地生活服务、智能助手等场景,但受限于API调用成本、数据准确性和用户画像的完整性。

核心原理

核心原理是:1) 客户画像提供用户上下文(位置、偏好、行为指标),用于个性化搜索;2) Google Places API提供附近地点的实时数据(类型、评分、地址等);3) OpenAI函数调用允许模型在对话中识别用户意图,并结构化地调用外部函数(如call_google_places_api),将自然语言转化为API请求。数据流为:用户输入→GPT模型解析意图→生成函数调用参数→执行Google Places API→返回结果→模型整合输出。关键设计决策包括:将API密钥存储在环境变量中、限制返回结果数量以控制成本、使用Place Details API获取更详细信息。

架构分析

系统由三部分组成:1) 客户画像模块(fetch_customer_profile)模拟数据库查询,返回用户JSON;2) Google Places API集成模块(call_google_places_api和get_place_details)负责构建请求、处理响应;3) 推荐引擎(provide_user_specific_recommendations)结合OpenAI模型,通过tools参数定义函数,实现意图识别和函数调用。设计思想是模块化、解耦,便于替换真实API和扩展其他API。可修改点包括:调整搜索半径、结果数量、添加更多偏好维度(如预算、评分阈值)。

完整实战教程

本笔记本围绕Google Places API与自定义用户画像的集成,以增强基于位置的搜索。我们的方法是结合Google Places API与用户偏好,旨在使地点发现更加个性化和相关。请注意,虽然我们在此实例中专注于Google Places API,但您还可以探索并类似地应用许多其他API。

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

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

请注意,虽然此系统高度通用,但其有效性可能因用户偏好和可用地点数据而异。出于本笔记本的目的,客户数据是虚构的,位置是硬编码的。

设置

Google Places API

要使用 Google Places 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)

常见问题

常见错误:1) API密钥未设置或无效,导致401错误,需检查环境变量GOOGLE_PLACES_API_KEY;2) 请求超时或配额耗尽,可增加重试机制和错误日志;3) 函数调用参数解析失败,需确保模型返回的arguments是合法JSON;4) 用户画像缺失,返回错误提示;5) 地址解析错误,如vicinity字段可能包含逗号,需处理。排查方法:打印响应状态码和内容,使用调试模式查看tool_calls。生产优化:使用缓存减少API调用,异步处理请求,设置更精细的搜索半径和类型,结合用户实时位置。

企业应用扩展

可扩展方向:1) 集成更多Google API(如Directions、Geocoding)或第三方API(如Yelp、Foursquare);2) 使用向量数据库存储用户画像,实现动态更新;3) 引入多轮对话,支持复杂查询(如“找一家评分高于4.5的意大利餐厅”);4) 添加推荐结果排序算法,结合用户评分和距离;5) 部署为微服务,使用容器化技术(Docker)和API网关管理。

总结

本教程面向有编程基础的开发者,深入讲解如何将Google Places API与自定义客户画像结合,通过OpenAI函数调用实现个性化地点推荐。文章涵盖客户画像构建、API集成、函数调用机制,并提供完整代码示例、环境配置、常见错误排查及生产环境优化建议,帮助开发者掌握AI工程实践中的关键技能。

内容来源与验证

继续系统学习

站内还有更多完整实战教程

加入会员,解锁全部教程、完整代码与持续更新内容。

加入会员
查看全部API 与工程实践教程