Refactor workflow agent and routing components for improved functionality

- Introduce WorkflowAgentStage to manage agent stage configurations and enhance interaction with the workflow engine.
- Implement WorkflowEdgeEvaluator for priority-aware edge evaluation, improving routing decisions based on conditions and user turns.
- Update WorkflowBrain to handle user turns and routing more effectively, ensuring agents cannot have only one default path.
- Enhance CallEndCoordinator to track speech events and manage call termination based on queued speech.
- Add new models and output handling for workflow interactions, improving clarity and maintainability.
- Update tests to validate the new routing logic and agent behavior under various scenarios.
This commit is contained in:
Xin Wang
2026-07-17 22:37:15 +08:00
parent 162a3d8bec
commit bdf3d3dd9c
23 changed files with 1374 additions and 475 deletions
@@ -1,14 +1,19 @@
"use client";
import { useState } from "react";
import { useCallback, useMemo, useState } from "react";
import type { WorkflowSettings } from "@/components/workflow/types";
import { defaultGraph, type WorkflowGraph } from "@/components/workflow/specs";
import type { DynamicVariableDefinition } from "@/lib/api";
import { defaultTurnConfig } from "@/lib/turn-config";
function initialWorkflowSettings(): WorkflowSettings {
const graph = defaultGraph();
type WorkflowPanel =
| { type: "debug" }
| { type: "settings" }
| { type: "node"; nodeId: string }
| { type: "edge"; edgeId: string }
| null;
function settingsFromGraph(graph: WorkflowGraph): WorkflowSettings {
return {
globalPrompt: graph.settings.globalPrompt,
llm: graph.settings.defaultLlmResourceId,
@@ -21,8 +26,8 @@ function initialWorkflowSettings(): WorkflowSettings {
topN: graph.settings.knowledgeTopN,
scoreThreshold: graph.settings.knowledgeScoreThreshold,
},
allowInterrupt: true,
turnConfig: defaultTurnConfig(),
allowInterrupt: graph.settings.enableInterrupt,
turnConfig: graph.settings.turnConfig,
};
}
@@ -32,19 +37,67 @@ export function useWorkflowEditorState() {
const [workflowGraph, setWorkflowGraph] = useState<WorkflowGraph>(() =>
defaultGraph(),
);
const [workflowSettings, setWorkflowSettings] = useState<WorkflowSettings>(
initialWorkflowSettings,
const workflowSettings = useMemo(
() => settingsFromGraph(workflowGraph),
[workflowGraph],
);
const setWorkflowSettings = useCallback((settings: WorkflowSettings) => {
setWorkflowGraph((graph) => ({
...graph,
settings: {
globalPrompt: settings.globalPrompt,
defaultLlmResourceId: settings.llm ?? "",
defaultAsrResourceId: settings.asr ?? "",
defaultTtsResourceId: settings.tts ?? "",
toolIds: settings.toolIds,
knowledgeBaseId: settings.knowledgeBaseId,
knowledgeMode: settings.knowledgeRetrievalConfig.mode,
knowledgeTopN: settings.knowledgeRetrievalConfig.topN,
knowledgeScoreThreshold:
settings.knowledgeRetrievalConfig.scoreThreshold,
enableInterrupt: settings.allowInterrupt,
turnConfig: settings.turnConfig,
},
}));
}, []);
const [workflowDynamicVariableDefinitions, setWorkflowDynamicVariableDefinitions] =
useState<Record<string, DynamicVariableDefinition>>({});
const [workflowDebugOpen, setWorkflowDebugOpen] = useState(false);
const [workflowSettingsOpen, setWorkflowSettingsOpen] = useState(false);
const [workflowEditingNodeId, setWorkflowEditingNodeId] = useState<string | null>(
null,
);
const [workflowEditingEdgeId, setWorkflowEditingEdgeId] = useState<string | null>(
null,
);
const [workflowPanel, setWorkflowPanel] = useState<WorkflowPanel>(null);
const workflowDebugOpen = workflowPanel?.type === "debug";
const workflowSettingsOpen = workflowPanel?.type === "settings";
const workflowEditingNodeId =
workflowPanel?.type === "node" ? workflowPanel.nodeId : null;
const workflowEditingEdgeId =
workflowPanel?.type === "edge" ? workflowPanel.edgeId : null;
const setWorkflowDebugOpen = useCallback((open: boolean) => {
setWorkflowPanel((panel) =>
open ? { type: "debug" } : panel?.type === "debug" ? null : panel,
);
}, []);
const setWorkflowSettingsOpen = useCallback((open: boolean) => {
setWorkflowPanel((panel) =>
open ? { type: "settings" } : panel?.type === "settings" ? null : panel,
);
}, []);
const setWorkflowEditingNodeId = useCallback((nodeId: string | null) => {
setWorkflowPanel((panel) =>
nodeId
? { type: "node", nodeId }
: panel?.type === "node"
? null
: panel,
);
}, []);
const setWorkflowEditingEdgeId = useCallback((edgeId: string | null) => {
setWorkflowPanel((panel) =>
edgeId
? { type: "edge", edgeId }
: panel?.type === "edge"
? null
: panel,
);
}, []);
const [activeNodeId, setActiveNodeId] = useState<string | null>(null);
const [dynamicVariablesOpen, setDynamicVariablesOpen] = useState(false);
@@ -65,6 +118,7 @@ export function useWorkflowEditorState() {
setWorkflowEditingNodeId,
workflowEditingEdgeId,
setWorkflowEditingEdgeId,
workflowPanel,
activeNodeId,
setActiveNodeId,
dynamicVariablesOpen,