Skip to content

Commit

Permalink
Merge pull request #33 from opper-ai/634-image-output-support
Browse files Browse the repository at this point in the history
feat: allow for generate images via sdk
  • Loading branch information
christopher-weir authored Sep 16, 2024
2 parents 6bef778 + 9d0afe8 commit 1627d69
Show file tree
Hide file tree
Showing 10 changed files with 181 additions and 103 deletions.
20 changes: 18 additions & 2 deletions examples/example-calls-multimodal.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
// Run example with "npx ts-node ./examples/example-calls.ts"
// Run example with "npx ts-node ./examples/example-calls-multimodal.ts"
import "dotenv/config";
import fs from "node:fs";
import path from "node:path";
import os from "node:os";

import Client from "../src";
import { image } from "../src/utils";
Expand All @@ -11,7 +14,7 @@ const client = new Client();
const input = {
image: image("examples/cat.png"),
};

const trace = await client.traces.start({
name: "node-sdk/calls/multimodal",
input: input,
Expand All @@ -26,6 +29,19 @@ const client = new Client();
});
console.log("description: ", message);

const cat = await client.generateImage({
parent_span_uuid: trace.uuid,
prompt: "Create an image of a cat",
});

// Create a temporary file path
const tempFilePath = path.join(os.tmpdir(), "generated_cat_image.png");

// Write the image bytes to the temporary file
fs.writeFileSync(tempFilePath, cat.bytes);

console.log(`image written to temporary file: ${tempFilePath}`);

await trace.end({
output: message,
});
Expand Down
133 changes: 69 additions & 64 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,66 +1,71 @@
{
"name": "opperai",
"version": "1.3.0",
"description": "Typescript SDK for the Opper API",
"main": "dist/index.js",
"module": "dist/index.mjs",
"types": "dist/index.d.ts",
"scripts": {
"build": "tsup",
"check:lint": "eslint 'src/**/*.ts'",
"check:type": "tsc --noEmit --project './tsconfig.json'",
"check": "npm run check:type && npm run check:lint && npm run test",
"prepare": "npm run build",
"test": "jest",
"publish-patch": "npm run check && npm run build && npm version patch",
"publish-minor": "npm run check && npm run build && npm version minor",
"publish-major": "npm run check && npm run build && npm version major",
"examples": "for file in examples/example-*.ts; do echo \"Running $file\"; npx ts-node \"$file\"; echo \"\"; done"
},
"exports": {
".": {
"require": "./dist/index.js",
"import": "./dist/index.mjs",
"types": "./dist/index.d.ts"
"name": "opperai",
"version": "1.3.0",
"description": "Typescript SDK for the Opper API",
"main": "dist/index.js",
"module": "dist/index.mjs",
"types": "dist/index.d.ts",
"scripts": {
"build": "tsup",
"check:lint": "eslint 'src/**/*.ts'",
"check:type": "tsc --noEmit --project './tsconfig.json'",
"check": "npm run check:type && npm run check:lint && npm run test",
"prepare": "npm run build",
"test": "jest",
"publish-patch": "npm run check && npm run build && npm version patch",
"publish-minor": "npm run check && npm run build && npm version minor",
"publish-major": "npm run check && npm run build && npm version major",
"examples": "for file in examples/example-*.ts; do echo \"Running $file\"; npx ts-node \"$file\"; echo \"\"; done"
},
"exports": {
".": {
"require": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
},
"import": {
"types": "./dist/index.d.ts",
"default": "./dist/index.mjs"
}
}
},
"engines": {
"node": ">=18"
},
"keywords": [
"opper",
"api",
"sdk",
"typescript"
],
"files": [
"README.md",
"index.js",
"dist/*"
],
"author": "Opper Technology AB",
"license": "ISC",
"devDependencies": {
"@sinclair/typebox": "^0.32.35",
"@types/jest": "29.5.12",
"@types/uuid": "^9.0.8",
"@typescript-eslint/eslint-plugin": "7.0.2",
"@typescript-eslint/parser": "7.0.2",
"dotenv": "^16.4.5",
"eslint": "8.56.0",
"eslint-config-prettier": "9.1.0",
"eslint-plugin-import": "2.29.1",
"eslint-plugin-prettier": "5.1.3",
"jest": "29.7.0",
"jest-environment-jsdom": "^29.7.0",
"prettier": "3.2.5",
"ts-jest": "29.1.2",
"tsup": "8.0.2",
"typescript": "5.3.3",
"zod": "^3.23.8",
"zod-to-json-schema": "^3.23.2"
},
"optionalDependencies": {
"@rollup/rollup-linux-x64-gnu": "^4.19.2"
}
},
"engines": {
"node": ">=18"
},
"keywords": [
"opper",
"api",
"sdk",
"typescript"
],
"files": [
"README.md",
"index.js",
"dist/*"
],
"author": "Opper Technology AB",
"license": "ISC",
"devDependencies": {
"@sinclair/typebox": "^0.32.35",
"@types/jest": "29.5.12",
"@types/uuid": "^9.0.8",
"@typescript-eslint/eslint-plugin": "7.0.2",
"@typescript-eslint/parser": "7.0.2",
"dotenv": "^16.4.5",
"eslint": "8.56.0",
"eslint-config-prettier": "9.1.0",
"eslint-plugin-import": "2.29.1",
"eslint-plugin-prettier": "5.1.3",
"jest": "29.7.0",
"jest-environment-jsdom": "^29.7.0",
"prettier": "3.2.5",
"ts-jest": "29.1.2",
"tsup": "8.0.2",
"typescript": "5.3.3",
"zod": "^3.23.8",
"zod-to-json-schema": "^3.23.2"
},
"optionalDependencies": {
"@rollup/rollup-linux-x64-gnu": "^4.19.2"
}
}
}
4 changes: 2 additions & 2 deletions src/__tests__/client.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Index, Options } from "../types";
import { OpperIndex, Options } from "../types";

