mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-06-06 19:35:44 +02:00
Move max_calls_per_parent_child to agent config in the UI
This commit is contained in:
parent
d01248efb1
commit
4df6d832c2
10 changed files with 209 additions and 314 deletions
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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 && (
|
||||
|
|
|
|||
|
|
@ -270,6 +270,7 @@ function reducer(state: State, action: Action): State {
|
|||
ragK: 3,
|
||||
controlType: "retain",
|
||||
outputVisibility: "user_facing",
|
||||
maxCallsPerParentAgent: 3,
|
||||
...action.agent
|
||||
});
|
||||
draft.selection = {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue