Is dict equivalent to Object? When I make a request with a field of type dict, I get a 400 error.
openai.BadRequestError: Error code: 400 - {'error': {'message': "Invalid schema for response_format 'Invoice': In context=(), 'required' is required to be supplied and to be an array including every key in properties. Extra required key 'invoice_number' supplied.", 'type': 'invalid_request_error', 'param': 'response_format', 'code': None}}
I am thinking that dict would simply temporarily turn off restrictions, so it should be “easy” to implement.
It seems that dict indeed corresponds to object.
from pydantic import BaseModel, Field
from rich import print as pprint
class Invoice(BaseModel):
invoice_number: dict = Field(description="Unique identifier for the invoice")
pprint(Invoice.model_json_schema())
{
'properties': {
'invoice_number': {
'description': 'Unique identifier for the invoice',
'title': 'Invoice Number',
'type': 'object'
}
},
'required': ['invoice_number'],
'title': 'Invoice',
'type': 'object'
}
For a full stack trace:
from langchain_openai import AzureChatOpenAI
llm = AzureChatOpenAI(azure_deployment="gpt-4o", api_version="2024-08-01-preview")
llm.with_structured_output(Invoice, method="json_schema").invoke("")
---------------------------------------------------------------------------
BadRequestError Traceback (most recent call last)
Cell In[2], line 4
1 from langchain_openai import AzureChatOpenAI
3 llm = AzureChatOpenAI(azure_deployment="gpt-4o", api_version="2024-08-01-preview")
----> 4 llm.with_structured_output(Invoice, method="json_schema").invoke("")
File c:\Users\BogdanPechounov\miniconda3\envs\ml-3.10\lib\site-packages\langchain_core\runnables\base.py:3027, in RunnableSequence.invoke(self, input, config, **kwargs)
3025 context.run(_set_config_context, config)
3026 if i == 0:
-> 3027 input = context.run(step.invoke, input, config, **kwargs)
3028 else:
3029 input = context.run(step.invoke, input, config)
File c:\Users\BogdanPechounov\miniconda3\envs\ml-3.10\lib\site-packages\langchain_core\runnables\base.py:5365, in RunnableBindingBase.invoke(self, input, config, **kwargs)
5359 def invoke(
5360 self,
5361 input: Input,
5362 config: Optional[RunnableConfig] = None,
5363 **kwargs: Optional[Any],
5364 ) -> Output:
-> 5365 return self.bound.invoke(
5366 input,
5367 self._merge_configs(config),
5368 **{**self.kwargs, **kwargs},
5369 )
File c:\Users\BogdanPechounov\miniconda3\envs\ml-3.10\lib\site-packages\langchain_core\language_models\chat_models.py:307, in BaseChatModel.invoke(self, input, config, stop, **kwargs)
296 def invoke(
297 self,
298 input: LanguageModelInput,
(...)
302 **kwargs: Any,
303 ) -> BaseMessage:
304 config = ensure_config(config)
305 return cast(
306 ChatGeneration,
--> 307 self.generate_prompt(
308 [self._convert_input(input)],
309 stop=stop,
310 callbacks=config.get("callbacks"),
311 tags=config.get("tags"),
312 metadata=config.get("metadata"),
313 run_name=config.get("run_name"),
314 run_id=config.pop("run_id", None),
315 **kwargs,
316 ).generations[0][0],
317 ).message
File c:\Users\BogdanPechounov\miniconda3\envs\ml-3.10\lib\site-packages\langchain_core\language_models\chat_models.py:843, in BaseChatModel.generate_prompt(self, prompts, stop, callbacks, **kwargs)
835 def generate_prompt(
836 self,
837 prompts: list[PromptValue],
(...)
840 **kwargs: Any,
841 ) -> LLMResult:
842 prompt_messages = [p.to_messages() for p in prompts]
--> 843 return self.generate(prompt_messages, stop=stop, callbacks=callbacks, **kwargs)
File c:\Users\BogdanPechounov\miniconda3\envs\ml-3.10\lib\site-packages\langchain_core\language_models\chat_models.py:683, in BaseChatModel.generate(self, messages, stop, callbacks, tags, metadata, run_name, run_id, **kwargs)
680 for i, m in enumerate(messages):
681 try:
682 results.append(
--> 683 self._generate_with_cache(
684 m,
685 stop=stop,
686 run_manager=run_managers[i] if run_managers else None,
687 **kwargs,
688 )
689 )
690 except BaseException as e:
691 if run_managers:
File c:\Users\BogdanPechounov\miniconda3\envs\ml-3.10\lib\site-packages\langchain_core\language_models\chat_models.py:908, in BaseChatModel._generate_with_cache(self, messages, stop, run_manager, **kwargs)
906 else:
907 if inspect.signature(self._generate).parameters.get("run_manager"):
--> 908 result = self._generate(
909 messages, stop=stop, run_manager=run_manager, **kwargs
910 )
911 else:
912 result = self._generate(messages, stop=stop, **kwargs)
File c:\Users\BogdanPechounov\miniconda3\envs\ml-3.10\lib\site-packages\langchain_openai\chat_models\base.py:683, in BaseChatOpenAI._generate(self, messages, stop, run_manager, **kwargs)
678 warnings.warn(
679 "Cannot currently include response headers when response_format is "
680 "specified."
681 )
682 payload.pop("stream")
--> 683 response = self.root_client.beta.chat.completions.parse(**payload)
684 elif self.include_response_headers:
685 raw_response = self.client.with_raw_response.create(**payload)
File c:\Users\BogdanPechounov\miniconda3\envs\ml-3.10\lib\site-packages\openai\resources\beta\chat\completions.py:156, in Completions.parse(self, messages, model, audio, response_format, frequency_penalty, function_call, functions, logit_bias, logprobs, max_completion_tokens, max_tokens, metadata, modalities, n, parallel_tool_calls, prediction, presence_penalty, seed, service_tier, stop, store, stream_options, temperature, tool_choice, tools, top_logprobs, top_p, user, extra_headers, extra_query, extra_body, timeout)
149 def parser(raw_completion: ChatCompletion) -> ParsedChatCompletion[ResponseFormatT]:
150 return _parse_chat_completion(
151 response_format=response_format,
152 chat_completion=raw_completion,
153 input_tools=tools,
154 )
--> 156 return self._post(
157 "/chat/completions",
158 body=maybe_transform(
159 {
160 "messages": messages,
161 "model": model,
162 "audio": audio,
163 "frequency_penalty": frequency_penalty,
164 "function_call": function_call,
165 "functions": functions,
166 "logit_bias": logit_bias,
167 "logprobs": logprobs,
168 "max_completion_tokens": max_completion_tokens,
169 "max_tokens": max_tokens,
170 "metadata": metadata,
171 "modalities": modalities,
172 "n": n,
173 "parallel_tool_calls": parallel_tool_calls,
174 "prediction": prediction,
175 "presence_penalty": presence_penalty,
176 "response_format": _type_to_response_format(response_format),
177 "seed": seed,
178 "service_tier": service_tier,
179 "stop": stop,
180 "store": store,
181 "stream": False,
182 "stream_options": stream_options,
183 "temperature": temperature,
184 "tool_choice": tool_choice,
185 "tools": tools,
186 "top_logprobs": top_logprobs,
187 "top_p": top_p,
188 "user": user,
189 },
190 completion_create_params.CompletionCreateParams,
191 ),
192 options=make_request_options(
193 extra_headers=extra_headers,
194 extra_query=extra_query,
195 extra_body=extra_body,
196 timeout=timeout,
197 post_parser=parser,
198 ),
199 # we turn the `ChatCompletion` instance into a `ParsedChatCompletion`
200 # in the `parser` function above
201 cast_to=cast(Type[ParsedChatCompletion[ResponseFormatT]], ChatCompletion),
202 stream=False,
203 )
File c:\Users\BogdanPechounov\miniconda3\envs\ml-3.10\lib\site-packages\openai\_base_client.py:1278, in SyncAPIClient.post(self, path, cast_to, body, options, files, stream, stream_cls)
1264 def post(
1265 self,
1266 path: str,
(...)
1273 stream_cls: type[_StreamT] | None = None,
1274 ) -> ResponseT | _StreamT:
1275 opts = FinalRequestOptions.construct(
1276 method="post", url=path, json_data=body, files=to_httpx_files(files), **options
1277 )
-> 1278 return cast(ResponseT, self.request(cast_to, opts, stream=stream, stream_cls=stream_cls))
File c:\Users\BogdanPechounov\miniconda3\envs\ml-3.10\lib\site-packages\openai\_base_client.py:955, in SyncAPIClient.request(self, cast_to, options, remaining_retries, stream, stream_cls)
952 else:
953 retries_taken = 0
--> 955 return self._request(
956 cast_to=cast_to,
957 options=options,
958 stream=stream,
959 stream_cls=stream_cls,
960 retries_taken=retries_taken,
961 )
File c:\Users\BogdanPechounov\miniconda3\envs\ml-3.10\lib\site-packages\openai\_base_client.py:1059, in SyncAPIClient._request(self, cast_to, options, retries_taken, stream, stream_cls)
1056 err.response.read()
1058 log.debug("Re-raising status error")
-> 1059 raise self._make_status_error_from_response(err.response) from None
1061 return self._process_response(
1062 cast_to=cast_to,
1063 options=options,
(...)
1067 retries_taken=retries_taken,
1068 )
BadRequestError: Error code: 400 - {'error': {'message': "Invalid schema for response_format 'Invoice': In context=(), 'required' is required to be supplied and to be an array including every key in properties. Extra required key 'invoice_number' supplied.", 'type': 'invalid_request_error', 'param': 'response_format', 'code': None}}
