Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: introduce Dependency Injection to enhance developer experience #2115

Merged
merged 68 commits into from
Jan 18, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
ba09600
feat: Add @elizaos/plugin-di package with initial implementation
btspoony Jan 10, 2025
ea51632
feat: add tests for content decorators in plugin-di
btspoony Jan 10, 2025
4b4e9e7
feat: implement base injectable action class in plugin-di
btspoony Jan 10, 2025
4212b24
fix: correct export typo and refactor evaluators in plugin-di
btspoony Jan 10, 2025
3717882
feat: enhance plugin-di with new evaluators and action examples
btspoony Jan 10, 2025
fb75e8e
feat: update CreateResource action to handle nullable content and ref…
btspoony Jan 10, 2025
66e5408
feat: enhance plugin-di with new sample action, evaluator, provider, …
btspoony Jan 10, 2025
218d644
refactor: update dependency injection for actions and providers
btspoony Jan 10, 2025
d3fe6df
fix: improve null safety in normalizeCharacter function
btspoony Jan 10, 2025
057196a
feat: apply di in agent index.ts
btspoony Jan 10, 2025
4ee4812
feat: enhance error handling and null safety in plugin-di
btspoony Jan 10, 2025
a7d67ba
docs: update README.md for Dependency Injection Plugin
btspoony Jan 10, 2025
18451af
fix: correct typo in comment for character normalization in index.ts
btspoony Jan 10, 2025
0a5fed5
feat: add external dependencies for plugin-di
btspoony Jan 10, 2025
d8d9723
Merge branch 'develop' into tbh-add-di
btspoony Jan 10, 2025
41b9d41
fix: pnpm-lock.yaml
btspoony Jan 10, 2025
96dd1ae
refactor: remove ExtendedPlugin type and update createPlugin return type
btspoony Jan 10, 2025
7f8e89d
Merge branch 'develop' into tbh-add-di
btspoony Jan 10, 2025
2d7b592
Merge branch 'develop' into tbh-add-di
btspoony Jan 10, 2025
28f263f
Merge branch 'develop' into tbh-add-di
btspoony Jan 10, 2025
28ae2d7
Merge branch 'develop' into tbh-add-di
wtfsayo Jan 10, 2025
10a530c
Update packages/plugin-di/src/actions/baseInjectableAction.ts
wtfsayo Jan 10, 2025
534f0a6
Update packages/plugin-di/src/actions/baseInjectableAction.ts
wtfsayo Jan 10, 2025
85ed71c
Update packages/plugin-di/src/actions/baseInjectableAction.ts
wtfsayo Jan 10, 2025
0b58ee1
Merge branch 'develop' into tbh-add-di
wtfsayo Jan 10, 2025
512d3de
Update package.json
wtfsayo Jan 10, 2025
8864bc5
Merge branch 'develop' into tbh-add-di
btspoony Jan 11, 2025
a29310b
Refactor: fix typo, rename Injactable to Injectable in plugin-di
btspoony Jan 11, 2025
d2448dc
chore: update dependencies and fix typos in plugin-di
btspoony Jan 11, 2025
ef77a73
fix: improve plugin normalization and export syntax in plugin-di
btspoony Jan 11, 2025
33edd1e
Apply suggestions from code review
btspoony Jan 11, 2025
cfebb7e
refactor: enhance error handling and code clarity in plugin-di
btspoony Jan 11, 2025
3942a46
Merge branch 'develop' into tbh-add-di
btspoony Jan 11, 2025
2ac448f
Apply suggestions from code review
btspoony Jan 11, 2025
9a9d9e3
feat: resolve coderabbitai
btspoony Jan 11, 2025
685bc20
Update packages/plugin-di/src/_examples/sampleAction.ts
btspoony Jan 12, 2025
c22aa1e
Merge branch 'develop' into tbh-add-di
btspoony Jan 12, 2025
20578fb
Merge branch 'develop' into tbh-add-di
btspoony Jan 13, 2025
b76919c
chore: update pnpm-lock.yaml
btspoony Jan 13, 2025
181e532
Merge branch 'develop' into tbh-add-di
btspoony Jan 13, 2025
ba253c1
fix: pnpm lock
btspoony Jan 13, 2025
67fbd53
Merge branch 'develop' into tbh-add-di
btspoony Jan 13, 2025
249a107
Merge branch 'develop' into tbh-add-di
btspoony Jan 14, 2025
4e06fb0
Merge branch 'develop' into tbh-add-di
btspoony Jan 14, 2025
8c2cdad
fix: update pnpm-lock and revert wrong modifications in default sampl…
btspoony Jan 14, 2025
49f7a09
chore: change version to 0.1.8+build.1 like others
btspoony Jan 14, 2025
6073827
Merge branch 'develop' into tbh-add-di
wtfsayo Jan 14, 2025
465221d
Merge branch 'develop' into tbh-add-di
btspoony Jan 14, 2025
36fcb62
Merge branch 'develop' into tbh-add-di
btspoony Jan 14, 2025
3fb2cd8
Merge branch 'develop' into tbh-add-di
btspoony Jan 15, 2025
569d940
Merge branch 'develop' into tbh-add-di
btspoony Jan 15, 2025
638c894
fix: pnpm-lock
btspoony Jan 15, 2025
20ce0e6
Merge branch 'develop' into tbh-add-di
btspoony Jan 16, 2025
3d08eab
Merge branch 'develop' into tbh-add-di
btspoony Jan 16, 2025
965c6d0
fix: pnpm-lock.yaml
btspoony Jan 16, 2025
2229e55
Merge branch 'develop' into tbh-add-di
btspoony Jan 17, 2025
3b7e8d0
fix: pnpm-lock.yaml
btspoony Jan 17, 2025
3d612cf
Merge branch 'develop' into tbh-add-di
btspoony Jan 17, 2025
4220924
chore: Update version and README for @elizaos/plugin-di
btspoony Jan 17, 2025
f70d523
Merge branch 'develop' into tbh-add-di
btspoony Jan 17, 2025
136f852
Merge branch 'develop' into tbh-add-di
btspoony Jan 17, 2025
6b774a4
refactor: update access modifiers in BaseInjectableAction and clean u…
btspoony Jan 17, 2025
b6b3e4b
refactor: change return type of processMessages callback in BaseInjec…
btspoony Jan 17, 2025
507208d
Merge branch 'develop' into tbh-add-di
btspoony Jan 17, 2025
3ae0b1d
Merge branch 'develop' into tbh-add-di
btspoony Jan 18, 2025
3015e58
chore: update pkg
btspoony Jan 18, 2025
f859be7
Merge branch 'develop' into tbh-add-di
btspoony Jan 18, 2025
16a29fc
Merge branch 'develop' into tbh-add-di
wtfsayo Jan 18, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions agent/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"@elizaos/plugin-binance": "workspace:*",
"@elizaos/plugin-avail": "workspace:*",
"@elizaos/plugin-bootstrap": "workspace:*",
"@elizaos/plugin-di": "workspace:*",
"@elizaos/plugin-cosmos": "workspace:*",
"@elizaos/plugin-intiface": "workspace:*",
"@elizaos/plugin-coinbase": "workspace:*",
Expand Down
4 changes: 4 additions & 0 deletions agent/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import {
import { zgPlugin } from "@elizaos/plugin-0g";

import { bootstrapPlugin } from "@elizaos/plugin-bootstrap";
import { normalizeCharacter } from "@elizaos/plugin-di";
import createGoatPlugin from "@elizaos/plugin-goat";
// import { intifacePlugin } from "@elizaos/plugin-intiface";
import { ThreeDGenerationPlugin } from "@elizaos/plugin-3d-generation";
Expand Down Expand Up @@ -1204,6 +1205,9 @@ const startAgents = async () => {
characters = await loadCharacters(charactersArg);
}

// Normalize characters for injectable plugins
characters = await Promise.all(characters.map(normalizeCharacter));

try {
for (const character of characters) {
await startAgent(character, directClient);
Expand Down
6 changes: 6 additions & 0 deletions packages/plugin-di/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
*

!dist/**
!package.json
!readme.md
!tsup.config.ts
139 changes: 139 additions & 0 deletions packages/plugin-di/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
# @elizaos/plugin-di - Dependency Injection Plugin for Eliza

This plugin provides a dependency injection system for Eliza plugins.

## What is Dependency Injection?

Dependency Injection is a design pattern that allows you to inject dependencies into a class or function. This pattern is useful for decoupling components and making your code more modular and testable.

## Examples of How to build a Plugin using Dependency Injection

Check the [example](./src/_examples/) folder for a simple example of how to create a plugin using Dependency Injection.

## Decorators for Dependency Injection

This plugin provides a set of decorators that you can use to inject dependencies into your classes or functions.

### From inversify

We use the [inversify](https://inversify.io/) library to provide the dependency injection system.
The following decorators are provided by the [inversify](https://inversify.io/) library.

#### `@injectable`

> Category: Class Decorator

This decorator marks a class as injectable. This means that you can inject this class into other classes using the `@inject` decorator.

```typescript
import { injectable } from "inversify";

@injectable()
class SampleClass {
}
```

Remember to register the class with the container before injecting it into other classes.

```typescript
import { globalContainer } from "@elizaos/plugin-di";

// Register the class with the container as a singleton, this means that the class will be instantiated only once.
globalContainer.bind(SingletonClass).toSelf().inSingletonScope();
// Register the class with the container as a request context, this means that the class will be instantiated for each request(in this case means each Character).
globalContainer.bind(CharactorContextClass).toSelf().inRequestScope();
```

#### `@inject`

> Category: Parameter Decorator

This decorator marks a parameter as an injection target. This means that the parameter will be injected with the appropriate dependency when the class is instantiated.

```typescript
import { injectable, inject } from "inversify";

@injectable()
class SampleClass {
constructor(
// Inject the SampleDependency as a public property of the class.
@inject("SampleDependency") public sampleDependency: SampleDependency
) {}
}
```

### From di plugin

DI plugin provides abstract classes that you can extend to create Injectable actions or evaluators.
And that provides the following decorators to improve the readability of the code.

#### `@property`

> Category: Property Decorator

This decorator is used to define a property in an action content class which will be used to generate the action content object Schema and content description template for LLM object generation.

```typescript
import { z } from 'zod';
import { property } from "@elizaos/plugin-di";

class SampleActionContent {
@property({
description: "Sample property description",
schema: z.string(),
})
sampleProperty: string;
}
```

## Abstract Classes for Injaectable Actions and Evaluators

This plugin provides abstract classes that you can extend to create Injectable actions or evaluators.

### `BaseInjectableAction`

This abstract class simplify the creation of injectable actions.
You don't need to think about the template for content generation, it will be generated automatically based on the properties of the content Class.
What you need to implement is the `execute` method.

```typescript
import { injectable } from "inversify";
import { BaseInjectableAction } from "@elizaos/plugin-di";

class SampleActionContent {
@property({
description: "Sample property description",
schema: z.string(),
})
property1: string;
}

@injectable()
class SampleAction extends BaseInjectableAction<SampleActionContent> {
constructor() {
super({
/** general action constent options */
contentClass: SampleActionContent,
});
}

