99from httpx import Timeout
1010from inline_snapshot import Is , snapshot
1111from pydantic import BaseModel
12+ from pytest_mock import MockerFixture
1213from typing_extensions import TypedDict
1314
1415from pydantic_ai import (
4142)
4243from pydantic_ai .agent import Agent
4344from pydantic_ai .builtin_tools import CodeExecutionTool , ImageGenerationTool , UrlContextTool , WebSearchTool
44- from pydantic_ai .exceptions import ModelRetry , UnexpectedModelBehavior , UserError
45+ from pydantic_ai .exceptions import ModelHTTPError , ModelRetry , UnexpectedModelBehavior , UserError
4546from pydantic_ai .messages import (
4647 BuiltinToolCallEvent , # pyright: ignore[reportDeprecated]
4748 BuiltinToolResultEvent , # pyright: ignore[reportDeprecated]
5455from ..parts_from_messages import part_types_from_messages
5556
5657with try_import () as imports_successful :
58+ from google .genai import errors
5759 from google .genai .types import (
5860 GenerateContentResponse ,
5961 GenerateContentResponseUsageMetadata ,
@@ -2929,3 +2931,96 @@ async def test_google_vertexai_image_generation(allow_model_requests: None, vert
29292931 identifier = 'f3edd8' ,
29302932 )
29312933 )
2934+
2935+
2936+ # API 에러 테스트 데이터
2937+ @pytest .mark .parametrize (
2938+ 'error_class,error_response,expected_status' ,
2939+ [(
2940+ errors .ServerError ,
2941+ {
2942+ 'error' : {
2943+ 'code' : 503 ,
2944+ 'message' : 'The service is currently unavailable.' ,
2945+ 'status' : 'UNAVAILABLE'
2946+ }
2947+ },
2948+ 503
2949+ ),
2950+ (
2951+ errors .ClientError ,
2952+ {
2953+ 'error' : {
2954+ 'code' : 400 ,
2955+ 'message' : 'Invalid request parameters' ,
2956+ 'status' : 'INVALID_ARGUMENT'
2957+ }
2958+ },
2959+ 400
2960+ ),
2961+ (
2962+ errors .ClientError ,
2963+ {
2964+ 'error' : {
2965+ 'code' : 429 ,
2966+ 'message' : 'Rate limit exceeded' ,
2967+ 'status' : 'RESOURCE_EXHAUSTED'
2968+ }
2969+ },
2970+ 429
2971+ ),
2972+ ])
2973+ async def test_google_api_errors_are_handled (
2974+ allow_model_requests : None ,
2975+ google_provider : GoogleProvider ,
2976+ mocker : MockerFixture ,
2977+ error_class : type [errors .APIError ],
2978+ error_response : dict ,
2979+ expected_status : int ,
2980+ ):
2981+ model = GoogleModel ('gemini-1.5-flash' , provider = google_provider )
2982+ mocked_error = error_class (expected_status , error_response )
2983+ mocker .patch .object (
2984+ model .client .aio .models ,
2985+ 'generate_content' ,
2986+ side_effect = mocked_error
2987+ )
2988+
2989+ agent = Agent (model = model )
2990+
2991+ with pytest .raises (ModelHTTPError ) as exc_info :
2992+ await agent .run ('This prompt will trigger the mocked error.' )
2993+
2994+ assert exc_info .value .status_code == expected_status
2995+ assert error_response ['error' ]['message' ] in str (exc_info .value .body )
2996+
2997+
2998+ @pytest .mark .parametrize (
2999+ 'error_class,expected_status' ,
3000+ [
3001+ (errors .UnknownFunctionCallArgumentError , 400 ),
3002+ (errors .UnsupportedFunctionError , 404 ),
3003+ (errors .FunctionInvocationError , 400 ),
3004+ (errors .UnknownApiResponseError , 422 ),
3005+ ])
3006+ async def test_google_specific_errors_are_handled (
3007+ allow_model_requests : None ,
3008+ google_provider : GoogleProvider ,
3009+ mocker : MockerFixture ,
3010+ error_class : type [errors .APIError ],
3011+ expected_status : int ,
3012+ ):
3013+
3014+ model = GoogleModel ('gemini-1.5-flash' , provider = google_provider )
3015+ mocked_error = error_class
3016+ mocker .patch .object (
3017+ model .client .aio .models ,
3018+ 'generate_content' ,
3019+ side_effect = mocked_error
3020+ )
3021+
3022+ agent = Agent (model = model )
3023+
3024+ with pytest .raises (ModelHTTPError ) as exc_info :
3025+ await agent .run ('This prompt will trigger the mocked error.' )
3026+ assert exc_info .value .status_code == expected_status
0 commit comments