diff --git a/sdks/python/apache_beam/ml/transforms/embeddings/huggingface.py b/sdks/python/apache_beam/ml/transforms/embeddings/huggingface.py index 56a9535b5764..c21762347453 100644 --- a/sdks/python/apache_beam/ml/transforms/embeddings/huggingface.py +++ b/sdks/python/apache_beam/ml/transforms/embeddings/huggingface.py @@ -211,10 +211,8 @@ def load_model(self): def run_inference( self, batch, session: requests.Session, inference_args=None): # HF router feature-extraction accepts inputs only (not nested options). - response = session.post( - self._config.api_url, - headers=self._config.authorization_token, - json={"inputs": batch}) + response = session.post(self._config.api_url, json={"inputs": batch}) + response.raise_for_status() return response.json() @@ -253,7 +251,7 @@ def __init__( else: self._api_url = api_url - _LOGGER.info("HuggingFace API URL: %s") + _LOGGER.info("HuggingFace API URL: %s", self._api_url) def get_token(self): return os.environ.get('HF_TOKEN') diff --git a/sdks/python/apache_beam/ml/transforms/embeddings/huggingface_test.py b/sdks/python/apache_beam/ml/transforms/embeddings/huggingface_test.py index 166827f3e907..d715eb343aa0 100644 --- a/sdks/python/apache_beam/ml/transforms/embeddings/huggingface_test.py +++ b/sdks/python/apache_beam/ml/transforms/embeddings/huggingface_test.py @@ -16,6 +16,7 @@ import contextlib import importlib.util +import json import os import shutil import sys @@ -23,10 +24,12 @@ import types import unittest import uuid +from unittest import mock import jsonpickle import numpy as np import pytest +import requests from parameterized import parameterized import apache_beam as beam @@ -476,6 +479,57 @@ def test_loading_model_from_artifacts_written_by_older_versions(self): self.assertEqual(model.model_name, DEFAULT_MODEL_NAME) +class InferenceAPIHandlerTest(unittest.TestCase): + _API_URL = 'https://example.test/embeddings' + + def _handler(self, token): + return huggingface.InferenceAPIEmbeddings( + hf_token=token, columns=['text'], + api_url=self._API_URL).get_model_handler() + + def _response(self, status_code, body): + response = requests.Response() + response.status_code = status_code + response.url = self._API_URL + response._content = json.dumps(body).encode('utf-8') + return response + + @parameterized.expand([ + ('explicit-token', 'explicit-token'), + (None, 'environment-token'), + ]) + def test_run_inference_uses_session_token(self, token, expected_token): + embeddings = [[0.1, 0.2]] + with mock.patch.dict(os.environ, {'HF_TOKEN': 'environment-token'}): + handler = self._handler(token) + with handler.load_model() as session: + with mock.patch.object(session, + 'send', + return_value=self._response(200, + embeddings)) as send: + result = handler.run_inference( + ['some text'], session, inference_args={'wait_for_model': True}) + self.assertEqual(result, embeddings) + request = send.call_args.args[0] + self.assertEqual(request.url, self._API_URL) + self.assertEqual( + request.headers['Authorization'], f'Bearer {expected_token}') + self.assertEqual(json.loads(request.body), {'inputs': ['some text']}) + + @parameterized.expand([401, 429, 503]) + def test_run_inference_raises_http_errors(self, status_code): + handler = self._handler('test-token') + response = self._response( + status_code, {'error': 'inference request failed'}) + with handler.load_model() as session: + with mock.patch.object(session, 'send', return_value=response): + with self.assertRaises(requests.HTTPError) as error: + handler.run_inference(['some text'], session) + self.assertIs(error.exception.response, response) + self.assertIn(str(status_code), str(error.exception)) + self.assertIn(self._API_URL, str(error.exception)) + + @unittest.skipIf(not _HF_TOKEN, 'HF_TOKEN environment variable not set.') class HuggingfaceInferenceAPITest(unittest.TestCase): def setUp(self):