WEBVTT

00:00.000 --> 00:07.000
Hi, folks.

00:07.000 --> 00:12.000
My name is Homsa, and this is Si, and together, we're the main tanners of Swift Open

00:12.000 --> 00:13.000
API Generator.

00:13.000 --> 00:19.000
Today, we'll show you how to build a proxy server in just about 15 minutes, all using Swift

00:19.000 --> 00:20.000
Open API.

00:20.000 --> 00:23.000
And we're going beyond a hell world service here.

00:23.000 --> 00:29.000
What are going to be calling real world, chatGPTAPI, and we'll even include

00:29.000 --> 00:30.000
the streaming here.

00:30.000 --> 00:34.000
We'll be covering a lot, and we'll be going pretty fast, but don't worry, we've already

00:34.000 --> 00:36.000
opened source of the code in the C today.

00:36.000 --> 00:40.000
It's part of the Swift Open API Generator examples directory, so you can check it out

00:40.000 --> 00:41.000
right after the session.

00:41.000 --> 00:45.000
Also, we're doing a live demo here with a lot of move-in bits.

00:45.000 --> 00:49.000
We'll be calling a real third party API, and one that's backed by an LLM.

00:49.000 --> 00:51.000
So, who knows what's going to happen.

00:51.000 --> 00:54.000
If something goes wrong, please bear with us.

00:54.000 --> 00:57.000
So, before we start voting, let's prepare our table.

00:57.000 --> 00:59.000
Here are the ingredients.

00:59.000 --> 01:03.000
We're going to be using Swift Open API to generate the client to talk to the

01:03.000 --> 01:07.000
chatGPTAPI, and we'll also generate the things that APIs server that uses the

01:07.000 --> 01:10.000
chatGPT client to make upstream calls.

01:10.000 --> 01:15.000
The escrowed is our editor for today, and we have the Swift and Open API extensions

01:15.000 --> 01:16.000
installed.

01:16.000 --> 01:20.000
We'll be using Def containers to build and run the server on Linux.

01:20.000 --> 01:25.000
And since Swift Open API clients and transports have a workable transport layer,

01:25.000 --> 01:30.000
we'll be using vapor and a single HTTP client on the Linux server,

01:30.000 --> 01:35.000
and we'll be using your all session when calling APIs from macOS.

01:35.000 --> 01:38.000
But first, why do we need to proxy in the first place?

01:38.000 --> 01:43.000
Why not just make the calls directly from the client up to the chatGPT?

01:43.000 --> 01:46.000
Well, there are a few reasons you might want to consider this approach.

01:46.000 --> 01:49.000
First, you don't want to hand out the credentials for your LM service API.

01:49.000 --> 01:55.000
Second, you might want to customize the prompt and evolve it pretty frequently.

01:55.000 --> 02:00.000
Third, you want to be able to switch the model you're using, or maybe even the backing

02:00.000 --> 02:02.000
LM service.

02:02.000 --> 02:06.000
And finally, you might want to provide a more tailored experience for your users,

02:06.000 --> 02:09.000
by transforming the request and responses.

02:09.000 --> 02:13.000
The proxy we're building today will take advantage of all of these benefits.

02:13.000 --> 02:16.000
So, what should we build?

02:17.000 --> 02:21.000
Well, finally, that's the goal, friends.

02:21.000 --> 02:26.000
But when we attend live games, we're often a bit underwhelmed by the creativity of the chance.

02:26.000 --> 02:32.000
The user go, like, let's go, team name, we can be better.

02:32.000 --> 02:39.000
We want something more engaging and something creative that includes specifics about our team and its players.

02:39.000 --> 02:43.000
Enter, chant VTT.

02:44.000 --> 02:47.000
That tailored API, thank you.

02:47.000 --> 02:52.000
And we're really proud of this one.

02:52.000 --> 02:59.000
It's a tailored API backed by CADGPT that provides team specific songs to sing a games.

02:59.000 --> 03:04.000
So, let's start building.

