Enhance conversation history and runtime variable management

- Update ConversationRecorder to include source and nodeId metadata in transcripts for better context tracking.
- Introduce optional variable handling in DynamicVariableStore, allowing for unset variables to be rendered as empty without raising errors.
- Refactor WorkflowBrain to apply turn configurations and manage interaction policies dynamically, improving agent responsiveness.
- Implement tests to ensure proper handling of updated session variables and workflow metadata in various scenarios.
This commit is contained in:
Xin Wang
2026-07-14 11:08:11 +08:00
parent 665f727796
commit f74040adf3
18 changed files with 848 additions and 194 deletions
@@ -56,14 +56,27 @@ export function GenericNode({ id, type, data, selected }: NodeProps) {
<div
data-node-id={id}
className={cn(
"group relative w-[250px] rounded-2xl border bg-card p-4 text-card-foreground shadow-sm transition-[border-color,box-shadow,transform]",
"group relative isolate w-[250px] rounded-2xl border bg-card p-4 text-card-foreground shadow-sm transition-[border-color,box-shadow,transform]",
isActive
? "border-success shadow-[0_12px_34px_color-mix(in_srgb,var(--success)_20%,transparent)] ring-2 ring-success/50"
? "border-success/90 shadow-[0_0_0_1px_color-mix(in_srgb,var(--success)_38%,transparent),0_0_18px_color-mix(in_srgb,var(--success)_38%,transparent),0_0_46px_color-mix(in_srgb,var(--success)_22%,transparent),0_16px_38px_color-mix(in_srgb,var(--success)_18%,transparent)] ring-1 ring-success/60"
: selected
? "border-primary shadow-[0_12px_34px_color-mix(in_srgb,var(--primary)_16%,transparent)]"
: "border-hairline hover:border-hairline-strong hover:shadow-md",
)}
>
{isActive && (
<>
<div
aria-hidden
className="pointer-events-none absolute -inset-1 rounded-[1.2rem] border border-success/35 opacity-80 motion-safe:animate-pulse"
/>
<div
aria-hidden
className="pointer-events-none absolute inset-x-5 -bottom-px h-px bg-gradient-to-r from-transparent via-success to-transparent shadow-[0_0_12px_var(--success)]"
/>
</>
)}
{spec.hasTarget && (
<Handle
type="target"
@@ -81,9 +94,12 @@ export function GenericNode({ id, type, data, selected }: NodeProps) {
/>
{isActive && (
<div className="absolute -top-3 left-3 flex items-center gap-1.5 rounded-full bg-success px-2 py-0.5 text-[10px] font-medium text-on-primary shadow-sm">
<span className="h-1.5 w-1.5 animate-pulse rounded-full bg-on-primary" />
对话中
<div className="absolute -top-3 left-3 flex items-center gap-2 rounded-full border border-success/60 bg-card/95 px-2.5 py-1 text-[10px] font-semibold tracking-[0.14em] text-success shadow-[0_0_16px_color-mix(in_srgb,var(--success)_42%,transparent)] backdrop-blur-sm">
<span className="relative flex h-2 w-2">
<span className="absolute inline-flex h-full w-full rounded-full bg-success opacity-55 motion-safe:animate-ping" />
<span className="relative inline-flex h-2 w-2 rounded-full bg-success shadow-[0_0_8px_var(--success)]" />
</span>
LIVE · 对话中
</div>
)}
@@ -78,6 +78,7 @@ import {
import { Switch } from "@/components/ui/switch";
import type { KnowledgeRetrievalConfig, TurnConfig } from "@/lib/api";
import { TurnConfigEditor } from "@/components/turn-config-editor";
import { normalizeTurnConfig } from "@/lib/turn-config";
import { Textarea } from "@/components/ui/textarea";
import {
Tooltip,
@@ -195,6 +196,8 @@ function fromFlow(nodes: Node[], edges: Edge[]): WorkflowGraph {
knowledgeMode: "automatic",
knowledgeTopN: 5,
knowledgeScoreThreshold: 0,
enableInterrupt: true,
turnConfig: defaultGraph().settings.turnConfig,
},
nodes: nodes.map((n) => ({
id: n.id,
@@ -261,6 +264,8 @@ function Canvas({
knowledgeTopN: settings.knowledgeRetrievalConfig.topN,
knowledgeScoreThreshold:
settings.knowledgeRetrievalConfig.scoreThreshold,
enableInterrupt: settings.allowInterrupt,
turnConfig: settings.turnConfig,
};
onChangeRef.current?.(graph);
}, [
@@ -273,6 +278,8 @@ function Canvas({
settings.toolIds,
settings.knowledgeBaseId,
settings.knowledgeRetrievalConfig,
settings.allowInterrupt,
settings.turnConfig,
]);
const onConnect = useCallback(
@@ -1519,6 +1526,9 @@ function AgentPanelForm({
topN: Number(draft.knowledgeTopN ?? 5),
scoreThreshold: Number(draft.knowledgeScoreThreshold ?? 0),
};
const agentTurnConfig = normalizeTurnConfig(
draft.turnConfig ?? workflowSettings.turnConfig,
);
const setInheritance = (inheritGlobalConfig: boolean) => {
if (inheritGlobalConfig) {
@@ -1550,6 +1560,9 @@ function AgentPanelForm({
knowledgeScoreThreshold:
draft.knowledgeScoreThreshold ??
workflowSettings.knowledgeRetrievalConfig.scoreThreshold,
enableInterrupt:
draft.enableInterrupt ?? workflowSettings.allowInterrupt,
turnConfig: agentTurnConfig,
});
};
@@ -1592,10 +1605,10 @@ function AgentPanelForm({
<SectionCard
icon={<MessageSquareText size={15} />}
title="提示词"
title={inheritsGlobal ? "任务" : "提示词"}
description={
inheritsGlobal
? "描述当前阶段任务,并与工作流全局提示词合并"
? "描述当前阶段要完成的目标;角色、能力和通用规则继承工作流全局配置"
: "描述当前独立助手的角色、能力和回答要求"
}
>
@@ -1603,7 +1616,11 @@ function AgentPanelForm({
rows={8}
value={draft.prompt ?? ""}
onChange={(event) => set("prompt", event.target.value)}
placeholder="请输入提示词,描述助手的角色、能力和回答要求"
placeholder={
inheritsGlobal
? "例如:确认用户身份,并收集需要查询的订单编号"
: "请输入提示词,描述助手的角色、能力和回答要求"
}
className="field-sizing-fixed min-h-28 resize-y border-hairline-strong bg-background text-sm text-foreground placeholder:text-muted-soft"
/>
</SectionCard>
@@ -1714,6 +1731,23 @@ function AgentPanelForm({
onChange={(toolIds) => set("toolIds", toolIds)}
/>
</SectionCard>
<SectionCard
icon={<Sparkles size={15} />}
title="交互策略"
description="配置当前 Agent 独立使用的打断和轮次检测策略"
>
<TurnConfigEditor
enabled={
draft.enableInterrupt ?? workflowSettings.allowInterrupt
}
config={agentTurnConfig}
onEnabledChange={(enableInterrupt) =>
set("enableInterrupt", enableInterrupt)
}
onConfigChange={(turnConfig) => set("turnConfig", turnConfig)}
/>
</SectionCard>
</>
)}
</div>
+8 -1
View File
@@ -3,7 +3,8 @@
import * as LucideIcons from "lucide-react";
import { Circle, type LucideIcon } from "lucide-react";
import type { NodeSpecDto } from "@/lib/api";
import type { NodeSpecDto, TurnConfig } from "@/lib/api";
import { defaultTurnConfig } from "@/lib/turn-config";
export type WorkflowNodeType = "start" | "agent" | "action" | "handoff" | "end";
export type ContextPolicy = "inherit" | "fresh";
@@ -37,6 +38,8 @@ export type WorkflowNodeData = {
llmResourceId?: string;
asrResourceId?: string;
ttsResourceId?: string;
enableInterrupt?: boolean;
turnConfig?: TurnConfig;
toolId?: string;
arguments?: Record<string, unknown>;
resultAssignments?: Record<string, string>;
@@ -142,6 +145,8 @@ export type WorkflowGraph = {
knowledgeMode: "automatic" | "on_demand";
knowledgeTopN: number;
knowledgeScoreThreshold: number;
enableInterrupt: boolean;
turnConfig: TurnConfig;
};
nodes: Array<{
id: string;
@@ -172,6 +177,8 @@ export function defaultGraph(): WorkflowGraph {
knowledgeMode: "automatic",
knowledgeTopN: 5,
knowledgeScoreThreshold: 0,
enableInterrupt: true,
turnConfig: defaultTurnConfig(),
},
nodes: [
{