Move max_calls_per_parent_child to agent config in the UI

This commit is contained in:
akhisud3195 2025-05-08 21:34:46 +05:30
parent d01248efb1
commit 4df6d832c2
10 changed files with 209 additions and 314 deletions

View file

@ -40,6 +40,7 @@ export function validateConfigChanges(configType: string, configChanges: Record<
connectedAgents: [],
controlType: 'retain',
outputVisibility: 'user_facing',
maxCallsPerParentAgent: 3,
} as z.infer<typeof WorkflowAgent>;
schema = WorkflowAgent;
break;

View file

@ -96,6 +96,7 @@ export function convertWorkflowToAgenticAPI(workflow: z.infer<typeof Workflow>):
tools: entities.filter(e => e.type == 'tool').map(e => e.name),
prompts: entities.filter(e => e.type == 'prompt').map(e => e.name),
connectedAgents: entities.filter(e => e.type === 'agent').map(e => e.name),
maxCallsPerParentAgent: agent.maxCallsPerParentAgent,
};
return agenticAgent;
}),

View file

@ -17,8 +17,9 @@ export const WorkflowAgent = z.object({
ragDataSources: z.array(z.string()).optional(),
ragReturnType: z.union([z.literal('chunks'), z.literal('content')]).default('chunks'),
ragK: z.number().default(3),
outputVisibility: z.union([z.literal('user_facing'), z.literal('internal')]).default('user_facing'),
outputVisibility: z.union([z.literal('user_facing'), z.literal('internal')]).default('user_facing').optional(),
controlType: z.union([z.literal('retain'), z.literal('relinquish_to_parent'), z.literal('relinquish_to_start')]).default('retain').describe('Whether this agent retains control after a turn, relinquishes to the parent agent, or relinquishes to the start agent'),
maxCallsPerParentAgent: z.number().default(3).describe('Maximum number of times this agent can be called by a parent agent in a single turn').optional(),
});
export const WorkflowPrompt = z.object({
name: z.string(),

View file

@ -634,13 +634,46 @@ export function AgentConfig({
</div>
</div>
</div>
<Input
value={agent.model}
onChange={(e) => handleUpdate({
...agent,
model: e.target.value as z.infer<typeof WorkflowAgent>['model']
})}
/>
<div className="w-full">
<Input
value={agent.model}
onChange={(e) => handleUpdate({
...agent,
model: e.target.value as z.infer<typeof WorkflowAgent>['model']
})}
className="w-full max-w-64"
/>
</div>
</div>
<div className="space-y-4">
<div className="flex items-center">
<label className={sectionHeaderStyles}>
Max calls from parent agent per turn
</label>
<div className="relative ml-2 group">
<Info
className="w-4 h-4 text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 cursor-pointer transition-colors"
/>
<div className="absolute bottom-full left-0 mb-2 p-3 w-80 rounded-lg shadow-lg border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300 text-xs invisible group-hover:visible z-50">
<div className="mb-1 font-medium">Max Calls Configuration</div>
This setting limits how many times a parent agent can call this agent in a single turn, to prevent infinite loops.
<div className="absolute h-2 w-2 bg-white dark:bg-gray-800 transform rotate-45 -bottom-1 left-4 border-r border-b border-gray-200 dark:border-gray-700"></div>
</div>
</div>
</div>
<div className="w-full">
<Input
type="number"
min="1"
value={agent.maxCallsPerParentAgent || 3}
onChange={(e) => handleUpdate({
...agent,
maxCallsPerParentAgent: parseInt(e.target.value)
})}
className="w-full max-w-24"
/>
</div>
</div>
{USE_TRANSFER_CONTROL_OPTIONS && (

View file

@ -270,6 +270,7 @@ function reducer(state: State, action: Action): State {
ragK: 3,
controlType: "retain",
outputVisibility: "user_facing",
maxCallsPerParentAgent: 3,
...action.agent
});
draft.selection = {

View file

@ -1,131 +0,0 @@
import { z } from "zod";
export const WorkflowAgent = z.object({
name: z.string(),
type: z.union([
z.literal('conversation'),
z.literal('post_process'),
z.literal('escalation'),
]),
description: z.string(),
disabled: z.boolean().default(false).optional(),
instructions: z.string(),
examples: z.string().optional(),
model: z.union([
z.literal('gpt-4o'),
z.literal('gpt-4o-mini'),
]),
locked: z.boolean().default(false).describe('Whether this agent is locked and cannot be deleted').optional(),
toggleAble: z.boolean().default(true).describe('Whether this agent can be enabled or disabled').optional(),
global: z.boolean().default(false).describe('Whether this agent is a global agent, in which case it cannot be connected to other agents').optional(),
ragDataSources: z.array(z.string()).optional(),
ragReturnType: z.union([z.literal('chunks'), z.literal('content')]).default('chunks'),
ragK: z.number().default(3),
controlType: z.union([z.literal('retain'), z.literal('relinquish_to_parent'), z.literal('relinquish_to_start')]).default('retain').describe('Whether this agent retains control after a turn, relinquishes to the parent agent, or relinquishes to the start agent'),
});
export const WorkflowPrompt = z.object({
name: z.string(),
type: z.union([
z.literal('base_prompt'),
z.literal('style_prompt'),
z.literal('greeting'),
]),
prompt: z.string(),
});
export const WorkflowTool = z.object({
name: z.string(),
description: z.string(),
mockTool: z.boolean().default(false).optional(),
autoSubmitMockedResponse: z.boolean().default(false).optional(),
mockInstructions: z.string().optional(),
parameters: z.object({
type: z.literal('object'),
properties: z.record(z.object({
type: z.string(),
description: z.string(),
})),
required: z.array(z.string()).optional(),
}),
isMcp: z.boolean().default(false).optional(),
mcpServerName: z.string().optional(),
});
export const Workflow = z.object({
name: z.string().optional(),
agents: z.array(WorkflowAgent),
prompts: z.array(WorkflowPrompt),
tools: z.array(WorkflowTool),
startAgent: z.string(),
createdAt: z.string().datetime(),
lastUpdatedAt: z.string().datetime(),
projectId: z.string(),
});
export const WorkflowTemplate = Workflow
.omit({
projectId: true,
lastUpdatedAt: true,
createdAt: true,
})
.extend({
name: z.string(),
description: z.string(),
});
export const ConnectedEntity = z.object({
type: z.union([z.literal('tool'), z.literal('prompt'), z.literal('agent')]),
name: z.string(),
});
export function sanitizeTextWithMentions(
text: string,
workflow: {
agents: z.infer<typeof WorkflowAgent>[],
tools: z.infer<typeof WorkflowTool>[],
prompts: z.infer<typeof WorkflowPrompt>[],
},
): {
sanitized: string;
entities: z.infer<typeof ConnectedEntity>[];
} {
// Regex to match [@type:name](#type:something) pattern where type is tool/prompt/agent
const mentionRegex = /\[@(tool|prompt|agent):([^\]]+)\]\(#mention\)/g;
const seen = new Set<string>();
// collect entities
const entities = Array
.from(text.matchAll(mentionRegex))
.filter(match => {
if (seen.has(match[0])) {
return false;
}
seen.add(match[0]);
return true;
})
.map(match => {
return {
type: match[1] as 'tool' | 'prompt' | 'agent',
name: match[2],
};
})
.filter(entity => {
seen.add(entity.name);
if (entity.type === 'agent') {
return workflow.agents.some(a => a.name === entity.name);
} else if (entity.type === 'tool') {
return workflow.tools.some(t => t.name === entity.name);
} else if (entity.type === 'prompt') {
return workflow.prompts.some(p => p.name === entity.name);
}
return false;
})
// sanitize text
for (const entity of entities) {
const id = `${entity.type}:${entity.name}`;
const textToReplace = `[@${id}](#mention)`;
text = text.replace(textToReplace, `[@${id}]`);
}
return {
sanitized: text,
entities,
};
}