Skip to content

Commit 5696099

Browse files
authored
Merge pull request #1467 from jonathangus/dynamic-template
feat: Make templates in composeContext dynamic
2 parents aa4b8d6 + 4b02b7f commit 5696099

File tree

6 files changed

+141
-37
lines changed

6 files changed

+141
-37
lines changed

docs/api/functions/composeContext.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ The parameters for composing the context.
2222

2323
The state object containing values to replace the placeholders in the template.
2424

25-
**params.template**: `string`
25+
**params.template**: `string` | `Function`
2626

27-
The template string containing placeholders to be replaced with state values.
27+
The template string or function returning a string containing placeholders to be replaced with state values.
2828

2929
**params.templatingEngine?**: `"handlebars"`
3030

docs/docs/api/functions/composeContext.md

+4-4
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@ Composes a context string by replacing placeholders in a template with values fr
1010

1111
An object containing the following properties:
1212

13-
- **state**: `State`
13+
- **state**: `State`
1414
The state object containing key-value pairs for replacing placeholders in the template.
1515

16-
- **template**: `string`
17-
A string containing placeholders in the format `{{placeholder}}`.
16+
- **template**: `string | Function`
17+
A string or function returning a string containing placeholders in the format `{{placeholder}}`.
1818

1919
- **templatingEngine**: `"handlebars" | undefined` _(optional)_
2020
The templating engine to use. If set to `"handlebars"`, the Handlebars engine is used for template compilation. Defaults to `undefined` (simple string replacement).
@@ -51,7 +51,7 @@ const contextHandlebars = composeContext({
5151
```javascript
5252
const advancedTemplate = `
5353
{{#if userAge}}
54-
Hello, {{userName}}!
54+
Hello, {{userName}}!
5555
{{#if (gt userAge 18)}}You are an adult.{{else}}You are a minor.{{/if}}
5656
{{else}}
5757
Hello! We don't know your age.

packages/client-twitter/src/post.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ import {
66
IAgentRuntime,
77
ModelClass,
88
stringToUuid,
9-
UUID,
9+
TemplateType,
10+
UUID
1011
} from "@elizaos/core";
1112
import { elizaLogger } from "@elizaos/core";
1213
import { ClientBase } from "./base.ts";
@@ -533,7 +534,7 @@ export class TwitterPostClient {
533534
private async generateTweetContent(
534535
tweetState: any,
535536
options?: {
536-
template?: string;
537+
template?: TemplateType;
537538
context?: string;
538539
}
539540
): Promise<string> {

packages/core/src/context.ts

+16-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import handlebars from "handlebars";
2-
import { type State } from "./types.ts";
2+
import { type State, type TemplateType } from "./types.ts";
33
import { names, uniqueNamesGenerator } from "unique-names-generator";
44

55
/**
@@ -13,7 +13,7 @@ import { names, uniqueNamesGenerator } from "unique-names-generator";
1313
*
1414
* @param {Object} params - The parameters for composing the context.
1515
* @param {State} params.state - The state object containing values to replace the placeholders in the template.
16-
* @param {string} params.template - The template string containing placeholders to be replaced with state values.
16+
* @param {TemplateType} params.template - The template string or function containing placeholders to be replaced with state values.
1717
* @param {"handlebars" | undefined} [params.templatingEngine] - The templating engine to use for compiling and evaluating the template (optional, default: `undefined`).
1818
* @returns {string} The composed context string with placeholders replaced by corresponding state values.
1919
*
@@ -25,23 +25,34 @@ import { names, uniqueNamesGenerator } from "unique-names-generator";
2525
* // Composing the context with simple string replacement will result in:
2626
* // "Hello, Alice! You are 30 years old."
2727
* const contextSimple = composeContext({ state, template });
28+
*
29+
* // Using composeContext with a template function for dynamic template
30+
* const template = ({ state }) => {
31+
* const tone = Math.random() > 0.5 ? "kind" : "rude";
32+
* return `Hello, {{userName}}! You are {{userAge}} years old. Be ${tone}`;
33+
* };
34+
* const contextSimple = composeContext({ state, template });
2835
*/
36+
2937
export const composeContext = ({
3038
state,
3139
template,
3240
templatingEngine,
3341
}: {
3442
state: State;
35-
template: string;
43+
template: TemplateType;
3644
templatingEngine?: "handlebars";
3745
}) => {
46+
const templateStr =
47+
typeof template === "function" ? template({ state }) : template;
48+
3849
if (templatingEngine === "handlebars") {
39-
const templateFunction = handlebars.compile(template);
50+
const templateFunction = handlebars.compile(templateStr);
4051
return templateFunction(state);
4152
}
4253

4354
// @ts-expect-error match isn't working as expected
44-
const out = template.replace(/{{\w+}}/g, (match) => {
55+
const out = templateStr.replace(/{{\w+}}/g, (match) => {
4556
const key = match.replace(/{{|}}/g, "");
4657
return state[key] ?? "";
4758
});

packages/core/src/tests/context.test.ts

+90
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,96 @@ describe("composeContext", () => {
7070
});
7171
});
7272

73+
describe("dynamic templates", () => {
74+
it("should handle function templates", () => {
75+
const state: State = {
76+
...baseState,
77+
userName: "Alice",
78+
userAge: 30,
79+
};
80+
const template = () => {
81+
return "Hello, {{userName}}! You are {{userAge}} years old.";
82+
};
83+
84+
const result = composeContext({ state, template });
85+
86+
expect(result).toBe("Hello, Alice! You are 30 years old.");
87+
});
88+
89+
it("should handle function templates with conditional logic", () => {
90+
const state: State = {
91+
...baseState,
92+
userName: "Alice",
93+
userAge: 30,
94+
};
95+
const isEdgy = true;
96+
const template = () => {
97+
if (isEdgy) {
98+
return "Hello, {{userName}}! You are {{userAge}} years old... whatever";
99+
}
100+
101+
return `Hello, {{userName}}! You are {{userAge}} years old`;
102+
};
103+
104+
const result = composeContext({ state, template });
105+
106+
expect(result).toBe(
107+
"Hello, Alice! You are 30 years old... whatever"
108+
);
109+
});
110+
111+
it("should handle function templates with conditional logic depending on state", () => {
112+
const template = ({ state }: { state: State }) => {
113+
if (state.userName) {
114+
return `Hello, {{userName}}! You are {{userAge}} years old.`;
115+
}
116+
117+
return `Hello, anon! You are {{userAge}} years old.`;
118+
};
119+
120+
const result = composeContext({
121+
state: {
122+
...baseState,
123+
userName: "Alice",
124+
userAge: 30,
125+
},
126+
template,
127+
});
128+
129+
const resultWithoutUsername = composeContext({
130+
state: {
131+
...baseState,
132+
userAge: 30,
133+
},
134+
template,
135+
});
136+
137+
expect(result).toBe("Hello, Alice! You are 30 years old.");
138+
expect(resultWithoutUsername).toBe(
139+
"Hello, anon! You are 30 years old."
140+
);
141+
});
142+
143+
it("should handle function templates with handlebars templating engine", () => {
144+
const state: State = {
145+
...baseState,
146+
userName: "Alice",
147+
userAge: 30,
148+
};
149+
const template = () => {
150+
return `{{#if userAge}}Hello, {{userName}}!{{else}}Hi there!{{/if}}`;
151+
};
152+
153+
const result = composeContext({
154+
state,
155+
template,
156+
templatingEngine: "handlebars",
157+
});
158+
159+
expect(result).toBe("Hello, Alice!");
160+
});
161+
});
162+
73163
// Test Handlebars templating
74164
describe("handlebars templating", () => {
75165
it("should process basic handlebars template", () => {

packages/core/src/types.ts

+26-24
Original file line numberDiff line numberDiff line change
@@ -678,6 +678,8 @@ export interface ModelConfiguration {
678678
experimental_telemetry?: TelemetrySettings;
679679
}
680680

681+
export type TemplateType = string | ((options: { state: State }) => string);
682+
681683
/**
682684
* Configuration for an agent character
683685
*/
@@ -708,30 +710,30 @@ export type Character = {
708710

709711
/** Optional prompt templates */
710712
templates?: {
711-
goalsTemplate?: string;
712-
factsTemplate?: string;
713-
messageHandlerTemplate?: string;
714-
shouldRespondTemplate?: string;
715-
continueMessageHandlerTemplate?: string;
716-
evaluationTemplate?: string;
717-
twitterSearchTemplate?: string;
718-
twitterActionTemplate?: string;
719-
twitterPostTemplate?: string;
720-
twitterMessageHandlerTemplate?: string;
721-
twitterShouldRespondTemplate?: string;
722-
farcasterPostTemplate?: string;
723-
lensPostTemplate?: string;
724-
farcasterMessageHandlerTemplate?: string;
725-
lensMessageHandlerTemplate?: string;
726-
farcasterShouldRespondTemplate?: string;
727-
lensShouldRespondTemplate?: string;
728-
telegramMessageHandlerTemplate?: string;
729-
telegramShouldRespondTemplate?: string;
730-
discordVoiceHandlerTemplate?: string;
731-
discordShouldRespondTemplate?: string;
732-
discordMessageHandlerTemplate?: string;
733-
slackMessageHandlerTemplate?: string;
734-
slackShouldRespondTemplate?: string;
713+
goalsTemplate?: TemplateType;
714+
factsTemplate?: TemplateType;
715+
messageHandlerTemplate?: TemplateType;
716+
shouldRespondTemplate?: TemplateType;
717+
continueMessageHandlerTemplate?: TemplateType;
718+
evaluationTemplate?: TemplateType;
719+
twitterSearchTemplate?: TemplateType;
720+
twitterActionTemplate?: TemplateType;
721+
twitterPostTemplate?: TemplateType;
722+
twitterMessageHandlerTemplate?: TemplateType;
723+
twitterShouldRespondTemplate?: TemplateType;
724+
farcasterPostTemplate?: TemplateType;
725+
lensPostTemplate?: TemplateType;
726+
farcasterMessageHandlerTemplate?: TemplateType;
727+
lensMessageHandlerTemplate?: TemplateType;
728+
farcasterShouldRespondTemplate?: TemplateType;
729+
lensShouldRespondTemplate?: TemplateType;
730+
telegramMessageHandlerTemplate?: TemplateType;
731+
telegramShouldRespondTemplate?: TemplateType;
732+
discordVoiceHandlerTemplate?: TemplateType;
733+
discordShouldRespondTemplate?: TemplateType;
734+
discordMessageHandlerTemplate?: TemplateType;
735+
slackMessageHandlerTemplate?: TemplateType;
736+
slackShouldRespondTemplate?: TemplateType;
735737
};
736738

737739
/** Character biography */

0 commit comments

Comments
 (0)