Google HomeGraph同步请求404:未收到初始同步意图问题求助
问题背景
开发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

