Skip to content

Commit 6c1f567

Browse files
authored
Add instructions for proxying a custom service (#105)
1 parent 56751b3 commit 6c1f567

File tree

1 file changed

+171
-1
lines changed

1 file changed

+171
-1
lines changed

README.md

+171-1
Original file line numberDiff line numberDiff line change
@@ -4229,7 +4229,7 @@ Contributions are welcome! This library uses the MIT license.
42294229
logic in their app to select which service to use:
42304230
42314231
```
4232-
let service = byok ? AIProxy.openaiDirectService() : AIProxy.openaiProxiedService()
4232+
let service = byok ? AIProxy.openaiDirectService() : AIProxy.openaiService()
42334233
```
42344234
2. We prevent the direct and proxied concrete types from diverging in the public
42354235
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.
43114311
}
43124312
```
43134313
4314+
***
4315+
43144316
### Release naming guidelines
43154317
43164318
Give each release a semantic version *without* a `v` prefix on the version name. That is the
43174319
most reliable way to make Xcode's `File > Add Package Dependency` flow default to sane version
43184320
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

Comments
 (0)