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
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-23 19:32 +0000
1from typing import Callable, List, Union
3from typing_extensions import Literal
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
14class HackCreateEmbeddingResponse(BaseModel):
15 embeddings: List[Embedding]
17 model: str
18 """The name of the model used to generate the embedding."""
20 object: Literal["list"]
21 """The object type, which is always "list"."""
23 usage: Usage
24 """The usage information for the request."""
27def hack_parser(
28 obj: HackCreateEmbeddingResponse,
29) -> HackCreateEmbeddingResponse:
30 if not obj.embeddings:
31 raise ValueError("No embedding data received")
33 return obj
36# Store the original method reference for unpatch support
37_original_async_embeddings_create = None
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.
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().
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
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 )
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"
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 )
105 AsyncEmbeddings.create = patched_create
108def unpatch_openai_embeddings() -> None:
109 """Restore OpenAI AsyncEmbeddings.create to its original implementation.
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
118 from openai.resources.embeddings import AsyncEmbeddings
120 AsyncEmbeddings.create = _original_async_embeddings_create
121 _original_async_embeddings_create = None