mirror of
https://github.com/langgenius/dify.git
synced 2026-05-05 01:48:04 +08:00
feat: llm node support tools
This commit is contained in:
@ -9,9 +9,9 @@ const ToolCalls = ({
|
||||
}: ToolCallsProps) => {
|
||||
return (
|
||||
<div className="my-1 space-y-1">
|
||||
{toolCalls.map((toolCall: ToolCallItem) => (
|
||||
{toolCalls.map((toolCall: ToolCallItem, index: number) => (
|
||||
<ToolCallItemComponent
|
||||
key={toolCall.tool_call_id}
|
||||
key={index}
|
||||
payload={toolCall}
|
||||
className="bg-background-gradient-bg-fill-chat-bubble-bg-2 shadow-none"
|
||||
/>
|
||||
|
||||
@ -319,6 +319,9 @@ export const useChat = (
|
||||
return player
|
||||
}
|
||||
|
||||
let toolCallId = ''
|
||||
let thoughtId = ''
|
||||
|
||||
ssePost(
|
||||
url,
|
||||
{
|
||||
@ -326,7 +329,19 @@ export const useChat = (
|
||||
},
|
||||
{
|
||||
isPublicAPI,
|
||||
onData: (message: string, isFirstMessage: boolean, { conversationId: newConversationId, messageId, taskId }: any) => {
|
||||
onData: (message: string, isFirstMessage: boolean, {
|
||||
conversationId: newConversationId,
|
||||
messageId,
|
||||
taskId,
|
||||
chunk_type,
|
||||
tool_icon,
|
||||
tool_icon_dark,
|
||||
tool_name,
|
||||
tool_arguments,
|
||||
tool_files,
|
||||
tool_error,
|
||||
tool_elapsed_time,
|
||||
}: any) => {
|
||||
if (!isAgentMode) {
|
||||
responseItem.content = responseItem.content + message
|
||||
}
|
||||
@ -336,6 +351,57 @@ export const useChat = (
|
||||
lastThought.thought = lastThought.thought + message // need immer setAutoFreeze
|
||||
}
|
||||
|
||||
if (chunk_type === 'tool_call') {
|
||||
if (!responseItem.toolCalls)
|
||||
responseItem.toolCalls = []
|
||||
toolCallId = uuidV4()
|
||||
responseItem.toolCalls?.push({
|
||||
id: toolCallId,
|
||||
type: 'tool',
|
||||
toolName: tool_name,
|
||||
toolArguments: tool_arguments,
|
||||
toolIcon: tool_icon,
|
||||
toolIconDark: tool_icon_dark,
|
||||
})
|
||||
}
|
||||
|
||||
if (chunk_type === 'tool_result') {
|
||||
const currentToolCallIndex = responseItem.toolCalls?.findIndex(item => item.id === toolCallId) ?? -1
|
||||
|
||||
if (currentToolCallIndex > -1) {
|
||||
responseItem.toolCalls![currentToolCallIndex].toolError = tool_error
|
||||
responseItem.toolCalls![currentToolCallIndex].toolDuration = tool_elapsed_time
|
||||
responseItem.toolCalls![currentToolCallIndex].toolFiles = tool_files
|
||||
responseItem.toolCalls![currentToolCallIndex].toolOutput = message
|
||||
}
|
||||
}
|
||||
|
||||
if (chunk_type === 'thought_start') {
|
||||
if (!responseItem.toolCalls)
|
||||
responseItem.toolCalls = []
|
||||
thoughtId = uuidV4()
|
||||
responseItem.toolCalls.push({
|
||||
id: thoughtId,
|
||||
type: 'thought',
|
||||
thoughtOutput: '',
|
||||
})
|
||||
}
|
||||
|
||||
if (chunk_type === 'thought') {
|
||||
const currentThoughtIndex = responseItem.toolCalls?.findIndex(item => item.id === thoughtId) ?? -1
|
||||
if (currentThoughtIndex > -1) {
|
||||
responseItem.toolCalls![currentThoughtIndex].thoughtOutput += message
|
||||
}
|
||||
}
|
||||
|
||||
if (chunk_type === 'thought_end') {
|
||||
const currentThoughtIndex = responseItem.toolCalls?.findIndex(item => item.id === thoughtId) ?? -1
|
||||
if (currentThoughtIndex > -1) {
|
||||
responseItem.toolCalls![currentThoughtIndex].thoughtOutput += message
|
||||
responseItem.toolCalls![currentThoughtIndex].thoughtCompleted = true
|
||||
}
|
||||
}
|
||||
|
||||
if (messageId && !hasSetResponseId) {
|
||||
questionItem.id = `question-${messageId}`
|
||||
responseItem.id = messageId
|
||||
|
||||
@ -64,11 +64,6 @@ export type CitationItem = {
|
||||
word_count: number
|
||||
}
|
||||
|
||||
export type IconObject = {
|
||||
background: string
|
||||
content: string
|
||||
}
|
||||
|
||||
export type IChatItem = {
|
||||
id: string
|
||||
content: string
|
||||
|
||||
@ -150,6 +150,10 @@ export const LLM_OUTPUT_STRUCT: Var[] = [
|
||||
variable: 'usage',
|
||||
type: VarType.object,
|
||||
},
|
||||
{
|
||||
variable: 'generation',
|
||||
type: VarType.object,
|
||||
},
|
||||
]
|
||||
|
||||
export const KNOWLEDGE_RETRIEVAL_OUTPUT_STRUCT: Var[] = [
|
||||
|
||||
@ -315,6 +315,11 @@ const Panel: FC<NodePanelProps<LLMNodeType>> = ({
|
||||
type="object"
|
||||
description={t(`${i18nPrefix}.outputVars.usage`, { ns: 'workflow' })}
|
||||
/>
|
||||
<VarItem
|
||||
name="generation"
|
||||
type="object"
|
||||
description={t(`${i18nPrefix}.outputVars.generation`, { ns: 'workflow' })}
|
||||
/>
|
||||
{inputs.structured_output_enabled && (
|
||||
<>
|
||||
<Split className="mt-3" />
|
||||
|
||||
@ -15,6 +15,7 @@ import {
|
||||
useState,
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { v4 as uuidV4 } from 'uuid'
|
||||
import {
|
||||
getProcessedInputs,
|
||||
processOpeningStatement,
|
||||
@ -266,6 +267,8 @@ export const useChat = (
|
||||
}
|
||||
|
||||
let hasSetResponseId = false
|
||||
let toolCallId = ''
|
||||
let thoughtId = ''
|
||||
|
||||
handleRun(
|
||||
bodyParams,
|
||||
@ -277,7 +280,6 @@ export const useChat = (
|
||||
chunk_type,
|
||||
tool_icon,
|
||||
tool_icon_dark,
|
||||
tool_call_id,
|
||||
tool_name,
|
||||
tool_arguments,
|
||||
tool_files,
|
||||
@ -289,43 +291,52 @@ export const useChat = (
|
||||
if (chunk_type === 'tool_call') {
|
||||
if (!responseItem.toolCalls)
|
||||
responseItem.toolCalls = []
|
||||
toolCallId = uuidV4()
|
||||
responseItem.toolCalls?.push({
|
||||
id: toolCallId,
|
||||
type: 'tool',
|
||||
tool_call_id,
|
||||
tool_name,
|
||||
tool_arguments,
|
||||
tool_icon,
|
||||
tool_icon_dark,
|
||||
tool_files,
|
||||
tool_error,
|
||||
tool_elapsed_time,
|
||||
toolName: tool_name,
|
||||
toolArguments: tool_arguments,
|
||||
toolIcon: tool_icon,
|
||||
toolIconDark: tool_icon_dark,
|
||||
})
|
||||
}
|
||||
|
||||
if (chunk_type === 'tool_result') {
|
||||
const currentToolCallIndex = responseItem.toolCalls?.findIndex(item => item.tool_call_id === tool_call_id) ?? -1
|
||||
const currentToolCallIndex = responseItem.toolCalls?.findIndex(item => item.id === toolCallId) ?? -1
|
||||
|
||||
if (currentToolCallIndex > -1)
|
||||
responseItem.toolCalls![currentToolCallIndex].tool_output = message
|
||||
if (currentToolCallIndex > -1) {
|
||||
responseItem.toolCalls![currentToolCallIndex].toolError = tool_error
|
||||
responseItem.toolCalls![currentToolCallIndex].toolDuration = tool_elapsed_time
|
||||
responseItem.toolCalls![currentToolCallIndex].toolFiles = tool_files
|
||||
responseItem.toolCalls![currentToolCallIndex].toolOutput = message
|
||||
}
|
||||
}
|
||||
|
||||
if (chunk_type === 'thought_start') {
|
||||
console.log(message, 'xx1')
|
||||
responseItem.toolCalls?.push({
|
||||
if (!responseItem.toolCalls)
|
||||
responseItem.toolCalls = []
|
||||
thoughtId = uuidV4()
|
||||
responseItem.toolCalls.push({
|
||||
id: thoughtId,
|
||||
type: 'thought',
|
||||
tool_elapsed_time,
|
||||
thoughtOutput: '',
|
||||
})
|
||||
}
|
||||
|
||||
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.id === thoughtId) ?? -1
|
||||
if (currentThoughtIndex > -1) {
|
||||
responseItem.toolCalls![currentThoughtIndex].thoughtOutput += message
|
||||
}
|
||||
}
|
||||
|
||||
if (chunk_type === 'thought') {
|
||||
console.log(message, 'xx3')
|
||||
if (chunk_type === 'thought_end') {
|
||||
const currentThoughtIndex = responseItem.toolCalls?.findIndex(item => item.id === thoughtId) ?? -1
|
||||
if (currentThoughtIndex > -1) {
|
||||
responseItem.toolCalls![currentThoughtIndex].thoughtOutput += message
|
||||
responseItem.toolCalls![currentThoughtIndex].thoughtCompleted = true
|
||||
}
|
||||
}
|
||||
|
||||
if (messageId && !hasSetResponseId) {
|
||||
|
||||
@ -22,18 +22,30 @@ 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,
|
||||
}))
|
||||
const formattedList = list.map((item) => {
|
||||
if (item.type === 'tool') {
|
||||
return {
|
||||
type: 'tool',
|
||||
toolName: item.name,
|
||||
toolProvider: item.provider,
|
||||
toolIcon: item.icon,
|
||||
toolIconDark: item.icon_dark,
|
||||
toolArguments: item.output.arguments,
|
||||
toolOutput: item.output.output,
|
||||
toolDuration: item.duration,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'model',
|
||||
modelName: item.name,
|
||||
modelProvider: item.provider,
|
||||
modelIcon: item.icon,
|
||||
modelIconDark: item.icon_dark,
|
||||
modelOutput: item.output,
|
||||
modelDuration: item.duration,
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
||||
@ -4,6 +4,8 @@ import {
|
||||
} from '@remixicon/react'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import AppIcon from '@/app/components/base/app-icon'
|
||||
import { Thinking } from '@/app/components/base/icons/src/vender/workflow'
|
||||
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'
|
||||
@ -24,17 +26,73 @@ const ToolCallItemComponent = ({
|
||||
<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>
|
||||
<div
|
||||
className="mb-1 flex cursor-pointer items-center hover:bg-background-gradient-bg-fill-chat-bubble-bg-2"
|
||||
onClick={() => {
|
||||
setExpand(!expand)
|
||||
}}
|
||||
>
|
||||
{
|
||||
!!payload.tool_elapsed_time && (
|
||||
payload.type === 'thought' && (
|
||||
<Thinking className="mr-1 h-4 w-4 shrink-0" />
|
||||
)
|
||||
}
|
||||
{
|
||||
payload.type === 'tool' && (
|
||||
<BlockIcon
|
||||
type={BlockEnum.Tool}
|
||||
toolIcon={payload.toolIcon}
|
||||
className="mr-1 h-4 w-4 shrink-0"
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
payload.type === 'model' && (
|
||||
<AppIcon
|
||||
iconType={typeof payload.modelIcon === 'string' ? 'image' : undefined}
|
||||
imageUrl={typeof payload.modelIcon === 'string' ? payload.modelIcon : undefined}
|
||||
background={typeof payload.modelIcon === 'object' ? payload.modelIcon.background : undefined}
|
||||
className="mr-1 h-4 w-4 shrink-0"
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
payload.type === 'thought' && (
|
||||
<div className="system-xs-medium mr-1 grow truncate text-text-secondary" title={payload.thoughtOutput}>
|
||||
{
|
||||
payload.thoughtCompleted && !expand && (payload.thoughtOutput || '') as string
|
||||
}
|
||||
{
|
||||
payload.thoughtCompleted && expand && 'THOUGHT'
|
||||
}
|
||||
{
|
||||
!payload.thoughtCompleted && 'THINKING...'
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
payload.type === 'tool' && (
|
||||
<div className="system-xs-medium mr-1 grow truncate text-text-secondary" title={payload.toolName}>{payload.toolName}</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
payload.type === 'model' && (
|
||||
<div className="system-xs-medium mr-1 grow truncate text-text-secondary" title={payload.modelName}>{payload.modelName}</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
!!payload.toolDuration && (
|
||||
<div className="system-xs-regular mr-1 shrink-0 text-text-tertiary">
|
||||
{payload.tool_elapsed_time?.toFixed(1)}
|
||||
{payload.toolDuration?.toFixed(1)}
|
||||
s
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
!!payload.modelDuration && (
|
||||
<div className="system-xs-regular mr-1 shrink-0 text-text-tertiary">
|
||||
{payload.modelDuration?.toFixed(1)}
|
||||
s
|
||||
</div>
|
||||
)
|
||||
@ -46,8 +104,8 @@ const ToolCallItemComponent = ({
|
||||
<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 === 'thought' && typeof payload.thoughtOutput === 'string' && (
|
||||
<div className="body-sm-medium text-text-tertiary">{payload.thoughtOutput}</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
@ -56,7 +114,7 @@ const ToolCallItemComponent = ({
|
||||
readOnly
|
||||
title={<div>{t('common.data', { ns: 'workflow' })}</div>}
|
||||
language={CodeLanguage.json}
|
||||
value={payload.tool_output}
|
||||
value={payload.modelOutput}
|
||||
isJSONStringifyBeauty
|
||||
/>
|
||||
)
|
||||
@ -67,7 +125,7 @@ const ToolCallItemComponent = ({
|
||||
readOnly
|
||||
title={<div>{t('common.input', { ns: 'workflow' })}</div>}
|
||||
language={CodeLanguage.json}
|
||||
value={payload.tool_arguments}
|
||||
value={payload.toolArguments}
|
||||
isJSONStringifyBeauty
|
||||
/>
|
||||
)
|
||||
@ -79,7 +137,7 @@ const ToolCallItemComponent = ({
|
||||
className="mt-1"
|
||||
title={<div>{t('common.output', { ns: 'workflow' })}</div>}
|
||||
language={CodeLanguage.json}
|
||||
value={payload.tool_output}
|
||||
value={payload.toolOutput}
|
||||
isJSONStringifyBeauty
|
||||
/>
|
||||
)
|
||||
|
||||
@ -651,6 +651,7 @@
|
||||
"nodes.llm.jsonSchema.warningTips.saveSchema": "Please finish editing the current field before saving the schema",
|
||||
"nodes.llm.model": "model",
|
||||
"nodes.llm.notSetContextInPromptTip": "To enable the context feature, please fill in the context variable in PROMPT.",
|
||||
"nodes.llm.outputVars.generation": "Generation Information",
|
||||
"nodes.llm.outputVars.output": "Generate content",
|
||||
"nodes.llm.outputVars.reasoning_content": "Reasoning Content",
|
||||
"nodes.llm.outputVars.usage": "Model Usage Information",
|
||||
|
||||
@ -651,6 +651,7 @@
|
||||
"nodes.llm.jsonSchema.warningTips.saveSchema": "请先完成当前字段的编辑",
|
||||
"nodes.llm.model": "模型",
|
||||
"nodes.llm.notSetContextInPromptTip": "要启用上下文功能,请在提示中填写上下文变量。",
|
||||
"nodes.llm.outputVars.generation": "生成信息",
|
||||
"nodes.llm.outputVars.output": "生成内容",
|
||||
"nodes.llm.outputVars.reasoning_content": "推理内容",
|
||||
"nodes.llm.outputVars.usage": "模型用量信息",
|
||||
|
||||
@ -34,16 +34,27 @@ export type IconObject = {
|
||||
}
|
||||
|
||||
export type ToolCallItem = {
|
||||
id: string
|
||||
type: 'model' | 'tool' | 'thought'
|
||||
tool_call_id?: string
|
||||
tool_name?: string
|
||||
tool_arguments?: string
|
||||
tool_icon?: string | IconObject
|
||||
tool_icon_dark?: string | IconObject
|
||||
tool_files?: string[]
|
||||
tool_error?: string
|
||||
tool_output?: Record<string, any> | string
|
||||
tool_elapsed_time?: number
|
||||
thoughtCompleted?: boolean
|
||||
thoughtOutput?: string
|
||||
|
||||
toolName?: string
|
||||
toolProvider?: string
|
||||
toolIcon?: string | IconObject
|
||||
toolIconDark?: string | IconObject
|
||||
toolArguments?: string
|
||||
toolOutput?: Record<string, any> | string
|
||||
toolFiles?: string[]
|
||||
toolError?: string
|
||||
toolDuration?: number
|
||||
|
||||
modelName?: string
|
||||
modelProvider?: string
|
||||
modelOutput?: Record<string, any> | string
|
||||
modelDuration?: number
|
||||
modelIcon?: string | IconObject
|
||||
modelIconDark?: string | IconObject
|
||||
}
|
||||
|
||||
export type ToolCallDetail = {
|
||||
|
||||
Reference in New Issue
Block a user