03:04.000 --> 03:10.000
First, we're going to switch over to terminal, and we're using the OpenAI API today,

03:11.000 --> 03:16.000
and specifically, CADGPT, which is the CADGPT API.

03:16.000 --> 03:20.000
We provide authentication credentials in the header,

03:20.000 --> 03:26.000
we choose the model, and we just take a load.

03:26.000 --> 03:31.000
And we actually get a reply back here with the LM standing pillow back.

03:31.000 --> 03:34.000
So, how do we go from Swift now?

03:34.000 --> 03:40.000
Well, fortunately, OpenAI publishes an Open API document on their official GitHub.

03:40.000 --> 03:47.000
And the Open API document contains over 100 operations, including the CADGPT API.

03:47.000 --> 03:51.000
And while we're only going to use the single chat completions operation today,

03:51.000 --> 03:56.000
since we're going to be generating all the data model and networking codes from the Open API document,

03:56.000 --> 04:02.000
later, we could add more functionality very easily, such as streaming audio or generating images.

04:02.000 --> 04:09.000
So, we can get started working on the client, let me hand it over to Si.

04:09.000 --> 04:11.000
Well, thanks, Monza.

04:11.000 --> 04:16.000
I'm going to switch over to DSCO, where we've already downloaded the Open API document.

04:16.000 --> 04:20.000
And the VS code, Open API plugin, comes with slightly UI preview,

04:20.000 --> 04:23.000
which allows you to explore the API interactively.

04:23.000 --> 04:26.000
This is that chat completions endpoint that we're going to use.

04:26.000 --> 04:30.000
And if I expand the operations, see the parameters it takes,

04:30.000 --> 04:34.000
you can see it takes the messages that make up the front, the model,

04:34.000 --> 04:39.000
and there's also the stream parameter here to expand the documentation.

04:39.000 --> 04:42.000
It says if we enable streaming the server is going to send tokens back

04:42.000 --> 04:47.000
as a data-only service end-to-end stream, terminates the biologic data payment.

04:47.000 --> 04:53.000
Okay, Swift Open API can generate all the tokens we need to call this API.

04:53.000 --> 04:59.000
So, I'm going to head over to package Monza first, and I'm going to add a generated chatGPT file.

05:00.000 --> 05:03.000
We'll start by adding the package dependencies that we need.

05:03.000 --> 05:08.000
And because we're going to use this generated clients from a proxy server target later,

05:08.000 --> 05:10.000
which will have its own Open API documents,

05:10.000 --> 05:17.000
I'm going to generate the chatGPT clients in a dedicated target.

05:17.000 --> 05:20.000
So, this target has depend on the Open API plugin,

05:20.000 --> 05:22.000
and it also depends on the Open API runtime library.

05:22.000 --> 05:26.000
You provide some of the common types used by the generated code.

05:26.000 --> 05:29.000
I'm going to add this target as a dependency as a CLI,

05:29.000 --> 05:32.000
along with the URL session transport.

05:32.000 --> 05:34.000
Now, I'm going to create a source directory for the target,

05:34.000 --> 05:38.000
and I'm going to remove that Open API document into that directory.

05:43.000 --> 05:46.000
And this plugin requires a company file.

05:46.000 --> 05:48.000
I'm going to create that too.

05:48.000 --> 05:57.000
Yeah, when they think you serve filtering configuration,

05:57.000 --> 06:00.000
those are just the operations we've introduced in today,

06:00.000 --> 06:03.000
which can speed up the build as well as a large API.

06:03.000 --> 06:08.000
The one more thing we need is, because this API is final communications,

06:08.000 --> 06:09.000
we're going to need a middleware.

06:09.000 --> 06:11.000
So, I'm going to create that now.

06:11.000 --> 06:21.000
So, this middleware adds a header field to every HTTP request,

06:21.000 --> 06:25.000
and the value for the header field contains an environment variable,

06:25.000 --> 06:27.000
and we'll use that to provide the API token.

