Skip to content

Commit

Permalink
Expose routes over OpenAPI (#33)
Browse files Browse the repository at this point in the history
* Expose routes over OpenAPI

Signed-off-by: Matteo Collina <[email protected]>

* fixup

Signed-off-by: Matteo Collina <[email protected]>

* moar guide

Signed-off-by: Matteo Collina <[email protected]>

* fixup

Signed-off-by: Matteo Collina <[email protected]>

* fixup

Signed-off-by: Matteo Collina <[email protected]>

* fixup

Signed-off-by: Matteo Collina <[email protected]>

---------

Signed-off-by: Matteo Collina <[email protected]>
  • Loading branch information
mcollina authored May 8, 2024
1 parent 34df76a commit 6b39c51
Show file tree
Hide file tree
Showing 14 changed files with 166 additions and 38 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/lint-md.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ jobs:
lint-md:
name: Linting Markdown
runs-on: ubuntu-latest
needs: setup-node-modulessteps:
needs: setup-node-modules
steps:
- name: Git Checkout
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11

Expand Down
1 change: 1 addition & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package-lock=true
23 changes: 21 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,31 @@ Steps for downloading and setting up AI Warp for local development.
is located at `ai-warp-app/platformatic.json`. **Note: this will be overwrited
every time you generate the test app.**
8. Start the test app.
8. Start the test app. From the `app-warp-ai` folder, run:
```bash
npm start
node ../dist/cli/start.js
```
### Testing a local model with llama2
To test a local model with with llama2, you can use the following to
download the model we used for testing:
```bash
curl -L -O https://huggingface.co/TheBloke/Mistral-7B-Instruct-v0.2-GGUF/resolve/main/mistral-7b-instruct-v0.2.Q8_0.gguf
```
Then, in your `platformatic.json` file, add:
```json
"aiProvider": {
"llama2": {
"modelPath": "./mistral-7b-instruct-v0.2.Q8_0.gguf"
}
},
```
## Important Notes
* AI Warp needs to be rebuilt for any code change to take affect in your test
Expand Down
10 changes: 5 additions & 5 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,18 @@ const stackable: Stackable<AiWarpConfig> = async function (fastify, opts) {
await fastify.register(fastifyUser as any, config.auth)
await fastify.register(authPlugin, opts)

await fastify.register(warpPlugin, opts) // needs to be registered here for fastify.ai to be decorated

await fastify.register(rateLimitPlugin, opts)
await fastify.register(apiPlugin, opts)

if (config.showAiWarpHomepage !== undefined && config.showAiWarpHomepage) {
await fastify.register(fastifyStatic, {
root: join(import.meta.dirname, 'static')
})
}

await fastify.register(platformaticService, opts)

await fastify.register(warpPlugin, opts) // needs to be registered here for fastify.ai to be decorated

await fastify.register(rateLimitPlugin, opts)
await fastify.register(apiPlugin, opts)
}

stackable.configType = 'ai-warp-app'
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
"lint-md": "markdownlint-cli2 .",
"lint-md:fix": "markdownlint-cli2 --fix .",
"test": "npm run test:unit && npm run test:e2e && npm run test:types",
"test:unit": "node --test --test-reporter=@reporters/github --test-reporter-destination=stdout --test-reporter=spec --test-reporter-destination=stdout --import=tsx ./tests/unit/index.ts",
"test:e2e": "node --test --test-reporter=@reporters/github --test-reporter-destination=stdout --test-reporter=spec --test-reporter-destination=stdout --import=tsx ./tests/e2e/index.ts",
"test:unit": "node --test --test-reporter=@reporters/github --test-reporter-destination=stdout --test-reporter=spec --test-reporter-destination=stdout --import=tsx --test-concurrency=1 ./tests/unit/*",
"test:e2e": "node --test --test-reporter=@reporters/github --test-reporter-destination=stdout --test-reporter=spec --test-reporter-destination=stdout --import=tsx --test-concurrency=1 ./tests/e2e/*",
"test:types": "tsd"
},
"engines": {
Expand Down
2 changes: 1 addition & 1 deletion plugins/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const plugin: FastifyPluginAsyncTypebox = async (fastify) => {
response: Type.String()
}),
default: Type.Object({
code: Type.String(),
code: Type.Optional(Type.String()),
message: Type.String()
})
}
Expand Down
59 changes: 59 additions & 0 deletions tests/e2e/api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { buildAiWarpApp } from '../utils/stackable.js'
import { AZURE_DEPLOYMENT_NAME, AZURE_MOCK_HOST } from '../utils/mocks/azure.js'
import { MOCK_CONTENT_RESPONSE, buildExpectedStreamBodyString } from '../utils/mocks/base.js'
import { OLLAMA_MOCK_HOST } from '../utils/mocks/ollama.js'
import { mockAllProviders } from '../utils/mocks/index.js'
mockAllProviders()

const expectedStreamBody = buildExpectedStreamBodyString()

Expand Down Expand Up @@ -108,6 +110,7 @@ for (const { name, config } of providers) {
prompt: 'asd'
})
})

assert.strictEqual(res.headers.get('content-type'), 'text/event-stream')

assert.strictEqual(chunkCallbackCalled, true)
Expand Down Expand Up @@ -170,3 +173,59 @@ it('calls the preResponseCallback', async () => {

await app.close()
})

it('provides all paths in OpenAPI', async () => {
const [app, port] = await buildAiWarpApp({
aiProvider: {
openai: {
model: 'gpt-3.5-turbo',
apiKey: ''
}
}
})

await app.start()

const res = await fetch(`http://localhost:${port}/documentation/json`)
const body = await res.json()

assert.deepStrictEqual(Object.keys(body.paths), [
'/api/v1/prompt',
'/api/v1/stream'
])

await app.close()
})

it('prompt with wrong JSON', async () => {
const [app, port] = await buildAiWarpApp({
aiProvider: {
openai: {
model: 'gpt-3.5-turbo',
apiKey: ''
}
}
})

await app.start()

const res = await fetch(`http://localhost:${port}/api/v1/prompt`, {
method: 'POST',
headers: {
'content-type': 'application/json'
},
body: JSON.stringify({
prompt: 'asd'
}).slice(0, 10)
})

assert.strictEqual(res.status, 400)

const body = await res.json()

assert.deepStrictEqual(body, {
message: 'Unexpected end of JSON input'
})

await app.close()
})
2 changes: 2 additions & 0 deletions tests/e2e/auth.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import assert from 'node:assert'
import { buildAiWarpApp } from '../utils/stackable.js'
import { AiWarpConfig } from '../../config.js'
import { authConfig, createToken } from '../utils/auth.js'
import { mockAllProviders } from '../utils/mocks/index.js'
mockAllProviders()

