@aigne/core 1.31.0 → 1.32.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -12,6 +12,35 @@
12
12
  * dependencies
13
13
  * @aigne/observability bumped to 0.1.0
14
14
 
15
+ ## [1.32.1](https://github.com/AIGNE-io/aigne-framework/compare/core-v1.32.0...core-v1.32.1) (2025-07-09)
16
+
17
+
18
+ ### Dependencies
19
+
20
+ * The following workspace dependencies were updated
21
+ * dependencies
22
+ * @aigne/observability-api bumped to 0.7.0
23
+
24
+ ## [1.32.0](https://github.com/AIGNE-io/aigne-framework/compare/core-v1.31.0...core-v1.32.0) (2025-07-08)
25
+
26
+
27
+ ### Features
28
+
29
+ * **core:** add jinja syntax support for prompt builder ([#230](https://github.com/AIGNE-io/aigne-framework/issues/230)) ([74436a7](https://github.com/AIGNE-io/aigne-framework/commit/74436a7faac0c59a32b0153481386162649f4357))
30
+ * support setting component id to different component data ([#226](https://github.com/AIGNE-io/aigne-framework/issues/226)) ([c7b3224](https://github.com/AIGNE-io/aigne-framework/commit/c7b32240e6660f34974615bcb9b91978a1191e3e))
31
+
32
+
33
+ ### Bug Fixes
34
+
35
+ * **core:** ensure output is a record type ([#228](https://github.com/AIGNE-io/aigne-framework/issues/228)) ([dfd9104](https://github.com/AIGNE-io/aigne-framework/commit/dfd910451e5f1f9edd94a719857e36d34fadbe45))
36
+
37
+
38
+ ### Dependencies
39
+
40
+ * The following workspace dependencies were updated
41
+ * dependencies
42
+ * @aigne/observability-api bumped to 0.6.0
43
+
15
44
  ## [1.31.0](https://github.com/AIGNE-io/aigne-framework/compare/core-v1.30.0...core-v1.31.0) (2025-07-04)
16
45
 
17
46
 
@@ -394,6 +394,9 @@ class Agent {
394
394
  */
395
395
  async processAgentOutput(input, output, options) {
396
396
  const { context } = options;
397
+ if (!(0, type_utils_js_1.isRecord)(output)) {
398
+ throw new Error(`expect to return a record type such as {result: ...}, but got (${typeof output}): ${output}`);
399
+ }
397
400
  const parsedOutput = (0, type_utils_js_1.checkArguments)(`Agent ${this.name} output`, this.outputSchema, output);
398
401
  const finalOutput = this.includeInputInOutput ? { ...input, ...parsedOutput } : parsedOutput;
399
402
  await this.postprocess(input, finalOutput, options);
@@ -286,7 +286,7 @@ class AIAgent extends agent_js_1.Agent {
286
286
  }
287
287
  // Continue LLM function calling loop if any tools were executed
288
288
  if (executedToolCalls.length) {
289
- toolCallMessages.push(template_js_1.AgentMessageTemplate.from(undefined, executedToolCalls.map(({ call }) => call)).format(), ...executedToolCalls.map(({ call, output }) => template_js_1.ToolMessageTemplate.from(output, call.id).format()));
289
+ toolCallMessages.push(await template_js_1.AgentMessageTemplate.from(undefined, executedToolCalls.map(({ call }) => call)).format(), ...(await Promise.all(executedToolCalls.map(({ call, output }) => template_js_1.ToolMessageTemplate.from(output, call.id).format()))));
290
290
  continue;
291
291
  }
292
292
  }
@@ -237,22 +237,12 @@ class AIGNEContext {
237
237
  span.setAttribute("output", JSON.stringify({}));
238
238
  }
239
239
  span.setStatus({ code: api_1.SpanStatusCode.OK });
240
- await this.observer?.traceExporter
241
- ?.upsertInitialSpan?.(span)
242
- .catch((err) => {
243
- logger_js_1.logger.error("upsertInitialSpan error", err?.message || err);
244
- });
245
240
  span.end();
246
241
  break;
247
242
  }
248
243
  case "agentFailed": {
249
244
  const { error } = args[0];
250
245
  span.setStatus({ code: api_1.SpanStatusCode.ERROR, message: error.message });
251
- await this.observer?.traceExporter
252
- ?.upsertInitialSpan?.(span)
253
- .catch((err) => {
254
- logger_js_1.logger.error("upsertInitialSpan error", err?.message || err);
255
- });
256
246
  span.end();
257
247
  break;
258
248
  }
@@ -12,6 +12,7 @@ const ai_agent_js_1 = require("../agents/ai-agent.js");
12
12
  const mcp_agent_js_1 = require("../agents/mcp-agent.js");
13
13
  const team_agent_js_1 = require("../agents/team-agent.js");
14
14
  const transform_agent_js_1 = require("../agents/transform-agent.js");
15
+ const prompt_builder_js_1 = require("../prompt/prompt-builder.js");
15
16
  const type_utils_js_1 = require("../utils/type-utils.js");
16
17
  const agent_js_js_1 = require("./agent-js.js");
17
18
  const agent_yaml_js_1 = require("./agent-yaml.js");
@@ -61,6 +62,8 @@ async function parseAgent(path, agent, options) {
61
62
  case "ai": {
62
63
  return ai_agent_js_1.AIAgent.from({
63
64
  ...agent,
65
+ instructions: agent.instructions &&
66
+ prompt_builder_js_1.PromptBuilder.from(agent.instructions, { workingDir: index_js_1.nodejs.path.dirname(path) }),
64
67
  memory,
65
68
  skills,
66
69
  });
@@ -5,6 +5,7 @@ import type { ChatModel, ChatModelInput } from "../agents/chat-model.js";
5
5
  import { ChatMessagesTemplate } from "./template.js";
6
6
  export interface PromptBuilderOptions {
7
7
  instructions?: string | ChatMessagesTemplate;
8
+ workingDir?: string;
8
9
  }
9
10
  export interface PromptBuildOptions extends Partial<Pick<AgentInvokeOptions, "context">> {
10
11
  agent?: AIAgent;
@@ -15,11 +16,14 @@ export interface PromptBuildOptions extends Partial<Pick<AgentInvokeOptions, "co
15
16
  export declare class PromptBuilder {
16
17
  static from(instructions: string | {
17
18
  path: string;
18
- } | GetPromptResult): PromptBuilder;
19
+ } | GetPromptResult, { workingDir }?: {
20
+ workingDir?: string;
21
+ }): PromptBuilder;
19
22
  private static fromFile;
20
23
  private static fromMCPPromptResult;
21
24
  constructor(options?: PromptBuilderOptions);
22
25
  instructions?: string | ChatMessagesTemplate;
26
+ workingDir?: string;
23
27
  build(options: PromptBuildOptions): Promise<ChatModelInput & {
24
28
  toolAgents?: Agent[];
25
29
  }>;
@@ -12,18 +12,18 @@ const memory_message_template_js_1 = require("./prompts/memory-message-template.
12
12
  const structured_stream_instructions_js_1 = require("./prompts/structured-stream-instructions.js");
13
13
  const template_js_1 = require("./template.js");
14
14
  class PromptBuilder {
15
- static from(instructions) {
15
+ static from(instructions, { workingDir } = {}) {
16
16
  if (typeof instructions === "string")
17
- return new PromptBuilder({ instructions });
17
+ return new PromptBuilder({ instructions, workingDir: workingDir });
18
18
  if (isFromPromptResult(instructions))
19
19
  return PromptBuilder.fromMCPPromptResult(instructions);
20
20
  if (isFromPath(instructions))
21
- return PromptBuilder.fromFile(instructions.path);
21
+ return PromptBuilder.fromFile(instructions.path, { workingDir });
22
22
  throw new Error(`Invalid instructions ${instructions}`);
23
23
  }
24
- static fromFile(path) {
24
+ static fromFile(path, { workingDir }) {
25
25
  const text = index_js_1.nodejs.fsSync.readFileSync(path, "utf-8");
26
- return PromptBuilder.from(text);
26
+ return PromptBuilder.from(text, { workingDir: workingDir || index_js_1.nodejs.path.dirname(path) });
27
27
  }
28
28
  static fromMCPPromptResult(result) {
29
29
  return new PromptBuilder({
@@ -55,8 +55,10 @@ class PromptBuilder {
55
55
  }
56
56
  constructor(options) {
57
57
  this.instructions = options?.instructions;
58
+ this.workingDir = options?.workingDir;
58
59
  }
59
60
  instructions;
61
+ workingDir;
60
62
  async build(options) {
61
63
  return {
62
64
  messages: await this.buildMessages(options),
@@ -70,9 +72,9 @@ class PromptBuilder {
70
72
  const { input } = options;
71
73
  const inputKey = options.agent?.inputKey;
72
74
  const message = inputKey && typeof input?.[inputKey] === "string" ? input[inputKey] : undefined;
73
- const messages = (typeof this.instructions === "string"
75
+ const messages = (await (typeof this.instructions === "string"
74
76
  ? template_js_1.ChatMessagesTemplate.from([template_js_1.SystemMessageTemplate.from(this.instructions)])
75
- : this.instructions)?.format(options.input) ?? [];
77
+ : this.instructions)?.format(options.input, { workingDir: this.workingDir })) ?? [];
76
78
  const memories = [];
77
79
  if (options.agent && options.context) {
78
80
  memories.push(...(await options.agent.retrieveMemories({ search: message }, { context: options.context })));
@@ -81,7 +83,7 @@ class PromptBuilder {
81
83
  memories.push(...options.context.memories);
82
84
  }
83
85
  if (memories.length)
84
- messages.push(...this.convertMemoriesToMessages(memories, options));
86
+ messages.push(...(await this.convertMemoriesToMessages(memories, options)));
85
87
  // if the agent is using structured stream mode, add the instructions
86
88
  const { structuredStreamMode, outputSchema } = options.agent || {};
87
89
  if (structuredStreamMode && outputSchema) {
@@ -102,7 +104,7 @@ class PromptBuilder {
102
104
  }
103
105
  return messages;
104
106
  }
105
- convertMemoriesToMessages(memories, options) {
107
+ async convertMemoriesToMessages(memories, options) {
106
108
  const messages = [];
107
109
  const other = [];
108
110
  const stringOrStringify = (value) => typeof value === "string" ? value : (0, yaml_1.stringify)(value);
@@ -117,7 +119,7 @@ class PromptBuilder {
117
119
  if (other.length) {
118
120
  messages.unshift({
119
121
  role: "system",
120
- content: template_js_1.PromptTemplate.from(options.agent?.memoryPromptTemplate || memory_message_template_js_1.MEMORY_MESSAGE_TEMPLATE).format({ memories: (0, yaml_1.stringify)(other) }),
122
+ content: await template_js_1.PromptTemplate.from(options.agent?.memoryPromptTemplate || memory_message_template_js_1.MEMORY_MESSAGE_TEMPLATE).format({ memories: (0, yaml_1.stringify)(other) }),
121
123
  });
122
124
  }
123
125
  return messages;
@@ -1,16 +1,30 @@
1
+ import nunjucks, { type Callback, type LoaderSource } from "nunjucks";
1
2
  import type { ChatModelInputMessage, ChatModelInputMessageContent, ChatModelOutputToolCall } from "../agents/chat-model.js";
3
+ export interface FormatOptions {
4
+ workingDir?: string;
5
+ }
2
6
  export declare class PromptTemplate {
3
7
  template: string;
4
8
  static from(template: string): PromptTemplate;
5
9
  constructor(template: string);
6
- format(variables?: Record<string, unknown>): string;
10
+ format(variables?: Record<string, unknown>, options?: FormatOptions): Promise<string>;
11
+ }
12
+ export declare class CustomLoader extends nunjucks.Loader {
13
+ options: {
14
+ workingDir: string;
15
+ };
16
+ constructor(options: {
17
+ workingDir: string;
18
+ });
19
+ async: boolean;
20
+ getSource(name: string, callback: Callback<Error, LoaderSource>): LoaderSource;
7
21
  }
8
22
  export declare class ChatMessageTemplate {
9
23
  role: "system" | "user" | "agent" | "tool";
10
24
  content?: ChatModelInputMessage["content"];
11
25
  name?: string | undefined;
12
26
  constructor(role: "system" | "user" | "agent" | "tool", content?: ChatModelInputMessage["content"], name?: string | undefined);
13
- format(variables?: Record<string, unknown>): ChatModelInputMessage;
27
+ format(variables?: Record<string, unknown>, options?: FormatOptions): Promise<ChatModelInputMessage>;
14
28
  }
15
29
  export declare class SystemMessageTemplate extends ChatMessageTemplate {
16
30
  static from(content: string, name?: string): SystemMessageTemplate;
@@ -22,19 +36,19 @@ export declare class AgentMessageTemplate extends ChatMessageTemplate {
22
36
  toolCalls?: ChatModelOutputToolCall[] | undefined;
23
37
  static from(template?: ChatModelInputMessage["content"], toolCalls?: ChatModelOutputToolCall[], name?: string): AgentMessageTemplate;
24
38
  constructor(content?: ChatModelInputMessage["content"], toolCalls?: ChatModelOutputToolCall[] | undefined, name?: string);
25
- format(variables?: Record<string, unknown>): {
39
+ format(variables?: Record<string, unknown>, options?: FormatOptions): Promise<{
26
40
  toolCalls: ChatModelOutputToolCall[] | undefined;
27
41
  role: import("../agents/chat-model.js").Role;
28
42
  content?: ChatModelInputMessageContent;
29
43
  toolCallId?: string;
30
44
  name?: string;
31
- };
45
+ }>;
32
46
  }
33
47
  export declare class ToolMessageTemplate extends ChatMessageTemplate {
34
48
  toolCallId: string;
35
49
  static from(content: object | string, toolCallId: string, name?: string): ToolMessageTemplate;
36
50
  constructor(content: object | string, toolCallId: string, name?: string);
37
- format(variables?: Record<string, unknown>): {
51
+ format(variables?: Record<string, unknown>, options?: FormatOptions): Promise<{
38
52
  toolCallId: string;
39
53
  role: import("../agents/chat-model.js").Role;
40
54
  content?: ChatModelInputMessageContent;
@@ -47,12 +61,12 @@ export declare class ToolMessageTemplate extends ChatMessageTemplate {
47
61
  };
48
62
  }[];
49
63
  name?: string;
50
- };
64
+ }>;
51
65
  }
52
66
  export declare class ChatMessagesTemplate {
53
67
  messages: ChatMessageTemplate[];
54
68
  static from(messages: ChatMessageTemplate[] | string): ChatMessagesTemplate;
55
69
  constructor(messages: ChatMessageTemplate[]);
56
- format(variables?: Record<string, unknown>): ChatModelInputMessage[];
70
+ format(variables?: Record<string, unknown>, options?: FormatOptions): Promise<ChatModelInputMessage[]>;
57
71
  }
58
72
  export declare function parseChatMessages(messages: unknown): ChatMessageTemplate[] | undefined;
@@ -3,10 +3,17 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.ChatMessagesTemplate = exports.ToolMessageTemplate = exports.AgentMessageTemplate = exports.UserMessageTemplate = exports.SystemMessageTemplate = exports.ChatMessageTemplate = exports.PromptTemplate = void 0;
6
+ exports.ChatMessagesTemplate = exports.ToolMessageTemplate = exports.AgentMessageTemplate = exports.UserMessageTemplate = exports.SystemMessageTemplate = exports.ChatMessageTemplate = exports.CustomLoader = exports.PromptTemplate = void 0;
7
7
  exports.parseChatMessages = parseChatMessages;
8
- const mustache_1 = __importDefault(require("mustache"));
8
+ const index_js_1 = require("@aigne/platform-helpers/nodejs/index.js");
9
+ const nunjucks_1 = __importDefault(require("nunjucks"));
9
10
  const zod_1 = require("zod");
11
+ const type_utils_js_1 = require("../utils/type-utils.js");
12
+ nunjucks_1.default.runtime.suppressValue = (v) => {
13
+ if ((0, type_utils_js_1.isNil)(v))
14
+ return "";
15
+ return typeof v === "object" ? JSON.stringify(v) : v;
16
+ };
10
17
  class PromptTemplate {
11
18
  template;
12
19
  static from(template) {
@@ -15,15 +22,46 @@ class PromptTemplate {
15
22
  constructor(template) {
16
23
  this.template = template;
17
24
  }
18
- format(variables) {
19
- return mustache_1.default.render(this.template, variables, undefined, {
20
- escape: (v) => {
21
- return typeof v === "object" ? JSON.stringify(v) : v;
22
- },
23
- });
25
+ async format(variables = {}, options) {
26
+ let env = new nunjucks_1.default.Environment();
27
+ if (options?.workingDir) {
28
+ env = new nunjucks_1.default.Environment(new CustomLoader({ workingDir: options.workingDir }));
29
+ }
30
+ return new Promise((resolve, reject) => env.renderString(this.template, variables, (err, res) => {
31
+ if (err || !res) {
32
+ reject(err || new Error(`Failed to render template: ${this.template}`));
33
+ }
34
+ else {
35
+ resolve(res);
36
+ }
37
+ }));
24
38
  }
25
39
  }
26
40
  exports.PromptTemplate = PromptTemplate;
41
+ class CustomLoader extends nunjucks_1.default.Loader {
42
+ options;
43
+ constructor(options) {
44
+ super();
45
+ this.options = options;
46
+ }
47
+ async = true;
48
+ getSource(name, callback) {
49
+ let result = null;
50
+ index_js_1.nodejs.fs.readFile(index_js_1.nodejs.path.join(this.options.workingDir, name), "utf-8").then((content) => {
51
+ result = {
52
+ src: content,
53
+ path: name,
54
+ noCache: true,
55
+ };
56
+ callback(null, result);
57
+ }, (error) => {
58
+ callback(error, null);
59
+ });
60
+ // nunjucks expects return LoaderSource synchronously, but we handle it asynchronously.
61
+ return result;
62
+ }
63
+ }
64
+ exports.CustomLoader = CustomLoader;
27
65
  class ChatMessageTemplate {
28
66
  role;
29
67
  content;
@@ -33,17 +71,17 @@ class ChatMessageTemplate {
33
71
  this.content = content;
34
72
  this.name = name;
35
73
  }
36
- format(variables) {
74
+ async format(variables, options) {
37
75
  let { content } = this;
38
76
  if (Array.isArray(content)) {
39
- content = content.map((i) => {
77
+ content = await Promise.all(content.map(async (i) => {
40
78
  if (i.type === "text")
41
- return { ...i, text: PromptTemplate.from(i.text).format(variables) };
79
+ return { ...i, text: await PromptTemplate.from(i.text).format(variables, options) };
42
80
  return i;
43
- });
81
+ }));
44
82
  }
45
83
  else if (typeof content === "string") {
46
- content = PromptTemplate.from(content).format(variables);
84
+ content = await PromptTemplate.from(content).format(variables, options);
47
85
  }
48
86
  return {
49
87
  role: this.role,
@@ -74,9 +112,9 @@ class AgentMessageTemplate extends ChatMessageTemplate {
74
112
  super("agent", content, name);
75
113
  this.toolCalls = toolCalls;
76
114
  }
77
- format(variables) {
115
+ async format(variables, options) {
78
116
  return {
79
- ...super.format(variables),
117
+ ...(await super.format(variables, options)),
80
118
  toolCalls: this.toolCalls,
81
119
  };
82
120
  }
@@ -93,9 +131,9 @@ class ToolMessageTemplate extends ChatMessageTemplate {
93
131
  : JSON.stringify(content, (_, value) => typeof value === "bigint" ? value.toString() : value), name);
94
132
  this.toolCallId = toolCallId;
95
133
  }
96
- format(variables) {
134
+ async format(variables, options) {
97
135
  return {
98
- ...super.format(variables),
136
+ ...(await super.format(variables, options)),
99
137
  toolCallId: this.toolCallId,
100
138
  };
101
139
  }
@@ -109,8 +147,8 @@ class ChatMessagesTemplate {
109
147
  constructor(messages) {
110
148
  this.messages = messages;
111
149
  }
112
- format(variables) {
113
- return this.messages.map((message) => message.format(variables));
150
+ async format(variables, options) {
151
+ return Promise.all(this.messages.map((message) => message.format(variables, options)));
114
152
  }
115
153
  }
116
154
  exports.ChatMessagesTemplate = ChatMessagesTemplate;
@@ -21,6 +21,9 @@ const agent_js_1 = require("../agents/agent.js");
21
21
  const type_utils_js_1 = require("./type-utils.js");
22
22
  require("./stream-polyfill.js");
23
23
  function objectToAgentResponseStream(json) {
24
+ if (!(0, type_utils_js_1.isRecord)(json)) {
25
+ throw new Error(`expect to return a record type such as {result: ...}, but got (${typeof json}): ${json}`);
26
+ }
24
27
  return new ReadableStream({
25
28
  pull(controller) {
26
29
  controller.enqueue({ delta: { json } });
@@ -5,6 +5,7 @@ import type { ChatModel, ChatModelInput } from "../agents/chat-model.js";
5
5
  import { ChatMessagesTemplate } from "./template.js";
6
6
  export interface PromptBuilderOptions {
7
7
  instructions?: string | ChatMessagesTemplate;
8
+ workingDir?: string;
8
9
  }
9
10
  export interface PromptBuildOptions extends Partial<Pick<AgentInvokeOptions, "context">> {
10
11
  agent?: AIAgent;
@@ -15,11 +16,14 @@ export interface PromptBuildOptions extends Partial<Pick<AgentInvokeOptions, "co
15
16
  export declare class PromptBuilder {
16
17
  static from(instructions: string | {
17
18
  path: string;
18
- } | GetPromptResult): PromptBuilder;
19
+ } | GetPromptResult, { workingDir }?: {
20
+ workingDir?: string;
21
+ }): PromptBuilder;
19
22
  private static fromFile;
20
23
  private static fromMCPPromptResult;
21
24
  constructor(options?: PromptBuilderOptions);
22
25
  instructions?: string | ChatMessagesTemplate;
26
+ workingDir?: string;
23
27
  build(options: PromptBuildOptions): Promise<ChatModelInput & {
24
28
  toolAgents?: Agent[];
25
29
  }>;
@@ -1,16 +1,30 @@
1
+ import nunjucks, { type Callback, type LoaderSource } from "nunjucks";
1
2
  import type { ChatModelInputMessage, ChatModelInputMessageContent, ChatModelOutputToolCall } from "../agents/chat-model.js";
3
+ export interface FormatOptions {
4
+ workingDir?: string;
5
+ }
2
6
  export declare class PromptTemplate {
3
7
  template: string;
4
8
  static from(template: string): PromptTemplate;
5
9
  constructor(template: string);
6
- format(variables?: Record<string, unknown>): string;
10
+ format(variables?: Record<string, unknown>, options?: FormatOptions): Promise<string>;
11
+ }
12
+ export declare class CustomLoader extends nunjucks.Loader {
13
+ options: {
14
+ workingDir: string;
15
+ };
16
+ constructor(options: {
17
+ workingDir: string;
18
+ });
19
+ async: boolean;
20
+ getSource(name: string, callback: Callback<Error, LoaderSource>): LoaderSource;
7
21
  }
8
22
  export declare class ChatMessageTemplate {
9
23
  role: "system" | "user" | "agent" | "tool";
10
24
  content?: ChatModelInputMessage["content"];
11
25
  name?: string | undefined;
12
26
  constructor(role: "system" | "user" | "agent" | "tool", content?: ChatModelInputMessage["content"], name?: string | undefined);
13
- format(variables?: Record<string, unknown>): ChatModelInputMessage;
27
+ format(variables?: Record<string, unknown>, options?: FormatOptions): Promise<ChatModelInputMessage>;
14
28
  }
15
29
  export declare class SystemMessageTemplate extends ChatMessageTemplate {
16
30
  static from(content: string, name?: string): SystemMessageTemplate;
@@ -22,19 +36,19 @@ export declare class AgentMessageTemplate extends ChatMessageTemplate {
22
36
  toolCalls?: ChatModelOutputToolCall[] | undefined;
23
37
  static from(template?: ChatModelInputMessage["content"], toolCalls?: ChatModelOutputToolCall[], name?: string): AgentMessageTemplate;
24
38
  constructor(content?: ChatModelInputMessage["content"], toolCalls?: ChatModelOutputToolCall[] | undefined, name?: string);
25
- format(variables?: Record<string, unknown>): {
39
+ format(variables?: Record<string, unknown>, options?: FormatOptions): Promise<{
26
40
  toolCalls: ChatModelOutputToolCall[] | undefined;
27
41
  role: import("../agents/chat-model.js").Role;
28
42
  content?: ChatModelInputMessageContent;
29
43
  toolCallId?: string;
30
44
  name?: string;
31
- };
45
+ }>;
32
46
  }
33
47
  export declare class ToolMessageTemplate extends ChatMessageTemplate {
34
48
  toolCallId: string;
35
49
  static from(content: object | string, toolCallId: string, name?: string): ToolMessageTemplate;
36
50
  constructor(content: object | string, toolCallId: string, name?: string);
37
- format(variables?: Record<string, unknown>): {
51
+ format(variables?: Record<string, unknown>, options?: FormatOptions): Promise<{
38
52
  toolCallId: string;
39
53
  role: import("../agents/chat-model.js").Role;
40
54
  content?: ChatModelInputMessageContent;
@@ -47,12 +61,12 @@ export declare class ToolMessageTemplate extends ChatMessageTemplate {
47
61
  };
48
62
  }[];
49
63
  name?: string;
50
- };
64
+ }>;
51
65
  }
52
66
  export declare class ChatMessagesTemplate {
53
67
  messages: ChatMessageTemplate[];
54
68
  static from(messages: ChatMessageTemplate[] | string): ChatMessagesTemplate;
55
69
  constructor(messages: ChatMessageTemplate[]);
56
- format(variables?: Record<string, unknown>): ChatModelInputMessage[];
70
+ format(variables?: Record<string, unknown>, options?: FormatOptions): Promise<ChatModelInputMessage[]>;
57
71
  }
58
72
  export declare function parseChatMessages(messages: unknown): ChatMessageTemplate[] | undefined;
@@ -2,7 +2,7 @@ import { nodejs } from "@aigne/platform-helpers/nodejs/index.js";
2
2
  import { ZodObject, z } from "zod";
3
3
  import { logger } from "../utils/logger.js";
4
4
  import { agentResponseStreamToObject, asyncGeneratorToReadableStream, isAsyncGenerator, objectToAgentResponseStream, onAgentResponseStreamEnd, } from "../utils/stream-utils.js";
5
- import { checkArguments, createAccessorArray, flat, isEmpty, } from "../utils/type-utils.js";
5
+ import { checkArguments, createAccessorArray, flat, isEmpty, isRecord, } from "../utils/type-utils.js";
6
6
  import { replaceTransferAgentToName, transferToAgentOutput, } from "./types.js";
7
7
  export * from "./types.js";
8
8
  export const agentOptionsSchema = z.object({
@@ -349,6 +349,9 @@ export class Agent {
349
349
  */
350
350
  async processAgentOutput(input, output, options) {
351
351
  const { context } = options;
352
+ if (!isRecord(output)) {
353
+ throw new Error(`expect to return a record type such as {result: ...}, but got (${typeof output}): ${output}`);
354
+ }
352
355
  const parsedOutput = checkArguments(`Agent ${this.name} output`, this.outputSchema, output);
353
356
  const finalOutput = this.includeInputInOutput ? { ...input, ...parsedOutput } : parsedOutput;
354
357
  await this.postprocess(input, finalOutput, options);
@@ -283,7 +283,7 @@ export class AIAgent extends Agent {
283
283
  }
284
284
  // Continue LLM function calling loop if any tools were executed
285
285
  if (executedToolCalls.length) {
286
- toolCallMessages.push(AgentMessageTemplate.from(undefined, executedToolCalls.map(({ call }) => call)).format(), ...executedToolCalls.map(({ call, output }) => ToolMessageTemplate.from(output, call.id).format()));
286
+ toolCallMessages.push(await AgentMessageTemplate.from(undefined, executedToolCalls.map(({ call }) => call)).format(), ...(await Promise.all(executedToolCalls.map(({ call, output }) => ToolMessageTemplate.from(output, call.id).format()))));
287
287
  continue;
288
288
  }
289
289
  }
@@ -231,22 +231,12 @@ export class AIGNEContext {
231
231
  span.setAttribute("output", JSON.stringify({}));
232
232
  }
233
233
  span.setStatus({ code: SpanStatusCode.OK });
234
- await this.observer?.traceExporter
235
- ?.upsertInitialSpan?.(span)
236
- .catch((err) => {
237
- logger.error("upsertInitialSpan error", err?.message || err);
238
- });
239
234
  span.end();
240
235
  break;
241
236
  }
242
237
  case "agentFailed": {
243
238
  const { error } = args[0];
244
239
  span.setStatus({ code: SpanStatusCode.ERROR, message: error.message });
245
- await this.observer?.traceExporter
246
- ?.upsertInitialSpan?.(span)
247
- .catch((err) => {
248
- logger.error("upsertInitialSpan error", err?.message || err);
249
- });
250
240
  span.end();
251
241
  break;
252
242
  }
@@ -6,6 +6,7 @@ import { AIAgent } from "../agents/ai-agent.js";
6
6
  import { MCPAgent } from "../agents/mcp-agent.js";
7
7
  import { TeamAgent } from "../agents/team-agent.js";
8
8
  import { TransformAgent } from "../agents/transform-agent.js";
9
+ import { PromptBuilder } from "../prompt/prompt-builder.js";
9
10
  import { tryOrThrow } from "../utils/type-utils.js";
10
11
  import { loadAgentFromJsFile } from "./agent-js.js";
11
12
  import { loadAgentFromYamlFile } from "./agent-yaml.js";
@@ -55,6 +56,8 @@ async function parseAgent(path, agent, options) {
55
56
  case "ai": {
56
57
  return AIAgent.from({
57
58
  ...agent,
59
+ instructions: agent.instructions &&
60
+ PromptBuilder.from(agent.instructions, { workingDir: nodejs.path.dirname(path) }),
58
61
  memory,
59
62
  skills,
60
63
  });
@@ -5,6 +5,7 @@ import type { ChatModel, ChatModelInput } from "../agents/chat-model.js";
5
5
  import { ChatMessagesTemplate } from "./template.js";
6
6
  export interface PromptBuilderOptions {
7
7
  instructions?: string | ChatMessagesTemplate;
8
+ workingDir?: string;
8
9
  }
9
10
  export interface PromptBuildOptions extends Partial<Pick<AgentInvokeOptions, "context">> {
10
11
  agent?: AIAgent;
@@ -15,11 +16,14 @@ export interface PromptBuildOptions extends Partial<Pick<AgentInvokeOptions, "co
15
16
  export declare class PromptBuilder {
16
17
  static from(instructions: string | {
17
18
  path: string;
18
- } | GetPromptResult): PromptBuilder;
19
+ } | GetPromptResult, { workingDir }?: {
20
+ workingDir?: string;
21
+ }): PromptBuilder;
19
22
  private static fromFile;
20
23
  private static fromMCPPromptResult;
21
24
  constructor(options?: PromptBuilderOptions);
22
25
  instructions?: string | ChatMessagesTemplate;
26
+ workingDir?: string;
23
27
  build(options: PromptBuildOptions): Promise<ChatModelInput & {
24
28
  toolAgents?: Agent[];
25
29
  }>;
@@ -9,18 +9,18 @@ import { MEMORY_MESSAGE_TEMPLATE } from "./prompts/memory-message-template.js";
9
9
  import { STRUCTURED_STREAM_INSTRUCTIONS } from "./prompts/structured-stream-instructions.js";
10
10
  import { AgentMessageTemplate, ChatMessagesTemplate, PromptTemplate, SystemMessageTemplate, UserMessageTemplate, } from "./template.js";
11
11
  export class PromptBuilder {
12
- static from(instructions) {
12
+ static from(instructions, { workingDir } = {}) {
13
13
  if (typeof instructions === "string")
14
- return new PromptBuilder({ instructions });
14
+ return new PromptBuilder({ instructions, workingDir: workingDir });
15
15
  if (isFromPromptResult(instructions))
16
16
  return PromptBuilder.fromMCPPromptResult(instructions);
17
17
  if (isFromPath(instructions))
18
- return PromptBuilder.fromFile(instructions.path);
18
+ return PromptBuilder.fromFile(instructions.path, { workingDir });
19
19
  throw new Error(`Invalid instructions ${instructions}`);
20
20
  }
21
- static fromFile(path) {
21
+ static fromFile(path, { workingDir }) {
22
22
  const text = nodejs.fsSync.readFileSync(path, "utf-8");
23
- return PromptBuilder.from(text);
23
+ return PromptBuilder.from(text, { workingDir: workingDir || nodejs.path.dirname(path) });
24
24
  }
25
25
  static fromMCPPromptResult(result) {
26
26
  return new PromptBuilder({
@@ -52,8 +52,10 @@ export class PromptBuilder {
52
52
  }
53
53
  constructor(options) {
54
54
  this.instructions = options?.instructions;
55
+ this.workingDir = options?.workingDir;
55
56
  }
56
57
  instructions;
58
+ workingDir;
57
59
  async build(options) {
58
60
  return {
59
61
  messages: await this.buildMessages(options),
@@ -67,9 +69,9 @@ export class PromptBuilder {
67
69
  const { input } = options;
68
70
  const inputKey = options.agent?.inputKey;
69
71
  const message = inputKey && typeof input?.[inputKey] === "string" ? input[inputKey] : undefined;
70
- const messages = (typeof this.instructions === "string"
72
+ const messages = (await (typeof this.instructions === "string"
71
73
  ? ChatMessagesTemplate.from([SystemMessageTemplate.from(this.instructions)])
72
- : this.instructions)?.format(options.input) ?? [];
74
+ : this.instructions)?.format(options.input, { workingDir: this.workingDir })) ?? [];
73
75
  const memories = [];
74
76
  if (options.agent && options.context) {
75
77
  memories.push(...(await options.agent.retrieveMemories({ search: message }, { context: options.context })));
@@ -78,7 +80,7 @@ export class PromptBuilder {
78
80
  memories.push(...options.context.memories);
79
81
  }
80
82
  if (memories.length)
81
- messages.push(...this.convertMemoriesToMessages(memories, options));
83
+ messages.push(...(await this.convertMemoriesToMessages(memories, options)));
82
84
  // if the agent is using structured stream mode, add the instructions
83
85
  const { structuredStreamMode, outputSchema } = options.agent || {};
84
86
  if (structuredStreamMode && outputSchema) {
@@ -99,7 +101,7 @@ export class PromptBuilder {
99
101
  }
100
102
  return messages;
101
103
  }
102
- convertMemoriesToMessages(memories, options) {
104
+ async convertMemoriesToMessages(memories, options) {
103
105
  const messages = [];
104
106
  const other = [];
105
107
  const stringOrStringify = (value) => typeof value === "string" ? value : stringify(value);
@@ -114,7 +116,7 @@ export class PromptBuilder {
114
116
  if (other.length) {
115
117
  messages.unshift({
116
118
  role: "system",
117
- content: PromptTemplate.from(options.agent?.memoryPromptTemplate || MEMORY_MESSAGE_TEMPLATE).format({ memories: stringify(other) }),
119
+ content: await PromptTemplate.from(options.agent?.memoryPromptTemplate || MEMORY_MESSAGE_TEMPLATE).format({ memories: stringify(other) }),
118
120
  });
119
121
  }
120
122
  return messages;
@@ -1,16 +1,30 @@
1
+ import nunjucks, { type Callback, type LoaderSource } from "nunjucks";
1
2
  import type { ChatModelInputMessage, ChatModelInputMessageContent, ChatModelOutputToolCall } from "../agents/chat-model.js";
3
+ export interface FormatOptions {
4
+ workingDir?: string;
5
+ }
2
6
  export declare class PromptTemplate {
3
7
  template: string;
4
8
  static from(template: string): PromptTemplate;
5
9
  constructor(template: string);
6
- format(variables?: Record<string, unknown>): string;
10
+ format(variables?: Record<string, unknown>, options?: FormatOptions): Promise<string>;
11
+ }
12
+ export declare class CustomLoader extends nunjucks.Loader {
13
+ options: {
14
+ workingDir: string;
15
+ };
16
+ constructor(options: {
17
+ workingDir: string;
18
+ });
19
+ async: boolean;
20
+ getSource(name: string, callback: Callback<Error, LoaderSource>): LoaderSource;
7
21
  }
8
22
  export declare class ChatMessageTemplate {
9
23
  role: "system" | "user" | "agent" | "tool";
10
24
  content?: ChatModelInputMessage["content"];
11
25
  name?: string | undefined;
12
26
  constructor(role: "system" | "user" | "agent" | "tool", content?: ChatModelInputMessage["content"], name?: string | undefined);
13
- format(variables?: Record<string, unknown>): ChatModelInputMessage;
27
+ format(variables?: Record<string, unknown>, options?: FormatOptions): Promise<ChatModelInputMessage>;
14
28
  }
15
29
  export declare class SystemMessageTemplate extends ChatMessageTemplate {
16
30
  static from(content: string, name?: string): SystemMessageTemplate;
@@ -22,19 +36,19 @@ export declare class AgentMessageTemplate extends ChatMessageTemplate {
22
36
  toolCalls?: ChatModelOutputToolCall[] | undefined;
23
37
  static from(template?: ChatModelInputMessage["content"], toolCalls?: ChatModelOutputToolCall[], name?: string): AgentMessageTemplate;
24
38
  constructor(content?: ChatModelInputMessage["content"], toolCalls?: ChatModelOutputToolCall[] | undefined, name?: string);
25
- format(variables?: Record<string, unknown>): {
39
+ format(variables?: Record<string, unknown>, options?: FormatOptions): Promise<{
26
40
  toolCalls: ChatModelOutputToolCall[] | undefined;
27
41
  role: import("../agents/chat-model.js").Role;
28
42
  content?: ChatModelInputMessageContent;
29
43
  toolCallId?: string;
30
44
  name?: string;
31
- };
45
+ }>;
32
46
  }
33
47
  export declare class ToolMessageTemplate extends ChatMessageTemplate {
34
48
  toolCallId: string;
35
49
  static from(content: object | string, toolCallId: string, name?: string): ToolMessageTemplate;
36
50
  constructor(content: object | string, toolCallId: string, name?: string);
37
- format(variables?: Record<string, unknown>): {
51
+ format(variables?: Record<string, unknown>, options?: FormatOptions): Promise<{
38
52
  toolCallId: string;
39
53
  role: import("../agents/chat-model.js").Role;
40
54
  content?: ChatModelInputMessageContent;
@@ -47,12 +61,12 @@ export declare class ToolMessageTemplate extends ChatMessageTemplate {
47
61
  };
48
62
  }[];
49
63
  name?: string;
50
- };
64
+ }>;
51
65
  }
52
66
  export declare class ChatMessagesTemplate {
53
67
  messages: ChatMessageTemplate[];
54
68
  static from(messages: ChatMessageTemplate[] | string): ChatMessagesTemplate;
55
69
  constructor(messages: ChatMessageTemplate[]);
56
- format(variables?: Record<string, unknown>): ChatModelInputMessage[];
70
+ format(variables?: Record<string, unknown>, options?: FormatOptions): Promise<ChatModelInputMessage[]>;
57
71
  }
58
72
  export declare function parseChatMessages(messages: unknown): ChatMessageTemplate[] | undefined;
@@ -1,5 +1,12 @@
1
- import Mustache from "mustache";
1
+ import { nodejs } from "@aigne/platform-helpers/nodejs/index.js";
2
+ import nunjucks from "nunjucks";
2
3
  import { z } from "zod";
4
+ import { isNil } from "../utils/type-utils.js";
5
+ nunjucks.runtime.suppressValue = (v) => {
6
+ if (isNil(v))
7
+ return "";
8
+ return typeof v === "object" ? JSON.stringify(v) : v;
9
+ };
3
10
  export class PromptTemplate {
4
11
  template;
5
12
  static from(template) {
@@ -8,12 +15,42 @@ export class PromptTemplate {
8
15
  constructor(template) {
9
16
  this.template = template;
10
17
  }
11
- format(variables) {
12
- return Mustache.render(this.template, variables, undefined, {
13
- escape: (v) => {
14
- return typeof v === "object" ? JSON.stringify(v) : v;
15
- },
18
+ async format(variables = {}, options) {
19
+ let env = new nunjucks.Environment();
20
+ if (options?.workingDir) {
21
+ env = new nunjucks.Environment(new CustomLoader({ workingDir: options.workingDir }));
22
+ }
23
+ return new Promise((resolve, reject) => env.renderString(this.template, variables, (err, res) => {
24
+ if (err || !res) {
25
+ reject(err || new Error(`Failed to render template: ${this.template}`));
26
+ }
27
+ else {
28
+ resolve(res);
29
+ }
30
+ }));
31
+ }
32
+ }
33
+ export class CustomLoader extends nunjucks.Loader {
34
+ options;
35
+ constructor(options) {
36
+ super();
37
+ this.options = options;
38
+ }
39
+ async = true;
40
+ getSource(name, callback) {
41
+ let result = null;
42
+ nodejs.fs.readFile(nodejs.path.join(this.options.workingDir, name), "utf-8").then((content) => {
43
+ result = {
44
+ src: content,
45
+ path: name,
46
+ noCache: true,
47
+ };
48
+ callback(null, result);
49
+ }, (error) => {
50
+ callback(error, null);
16
51
  });
52
+ // nunjucks expects return LoaderSource synchronously, but we handle it asynchronously.
53
+ return result;
17
54
  }
18
55
  }
19
56
  export class ChatMessageTemplate {
@@ -25,17 +62,17 @@ export class ChatMessageTemplate {
25
62
  this.content = content;
26
63
  this.name = name;
27
64
  }
28
- format(variables) {
65
+ async format(variables, options) {
29
66
  let { content } = this;
30
67
  if (Array.isArray(content)) {
31
- content = content.map((i) => {
68
+ content = await Promise.all(content.map(async (i) => {
32
69
  if (i.type === "text")
33
- return { ...i, text: PromptTemplate.from(i.text).format(variables) };
70
+ return { ...i, text: await PromptTemplate.from(i.text).format(variables, options) };
34
71
  return i;
35
- });
72
+ }));
36
73
  }
37
74
  else if (typeof content === "string") {
38
- content = PromptTemplate.from(content).format(variables);
75
+ content = await PromptTemplate.from(content).format(variables, options);
39
76
  }
40
77
  return {
41
78
  role: this.role,
@@ -63,9 +100,9 @@ export class AgentMessageTemplate extends ChatMessageTemplate {
63
100
  super("agent", content, name);
64
101
  this.toolCalls = toolCalls;
65
102
  }
66
- format(variables) {
103
+ async format(variables, options) {
67
104
  return {
68
- ...super.format(variables),
105
+ ...(await super.format(variables, options)),
69
106
  toolCalls: this.toolCalls,
70
107
  };
71
108
  }
@@ -81,9 +118,9 @@ export class ToolMessageTemplate extends ChatMessageTemplate {
81
118
  : JSON.stringify(content, (_, value) => typeof value === "bigint" ? value.toString() : value), name);
82
119
  this.toolCallId = toolCallId;
83
120
  }
84
- format(variables) {
121
+ async format(variables, options) {
85
122
  return {
86
- ...super.format(variables),
123
+ ...(await super.format(variables, options)),
87
124
  toolCallId: this.toolCallId,
88
125
  };
89
126
  }
@@ -96,8 +133,8 @@ export class ChatMessagesTemplate {
96
133
  constructor(messages) {
97
134
  this.messages = messages;
98
135
  }
99
- format(variables) {
100
- return this.messages.map((message) => message.format(variables));
136
+ async format(variables, options) {
137
+ return Promise.all(this.messages.map((message) => message.format(variables, options)));
101
138
  }
102
139
  }
103
140
  const systemChatMessageSchema = z.object({
@@ -1,8 +1,11 @@
1
1
  import equal from "fast-deep-equal";
2
2
  import { isAgentResponseDelta, isEmptyChunk, } from "../agents/agent.js";
3
- import { omitBy } from "./type-utils.js";
3
+ import { isRecord, omitBy } from "./type-utils.js";
4
4
  import "./stream-polyfill.js";
5
5
  export function objectToAgentResponseStream(json) {
6
+ if (!isRecord(json)) {
7
+ throw new Error(`expect to return a record type such as {result: ...}, but got (${typeof json}): ${json}`);
8
+ }
6
9
  return new ReadableStream({
7
10
  pull(controller) {
8
11
  controller.enqueue({ delta: { json } });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aigne/core",
3
- "version": "1.31.0",
3
+ "version": "1.32.1",
4
4
  "description": "AIGNE core library for building AI-powered applications",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -74,6 +74,7 @@
74
74
  "jsonata": "^2.0.6",
75
75
  "mustache": "^4.2.0",
76
76
  "nanoid": "^5.1.5",
77
+ "nunjucks": "^3.2.4",
77
78
  "p-retry": "^6.2.1",
78
79
  "raw-body": "^3.0.0",
79
80
  "strict-event-emitter": "^0.5.1",
@@ -82,7 +83,7 @@
82
83
  "yaml": "^2.8.0",
83
84
  "zod": "^3.25.67",
84
85
  "zod-to-json-schema": "^3.24.6",
85
- "@aigne/observability-api": "^0.5.0",
86
+ "@aigne/observability-api": "^0.7.0",
86
87
  "@aigne/platform-helpers": "^0.3.0"
87
88
  },
88
89
  "devDependencies": {
@@ -92,6 +93,7 @@
92
93
  "@types/express": "^5.0.3",
93
94
  "@types/mustache": "^4.2.6",
94
95
  "@types/node": "^24.0.10",
96
+ "@types/nunjucks": "^3.2.6",
95
97
  "compression": "^1.8.0",
96
98
  "detect-port": "^2.1.0",
97
99
  "express": "^5.1.0",