06:27.000 --> 06:33.000
So, we have all the pieces we need to start calling the chatGPT API from the CLI.

06:33.000 --> 06:37.000
Over here, and I'll start by importing the modules we're going to use,

06:37.000 --> 06:41.000
including the chatGPT module for the generative files.

06:41.000 --> 06:47.000
And our free compliance, using the URL session transport,

06:47.000 --> 06:50.000
and the middleware that we just created.

06:50.000 --> 06:54.000
Next, I'm going to prepare the messages that make up the prompt.

06:54.000 --> 06:56.000
First, we have the system prompt.

06:56.000 --> 06:58.000
Next, a little odd thing.

06:58.000 --> 07:03.000
And we're going to ask the LN to build a fun and witty chance for a team,

07:03.000 --> 07:08.000
and then we'll determine which team I'm going to make my mind open for.

07:08.000 --> 07:11.000
Next, I'm going to print a placeholder to the console,

07:11.000 --> 07:14.000
and I also don't want to say we're buffering on standard output.

07:14.000 --> 07:16.000
That means, whatever we've printed the console,

07:16.000 --> 07:19.000
which will be our part for requests, we'll see them immediately.

07:19.000 --> 07:21.000
It's an important first streaming network.

07:21.000 --> 07:24.000
Now, if you want to make the request,

07:24.000 --> 07:30.000
the generative client will contain one method for every API operation.

07:30.000 --> 07:33.000
And the parameters that method exists will be generated data model time.

07:33.000 --> 07:35.000
So here we've got those messages.

07:35.000 --> 07:39.000
We also have the model, which is a generated enum value.

07:39.000 --> 07:41.000
We're going to enable streaming.

07:41.000 --> 07:43.000
And we're going to use this extra parameter here,

07:43.000 --> 07:45.000
to temperature, which we're going to set the zero,

07:45.000 --> 07:48.000
because we don't want any hot takes during the demo.

07:48.000 --> 07:53.000
So the response type itself is also a generated enum.

07:53.000 --> 07:56.000
So it allows you to exhaustively switch over all the documented responses

07:56.000 --> 07:57.000
to serve a good return.

07:58.000 --> 08:00.000
Or if you know what you're expecting,

08:00.000 --> 08:04.000
you can use a type say throwing API to extract the content type.

08:04.000 --> 08:07.000
You want here, we expect text event stream,

08:07.000 --> 08:10.000
which is the content type and serve a sense event.

08:10.000 --> 08:13.000
And it returns an async sequence of bytes.

08:13.000 --> 08:15.000
So there's no buffering in the client.

08:15.000 --> 08:19.000
The open API runtime library provides convenient helper functions

08:19.000 --> 08:23.000
for common content types, the video was streaming, including certain sense events.

08:23.000 --> 08:27.000
So with this one line, we can decode that async sequence of bytes

08:27.000 --> 08:31.000
into an async sequence of type values.

08:31.000 --> 08:35.000
Now all this left is for us to iterate over the async sequence

08:35.000 --> 08:39.000
and print the partial responses as they come in.

08:39.000 --> 08:42.000
So this is all the code you need to write by hand

08:42.000 --> 08:45.000
to make a streaming API call to catch easy.

08:45.000 --> 08:48.000
All the serialization of the data model types,

08:48.000 --> 08:51.000
as well as the streaming HTTP request and response logic,

08:51.000 --> 08:53.000
is all handled by the iterative code.

08:53.000 --> 08:57.000
So only doesn't make it serve well and faster to get started on a project.

08:57.000 --> 09:00.000
It also removes a whole class of bugs that have comforted PhDs

09:00.000 --> 09:03.000
and thought it's right to put by hand every time.

09:03.000 --> 09:05.000
So let's give it a go.

09:09.000 --> 09:12.000
So let's say you're, again, you don't even really know much about the team,

09:12.000 --> 09:14.000
but because we're using an LLM,

09:14.000 --> 09:16.000
we can use an actual language to identify the team.