const aiProvider: AiWarpConfig['aiProvider'] = {
openai: {
Expand Down
6 changes: 0 additions & 6 deletions tests/e2e/index.ts

This file was deleted.

78 changes: 65 additions & 13 deletions tests/e2e/rate-limiting.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import fastifyPlugin from 'fastify-plugin'
import { AiWarpConfig } from '../../config.js'
import { buildAiWarpApp } from '../utils/stackable.js'
import { authConfig, createToken } from '../utils/auth.js'
import { mockAllProviders } from '../utils/mocks/index.js'
mockAllProviders()

const aiProvider: AiWarpConfig['aiProvider'] = {
openai: {
Expand All @@ -28,7 +30,15 @@ it('calls ai.rateLimiting.max callback', async () => {

await app.start()

const res = await fetch(`http://localhost:${port}`)
const res = await fetch(`http://localhost:${port}/api/v1/prompt`, {
method: 'POST',
headers: {
'content-type': 'application/json'
},
body: JSON.stringify({
prompt: 'asd'
})
})
assert.strictEqual(callbackCalled, true)
assert.strictEqual(res.headers.get('x-ratelimit-limit'), `${expectedMax}`)
} finally {
Expand All @@ -50,7 +60,15 @@ it('calls ai.rateLimiting.allowList callback', async () => {

await app.start()

await fetch(`http://localhost:${port}`)
await fetch(`http://localhost:${port}/api/v1/prompt`, {
method: 'POST',
headers: {
'content-type': 'application/json'
},
body: JSON.stringify({
prompt: 'asd'
})
})
assert.strictEqual(callbackCalled, true)
} finally {
await app.close()
Expand All @@ -76,13 +94,21 @@ it('calls ai.rateLimiting.onBanReach callback', async () => {

app.ai.rateLimiting.errorResponseBuilder = () => {
errorResponseBuilderCalled = true
return { error: 'rate limited' }
return { message: 'rate limited' }
}
}))

await app.start()

await fetch(`http://localhost:${port}`)
await fetch(`http://localhost:${port}/api/v1/prompt`, {
method: 'POST',
headers: {
'content-type': 'application/json'
},
body: JSON.stringify({
prompt: 'asd'
})
})
assert.strictEqual(onBanReachCalled, true)
assert.strictEqual(errorResponseBuilderCalled, true)
} finally {
Expand All @@ -104,7 +130,15 @@ it('calls ai.rateLimiting.keyGenerator callback', async () => {

await app.start()

await fetch(`http://localhost:${port}`)
await fetch(`http://localhost:${port}/api/v1/prompt`, {
method: 'POST',
headers: {
'content-type': 'application/json'
},
body: JSON.stringify({
prompt: 'asd'
})
})
assert.strictEqual(callbackCalled, true)
} finally {
await app.close()
Expand All @@ -120,13 +154,21 @@ it('calls ai.rateLimiting.errorResponseBuilder callback', async () => {
app.ai.rateLimiting.max = () => 0
app.ai.rateLimiting.errorResponseBuilder = () => {
callbackCalled = true
return { error: 'rate limited' }
return { message: 'rate limited' }
}
}))

await app.start()

await fetch(`http://localhost:${port}`)
await fetch(`http://localhost:${port}/api/v1/prompt`, {
method: 'POST',
headers: {
'content-type': 'application/json'
},
body: JSON.stringify({
prompt: 'asd'
})
})
assert.strictEqual(callbackCalled, true)
} finally {
await app.close()
Expand Down Expand Up @@ -156,17 +198,27 @@ it('uses the max for a specific claim', async () => {
try {
await app.start()

let res = await fetch(`http://localhost:${port}`, {
let res = await fetch(`http://localhost:${port}/api/v1/prompt`, {
method: 'POST',
headers: {
Authorization: `Bearer ${createToken({ rateLimitMax: '10' })}`
}
Authorization: `Bearer ${createToken({ rateLimitMax: '10' })}`,
'content-type': 'application/json'
},
body: JSON.stringify({
prompt: 'asd'
})
})
assert.strictEqual(res.headers.get('x-ratelimit-limit'), '10')

res = await fetch(`http://localhost:${port}`, {
res = await fetch(`http://localhost:${port}/api/v1/prompt`, {
method: 'POST',
headers: {
Authorization: `Bearer ${createToken({ rateLimitMax: '100' })}`
}
Authorization: `Bearer ${createToken({ rateLimitMax: '100' })}`,
'content-type': 'application/json'
},
body: JSON.stringify({
prompt: 'asd'
})
})
assert.strictEqual(res.headers.get('x-ratelimit-limit'), '100')
} finally {
Expand Down
2 changes: 2 additions & 0 deletions tests/unit/ai-providers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import { MOCK_CONTENT_RESPONSE, buildExpectedStreamBodyString } from '../utils/m
import { OLLAMA_MOCK_HOST } from '../utils/mocks/ollama.js'
import { AZURE_DEPLOYMENT_NAME, AZURE_MOCK_HOST } from '../utils/mocks/azure.js'
import { mockLlama2 } from '../utils/mocks/llama2.js'
import { mockAllProviders } from '../utils/mocks/index.js'
mockAllProviders()

const expectedStreamBody = buildExpectedStreamBodyString()

Expand Down
2 changes: 2 additions & 0 deletions tests/unit/generator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { join } from 'node:path'
import AiWarpGenerator from '../../lib/generator.js'
import { generateGlobalTypesFile } from '../../lib/templates/types.js'
import { generatePluginWithTypesSupport } from '@platformatic/generators/lib/create-plugin.js'
import { mockAllProviders } from '../utils/mocks/index.js'
mockAllProviders()

const tempDirBase = join(import.meta.dirname, 'tmp')

Expand Down
5 changes: 0 additions & 5 deletions tests/unit/index.ts

This file was deleted.

7 changes: 4 additions & 3 deletions tests/utils/stackable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,14 @@ export async function buildAiWarpApp (config: AiWarpConfig): Promise<[FastifyIns
server: {
port,
forceCloseConnections: true,
healthCheck: {
enabled: false
},
healthCheck: false,
logger: {
level: 'silent'
}
},
service: {
openapi: true
},
...config
}, stackable)

Expand Down

0 comments on commit 6b39c51

Please sign in to comment.