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

chore: dynamic plugin imports #1383

Open
wants to merge 33 commits into
base: develop
Choose a base branch
from

Conversation

ChristopherTrimboli
Copy link
Contributor

Relates to:

None, just looking for improvements.

Risks

HIGH - could break plugins

Background

Plugins even if unused are being always imported into the agent/index.ts at top of file.
This adds much memory usage to the JS runtime and not most efficient way to load optional plugins.

What does this PR do?

Dynamically imports plugins when needed based on ENV / secrets loaded.

What kind of change is this?

Improvements (misc. changes to existing features)

Why are we doing this? Any context or related work?

Makes Eliza faster / slimmer / less bloated / scales infinite plugins with minimal runtime overhead.

Documentation changes needed?

My changes do not require a change to the project documentation.

Testing

Try out different combinations of plugins off and on, see if loads or breaks.

Discord username

cjft

@ChristopherTrimboli
Copy link
Contributor Author

P.S. - seems like passed all tests, omg, nice... just noticed could do same for all client packages: if we like this change, ill do another PR for dynamic client imports too, massive optz.

@ChristopherTrimboli
Copy link
Contributor Author

ChristopherTrimboli commented Dec 22, 2024

hmmm avaer told me that "alot of wrappings" I can make better abstraction for loading dynamic imports... I'll work on. Like a function for it instead. Bit complex because of all the diff conditionals / import styles / 1 is a default import. I'll see if can do nicely.

@ChristopherTrimboli
Copy link
Contributor Author

hmmm avaer told me that "alot of wrappings" I can make better abstraction for loading dynamic imports... I'll work on. Like a function for it instead. Bit complex because of all the diff conditionals / import styles / 1 is a default import. I'll see if can do nicely.

done, now have loadPlugin() abstraction with very simple string based plugin config.

@ryanleecode
Copy link
Contributor

ryanleecode commented Dec 23, 2024

Why do it like this? Why not pass a callback to loadPlugin that is async () => import('my plugin')

@ryanleecode
Copy link
Contributor

The probably is the public api is purely string base its not type safe where as if you do callback import and you don't have the dependency installed, typescript will not compile it.

@odilitime
Copy link
Collaborator

Probably need to update

* 5. Edit the 'index.ts' file in 'agent/src': *

for future plugin devs

@ChristopherTrimboli
Copy link
Contributor Author

ChristopherTrimboli commented Dec 23, 2024

Why do it like this? Why not pass a callback to loadPlugin that is async () => import('my plugin')

OK yeah callback param is better, I'll update.

@ChristopherTrimboli
Copy link
Contributor Author

changed to callback params: 2efee64

@ChristopherTrimboli
Copy link
Contributor Author

Probably need to update

* 5. Edit the 'index.ts' file in 'agent/src': *

for future plugin devs

c5d4dc4 updated plugin instructions

@ryanleecode
Copy link
Contributor

This doesn't work your loadPlugin returns null but the function is typed as Promise<any> where as the constructor parameter only takes Plugin[]. Giving it a null plugin will definately fail.

IMO the correct approach would be something as follows.

  1. Create a dynamic Plugin type such as
export type DynamicPlugin = {
    readonly factory: () => Promise<Plugin>;
    readonly shouldInit: (character: Character) => boolean;
};
  1. Make the AgenRuntime constructor private and create a static factory function that is async and accepts ReadonlyArray<Plugin | DynamicPlugin> as a parameter. Await the dynamic parameters in the async static factory. Bonus points for adding _tag: 'Plugin' and _tag: 'DynamicPlugin to the respective types, although this would be a high impact change since it would fuck over downstream plugins.

@ChristopherTrimboli
Copy link
Contributor Author

This doesn't work your loadPlugin returns null but the function is typed as Promise<any> where as the constructor parameter only takes Plugin[]. Giving it a null plugin will definately fail.

IMO the correct approach would be something as follows.

  1. Create a dynamic Plugin type such as
export type DynamicPlugin = {
    readonly factory: () => Promise<Plugin>;
    readonly shouldInit: (character: Character) => boolean;
};
  1. Make the AgenRuntime constructor private and create a static factory function that is async and accepts ReadonlyArray<Plugin | DynamicPlugin> as a parameter. Await the dynamic parameters in the async static factory. Bonus points for adding _tag: 'Plugin' and _tag: 'DynamicPlugin to the respective types, although this would be a high impact change since it would fuck over downstream plugins.

I see, more complex with types then I originally thought, will think about / work on, thanks for feedback.

@ryanleecode
Copy link
Contributor

This doesn't work your loadPlugin returns null but the function is typed as Promise<any> where as the constructor parameter only takes Plugin[]. Giving it a null plugin will definately fail.
IMO the correct approach would be something as follows.

  1. Create a dynamic Plugin type such as
