feat: llm node support tools

This commit is contained in:
zxhlyh
2026-01-07 16:28:41 +08:00
parent 04f40303fd
commit 1d93f41fcf
23 changed files with 282 additions and 76 deletions

View File

@ -275,6 +275,8 @@ export const useChat = (
messageId,
taskId,
chunk_type,
tool_icon,
tool_icon_dark,
tool_call_id,
tool_name,
tool_arguments,
@ -288,9 +290,12 @@ export const useChat = (
if (!responseItem.toolCalls)
responseItem.toolCalls = []
responseItem.toolCalls?.push({
type: 'tool',
tool_call_id,
tool_name,
tool_arguments,
tool_icon,
tool_icon_dark,
tool_files,
tool_error,
tool_elapsed_time,
@ -305,16 +310,22 @@ export const useChat = (
}
if (chunk_type === 'thought_start') {
console.log(message, 'xx1')
responseItem.toolCalls?.push({
is_thought: true,
type: 'thought',
tool_elapsed_time,
})
}
if (chunk_type === 'thought_end') {
console.log(message, 'xx2')
// const currentThoughtIndex = responseItem.toolCalls?.findIndex(item => item.is_thought) ?? -1
// if (currentThoughtIndex > -1)
// responseItem.toolCalls![currentThoughtIndex].tool_output = message
}
if (chunk_type === 'thought') {
const currentThoughtIndex = responseItem.toolCalls?.findIndex(item => item.is_thought) ?? -1
if (currentThoughtIndex > -1)
responseItem.toolCalls![currentThoughtIndex].tool_output = message
console.log(message, 'xx3')
}
if (messageId && !hasSetResponseId) {

View File

@ -1,6 +1,7 @@
import type {
AgentLogItemWithChildren,
IterationDurationMap,
LLMTraceItem,
LoopDurationMap,
LoopVariableMap,
NodeTracing,
@ -79,8 +80,18 @@ export const useLogs = () => {
}
}, [setAgentOrToolLogItemStack, setAgentOrToolLogListMap])
const [showLLMDetail, {
setTrue: setShowLLMDetailTrue,
setFalse: setShowLLMDetailFalse,
}] = useBoolean(false)
const [llmResultList, setLLMResultList] = useState<LLMTraceItem[]>([])
const handleShowLLMDetail = useCallback((detail: LLMTraceItem[]) => {
setShowLLMDetailTrue()
setLLMResultList(detail)
}, [setShowLLMDetailTrue, setLLMResultList])
return {
showSpecialResultPanel: showRetryDetail || showIteratingDetail || showLoopingDetail || !!agentOrToolLogItemStack.length,
showSpecialResultPanel: showRetryDetail || showIteratingDetail || showLoopingDetail || !!agentOrToolLogItemStack.length || showLLMDetail,
showRetryDetail,
setShowRetryDetailTrue,
setShowRetryDetailFalse,
@ -111,5 +122,12 @@ export const useLogs = () => {
agentOrToolLogItemStack,
agentOrToolLogListMap,
handleShowAgentOrToolLog,
showLLMDetail,
setShowLLMDetailTrue,
setShowLLMDetailFalse,
llmResultList,
setLLMResultList,
handleShowLLMDetail,
}
}

View File

@ -153,7 +153,7 @@ const RunPanel: FC<RunProps> = ({
</div>
</div>
{/* panel detail */}
<div ref={ref} className={cn('relative h-0 grow overflow-y-auto rounded-b-xl bg-components-panel-bg')}>
<div ref={ref} className={cn('relative h-0 grow overflow-y-auto rounded-b-xl bg-background-section')}>
{loading && (
<div className="flex h-full items-center justify-center bg-components-panel-bg">
<Loading />
@ -192,7 +192,7 @@ const RunPanel: FC<RunProps> = ({
)}
{!loading && currentTab === 'TRACING' && (
<TracingPanel
className="bg-background-section-burn"
className="bg-background-section"
list={list}
/>
)}

View File

@ -1,37 +1,37 @@
import type { NodeTracing } from '@/types/workflow'
import type { LLMTraceItem, NodeTracing } from '@/types/workflow'
import {
RiArrowRightSLine,
RiRestartFill,
} from '@remixicon/react'
import { useTranslation } from 'react-i18next'
import Button from '@/app/components/base/button'
import { Thinking } from '@/app/components/base/icons/src/vender/workflow'
type LLMLogTriggerProps = {
nodeInfo: NodeTracing
onShowLLMDetail: (detail: NodeTracing[]) => void
onShowLLMDetail: (detail: LLMTraceItem[]) => void
}
const LLMLogTrigger = ({
nodeInfo,
onShowLLMDetail,
}: LLMLogTriggerProps) => {
const { t } = useTranslation()
const { retryDetail } = nodeInfo
const llmTrace = nodeInfo?.execution_metadata?.llm_trace || []
const handleShowRetryResultList = (e: React.MouseEvent<HTMLButtonElement>) => {
const handleShowLLMDetail = (e: React.MouseEvent<HTMLButtonElement>) => {
e.stopPropagation()
e.nativeEvent.stopImmediatePropagation()
onShowLLMDetail(retryDetail || [])
onShowLLMDetail(llmTrace || [])
}
return (
<Button
className="mb-1 flex w-full items-center justify-between"
variant="tertiary"
onClick={handleShowRetryResultList}
onClick={handleShowLLMDetail}
>
<div className="flex items-center">
<RiRestartFill className="mr-0.5 h-4 w-4 shrink-0 text-components-button-tertiary-text" />
{t('nodes.common.retry.retries', { ns: 'workflow', num: retryDetail?.length })}
<Thinking className="mr-[5px] h-4 w-4 shrink-0 text-components-button-tertiary-text" />
{t('detail', { ns: 'runLog' })}
</div>
<RiArrowRightSLine className="h-4 w-4 shrink-0 text-components-button-tertiary-text" />
</Button>

View File

@ -1,16 +1,19 @@
'use client'
import type { FC } from 'react'
import type { NodeTracing } from '@/types/workflow'
import type {
LLMTraceItem,
ToolCallItem,
} from '@/types/workflow'
import {
RiArrowLeftLine,
} from '@remixicon/react'
import { memo } from 'react'
import { useTranslation } from 'react-i18next'
import TracingPanel from '../tracing-panel'
import ToolCallItemComponent from '@/app/components/workflow/run/llm-log/tool-call-item'
type Props = {
list: NodeTracing[]
list: LLMTraceItem[]
onBack: () => void
}
@ -19,6 +22,18 @@ const LLMResultPanel: FC<Props> = ({
onBack,
}) => {
const { t } = useTranslation()
const formattedList = list.map(item => ({
type: item.type,
tool_call_id: item.provider,
tool_name: item.name,
tool_arguments: item.type === 'tool' ? item.output.arguments : undefined,
tool_icon: item.icon,
tool_icon_dark: item.icon_dark,
tool_files: [],
tool_error: item.error,
tool_output: item.type === 'tool' ? item.output.output : item.output,
tool_elapsed_time: item.duration,
}))
return (
<div>
@ -33,13 +48,13 @@ const LLMResultPanel: FC<Props> = ({
<RiArrowLeftLine className="mr-1 h-4 w-4" />
{t('singleRun.back', { ns: 'workflow' })}
</div>
<TracingPanel
list={list.map((item, index) => ({
...item,
title: `${t('nodes.common.retry.retry', { ns: 'workflow' })} ${index + 1}`,
}))}
className="bg-background-section-burn"
/>
<div className="space-y-1 p-2">
{
formattedList.map((item, index) => (
<ToolCallItemComponent key={index} payload={item as ToolCallItem} />
))
}
</div>
</div>
)
}

View File

@ -0,0 +1,94 @@
import type { ToolCallItem } from '@/types/workflow'
import {
RiArrowDownSLine,
} from '@remixicon/react'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import BlockIcon from '@/app/components/workflow/block-icon'
import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor'
import { CodeLanguage } from '@/app/components/workflow/nodes/code/types'
import { BlockEnum } from '@/app/components/workflow/types'
import { cn } from '@/utils/classnames'
type ToolCallItemComponentProps = {
className?: string
payload: ToolCallItem
}
const ToolCallItemComponent = ({
className,
payload,
}: ToolCallItemComponentProps) => {
const { t } = useTranslation()
const [expand, setExpand] = useState(false)
return (
<div
className={cn('rounded-[10px] border-[0.5px] border-components-panel-border bg-background-default-subtle px-2 pb-1 pt-2 shadow-xs', className)}
>
<div className="mb-1 flex cursor-pointer items-center hover:bg-background-gradient-bg-fill-chat-bubble-bg-2" onClick={() => setExpand(!expand)}>
<BlockIcon
type={BlockEnum.Tool}
toolIcon={payload.tool_icon}
className="mr-1 h-4 w-4 shrink-0"
/>
<div className="mr-1 grow truncate" title={payload.tool_name}>{payload.tool_name}</div>
{
!!payload.tool_elapsed_time && (
<div className="system-xs-regular mr-1 shrink-0 text-text-tertiary">
{payload.tool_elapsed_time?.toFixed(1)}
s
</div>
)
}
<RiArrowDownSLine className="h-4 w-4 shrink-0" />
</div>
{
expand && (
<div className="relative px-2 pl-9">
<div className="absolute bottom-1 left-2 top-1 w-[1px] bg-divider-regular"></div>
{
payload.type === 'thought' && typeof payload.tool_output === 'string' && (
<div className="body-sm-medium text-text-tertiary">{payload.tool_output}</div>
)
}
{
payload.type === 'model' && (
<CodeEditor
readOnly
title={<div>{t('common.data', { ns: 'workflow' })}</div>}
language={CodeLanguage.json}
value={payload.tool_output}
isJSONStringifyBeauty
/>
)
}
{
payload.type === 'tool' && (
<CodeEditor
readOnly
title={<div>{t('common.input', { ns: 'workflow' })}</div>}
language={CodeLanguage.json}
value={payload.tool_arguments}
isJSONStringifyBeauty
/>
)
}
{
payload.type === 'tool' && (
<CodeEditor
readOnly
className="mt-1"
title={<div>{t('common.output', { ns: 'workflow' })}</div>}
language={CodeLanguage.json}
value={payload.tool_output}
isJSONStringifyBeauty
/>
)
}
</div>
)
}
</div>
)
}
export default ToolCallItemComponent

View File

@ -3,6 +3,7 @@ import type { FC } from 'react'
import type {
AgentLogItemWithChildren,
IterationDurationMap,
LLMTraceItem,
LoopDurationMap,
LoopVariableMap,
NodeTracing,
@ -44,7 +45,7 @@ type Props = {
onShowLoopDetail?: (detail: NodeTracing[][], loopDurationMap: LoopDurationMap, loopVariableMap: LoopVariableMap) => void
onShowRetryDetail?: (detail: NodeTracing[]) => void
onShowAgentOrToolLog?: (detail?: AgentLogItemWithChildren) => void
onShowLLMDetail?: (detail: NodeTracing[]) => void
onShowLLMDetail?: (detail: LLMTraceItem[]) => void
notShowIterationNav?: boolean
notShowLoopNav?: boolean
}
@ -99,7 +100,7 @@ const NodePanel: FC<Props> = ({
const isRetryNode = hasRetryNode(nodeInfo.node_type) && !!nodeInfo.retryDetail?.length
const isAgentNode = nodeInfo.node_type === BlockEnum.Agent && !!nodeInfo.agentLog?.length
const isToolNode = nodeInfo.node_type === BlockEnum.Tool && !!nodeInfo.agentLog?.length
const isLLMNode = nodeInfo.node_type === BlockEnum.LLM && !!nodeInfo.generation_detail
const isLLMNode = nodeInfo.node_type === BlockEnum.LLM && !!nodeInfo.execution_metadata?.llm_trace?.length
const inputsTitle = useMemo(() => {
let text = t('common.input', { ns: 'workflow' })

View File

@ -2,6 +2,7 @@
import type { FC } from 'react'
import type {
AgentLogItemWithChildren,
LLMTraceItem,
NodeTracing,
} from '@/types/workflow'
import { useTranslation } from 'react-i18next'
@ -46,7 +47,7 @@ export type ResultPanelProps = {
handleShowLoopResultList?: (detail: NodeTracing[][], loopDurationMap: any) => void
onShowRetryDetail?: (detail: NodeTracing[]) => void
handleShowAgentOrToolLog?: (detail?: AgentLogItemWithChildren) => void
onShowLLMDetail?: (detail: NodeTracing[]) => void
onShowLLMDetail?: (detail: LLMTraceItem[]) => void
}
const ResultPanel: FC<ResultPanelProps> = ({
@ -81,7 +82,7 @@ const ResultPanel: FC<ResultPanelProps> = ({
const isRetryNode = hasRetryNode(nodeInfo?.node_type) && !!nodeInfo?.retryDetail?.length
const isAgentNode = nodeInfo?.node_type === BlockEnum.Agent && !!nodeInfo?.agentLog?.length
const isToolNode = nodeInfo?.node_type === BlockEnum.Tool && !!nodeInfo?.agentLog?.length
const isLLMNode = nodeInfo?.node_type === BlockEnum.LLM && !!nodeInfo?.generation_detail
const isLLMNode = nodeInfo?.node_type === BlockEnum.LLM && !!nodeInfo?.execution_metadata?.llm_trace?.length
return (
<div className="bg-components-panel-bg py-2">

View File

@ -1,6 +1,7 @@
import type {
AgentLogItemWithChildren,
IterationDurationMap,
LLMTraceItem,
LoopDurationMap,
LoopVariableMap,
NodeTracing,
@ -33,7 +34,7 @@ export type SpecialResultPanelProps = {
showLLMDetail?: boolean
setShowLLMDetailFalse?: () => void
llmResultList?: NodeTracing[]
llmResultList?: LLMTraceItem[]
}
const SpecialResultPanel = ({
showRetryDetail,

View File

@ -91,6 +91,11 @@ const TracingPanel: FC<TracingPanelProps> = ({
agentOrToolLogItemStack,
agentOrToolLogListMap,
handleShowAgentOrToolLog,
showLLMDetail,
setShowLLMDetailFalse,
llmResultList,
handleShowLLMDetail,
} = useLogs()
const renderNode = (node: NodeTracing) => {
@ -153,6 +158,7 @@ const TracingPanel: FC<TracingPanelProps> = ({
onShowLoopDetail={handleShowLoopResultList}
onShowRetryDetail={handleShowRetryResultList}
onShowAgentOrToolLog={handleShowAgentOrToolLog}
onShowLLMDetail={handleShowLLMDetail}
hideInfo={hideNodeInfo}
hideProcessDetail={hideNodeProcessDetail}
/>
@ -182,6 +188,10 @@ const TracingPanel: FC<TracingPanelProps> = ({
agentOrToolLogItemStack={agentOrToolLogItemStack}
agentOrToolLogListMap={agentOrToolLogListMap}
handleShowAgentOrToolLog={handleShowAgentOrToolLog}
showLLMDetail={showLLMDetail}
setShowLLMDetailFalse={setShowLLMDetailFalse}
llmResultList={llmResultList}
/>
)
}