In this lesson, you'll learn how you can use your Pydantic data models directly in your API call to an LLM provider. And what you'll see is that this gives you a much cleaner and more reliable way to get a structured response than what we were doing in the last lesson of all that error handling and retry logic that you had to build. And what you're going to do here is to play with this across a number of different frameworks and LLM providers. And with that, you'll get firsthand experience with the fact that it kind of doesn't matter which LLM provider you're working with or which agent framework. Really, the way in which you're going to use pydantic data models to get the structure that you want in the response is essentially the same across different LLMs and agent frameworks. So, let's jump into the notebook and see how that works. All right, so to start off with, you're adding a couple of imports here. You're importing instructor, which you can use as a wrapper around a number of different LLM providers in the API call. to use your pydantic data model in the API call to get a structured response. And the way instructor works is actually quite similar to what you were doing in the last lesson. You pass in the pydantic data model. instructor extracts the JSON model schema, and then constructs a prompt from that. And if there's any issue in the response, it will do a series of retries in order to get you back the response you're looking for. And you're going to use that with anthropic to start off this lesson. The next thing you can do is Let's go ahead and define the pydantic data models you're familiar with from the previous lesson. So you have the same UserInput model here and the same CustomerQuery model that inherits from UserInput. You can go ahead and define those. And then you can create some example user input data. So here you have a user_input_json, just like you did before with information in all the fields. And you can validate that user_input by using your model_validate_json method. on the input. And then you can construct a prompt. You're going to make a really simple prompt that just says analyze the following customer query, passing in that user input, and provide a structured response. And then you can go ahead and set up an LLM call. In this case, using instructor in combination with anthropic to create a client where you can ask for a response and pass in your prompt as well as a response model. in this case your CustomerQuery model as the response format that you're looking for. So with instructor, you could do this with OpenAI or Gemini or many of the other LLM providers out there, but just to change things up a little bit, we're going to try this with Anthropic. And then you can have a look at what you got back. So in this case, what you got back is actually an instance of your CustomerQuery data model. So the Instructor package has gone off, made the LLM call for you, and done any retrying that was necessary, and then also validated the data and passed you back a populated instance of your CustomerQuery data model. So there's no more need for extra validation steps on your end. You get back a valid data model with all the fields filled out. And so you could use instructor just like this with a number of other LLM providers, but there are also ways you can do this directly with an API call to Open AI, for example. So here what you're doing is creating an OpenAI client and then calling the beta API with a response format of CustomerQuery, your data model. And then you're going to grab the content of the response. and print out the type of response you're getting back as well as what's in it. And when you do that, what you can see is that you're getting back a string, just like you would in a normal LLM response, and that the format of that string looks to be approximately a representation of your data model. And so what's different here from what you just did with instructor and Anthropic is that you're not getting back a validated instance of your data model directly, but you're getting back a JSON string, and what OpenAI is doing behind the scenes here is something called constrained generation, which is to say because you've indicated that you're looking for JSON format in the response, the way that the response is actually generated guarantees that you'll get back valid JSON. Now that's not to say that this is guaranteed to be a valid instance of your data model, but it is guaranteed to be valid JSON in this case. You're never going to have to deal with any kind of malformed JSON in the response when you're using OpenAI in this way. And what you can do next is to go ahead and try to create an instance of your CustomerQuery data model from that response_content using the model_validate_json method. and then print out what you get back. So in this case it worked. You got back valid data and didn't run into any validation errors in populating your data model. But it was one extra step to get to a valid instance of your model. It turns out that that OpenAI also offers another version of their API, this responses API, where you can pass in a parameter called text_format and put your Pydantic data model in there. And then when you run this, we'll take a look at what kind of response you get back. So in this case, you can see that it's some kind of class, uh, with some nesting structure and deep down inside of there, it looks like you have an instance of your CustomerQuery data model. And what I think is really interesting to do at this point before digging into what actually is inside of that response is to look at that response structure a little bit more closely. So here you're just defining a little function to pass in that LLM response and you don't need to worry about the syntax that's going on here, but this .mro() is what's known as method resolution order in Python and that'll pull just allow you to print out the inheritance structure of that response. And so when you run that, what you can see is at the top level, you have this nested structure, and if you unpack that all the way down to the bottom, you eventually get to a pydantic.main.BaseModel. And so, it's very interesting to note that the response coming back to you from OpenAI is itself a pydantic model. And what you've got in this case is an instance of your own pydantic model nested deep inside another pydantic model from OpenAI. And it turns out that this is very common. A lot of LLM providers out there are returning a pydantic data model to you. as the response type. And then deep down inside that model somewhere is the content that you're interested in. And it's at this point that I think you can start to see that when it comes to LLM workflows, it's kind of pydantic models all the way down. You've got pydantic models that you're passing around to get the structure you want. The responses that are coming back many times are pydantic models themselves, and that's because these LLM providers are doing their own data validation on their end before sending you back a response. And then as you'll see in the next lesson, you'll also be using pydantic data models and tools. calls. So for me at least, I think this is kind of cool to discover how pydantic models are just woven through and through in LLM workflows. And so, let's take a look at what you actually got back in this case. When you're using the responses API from OpenAI then the content you're looking for is inside of response.output_parsed. And so you can see inside of there, you have an instance of your CustomerQuery data model, which means that the API has returned valid data, and there's no more validation required on your end. And so in this case, OpenAI is handling the construction of the initial JSON using constrained generation. And also populating the data model for you, handling any validation before handing it back to you. And finally, I feel like this lesson would not be complete if we didn't take a look at pydantic_ai, which is an agent framework built by the folks at Pydantic, where you can create an agent like this, pass in your data model as the output_type and it's just very simple to set up an LLM call to really just about any LLM provider. In this case, just for fun, we're going to go ahead and call Gemini. And this nest_asyncio stuff, you don't need to worry about. This is just so that pydantic_ai plays well within the Jupyter notebook environment. So let's run that. And then look at what you get back. So in this case again, in response.output, you've got an instance of your CustomerQuery data model. and so you've got valid data coming back directly in the response. And so you can see that there are some differences in the responses that you're getting back, namely the tags field has different tags in each case, and that's just due to the fact that that's a list of strings that each LLM provider is going to populate a little bit differently. And in this case with pydantic_ai, you could go ahead and switch to another model provider quite easily, like openai for example. Try calling openai:gpt-4o. And that worked. And you can see that you get back a slightly different response, but all you have to do is change out this model string to call a different LLM provider. And so here you've seen how you can pass your Pydantic data model directly in the prompt to get structured output with a lot less hassle than you were dealing with in the last lesson. So in this lesson, you've been using Pydantic data models directly in your API calls to different LLM providers and agent frameworks. And you've seen that this can be an effective way of getting structured output and validated data from an LLM. Next up, we're going to take a look at tool calling, which is the other big use case for pydantic models within LLM workflows. So in the next lesson, you'll use pydantic models in the definition of tools that you'll pass to an LLM in an API call. And that will allow the LLM to give you back the exact parameters you need to call the tool that uses a function or an API as part of your system. I'll see you there.