You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Google HomeGraph同步请求404:未收到初始同步意图问题求助

Google Home HomeGraph同步问题排查

问题背景

开发Google Home账号关联与HomeGraph API同步功能,采用Python Flask+ngrok搭建本地服务,已完成用户OAuth2授权(使用openid、https://www.googleapis.com/auth/userinfo.profile、https://www.googleapis.com/auth/userinfo.email范围,因HomeGraph用户范围限制改用服务账号调用API)。完成授权后未收到action.devices.SYNC初始同步意图,调用devices:requestSync接口返回404错误,提示Requested entity was not found,核心原因是agent_user_id未与服务建立关联,导致无法完成设备同步。

核心错误信息

{
  "error": {
    "code": 404,
    "message": "Requested entity was not found.",
    "status": "NOT_FOUND"
  }
}

相关代码片段

请求同步函数

def request_sync():
    creds = service_account.Credentials.from_service_account_file(
        'filepath',
        scopes=['https://www.googleapis.com/auth/homegraph']
    )

    auth_request = google.auth.transport.requests.Request()
    creds.refresh(auth_request)
    # Define the URL for the HomeGraph API endpoint
    url = "https://homegraph.googleapis.com/v1/devices:requestSync"

    # Define the headers for the request
    headers = {
        "Authorization": "Bearer " + creds.token,
        "Content-Type": "application/json"
    }
     # Generate a unique requestId
    request_id = str(uuid.uuid4())

    agent_user_id = session.get('agent_user_id')
    # Define the body of the request
    body = {
        "agentUserId": agent_user_id,
        "async": True  # Set this to False if you want a synchronous request
    }
        # Print the body of the request for debugging
    print("Request Body:")
    print(json.dumps(body, indent=4))

    # Send the POST request
    response = requests.post(url, headers=headers, json=body)

    # Check if the request was successful
    if response.status_code == 200:
        print("Sync request was successful.")
    else:
        print("Failed to send sync request: {}".format(response.content))
        
        # Print more detailed error information for debugging
        print("Response Status Code: {}".format(response.status_code))
        print("Response Text: {}".format(response.text))
        try:
            print("Response JSON: {}".format(json.dumps(response.json(), indent=4)))
        except ValueError:
            print("Response could not be parsed as JSON.")
        return response.text

意图处理代码

@app.route('/', methods=['POST'])
def handle_request():
    global current_scene  # Declare the variable as global
    global agent_user_id
    data = request.get_json()
    intent = data['inputs'][0]['intent']

    if intent == 'action.devices.SYNC':
        print(intent)
        redirect(url_for('sync'))

    return json.dumps({"status": "success"})

OAuth2授权流程代码

@app.route('/callback')
def callback():
    print('Callback route was called')
    global session
    # Specify the state when creating the flow in the callback so that it can
    # verified in the authorization server response.
    #Many flask functions as imported at top
    state = session['state']

    flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file(
        CLIENT_SECRETS_FILE, scopes=SCOPES, state=state)
    flow.redirect_uri = "https://903e-2607-fea8-ff01-7fe9-5c8f-a50d-dd11-2c8.ngrok-free.app/callback"

    # Use the authorization server's response to fetch the OAuth 2.0 tokens. (this url is the response from google in our authrisation function where it redirects to google.  Google then responds to this oauth2 callback so we are passing that authorisation response exactly to get the token for making authenticated requests)
    authorization_response = request.url
    print(state)
    flow.fetch_token(authorization_response=authorization_response)

    # Store credentials in the session.
    credentials = flow.credentials
    # Convert credentials to a dictionary and store it back in credentials
    credentials = credentials_to_dict(credentials)

    session['credentials'] = credentials
    # Set agent_user_id in session after OAuth 2.0 flow is complete.
   
    user_info = get_user_info(credentials['token'])
    session['agent_user_id'] = user_info['sub']  # unique id for signed in account
    print(session['agent_user_id'])
    return request_sync()

@app.route('/authorize')
def authorize():
    print('Authorize route was called')
    # Create flow instance to manage the OAuth 2.0 Authorization Grant Flow steps.
    flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file(
        CLIENT_SECRETS_FILE, scopes=SCOPES)

    flow.redirect_uri = "https://903e-2607-fea8-ff01-7fe9-5c8f-a50d-dd11-2c8.ngrok-free.app/callback"
    #creates auth url with link to google and returns it as well as the included state to variables
    authorization_url, state = flow.authorization_url(
        access_type='offline',
        include_granted_scopes='true',
        prompt='consent')

    global session
    session['state'] = state
    print(state)
   
    return redirect(authorization_url)

def credentials_to_dict(credentials):
    return {
        'token': credentials.token,
        'refresh_token': credentials.refresh_token,
        'token_uri': credentials.token_uri,
        'client_id': credentials.client_id,
        'client_secret': credentials.client_secret,
        'scopes': credentials.scopes
    }

同步意图响应代码

@app.route('/sync')
def sync():
    creds = service_account.Credentials.from_service_account_file(
        'filepath',
        scopes=['https://www.googleapis.com/auth/homegraph']
    )

    auth_request = google.auth.transport.requests.Request()
    creds.refresh(auth_request)
    # Define the URL for the HomeGraph API endpoint
    url = "https://homegraph.googleapis.com/v1/devices:sync"

    scenes = []
    with open('Scenes.txt', 'r') as file:
        for line in file:
            scene = json.loads(line.strip())  
            scenes.append(scene)

    # Generate a unique requestId
    request_id = str(uuid.uuid4())
    # Define the headers for the request
    headers = {
        "Authorization": "Bearer " + creds.token,
        "Content-Type": "application/json"
    }

    agent_user_id = session.get('agent_user_id')
    # Define the body of the response
    body = {
        "requestId": request_id,
        "payload": {
            "agentUserId": agent_user_id,
            "devices": scenes
        }
    }
    # Return the response
    requests.post(url, headers=headers, json=body) # Send the POST request
    print (body)
    return body

排查关键点

  • 意图处理路由逻辑错误:handle_request函数中收到action.devices.SYNC意图后,执行redirect(url_for('sync'))但未返回该重定向结果,而是直接返回{"status": "success"}。Google无法获取到SYNC响应,导致无法建立agent_user_id与服务的关联。修复方法:将redirect(url_for('sync'))改为return redirect(url_for('sync')),或者直接在该分支中构造SYNC响应返回。

  • Sync接口调用方式错误:sync函数中主动POST到devices:sync接口是错误的。正确流程是:当Google向你的服务发送action.devices.SYNC意图时,你需要返回符合格式的SYNC响应,而不是主动调用该接口。应删除requests.post(url, headers=headers, json=body),直接返回构造好的body作为响应。

  • agent_user_id关联时机错误:OAuth授权完成后直接调用requestSync,此时Google尚未通过SYNC响应建立该agent_user_id的关联记录,导致404错误。必须先确保Google成功获取到SYNC响应,建立关联后,才能调用requestSync触发同步。

  • 服务账号权限配置:确认服务账号已在Google Cloud Console中被授予HomeGraph API的访问权限,并且已启用HomeGraph API服务。

  • ngrok地址一致性:检查OAuth回调地址、Flask的webhook地址(即handle_request的路由地址)是否与Google Action Console中配置的服务地址完全一致,包括协议(https)、域名和路径。

内容的提问来源于stack exchange,提问作者GameStation

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 03:34:52