xinjjj commited on
Commit
bc86a46
·
1 Parent(s): c865e94

feat(gpt): add GPT-5.4 support

Browse files
embodied_gen/utils/gpt_clients.py CHANGED
@@ -14,18 +14,21 @@
14
  # implied. See the License for the specific language governing
15
  # permissions and limitations under the License.
16
 
17
- import math
18
  import base64
19
  import logging
 
20
  import os
21
  from io import BytesIO
22
  from typing import Optional
23
 
 
24
  import yaml
25
  from openai import AzureOpenAI, OpenAI # pip install openai
26
  from PIL import Image
27
  from tenacity import (
28
  retry,
 
29
  stop_after_attempt,
30
  stop_after_delay,
31
  wait_random_exponential,
@@ -40,7 +43,12 @@ __all__ = [
40
  "GPTclient",
41
  ]
42
 
43
- CONFIG_FILE = "embodied_gen/utils/gpt_config.yaml"
 
 
 
 
 
44
 
45
 
46
  def combine_images_to_grid(
@@ -76,7 +84,36 @@ def combine_images_to_grid(
76
 
77
 
78
  class GPTclient:
79
- """A client to interact with the GPT model via OpenAI or Azure API."""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
 
81
  def __init__(
82
  self,
@@ -86,21 +123,27 @@ class GPTclient:
86
  api_version: str = None,
87
  check_connection: bool = True,
88
  verbose: bool = False,
 
89
  ):
90
  if api_version is not None:
91
  self.client = AzureOpenAI(
92
  azure_endpoint=endpoint,
93
  api_key=api_key,
94
  api_version=api_version,
 
 
95
  )
96
  else:
97
  self.client = OpenAI(
98
  base_url=endpoint,
99
  api_key=api_key,
 
 
100
  )
101
 
102
  self.endpoint = endpoint
103
  self.model_name = model_name
 
104
  self.image_formats = {".png", ".jpg", ".jpeg", ".webp", ".bmp", ".gif"}
105
  self.verbose = verbose
106
  if check_connection:
@@ -108,11 +151,18 @@ class GPTclient:
108
 
109
  logger.info(f"Using GPT model: {self.model_name}.")
110
 
 
 
 
 
 
111
  @retry(
112
- wait=wait_random_exponential(min=1, max=20),
113
- stop=(stop_after_attempt(10) | stop_after_delay(30)),
 
114
  )
115
  def completion_with_backoff(self, **kwargs):
 
116
  return self.client.chat.completions.create(**kwargs)
117
 
118
  def query(
@@ -122,19 +172,16 @@ class GPTclient:
122
  system_role: Optional[str] = None,
123
  params: Optional[dict] = None,
124
  ) -> Optional[str]:
125
- """Queries the GPT model with a text and optional image prompts.
126
 
127
  Args:
128
- text_prompt (str): The main text input that the model responds to.
129
- image_base64 (Optional[List[str]]): A list of image base64 strings
130
- or local image paths or PIL.Image to accompany the text prompt.
131
- system_role (Optional[str]): Optional system-level instructions
132
- that specify the behavior of the assistant.
133
- params (Optional[dict]): Additional parameters for GPT setting.
134
 
135
  Returns:
136
- Optional[str]: The response content generated by the model based on
137
- the prompt. Returns `None` if an error occurs.
138
  """
139
  if system_role is None:
140
  system_role = "You are a highly knowledgeable assistant specializing in physics, engineering, and object properties." # noqa
@@ -176,21 +223,49 @@ class GPTclient:
176
  }
177
  )
178
 
179
- payload = {
180
- "messages": [
181
- {"role": "system", "content": system_role},
182
- {"role": "user", "content": content_user},
183
- ],
184
- "temperature": 0.1,
185
- "max_tokens": 500,
186
- "top_p": 0.1,
187
- "frequency_penalty": 0,
188
- "presence_penalty": 0,
189
- "stop": None,
190
- "model": self.model_name,
191
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
192
 
193
  if params:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
194
  payload.update(params)
195
 
196
  response = None
@@ -208,20 +283,28 @@ class GPTclient:
208
  return response
209
 
210
  def check_connection(self) -> None:
211
- """Check whether the GPT API connection is working."""
 
 
 
 
212
  try:
213
- response = self.completion_with_backoff(
214
  messages=[
215
  {"role": "system", "content": "You are a test system."},
216
  {"role": "user", "content": "Hello"},
217
  ],
218
  model=self.model_name,
219
- temperature=0,
220
- max_tokens=100,
221
  )
222
- content = response.choices[0].message.content
223
- logger.info(f"Connection check success.")
224
- except Exception as e:
 
 
 
 
 
 
225
  raise ConnectionError(
226
  f"Failed to connect to GPT API at {self.endpoint}, "
227
  f"please check setting in `{CONFIG_FILE}` and `README`."
@@ -239,6 +322,7 @@ endpoint = os.environ.get("ENDPOINT", agent_config.get("endpoint"))
239
  api_key = os.environ.get("API_KEY", agent_config.get("api_key"))
240
  api_version = os.environ.get("API_VERSION", agent_config.get("api_version"))
241
  model_name = os.environ.get("MODEL_NAME", agent_config.get("model_name"))
 
242
 
243
  GPT_CLIENT = GPTclient(
244
  endpoint=endpoint,
@@ -246,4 +330,10 @@ GPT_CLIENT = GPTclient(
246
  api_version=api_version,
247
  model_name=model_name,
248
  check_connection=False,
 
249
  )
 
 
 
 
 
 
14
  # implied. See the License for the specific language governing
15
  # permissions and limitations under the License.
16
 
17
+
18
  import base64
19
  import logging
20
+ import math
21
  import os
22
  from io import BytesIO
23
  from typing import Optional
24
 
25
+ import openai
26
  import yaml
27
  from openai import AzureOpenAI, OpenAI # pip install openai
28
  from PIL import Image
29
  from tenacity import (
30
  retry,
31
+ retry_if_not_exception_type,
32
  stop_after_attempt,
33
  stop_after_delay,
34
  wait_random_exponential,
 
43
  "GPTclient",
44
  ]
45
 
46
+ _CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
47
+ CONFIG_FILE = os.path.join(_CURRENT_DIR, "gpt_config.yaml")
48
+ DEFAULT_GPT_TIMEOUT = float(os.environ.get("GPT_TIMEOUT", 120))
49
+ # GPT-5.x counts reasoning tokens against this cap, so it must be high
50
+ # enough to leave room for both reasoning and the visible reply.
51
+ GPT5_DEFAULT_MAX_COMPLETION_TOKENS = 8192
52
 
53
 
54
  def combine_images_to_grid(
 
84
 
85
 
86
  class GPTclient:
87
+ """A client to interact with GPT models via OpenAI or Azure API.
88
+
89
+ Supports text and image prompts, connection checking, and configurable parameters.
90
+
91
+ Args:
92
+ endpoint (str): API endpoint URL.
93
+ api_key (str): API key for authentication.
94
+ model_name (str, optional): Model name to use.
95
+ api_version (str, optional): API version (for Azure).
96
+ check_connection (bool, optional): Whether to check API connection.
97
+ verbose (bool, optional): Enable verbose logging.
98
+ timeout (float, optional): Max seconds for a single GPT request.
99
+
100
+ Example:
101
+ ```sh
102
+ export ENDPOINT="https://yfb-openai-sweden.openai.azure.com"
103
+ export API_KEY="xxxxxx"
104
+ export API_VERSION="2025-03-01-preview"
105
+ export MODEL_NAME="yfb-gpt-4o-sweden"
106
+ ```
107
+ ```py
108
+ from embodied_gen.utils.gpt_clients import GPT_CLIENT
109
+
110
+ response = GPT_CLIENT.query("Describe the physics of a falling apple.")
111
+ response = GPT_CLIENT.query(
112
+ text_prompt="Describe the content in each image."
113
+ image_base64=["path/to/image1.png", "path/to/image2.jpg"],
114
+ )
115
+ ```
116
+ """
117
 
118
  def __init__(
119
  self,
 
123
  api_version: str = None,
124
  check_connection: bool = True,
125
  verbose: bool = False,
126
+ timeout: float = DEFAULT_GPT_TIMEOUT,
127
  ):
128
  if api_version is not None:
129
  self.client = AzureOpenAI(
130
  azure_endpoint=endpoint,
131
  api_key=api_key,
132
  api_version=api_version,
133
+ timeout=timeout,
134
+ max_retries=0,
135
  )
136
  else:
137
  self.client = OpenAI(
138
  base_url=endpoint,
139
  api_key=api_key,
140
+ timeout=timeout,
141
+ max_retries=0,
142
  )
143
 
144
  self.endpoint = endpoint
145
  self.model_name = model_name
146
+ self.timeout = timeout
147
  self.image_formats = {".png", ".jpg", ".jpeg", ".webp", ".bmp", ".gif"}
148
  self.verbose = verbose
149
  if check_connection:
 
151
 
152
  logger.info(f"Using GPT model: {self.model_name}.")
153
 
154
+ @staticmethod
155
+ def _is_gpt5_model(model_name: str) -> bool:
156
+ name = (model_name or "").lower()
157
+ return "gpt-5" in name or "gpt5" in name
158
+
159
  @retry(
160
+ retry=retry_if_not_exception_type(openai.BadRequestError),
161
+ wait=wait_random_exponential(min=1, max=10),
162
+ stop=stop_after_attempt(5) | stop_after_delay(DEFAULT_GPT_TIMEOUT),
163
  )
164
  def completion_with_backoff(self, **kwargs):
165
+ """Performs a chat completion request with retry/backoff."""
166
  return self.client.chat.completions.create(**kwargs)
167
 
168
  def query(
 
172
  system_role: Optional[str] = None,
173
  params: Optional[dict] = None,
174
  ) -> Optional[str]:
175
+ """Queries the GPT model with text and optional image prompts.
176
 
177
  Args:
178
+ text_prompt (str): Main text input.
179
+ image_base64 (Optional[list[str | Image.Image]], optional): List of image base64 strings, file paths, or PIL Images.
180
+ system_role (Optional[str], optional): System-level instructions.
181
+ params (Optional[dict], optional): Additional GPT parameters.
 
 
182
 
183
  Returns:
184
+ Optional[str]: Model response content, or None if error.
 
185
  """
186
  if system_role is None:
187
  system_role = "You are a highly knowledgeable assistant specializing in physics, engineering, and object properties." # noqa
 
223
  }
224
  )
225
 
226
+ is_gpt5 = self._is_gpt5_model(self.model_name)
227
+ if is_gpt5:
228
+ # GPT-5.x only supports default temperature/top_p and uses
229
+ # `max_completion_tokens` instead of `max_tokens`.
230
+ payload = {
231
+ "messages": [
232
+ {"role": "system", "content": system_role},
233
+ {"role": "user", "content": content_user},
234
+ ],
235
+ "max_completion_tokens": GPT5_DEFAULT_MAX_COMPLETION_TOKENS,
236
+ "model": self.model_name,
237
+ }
238
+ else:
239
+ payload = {
240
+ "messages": [
241
+ {"role": "system", "content": system_role},
242
+ {"role": "user", "content": content_user},
243
+ ],
244
+ "temperature": 0.1,
245
+ "max_tokens": 500,
246
+ "top_p": 0.1,
247
+ "frequency_penalty": 0,
248
+ "presence_penalty": 0,
249
+ "stop": None,
250
+ "model": self.model_name,
251
+ }
252
 
253
  if params:
254
+ params = dict(params)
255
+ if is_gpt5:
256
+ # GPT-5.x rejects custom temperature/top_p/penalty/stop and
257
+ # uses `max_completion_tokens` instead of `max_tokens`.
258
+ if "max_tokens" in params and "max_completion_tokens" not in params:
259
+ params["max_completion_tokens"] = params.pop("max_tokens")
260
+ for k in (
261
+ "temperature",
262
+ "top_p",
263
+ "frequency_penalty",
264
+ "presence_penalty",
265
+ "stop",
266
+ "max_tokens",
267
+ ):
268
+ params.pop(k, None)
269
  payload.update(params)
270
 
271
  response = None
 
283
  return response
284
 
285
  def check_connection(self) -> None:
286
+ """Checks whether the GPT API connection is working.
287
+
288
+ Raises:
289
+ ConnectionError: If connection fails.
290
+ """
291
  try:
292
+ probe_kwargs = dict(
293
  messages=[
294
  {"role": "system", "content": "You are a test system."},
295
  {"role": "user", "content": "Hello"},
296
  ],
297
  model=self.model_name,
 
 
298
  )
299
+ if self._is_gpt5_model(self.model_name):
300
+ probe_kwargs["max_completion_tokens"] = 100
301
+ else:
302
+ probe_kwargs["temperature"] = 0
303
+ probe_kwargs["max_tokens"] = 100
304
+ response = self.completion_with_backoff(**probe_kwargs)
305
+ response.choices[0].message.content
306
+ logger.info("Connection check success.")
307
+ except Exception:
308
  raise ConnectionError(
309
  f"Failed to connect to GPT API at {self.endpoint}, "
310
  f"please check setting in `{CONFIG_FILE}` and `README`."
 
322
  api_key = os.environ.get("API_KEY", agent_config.get("api_key"))
323
  api_version = os.environ.get("API_VERSION", agent_config.get("api_version"))
324
  model_name = os.environ.get("MODEL_NAME", agent_config.get("model_name"))
325
+ timeout = DEFAULT_GPT_TIMEOUT
326
 
327
  GPT_CLIENT = GPTclient(
328
  endpoint=endpoint,
 
330
  api_version=api_version,
331
  model_name=model_name,
332
  check_connection=False,
333
+ timeout=timeout,
334
  )
335
+
336
+
337
+ if __name__ == "__main__":
338
+ response = GPT_CLIENT.query("What is the capital of China?")
339
+ print(f"Response: {response}")
embodied_gen/utils/gpt_config.yaml CHANGED
@@ -1,5 +1,5 @@
1
  # config.yaml
2
- agent_type: "qwen2.5-vl" # gpt-4o or qwen2.5-vl
3
 
4
  gpt-4o:
5
  endpoint: https://xxx.openai.azure.com
@@ -7,6 +7,12 @@ gpt-4o:
7
  api_version: 2025-xx-xx
8
  model_name: yfb-gpt-4o
9
 
 
 
 
 
 
 
10
  qwen2.5-vl:
11
  endpoint: https://openrouter.ai/api/v1
12
  api_key: sk-or-v1-xxx
 
1
  # config.yaml
2
+ agent_type: "gpt-5.4" # gpt-4o, gpt-5.4 or qwen2.5-vl
3
 
4
  gpt-4o:
5
  endpoint: https://xxx.openai.azure.com
 
7
  api_version: 2025-xx-xx
8
  model_name: yfb-gpt-4o
9
 
10
+ gpt-5.4:
11
+ endpoint: https://yfb-openai-sweden.openai.azure.com/
12
+ api_key: xxx
13
+ api_version: 2024-12-01-preview
14
+ model_name: gpt-5.4
15
+
16
  qwen2.5-vl:
17
  endpoint: https://openrouter.ai/api/v1
18
  api_key: sk-or-v1-xxx