export type DynamicPlugin = {
    readonly factory: () => Promise<Plugin>;
    readonly shouldInit: (character: Character) => boolean;
};
  1. Make the AgenRuntime constructor private and create a static factory function that is async and accepts ReadonlyArray<Plugin | DynamicPlugin> as a parameter. Await the dynamic parameters in the async static factory. Bonus points for adding _tag: 'Plugin' and _tag: 'DynamicPlugin to the respective types, although this would be a high impact change since it would fuck over downstream plugins.

I see, more complex with types then I originally thought, will think about / work on, thanks for feedback.

Actually I stand mistaken since there is a filter(Boolean) and the end the nulls get filtered out. So this would work but I don't think we need the third parameter b/c u can do import("whatever").then((mod) => mod.myPlugin) which is also type safe.

@ryanleecode
Copy link
Contributor

Few more comments.

  1. probably export the loadPlugin function
  2. create another function loadPlugins, and then i can just spread the resulting array

(found issues in Plugin types)
@ChristopherTrimboli
Copy link
Contributor Author

OK strictly type loadPlugins with Plugin + mod style, this works and is type compliant however we now have 3 plugins not correctly typed to Plugin that were hidden found:

@elizaos/plugin-solana
@elizaos/plugin-near
@elizaos/plugin-goat

image

This is bigger PR to fix all plugins now, was never type safe to begin with tbh.

@ryanleecode
Copy link
Contributor

Have you run pnpm build in the root of the monorepo?

@ChristopherTrimboli
Copy link
Contributor Author

Have you run pnpm build in the root of the monorepo?

ah, fixed, OK everything compiling / working great. Gonna work on:

1.) export the loadPlugin function
2.) create another function loadPlugins, and then i can just spread the resulting array

to cleanup and ready for review / testing. thanks for help! TS god

agent/src/index.ts Outdated Show resolved Hide resolved
@ChristopherTrimboli
Copy link
Contributor Author

I went big boy AAA on this one:

23cb080

I think this proper now. ready for review kind citizen.

agent/src/index.ts Outdated Show resolved Hide resolved
agent/src/index.ts Outdated Show resolved Hide resolved
@0xCardinalError
Copy link
Contributor

Nice PR. On the topic of plugins, I am thinking about what would be good solution in long term for plugins, as there will be hundreds of them. It makes no sense to import them all or even have them in code. I like the approach of just adding them in .json file for each character. So maybe we can have some core plugins and just contrib plugins where it can be whatever.

@odilitime
Copy link
Collaborator

If I fix the conflicts is this ready to merge?

@ChristopherTrimboli
Copy link
Contributor Author

If I fix the conflicts is this ready to merge?

I fixed conflicts and added more new plugins back. Should be good for testing in dev, I didn't get chance to try every plugin, this high risk. I think it's ready for merge myself.

@ChristopherTrimboli
Copy link
Contributor Author

ChristopherTrimboli commented Dec 30, 2024

Nice PR. On the topic of plugins, I am thinking about what would be good solution in long term for plugins, as there will be hundreds of them. It makes no sense to import them all or even have them in code. I like the approach of just adding them in .json file for each character. So maybe we can have some core plugins and just contrib plugins where it can be whatever.

true some theory crafting there, but issue is types need to be correct in build, I originally made some string solution but does not validate Plugin type in TS properly, ryanleecode roasted me, I think .json for plugins may not workout.

@0xCardinalError
Copy link
Contributor

Nice PR. On the topic of plugins, I am thinking about what would be good solution in long term for plugins, as there will be hundreds of them. It makes no sense to import them all or even have them in code. I like the approach of just adding them in .json file for each character. So maybe we can have some core plugins and just contrib plugins where it can be whatever.

true some theory crafting there, but issue is types need to be correct in build, I originally made some string solution but does not validate Plugin type in TS properly, ryanleecode roasted me, I think .json for plugins may not workout.

I found it perfectly suitable that char is build with .json and then we just add something like

    "plugins": [
        "@elizaos/plugin-dexscreener", ""@elizaos/plugin-geckio","
    ],
    ```
    
    to it and list plugins we want to load besides "core" ones. 

@ChristopherTrimboli
Copy link
Contributor Author

Nice PR. On the topic of plugins, I am thinking about what would be good solution in long term for plugins, as there will be hundreds of them. It makes no sense to import them all or even have them in code. I like the approach of just adding them in .json file for each character. So maybe we can have some core plugins and just contrib plugins where it can be whatever.

true some theory crafting there, but issue is types need to be correct in build, I originally made some string solution but does not validate Plugin type in TS properly, ryanleecode roasted me, I think .json for plugins may not workout.

I found it perfectly suitable that char is build with .json and then we just add something like

    "plugins": [
        "@elizaos/plugin-dexscreener", ""@elizaos/plugin-geckio","
    ],
    ```
    
    to it and list plugins we want to load besides "core" ones. 

got clarification, forgot Eliza is multi-agent support, understood, will handle the plugin in .json and active for all loaded characters in dynamic import style, not too bad update, coming soon.

@ChristopherTrimboli
Copy link
Contributor Author

ready for review, let's stick to just the top of file imports being removed, not overcomplicate this, 1 for 1 these changes are an improvment, nothing different logically, plugins in character configs still do their thing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants