feat: Add Citations and Attributions to Agent Node (#18558)

Co-authored-by: oneness0 <2902216407@qq.com>
Co-authored-by: Novice <novice12185727@gmail.com>
This commit is contained in:
Chieh Wang
2025-07-16 15:46:15 +08:00
committed by GitHub
parent bdb9f29948
commit 30aa052a57
10 changed files with 78 additions and 6 deletions

View File

@@ -41,6 +41,7 @@ class AgentStrategyParameter(PluginParameter):
APP_SELECTOR = CommonParameterType.APP_SELECTOR.value APP_SELECTOR = CommonParameterType.APP_SELECTOR.value
MODEL_SELECTOR = CommonParameterType.MODEL_SELECTOR.value MODEL_SELECTOR = CommonParameterType.MODEL_SELECTOR.value
TOOLS_SELECTOR = CommonParameterType.TOOLS_SELECTOR.value TOOLS_SELECTOR = CommonParameterType.TOOLS_SELECTOR.value
ANY = CommonParameterType.ANY.value
# deprecated, should not use. # deprecated, should not use.
SYSTEM_FILES = CommonParameterType.SYSTEM_FILES.value SYSTEM_FILES = CommonParameterType.SYSTEM_FILES.value

View File

@@ -14,6 +14,7 @@ class CommonParameterType(StrEnum):
APP_SELECTOR = "app-selector" APP_SELECTOR = "app-selector"
MODEL_SELECTOR = "model-selector" MODEL_SELECTOR = "model-selector"
TOOLS_SELECTOR = "array[tools]" TOOLS_SELECTOR = "array[tools]"
ANY = "any"
# Dynamic select parameter # Dynamic select parameter
# Once you are not sure about the available options until authorization is done # Once you are not sure about the available options until authorization is done

View File

@@ -5,6 +5,7 @@ from pydantic import BaseModel, Field, field_validator
from core.entities.parameter_entities import CommonParameterType from core.entities.parameter_entities import CommonParameterType
from core.tools.entities.common_entities import I18nObject from core.tools.entities.common_entities import I18nObject
from core.workflow.nodes.base.entities import NumberType
class PluginParameterOption(BaseModel): class PluginParameterOption(BaseModel):
@@ -38,6 +39,7 @@ class PluginParameterType(enum.StrEnum):
APP_SELECTOR = CommonParameterType.APP_SELECTOR.value APP_SELECTOR = CommonParameterType.APP_SELECTOR.value
MODEL_SELECTOR = CommonParameterType.MODEL_SELECTOR.value MODEL_SELECTOR = CommonParameterType.MODEL_SELECTOR.value
TOOLS_SELECTOR = CommonParameterType.TOOLS_SELECTOR.value TOOLS_SELECTOR = CommonParameterType.TOOLS_SELECTOR.value
ANY = CommonParameterType.ANY.value
DYNAMIC_SELECT = CommonParameterType.DYNAMIC_SELECT.value DYNAMIC_SELECT = CommonParameterType.DYNAMIC_SELECT.value
# deprecated, should not use. # deprecated, should not use.
@@ -151,6 +153,10 @@ def cast_parameter_value(typ: enum.StrEnum, value: Any, /):
if value and not isinstance(value, list): if value and not isinstance(value, list):
raise ValueError("The tools selector must be a list.") raise ValueError("The tools selector must be a list.")
return value return value
case PluginParameterType.ANY:
if value and not isinstance(value, str | dict | list | NumberType):
raise ValueError("The var selector must be a string, dictionary, list or number.")
return value
case PluginParameterType.ARRAY: case PluginParameterType.ARRAY:
if not isinstance(value, list): if not isinstance(value, list):
# Try to parse JSON string for arrays # Try to parse JSON string for arrays

View File

@@ -16,6 +16,7 @@ from core.plugin.entities.parameters import (
cast_parameter_value, cast_parameter_value,
init_frontend_parameter, init_frontend_parameter,
) )
from core.rag.entities.citation_metadata import RetrievalSourceMetadata
from core.tools.entities.common_entities import I18nObject from core.tools.entities.common_entities import I18nObject
from core.tools.entities.constants import TOOL_SELECTOR_MODEL_IDENTITY from core.tools.entities.constants import TOOL_SELECTOR_MODEL_IDENTITY
@@ -179,6 +180,10 @@ class ToolInvokeMessage(BaseModel):
data: Mapping[str, Any] = Field(..., description="Detailed log data") data: Mapping[str, Any] = Field(..., description="Detailed log data")
metadata: Optional[Mapping[str, Any]] = Field(default=None, description="The metadata of the log") metadata: Optional[Mapping[str, Any]] = Field(default=None, description="The metadata of the log")
class RetrieverResourceMessage(BaseModel):
retriever_resources: list[RetrievalSourceMetadata] = Field(..., description="retriever resources")
context: str = Field(..., description="context")
class MessageType(Enum): class MessageType(Enum):
TEXT = "text" TEXT = "text"
IMAGE = "image" IMAGE = "image"
@@ -191,13 +196,22 @@ class ToolInvokeMessage(BaseModel):
FILE = "file" FILE = "file"
LOG = "log" LOG = "log"
BLOB_CHUNK = "blob_chunk" BLOB_CHUNK = "blob_chunk"
RETRIEVER_RESOURCES = "retriever_resources"
type: MessageType = MessageType.TEXT type: MessageType = MessageType.TEXT
""" """
plain text, image url or link url plain text, image url or link url
""" """
message: ( message: (
JsonMessage | TextMessage | BlobChunkMessage | BlobMessage | LogMessage | FileMessage | None | VariableMessage JsonMessage
| TextMessage
| BlobChunkMessage
| BlobMessage
| LogMessage
| FileMessage
| None
| VariableMessage
| RetrieverResourceMessage
) )
meta: dict[str, Any] | None = None meta: dict[str, Any] | None = None
@@ -243,6 +257,7 @@ class ToolParameter(PluginParameter):
FILES = PluginParameterType.FILES.value FILES = PluginParameterType.FILES.value
APP_SELECTOR = PluginParameterType.APP_SELECTOR.value APP_SELECTOR = PluginParameterType.APP_SELECTOR.value
MODEL_SELECTOR = PluginParameterType.MODEL_SELECTOR.value MODEL_SELECTOR = PluginParameterType.MODEL_SELECTOR.value
ANY = PluginParameterType.ANY.value
DYNAMIC_SELECT = PluginParameterType.DYNAMIC_SELECT.value DYNAMIC_SELECT = PluginParameterType.DYNAMIC_SELECT.value
# MCP object and array type parameters # MCP object and array type parameters

View File

@@ -22,7 +22,7 @@ from core.workflow.enums import SystemVariableKey
from core.workflow.graph_engine.entities.event import AgentLogEvent from core.workflow.graph_engine.entities.event import AgentLogEvent
from core.workflow.nodes.base import BaseNode from core.workflow.nodes.base import BaseNode
from core.workflow.nodes.enums import NodeType from core.workflow.nodes.enums import NodeType
from core.workflow.nodes.event import RunCompletedEvent, RunStreamChunkEvent from core.workflow.nodes.event import RunCompletedEvent, RunRetrieverResourceEvent, RunStreamChunkEvent
from core.workflow.utils.variable_template_parser import VariableTemplateParser from core.workflow.utils.variable_template_parser import VariableTemplateParser
from extensions.ext_database import db from extensions.ext_database import db
from factories import file_factory from factories import file_factory
@@ -373,6 +373,12 @@ class ToolNode(BaseNode[ToolNodeData]):
agent_logs.append(agent_log) agent_logs.append(agent_log)
yield agent_log yield agent_log
elif message.type == ToolInvokeMessage.MessageType.RETRIEVER_RESOURCES:
assert isinstance(message.message, ToolInvokeMessage.RetrieverResourceMessage)
yield RunRetrieverResourceEvent(
retriever_resources=message.message.retriever_resources,
context=message.message.context,
)
# Add agent_logs to outputs['json'] to ensure frontend can access thinking process # Add agent_logs to outputs['json'] to ensure frontend can access thinking process
json_output: list[dict[str, Any]] = [] json_output: list[dict[str, Any]] = []

View File

