Skip to content

Commit

Permalink
typescript openai plugin refactor (#24)
Browse files Browse the repository at this point in the history
* first pass openai plugin refactor

* add yml file for action to test typescript openai

* add starpoint as a package and not use relative path

* update npm package version for starpoint sdk
  • Loading branch information
jsungg authored Jul 29, 2023
1 parent 19428f0 commit 12db501
Show file tree
Hide file tree
Showing 13 changed files with 6,964 additions and 37 deletions.
25 changes: 25 additions & 0 deletions .github/workflows/test_typescript_openai.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: Typescript OpenAI Tests

on:
pull_request:
branches:
- main

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

jobs:
test-typescript-sdk:
name: Typescript OpenAI SDK Tests
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./typescript-openai
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: "16.x"
- run: npm install
- run: npm test
5 changes: 5 additions & 0 deletions typescript-openai/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
};
6,675 changes: 6,675 additions & 0 deletions typescript-openai/package-lock.json

Large diffs are not rendered by default.

33 changes: 33 additions & 0 deletions typescript-openai/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"name": "starpoint-openai",
"author": "pointable <package-maintainers@pointable.ai>",
"version": "0.1.0",
"description": "TypeScript OpenAI API for starpoint.ai",
"source": "src/index.ts",
"main": "dist/main.js",
"types": "dist/types.d.ts",
"module": "dist/module.js",
"devDependencies": {
"@parcel/packager-ts": "^2.8.3",
"@parcel/transformer-typescript-types": "^2.8.3",
"@types/jest": "^29.5.3",
"@types/node": "^20.4.2",
"@types/validator": "^13.7.17",
"jest": "^29.6.1",
"parcel": "^2.8.3",
"ts-jest": "^29.1.1",
"typescript": "^5.0.4",
"uuid": "^9.0.0"
},
"scripts": {
"dev": "parcel watch",
"build": "parcel build",
"test": "jest"
},
"dependencies": {
"axios": "^1.3.5",
"openai": "^3.3.0",
"starpoint": "^0.3.2",
"validator": "^13.9.0"
}
}
File renamed without changes.
54 changes: 45 additions & 9 deletions typescript/src/open-ai/index.ts → typescript-openai/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,23 @@
import { Configuration, OpenAIApi } from "openai";
import { APIResult, ErrorResponse, Option } from "../common-types";
import { db } from "starpoint";
import {
APIResult,
ErrorResponse,
Option,
} from "../../typescript/src/common-types";
import { backfillDocumentMetadata } from "./utility";
import {
BuildAndInsertEmbeddingsFromOpenAIRequest,
BuildAndInsertEmbeddingsRequest,
BuildAndInsertEmbeddingsFromOpenAIResponse,
} from "./types";
import { OPENAI_INSTANCE_INIT_ERROR } from "./constants";
import { sanitizeCollectionIdentifiersInRequest } from "../validators";
import { InsertResponse, TransposeAndInsertRequest } from "../writer/types";
import { handleError } from "../utility";
import { sanitizeCollectionIdentifiersInRequest } from "../../typescript/src/validators";
import {
InsertResponse,
TransposeAndInsertRequest,
} from "../../typescript/src/writer/types";
import { handleError } from "../../typescript/src/utility";

export interface InitOpenAIRequest {
openai_key?: Option<string>;
Expand All @@ -32,13 +41,11 @@ export const buildAndInsertEmbeddingsFromOpenAIFactory =
openAIApiClient: OpenAIApi | null,
columnInsert: (
req: TransposeAndInsertRequest
) => Promise<APIResult<InsertResponse, ErrorResponse>>
) => Promise<APIResult<InsertResponse>>
) =>
async (
request: BuildAndInsertEmbeddingsFromOpenAIRequest
): Promise<
APIResult<BuildAndInsertEmbeddingsFromOpenAIResponse, ErrorResponse>
> => {
): Promise<APIResult<BuildAndInsertEmbeddingsFromOpenAIResponse | null>> => {
try {
if (openAIApiClient === null) {
throw new Error(OPENAI_INSTANCE_INIT_ERROR);
Expand All @@ -55,6 +62,7 @@ export const buildAndInsertEmbeddingsFromOpenAIFactory =
});

const embeddingData = embeddingResponse.data.data;

if (embeddingData === null) {
const response: APIResult<BuildAndInsertEmbeddingsFromOpenAIResponse> =
{
Expand All @@ -75,7 +83,7 @@ export const buildAndInsertEmbeddingsFromOpenAIFactory =
);

const requestedDocumentMetadata =
document_metadata !== null
document_metadata !== null && document_metadata !== undefined
? document_metadata
: backfillDocumentMetadata(input_data);

Expand All @@ -96,3 +104,31 @@ export const buildAndInsertEmbeddingsFromOpenAIFactory =
return handleError(err);
}
};

