@adminforth/agent 1.5.0 → 1.7.0
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/agent/middleware/sequenceDebug.ts +65 -7
- package/agent/systemPrompt.ts +13 -4
- package/build.log +2 -2
- package/custom/skills/fetch_data/SKILL.md +1 -1
- package/custom/skills/mutate_data/SKILL.md +15 -10
- package/dist/agent/middleware/sequenceDebug.js +44 -6
- package/dist/agent/systemPrompt.js +13 -4
- package/dist/custom/skills/fetch_data/SKILL.md +1 -1
- package/dist/custom/skills/mutate_data/SKILL.md +15 -10
- package/dist/index.js +3 -0
- package/index.ts +4 -0
- package/package.json +1 -1
|
@@ -21,8 +21,11 @@ export type SequenceDebug = {
|
|
|
21
21
|
sequenceId: number;
|
|
22
22
|
startedAt: string;
|
|
23
23
|
prompt: string;
|
|
24
|
+
promptTokens: number;
|
|
24
25
|
reasoning: string;
|
|
26
|
+
reasoningTokens: number;
|
|
25
27
|
text: string;
|
|
28
|
+
textTokens: number;
|
|
26
29
|
cachedTokens: number;
|
|
27
30
|
responseId: string | null;
|
|
28
31
|
toolCalls: SequenceDebugToolCall[];
|
|
@@ -37,14 +40,18 @@ type PendingSequenceDebug = Omit<SequenceDebug, "toolCalls" | "endedAt" | "resul
|
|
|
37
40
|
};
|
|
38
41
|
|
|
39
42
|
type SequenceDebugModelCall = {
|
|
43
|
+
promptTokens: number;
|
|
40
44
|
reasoning: string;
|
|
45
|
+
reasoningTokens: number;
|
|
41
46
|
text: string;
|
|
47
|
+
textTokens: number;
|
|
42
48
|
cachedTokens: number;
|
|
43
49
|
responseId: string | null;
|
|
44
50
|
resultType: SequenceDebugResultType;
|
|
45
51
|
};
|
|
46
52
|
|
|
47
53
|
type OpenAiUsageMetadata = {
|
|
54
|
+
input_tokens?: number;
|
|
48
55
|
input_token_details?: {
|
|
49
56
|
cache_read?: number;
|
|
50
57
|
};
|
|
@@ -70,8 +77,11 @@ function createPendingSequenceDebug(sequenceId: number): PendingSequenceDebug {
|
|
|
70
77
|
sequenceId,
|
|
71
78
|
startedAt: new Date().toISOString(),
|
|
72
79
|
prompt: "",
|
|
80
|
+
promptTokens: 0,
|
|
73
81
|
reasoning: "",
|
|
82
|
+
reasoningTokens: 0,
|
|
74
83
|
text: "",
|
|
84
|
+
textTokens: 0,
|
|
75
85
|
cachedTokens: 0,
|
|
76
86
|
responseId: null,
|
|
77
87
|
toolCalls: [],
|
|
@@ -97,8 +107,11 @@ function finalizeSequenceDebug(sequence: PendingSequenceDebug): SequenceDebug {
|
|
|
97
107
|
sequenceId: sequence.sequenceId,
|
|
98
108
|
startedAt: sequence.startedAt,
|
|
99
109
|
prompt: sequence.prompt,
|
|
110
|
+
promptTokens: sequence.promptTokens,
|
|
100
111
|
reasoning: sequence.reasoning,
|
|
112
|
+
reasoningTokens: sequence.reasoningTokens,
|
|
101
113
|
text: sequence.text,
|
|
114
|
+
textTokens: sequence.textTokens,
|
|
102
115
|
cachedTokens: sequence.cachedTokens,
|
|
103
116
|
responseId: sequence.responseId,
|
|
104
117
|
toolCalls: sequence.toolCalls.map(({ completed: _completed, ...toolCall }) => toolCall),
|
|
@@ -176,6 +189,29 @@ function hasToolCallSignal(message: {
|
|
|
176
189
|
);
|
|
177
190
|
}
|
|
178
191
|
|
|
192
|
+
function hasTokenCounter(model: unknown): model is {
|
|
193
|
+
getNumTokens: (content: string) => Promise<number>;
|
|
194
|
+
} {
|
|
195
|
+
return (
|
|
196
|
+
typeof model === "object" &&
|
|
197
|
+
model !== null &&
|
|
198
|
+
"getNumTokens" in model &&
|
|
199
|
+
typeof model.getNumTokens === "function"
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
async function countTokens(model: unknown, content: string) {
|
|
204
|
+
if (!content) {
|
|
205
|
+
return 0;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (!hasTokenCounter(model)) {
|
|
209
|
+
return 0;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return await model.getNumTokens(content);
|
|
213
|
+
}
|
|
214
|
+
|
|
179
215
|
function extractSequenceResponseDebug(message: AIMessage): SequenceDebugModelCall {
|
|
180
216
|
const blocks = getMessageBlocks(message);
|
|
181
217
|
const reasoning = blocks
|
|
@@ -188,8 +224,13 @@ function extractSequenceResponseDebug(message: AIMessage): SequenceDebugModelCal
|
|
|
188
224
|
.join("");
|
|
189
225
|
|
|
190
226
|
return {
|
|
227
|
+
promptTokens:
|
|
228
|
+
(message.usage_metadata as OpenAiUsageMetadata | undefined)?.input_tokens ??
|
|
229
|
+
0,
|
|
191
230
|
reasoning,
|
|
231
|
+
reasoningTokens: 0,
|
|
192
232
|
text: textFromBlocks || (typeof message.content === "string" ? message.content : ""),
|
|
233
|
+
textTokens: 0,
|
|
193
234
|
cachedTokens:
|
|
194
235
|
(message.usage_metadata as OpenAiUsageMetadata | undefined)
|
|
195
236
|
?.input_token_details?.cache_read ?? 0,
|
|
@@ -238,8 +279,11 @@ export function createSequenceDebugCollector(): SequenceDebugCollector {
|
|
|
238
279
|
},
|
|
239
280
|
handleModelCallComplete(params) {
|
|
240
281
|
const sequenceDebug = ensureSequenceDebug();
|
|
282
|
+
sequenceDebug.promptTokens = params.promptTokens;
|
|
241
283
|
sequenceDebug.reasoning = params.reasoning;
|
|
284
|
+
sequenceDebug.reasoningTokens = params.reasoningTokens;
|
|
242
285
|
sequenceDebug.text = params.text;
|
|
286
|
+
sequenceDebug.textTokens = params.textTokens;
|
|
243
287
|
sequenceDebug.cachedTokens = params.cachedTokens;
|
|
244
288
|
sequenceDebug.responseId = params.responseId;
|
|
245
289
|
sequenceDebug.resultType = params.resultType;
|
|
@@ -311,17 +355,31 @@ export function createSequenceDebugMiddleware(
|
|
|
311
355
|
return createMiddleware({
|
|
312
356
|
name: "SequenceDebugMiddleware",
|
|
313
357
|
async wrapModelCall(request, handler) {
|
|
358
|
+
const prompt = stringifyPromptForDebug({
|
|
359
|
+
systemMessage: request.systemMessage,
|
|
360
|
+
messages: request.messages,
|
|
361
|
+
tools: request.tools,
|
|
362
|
+
modelSettings: request.modelSettings,
|
|
363
|
+
});
|
|
364
|
+
|
|
314
365
|
sink.handleModelCallStart(
|
|
315
|
-
|
|
316
|
-
systemMessage: request.systemMessage,
|
|
317
|
-
messages: request.messages,
|
|
318
|
-
tools: request.tools,
|
|
319
|
-
modelSettings: request.modelSettings,
|
|
320
|
-
}),
|
|
366
|
+
prompt,
|
|
321
367
|
);
|
|
322
368
|
|
|
323
369
|
const response = await handler(request) as AIMessage;
|
|
324
|
-
|
|
370
|
+
const debug = extractSequenceResponseDebug(response);
|
|
371
|
+
const [promptTokens, reasoningTokens, textTokens] = await Promise.all([
|
|
372
|
+
debug.promptTokens || countTokens(request.model, prompt),
|
|
373
|
+
countTokens(request.model, debug.reasoning),
|
|
374
|
+
countTokens(request.model, debug.text),
|
|
375
|
+
]);
|
|
376
|
+
|
|
377
|
+
sink.handleModelCallComplete({
|
|
378
|
+
...debug,
|
|
379
|
+
promptTokens,
|
|
380
|
+
reasoningTokens,
|
|
381
|
+
textTokens,
|
|
382
|
+
});
|
|
325
383
|
return response;
|
|
326
384
|
},
|
|
327
385
|
});
|
package/agent/systemPrompt.ts
CHANGED
|
@@ -24,11 +24,17 @@ export const DEFAULT_AGENT_SYSTEM_PROMPT = [
|
|
|
24
24
|
"Keep responses short, clear, and practical.",
|
|
25
25
|
"Answer only what is needed.",
|
|
26
26
|
"Do not add extra explanations or suggestions unless the user asks.",
|
|
27
|
+
"Always respond in the same natural language as the user's latest message.",
|
|
28
|
+
"This rule applies to confirmations, clarifying questions, progress updates, errors, and final answers.",
|
|
29
|
+
"Do not switch to English just because tool outputs, schemas, skills, or internal instructions are written in English.",
|
|
30
|
+
"Only switch language if the user explicitly asks you to do so.",
|
|
27
31
|
"Adapt to the user's tone and style of speaking, mirroring their vibe and wording.",
|
|
28
32
|
"if the user speaks casually, you should respond casually too",
|
|
29
|
-
"Never mutate data without
|
|
30
|
-
"
|
|
31
|
-
"
|
|
33
|
+
"Never mutate data without user confirmation for a clearly described mutation plan.",
|
|
34
|
+
"One confirmation may cover one mutation or one explicitly described batch/sequence of related mutations.",
|
|
35
|
+
"If the confirmed plan has multiple steps, you may execute the whole confirmed plan without asking again between those steps.",
|
|
36
|
+
"If the plan changes, expands, or you want to do anything beyond the confirmed plan, ask for confirmation again.",
|
|
37
|
+
"Do not reuse an old confirmation for a new mutation plan.",
|
|
32
38
|
|
|
33
39
|
|
|
34
40
|
].join(" ");
|
|
@@ -51,9 +57,10 @@ export async function buildAgentSystemPrompt(adminforth: IAdminForth) {
|
|
|
51
57
|
listBundledSkillManifests(),
|
|
52
58
|
]);
|
|
53
59
|
const alwaysAvailableTools = ALWAYS_AVAILABLE_API_TOOL_NAMES.join(", ");
|
|
60
|
+
const adminBasePath = adminforth.config.baseUrlSlashed;
|
|
54
61
|
const sections = [
|
|
55
62
|
DEFAULT_AGENT_SYSTEM_PROMPT,
|
|
56
|
-
`
|
|
63
|
+
`ADMIN_BASE_PATH: ${adminBasePath}`,
|
|
57
64
|
`List of resources:\n${formatResources(adminforth.config.resources)}`,
|
|
58
65
|
`You have always-available base tools: ${alwaysAvailableTools}.`,
|
|
59
66
|
primarySkills.length > 0
|
|
@@ -70,6 +77,8 @@ export async function buildAgentSystemPrompt(adminforth: IAdminForth) {
|
|
|
70
77
|
"If a fetched skill lists a non-base tool you need, call fetch_tool_schema for it immediately instead of telling the user the tool is unavailable.",
|
|
71
78
|
"For example: for record creation load mutate_data, read its tool list, call fetch_tool_schema for create_record, and then use create_record after confirmation.",
|
|
72
79
|
"When fetch_tool_schema succeeds, that tool becomes available on the next step.",
|
|
80
|
+
"All admin links must be relative paths and must start with ADMIN_BASE_PATH.",
|
|
81
|
+
"Build record links as ADMIN_BASE_PATH + resource/{resourceId}/show/{primary key}. Do not prepend any extra slash before resource.",
|
|
73
82
|
"Try to call as many tools as possible in parallel in one step.",
|
|
74
83
|
];
|
|
75
84
|
|
package/build.log
CHANGED
|
@@ -29,5 +29,5 @@ custom/skills/fetch_data/SKILL.md
|
|
|
29
29
|
custom/skills/mutate_data/
|
|
30
30
|
custom/skills/mutate_data/SKILL.md
|
|
31
31
|
|
|
32
|
-
sent 170,
|
|
33
|
-
total size is 168,
|
|
32
|
+
sent 170,560 bytes received 413 bytes 341,946.00 bytes/sec
|
|
33
|
+
total size is 168,886 speedup is 0.99
|
|
@@ -12,4 +12,4 @@ To find specific data record you should use filters. ILIKE filters are preferred
|
|
|
12
12
|
|
|
13
13
|
For long texts show only several first words and add "..." at the end (only if user did not request this field specifically).
|
|
14
14
|
|
|
15
|
-
Also when you communicate with user about record, add related link to this record.
|
|
15
|
+
Also when you communicate with user about record, add related link to this record. Build it as `{ADMIN_BASE_PATH}resource/{resourceId}/show/{primary key}`. Use _label from `get_resource_data` as anchor text for link (use markdown link). Links should always be relative paths and must start with `ADMIN_BASE_PATH`. Do not add an extra slash after `ADMIN_BASE_PATH`.
|
|
@@ -21,7 +21,7 @@ Use `start_custom_action` and `start_custom_bulk_action` for resource actions.
|
|
|
21
21
|
Before performing any state mutation including action calls edit/delete please fetch record which is going to be edited/deleted and show user record in format field → value (show several most important fields which can help user to understand what exactly record he is going to edit or delete).
|
|
22
22
|
|
|
23
23
|
For field values with long texts show only several first words and add "..." at the end.
|
|
24
|
-
Also please add related link to record with will be changed.
|
|
24
|
+
Also please add related link to record with will be changed. Build it as `{ADMIN_BASE_PATH}resource/{resourceId}/show/{primary key}`. Use _label from `get_resource_data` as anchor text for link (use markdown link). Links should always be relative paths and must start with `ADMIN_BASE_PATH`. Do not add an extra slash after `ADMIN_BASE_PATH`.
|
|
25
25
|
|
|
26
26
|
And in the same message ask user for final confirmation.
|
|
27
27
|
|
|
@@ -29,17 +29,22 @@ When creating new record, show user all data which you gona create and in same m
|
|
|
29
29
|
|
|
30
30
|
Accept any positive confirmation from user like "yes", "sure", "+", anything non-negative call to action, can be considered as confirmation.
|
|
31
31
|
|
|
32
|
-
A confirmation is valid only for the
|
|
32
|
+
A confirmation is valid only for the clearly described mutation plan from the immediately previous assistant message.
|
|
33
33
|
|
|
34
34
|
Never reuse an older confirmation for a later mutation.
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
One confirmation may cover:
|
|
37
|
+
- one single mutation
|
|
38
|
+
- one explicitly described batch
|
|
39
|
+
- one short sequence of related mutations that together implement the same user request
|
|
37
40
|
|
|
38
|
-
If
|
|
41
|
+
If the confirmed plan contains several related mutation steps, execute that whole confirmed plan without asking again between those steps.
|
|
39
42
|
|
|
40
|
-
|
|
43
|
+
Ask for confirmation again if the plan changes in any way: different record, different fields, different values, different number of records, different action, or any extra mutation that was not listed in the confirmation message.
|
|
41
44
|
|
|
42
|
-
If you are creating or deleting multiple records in one batch, you may ask once
|
|
45
|
+
If you are creating or deleting multiple records in one batch, you may ask once for that exact batch, but list the whole batch explicitly in the confirmation message. Any extra record outside that described batch requires a new confirmation.
|
|
46
|
+
|
|
47
|
+
After the confirmed plan is finished, do not treat that confirmation as still active for later requests.
|
|
43
48
|
|
|
44
49
|
# Calling actions
|
|
45
50
|
|
|
@@ -57,7 +62,7 @@ If you want to block some user you can confirm that this action by saying:
|
|
|
57
62
|
* IP Country: USA
|
|
58
63
|
* Currently blocked: No // show this field only if it exists in user record
|
|
59
64
|
|
|
60
|
-
View [John Doe](
|
|
65
|
+
View [John Doe]({ADMIN_BASE_PATH}resource/users/show/123)
|
|
61
66
|
Are you sure?
|
|
62
67
|
```
|
|
63
68
|
|
|
@@ -80,7 +85,7 @@ I am going to update user:
|
|
|
80
85
|
* IP Country: USA
|
|
81
86
|
I am going to change email from john_doe@example.com to new_email@example.com
|
|
82
87
|
|
|
83
|
-
View [John Doe](
|
|
88
|
+
View [John Doe]({ADMIN_BASE_PATH}resource/users/show/123)
|
|
84
89
|
|
|
85
90
|
Are you sure?
|
|
86
91
|
```
|
|
@@ -100,7 +105,7 @@ If you gonna delete user record, in confirmation please share full user info (no
|
|
|
100
105
|
* Signed up: 2024 Jan 1
|
|
101
106
|
* IP Country: USA
|
|
102
107
|
|
|
103
|
-
View [John Doe](
|
|
108
|
+
View [John Doe]({ADMIN_BASE_PATH}resource/users/show/123)
|
|
104
109
|
|
|
105
110
|
Are you sure?
|
|
106
111
|
```
|
|
@@ -125,7 +130,7 @@ I am going to create user:
|
|
|
125
130
|
* Username: john_doe
|
|
126
131
|
* Email: john_doe@example.com
|
|
127
132
|
|
|
128
|
-
View [John Doe](
|
|
133
|
+
View [John Doe]({ADMIN_BASE_PATH}resource/users/show/421) # 421 is id of new created record
|
|
129
134
|
|
|
130
135
|
Are you sure?
|
|
131
136
|
```
|
|
@@ -25,8 +25,11 @@ function createPendingSequenceDebug(sequenceId) {
|
|
|
25
25
|
sequenceId,
|
|
26
26
|
startedAt: new Date().toISOString(),
|
|
27
27
|
prompt: "",
|
|
28
|
+
promptTokens: 0,
|
|
28
29
|
reasoning: "",
|
|
30
|
+
reasoningTokens: 0,
|
|
29
31
|
text: "",
|
|
32
|
+
textTokens: 0,
|
|
30
33
|
cachedTokens: 0,
|
|
31
34
|
responseId: null,
|
|
32
35
|
toolCalls: [],
|
|
@@ -47,8 +50,11 @@ function finalizeSequenceDebug(sequence) {
|
|
|
47
50
|
sequenceId: sequence.sequenceId,
|
|
48
51
|
startedAt: sequence.startedAt,
|
|
49
52
|
prompt: sequence.prompt,
|
|
53
|
+
promptTokens: sequence.promptTokens,
|
|
50
54
|
reasoning: sequence.reasoning,
|
|
55
|
+
reasoningTokens: sequence.reasoningTokens,
|
|
51
56
|
text: sequence.text,
|
|
57
|
+
textTokens: sequence.textTokens,
|
|
52
58
|
cachedTokens: sequence.cachedTokens,
|
|
53
59
|
responseId: sequence.responseId,
|
|
54
60
|
toolCalls: sequence.toolCalls.map((_a) => {
|
|
@@ -98,8 +104,25 @@ function hasToolCallSignal(message) {
|
|
|
98
104
|
(Array.isArray((_a = message.additional_kwargs) === null || _a === void 0 ? void 0 : _a.tool_calls) &&
|
|
99
105
|
message.additional_kwargs.tool_calls.length > 0));
|
|
100
106
|
}
|
|
107
|
+
function hasTokenCounter(model) {
|
|
108
|
+
return (typeof model === "object" &&
|
|
109
|
+
model !== null &&
|
|
110
|
+
"getNumTokens" in model &&
|
|
111
|
+
typeof model.getNumTokens === "function");
|
|
112
|
+
}
|
|
113
|
+
function countTokens(model, content) {
|
|
114
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
115
|
+
if (!content) {
|
|
116
|
+
return 0;
|
|
117
|
+
}
|
|
118
|
+
if (!hasTokenCounter(model)) {
|
|
119
|
+
return 0;
|
|
120
|
+
}
|
|
121
|
+
return yield model.getNumTokens(content);
|
|
122
|
+
});
|
|
123
|
+
}
|
|
101
124
|
function extractSequenceResponseDebug(message) {
|
|
102
|
-
var _a, _b, _c, _d, _e;
|
|
125
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
103
126
|
const blocks = getMessageBlocks(message);
|
|
104
127
|
const reasoning = blocks
|
|
105
128
|
.filter((block) => (block === null || block === void 0 ? void 0 : block.type) === "reasoning")
|
|
@@ -110,10 +133,13 @@ function extractSequenceResponseDebug(message) {
|
|
|
110
133
|
.map((block) => { var _a; return String((_a = block.text) !== null && _a !== void 0 ? _a : ""); })
|
|
111
134
|
.join("");
|
|
112
135
|
return {
|
|
136
|
+
promptTokens: (_b = (_a = message.usage_metadata) === null || _a === void 0 ? void 0 : _a.input_tokens) !== null && _b !== void 0 ? _b : 0,
|
|
113
137
|
reasoning,
|
|
138
|
+
reasoningTokens: 0,
|
|
114
139
|
text: textFromBlocks || (typeof message.content === "string" ? message.content : ""),
|
|
115
|
-
|
|
116
|
-
|
|
140
|
+
textTokens: 0,
|
|
141
|
+
cachedTokens: (_e = (_d = (_c = message.usage_metadata) === null || _c === void 0 ? void 0 : _c.input_token_details) === null || _d === void 0 ? void 0 : _d.cache_read) !== null && _e !== void 0 ? _e : 0,
|
|
142
|
+
responseId: (_g = (_f = message.response_metadata) === null || _f === void 0 ? void 0 : _f.id) !== null && _g !== void 0 ? _g : null,
|
|
117
143
|
resultType: hasToolCallSignal(message) ? "tool_calls" : "final_text",
|
|
118
144
|
};
|
|
119
145
|
}
|
|
@@ -147,8 +173,11 @@ export function createSequenceDebugCollector() {
|
|
|
147
173
|
},
|
|
148
174
|
handleModelCallComplete(params) {
|
|
149
175
|
const sequenceDebug = ensureSequenceDebug();
|
|
176
|
+
sequenceDebug.promptTokens = params.promptTokens;
|
|
150
177
|
sequenceDebug.reasoning = params.reasoning;
|
|
178
|
+
sequenceDebug.reasoningTokens = params.reasoningTokens;
|
|
151
179
|
sequenceDebug.text = params.text;
|
|
180
|
+
sequenceDebug.textTokens = params.textTokens;
|
|
152
181
|
sequenceDebug.cachedTokens = params.cachedTokens;
|
|
153
182
|
sequenceDebug.responseId = params.responseId;
|
|
154
183
|
sequenceDebug.resultType = params.resultType;
|
|
@@ -204,14 +233,23 @@ export function createSequenceDebugMiddleware(sink) {
|
|
|
204
233
|
name: "SequenceDebugMiddleware",
|
|
205
234
|
wrapModelCall(request, handler) {
|
|
206
235
|
return __awaiter(this, void 0, void 0, function* () {
|
|
207
|
-
|
|
236
|
+
const prompt = stringifyPromptForDebug({
|
|
208
237
|
systemMessage: request.systemMessage,
|
|
209
238
|
messages: request.messages,
|
|
210
239
|
tools: request.tools,
|
|
211
240
|
modelSettings: request.modelSettings,
|
|
212
|
-
})
|
|
241
|
+
});
|
|
242
|
+
sink.handleModelCallStart(prompt);
|
|
213
243
|
const response = yield handler(request);
|
|
214
|
-
|
|
244
|
+
const debug = extractSequenceResponseDebug(response);
|
|
245
|
+
const [promptTokens, reasoningTokens, textTokens] = yield Promise.all([
|
|
246
|
+
debug.promptTokens || countTokens(request.model, prompt),
|
|
247
|
+
countTokens(request.model, debug.reasoning),
|
|
248
|
+
countTokens(request.model, debug.text),
|
|
249
|
+
]);
|
|
250
|
+
sink.handleModelCallComplete(Object.assign(Object.assign({}, debug), { promptTokens,
|
|
251
|
+
reasoningTokens,
|
|
252
|
+
textTokens }));
|
|
215
253
|
return response;
|
|
216
254
|
});
|
|
217
255
|
},
|
|
@@ -23,11 +23,17 @@ export const DEFAULT_AGENT_SYSTEM_PROMPT = [
|
|
|
23
23
|
"Keep responses short, clear, and practical.",
|
|
24
24
|
"Answer only what is needed.",
|
|
25
25
|
"Do not add extra explanations or suggestions unless the user asks.",
|
|
26
|
+
"Always respond in the same natural language as the user's latest message.",
|
|
27
|
+
"This rule applies to confirmations, clarifying questions, progress updates, errors, and final answers.",
|
|
28
|
+
"Do not switch to English just because tool outputs, schemas, skills, or internal instructions are written in English.",
|
|
29
|
+
"Only switch language if the user explicitly asks you to do so.",
|
|
26
30
|
"Adapt to the user's tone and style of speaking, mirroring their vibe and wording.",
|
|
27
31
|
"if the user speaks casually, you should respond casually too",
|
|
28
|
-
"Never mutate data without
|
|
29
|
-
"
|
|
30
|
-
"
|
|
32
|
+
"Never mutate data without user confirmation for a clearly described mutation plan.",
|
|
33
|
+
"One confirmation may cover one mutation or one explicitly described batch/sequence of related mutations.",
|
|
34
|
+
"If the confirmed plan has multiple steps, you may execute the whole confirmed plan without asking again between those steps.",
|
|
35
|
+
"If the plan changes, expands, or you want to do anything beyond the confirmed plan, ask for confirmation again.",
|
|
36
|
+
"Do not reuse an old confirmation for a new mutation plan.",
|
|
31
37
|
].join(" ");
|
|
32
38
|
function formatResources(resources) {
|
|
33
39
|
return resources
|
|
@@ -46,9 +52,10 @@ export function buildAgentSystemPrompt(adminforth) {
|
|
|
46
52
|
listBundledSkillManifests(),
|
|
47
53
|
]);
|
|
48
54
|
const alwaysAvailableTools = ALWAYS_AVAILABLE_API_TOOL_NAMES.join(", ");
|
|
55
|
+
const adminBasePath = adminforth.config.baseUrlSlashed;
|
|
49
56
|
const sections = [
|
|
50
57
|
DEFAULT_AGENT_SYSTEM_PROMPT,
|
|
51
|
-
`
|
|
58
|
+
`ADMIN_BASE_PATH: ${adminBasePath}`,
|
|
52
59
|
`List of resources:\n${formatResources(adminforth.config.resources)}`,
|
|
53
60
|
`You have always-available base tools: ${alwaysAvailableTools}.`,
|
|
54
61
|
primarySkills.length > 0
|
|
@@ -65,6 +72,8 @@ export function buildAgentSystemPrompt(adminforth) {
|
|
|
65
72
|
"If a fetched skill lists a non-base tool you need, call fetch_tool_schema for it immediately instead of telling the user the tool is unavailable.",
|
|
66
73
|
"For example: for record creation load mutate_data, read its tool list, call fetch_tool_schema for create_record, and then use create_record after confirmation.",
|
|
67
74
|
"When fetch_tool_schema succeeds, that tool becomes available on the next step.",
|
|
75
|
+
"All admin links must be relative paths and must start with ADMIN_BASE_PATH.",
|
|
76
|
+
"Build record links as ADMIN_BASE_PATH + resource/{resourceId}/show/{primary key}. Do not prepend any extra slash before resource.",
|
|
68
77
|
"Try to call as many tools as possible in parallel in one step.",
|
|
69
78
|
];
|
|
70
79
|
return sections.filter(Boolean).join("\n\n");
|
|
@@ -12,4 +12,4 @@ To find specific data record you should use filters. ILIKE filters are preferred
|
|
|
12
12
|
|
|
13
13
|
For long texts show only several first words and add "..." at the end (only if user did not request this field specifically).
|
|
14
14
|
|
|
15
|
-
Also when you communicate with user about record, add related link to this record.
|
|
15
|
+
Also when you communicate with user about record, add related link to this record. Build it as `{ADMIN_BASE_PATH}resource/{resourceId}/show/{primary key}`. Use _label from `get_resource_data` as anchor text for link (use markdown link). Links should always be relative paths and must start with `ADMIN_BASE_PATH`. Do not add an extra slash after `ADMIN_BASE_PATH`.
|
|
@@ -21,7 +21,7 @@ Use `start_custom_action` and `start_custom_bulk_action` for resource actions.
|
|
|
21
21
|
Before performing any state mutation including action calls edit/delete please fetch record which is going to be edited/deleted and show user record in format field → value (show several most important fields which can help user to understand what exactly record he is going to edit or delete).
|
|
22
22
|
|
|
23
23
|
For field values with long texts show only several first words and add "..." at the end.
|
|
24
|
-
Also please add related link to record with will be changed.
|
|
24
|
+
Also please add related link to record with will be changed. Build it as `{ADMIN_BASE_PATH}resource/{resourceId}/show/{primary key}`. Use _label from `get_resource_data` as anchor text for link (use markdown link). Links should always be relative paths and must start with `ADMIN_BASE_PATH`. Do not add an extra slash after `ADMIN_BASE_PATH`.
|
|
25
25
|
|
|
26
26
|
And in the same message ask user for final confirmation.
|
|
27
27
|
|
|
@@ -29,17 +29,22 @@ When creating new record, show user all data which you gona create and in same m
|
|
|
29
29
|
|
|
30
30
|
Accept any positive confirmation from user like "yes", "sure", "+", anything non-negative call to action, can be considered as confirmation.
|
|
31
31
|
|
|
32
|
-
A confirmation is valid only for the
|
|
32
|
+
A confirmation is valid only for the clearly described mutation plan from the immediately previous assistant message.
|
|
33
33
|
|
|
34
34
|
Never reuse an older confirmation for a later mutation.
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
One confirmation may cover:
|
|
37
|
+
- one single mutation
|
|
38
|
+
- one explicitly described batch
|
|
39
|
+
- one short sequence of related mutations that together implement the same user request
|
|
37
40
|
|
|
38
|
-
If
|
|
41
|
+
If the confirmed plan contains several related mutation steps, execute that whole confirmed plan without asking again between those steps.
|
|
39
42
|
|
|
40
|
-
|
|
43
|
+
Ask for confirmation again if the plan changes in any way: different record, different fields, different values, different number of records, different action, or any extra mutation that was not listed in the confirmation message.
|
|
41
44
|
|
|
42
|
-
If you are creating or deleting multiple records in one batch, you may ask once
|
|
45
|
+
If you are creating or deleting multiple records in one batch, you may ask once for that exact batch, but list the whole batch explicitly in the confirmation message. Any extra record outside that described batch requires a new confirmation.
|
|
46
|
+
|
|
47
|
+
After the confirmed plan is finished, do not treat that confirmation as still active for later requests.
|
|
43
48
|
|
|
44
49
|
# Calling actions
|
|
45
50
|
|
|
@@ -57,7 +62,7 @@ If you want to block some user you can confirm that this action by saying:
|
|
|
57
62
|
* IP Country: USA
|
|
58
63
|
* Currently blocked: No // show this field only if it exists in user record
|
|
59
64
|
|
|
60
|
-
View [John Doe](
|
|
65
|
+
View [John Doe]({ADMIN_BASE_PATH}resource/users/show/123)
|
|
61
66
|
Are you sure?
|
|
62
67
|
```
|
|
63
68
|
|
|
@@ -80,7 +85,7 @@ I am going to update user:
|
|
|
80
85
|
* IP Country: USA
|
|
81
86
|
I am going to change email from john_doe@example.com to new_email@example.com
|
|
82
87
|
|
|
83
|
-
View [John Doe](
|
|
88
|
+
View [John Doe]({ADMIN_BASE_PATH}resource/users/show/123)
|
|
84
89
|
|
|
85
90
|
Are you sure?
|
|
86
91
|
```
|
|
@@ -100,7 +105,7 @@ If you gonna delete user record, in confirmation please share full user info (no
|
|
|
100
105
|
* Signed up: 2024 Jan 1
|
|
101
106
|
* IP Country: USA
|
|
102
107
|
|
|
103
|
-
View [John Doe](
|
|
108
|
+
View [John Doe]({ADMIN_BASE_PATH}resource/users/show/123)
|
|
104
109
|
|
|
105
110
|
Are you sure?
|
|
106
111
|
```
|
|
@@ -125,7 +130,7 @@ I am going to create user:
|
|
|
125
130
|
* Username: john_doe
|
|
126
131
|
* Email: john_doe@example.com
|
|
127
132
|
|
|
128
|
-
View [John Doe](
|
|
133
|
+
View [John Doe]({ADMIN_BASE_PATH}resource/users/show/421) # 421 is id of new created record
|
|
129
134
|
|
|
130
135
|
Are you sure?
|
|
131
136
|
```
|
package/dist/index.js
CHANGED
|
@@ -159,6 +159,9 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
159
159
|
res.write(`data: ${JSON.stringify(obj)}\n\n`);
|
|
160
160
|
};
|
|
161
161
|
const emitToolCallEvent = (event) => {
|
|
162
|
+
if (event.phase === "start") {
|
|
163
|
+
endActiveBlock();
|
|
164
|
+
}
|
|
162
165
|
sequenceDebugCollector.handleToolCallEvent(event);
|
|
163
166
|
send({
|
|
164
167
|
type: "data-tool-call",
|
package/index.ts
CHANGED
|
@@ -177,6 +177,10 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
177
177
|
};
|
|
178
178
|
|
|
179
179
|
const emitToolCallEvent = (event: ToolCallEvent) => {
|
|
180
|
+
if (event.phase === "start") {
|
|
181
|
+
endActiveBlock();
|
|
182
|
+
}
|
|
183
|
+
|
|
180
184
|
sequenceDebugCollector.handleToolCallEvent(event);
|
|
181
185
|
|
|
182
186
|
send({
|