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}}