mirror of https://github.com/langgenius/dify.git
Merge branch 'main' into kimiwang/web-base-url-251218
This commit is contained in:
commit
a04a930477
|
|
@ -18,34 +18,20 @@ This module provides the interface for invoking and authenticating various model
|
|||
|
||||
- Model provider display
|
||||
|
||||

|
||||
|
||||
Displays a list of all supported providers, including provider names, icons, supported model types list, predefined model list, configuration method, and credentials form rules, etc. For detailed rule design, see: [Schema](./docs/en_US/schema.md).
|
||||
Displays a list of all supported providers, including provider names, icons, supported model types list, predefined model list, configuration method, and credentials form rules, etc.
|
||||
|
||||
- Selectable model list display
|
||||
|
||||

|
||||
|
||||
After configuring provider/model credentials, the dropdown (application orchestration interface/default model) allows viewing of the available LLM list. Greyed out items represent predefined model lists from providers without configured credentials, facilitating user review of supported models.
|
||||
|
||||
In addition, this list also returns configurable parameter information and rules for LLM, as shown below:
|
||||
|
||||

|
||||
|
||||
These parameters are all defined in the backend, allowing different settings for various parameters supported by different models, as detailed in: [Schema](./docs/en_US/schema.md#ParameterRule).
|
||||
In addition, this list also returns configurable parameter information and rules for LLM. These parameters are all defined in the backend, allowing different settings for various parameters supported by different models.
|
||||
|
||||
- Provider/model credential authentication
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
The provider list returns configuration information for the credentials form, which can be authenticated through Runtime's interface. The first image above is a provider credential DEMO, and the second is a model credential DEMO.
|
||||
The provider list returns configuration information for the credentials form, which can be authenticated through Runtime's interface.
|
||||
|
||||
## Structure
|
||||
|
||||

|
||||
|
||||
Model Runtime is divided into three layers:
|
||||
|
||||
- The outermost layer is the factory method
|
||||
|
|
@ -60,9 +46,6 @@ Model Runtime is divided into three layers:
|
|||
|
||||
It offers direct invocation of various model types, predefined model configuration information, getting predefined/remote model lists, model credential authentication methods. Different models provide additional special methods, like LLM's pre-computed tokens method, cost information obtaining method, etc., **allowing horizontal expansion** for different models under the same provider (within supported model types).
|
||||
|
||||
## Next Steps
|
||||
## Documentation
|
||||
|
||||
- Add new provider configuration: [Link](./docs/en_US/provider_scale_out.md)
|
||||
- Add new models for existing providers: [Link](./docs/en_US/provider_scale_out.md#AddModel)
|
||||
- View YAML configuration rules: [Link](./docs/en_US/schema.md)
|
||||
- Implement interface methods: [Link](./docs/en_US/interfaces.md)
|
||||
For detailed documentation on how to add new providers or models, please refer to the [Dify documentation](https://docs.dify.ai/).
|
||||
|
|
|
|||
|
|
@ -18,34 +18,20 @@
|
|||
|
||||
- 模型供应商展示
|
||||
|
||||

|
||||
|
||||
展示所有已支持的供应商列表,除了返回供应商名称、图标之外,还提供了支持的模型类型列表,预定义模型列表、配置方式以及配置凭据的表单规则等等,规则设计详见:[Schema](./docs/zh_Hans/schema.md)。
|
||||
展示所有已支持的供应商列表,除了返回供应商名称、图标之外,还提供了支持的模型类型列表,预定义模型列表、配置方式以及配置凭据的表单规则等等。
|
||||
|
||||
- 可选择的模型列表展示
|
||||
|
||||

|
||||
配置供应商/模型凭据后,可在此下拉(应用编排界面/默认模型)查看可用的 LLM 列表,其中灰色的为未配置凭据供应商的预定义模型列表,方便用户查看已支持的模型。
|
||||
|
||||
配置供应商/模型凭据后,可在此下拉(应用编排界面/默认模型)查看可用的 LLM 列表,其中灰色的为未配置凭据供应商的预定义模型列表,方便用户查看已支持的模型。
|
||||
|
||||
除此之外,该列表还返回了 LLM 可配置的参数信息和规则,如下图:
|
||||
|
||||

|
||||
|
||||
这里的参数均为后端定义,相比之前只有 5 种固定参数,这里可为不同模型设置所支持的各种参数,详见:[Schema](./docs/zh_Hans/schema.md#ParameterRule)。
|
||||
除此之外,该列表还返回了 LLM 可配置的参数信息和规则。这里的参数均为后端定义,相比之前只有 5 种固定参数,这里可为不同模型设置所支持的各种参数。
|
||||
|
||||
- 供应商/模型凭据鉴权
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
供应商列表返回了凭据表单的配置信息,可通过 Runtime 提供的接口对凭据进行鉴权,上图 1 为供应商凭据 DEMO,上图 2 为模型凭据 DEMO。
|
||||
供应商列表返回了凭据表单的配置信息,可通过 Runtime 提供的接口对凭据进行鉴权。
|
||||
|
||||
## 结构
|
||||
|
||||

|
||||
|
||||
Model Runtime 分三层:
|
||||
|
||||
- 最外层为工厂方法
|
||||
|
|
@ -59,8 +45,7 @@ Model Runtime 分三层:
|
|||
对于供应商/模型凭据,有两种情况
|
||||
|
||||
- 如 OpenAI 这类中心化供应商,需要定义如**api_key**这类的鉴权凭据
|
||||
- 如[**Xinference**](https://github.com/xorbitsai/inference)这类本地部署的供应商,需要定义如**server_url**这类的地址凭据,有时候还需要定义**model_uid**之类的模型类型凭据,就像下面这样,当在供应商层定义了这些凭据后,就可以在前端页面上直接展示,无需修改前端逻辑。
|
||||

|
||||
- 如[**Xinference**](https://github.com/xorbitsai/inference)这类本地部署的供应商,需要定义如**server_url**这类的地址凭据,有时候还需要定义**model_uid**之类的模型类型凭据。当在供应商层定义了这些凭据后,就可以在前端页面上直接展示,无需修改前端逻辑。
|
||||
|
||||
当配置好凭据后,就可以通过 DifyRuntime 的外部接口直接获取到对应供应商所需要的**Schema**(凭据表单规则),从而在可以在不修改前端逻辑的情况下,提供新的供应商/模型的支持。
|
||||
|
||||
|
|
@ -74,20 +59,6 @@ Model Runtime 分三层:
|
|||
|
||||
- 模型凭据 (**在供应商层定义**):这是一类不经常变动,一般在配置好后就不会再变动的参数,如 **api_key**、**server_url** 等。在 DifyRuntime 中,他们的参数名一般为**credentials: dict[str, any]**,Provider 层的 credentials 会直接被传递到这一层,不需要再单独定义。
|
||||
|
||||
## 下一步
|
||||
## 文档
|
||||
|
||||
### [增加新的供应商配置 👈🏻](./docs/zh_Hans/provider_scale_out.md)
|
||||
|
||||
当添加后,这里将会出现一个新的供应商
|
||||
|
||||

|
||||
|
||||
### [为已存在的供应商新增模型 👈🏻](./docs/zh_Hans/provider_scale_out.md#%E5%A2%9E%E5%8A%A0%E6%A8%A1%E5%9E%8B)
|
||||
|
||||
当添加后,对应供应商的模型列表中将会出现一个新的预定义模型供用户选择,如 GPT-3.5 GPT-4 ChatGLM3-6b 等,而对于支持自定义模型的供应商,则不需要新增模型。
|
||||
|
||||

|
||||
|
||||
### [接口的具体实现 👈🏻](./docs/zh_Hans/interfaces.md)
|
||||
|
||||
你可以在这里找到你想要查看的接口的具体实现,以及接口的参数和返回值的具体含义。
|
||||
有关如何添加新供应商或模型的详细文档,请参阅 [Dify 文档](https://docs.dify.ai/)。
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import {
|
|||
isMermaidCodeComplete,
|
||||
prepareMermaidCode,
|
||||
processSvgForTheme,
|
||||
sanitizeMermaidCode,
|
||||
svgToBase64,
|
||||
waitForDOMElement,
|
||||
} from './utils'
|
||||
|
|
@ -71,7 +72,7 @@ const initMermaid = () => {
|
|||
const config: MermaidConfig = {
|
||||
startOnLoad: false,
|
||||
fontFamily: 'sans-serif',
|
||||
securityLevel: 'loose',
|
||||
securityLevel: 'strict',
|
||||
flowchart: {
|
||||
htmlLabels: true,
|
||||
useMaxWidth: true,
|
||||
|
|
@ -267,6 +268,8 @@ const Flowchart = (props: FlowchartProps) => {
|
|||
finalCode = prepareMermaidCode(primitiveCode, look)
|
||||
}
|
||||
|
||||
finalCode = sanitizeMermaidCode(finalCode)
|
||||
|
||||
// Step 2: Render chart
|
||||
const svgGraph = await renderMermaidChart(finalCode, look)
|
||||
|
||||
|
|
@ -297,9 +300,9 @@ const Flowchart = (props: FlowchartProps) => {
|
|||
const configureMermaid = useCallback((primitiveCode: string) => {
|
||||
if (typeof window !== 'undefined' && isInitialized) {
|
||||
const themeVars = THEMES[currentTheme]
|
||||
const config: any = {
|
||||
const config: MermaidConfig = {
|
||||
startOnLoad: false,
|
||||
securityLevel: 'loose',
|
||||
securityLevel: 'strict',
|
||||
fontFamily: 'sans-serif',
|
||||
maxTextSize: 50000,
|
||||
gantt: {
|
||||
|
|
@ -325,7 +328,8 @@ const Flowchart = (props: FlowchartProps) => {
|
|||
config.theme = currentTheme === 'dark' ? 'dark' : 'neutral'
|
||||
|
||||
if (isFlowchart) {
|
||||
config.flowchart = {
|
||||
type FlowchartConfigWithRanker = NonNullable<MermaidConfig['flowchart']> & { ranker?: string }
|
||||
const flowchartConfig: FlowchartConfigWithRanker = {
|
||||
htmlLabels: true,
|
||||
useMaxWidth: true,
|
||||
nodeSpacing: 60,
|
||||
|
|
@ -333,6 +337,7 @@ const Flowchart = (props: FlowchartProps) => {
|
|||
curve: 'linear',
|
||||
ranker: 'tight-tree',
|
||||
}
|
||||
config.flowchart = flowchartConfig as unknown as MermaidConfig['flowchart']
|
||||
}
|
||||
|
||||
if (currentTheme === 'dark') {
|
||||
|
|
@ -531,7 +536,7 @@ const Flowchart = (props: FlowchartProps) => {
|
|||
|
||||
{isLoading && !svgString && (
|
||||
<div className='px-[26px] py-4'>
|
||||
<LoadingAnim type='text'/>
|
||||
<LoadingAnim type='text' />
|
||||
<div className="mt-2 text-sm text-gray-500">
|
||||
{t('common.wait_for_completion', 'Waiting for diagram code to complete...')}
|
||||
</div>
|
||||
|
|
@ -564,7 +569,7 @@ const Flowchart = (props: FlowchartProps) => {
|
|||
{errMsg && (
|
||||
<div className={themeClasses.errorMessage}>
|
||||
<div className="flex items-center">
|
||||
<ExclamationTriangleIcon className={themeClasses.errorIcon}/>
|
||||
<ExclamationTriangleIcon className={themeClasses.errorIcon} />
|
||||
<span className="ml-2">{errMsg}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { cleanUpSvgCode } from './utils'
|
||||
import { cleanUpSvgCode, prepareMermaidCode, sanitizeMermaidCode } from './utils'
|
||||
|
||||
describe('cleanUpSvgCode', () => {
|
||||
it('replaces old-style <br> tags with the new style', () => {
|
||||
|
|
@ -6,3 +6,54 @@ describe('cleanUpSvgCode', () => {
|
|||
expect(result).toEqual('<br/>test<br/>')
|
||||
})
|
||||
})
|
||||
|
||||
describe('sanitizeMermaidCode', () => {
|
||||
it('removes click directives to prevent link/callback injection', () => {
|
||||
const unsafeProtocol = ['java', 'script:'].join('')
|
||||
const input = [
|
||||
'gantt',
|
||||
'title Demo',
|
||||
'section S1',
|
||||
'Task 1 :a1, 2020-01-01, 1d',
|
||||
`click A href "${unsafeProtocol}alert(location.href)"`,
|
||||
'click B call callback()',
|
||||
].join('\n')
|
||||
|
||||
const result = sanitizeMermaidCode(input)
|
||||
|
||||
expect(result).toContain('gantt')
|
||||
expect(result).toContain('Task 1')
|
||||
expect(result).not.toContain('click A')
|
||||
expect(result).not.toContain('click B')
|
||||
expect(result).not.toContain(unsafeProtocol)
|
||||
})
|
||||
|
||||
it('removes Mermaid init directives to prevent config overrides', () => {
|
||||
const input = [
|
||||
'%%{init: {"securityLevel":"loose"}}%%',
|
||||
'graph TD',
|
||||
'A-->B',
|
||||
].join('\n')
|
||||
|
||||
const result = sanitizeMermaidCode(input)
|
||||
|
||||
expect(result).toEqual(['graph TD', 'A-->B'].join('\n'))
|
||||
})
|
||||
})
|
||||
|
||||
describe('prepareMermaidCode', () => {
|
||||
it('sanitizes click directives in flowcharts', () => {
|
||||
const unsafeProtocol = ['java', 'script:'].join('')
|
||||
const input = [
|
||||
'graph TD',
|
||||
'A[Click]-->B',
|
||||
`click A href "${unsafeProtocol}alert(1)"`,
|
||||
].join('\n')
|
||||
|
||||
const result = prepareMermaidCode(input, 'classic')
|
||||
|
||||
expect(result).toContain('graph TD')
|
||||
expect(result).not.toContain('click ')
|
||||
expect(result).not.toContain(unsafeProtocol)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -2,6 +2,28 @@ export function cleanUpSvgCode(svgCode: string): string {
|
|||
return svgCode.replaceAll('<br>', '<br/>')
|
||||
}
|
||||
|
||||
export const sanitizeMermaidCode = (mermaidCode: string): string => {
|
||||
if (!mermaidCode || typeof mermaidCode !== 'string')
|
||||
return ''
|
||||
|
||||
return mermaidCode
|
||||
.split('\n')
|
||||
.filter((line) => {
|
||||
const trimmed = line.trimStart()
|
||||
|
||||
// Mermaid directives can override config; treat as untrusted in chat context.
|
||||
if (trimmed.startsWith('%%{'))
|
||||
return false
|
||||
|
||||
// Mermaid click directives can create JS callbacks/links inside rendered SVG.
|
||||
if (trimmed.startsWith('click '))
|
||||
return false
|
||||
|
||||
return true
|
||||
})
|
||||
.join('\n')
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares mermaid code for rendering by sanitizing common syntax issues.
|
||||
* @param {string} mermaidCode - The mermaid code to prepare
|
||||
|
|
@ -12,10 +34,7 @@ export const prepareMermaidCode = (mermaidCode: string, style: 'classic' | 'hand
|
|||
if (!mermaidCode || typeof mermaidCode !== 'string')
|
||||
return ''
|
||||
|
||||
let code = mermaidCode.trim()
|
||||
|
||||
// Security: Sanitize against javascript: protocol in click events (XSS vector)
|
||||
code = code.replace(/(\bclick\s+\w+\s+")javascript:[^"]*(")/g, '$1#$2')
|
||||
let code = sanitizeMermaidCode(mermaidCode.trim())
|
||||
|
||||
// Convenience: Basic BR replacement. This is a common and safe operation.
|
||||
code = code.replace(/<br\s*\/?>/g, '\n')
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import React, { useMemo, useState } from 'react'
|
|||
import { useTranslation } from 'react-i18next'
|
||||
import { produce } from 'immer'
|
||||
import type { Emoji, WorkflowToolProviderOutputParameter, WorkflowToolProviderParameter, WorkflowToolProviderRequest } from '../types'
|
||||
import { buildWorkflowOutputParameters } from './utils'
|
||||
import cn from '@/utils/classnames'
|
||||
import Drawer from '@/app/components/base/drawer-plus'
|
||||
import Input from '@/app/components/base/input'
|
||||
|
|
@ -47,7 +48,9 @@ const WorkflowToolAsModal: FC<Props> = ({
|
|||
const [name, setName] = useState(payload.name)
|
||||
const [description, setDescription] = useState(payload.description)
|
||||
const [parameters, setParameters] = useState<WorkflowToolProviderParameter[]>(payload.parameters)
|
||||
const outputParameters = useMemo<WorkflowToolProviderOutputParameter[]>(() => payload.outputParameters, [payload.outputParameters])
|
||||
const rawOutputParameters = payload.outputParameters
|
||||
const outputSchema = payload.tool?.output_schema
|
||||
const outputParameters = useMemo<WorkflowToolProviderOutputParameter[]>(() => buildWorkflowOutputParameters(rawOutputParameters, outputSchema), [rawOutputParameters, outputSchema])
|
||||
const reservedOutputParameters: WorkflowToolProviderOutputParameter[] = [
|
||||
{
|
||||
name: 'text',
|
||||
|
|
|
|||
|
|
@ -0,0 +1,47 @@
|
|||
import { VarType } from '@/app/components/workflow/types'
|
||||
import type { WorkflowToolProviderOutputParameter, WorkflowToolProviderOutputSchema } from '../types'
|
||||
import { buildWorkflowOutputParameters } from './utils'
|
||||
|
||||
describe('buildWorkflowOutputParameters', () => {
|
||||
it('returns provided output parameters when array input exists', () => {
|
||||
const params: WorkflowToolProviderOutputParameter[] = [
|
||||
{ name: 'text', description: 'final text', type: VarType.string },
|
||||
]
|
||||
|
||||
const result = buildWorkflowOutputParameters(params, null)
|
||||
|
||||
expect(result).toBe(params)
|
||||
})
|
||||
|
||||
it('derives parameters from schema when explicit array missing', () => {
|
||||
const schema: WorkflowToolProviderOutputSchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
answer: {
|
||||
type: VarType.string,
|
||||
description: 'AI answer',
|
||||
},
|
||||
attachments: {
|
||||
type: VarType.arrayFile,
|
||||
description: 'Supporting files',
|
||||
},
|
||||
unknown: {
|
||||
type: 'custom',
|
||||
description: 'Unsupported type',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const result = buildWorkflowOutputParameters(undefined, schema)
|
||||
|
||||
expect(result).toEqual([
|
||||
{ name: 'answer', description: 'AI answer', type: VarType.string },
|
||||
{ name: 'attachments', description: 'Supporting files', type: VarType.arrayFile },
|
||||
{ name: 'unknown', description: 'Unsupported type', type: undefined },
|
||||
])
|
||||
})
|
||||
|
||||
it('returns empty array when no source information is provided', () => {
|
||||
expect(buildWorkflowOutputParameters(null, null)).toEqual([])
|
||||
})
|
||||
})
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
import type { WorkflowToolProviderOutputParameter, WorkflowToolProviderOutputSchema } from '../types'
|
||||
import { VarType } from '@/app/components/workflow/types'
|
||||
|
||||
const validVarTypes = new Set<string>(Object.values(VarType))
|
||||
|
||||
const normalizeVarType = (type?: string): VarType | undefined => {
|
||||
if (!type)
|
||||
return undefined
|
||||
|
||||
return validVarTypes.has(type) ? type as VarType : undefined
|
||||
}
|
||||
|
||||
export const buildWorkflowOutputParameters = (
|
||||
outputParameters: WorkflowToolProviderOutputParameter[] | null | undefined,
|
||||
outputSchema?: WorkflowToolProviderOutputSchema | null,
|
||||
): WorkflowToolProviderOutputParameter[] => {
|
||||
if (Array.isArray(outputParameters))
|
||||
return outputParameters
|
||||
|
||||
if (!outputSchema?.properties)
|
||||
return []
|
||||
|
||||
return Object.entries(outputSchema.properties).map(([name, schema]) => ({
|
||||
name,
|
||||
description: schema.description,
|
||||
type: normalizeVarType(schema.type),
|
||||
}))
|
||||
}
|
||||
Loading…
Reference in New Issue