The Art of Mocking & Building JSON Schema in Prism

TJ Miller (00:00)
Yo, welcome back to the Slightly Caffeinated Podcast. I'm TJ Miller.

Chris Gmyr (00:04)
and I'm Chris Gmyr

TJ Miller (00:05)
Yo, so Chris, what is new in your world?

Chris Gmyr (00:09)
Hey TJ. yeah, had a bunch of family come into town last week and into this week. So just been busy and took a few days off and did some fall things and got the kids some pumpkins, went to a farm and had a bunch of activities like that. So yeah, a little crazy with shortened work weeks, trying to get a bunch of things done. But also get to take a break and hang out with family. So yeah, how about you?

TJ Miller (00:36)
Yeah, Yeah, yeah. Did you, before we get into that, did you, did you carve pumpkins yet?

Chris Gmyr (00:41)
We did not. So we just bought them. Got to keep them a little fresh for a while. So we'll probably carve them up probably the weekend before Halloween. Because at least around here, especially it's starting to get chilly and the squirrels are going nuts. So they'll probably start attacking the pumpkins if they are carved. They usually get into them and look pretty gnarly after about a week. So trying to save the kids' tears from destroying their pumpkins.

TJ Miller (00:46)
do.

Mm-hmm.

Yep. Yep.

Hahaha.

Chris Gmyr (01:10)
until absolutely necessary. So yeah, we're going to wait a while, a couple weeks.

TJ Miller (01:14)
Yeah, I was curious what you might have carved. So we'll have to see where we're pumpkins pretty soon here too. And we'll see how it turns out. We typically carve them a little early here, but last year we carved them early, left them inside, which was fine, but the dogs started getting into them. I can't win. go outside, get hit by the squirrels. keep them inside, the dogs get them.

Chris Gmyr (01:39)
Yeah, totally. Might have to wait a little while too.

TJ Miller (01:41)
Yeah. Yeah. Yeah, man. man. What's new in my world? not a whole lot, dude. I've been, you know, just kind of chugging my way through work. Still getting acclimated there. It's tough starting somewhere new. Like I want to, I want to know all the things and be super useful and ultra productive, but there's just, it takes time. Like, and you can't, like, there's, guess a little bit you can do to kind of accelerate that a little bit, but at the same time, it's just.

like working on projects over time and getting experience and exposure to all the different pieces and parts of the app and learning different stuff. Geocode has been around for a while, so there's a lot of this app that's existed for a good bit. Even before I started, the primary app was a Lumen app.

I don't know if, I think there's people maybe even listening to don't even know about Lumen. It's been a minute. So it was a Lumen app and, you know, first couple weeks after I started, you know, we got that upgraded with, Laravel shift all the way up to Laravel 11. So, you know, there's been like pieces and parts of this app that have been around for a while and styles and techniques change. And so you're kind of like running into stuff. You're like, I haven't seen this technique used in.

Chris Gmyr (02:39)
Yeah, probably,

TJ Miller (03:03)
a little while and you got to kind of try to load that stuff up into memory and, and move through it. So lots of, lots of cool stuff like and then jamming on Prism whenever I've got the time to do it. So that's, that's been progressing well. I'm like continuing to get stuff out there and there's been pretty fair amount of like community involvement, some of the new developments and we can get into that a little bit too. that's been, that's been pretty fun. And last night,

My wife, like I'm super lame. I'm not, I'm not much into costumes around Halloween. my wife is, my son's super into it and he had his, annual trunker treat last night at school. they, like a bunch of people like pull into the parking lot, pop open their trunks, decorate them all crazy. Like there was a, one of them had like Bob's burgers all set up. another one of them was like ultra beetle juice. There was a pretty cool Hogwarts one.

Chris Gmyr (03:42)
Nice.

TJ Miller (03:57)
But they got, my wife and son got dressed up and did some trick or treating around the school. So that was, that was cool. My wife did Ms. Frizzle and did an excellent job with that. And my, my son, we assembled his costume out of a handful of different parts, but he is a character from Gravity Falls and he's super proud of how it turned out. So fun times, man. And get, get to cashing on the

Chris Gmyr (04:24)
Yes, yeah, that's awesome.

TJ Miller (04:26)
the dad tax and get some candy. And instead of buying candy for us, I bought us the, a box of like trick or treat Pokemon cards. So we've been, you know, we, as I said, between now and Halloween, like we'll open up like between the two of us, like a pack a day. And when we get to Halloween, if there's any left, we'll open up the rest of them. So that's been fun.

Chris Gmyr (04:37)
nice here.

Is that like the 25 pack or like 50 pack or like one of the big like bricks with all the individual packages inside of it?

TJ Miller (04:59)
Yeah, it's a big brick. think it's got like 35 packs in it. But each pack's only got like three cards. So it's not like a big windfall, but they're all the Halloween like exclusive. They're the Halloween special exclusive ones. So get some cool cards. I would jump the gun a little bit. I think we opened like three packs each between yesterday and today. And so far, my son has gotten all the cool cards. Yeah.

Chris Gmyr (05:14)
Yeah. Nice.

Nice. Yeah, that's funny. Last week when my cousin was in town with her kids, she brought one of those bricks for my son. all the kids just opened up the entire thing. We had wrappers and cards and all that stuff all over the place. They organized them, all the duplicates and the ones that he wanted to keep. So he basically rifled through the entire box and kept the ones that he wanted to. And then we're just going to put the rest.

TJ Miller (05:41)
Yeah.

Chris Gmyr (05:56)
just kind of loose with candy and stuff like that. Because we usually leave like a bucket of candy and stuff like that outside. Because we're always trick or treating ourselves. So we'll just put the cards in there with a bunch of candy. And whoever wants to take them can take them. But they had a good time opening up all the 35 some odd decks of cards. So was pretty fun.

TJ Miller (06:06)
Yep.

Yep, I've never done them. I think on my current collection, like one of the first cards I got before I started collecting again with my son, because he got into like, we didn't even really collect, we play against each other. So like we've got our sets, but like one of the first cards I got was a Gengar like Halloween edition card. And that was actually given to my son by one of his friends for me. So it's like his friend.

gave him a card for me to get my stuff started because he knew I loved like big gangar fan over here. So I think at this point I've got, I think I've probably got more gangars than I do anything else, you know? It's fun. And I'm, I'm still on a, I'm still on a losing streak. My son is just beating the snot out of me left and right. So we'll see.

Chris Gmyr (07:00)
That's awesome.

Totally. Yeah, we go in ebb and flows here. we play in a bunch. Sometimes he just wants to go. Like, we have a local meetup here at one of the card and gaming shops. So we go there, and all he wants to do is try and swap and trade cards. But he's still kind of started on it, too. Like, all these people at the meetup have big hologram cards and expensive things. And it's like, dude, you don't have all those yet. You got to.

TJ Miller (07:38)
Yeah

Chris Gmyr (07:39)
collect and buy some more, or just buy the individual ones, like online or at some of the other card places. They sell the individual ones. So just got to build it up in other ways. But yeah.

TJ Miller (07:50)
Go rake some leaves, earn some money. Go buy your cards.

Chris Gmyr (07:52)
Yeah, exactly. But he has fun with it, and we play here and there too. a few months ago, he went crazy with building out different decks for different energies around different people and different characters and setting up against other energies. So he already knows, because my wife and I don't have very many decks. I think we have like three between the two of us.

So he already knows like what those energies are. So he's like, this is my deck to play against dad. This is my decorative to play against mom. And then I have some extras here and there too. So he's definitely got us dialed in pretty well. So it's hard to beat when you're doing that, the resistance.

TJ Miller (08:21)
You

Yeah, it's, so hard to play against my son because he has like, I guess I'd call it like not real strategy when building his decks. Like I'm sitting there like trying to figure out all the different synergies and how I can like maximize my deck, like min maxing the whole thing. And he's just like, he's thoughtfully putting stuff together, but really he's just got such stronger cards than me that he like stacks his deck and I can't.

I can't, I literally just can't compete because nothing I do can like take out, you know, four EXs in a row. Like I, I just, I don't have the kind of cards to beat that. So, you know, no matter what I'm toast.

Chris Gmyr (09:16)
Yeah, totally. Yeah. Cool. Before we continue on, to circle back to the Lumen kind of discussion. So Lumen is more or less like unused now, but it's basically like the API only version of Larry Bell. It's like a slimmed down version that it was, I would say, pretty popular. I don't know, five?

plus years ago or so now, and then kind of tailed off. People wanted more of a micro framework with less overhead. But it's still active. think they have version 11 out more recently to keep up with the Laravel releases. But I definitely don't see it as much out in the wild as we did before. So do you know? Just curious why or what?

capabilities that GeoCodeo needed to upgrade to full-blown Laravel after Lumen. Do you have any sort of insight on that?

