@@ -4229,7 +4229,7 @@ Contributions are welcome! This library uses the MIT license.
4229
4229
logic in their app to select which service to use:
4230
4230
4231
4231
```
4232
- let service = byok ? AIProxy.openaiDirectService() : AIProxy.openaiProxiedService ()
4232
+ let service = byok ? AIProxy.openaiDirectService() : AIProxy.openaiService ()
4233
4233
```
4234
4234
2. We prevent the direct and proxied concrete types from diverging in the public
4235
4235
interface. As we add more functionality to the service's protocol, the compiler helps
@@ -4311,8 +4311,178 @@ Contributions are welcome! This library uses the MIT license.
4311
4311
}
4312
4312
```
4313
4313
4314
+ ***
4315
+
4314
4316
### Release naming guidelines
4315
4317
4316
4318
Give each release a semantic version *without* a `v` prefix on the version name. That is the
4317
4319
most reliable way to make Xcode's `File > Add Package Dependency` flow default to sane version
4318
4320
values.
4321
+
4322
+ ***
4323
+
4324
+ ### How to use AIProxySwift with custom services
4325
+
4326
+ If you'd like to proxy calls to a service that we don't have built-in support for, you can do
4327
+ that with the following steps.
4328
+
4329
+ We recommend that you first go through the [standard integration video](https://www.aiproxy.com/docs/integration-guide.html)
4330
+ for a built-in service. This way, any complications that you encounter with DeviceCheck will
4331
+ be overcome before you embark on the custom integration. Once you are seeing 200s from a
4332
+ built-in service, take the following steps to add a custom service to your app:
4333
+
4334
+ 1. Create an encodable representation of the request body. Let's say you are looking at a
4335
+ service's API docs and they specify an endpoint like this:
4336
+
4337
+ POST api.example.com/chat
4338
+
4339
+ Request body:
4340
+
4341
+ - `great_prompt`: String
4342
+
4343
+ You would define a request body that looks like this:
4344
+
4345
+ ```swift
4346
+ struct ChatRequestBody: Encodable {
4347
+ let greatPrompt: String
4348
+
4349
+ enum CodingKey: String, CodingKeys {
4350
+ case greatPrompt = "great_prompt"
4351
+ }
4352
+ }
4353
+ ```
4354
+
4355
+ 2. Create a decodable represenation of the response body. Imagining an expanded API
4356
+ definition from above:
4357
+
4358
+ POST api.example.com/chat
4359
+
4360
+ Request body:
4361
+
4362
+ - `great_prompt`: String
4363
+
4364
+ Response body:
4365
+
4366
+ - `generated_message`: String
4367
+
4368
+ You would define a response body that looks like this:
4369
+
4370
+ ```swift
4371
+ struct ChatResponseBody: Decodable {
4372
+ let generatedMessage: String?
4373
+
4374
+ enum CodingKey: String, CodingKeys {
4375
+ case generatedMessage = "generated_message"
4376
+ }
4377
+ }
4378
+ ```
4379
+
4380
+ This example is straightforward. If the response body has a nested structure, which many
4381
+ do, you will need to add Decodables for the nested types. See the [Contribution style guidelines](#contribution-style-guidelines)
4382
+ above for an example of creating nested decodables.
4383
+
4384
+ Pasting the API documentation into an LLM may get you a close representation of the nested
4385
+ structure that you can then polish.
4386
+
4387
+
4388
+ 3. Pay attention to the authorization header in your service's API docs. If it is of the form
4389
+ `Authorization: Bearer your-key` then it will work out of the box. If it is another form,
4390
+ please message me as I'll need to do a backend deploy (it's quick).
4391
+
4392
+ 4. Create your service in the AIProxy dashboard entering in the base proxy URL for your
4393
+ service, e.g. in the example above it would be `api.example.com`
4394
+
4395
+ 5. Submit your API key through the AIProxy dashboard for your service. You will get back a
4396
+ partial key and a service URL.
4397
+
4398
+ 6. Use the information from preceeding steps to craft a request to AIProxy using the top level
4399
+ helper `AIProxy.request`. Continuing the example from above:
4400
+
4401
+ ```swift
4402
+ import AIProxy
4403
+
4404
+ func makeTestRequest() async throws {
4405
+ let requestBody = ChatRequestBody(
4406
+ greatPrompt: "hello world"
4407
+ )
4408
+
4409
+ let request = try await AIProxy.request(
4410
+ partialKey: "partial-key-from-step-5",
4411
+ serviceURL: "service-url-from-step-5",
4412
+ proxyPath: "/chat",
4413
+ body: try JSONEncoder().encode(requestBody),
4414
+ verb: .post,
4415
+ headers: [
4416
+ "content-type": "application/json"
4417
+ ]
4418
+ )
4419
+
4420
+ let session = AIProxy.session()
4421
+ let (data, res) = try await session.data(for: request)
4422
+ guard let httpResponse = res as? HTTPURLResponse else {
4423
+ print("Network response is not an http response")
4424
+ return
4425
+ }
4426
+ guard httpResponse.statusCode >= 200 && httpResponse.statusCode <= 299 else {
4427
+ print("Non-200 response")
4428
+ return
4429
+ }
4430
+ let chatResponseBody = try JSONDecoder().decode(
4431
+ ChatResponseBody.self,
4432
+ from: data
4433
+ )
4434
+ print(chatResponseBody.generatedMessage)
4435
+ }
4436
+ ```
4437
+
4438
+ 6. Watch the Live Console in the AIProxy dashboard as you make test requests. It will tell you
4439
+ if a status code other than 200 is being returned.
4440
+
4441
+ At this point you should see successful responses in your Xcode project. If you are not, double
4442
+ check your decodable definitions. If you are still not getting successful responses, message me
4443
+ your encodables and decodables and I'll take a look as as soon as possible.
4444
+
4445
+ ***
4446
+
4447
+ ### Traffic sniffing with docker and mitmproxy (advanced)
4448
+
4449
+ The method above uses the documentation of a service to build the appropriate request and
4450
+ response structures. There is another way, which takes longer to set up but has the advantage
4451
+ of not relying on potentially stale documentation.
4452
+
4453
+ Most providers have an official node client. You can run the node client in a sample project
4454
+ inside a docker container and point traffic at mitmproxy to inspect the contents of the
4455
+ request/response structures. You can then take the request/response bodies and paste them into
4456
+ an LLM to generate the encodable/decodable swift representations. Here's how:
4457
+
4458
+ 1. Install mitmproxy and run it with `mitmproxy --listen-port 9090`
4459
+
4460
+ 2. Create a Docker container using the client you are interested in. For example, to sniff traffic
4461
+ from Gemini's official lib, I do this:
4462
+
4463
+ mkdir ~/dev/node_docker_sandbox
4464
+ cd ~/dev/node_docker_sandbox
4465
+ cp ~/.mitmproxy/mitmproxy-ca-cert.pem .
4466
+ docker pull node:22
4467
+ vim Dockerfile
4468
+
4469
+ FROM node:22
4470
+ WORKDIR /entrypoint
4471
+ COPY mitmproxy-ca-cert.pem /usr/local/share/ca-certificates/mitmproxy-ca-cert.pem
4472
+ ENV NODE_EXTRA_CA_CERTS=/usr/local/share/ca-certificates/mitmproxy-ca-cert.pem
4473
+ CMD ["node", "/entrypoint/generative-ai-js/samples/text_generation.js"]
4474
+
4475
+ git clone https://github.com/google/generative-ai-js
4476
+ npm install --prefix generative-ai-js/samples
4477
+ docker --debug build -t node_docker_sandbox .
4478
+
4479
+ 3. In Docker Desktop, go to Settings > Resources > Proxies and flip on 'Manual proxy
4480
+ configuration'. Set both 'Web Server' and 'Secure Web Server' to `http://localhost:9090`
4481
+
4482
+ 4. Run the docker container:
4483
+
4484
+ docker run --volume "$(pwd)/:/entrypoint/" node_docker_sandbox
4485
+
4486
+ If all is set up correctly, you will see requests and responses flow through mitmproxy in plain
4487
+ text. You can use those bodies to build your swift structs, implementing an encodable
4488
+ representation for the request body and decodable representation for response body.
0 commit comments