Coverage for qreward / client / patch_openai.py: 100.00%

38 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-04-23 19:32 +0000

1from typing import Callable, List, Union 

2 

3from typing_extensions import Literal 

4 

5from openai._base_client import make_request_options 

6from openai._models import BaseModel 

7from openai._types import Omit 

8from openai._utils import is_given, maybe_transform 

9from openai.types import embedding_create_params 

10from openai.types.create_embedding_response import Embedding, Usage 

11from openai.types.embedding_model import EmbeddingModel 

12 

13 

14class HackCreateEmbeddingResponse(BaseModel): 

15 embeddings: List[Embedding] 

16 

17 model: str 

18 """The name of the model used to generate the embedding.""" 

19 

20 object: Literal["list"] 

21 """The object type, which is always "list".""" 

22 

23 usage: Usage 

24 """The usage information for the request.""" 

25 

26 

27def hack_parser( 

28 obj: HackCreateEmbeddingResponse, 

29) -> HackCreateEmbeddingResponse: 

30 if not obj.embeddings: 

31 raise ValueError("No embedding data received") 

32 

33 return obj 

34 

35 

36# Store the original method reference for unpatch support 

37_original_async_embeddings_create = None 

38 

39 

40# ===== PATCH 函数 ===== 

41def patch_openai_embeddings( 

42 custom_return_cls: type = HackCreateEmbeddingResponse, 

43 custom_parser: Callable = hack_parser, 

44) -> None: 

45 """Replace OpenAI AsyncEmbeddings.create with a custom implementation. 

46 

47 Replaces the method at runtime to return a custom Response class 

48 and use a custom parser. Can be reversed with unpatch_openai_embeddings(). 

49 

50 Args: 

51 custom_return_cls: Custom return class (BaseModel subclass) 

52 custom_parser: Custom parser function that receives and returns 

53 a custom_return_cls instance 

54 """ 

55 global _original_async_embeddings_create 

56 from openai.resources.embeddings import AsyncEmbeddings 

57 

58 # Save original method only on first patch to support idempotent unpatch. 

59 # Use getattr to handle test scenarios where AsyncEmbeddings may be mocked 

60 # with a dummy class that lacks a 'create' attribute. 

61 if _original_async_embeddings_create is None: 

62 _original_async_embeddings_create = getattr( 

63 AsyncEmbeddings, "create", None 

64 ) 

65 

66 async def patched_create( 

67 self, 

68 *, 

69 input, 

70 model: Union[str, EmbeddingModel], 

71 dimensions: int | Omit = Omit(), 

72 encoding_format: Literal["float", "base64"] | Omit = Omit(), 

73 user: str | Omit = Omit(), 

74 extra_headers=None, 

75 extra_query=None, 

76 extra_body=None, 

77 timeout=None, 

78 ): 

79 params = { 

80 "input": input, 

81 "model": model, 

82 "user": user, 

83 "dimensions": dimensions, 

84 "encoding_format": encoding_format, 

85 } 

86 if not is_given(encoding_format): 

87 params["encoding_format"] = "base64" 

88 

89 return await self._post( 

90 "/embeddings", 

91 body=maybe_transform( 

92 params, 

93 embedding_create_params.EmbeddingCreateParams, 

94 ), 

95 options=make_request_options( 

96 extra_headers=extra_headers, 

97 extra_query=extra_query, 

98 extra_body=extra_body, 

99 timeout=timeout, 

100 post_parser=custom_parser, 

101 ), 

102 cast_to=custom_return_cls, 

103 ) 

104 

105 AsyncEmbeddings.create = patched_create 

106 

107 

108def unpatch_openai_embeddings() -> None: 

109 """Restore OpenAI AsyncEmbeddings.create to its original implementation. 

110 

111 This reverses the effect of patch_openai_embeddings(). If patch has not 

112 been applied, this function is a no-op (idempotent). 

113 """ 

114 global _original_async_embeddings_create 

115 if _original_async_embeddings_create is None: 

116 return 

117 

118 from openai.resources.embeddings import AsyncEmbeddings 

119 

120 AsyncEmbeddings.create = _original_async_embeddings_create 

121 _original_async_embeddings_create = None