TJ Miller (10:16)
Yeah. Yeah. Yeah. So I was actually on a call yesterday with, Jesse elite and we were just like catching up and showing off each other's like them setups with to each other. was pretty cool. but I actually had mentioned something about Lumen to him and we went and looked it up and the Lumen repos actually archived at this point. So, the docs, yeah, the docs still run up to like version 11, which is where Larabelle is at right now. But, I think.

Chris Gmyr (10:36)
What is that?

TJ Miller (10:45)
I think it's, it's kind of an archived project at this point. And originally it was significantly faster than Laravel and that's kind of why it came into existence. And this is pre PHP eight. So Lumen was there, there was a justifiable place for it in, the space because it was significantly faster. And that was part of the choice of using it here was that, you know, we wanted to have a very fast API.

for like doing geocoding and it didn't need anything else. So it didn't need like the views system. It like, it needed just like simple cues and a simple API. So that's, mean, I think that was the right choice too, you know, cause that was around like this app pre-exists like PHP eight even. So, you know, if you wanted something highly performant and, and it just be in an API, that's, that was the call to make at the time.

But after PHP eight, the performance gap between the two really closed by a pretty significant margin where it became nominal to choose like full blown Laravel over Lumen. so I think part of the, the choice to move from Lumen to Laravel 11 is one, like the repo is archived. It's not like, I don't think it's still like an active actively ran project anymore, but.

Also, you know, if there's an opportunity to like bring that up to Laravel 11, I think it was great, a great decision to from like perspective of like me starting in great time to like upgrade it. And then that way, you know, we're all, we're all moving together on the latest and greatest stuff. And then it's easier to keep up to date too. You know, there's a lots of new features that we want to take advantage of in, in Laravel 11 and new stuff that's coming out. Like.

I definitely see some room for us using the new defer stuff. So I think there's like new functionality that we want to be able to take advantage of too. And you know, now, now we can, whereas before like we were kind of stuck in Lumenworld and not able to do that.

Chris Gmyr (12:52)
Totally. Yeah. I think with the latest releases in both Laravel and PHP, the speed and performance have improved significantly. So like you said, less need for Lumen and those micro or slimmer frameworks too. And you get the full spectrum of the Laravel feature set. For performance, I've...

I haven't done it myself because I really haven't had a need to do it yet. But have you experimented with any performance tweaking within Laravel? I've heard of or seen people take out certain providers that they don't need. So if you're only using like Laravel API, like you can get rid of the view providers and some of things like that that are

needed for an API-only project. Have you experimented with any of the framework tweaking or config providers, things like that?

TJ Miller (13:57)
No, and I think I'm pretty opinionated about leaving that stuff as it is. You know, I don't want to... One, I don't necessarily always know where the app's heading and I don't want to... I want to have everything available to me depending on where the app wants to go. I also don't think it's actually that big of a performance gain. And I think you're sitting in a really unique position if you actually need to eke out that level of performance. And I think if you are in that position,

there's maybe better strategies to solve that problem. I think that's something that

Now who was it? Mohammed had been blogging about getting into Golang a bit was he had some endpoints that were really performance sensitive. And so he ended up putting those endpoints inside of, you know, a Go application instead of inside the Laravel application. And that ended up just getting him the performance that he needed out of it. So I think there's like strategies and like different decisions to make based on the kind of performance that you need. But I just, I

At the end of the day, I don't think it's that much of a gain. And to some extent, I think that there's risks in unraveling some of that.

You don't necessarily know what like different things depend on. Like you may pull in a package for some other reason, but then that package has got like some dependency on the view system being present, you know, and now, now you're like running the errors that you've got to try to figure out and chase down and you're like, I've got to add that service provider back for this to work. I just, it seems like a lot of work for not a lot of gain.

Chris Gmyr (15:36)
Yeah, totally. Yeah.

TJ Miller (15:39)
Yeah. How about you? Have you played around with that at all?

Chris Gmyr (15:42)
I have not. We haven't really ran into a situation where it was really needed. And like you said, you kind of run into weird oddities like pulling in a package. Like if you get rid of the view layer, but you're using Horizon, Horizon has its UI. So it's probably going to bomb out from that. Or you just don't use the UI in general. But it's a pretty helpful tool being there. So

Like you said, I think it would need to be a very specific use case to try and peel those layers away. And I don't know if it'd be too much benefit besides going to a different framework or peeling off a few different endpoints to do a specific use case for performance. So yeah, I totally agree with you. And it will also help kind of keep everything in sync and keep it easily upgradable in the future.

