Skip to content

Commit ea43560

Browse files
committed
feat: enhance API tool handling and context management in middleware
1 parent 080bcc5 commit ea43560

5 files changed

Lines changed: 135 additions & 13 deletions

File tree

agent/middleware/apiBasedTools.ts

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import { ToolMessage } from "@langchain/core/messages";
22
import { createMiddleware } from "langchain";
3-
import { logger } from "adminforth";
4-
import { type ApiBasedTool } from "../../apiBasedTools.js";
3+
import { logger, type AdminUser, type IAdminForth } from "adminforth";
4+
import {
5+
formatApiBasedToolCall,
6+
type ApiBasedTool,
7+
} from "../../apiBasedTools.js";
58
import {
69
createToolCallTracker,
710
type ToolCallEventSink,
@@ -46,6 +49,7 @@ function getEnabledApiToolNames(messages: unknown[]) {
4649

4750
export function createApiBasedToolsMiddleware(
4851
apiBasedTools: Record<string, ApiBasedTool>,
52+
adminforth: IAdminForth,
4953
) {
5054
const alwaysAvailableApiToolNames = new Set<string>(ALWAYS_AVAILABLE_API_TOOL_NAMES);
5155
const dynamicTools = Object.fromEntries(
@@ -71,14 +75,33 @@ export function createApiBasedToolsMiddleware(
7175
async wrapToolCall(request, handler) {
7276
const startedAt = Date.now();
7377
const toolInput = JSON.stringify(request.toolCall.args ?? {});
74-
const { emitToolCallEvent } = request.runtime.context as {
78+
const { adminUser, emitToolCallEvent, userTimeZone } = request.runtime.context as {
79+
adminUser: AdminUser;
7580
emitToolCallEvent: ToolCallEventSink;
81+
userTimeZone: string;
7682
};
83+
const toolArgs = (request.toolCall.args ?? {}) as Record<string, unknown>;
84+
let toolInfo: string | undefined;
85+
86+
if (request.toolCall.name === "fetch_skill") {
87+
toolInfo = `Load ${(toolArgs.skillName as string).split("_").join(" ")} skill`;
88+
} else if (request.toolCall.name === "fetch_tool_schema") {
89+
toolInfo = `Load ${(toolArgs.toolName as string).split("_").join(" ")} tool `;
90+
} else {
91+
toolInfo = await formatApiBasedToolCall({
92+
adminforth,
93+
adminUser,
94+
inputs: toolArgs,
95+
toolName: request.toolCall.name,
96+
userTimeZone,
97+
});
98+
}
7799
const toolCallTracker = createToolCallTracker({
78100
emit: emitToolCallEvent,
79101
toolCallId: request.toolCall.id,
80102
toolName: request.toolCall.name,
81-
input: (request.toolCall.args ?? {}) as Record<string, unknown>,
103+
toolInfo,
104+
input: toolArgs,
82105
startedAt,
83106
});
84107
toolCallTracker.start();

agent/simpleAgent.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import { createAgent, summarizationMiddleware } from "langchain";
2-
import { logger, type AdminUser, type CompletionAdapter } from "adminforth";
2+
import {
3+
logger,
4+
type AdminUser,
5+
type CompletionAdapter,
6+
type IAdminForth,
7+
} from "adminforth";
38
import { BaseCallbackHandler } from "@langchain/core/callbacks/base";
49
import {type BaseCheckpointSaver, type Messages } from "@langchain/langgraph";
510
import type { LLMResult } from "@langchain/core/outputs";
@@ -219,6 +224,7 @@ export async function callAgent(params: {
219224
checkpointer?: BaseCheckpointSaver;
220225
messages: Messages;
221226
adminUser: AdminUser;
227+
adminforth: IAdminForth;
222228
apiBasedTools: Record<string, ApiBasedTool>;
223229
customComponentsDir: string;
224230
sessionId: string;
@@ -234,6 +240,7 @@ export async function callAgent(params: {
234240
checkpointer,
235241
messages,
236242
adminUser,
243+
adminforth,
237244
apiBasedTools,
238245
customComponentsDir,
239246
sessionId,
@@ -244,7 +251,7 @@ export async function callAgent(params: {
244251
} = params;
245252

246253
const tools = await createAgentTools(customComponentsDir, apiBasedTools);
247-
const apiBasedToolsMiddleware = createApiBasedToolsMiddleware(apiBasedTools);
254+
const apiBasedToolsMiddleware = createApiBasedToolsMiddleware(apiBasedTools, adminforth);
248255
const openAiResponsesContinuationMiddleware =
249256
createOpenAiResponsesContinuationMiddleware();
250257
const sequenceDebugMiddleware = createSequenceDebugMiddleware(

agent/toolCallEvents.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export type ToolCallEvent =
66
| {
77
toolCallId: string;
88
toolName: string;
9+
toolInfo?: string;
910
phase: "start";
1011
input: string;
1112
}
@@ -64,6 +65,7 @@ export function createToolCallTracker(params: {
6465
emit: ToolCallEventSink;
6566
toolCallId?: string;
6667
toolName: string;
68+
toolInfo?: string;
6769
input?: Record<string, unknown>;
6870
startedAt?: number;
6971
}) {
@@ -75,6 +77,7 @@ export function createToolCallTracker(params: {
7577
params.emit({
7678
toolCallId,
7779
toolName: params.toolName,
80+
toolInfo: params.toolInfo,
7881
phase: "start",
7982
input: YAML.stringify(params.input ?? {}),
8083
});

apiBasedTools.ts

Lines changed: 95 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -65,10 +65,11 @@ type ToolHttpResponse = IAdminForthHttpResponse & {
6565
type ToolOverrideCallParams = Pick<ApiBasedToolCallParams, 'httpExtra' | 'inputs' | 'userTimeZone'>;
6666

6767
type ToolOverrideContext = {
68-
output: unknown;
68+
output?: unknown;
6969
adminUser?: AdminUser;
7070
httpExtra?: Partial<HttpExtra>;
7171
inputs?: Record<string, unknown>;
72+
resourceLabel?: string;
7273
userTimeZone?: string;
7374
invokeTool: (toolName: string, params?: ToolOverrideCallParams) => Promise<unknown>;
7475
};
@@ -98,6 +99,42 @@ type DateTimeColumnType = AdminForthDataTypes.DATETIME | AdminForthDataTypes.TIM
9899

99100
const DEFAULT_USER_TIME_ZONE = 'UTC';
100101

102+
function getInputString(inputs: Record<string, unknown> | undefined, key: string) {
103+
const value = inputs?.[key];
104+
105+
return typeof value === 'string' && value ? value : undefined;
106+
}
107+
108+
function getInputArrayLength(inputs: Record<string, unknown> | undefined, key: string) {
109+
const value = inputs?.[key];
110+
111+
return Array.isArray(value) ? value.length : undefined;
112+
}
113+
114+
function resourceLabel(adminforth: IAdminForth, inputs: Record<string, unknown> | undefined) {
115+
const resourceId = getInputString(inputs, 'resourceId');
116+
const resource = adminforth.config.resources.find((res) => res.resourceId === resourceId);
117+
118+
return resource?.label ?? resourceId ?? 'resource';
119+
}
120+
121+
function getDataPrefix(inputs: Record<string, unknown> | undefined) {
122+
const offset = typeof inputs?.offset === 'number' ? inputs.offset : undefined;
123+
const limit = typeof inputs?.limit === 'number' ? inputs.limit : undefined;
124+
125+
if (offset !== undefined && limit !== undefined) {
126+
return `${offset}-${offset + limit} `;
127+
}
128+
129+
return limit === undefined ? '' : `${limit} `;
130+
}
131+
132+
function actionText(inputs: Record<string, unknown> | undefined) {
133+
const actionId = getInputString(inputs, 'actionId');
134+
135+
return actionId ? ` action ${actionId}` : ' action';
136+
}
137+
101138
const TOOL_OVERRIDES: Record<string, ToolOverride> = {
102139
get_resource: {
103140
wipe_frontend_specific_data: [
@@ -106,11 +143,12 @@ const TOOL_OVERRIDES: Record<string, ToolOverride> = {
106143
'resource.options.actions[].customComponent',
107144
'resource.options.pageInjections',
108145
],
109-
format_tool: async ({ }) => {
110-
return "get resource Apartments"
111-
}
146+
format_tool: ({ resourceLabel }) => `Get ${resourceLabel} resource`,
112147
},
113148
get_resource_data: {
149+
format_tool: ({ inputs, resourceLabel }) => (
150+
`Get ${getDataPrefix(inputs)}${resourceLabel}`
151+
),
114152
post_process_response: async ({ output, inputs, invokeTool, userTimeZone }) => {
115153
if (hasToolError(output)) {
116154
return output;
@@ -132,9 +170,37 @@ const TOOL_OVERRIDES: Record<string, ToolOverride> = {
132170

133171
return response;
134172
},
135-
format_tool: async ({ }) => {
136-
return "get 1-20 Apartment filtered listed=yes"
137-
}
173+
},
174+
aggregate: {
175+
format_tool: ({ resourceLabel }) => `Aggregate ${resourceLabel}`,
176+
},
177+
start_custom_action: {
178+
format_tool: ({ inputs, resourceLabel }) => `Run ${resourceLabel}${actionText(inputs)}`,
179+
},
180+
start_custom_bulk_action: {
181+
format_tool: ({ inputs, resourceLabel }) => {
182+
const recordCount = getInputArrayLength(inputs, 'recordIds');
183+
const recordsText = recordCount === undefined ? '' : ` for ${recordCount} records`;
184+
185+
return `Run ${resourceLabel}${actionText(inputs)}${recordsText}`;
186+
},
187+
},
188+
start_bulk_action: {
189+
format_tool: ({ inputs, resourceLabel }) => {
190+
const recordCount = getInputArrayLength(inputs, 'recordIds');
191+
const recordsText = recordCount === undefined ? '' : ` for ${recordCount} records`;
192+
193+
return `Run ${resourceLabel}${actionText(inputs)}${recordsText}`;
194+
},
195+
},
196+
create_record: {
197+
format_tool: ({ resourceLabel }) => `Create ${resourceLabel}`,
198+
},
199+
update_record: {
200+
format_tool: ({ resourceLabel }) => `Update ${resourceLabel}`,
201+
},
202+
delete_record: {
203+
format_tool: ({ resourceLabel }) => `Delete ${resourceLabel}`,
138204
},
139205
};
140206

@@ -401,6 +467,28 @@ function endpointPathToToolName(path: string) {
401467
.replace(/^_+|_+$/g, '');
402468
}
403469

470+
export async function formatApiBasedToolCall(params: {
471+
adminforth: IAdminForth;
472+
adminUser?: AdminUser;
473+
httpExtra?: Partial<HttpExtra>;
474+
inputs?: Record<string, unknown>;
475+
toolName: string;
476+
userTimeZone?: string;
477+
}) {
478+
const formatTool = TOOL_OVERRIDES[params.toolName]?.format_tool;
479+
480+
return await formatTool?.({
481+
adminUser: params.adminUser,
482+
httpExtra: params.httpExtra,
483+
inputs: params.inputs,
484+
resourceLabel: resourceLabel(params.adminforth, params.inputs),
485+
userTimeZone: params.userTimeZone,
486+
invokeTool: async () => {
487+
throw new Error('Tool info formatting cannot invoke tools');
488+
},
489+
});
490+
}
491+
404492
function normalizeCookies(cookies?: Partial<HttpExtra>['cookies']): CookieItem[] {
405493
if (!cookies) {
406494
return [];

index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,7 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
338338
new HumanMessage(prompt),
339339
],
340340
adminUser,
341+
adminforth: this.adminforth,
341342
apiBasedTools: this.apiBasedTools,
342343
customComponentsDir: this.adminforth.config.customization.customComponentsDir,
343344
sessionId,

0 commit comments

Comments
 (0)