import Client from "../index";

Expand Down Expand Up @@ -94,7 +94,7 @@ describe("OpperAIClient", () => {
});

it("should be able to list indexes", async () => {
const mockIndexes: Index[] = [
const mockIndexes: OpperIndex[] = [
{
uuid: "1",
name: "Test Index 1",
Expand Down
4 changes: 2 additions & 2 deletions src/__tests__/opperai-indexes.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Index } from "../types";
import { OpperIndex } from "../types";

import Client from "../index";
import Indexes from "../indexes";
Expand All @@ -15,7 +15,7 @@ describe("OpperAIIndexes", () => {
let opperAIIndexes: Indexes;
const mockApiKey = "test-api-key";

const mockIndexes: Index[] = [
const mockIndexes: OpperIndex[] = [
{
uuid: "1",
name: "Test Index 1",
Expand Down
3 changes: 3 additions & 0 deletions src/api-resource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,9 @@ class APIResource {
protected calcURLCall = () => {
return `${this._client.baseURL}/v1/call`;
};
protected calcURLGenerateImage = () => {
return `${this._client.baseURL}/v1/generate-image`;
};
protected calcURLGetFunctionByPath = (path: string) => {
return `${this._client.baseURL}/api/v1/functions/by_path/${path}`;
};
Expand Down
36 changes: 32 additions & 4 deletions src/functions.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
import { OpperFunction, OpperCall, OpperAIChatResponse, Chat, OpperAIStream } from "./types";
import {
OpperFunction,
OpperCall,
OpperChatResponse,
OpperGenerateImage,
OpperImageResponse,
Chat,
OpperAIStream,
} from "./types";

import APIResource from "./api-resource";
import { OpperError } from "./errors";
Expand All @@ -18,13 +26,13 @@ class Functions extends APIResource {
message,
parent_span_uuid,
examples,
}: Chat): Promise<OpperAIChatResponse> {
}: Chat): Promise<OpperChatResponse> {
const url = this.calcURLChat(path);
const body = this.calcChatPayload(message, parent_span_uuid, examples);

const response = await this.doPost(url, body);

return (await response.json()) as OpperAIChatResponse;
return (await response.json()) as OpperChatResponse;
}

/**
Expand Down Expand Up @@ -71,7 +79,7 @@ class Functions extends APIResource {
* @returns A promise that resolves to the created function.
* @throws {OpperError} If the function already exists and update is false.
*/
public async call(fn: OpperCall): Promise<OpperAIChatResponse> {
public async call(fn: OpperCall): Promise<OpperChatResponse> {
const response = await this.doPost(this.calcURLCall(), {
...fn,
input_type: fn?.input_schema,
Expand All @@ -87,6 +95,26 @@ class Functions extends APIResource {
throw new OpperError(`Failed to call function: ${response.statusText}`);
}

public async generateImage(args: OpperGenerateImage): Promise<OpperImageResponse> {
const response = await this.doPost(this.calcURLGenerateImage(), {
...args,
model: "azure/dall-e-3-eu",
format: "b64_json",
});

if (response.ok) {
const data = await response.json();
const base64Image = data.result.base64_image;
const imageBytes = Buffer.from(base64Image, "base64");

return {
bytes: imageBytes,
};
}

throw new OpperError(`Failed to generate image: ${response.statusText}`);
}

/**
* This method is a helper which can be used in node middleware
* to pipe the OpperAI chat stream directly to the client. See examples.
Expand Down
17 changes: 15 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { OpperAIChatResponse, OpperCall, Options } from "./types";
import {
OpperChatResponse,
OpperCall,
OpperGenerateImage,
OpperImageResponse,
Options,
} from "./types";

import { OpperError } from "./errors";

Expand Down Expand Up @@ -61,9 +67,16 @@ class Client {
);
};

call = async (fn: OpperCall): Promise<OpperAIChatResponse> => {
call = async (fn: OpperCall): Promise<OpperChatResponse> => {
return await this.functions.call(fn);
};

generateImage = async (args: OpperGenerateImage): Promise<OpperImageResponse> => {
return await this.functions.generateImage(args);
};
}

// Types which are exported for use outside of the SDK
export { OpperChatResponse, OpperImageResponse, OpperIndexDocument, OpperIndex } from "./types";

export default Client;
18 changes: 9 additions & 9 deletions src/indexes.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Document, Filter, Index } from "./types";
import { OpperIndexDocument, OpperIndexFilter, OpperIndex } from "./types";

import APIResource from "./api-resource";

Expand All @@ -14,7 +14,7 @@ class Indexes extends APIResource {

const response = await this.doGet(url);

const indexes: Index[] = await response.json();
const indexes: OpperIndex[] = await response.json();

return indexes;
}
Expand All @@ -25,7 +25,7 @@ class Indexes extends APIResource {
* @returns A promise that resolves to the index with the given name, or null if no index with the given name exists.
* @throws {APIError} If the response status is not 200.
*/
public async get(name: string): Promise<Index | null> {
public async get(name: string): Promise<OpperIndex | null> {
const list = await this.list();

const index = list.find((index) => index.name === name);
Expand All @@ -43,7 +43,7 @@ class Indexes extends APIResource {
* @returns A promise that resolves to the created index.
* @throws {APIError} If the response status is not 200.
*/
public async create(name: string, embedding_model?: string): Promise<Index> {
public async create(name: string, embedding_model?: string): Promise<OpperIndex> {
const response = await this.doPost(this.calcURLIndexes(), {
name,
...(embedding_model && { embedding_model }),
Expand Down Expand Up @@ -77,7 +77,7 @@ class Indexes extends APIResource {
* await client.indexes.add(index, document);
* ```
*/
public async add(index: Index, document: Document): Promise<void> {
public async add(index: OpperIndex, document: OpperIndexDocument): Promise<void> {
await this.doPost(this.calcURLAddIndex(index.uuid), document);
}

Expand All @@ -91,20 +91,20 @@ class Indexes extends APIResource {
* @throws {APIError} If the response status is not 200.
*/
public async retrieve(
index: Index,
index: OpperIndex,
query: string,
k: number,
filters: Filter[] | null,
filters: OpperIndexFilter[] | null,
parent_span_uuid?: string | null
): Promise<Document[]> {
): Promise<OpperIndexDocument[]> {
const response = await this.doPost(this.calcURLQueryIndex(index.uuid), {
q: query,
k: k,
filters: filters,
parent_span_uuid,
});

const documents: Document[] = await response.json();
const documents: OpperIndexDocument[] = await response.json();

return documents;
}
Expand Down
Loading

0 comments on commit 1627d69

Please sign in to comment.