Skip to content

Commit

Permalink
Add e2e test
Browse files Browse the repository at this point in the history
  • Loading branch information
blrchen committed May 2, 2023
1 parent 98f26c2 commit 1effb9b
Show file tree
Hide file tree
Showing 8 changed files with 102 additions and 80 deletions.
24 changes: 13 additions & 11 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,33 +1,35 @@
# Building layer
FROM node:18-alpine as development
# Optional NPM automation (auth) token build argument
# ARG NPM_TOKEN
# Optionally authenticate NPM registry
# RUN npm set //registry.npmjs.org/:_authToken ${NPM_TOKEN}
WORKDIR /app

# Copy configuration files
COPY tsconfig*.json ./
COPY package*.json ./
# Install dependencies from package-lock.json, see https://docs.npmjs.com/cli/v7/commands/npm-ci

# Install dependencies from package-lock.json
RUN npm ci

# Copy application sources (.ts, .tsx, js)
COPY src/ src/

# Build application (produces dist/ folder)
RUN npm run build

# Runtime (production) layer
FROM node:18-alpine as production
# Optional NPM automation (auth) token build argument
# ARG NPM_TOKEN
# Optionally authenticate NPM registry
# RUN npm set //registry.npmjs.org/:_authToken ${NPM_TOKEN}
WORKDIR /app

# Copy dependencies files
COPY package*.json ./
# Install runtime dependecies (without dev/test dependecies)
RUN npm ci --omit=dev

# Install runtime dependencies (without dev/test dependencies)
RUN npm ci --only=production

# Copy production build
COPY --from=development /app/dist/ ./dist/

# Expose application port
EXPOSE 3000

# Start application
CMD [ "node", "dist/main.js" ]
23 changes: 17 additions & 6 deletions README.en-US.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,24 @@ curl -X "POST" "http://localhost:3000/v1/chat/completions" \
}'
```

## Local Running and Testing, Using WebChat for Streaming Test
## App has been tested

1. Clone code in command line window
2. Update environment variable of OPENAPI_API_KEY on line nine of `docker-compose.yml` file with `YOUR_RESOURCE_ID:YOUR_MODEL_DEPLOYMENT:YOUR_API_KEY`
3. Execute build: `run docker-compose build`
4. Start service: `run docker-compose up -d`
5. Launch `http://localhost:3000`
The following apps have been tested and confirmed to work with the azure-openai-proxy:

| App Name | E2E Docker-compose file |
|------------------|-------------------------|
| [chatbot-ui](https://github.com/mckaywrigley/chatbot-ui) | [docker-compose.yml](./e2e/chatbot-ui/docker-compose.yml) |
| [chatgpt-web](https://github.com/Chanzhaoyu/chatgpt-web) | [docker-compose.yml](./e2e/chatgpt-web/docker-compose.yml) |
| [gptlite](https://github.com/blrchen/gptlite) | [docker-compose.yml](./e2e/gptlite/docker-compose.yml) |

To run a test locally, please follow these steps:

1. Clone the code in a command-line window.
2. Update the environment variable `OPENAPI_API_KEY` with `YOUR_RESOURCE_ID:YOUR_MODEL_DEPLOYMENT:YOUR_API_KEY`. Alternatively, you can update the OPENAPI_API_KEY value directly in the docker-compose.yml file.
3. Navigate to the directory containing the `docker-compose.yml` file for the app you want to test.
4. Execute the build command: `docker-compose build`.
5. Start the service: `docker-compose up -d`.
6. Based on the exposed port defined in the docker-compose.yml file, launch the app to test it locally. For example, visit http://localhost:3000.

## Frequently Asked Questions

Expand Down
23 changes: 17 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,24 @@ curl -X "POST" "http://localhost:3000/v1/chat/completions" \
}'
```

## 本地运行和测试,使用WebChat测试streaming
## 已测试应用

1. 克隆代码到命令行窗口
2. 更新 `docker-compose.yml` 文件中第九行的 `OPENAI_API_KEY` 环境变量,换成`YOUR_RESOURCE_ID:YOUR_MODEL_DEPLOYMENT:YOUR_API_KEY`
3. 执行构建:运行 `docker-compose build`
4. 启动服务:运行 `docker-compose up -d`
5. 运行`http://localhost:3000`
以下应用已经过测试,确认可以与 azure-openai-proxy 一起工作:

| App Name | E2E Docker-compose file |
|------------------|-------------------------|
| [chatbot-ui](https://github.com/mckaywrigley/chatbot-ui) | [docker-compose.yml](./e2e/chatbot-ui/docker-compose.yml) |
| [chatgpt-web](https://github.com/Chanzhaoyu/chatgpt-web) | [docker-compose.yml](./e2e/chatgpt-web/docker-compose.yml) |
| [gptlite](https://github.com/blrchen/gptlite) | [docker-compose.yml](./e2e/gptlite/docker-compose.yml) |

要在本地运行测试,请按照以下步骤操作:

1. 在命令行窗口中克隆代码。
2. 更新环境变量`OPENAPI_API_KEY`的值为`YOUR_RESOURCE_ID:YOUR_MODEL_DEPLOYMENT:YOUR_API_KEY`。或者,直接在`docker-compose.yml`文件中更新`OPENAPI_API_KEY`值。
3. 导航到包含要测试的应用程序的`docker-compose.yml`文件所在的目录。
3. 执行构建命令:`docker-compose build`
4. 启动服务:`docker-compose up -d`
5. 根据`docker-compose.yml`文件中定义的公开端口,启动应用以在本地进行测试。例如,访问 http://localhost:3000。

## 常见问题

Expand Down
25 changes: 0 additions & 25 deletions docker-compose.yml

This file was deleted.

59 changes: 39 additions & 20 deletions src/app.controller.ts
Original file line number Diff line number Diff line change
@@ -1,64 +1,83 @@
import { Controller, Get, Logger, Post, Req, Res } from '@nestjs/common';
import { AxiosHeaders } from 'axios';
import { Controller, Get, Post, Req, Res, Logger } from '@nestjs/common';
import { Request, Response } from 'express';
import { AxiosHeaders } from 'axios';
import { AppService } from './app.service';

const DEFAULT_API_VERSION = '2023-03-15-preview';

@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}

@Get('models')
models() {
getModels() {
return this.appService.getModels();
}
}

const DEFAULT_API_VERSION = '2023-03-15-preview';

@Controller('chat')
export class ChatController {
private readonly logger = new Logger(ChatController.name);

constructor(private readonly appService: AppService) {}

@Get()
getHello(): string {
getVersion(): string {
return this.appService.getVersion();
}

@Post('completions')
async completions(@Req() request: Request, @Res() res: Response) {
async postCompletions(@Req() request: Request, @Res() response: Response) {
const auth = request.headers['authorization'];
const apiKey = auth.replace('Bearer ', '');
const [resource_id, deployment_id, azureApiKey, apiVersion] = apiKey.split(':');
const [resourceId, deploymentId, azureApiKey, apiVersion] = apiKey.split(':');
this.logger.debug(
`resource_id: ${resource_id}, deployment_id: ${deployment_id}, azureApiKey: ${azureApiKey}, apiVersion: ${apiVersion}`,
`resourceId: ${resourceId}, deploymentId: ${deploymentId}, azureApiKey: ${azureApiKey}, apiVersion: ${apiVersion}`,
);
const endpoint = `https://${resource_id}.openai.azure.com`;

const endpoint = `https://${resourceId}.openai.azure.com`;
const stream = request.body['stream'];
const response = await this.appService.getCompletions(
const openaiResponse = await this.appService.getCompletions(
endpoint,
deployment_id,
deploymentId,
azureApiKey,
request.body,
stream,
apiVersion || DEFAULT_API_VERSION,
);

// set response headers
for (const [key, value] of response.headers as AxiosHeaders) {
res.header[key] = value;
// Set response headers
if (openaiResponse.headers instanceof Array) {
for (const [key, value] of openaiResponse.headers as AxiosHeaders) {
response.header[key] = value;
}
}

if (openaiResponse.status < 200 || openaiResponse.status >= 300) {
this.logger.error(
`The OpenAI has returned an error with status code ${openaiResponse.status} and message ${openaiResponse.statusText}`,
);
}
res.status(response.status);

response.status(openaiResponse.status);

if (stream) {
const streamData = response.data;
const streamData = openaiResponse.data;
streamData.on('data', (data) => {
res.write(data);
// Checks for the specific newline character returned by Azure OpenAI and
// replaces it with the expected newline character used by OpenAI
const decodedData = data.toString('utf8');
if (decodedData.includes('data: [DONE]')) {
response.write(`${decodedData}\n`);
} else {
response.write(data);
}
});
streamData.on('end', () => {
res.end();
response.end();
});
} else {
res.send(response.data);
response.send(openaiResponse.data);
}
}
}
24 changes: 14 additions & 10 deletions src/app.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ export class AppService {
stream: boolean,
apiVersion: string,
) {
const deployment_id = this.getDeploymentId(mapping, body['model']);
this.logger.debug(`deployment_id: ${deployment_id}`);
const url = `${endpoint}/openai/deployments/${deployment_id}/chat/completions?api-version=${apiVersion}`;
const deploymentId = this.getDeploymentId(mapping, body['model']);
this.logger.debug(`deploymentId: ${deploymentId}`);
const url = `${endpoint}/openai/deployments/${deploymentId}/chat/completions?api-version=${apiVersion}`;
const headers = {
'api-key': azureApiKey,
'Content-Type': 'application/json',
Expand All @@ -36,30 +36,34 @@ export class AppService {
if (stream) {
config['responseType'] = 'stream';
}
const ret = this.httpService.post(url, body, config);
const response = this.httpService.post(url, body, config);
try {
return await firstValueFrom(ret);
return await firstValueFrom(response);
} catch (e) {
return e.response;
}
}

private getDeploymentId(mapping: string, model: string): string {
this.logger.debug(`mapping: ${mapping}, model: ${model}`);
if (mapping.includes(',')) {
let defaultDeploymentId = '';
const modelMapping = mapping.split(',').reduce((acc: Record<string, string>, pair: string) => {
const [key, value] = pair.split('|');
if (defaultDeploymentId === '') defaultDeploymentId = value;
if (!defaultDeploymentId) {
defaultDeploymentId = value;
}
acc[key] = value;
return acc;
}, {});

if (!model) {
return defaultDeploymentId;
}
const deploymentId = modelMapping[model];
return deploymentId || defaultDeploymentId;
} else {
return mapping;

return modelMapping[model] || defaultDeploymentId;
}

return mapping;
}
}
2 changes: 1 addition & 1 deletion src/models.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export let models = {
export const models = {
object: 'list',
data: [
{
Expand Down
2 changes: 1 addition & 1 deletion test/app.e2e-spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import * as request from 'supertest';
import { AppModule } from './../src/app.module';
import { AppModule } from '../src/app.module';

describe('AppController (e2e)', () => {
let app: INestApplication;
Expand Down

0 comments on commit 1effb9b

Please sign in to comment.