09:16.000 --> 09:18.000
Here, I'm asking for a example,

09:18.000 --> 09:20.000
that team with the bull logo,

09:20.000 --> 09:24.000
and we get a streaming response with a chance for the Chicago.

09:24.000 --> 09:26.000
That's right.

09:26.000 --> 09:28.000
Let's try something else.

09:28.000 --> 09:30.000
So once you give it to your favorite player.

09:30.000 --> 09:31.000
Clay Johnson.

09:31.000 --> 09:32.000
Clay Johnson.

09:32.000 --> 09:34.000
Let's play Johnson.

09:34.000 --> 09:35.000
Let's play Johnson.

09:35.000 --> 09:38.000
So we'll ask for the team,

09:38.000 --> 09:40.000
Clay Johnson.

09:40.000 --> 09:41.000
Thanks.

09:43.000 --> 09:45.000
Can we get?

09:45.000 --> 09:49.000
Oh, we have a Golden State Warriors chapter.

09:49.000 --> 09:53.000
So I say a few people have a chat about that, which is great.

09:53.000 --> 09:55.000
And this is a wrong team.

09:55.000 --> 09:58.000
And for those of you who are not basketball fans, let me explain.

09:58.000 --> 10:01.000
This player moves teams in the Golden State Warriors.

10:01.000 --> 10:02.000
Two Dallas map Chris.

10:02.000 --> 10:05.000
But they only move teams at the start of this season.

10:05.000 --> 10:08.000
So what's happened here is the LLM is on the best kickhand

10:08.000 --> 10:10.000
with the day to extreme them.

10:10.000 --> 10:12.000
They don't have access to the LLM team monsters.

10:13.000 --> 10:14.000
That's okay.

10:14.000 --> 10:16.000
Because we're controlling a system prompt,

10:16.000 --> 10:20.000
we can provide more context as part of the request.

10:20.000 --> 10:22.000
So I'm going to head over to BS code.

10:22.000 --> 10:26.000
I'm going to head a new file for players.txt.

10:26.000 --> 10:30.000
And I'm going to pay for all the current players

10:30.000 --> 10:33.000
and the team will pay for that.

10:33.000 --> 10:37.000
Then I'm going to provide the contents of this file

10:37.000 --> 10:39.000
as part of the system for.

10:42.000 --> 10:45.000
Now we're going to head back to console.

10:45.000 --> 10:47.000
We can run the same example.

10:47.000 --> 10:51.000
Hopefully we get back what we want.

10:51.000 --> 10:54.000
That Dallas map is done.

10:54.000 --> 10:56.000
Okay, let's check in with whatever else.

10:56.000 --> 11:04.000
So we've written a streaming chat GPT client using Swift Open API.

11:04.000 --> 11:07.000
So we've reached the point where we'd like to build all of them.

11:07.000 --> 11:09.000
With data that we maintain.

11:09.000 --> 11:11.000
I'm probably want to update.

11:11.000 --> 11:13.000
So I'm going to hand out on the.

11:13.000 --> 11:16.000
We'll start moving some of this logic into the proxy.

11:16.000 --> 11:17.000
Thank you.

11:17.000 --> 11:21.000
So we'll start by creating a new target for the proxy server.

11:27.000 --> 11:30.000
And this target depends on the chat GPT client we already had.

11:30.000 --> 11:32.000
And it uses Swift Open API generator.

11:32.000 --> 11:36.000
So we're going to need to provide an Open API documents and a config file.

11:36.000 --> 11:41.000
So create the target directory first.

11:41.000 --> 11:44.000
And the Open API document.

11:49.000 --> 11:51.000
Open the preview.

11:51.000 --> 11:55.000
And here we have the tailored API for trend GPT.

11:55.000 --> 11:59.000
It has one input for the user prompt.

11:59.000 --> 12:03.000
And a streaming format for output called JSON lines.

12:04.000 --> 12:18.000
So now that we have the API we need to set up the config file.

12:18.000 --> 12:21.000
And online the config file we saw on the client.