const initialize = (
openaiKey: string,
starpointAPI: ReturnType<typeof db.initialize>
) => {
const { columnInsert } = starpointAPI;

// openai
const openAIClient = initOpenAI(openaiKey);
const buildAndInsertEmbeddingsFromOpenAI =
buildAndInsertEmbeddingsFromOpenAIFactory(openAIClient, columnInsert);
const buildAndInsertEmbeddings = async (
req: BuildAndInsertEmbeddingsRequest
) => {
const { ...rest } = req;
buildAndInsertEmbeddingsFromOpenAI({
model: "text-embedding-ada-002",
...rest,
});
};
return {
// openai
buildAndInsertEmbeddingsNoDefault: buildAndInsertEmbeddingsFromOpenAI,
buildAndInsertEmbeddings,
};
};

export const starpointOpenai = { initialize };
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { CreateEmbeddingRequestInput, CreateEmbeddingResponse } from "openai";
import { ByWrapper, Metadata, Option } from "../common-types";
import { InsertResponse } from "../writer/types";
import { ByWrapper, Metadata, Option } from "../../typescript/src/common-types";
import { InsertResponse } from "../../typescript/src/writer/types";

export interface InitOpenAIRequest {
openai_key?: Option<string>;
Expand All @@ -19,10 +19,10 @@ export type BuildAndInsertEmbeddingsFromOpenAIRequest = ByWrapper<{
model: string;
input_data: CreateEmbeddingRequestInput;
document_metadata?: Option<Metadata[]>;
openai_user?: Option<string>;
openai_user?: string;
}>;

export interface BuildAndInsertEmbeddingsFromOpenAIResponse {
openai_response: CreateEmbeddingResponse;
starpoint_response: InsertResponse;
starpoint_response?: Option<InsertResponse>;
}
File renamed without changes.
File renamed without changes.
168 changes: 168 additions & 0 deletions typescript-openai/tests/openai.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
import axios, { AxiosResponse } from "axios";
import { v4 as uuid4 } from "uuid";
import { db } from "../../typescript/src";
import { starpointOpenai } from "../src";
import { OpenAIApi } from "openai";
import { create } from "domain";
import { BuildAndInsertEmbeddingsFromOpenAIRequest } from "../src/types";

// Mock Axios
jest.mock("axios");
const mockedAxios = axios as jest.Mocked<typeof axios>;

// Mock OpenAI
jest.mock("openai");
const createEmbeddingSpy = jest.spyOn(OpenAIApi.prototype, "createEmbedding");

// Mock starpoint initialized objects
const starpointDbSpy = jest.spyOn(db, "initialize");
starpointDbSpy.mockReturnValue({
inferSchema: jest.fn(),
columnInsert: jest.fn(),
insertDocuments: jest.fn(),
deleteDocuments: jest.fn(),
updateDocuments: jest.fn(),
createCollection: jest.fn(),
deleteCollection: jest.fn(),
embed: jest.fn(),
queryDocuments: jest.fn(),
});
const starpointOpenAiClientSpy = jest.spyOn(starpointOpenai, "initialize");

beforeAll(() => {
mockedAxios.create.mockReturnThis();
});

afterEach(() => {
mockedAxios.delete.mockClear();
mockedAxios.put.mockClear();
mockedAxios.patch.mockClear();
mockedAxios.get.mockClear();
mockedAxios.post.mockClear();
starpointDbSpy.mockClear();
starpointOpenAiClientSpy.mockClear();
createEmbeddingSpy.mockReset();
});

describe("starpointOpenAi.initialize", () => {
it("should correctly set openAIKey and starpointAi and default urls", () => {
const MOCK_OPENAI_KEY = uuid4();
const MOCK_API_KEY = uuid4();
const dbClient = db.initialize(MOCK_API_KEY);
expect(starpointDbSpy).toHaveBeenCalled();
expect(starpointDbSpy).toHaveBeenCalledWith(MOCK_API_KEY);
starpointOpenai.initialize(MOCK_OPENAI_KEY, dbClient);
expect(starpointOpenAiClientSpy).toHaveBeenCalled();
expect(starpointOpenAiClientSpy).toHaveBeenCalledWith(
MOCK_OPENAI_KEY,
dbClient
);
});
});

describe("buildAndInsertEmbeddings", () => {
it("should return an openai response, but not a starpoint response", async () => {
const MOCK_COLLECTION_ID = uuid4();
const MOCK_API_KEY = uuid4();
const mockInitializeClient = starpointDbSpy.getMockImplementation();
const dbClient = mockInitializeClient(MOCK_API_KEY);
const MOCK_OPENAI_KEY = uuid4();
const mockRequest = {
collection_id: MOCK_COLLECTION_ID,
input_data: "this is test input",
};
const starpointOpenAiClient = starpointOpenai.initialize(
MOCK_OPENAI_KEY,
dbClient
);
const columnInsertSpy = jest.spyOn(dbClient, "columnInsert");

await starpointOpenAiClient.buildAndInsertEmbeddings(mockRequest);
// check that mock returned an openai response
expect(createEmbeddingSpy).toHaveBeenCalled();

// check that starpoint column insert is not called;
expect(columnInsertSpy).not.toHaveBeenCalled();
});
it("should return both an openai response and a starpoint response", async () => {
const MOCK_COLLECTION_ID = uuid4();
const MOCK_API_KEY = uuid4();
const mockInitializeClient = starpointDbSpy.getMockImplementation();
const dbClient = mockInitializeClient(MOCK_API_KEY);
const MOCK_OPENAI_KEY = uuid4();
const mockRequest = {
collection_id: MOCK_COLLECTION_ID,
input_data: "this is test input",
};
const starpointOpenAiClient = starpointOpenai.initialize(
MOCK_OPENAI_KEY,
dbClient
);

const mockCreateEmbeddingResponse = {
data: {
object: "mock",
model: "text-embedding-ada-002",
usage: { prompt_tokens: 0, total_tokens: 0 },
data: [
{ index: 1, embedding: [0.1, 0.2, 0.3], object: "mock" },
{ index: 2, embedding: [0.4, 0.5, 0.6], object: "mock" },
],
},
status: 200,
statusText: "ok",
headers: null,
config: null,
};
const mockColumnInsertResponse = {
data: {
collection_id: MOCK_COLLECTION_ID,
documents: [],
},
error: null,
};
const columnInsertSpy = jest.spyOn(dbClient, "columnInsert");

createEmbeddingSpy.mockResolvedValue(mockCreateEmbeddingResponse);
columnInsertSpy.mockResolvedValue(mockColumnInsertResponse);
await starpointOpenAiClient.buildAndInsertEmbeddings(mockRequest);
// check that mock returned an openai response
expect(createEmbeddingSpy).toHaveBeenCalled();

// check that starpoint column insert is called;
expect(columnInsertSpy).toHaveBeenCalled();

columnInsertSpy.mockReset();
});
});

describe("buildAndInsertEmbeddingsNoDefault", () => {
it("should return only an openai response and not a starpoint response", async () => {
const MOCK_COLLECTION_ID = uuid4();
const MOCK_API_KEY = uuid4();
const mockInitializeClient = starpointDbSpy.getMockImplementation();
const dbClient = mockInitializeClient(MOCK_API_KEY);
const MOCK_OPENAI_KEY = uuid4();
const mockRequest: BuildAndInsertEmbeddingsFromOpenAIRequest = {
collection_id: MOCK_COLLECTION_ID,
model: "text-image-ada-002",
input_data: "this is test input",
document_metadata: [{ car: 2 }, { car: 1 }],
};
const starpointOpenAiClient = starpointOpenai.initialize(
MOCK_OPENAI_KEY,
dbClient
);
const columnInsertSpy = jest.spyOn(dbClient, "columnInsert");

await starpointOpenAiClient.buildAndInsertEmbeddingsNoDefault(mockRequest);
// check that mock returned an openai response
expect(createEmbeddingSpy).toHaveBeenCalled();

// check that starpoint column insert is not called;
expect(columnInsertSpy).not.toHaveBeenCalled();

columnInsertSpy.mockReset();
createEmbeddingSpy.mockReset();
});
});
7 changes: 7 additions & 0 deletions typescript-openai/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"compilerOptions": {
"target": "ES5",
"lib": ["ES2015"],
"esModuleInterop": true
}
}
4 changes: 2 additions & 2 deletions typescript/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "starpoint",
"author": "pointable <package-maintainers@pointable.ai>",
"version": "0.3.2",
"version": "0.3.3",
"description": "TypeScript API for starpoint.ai",
"source": "src/index.ts",
"main": "dist/main.js",
Expand Down Expand Up @@ -29,4 +29,4 @@
"openai": "^3.3.0",
"validator": "^13.9.0"
}
}
}
Loading

0 comments on commit 12db501

Please sign in to comment.