because you never know what's going to happen with the framework or the underlying entities and mechanisms within that. So taking something like that out could mean that you're opening yourself up for a little bit more pain in the future trying to upgrade them. And I think with the Laravel 11 upgrade and config and skeleton structure of it, it makes some of these frameworks

TJ Miller (16:51)
Yeah.

Chris Gmyr (17:04)
configs a little more abstract and sometimes even impossible to get to anymore. So I think less customization is probably better overall, unless you really need to dig in or if you can't go to a go setup or use some sort of other framework. But I think I'd be very hesitant to actually pull the trigger on ripping things out, unless you had a really good reason to do so.

which I think might be pretty minimal.

TJ Miller (17:36)
Yeah, totally. I think there's, I think it kind of falls somewhat in line with like following framework conventions. And I'm big on, I'm big on, you know, having expectations. Like when I go into a code, like a layer of our code base, have expectations around the different affordances that I have available to me. And by like ripping some of that stuff out, you're like, you're upsetting that.

that like, expect, like the expectation of like, this is going to exist. This is here. Like I've got this affordance. This is something in my tool belt. Well, no, I don't. I've got, I've now got to put that into my context for this application. And I don't know. Like it just, I can't, it's, it's hard for me to feel like it's worth it. I'm sure there's somebody out there who's got a specific use case that like, it makes maybe sense for, but I think even then there's alternative ways to handle that.

Chris Gmyr (18:31)
Totally. Yeah, so if you're listening to this and you've done something like that performance-wise and had some positive results, we'd love to hear about it. I'm definitely curious to see if anyone had any success with that, not only for the specific performance use case, but ideally like over time and across multiple layer-by-layer upgrades. I think it'd be interesting to hear from anyone who had done that.

TJ Miller (18:57)
Yeah, please, please prove me wrong. Like make, make me feel the other way about it. I kind of know. Definitely opinionated.

So you want to dive into what I've been working on with Prism a little bit and at work has been involved with doing some mocking and making decisions around mocks. You want to talk about that a little bit?

Chris Gmyr (19:20)
Yep, definitely.

TJ Miller (19:21)
So I think there's like, mocking is just one of a few like test doubling strategies. And I feel like mocking is an interesting one because I think you hear a lot of like, don't mock what you don't own. But at the same time, like that's often the thing that I'm reaching for to mock is like not my thing. Otherwise I have maybe different strategies for handling stuff. And

even mocking your own stuff, like it just feels dangerous. But at the same time, I don't like, I don't have another way to go about it because you know, you're mocking something and I feel like sometimes that leads you to testing the implementation rather than testing like outcomes. I don't know, man, how do you feel about mocking?

Chris Gmyr (20:15)
Yeah, I feel pretty similar. Because at least at work, we have a lot of external services and APIs that we use. everything is in where the majority of stuff is in monolith and things calling other stuff. And it's a little spaghetti in different areas. So we tend to have a lot of external API calls and not really that visible within the tests that the things that are calling those.

external sources. So we've had to leverage things like either explicit mocks for newer code. So a lot of times for newer work, we've chosen to use Lirvel's HTTP library or helper and basically turn off any sort of external calls from that, which brings up an exception to

the test if anything is calling that wasn't mocked. So it basically forces us to mock everything within this external service that we're doing. Yes. Yep. Yep, exactly. So that allows us to know if anything is kind of falling through the cracks, something that we didn't expect to happen. So even though that's mocking what we don't own, because we don't own that external

TJ Miller (21:21)
And that's the prevent stray request option, right?

Chris Gmyr (21:36)
API typically. It just makes everything so much faster. And you can be very explicit and be able to test more within the test suite. So you can more easily test successful paths, records created or deleted or whatever. But you can also have unhappy path test too. So by passing in

like a 404 or 500, you can more easily test those failure paths within your code, which is pretty nice. Without mocks and without fakes or anything else like that, it would probably be a lot harder to do that for at least the unhappy paths. And also not having to make all these extra calls out into the wild.

TJ Miller (22:25)
Yeah. And that's, that's definitely a strategy that I'm using inside of Prism. Like I'm exclusively using Laravel's HTTP helper for interacting with all the different providers. So like OpenAI or Anthropic. in one of the earlier versions when I was working on it, at the time it was Sparkle, I was using the HTTP helper, built my own Anthropic like client.

