Skip to content

Commit

Permalink
Merge pull request #13 from metaskills/RequiredProps
Browse files Browse the repository at this point in the history
Major Assistant & Tool Constructor Changes & Deployment Options v1.2
  • Loading branch information
metaskills committed Jun 5, 2024
2 parents 0b435f5 + cb0da5f commit 8421acb
Show file tree
Hide file tree
Showing 21 changed files with 207 additions and 147 deletions.
33 changes: 33 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,39 @@

See this http://keepachangelog.com link for information on how we want this documented formatted.

## v1.2.0

### Changed

**Major Assistant & Tool Constructor Changes**

Both Assistant & Tool have removed their (name, description, instructions) ordinal parameters in favor a single options object. Now, the constructor signature is:

```javascript
// Using Assistant.create() factory.
//
assistant = new Assistant.craete({
name: "My Assistant",
instructions: "My Assistant Instructions",
...
});

// Or using ES6 Classes.
//
class MyAssistant extends Assistant {
constructor() {
super({
name: "My Assistant",
instructions: "My Assistant Instructions",
});
}
})
```

## Added

A new `skipUpdate` option to use for deployments such as staging where it might be desirable to use the Assistants remote resource instructions or other properties.

## v1.1.0

### Changed
Expand Down
91 changes: 58 additions & 33 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ The new Assistants API from OpenAI sets a new industry standard, significantly a
Experts.js aims to simplify the usage of this new API by removing the complexity of managing [Run](https://platform.openai.com/docs/assistants/how-it-works/runs-and-run-steps) objects and allowing Assistants to be linked together as Tools.

```javascript
import { Assistant, Thread } from "experts";

const thread = await Thread.create();
const assistant = await MyAssistant.create();
const assistant = await Assistant.create();
const output = await assistant.ask("Say hello.", thread.id);
console.log(output) // Hello
```
Expand Down Expand Up @@ -46,11 +48,10 @@ The constructor of our [Assistant](https://platform.openai.com/docs/assistants/h
```javascript
class MyAssistant extends Assistant {
constructor() {
const name = "My Assistant";
const description = "...";
const instructions = "..."
super(name, description, instructions, {
model: "gpt-4-turbo",
super({
name: "My Assistant",
instructions: "...",
model: "gpt-4o",
tools: [{ type: "file_search" }],
temperature: 0.1,
tool_resources: {
Expand All @@ -65,10 +66,18 @@ class MyAssistant extends Assistant {
const assistant = await MyAssistant.create();
```

The Experts.js async `create()` function will:
The Experts.js async `Assistant.create()` base factory function is a simple way to create an assistant using the same constructor options.

* Find or create your assistant by name.
* Updates the assistants configurations to latest.
```javascript
const assistant = Assistant.create({
name: "My Assistant",
instructions: "...",
model: "gpt-4o",
});
```

> [!IMPORTANT]
> Creating assistants without an `id` parameter will always create a new assistant. See our [deployment](#deployment) section for more information.
### Simple Ask Interface

Expand All @@ -86,10 +95,10 @@ Normal OpenAI [tools and function calling](https://platform.openai.com/docs/assi
```javascript
class MainAssistant extends Assistant {
constructor() {
const name = "Company Assistant;
const description = "...";
const instructions = "...";
super(name, description, instructions);
super({
name: "Company Assistant",
instructions: "...",
});
this.addAssistantTool(ProductsTools);
}
}
Expand Down Expand Up @@ -138,10 +147,9 @@ Using an [Assistant](#assistants) as a Tool is central focal point of the Expert
```javascript
class EchoTool extends Tool {
constructor() {
const name = "Echo Tool";
const description = "Echo";
const instructions = "Echo the same text back to the user";
super(name, description, instructions, {
super({
name: "Echo Tool",
instructions: "Echo the same text back to the user",
parentsTools: [
{
type: "function",
Expand Down Expand Up @@ -171,10 +179,10 @@ Tools are added to your [Assistant](#assistants) via the `addAssistantTool` func
```javascript
class MainAssistant extends Assistant {
constructor() {
const name = "Company Assistant;
const description = "...";
const instructions = "...";
super(name, description, instructions);
super({
name: "Company Assistant",
instructions: "..."
});
this.addAssistantTool(EchoTool);
}
}
Expand All @@ -189,8 +197,8 @@ By default Tools are backed by an LLM `model` and perform all the same lifecycle
```javascript
class AnswerTwoTool extends Tool {
constructor() {
// ...
super(name, description, instructions, {
super({
// ...
llm: false,
parentsTools: [...],
});
Expand All @@ -217,8 +225,8 @@ Alternatively, LLM backed Tools could opt to redirect their own tool outputs bac
```javascript
class ProductsTool extends Tool {
constructor() {
// ...
super(name, description, instructions, {
super({
// ...
temperature: 0.1,
tools: [{ type: "code_interpreter" }],
outputs: "tools",
Expand Down Expand Up @@ -269,8 +277,8 @@ First, you can specify `run_options` in the Assistant's constructor. These optio
```javascript
class CarpenterAssistant extends Assistant {
constructor() {
// ...
super(name, description, instructions, {
super({
// ...
run_options: {
tool_choice: {
type: "function",
Expand Down Expand Up @@ -353,10 +361,9 @@ Using a [Vector Store](https://platform.openai.com/docs/assistants/tools/file-se

class VectorSearchAssistant extends Assistant {
constructor() {
const name = "Vector Search Assistant";
const description = "...";
const instructions = "..."
super(name, description, instructions, {
super({
name: "Vector Search Assistant",
instructions: "...",
tools: [{ type: "file_search" }],
temperature: 0.1,
tool_resources: {
Expand All @@ -376,8 +383,9 @@ Using the [Streaming & Events](#streaming--events) feature to report token usage
```javascript
class MyAssistant extends Assistant {
constructor() {
// ...
super(name, description, instructions);
super({
// ...
});
this.on("runStepDone", this.#reportUsage.bind(this));
}

Expand All @@ -391,6 +399,23 @@ class MyAssistant extends Assistant {
}
```
## Deployments
In order for an Assistant to be deployed to a production environment, we recommend the following configurations. First, create or find your assistant's id. The string will be in the format of `asst_abc123`. Then pass this id into the Assistant's or Tools's constructor. This will ensure that the same assistant is used across all deployments.
```javascript
class MyAssistant extends Assistant {
constructor() {
super({
// ...
id: process.env.MY_ASSISTANT_ID
});
}
}
```
Once an Assistant or Tool is found by id, any remote configurations that are different present are overwritten by the local configurations. If required, for example in a staging environment, you can bypass this behavior by setting the `skipUpdate` option to `true`.
## Environment Variables
### Global Model Configuration
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "experts",
"version": "1.1.0",
"version": "1.2.0",
"description": "An opinionated panel of experts implementation using OpenAI's Assistants API",
"type": "module",
"main": "./src/index.js",
Expand Down
49 changes: 25 additions & 24 deletions src/experts/assistant.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,16 @@ class Assistant {
#expertsOutputs = [];
#asyncListeners = {};

static async create() {
const asst = new this();
static async create(options = {}) {
const asst = new this(options);
await asst.init();
return asst;
}

constructor(agentName, description, instructions, options = {}) {
this.agentName = agentName;
this.description = description;
this.instructions = instructions;
constructor(options = {}) {
this.name = options.name;
this.description = options.description;
this.instructions = options.instructions;
this.llm = options.llm !== undefined ? options.llm : true;
if (this.llm) {
this.id = options.id;
Expand All @@ -44,6 +44,8 @@ class Assistant {
this._metadata = options.metadata;
this.response_format = options.response_format || "auto";
this.run_options = options.run_options || {};
this.skipUpdate =
options.skipUpdate !== undefined ? options.skipUpdate : false;
this.emitter = new EventEmitter2({ maxListeners: 0, newListener: true });
this.emitter.on("newListener", this.#newListener.bind(this));
}
Expand Down Expand Up @@ -72,6 +74,10 @@ class Assistant {
return this.assistant.metadata;
}

get nameOrID() {
return this.name || this.id;
}

// Interface

async ask(message, threadID, options = {}) {
Expand Down Expand Up @@ -155,7 +161,7 @@ class Assistant {
}
} else {
if (!thread.hasAssistantMetadata) {
thread.addMetaData({ assistant: this.agentName });
thread.addMetaData({ assistant: this.nameOrID });
}
}
return thread;
Expand Down Expand Up @@ -281,18 +287,23 @@ class Assistant {

async #findByID() {
if (!this.id) return;
const assistant = await openai.beta.assistants.retrieve(this.id);
let assistant;
try {
assistant = await openai.beta.assistants.retrieve(this.id);
} catch (error) {
if (error.status !== 404) throw error;
}
if (!assistant) return;
debug(`💁‍♂️ Found by id ${this.agentName} assistant ${this.id}`);
debug(`💁‍♂️ Found by id ${this.nameOrID}assistant ${this.id}`);
await this.#update(assistant);
return assistant;
}

async #update(assistant) {
if (!assistant) return;
if (!assistant || this.skipUpdate) return;
await openai.beta.assistants.update(assistant.id, {
model: this.model,
name: this.agentName,
name: this.nameOrID,
description: this.description,
instructions: this.instructions,
tools: this.tools,
Expand All @@ -302,13 +313,13 @@ class Assistant {
top_p: this.top_p,
response_format: this.response_format,
});
debug(`💁‍♂️ Updated ${this.agentName} assistant ${assistant.id}`);
debug(`💁‍♂️ Updated ${this.nameOrID} assistant ${assistant.id}`);
}

async #create() {
const assistant = await openai.beta.assistants.create({
model: this.model,
name: this.agentName,
name: this.nameOrID,
description: this.description,
instructions: this.instructions,
temperature: this.temperature,
Expand All @@ -318,19 +329,9 @@ class Assistant {
metadata: this._metadata,
response_format: this.response_format,
});
debug(`💁‍♂️ Created ${this.agentName} assistant ${assistant.id}`);
debug(`💁‍♂️ Created ${this.nameOrID} assistant ${assistant.id}`);
return assistant;
}

async #deleteByName() {
const assistant = (
await openai.beta.assistants.list({ limit: 100 })
).data.find((a) => a.name === this.agentName);
if (assistant !== undefined) {
debug(`🗑️ Deleting assistant: ${this.agentName}`);
await openai.beta.assistants.del(assistant.id);
}
}
}

export { Assistant };
2 changes: 1 addition & 1 deletion src/experts/thread.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ class Thread {
if (!threadID) {
thread = await Thread.create();
await this.addMetaData({ [threadKey]: thread.id });
await thread.addMetaData({ tool: tool.agentName });
await thread.addMetaData({ tool: tool.nameOrID });
} else {
thread = await Thread.find(threadID);
}
Expand Down
4 changes: 2 additions & 2 deletions src/experts/tool.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ class Tool extends Assistant {
return name;
}

constructor(agentName, description, instructions, options = {}) {
super(agentName, description, instructions, options);
constructor(options = {}) {
super(options);
this.isTool = true;
this.hasThread = options.hasThread !== undefined ? options.hasThread : true;
this.outputs = options.outputs || "default";
Expand Down
Loading

0 comments on commit 8421acb

Please sign in to comment.