/**
* It will be called by `handler` function when the action is triggered.
*/
async execute(
content: SampleActionContent | null,
runtime: IAgentRuntime,
message: Memory,
state: State,
callback?: HandlerCallback
): Promise<void> {
// Your action logic here
}
}
```

### `BaseInjectableEvaluator`

This abstract class simplify the creation of injectable evaluators.

Please refer to the [sampleEvaluator](./src/_examples/sampleEvaluator.ts) for an example of how to create an evaluator.
3 changes: 3 additions & 0 deletions packages/plugin-di/eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import eslintGlobalConfig from "../../eslint.config.mjs";

export default [...eslintGlobalConfig];
40 changes: 40 additions & 0 deletions packages/plugin-di/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"name": "@elizaos/plugin-di",
"version": "0.1.9-alpha.1",
"type": "module",
"main": "dist/index.js",
"module": "dist/index.js",
"types": "dist/index.d.ts",
"exports": {
"./package.json": "./package.json",
".": {
"import": {
"@elizaos/source": "./src/index.ts",
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
}
},
"files": [
"dist"
],
"dependencies": {
"@elizaos/core": "workspace:*",
"inversify": "^6.2.1",
"reflect-metadata": "^0.2.2",
"uuid": "11.0.3",
"zod": "3.23.8"
},
"devDependencies": {
"@types/node": "^20.0.0",
"@types/uuid": "10.0.0",
"tsup": "8.3.5",
"vitest": "2.1.4"
},
"scripts": {
"build": "tsup --format esm --dts",
"dev": "tsup --format esm --dts --watch",
"lint": "eslint --fix --cache .",
"test": "vitest run"
}
}
165 changes: 165 additions & 0 deletions packages/plugin-di/src/_examples/sampleAction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
import {
IAgentRuntime,
Memory,
HandlerCallback,
State,
elizaLogger,
} from "@elizaos/core";
import { z } from "zod";
import { inject, injectable } from "inversify";
import { BaseInjectableAction } from "../actions";
import { ActionOptions } from "../types";
import { property } from "../decorators";
import { globalContainer } from "../di";
import { SampleProvider } from "./sampleProvider";

/**
* The content class for the action
*/
export class CreateResourceContent {
@property({
description: "Name of the resource",
schema: z.string(),
})
name: string;

@property({
description: "Type of resource (document, image, video)",
schema: z.string(),
})
type: string;

@property({
description: "Description of the resource",
schema: z.string(),
})
description: string;

@property({
description: "Array of tags to categorize the resource",
schema: z.array(z.string()),
})
tags: string[];
}

/**
* Options for the CreateResource action
*/
const options: ActionOptions<CreateResourceContent> = {
name: "CREATE_RESOURCE",
similes: [],
description: "Create a new resource with the specified details",
examples: [
[
{
user: "{{user1}}",
content: {
text: "Create a new resource with the name 'Resource1' and type 'TypeA'",
},
},
{
user: "{{agentName}}",
content: {
text: `Resource created successfully:
- Name: Resource1
- Type: TypeA`,
},
},
],
[
{
user: "{{user1}}",
content: {
text: "Create a new resource with the name 'Resource2' and type 'TypeB'",
},
},
{
user: "{{agentName}}",
content: {
text: `Resource created successfully:
- Name: Resource2
- Type: TypeB`,
},
},
],
],
contentClass: CreateResourceContent,
};

/**
* CreateResourceAction
*/
@injectable()
export class CreateResourceAction extends BaseInjectableAction<CreateResourceContent> {
constructor(
@inject(SampleProvider)
private readonly sampleProvider: SampleProvider
) {
super(options);
}

async validate(
runtime: IAgentRuntime,
_message: Memory,
_state?: State
): Promise<boolean> {
return !!runtime.character.settings.secrets?.API_KEY;
}

async execute(
content: CreateResourceContent | null,
runtime: IAgentRuntime,
message: Memory,
state: State,
callback?: HandlerCallback
): Promise<any | null> {
if (!content) {
const error = "No content provided for the action.";
elizaLogger.warn(error);
await callback?.({ text: error }, []);
return;
}

// Call injected provider to do some work
try {
const result = await this.sampleProvider.get(
runtime,
message,
state
);
if (!result) {
elizaLogger.warn("Provider did not return a result.");
} else {
elizaLogger.info("Privder result:", result);
}
// Use result in callback
} catch (error) {
elizaLogger.error("Provider error:", error);
}

// persist relevant data if needed to memory/knowledge
// const memory = {
// type: "resource",
// content: resourceDetails.object,
// timestamp: new Date().toISOString()
// };

// await runtime.storeMemory(memory);

callback?.(
{
text: `Resource created successfully:
- Name: ${content.name}
- Type: ${content.type}
- Description: ${content.description}
- Tags: ${content.tags.join(", ")}

Resource has been stored in memory.`,
},
[]
);
}
}

// Register the action with the global container
globalContainer.bind(CreateResourceAction).toSelf().inRequestScope();
Loading
Loading