@@ -117,7 +117,7 @@ const Question: FC<QuestionProps> = ({
</div> </div>
<div <div
ref={contentRef} ref={contentRef}
className='bg-background-gradient-bg-fill-chat-bubble-bg-3 w-full rounded-2xl px-4 py-3 text-sm text-text-primary' className='w-full rounded-2xl bg-background-gradient-bg-fill-chat-bubble-bg-3 px-4 py-3 text-sm text-text-primary'
style={theme?.chatBubbleColorStyle ? CssTransform(theme.chatBubbleColorStyle) : {}} style={theme?.chatBubbleColorStyle ? CssTransform(theme.chatBubbleColorStyle) : {}}
> >
{ {

View File

@@ -21,6 +21,7 @@ export enum FormTypeEnum {
toolSelector = 'tool-selector', toolSelector = 'tool-selector',
multiToolSelector = 'array[tools]', multiToolSelector = 'array[tools]',
appSelector = 'app-selector', appSelector = 'app-selector',
any = 'any',
object = 'object', object = 'object',
array = 'array', array = 'array',
dynamicSelect = 'dynamic-select', dynamicSelect = 'dynamic-select',

View File

@@ -21,6 +21,7 @@ import ModelParameterModal from '@/app/components/plugins/plugin-detail-panel/mo
import ToolSelector from '@/app/components/plugins/plugin-detail-panel/tool-selector' import ToolSelector from '@/app/components/plugins/plugin-detail-panel/tool-selector'
import MultipleToolSelector from '@/app/components/plugins/plugin-detail-panel/multiple-tool-selector' import MultipleToolSelector from '@/app/components/plugins/plugin-detail-panel/multiple-tool-selector'
import AppSelector from '@/app/components/plugins/plugin-detail-panel/app-selector' import AppSelector from '@/app/components/plugins/plugin-detail-panel/app-selector'
import VarReferencePicker from '@/app/components/workflow/nodes/_base/components/variable/var-reference-picker'
import RadioE from '@/app/components/base/radio/ui' import RadioE from '@/app/components/base/radio/ui'
import type { import type {
NodeOutPutVar, NodeOutPutVar,
@@ -412,6 +413,38 @@ function Form<
) )
} }
if (formSchema.type === FormTypeEnum.any) {
const {
variable, label, required, scope,
} = formSchema as (CredentialFormSchemaTextInput | CredentialFormSchemaSecretInput)
return (
<div key={variable} className={cn(itemClassName, 'py-3')}>
<div className={cn(fieldLabelClassName, 'system-sm-semibold flex items-center py-2 text-text-secondary')}>
{label[language] || label.en_US}
{required && (
<span className='ml-1 text-red-500'>*</span>
)}
{tooltipContent}
</div>
<VarReferencePicker
zIndex={1001}
readonly={false}
isShowNodeName
nodeId={nodeId || ''}
value={value[variable] || []}
onChange={item => handleFormChange(variable, item as any)}
filterVar={(varPayload) => {
if (!scope) return true
return scope.split('&').includes(varPayload.type)
}}
/>
{fieldMoreInfo?.(formSchema)}
{validating && changeKey === variable && <ValidatingTip />}
</div>
)
}
// @ts-expect-error it work // @ts-expect-error it work
if (!Object.values(FormTypeEnum).includes(formSchema.type)) if (!Object.values(FormTypeEnum).includes(formSchema.type))
return customRenderField?.(formSchema as CustomFormSchema, filteredProps) return customRenderField?.(formSchema as CustomFormSchema, filteredProps)

View File

@@ -48,7 +48,7 @@ const ModelListItem = ({ model, provider, isConfigurable, onConfig, onModifyLoad
<div <div
key={model.model} key={model.model}
className={classNames( className={classNames(
'group flex items-center pl-2 pr-2.5 h-8 rounded-lg', 'group flex h-8 items-center rounded-lg pl-2 pr-2.5',
isConfigurable && 'hover:bg-components-panel-on-panel-item-bg-hover', isConfigurable && 'hover:bg-components-panel-on-panel-item-bg-hover',
model.deprecated && 'opacity-60', model.deprecated && 'opacity-60',
)} )}

View File

@@ -13,8 +13,8 @@ import type { Memory, Var } from '../../types'
import { VarType as VarKindType } from '../../types' import { VarType as VarKindType } from '../../types'
import useAvailableVarList from '../_base/hooks/use-available-var-list' import useAvailableVarList from '../_base/hooks/use-available-var-list'
import produce from 'immer' import produce from 'immer'
import { isSupportMCP } from '@/utils/plugin-version-feature'
import { FormTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' import { FormTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
import { isSupportMCP } from '@/utils/plugin-version-feature'
import { generateAgentToolValue, toolParametersToFormSchemas } from '@/app/components/tools/utils/to-form-schema' import { generateAgentToolValue, toolParametersToFormSchemas } from '@/app/components/tools/utils/to-form-schema'
export type StrategyStatus = { export type StrategyStatus = {
@@ -95,11 +95,20 @@ const useConfig = (id: string, payload: AgentNodeType) => {
) )
return res return res
}, [inputs.agent_parameters, currentStrategy?.parameters]) }, [inputs.agent_parameters, currentStrategy?.parameters])
const getParamVarType = useCallback((paramName: string) => {
const isVariable = currentStrategy?.parameters.some(
param => param.name === paramName && param.type === FormTypeEnum.any,
)
if (isVariable) return VarType.variable
return VarType.constant
}, [currentStrategy?.parameters])
const onFormChange = (value: Record<string, any>) => { const onFormChange = (value: Record<string, any>) => {
const res: ToolVarInputs = {} const res: ToolVarInputs = {}
Object.entries(value).forEach(([key, val]) => { Object.entries(value).forEach(([key, val]) => {
res[key] = { res[key] = {
type: VarType.constant, type: getParamVarType(key),
value: val, value: val,
} }
}) })