12:21.000 --> 12:24.000
This one will actually generate the server step for us.

12:24.000 --> 12:26.000
So another way to the plugin configure.

12:26.000 --> 12:31.000
We can create the entry file.

12:31.000 --> 12:34.000
And we can create a new dependencies.

12:34.000 --> 12:37.000
And define a type that conforms to this generated protocol.

12:37.000 --> 12:39.000
Called API protocol.

12:39.000 --> 12:44.000
This protocol defines one function for each operation in the Open API document.

12:44.000 --> 12:47.000
And the input and output types are all generated.

12:47.000 --> 12:51.000
And the handled with deceleration and validation of the request.

12:51.000 --> 12:54.000
And the serialization of the response.

12:54.000 --> 12:58.000
So for now this handler just goes an unemployed error.

12:58.000 --> 13:04.000
And we do that by calling this generated function called register handlers.

13:04.000 --> 13:10.000
Which route is the incoming requests to our type save and the functions here above.

13:10.000 --> 13:12.000
So now that we have the scaffolding ready.

13:12.000 --> 13:15.000
Let's start filling in the business logic in the handler.

13:15.000 --> 13:18.000
We step by unwraping the request content in the body.

13:18.000 --> 13:22.000
And returning an error in an unexpected content was provided.

13:22.000 --> 13:27.000
Next I'm going to switch over the command line code base.

13:27.000 --> 13:30.000
I'm going to take all of this code.

13:30.000 --> 13:35.000
I'm going to copy it and paste it in the handler.

13:35.000 --> 13:41.000
And we're already now we're updating this code that used to run in our macOS CI to instead run

13:41.000 --> 13:43.000
in our Linux server.

13:43.000 --> 13:48.000
So first I'm going to update a transport from URL session to a single HTTP client.

13:48.000 --> 13:52.000
And again notice that this is the same generated touch HTTP client we saw before.

13:52.000 --> 13:56.000
Just go strap with a different underlying HTTP library.

13:56.000 --> 14:00.000
So next I'm going to update the input instead of taking the command line arguments.

14:00.000 --> 14:04.000
That we're going to take it from the request by.

14:04.000 --> 14:11.000
And next I'm going to delete all of this code that used to run to the console.

14:11.000 --> 14:16.000
So what we have here is this async sequence of these shared GPT payload elements.

14:16.000 --> 14:21.000
So we need to map over to when async sequence of these proxy payload elements,

14:21.000 --> 14:23.000
which are simplified version of them.

14:23.000 --> 14:28.000
And finally, we take this map async sequence.

14:28.000 --> 14:30.000
We encode it as JSON lines.

14:30.000 --> 14:35.000
And we will turn it as the 200 okay response from our server.

14:35.000 --> 14:44.000
So I'm going to build and run the server now.

14:44.000 --> 14:46.000
And in a second when it finishes building,

14:46.000 --> 14:51.000
it will start running on localhost port 8080.

14:52.000 --> 14:57.000
That's great, that's running. So let's give it a try.

14:57.000 --> 15:00.000
So we're using crawl here in streaming mode,

15:00.000 --> 15:06.000
and we're calling the chant endpoint on our local server.

15:06.000 --> 15:09.000
And once the HTTP starts streaming the response to the proxy,

15:09.000 --> 15:13.000
we see forwarded to crawl as this individual JSON line events.

15:13.000 --> 15:15.000
So let's recap what we are.

15:15.000 --> 15:18.000
We've got this Linux proxy server offering a tailored API,

15:18.000 --> 15:22.000
and of course, tragedy, PT, and it returns a streaming response.

15:22.000 --> 15:24.000
And since we give it a few more minutes left,

15:24.000 --> 15:28.000
let me head over to side to update the climbs to call this proxy server now.

15:31.000 --> 15:33.000
Okay, thanks Sansa.

15:33.000 --> 15:37.000
So I'm going to leave this proxy server running

15:37.000 --> 15:39.000
and inside the Linux container,