and then was using Nuno Maduro's OpenAI package for OpenAI. And he's got some really great test affordances in there, but the reason why I've just made the decision with Prism to exclusively stick with Laravel's helper is because how much easier it makes things to test. Like you can toss in like the response, so you can mock out the response for the endpoint. You can assert your request, you can assert your responses, and that prevents stray request is like

awesome for making sure that you're not like making additional requests outside of what your expectations are. think it's absolutely the way to go. And before you could do that all with Guzzle too. And one of the things that I did for any library that I made that would make HTTP requests was the ability to override that Guzzle client with like a Guzzle mock basically.

and be able to do all of the same stuff where you can assert the request response. It's just way more elegant using the HTTP helper.

Chris Gmyr (23:54)
Yeah, totally. It makes it really easy to do any of these things and a lot less verbose as well, which is also nice.

TJ Miller (24:03)
Yeah, think definitely like mocking out API requests is like crucial. Like you don't want to be making like round trips and like actually hitting real APIs. I think, and like I...

I know that it's a little risky, but like I implicitly trust API contracts and, just, you know, you're going to get bit every now and then by it where someone makes a change that you're not expecting. But I think for the most part, like people tend to honor API contracts pretty well, unless you're like migrating between versions. So for the most part, I feel really pretty safe mocking the inputs and outputs of an API request.

But when you get into mocking things like, for example, I have the way that like Prism works under the hood swapping between the different providers is there's like a manager class. It works almost identically to the way that the cash manager works inside of Larrivo. Actually more or less like copied and pasted the code and then like hacked away at it until it did what I wanted to. Cause I just like, that's the kind of system that I wanted to build. So.

Where else better to get that then from, from Larrivo. And there's, you know, I didn't want, like I was making it, what is this test? So I'm writing a test specifically around like text generation. like the, the unit that I'm trying to test is like text generation, but that becomes available to you through resolving out of the manager. I'm like mocking the manager and then I'm sending a like text generator like through that mock.

And it just, even though I'm mocking my own thing, like the thing that I do own, just something feels weird about it. And the other day I caught a test where I was just like, curious if I was actually, I wonder, I don't know, I just thought to myself, I wonder if this will break if I change this value. I changed a value and it didn't break. And I'm like, that's not good. A hundred percent because of this mock. I like, I've got to go back now and just like, re-figure out how to test this whole thing.

so it like, it'll definitely get you into trouble. I think. Mocking things outside of HTTP request is like.

I think there's like oftentimes that I don't see another way forward, but it also like feels pretty bad. I don't know. And then you get into things like different tools like PHP VCR too, which I tend to stay away from at this point. It's a super helpful tool for like recording request responses for, you know, things that we can't necessarily, like if it's not using the HTTP helper.

Chris Gmyr (26:25)
Yeah.

TJ Miller (26:43)
or you don't have a way to send in a mock guzzle client where it's a totally opaque library, PHP VCR is really handy for doing that. But I've found that you tend to run into a lot of differences between CI and local dev because part of the way that it works is it verifies headers. And so if your PHP version's even a little different between

your local and your CI, like you can be on 8.3, but one may be on like 8.3.1, your local may be on 8.3.10, because it's just more up to date. And now it's not matching the requests anymore because the PHP header is different. You know, it can be a little finicky. So I tend to, I tend to stay away from it, but I don't know, man.

Testin's hard sometimes.

Chris Gmyr (27:38)
Yeah. Yeah, we use VCR pretty heavily from our older code, more the legacy side of the application. Because as I said earlier, we just have so many external API calls. And it would slow down the test suite significantly. It'd probably take hours to run the entire test suite if we didn't have VCR.

And we've actually forked PHP VCR and adjusted it and also made a bunch of customizations within the app to make it work. I think it bypasses a lot of those headers most of the time. it's a little bit more flexible, but still, it's not the friendliest tool to use, especially if you start customizing it. And also, if you want to

start testing these unhappy paths. Because what we've had over time is some engineers would record the VCR, is like a big blob of JSON for the requests and results. So they would go through a happy path, record that, copy that fixture to another test, and then change some of those values, get a return from the JSON.

for different statuses or different data or anything like that. And there's no real key or information of what was changed. So if that developer went away to another place or someone new coming into that task trying to recreate the VCR fixture, it would always fail because it just wasn't known that someone made manual changes to the fixture.

And it just makes a complete mess if you do it that way. But without the mocking with the HTTP library and helper, there's really limited options of what you can do with that. So it's kind of a mess with the tests that we do have PHP VCR enabled on. So that's why we've been pushing for the more manual mocks with HTTP helper and the prevent.

stray requests and that has been made a world of difference in the tests and readability and maintainability because everything is just very explicit within the tests and the helpers that we have for the tests.

TJ Miller (30:08)
Yeah, no, that makes a ton of sense. And I think like talking about PHP VCR as a library, like I think it doesn't see, like it's not the most, up to date library. think the latest updates are from eight months ago. There's 47 issues, 35 open pull requests. So it's a tool that I think gets used a lot, but it's, it's, I think a little stale and it's a challenging project to try to.

inject itself into PHP and like magically override curl handlers and other ways of making requests. Like it's kind of a gnarly thing to do, but there's some situations where it's either, you know, you use PHP VCR or you end up rolling your own clients for things. I'm a big advocate for just, you know, even if there are SDKs out there for the most part, I'll still probably reach for.

rolling my own library unless they've built in like good, good helpers to help me test like, like what Nuno did with the open AI PHP package. there's like some really nice test affordances in there. Like that, made it pretty usable for my use case. But, with Prism, I just, want to own, I want to own everything. So I'm rolling my own clients for everything. Plus I'm only using, you know, a handful of endpoints, whereas

The OpenAI package is covering everything that OpenAI offers. So a little bit different.

Chris Gmyr (31:41)
Yeah, totally. I'm 100 % for building your own clients or local SDKs for things. Because typically, like you said, the official SDKs are bloated. Typically, it doesn't seem like the PHP options are very well built for the most part anyways. Because typically, people will either have some auto-generated thing from their OpenAI spec or, I don't know.

just get some contractors in there or something like that, and you deep dive into some of these SDKs that are available, and they're just not very well built or even maintained or not testable at all. So at that point, you're just better off building your own client locally for the explicit use cases that you need. If you only need five endpoints from the service, it's going to be a lot easier to

build and maintain and build on the testing helpers for that than trying to test with this first party client or SDK package, at least from my experience.

TJ Miller (32:49)
Yeah, and it gives you some affordances to do cool and different stuff too that's maybe more contextual to your application. You can create SDKs with less generic methods. You can create stuff that's very specific to what action you want to take. And then you can do things like pass in one of your models instead of having to pass in an array of request options. You can just say, no, here's the model, and I'll map that to the request option inside of our SDK.

And now we've got this like nice tight integration that, you know, reads well. It's very obvious, like what's going on. And then you can build in all of your own like test affordances for things too. I think there's just so many advantages to roll in your own SDK, especially if you're just using like a subset of things and it's not overly complex of like what the SDKs are doing. Some of them do quite a bit of work for like more complex endpoints, but for the most part, like

Go source dive it too. It's open source. Go take a look at how they're handling that endpoint, and you can bring that into your SDK also.

Chris Gmyr (33:50)
Yeah, just take the pieces that you want and leave the rest. I think that testability piece is the biggest part of it, or one the biggest parts of it.

TJ Miller (33:58)
Yeah, that's something that I'm going to need to start sweating soon with Prism is making it easy for other people to like test their like Prism integrations and not have to make like actual API requests to places. I'm not even sure how to approach that yet, but I know that that's something that like, I want to make sure that I offer is easy ways to test things so that someone's not trying to wrap PHP VCR around Prism, know?

Chris Gmyr (34:28)
Yep, totally.

TJ Miller (34:30)
Cool, man. Well, we can touch on like a little bit of a Prism update to you. A bunch of stuff that I've been working on. I don't know. There's a good chance y'all are hearing my dogs going bananas in the background. I think they've got some like work going on around our house and they're just too excited because they think everybody's here for them and they've got to go hang out now. So yeah, with Prism, the big thing I've been working on over this past week is building up some sort of way to create

JSON schemas in a sane way, specifically around tool calling. So with tool calling, you have to send some JSON schema over to your provider. So OpenAI, for example, you're going to provide it a tool. Let's say it's a weather tool. And you have two parameters to your tool. There's going to be maybe the city.

that you want to check the weather for and then maybe the like the date of what you want to check the weather for. So you want to either want to like check the weather for today, current conditions, or you want to check the forecast for like the next two days or whatever. So you have like two parameters. Well, the way you tell OpenAI that there's these multiple parameters is you give them JSON schema of like what they're supposed to pass over. So they could have a parameter that's going to be

a string for the city, or maybe it's a number for the zip code. And then you've got your, like your, you know, how many days you want the forecast or whatever. Like that could be an enum. So you can define JSON schema for what the values of these parameters are supposed to be. And then you send that along to the model and then it, you know, sends, tries to send that back in like whatever information. So, one example that I've been using.

is kind of creating this like user object. So it's like a JSON schema of an object that's a user. And then there's an array of hobby objects for that user. And that hobby object exists of like a name and a description. So then you can send to OpenAI with that tool attached, like with this like hobby creator, a user creator tool, right? Like those are the fields that I need. need a user, the user has a name, and then that user has like an array of hobbies.

And so you can send it, like I could send some text as a prompt, like, hey, TJ flips a balisong for fun and created the prism package for Laravel. And what it sent back to the tool as arguments were an object that is like names TJ and then an array of these hobbies of two items, because I said I flip balisong for fun and I like write the package. So it like extracted those things from that prompt and then

provided me with an object to then do something with. So if I wanted to like create an eloquent model out of that, inside of that closure, that's something that I could do. So it's pretty cool, trying to, like I didn't want to pull in a third party package or anything. I'm trying to keep this package like kind of, kind of light, but I'm also very opinionated about how we do stuff. So I'm, I'm.

you know, kind of particular about it. So I've been putting together sort of this like way to build JSON schemas. And as I was coming across it, like one of the big features down the line is like, like structured outputs. So you can basically give OpenAI a big JSON schema blob. And it also like, its end result is this like object.

Well, I'm finding out that the classes that I've put together also basically have support for that now. Like, it's kind of crazy. Like, just, I've ended up building out a set of classes that are like schemas. So there's like a class that's an array schema, string, enum, object, all of those. And then I've kind of built a few ways to compose those together. And so you can create these like pretty rich JSON schema objects.

out of a handful of classes. And then ultimately, you just run a two array method on it, and there you go. You've got JSON schema as an associative array. I really didn't know how to approach it, but I'm really happy with the way that it turned out. And hopefully, getting that PR merged later today, I think I've got the documentation together for it also.

That's been the big push and I've had like a couple of people pitching in on it, which has been great. Like people offering a whole bunch of new feedback. like shout out to like Matt Glover and Sam Jones. they've been both between like Twitter DMS and, and GitHub issues, going back and forth, giving me a ton of feedback on the direction that it's heading and, super stoked with how it turned out, especially not.

Like getting into the problem, not really sure how I was going to approach it.

Chris Gmyr (39:35)
Yeah, that's really awesome and super glad to hear that the Prism community is growing so much so fast too, because yeah, I've had a lot of help and input into a lot of things that you're working on. So that's really super cool to see.

TJ Miller (39:52)
Yeah, that was, that's kind of the challenging part about building some of this is I don't have, like I'm, my use cases right now are really pretty simple. So the package like covers my use cases pretty easily. And I know there's people out there doing like far more advanced stuff on a day-to-day basis than what I'm doing. And like, that's, that's the struggle is like, I'm sitting here like in theory land, like, people might use it this way or might use it that way.

Like, let me account for all of that stuff, but I'm missing the like more practical use cases and like how people actually want to use it. So this kind of feedback that I was getting around building this feature set was super valuable of just like, yeah, I have like this use case. And so I need, you know, those parameters shaped a certain way. And I'm like, all right, well, like, let me see if I can create, create that JSON schema and,

If I couldn't, you know what that means. I knew I had to go back to the drawing board a little bit. And I had built them all very specifically as parameters. And then once I realized that it's actually a more broad abstraction, I kind of pulled it out of being parameter specific and brought that into like, here, this is schema stuff. Because it was all like an array parameter or an enum or a string parameter. Like, no, let's just.

create a larger abstraction of just like, hey, here's schemas and how to build schemas because I realized that that's also probably going to be super vital to structured outputs and object generation.

Chris Gmyr (41:36)
Yeah, that's really cool. So it seems like, and we'll put the link to the PR in the show notes, but it seems like it's a pretty simple implementation. Just have a contract for essentially two arrays, so almost like Laravel's arrayable. And then each of the schema variants, so like numbers versus Booleans versus array and so on.

just implement that in different ways, a couple different parameters or methods along with that. And then you basically have a tool builder with these parameter schema options and just pass everything through there. And it just does the work for you in looping through everything and calling the two array method on all the schemas. So yeah, nice lightweight implementation and very easy to follow.

TJ Miller (42:30)
Yeah, I couldn't believe how simple it actually ended up being. I thought it was going to be a lot more complex. And I think that was coming from, I built a package quite a while ago that does JSON schema assertions against Laravel responses. And that had to support way more pieces of JSON schema, being able to reference stuff. That's where things, I think, start getting a lot more complex. But I couldn't believe how simple this actually

ends up being. And then, like on the tool builder, there's a fluent class for building up your tools. Instead of requiring you to pass in new objects for everything, I added a fluent helper method for each of the schema objects. So on the tool, can say with string parameter. And you can pass in basically the same argument list as the constructor for the class. But now you don't have to import the class, new something up.

You're just able to kind of like pass that stuff in and I handled newing up things behind the hood. So you've got like a really nice fluid interface for building up these like parameter schemas like right off of the tool also. Just trying to add as much like polish to everything as possible.

Chris Gmyr (43:46)
Nice. Yeah, I love the builder pattern. It makes things so much nicer in app user land. yeah, looking slick.

TJ Miller (43:57)
Yeah, I'm trying not to go overboard with it. Like this morning I woke up and had the idea of actually creating a sort of eloquent builder kind of approach for schemas so that you could basically create, like have a schema builder and all of the like, for example, an object, like the object schema class would then have like.

other additional fluent methods on it to like kind of build up the object. Cause like an object has properties and then each of those properties is actually a schema object. So it would probably have like fluent methods on top of that of having like with string property, with enum property and all of this. So you've got like fluent building all the way around, like even through the schema classes themselves.

I think ultimately I still want to do that, but it's tough striking that balance between like, kind of want to do this and it's like a super interesting idea. And I think it, someone would find it useful, but trying not to spend too much time going down those rabbit holes and just like focusing on shipping stuff. Cause I know there's like people who need this functionality to like, they need the functionality in order to use Prism.

basically. So like, do I spend three more days like going down this rabbit hole and like making like this fluent building of every object or do I just like get it shipped and circle back to it later if it makes sense? And I'm going to ship it now, but it's I've found that like a really tough balance to strike.

Chris Gmyr (45:20)
Yeah.

Yeah, totally. And what I think you have right now with the explicit method names for the different types, I think that's a great middle ground. Because like you said, you can ship right now. You can get people using this, and that's great. But you're also renaming it and getting rid of the width parameter. So I think maybe in the future, you could use that with that eloquent type builder.

because you can basically send that builder into with parameter. And that will handle anything that you send through it from your Eloquent-esque schema builder, if and when you do that. So I think you have potentially multiple options here with where you can go in the future and also not have to do that right now.

TJ Miller (46:29)
Yeah, and I think it's like for defining parameters for your tools is kind of like low stakes for building schemas. Because I think more often than not, you're going to have like simple parameters. Like you're going to just need like a string or for like whether you're talking one or maybe two parameters for that closure or callable.

So I think they're going to be relatively simple. I know that some of the use cases that people approached me with are a little bit more complex and like this definitely works for that. But I think where a more thorough schema builder will come into play is like when I get to like that structured output where like you probably creating at that point, you're probably creating more complex schemas to get.

because that's going to be the LLM's job is to fulfill that think that's where that comes into play. so it's like, can always circle back to that too when I'm working on that feature set. So it's not like I'm walking away from it and may or may not come back to it. I'll definitely be revisiting this stuff in another month or so. I've an out that I can push it off to.

Chris Gmyr (47:47)
Yeah, totally. Yeah, looking great for right now.

TJ Miller (47:50)
Cool. Appreciate it, man. So I think on that note, we can probably wrap it up. Yeah. Thanks for listening to another episode of the Slightly Caffeinated podcast. Show notes and links are available at slightlycaffeinated.fm. You can find us on Twitter at Slightly Caff Pod. And we would love for you to email us at hey at slightlycaffeinated.fm with content suggestions, feedback, all the good stuff. Yeah.

Chris Gmyr (47:54)
Yeah, let's wrap up.

TJ Miller (48:18)
Thanks for another good one, Chris, and thanks for y'all listening.

Chris Gmyr (48:21)
Thanks TJ, we'll see everyone next week.

TJ Miller (48:24)
Yeah, see it.

Creators and Guests

Chris Gmyr
Host
Chris Gmyr
Husband, dad, & grilling aficionado. Loves Laravel & coffee. Staff Engineer @ Curology | TrianglePHP Co-Organizer
TJ Miller
Host
TJ Miller
Dreamer ⋅ ADHD advocate ⋅ Laravel astronaut ⋅ Building Prism ⋅ Principal at Geocodio ⋅ Thoughts are mine!
The Art of Mocking & Building JSON Schema in Prism
Broadcast by