mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-09-08 10:37:14 +00:00
fix(agent-debug): simplify and localize tool call traces
This commit is contained in:
@@ -5,7 +5,7 @@ import { toast } from 'sonner';
|
|||||||
import { AlertTriangle, Trash2 } from 'lucide-react';
|
import { AlertTriangle, Trash2 } from 'lucide-react';
|
||||||
import { httpClient } from '@/app/infra/http/HttpClient';
|
import { httpClient } from '@/app/infra/http/HttpClient';
|
||||||
import { useCurrentWorkspace } from '@/app/infra/http';
|
import { useCurrentWorkspace } from '@/app/infra/http';
|
||||||
import { Agent } from '@/app/infra/entities/api';
|
import { Agent, AgentPlatformTool } from '@/app/infra/entities/api';
|
||||||
import { useSidebarData } from '@/app/home/components/home-sidebar/SidebarDataContext';
|
import { useSidebarData } from '@/app/home/components/home-sidebar/SidebarDataContext';
|
||||||
import ProcessorDetailWorkbench from '@/app/home/components/processor-detail/ProcessorDetailWorkbench';
|
import ProcessorDetailWorkbench from '@/app/home/components/processor-detail/ProcessorDetailWorkbench';
|
||||||
import EntityBasicInfoDialog, {
|
import EntityBasicInfoDialog, {
|
||||||
@@ -41,6 +41,7 @@ export default function AgentDetailContent({ id }: { id: string }) {
|
|||||||
currentWorkspace?.permissions.includes('runtime.operate') ?? false;
|
currentWorkspace?.permissions.includes('runtime.operate') ?? false;
|
||||||
const { refreshPipelines, pipelines, setDetailEntityName } = useSidebarData();
|
const { refreshPipelines, pipelines, setDetailEntityName } = useSidebarData();
|
||||||
const [agent, setAgent] = useState<Agent | null>(null);
|
const [agent, setAgent] = useState<Agent | null>(null);
|
||||||
|
const [platformTools, setPlatformTools] = useState<AgentPlatformTool[]>([]);
|
||||||
const [loading, setLoading] = useState(!isCreateMode);
|
const [loading, setLoading] = useState(!isCreateMode);
|
||||||
const [formDirty, setFormDirty] = useState(false);
|
const [formDirty, setFormDirty] = useState(false);
|
||||||
const [formSaving, setFormSaving] = useState(false);
|
const [formSaving, setFormSaving] = useState(false);
|
||||||
@@ -224,14 +225,17 @@ export default function AgentDetailContent({ id }: { id: string }) {
|
|||||||
onSavingChange={setFormSaving}
|
onSavingChange={setFormSaving}
|
||||||
onRunnerStatusChange={setRunnerStatus}
|
onRunnerStatusChange={setRunnerStatus}
|
||||||
onSupportedEventPatternsChange={setSupportedEventPatterns}
|
onSupportedEventPatternsChange={setSupportedEventPatterns}
|
||||||
|
onPlatformToolsChange={setPlatformTools}
|
||||||
/>
|
/>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
}
|
}
|
||||||
debugTitle={canOperate ? t('agents.debugTab') : undefined}
|
debugTitle={canOperate ? t('agents.debugTab') : undefined}
|
||||||
|
debugDescription={t('agents.debugPlatformNotice')}
|
||||||
debugContent={
|
debugContent={
|
||||||
canOperate ? (
|
canOperate ? (
|
||||||
<AgentDebugPanel
|
<AgentDebugPanel
|
||||||
agentId={id}
|
agentId={id}
|
||||||
|
platformTools={platformTools}
|
||||||
hasUnsavedChanges={formDirty}
|
hasUnsavedChanges={formDirty}
|
||||||
beforeRun={async () => agentFormRef.current?.save() ?? false}
|
beforeRun={async () => agentFormRef.current?.save() ?? false}
|
||||||
onOpenRunnerConfig={() =>
|
onOpenRunnerConfig={() =>
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import {
|
|||||||
Play,
|
Play,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { httpClient } from '@/app/infra/http/HttpClient';
|
import { httpClient } from '@/app/infra/http/HttpClient';
|
||||||
|
import type { AgentPlatformTool } from '@/app/infra/entities/api';
|
||||||
|
import { extractI18nObject } from '@/i18n/I18nProvider';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
@@ -43,6 +45,7 @@ import { executionSteps, type DebugExecutionEvent } from './debug-execution';
|
|||||||
interface AgentDebugPanelProps {
|
interface AgentDebugPanelProps {
|
||||||
agentId: string;
|
agentId: string;
|
||||||
availableEventTypes: string[];
|
availableEventTypes: string[];
|
||||||
|
platformTools?: AgentPlatformTool[];
|
||||||
supportedEventPatterns?: string[];
|
supportedEventPatterns?: string[];
|
||||||
beforeRun?: () => Promise<boolean>;
|
beforeRun?: () => Promise<boolean>;
|
||||||
hasUnsavedChanges?: boolean;
|
hasUnsavedChanges?: boolean;
|
||||||
@@ -171,12 +174,16 @@ function matchesEventPattern(pattern: string, eventType: string) {
|
|||||||
export default function AgentDebugPanel({
|
export default function AgentDebugPanel({
|
||||||
agentId,
|
agentId,
|
||||||
availableEventTypes,
|
availableEventTypes,
|
||||||
|
platformTools = [],
|
||||||
supportedEventPatterns = ['*'],
|
supportedEventPatterns = ['*'],
|
||||||
beforeRun,
|
beforeRun,
|
||||||
hasUnsavedChanges = false,
|
hasUnsavedChanges = false,
|
||||||
onOpenRunnerConfig,
|
onOpenRunnerConfig,
|
||||||
}: AgentDebugPanelProps) {
|
}: AgentDebugPanelProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const toolLabels = Object.fromEntries(
|
||||||
|
platformTools.map((tool) => [tool.name, extractI18nObject(tool.label)]),
|
||||||
|
);
|
||||||
const [preset, setPreset] = useState('message.received');
|
const [preset, setPreset] = useState('message.received');
|
||||||
const [customEventType, setCustomEventType] = useState('custom.event');
|
const [customEventType, setCustomEventType] = useState('custom.event');
|
||||||
const [inputText, setInputText] = useState('');
|
const [inputText, setInputText] = useState('');
|
||||||
@@ -428,9 +435,6 @@ export default function AgentDebugPanel({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full min-h-0 min-w-0 flex-col">
|
<div className="flex h-full min-h-0 min-w-0 flex-col">
|
||||||
<p className="shrink-0 border-b bg-muted/20 px-3 py-2 text-xs leading-relaxed text-muted-foreground">
|
|
||||||
{t('agents.debugPlatformNotice')}
|
|
||||||
</p>
|
|
||||||
<div ref={transcriptRef} className="min-h-0 flex-1 overflow-y-auto p-3">
|
<div ref={transcriptRef} className="min-h-0 flex-1 overflow-y-auto p-3">
|
||||||
<div className="mb-3">
|
<div className="mb-3">
|
||||||
<p className="text-sm font-medium">{t('agents.debugTranscript')}</p>
|
<p className="text-sm font-medium">{t('agents.debugTranscript')}</p>
|
||||||
@@ -493,6 +497,7 @@ export default function AgentDebugPanel({
|
|||||||
<AgentExecutionTrace
|
<AgentExecutionTrace
|
||||||
events={entry.events}
|
events={entry.events}
|
||||||
finished={entry.finished}
|
finished={entry.finished}
|
||||||
|
toolLabels={toolLabels}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{entry.text && (
|
{entry.text && (
|
||||||
|
|||||||
@@ -22,9 +22,11 @@ const textClass =
|
|||||||
export default function AgentExecutionTrace({
|
export default function AgentExecutionTrace({
|
||||||
events,
|
events,
|
||||||
finished = false,
|
finished = false,
|
||||||
|
toolLabels = {},
|
||||||
}: {
|
}: {
|
||||||
events: DebugExecutionEvent[];
|
events: DebugExecutionEvent[];
|
||||||
finished?: boolean;
|
finished?: boolean;
|
||||||
|
toolLabels?: Record<string, string>;
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const steps = useMemo(() => executionSteps(events), [events]);
|
const steps = useMemo(() => executionSteps(events), [events]);
|
||||||
@@ -36,9 +38,9 @@ export default function AgentExecutionTrace({
|
|||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<div className="min-w-0 space-y-3">
|
<div className="min-w-0 space-y-3">
|
||||||
{ended && (
|
{ended && toolCount > 0 && (
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
{t(toolCount ? 'agents.debugToolCount' : 'agents.debugNoToolCalls', {
|
{t('agents.debugToolCount', {
|
||||||
count: toolCount,
|
count: toolCount,
|
||||||
})}
|
})}
|
||||||
</p>
|
</p>
|
||||||
@@ -68,15 +70,22 @@ export default function AgentExecutionTrace({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<section
|
<details
|
||||||
key={index}
|
key={index}
|
||||||
className="min-w-0 space-y-2 rounded-md border bg-background p-3"
|
className="min-w-0 rounded-md border bg-background p-3"
|
||||||
>
|
>
|
||||||
<div className="flex flex-wrap items-center justify-between gap-2 text-xs">
|
<summary className="cursor-pointer text-xs">
|
||||||
<span className="min-w-0 break-all font-medium">
|
<span className="min-w-0 break-all font-medium">
|
||||||
<Wrench className="mr-1.5 inline size-3.5" />
|
<Wrench className="mr-1.5 inline size-3.5" />
|
||||||
{step.name}
|
{toolLabels[step.name] || step.name}
|
||||||
</span>
|
</span>
|
||||||
|
{toolLabels[step.name] && toolLabels[step.name] !== step.name && (
|
||||||
|
<code className="ml-2 break-all font-mono text-xs text-muted-foreground">
|
||||||
|
{step.name}
|
||||||
|
</code>
|
||||||
|
)}
|
||||||
|
</summary>
|
||||||
|
<div className="mt-2 space-y-2">
|
||||||
<span
|
<span
|
||||||
className={
|
className={
|
||||||
step.status === 'failed'
|
step.status === 'failed'
|
||||||
@@ -100,33 +109,33 @@ export default function AgentExecutionTrace({
|
|||||||
: 'agents.debugToolCompleted',
|
: 'agents.debugToolCompleted',
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
|
{step.parameters !== undefined && (
|
||||||
|
<div>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
{t('agents.debugToolArguments')}
|
||||||
|
</p>
|
||||||
|
<pre className="mt-1 whitespace-pre-wrap break-all rounded bg-muted/40 p-2 font-mono text-xs">
|
||||||
|
{formatValue(step.parameters)}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{step.result !== undefined && step.result !== null && (
|
||||||
|
<div>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
{t('agents.debugToolResult')}
|
||||||
|
</p>
|
||||||
|
<pre className="mt-1 whitespace-pre-wrap break-all rounded bg-muted/40 p-2 font-mono text-xs">
|
||||||
|
{formatValue(step.result)}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{step.error && (
|
||||||
|
<pre className={`${textClass} text-destructive`}>
|
||||||
|
{step.error}
|
||||||
|
</pre>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
{step.parameters !== undefined && (
|
</details>
|
||||||
<div>
|
|
||||||
<p className="text-xs text-muted-foreground">
|
|
||||||
{t('agents.debugToolArguments')}
|
|
||||||
</p>
|
|
||||||
<pre className="mt-1 max-h-64 overflow-auto whitespace-pre-wrap break-all rounded bg-muted/40 p-2 font-mono text-xs">
|
|
||||||
{formatValue(step.parameters)}
|
|
||||||
</pre>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{step.result !== undefined && step.result !== null && (
|
|
||||||
<div>
|
|
||||||
<p className="text-xs text-muted-foreground">
|
|
||||||
{t('agents.debugToolResult')}
|
|
||||||
</p>
|
|
||||||
<pre className="mt-1 max-h-64 overflow-auto whitespace-pre-wrap break-all rounded bg-muted/40 p-2 font-mono text-xs">
|
|
||||||
{formatValue(step.result)}
|
|
||||||
</pre>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{step.error && (
|
|
||||||
<pre className={`${textClass} text-destructive`}>
|
|
||||||
{step.error}
|
|
||||||
</pre>
|
|
||||||
)}
|
|
||||||
</section>
|
|
||||||
),
|
),
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ interface AgentFormComponentProps {
|
|||||||
onSavingChange?: (saving: boolean) => void;
|
onSavingChange?: (saving: boolean) => void;
|
||||||
onRunnerStatusChange?: (status: AgentRunnerStatus) => void;
|
onRunnerStatusChange?: (status: AgentRunnerStatus) => void;
|
||||||
onSupportedEventPatternsChange?: (patterns: string[]) => void;
|
onSupportedEventPatternsChange?: (patterns: string[]) => void;
|
||||||
|
onPlatformToolsChange?: (tools: AgentPlatformTool[]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AgentConfigSection =
|
export type AgentConfigSection =
|
||||||
@@ -124,6 +125,7 @@ function AgentFormComponent(
|
|||||||
onSavingChange,
|
onSavingChange,
|
||||||
onRunnerStatusChange,
|
onRunnerStatusChange,
|
||||||
onSupportedEventPatternsChange,
|
onSupportedEventPatternsChange,
|
||||||
|
onPlatformToolsChange,
|
||||||
}: AgentFormComponentProps,
|
}: AgentFormComponentProps,
|
||||||
ref: ForwardedRef<AgentFormHandle>,
|
ref: ForwardedRef<AgentFormHandle>,
|
||||||
) {
|
) {
|
||||||
@@ -131,6 +133,9 @@ function AgentFormComponent(
|
|||||||
const [runnerConfigSchema, setRunnerConfigSchema] =
|
const [runnerConfigSchema, setRunnerConfigSchema] =
|
||||||
useState<PipelineConfigTab | null>(null);
|
useState<PipelineConfigTab | null>(null);
|
||||||
const [platformTools, setPlatformTools] = useState<AgentPlatformTool[]>([]);
|
const [platformTools, setPlatformTools] = useState<AgentPlatformTool[]>([]);
|
||||||
|
useEffect(() => {
|
||||||
|
onPlatformToolsChange?.(platformTools);
|
||||||
|
}, [onPlatformToolsChange, platformTools]);
|
||||||
const [platformToolCatalogAvailable, setPlatformToolCatalogAvailable] =
|
const [platformToolCatalogAvailable, setPlatformToolCatalogAvailable] =
|
||||||
useState(true);
|
useState(true);
|
||||||
const [hostTools, setHostTools] = useState<PluginTool[]>([]);
|
const [hostTools, setHostTools] = useState<PluginTool[]>([]);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { ReactNode, useState } from 'react';
|
import { ReactNode, useState } from 'react';
|
||||||
import { BarChart3, Bug, Settings } from 'lucide-react';
|
import { BarChart3, Bug, Info, Settings } from 'lucide-react';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import {
|
import {
|
||||||
@@ -36,6 +36,7 @@ interface ProcessorDetailWorkbenchProps {
|
|||||||
configTitle: string;
|
configTitle: string;
|
||||||
configContent: ReactNode;
|
configContent: ReactNode;
|
||||||
debugTitle?: string;
|
debugTitle?: string;
|
||||||
|
debugDescription?: string;
|
||||||
debugContent?: ReactNode;
|
debugContent?: ReactNode;
|
||||||
debugConnected?: boolean;
|
debugConnected?: boolean;
|
||||||
debugConnectedLabel?: string;
|
debugConnectedLabel?: string;
|
||||||
@@ -58,6 +59,7 @@ export default function ProcessorDetailWorkbench({
|
|||||||
configTitle,
|
configTitle,
|
||||||
configContent,
|
configContent,
|
||||||
debugTitle,
|
debugTitle,
|
||||||
|
debugDescription,
|
||||||
debugContent,
|
debugContent,
|
||||||
debugConnected,
|
debugConnected,
|
||||||
debugConnectedLabel,
|
debugConnectedLabel,
|
||||||
@@ -196,6 +198,22 @@ export default function ProcessorDetailWorkbench({
|
|||||||
<div className="flex min-w-0 items-center gap-2 font-medium">
|
<div className="flex min-w-0 items-center gap-2 font-medium">
|
||||||
<Bug className="size-4 shrink-0" />
|
<Bug className="size-4 shrink-0" />
|
||||||
<span className="truncate">{debugTitle}</span>
|
<span className="truncate">{debugTitle}</span>
|
||||||
|
{debugDescription && (
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
aria-label={debugDescription}
|
||||||
|
className="inline-flex shrink-0 text-muted-foreground hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring rounded-sm"
|
||||||
|
>
|
||||||
|
<Info className="size-4" />
|
||||||
|
</button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent className="max-w-xs whitespace-normal leading-relaxed">
|
||||||
|
{debugDescription}
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
{debugConnected !== undefined && (
|
{debugConnected !== undefined && (
|
||||||
<span className="flex items-center gap-1.5 text-xs text-muted-foreground">
|
<span className="flex items-center gap-1.5 text-xs text-muted-foreground">
|
||||||
|
|||||||
Reference in New Issue
Block a user