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: Make templates in composeContext dynamic #1467

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

Conversation

jonathangus
Copy link
Contributor

Relates to:

Risks

Medium, might be a package that I missed to update where this could introduce a typescript error.

Background

What does this PR do?

Make it possible to use dynamic templates to make the agent more fun and unique

Idea taken from:
https://x.com/dabit3/status/1872126857774031201

What kind of change is this?

Features

Documentation changes needed?

Updated the composeContext docs indocs/docs/api/functions/composeContext.md and docs/api/functions/composeContext.md

Testing

Added test logic to packages/core/src/tests/context.test.ts

Detailed testing steps

templates: {
    lensPostTemplate: generateTemplate()
}

Discord username

@0xheavydev

@HashWarlock
Copy link
Collaborator

I disagree with this change. The easiest way to go about this is to create a script that can update the character file with templates field and define the templates they want to define. I did this just now with a simple script.

Script:
image
image

Updated File:
image

Test the agent:
image
image

Try to get info based on template instructions added:
image

This feature is not needed since this is already available through the JSON templates field

@HashWarlock
Copy link
Collaborator

@jonathangus I added a PR as an example of how to do this without changing any eliza code. #1554

@jonathangus
Copy link
Contributor Author

@HashWarlock if I'm not mistaken this will still make the template static in runtime?

The idea here is to be able to change template logic in runtime.

Since it is a function we can now have any conditional logic. Was thinking to even make it async to give the developer more control, but that change was major so might revisit later.

@HashWarlock
Copy link
Collaborator

@jonathangus, maybe I'm misunderstanding then.

How would this be different than having the template describe the conditional logic with the state variable checks as examples that would cause the agent to return a different response? This is how choosing the action replies on Twitter works when the agent would like, re-tweet, Quote, or reply.

If I want a template to do conditional logic that executes 100% correctly, like a calculation, it would be beneficial, but maybe it's better to add tools for that case.

From my point of view, this starts to approach a customized agent direction that requires new builds every time you add new logic. But if it doesn't break all templates w/ the new type accepting strings, then this can be useful.

Some questions:

  • Does this require also editing all the other templates among the different clients as well?
  • Can we expand on the doc to make sure devs understand how to implement this?

@jonathangus
Copy link
Contributor Author

Thanks for the feedback @HashWarlock

It can probably be achieved by describing the logic in the template, but having it as a function resolver allows us to use state and external data not accessible in the agent.

Just to clarify, this will still support all current clients and agents that use templates as strings. It simply introduces the possibility to pass a function that will resolve the template string. For me, this improves the developer experience by allowing developers to more easily customize their agents, and I’m personally a fan of using function resolvers when building SDKs.

I updated the related documentation I could find, but I may have missed something.

@HashWarlock
Copy link
Collaborator

@jonathangus Ahhhh, I think I understand clearly now. Let's me know if my thoughts are aligned here.

Example: I want to create a template function that would require the user to have an active subscription to get a response. Some psuedo-code test could look like:

    runtime = {
            databaseAdapter: {
                getAccountById: vi.fn(userId).mockResolvedValue({
                     id: userId,
                     name: "Suh Dude",
                     username: "suhdude",
                     details: {
                       subscriptionNftId: "1",
                       subscriptionNftApiUrl: "https://api/getStatus?id=1", // returns true
                     },
                 });,
            },
            state: {
                 userName: "hehehe",
                 userId: "wa-suh-dude-ah-ha-aa",
            }
    } as unknown as IAgentRuntime;
    
    
    it("should reply with REPLY if subscription is active", () => {
            const userId = runtime.state.userId;
            const template = () => {
                const userAccount = runtime.databaseAdapter.getAccountById(userId);
                const subscriptionStatus: boolean = await fetch(userAccount.details.subscriptionNftApiUrl);
                return (subscriptionStatus=== true) ? "REPLY" : "IGNORE";
            };

            const result = composeContext({ runtime.state, template });

            expect(result).toBe("REPLY");
    });
}

If I'm right in this regard, you have changed my perspective, sir 😆 I actually need this for a project I'm working on currently. Thank you for the meaningful discussion! 🙏

@jonathangus
Copy link
Contributor Author

Exactly! It’s a great example that shows what we can do with more configuration options for the template.

Though this would require the template and composeContext function to be asynchronous, which is a larger change than I initially intended and will affect more packages if implemented.

Could it be a good start to support synchronous callbacks?

@HashWarlock
Copy link
Collaborator

Exactly! It’s a great example that shows what we can do with more configuration options for the template.

Though this would require the template and composeContext function to be asynchronous, which is a larger change than I initially intended and will affect more packages if implemented.

Could it be a good start to support synchronous callbacks?

No worries on the async part, I'm pretty sure a provider could get that info then update the runtime state instead. Then there is no need to change too much. Let's get any conflicts fixed and I'll approve PR 👾

@jonathangus
Copy link
Contributor Author

Ready! @HashWarlock

@HashWarlock HashWarlock self-requested a review December 31, 2024 18:10
Copy link
Collaborator

@HashWarlock HashWarlock left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, I see build errors but they seem to be from other merges.

@jonathangus
Copy link
Contributor Author

Looks to be resolved @HashWarlock

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

Successfully merging this pull request may close these issues.

4 participants