15:39.000 --> 15:42.000
listing on localhosts, and we'll update the CLI to call that,

15:42.000 --> 15:45.000
instead of calling CHAPGPT directly.

15:46.000 --> 15:49.000
Now, because the proxy server was also defined using Open API,

15:49.000 --> 15:51.000
we're going to use Swift Open API one more time,

15:51.000 --> 15:54.000
but this time we're going to be generating a proxy client.

15:54.000 --> 15:56.000
So I'm going to start by taking the Open API document,

15:56.000 --> 15:59.000
and the config file from the proxy server,

15:59.000 --> 16:02.000
and copy it into the client CLI target directory.

16:02.000 --> 16:06.000
I'll update the config file to generate the client code.

16:08.000 --> 16:11.000
So I'll update the package manifest so that the client CLI

16:11.000 --> 16:14.000
or no longer depends on the CHAPGPT module,

16:14.000 --> 16:16.000
because it doesn't need anymore.

16:16.000 --> 16:20.000
And instead, it uses Open API plugin to generate the proxy client.

16:20.000 --> 16:24.000
So now I'll update the code for the CLI.

16:24.000 --> 16:28.000
I'll start by removing the CHAPGPT import,

16:28.000 --> 16:32.000
and then replace CHAPGPT.client with just client.

16:32.000 --> 16:35.000
Because this client is now target local,

16:35.000 --> 16:38.000
it's the generated client for the proxy server.

16:38.000 --> 16:41.000
We're going to use a local first URL,

16:41.000 --> 16:43.000
and we don't need this middle way more,

16:43.000 --> 16:47.000
because authentication is being handled by the proxy server.

16:47.000 --> 16:50.000
The system point is also being handled by the proxy server,

16:50.000 --> 16:52.000
along with the updated team roasters.

16:52.000 --> 16:53.000
So we'll give it to that,

16:53.000 --> 16:59.000
and any code relates to creating the request or making the request.

16:59.000 --> 17:03.000
And I'll replace it with this updated code to make a request

17:03.000 --> 17:04.000
to the proxy server.

17:04.000 --> 17:07.000
So here we're using the Create CHAPGPT endpoint,

17:07.000 --> 17:09.000
providing just a user input,

17:09.000 --> 17:12.000
and we're iterating over much simplified JSON lines

17:12.000 --> 17:16.000
async sequence for the contains only the information that we're interested in.

17:16.000 --> 17:18.000
So now if I run our example one more time,

17:18.000 --> 17:20.000
we'll play Thompson,

17:20.000 --> 17:23.000
which hopefully still get what we want,

17:23.000 --> 17:25.000
which is a data smaver exchange because the service handling

17:25.000 --> 17:27.000
those updated roasters in the system prompt,

17:27.000 --> 17:30.000
and should all be streamed ends at end.

17:31.000 --> 17:33.000
So we've got to be as code,

17:33.000 --> 17:37.000
we can see that vapor handled the chant request here.

17:37.000 --> 17:41.000
So we've covered a lot of ground here, so let me recap.

17:41.000 --> 17:43.000
We've built a proxy server,

17:43.000 --> 17:46.000
and a CLI for a tailored use case backed by CHAPGPT,

17:46.000 --> 17:48.000
and it had ends streaming.

17:48.000 --> 17:51.000
And we didn't need to write a single line of networking code,

17:51.000 --> 17:53.000
or serialization logic, because of Swift open API.

17:53.000 --> 17:55.000
In just over 15 minutes,

17:55.000 --> 17:56.000
we've written three targets,

17:56.000 --> 17:59.000
using three different underlying transports for two platforms.

18:00.000 --> 18:03.000
Thank you so much for joining us for this session.

18:03.000 --> 18:05.000
If you have any questions about this demo,

18:05.000 --> 18:07.000
we'll split the open API project,

18:07.000 --> 18:08.000
we can just follow and say hi,

18:08.000 --> 18:11.000
and we'll be out in the hallway after this session.

