Skip to content

Commit

Permalink
🚧 wip: dify
Browse files Browse the repository at this point in the history
  • Loading branch information
cy948 committed Oct 17, 2024
1 parent 41c55f8 commit ac00cba
Show file tree
Hide file tree
Showing 18 changed files with 348 additions and 3 deletions.
1 change: 1 addition & 0 deletions src/app/(backend)/webapi/chat/[provider]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export const runtime = 'edge';

export const POST = checkAuth(async (req: Request, { params, jwtPayload, createRuntime }) => {
const { provider } = params;
console.log(jwtPayload);

try {
// ============ 1. init chat model ============ //
Expand Down
8 changes: 7 additions & 1 deletion src/const/settings/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ export const DEFAULT_AGENT_CHAT_CONFIG: LobeAgentChatConfig = {

export const DEFAULT_AGENT_CONFIG: LobeAgentConfig = {
chatConfig: DEFAULT_AGENT_CHAT_CONFIG,
dify: {
baseUrl: 'https://api.dify.ai/v1',
enabled: false,
token: '',
userId: 'devTest',
},
model: DEFAULT_MODEL,
params: {
frequency_penalty: 0,
Expand All @@ -32,7 +38,7 @@ export const DEFAULT_AGENT_CONFIG: LobeAgentConfig = {
plugins: [],
provider: ModelProvider.OpenAI,
systemRole: '',
tts: DEFAUTT_AGENT_TTS_CONFIG,
tts: DEFAUTT_AGENT_TTS_CONFIG
};

export const DEFAULT_AGENT: UserDefaultAgent = {
Expand Down
2 changes: 2 additions & 0 deletions src/features/AgentSetting/AgentSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import AgentModal from './AgentModal';
import AgentPlugin from './AgentPlugin';
import AgentPrompt from './AgentPrompt';
import AgentTTS from './AgentTTS';
import Dify from './Dify'
import StoreUpdater, { StoreUpdaterProps } from './StoreUpdater';
import { Provider, createStore } from './store';

Expand All @@ -19,6 +20,7 @@ export const AgentSettings = (props: AgentSettingsProps) => {
<AgentModal />
<AgentTTS />
<AgentPlugin />
<Dify />
</Provider>
);
};
91 changes: 91 additions & 0 deletions src/features/AgentSetting/Dify/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
'use client';

import { Form, type ItemGroup, Input } from '@lobehub/ui';
import { Switch } from 'antd';
import { memo, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';

import { FORM_STYLE } from '@/const/layoutTokens';

import { useStore } from '../store';
import { useUserStore } from '@/store/user';

const AgentMeta = memo(() => {
const { t } = useTranslation('setting');

const [setAgentConfig, agentConfig ] = useStore((s) => [
s.setAgentConfig,
s.config
]);

const updateKeyVaultConfig = useUserStore((s)=>s.updateKeyVaultConfig)

const [difyBaseUrl, setDifyBaseUrl] = useState<string>('https://api.dify.ai/v1')
const [difyToken, setDifyToken] = useState<string>('app-1WHrUsnegCQqYgSjN952dHyt')
const [difyUserId, setDifyUserId] = useState<string>('dev')
const [difyEnabled, setDifyEnable] = useState<boolean>(true)

useEffect(() => {
setAgentConfig({
dify: {
baseUrl: difyBaseUrl,
token: difyToken,
userId: difyUserId,
enabled: difyEnabled,
},
provider: 'dify',
model: 'Dofy Workflow'
})
updateKeyVaultConfig('dify', {
baseUrl: difyBaseUrl,
token: difyToken,
userId: difyUserId,
})
}, [difyBaseUrl, difyToken, difyUserId, difyEnabled])

const metaData: ItemGroup = {
children: [
{
children: (
<Input
onChange={(event) => setDifyBaseUrl(event.currentTarget.value)}
placeholder='https://api.dify.ai/v1'
value={agentConfig.dify.baseUrl} />
),
label: 'BaseUrl'
},
{
children: (
<Input
onChange={(event) => setDifyToken(event.currentTarget.value)}
value={agentConfig.dify.token}
/>
),
label: 'Token'
},
{
children: (
<Input
onChange={(event) => setDifyUserId(event.currentTarget.value)}
value={agentConfig.dify.userId}
/>
),
label: 'UserId'
},
{
children: (
<Switch
onChange={setDifyEnable}
value={agentConfig.dify.enabled}
/>
),
label: 'BaseUrl'
}
],
title: t('settingAgent.title'),
};

return <Form items={[metaData]} itemsType={'group'} variant={'pure'} {...FORM_STYLE} />;
});

export default AgentMeta;
3 changes: 2 additions & 1 deletion src/features/AgentSetting/store/action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ export interface Action extends PublicAction {
resetAgentMeta: () => void;
setAgentConfig: (config: DeepPartial<LobeAgentConfig>) => void;
setAgentMeta: (meta: Partial<MetaData>) => void;

setChatConfig: (config: Partial<LobeAgentChatConfig>) => void;

streamUpdateMetaArray: (key: keyof MetaData) => any;
streamUpdateMetaString: (key: keyof MetaData) => any;
toggleAgentPlugin: (pluginId: string, state?: boolean) => void;
Expand Down Expand Up @@ -291,6 +291,7 @@ export const store: StateCreator<Store, [['zustand/devtools', never]]> = (set, g
};
},


toggleAgentPlugin: (id, state) => {
get().dispatchConfig({ pluginId: id, state, type: 'togglePlugin' });
},
Expand Down
8 changes: 8 additions & 0 deletions src/libs/agent-runtime/AgentRuntime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import {
import { LobeUpstageAI } from './upstage';
import { LobeZeroOneAI } from './zeroone';
import { LobeZhipuAI } from './zhipu';
import LobeDify from './dify';

export interface AgentChatOptions {
enableTrace?: boolean;
Expand Down Expand Up @@ -154,6 +155,7 @@ class AgentRuntime {
upstage: Partial<ClientOptions>;
zeroone: Partial<ClientOptions>;
zhipu: Partial<ClientOptions>;
dify: Partial<{ baseUrl: string; token: string; userId: string; conversation_id: string}>;
}>,
) {
let runtimeModel: LobeRuntimeAI;
Expand Down Expand Up @@ -314,6 +316,12 @@ class AgentRuntime {
runtimeModel = new LobeHunyuanAI(params.hunyuan);
break;
}

case ModelProvider.Dify: {
console.log('[initializeWithProviderOptions]: ', params.dify);
runtimeModel = new LobeDify(params.dify || {})
break
}
}

return new AgentRuntime(runtimeModel);
Expand Down
82 changes: 82 additions & 0 deletions src/libs/agent-runtime/dify/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { ChatClient } from 'dify-client'
import { LobeRuntimeAI } from '../BaseAI';
import { AgentRuntimeErrorType } from '../error';
import { ChatCompetitionOptions, ChatStreamPayload, ModelProvider } from '../types';
import { AgentRuntimeError } from '../utils/createError';
import { StreamingResponse } from '../utils/response';
import { DifyStream } from '../utils/streams/dify';
import urlJoin from 'url-join';

export interface DifyParams {
baseUrl: string
token?: string
userId: string
conversation_id?: string
}

export class LobeDify implements LobeRuntimeAI {
client: ChatClient;
difyParams: DifyParams

constructor({ baseUrl, token, userId, conversation_id }: Partial<DifyParams>) {
console.log('[LobeDify]: ', {baseUrl, token, userId, conversation_id});

if (!(userId && token))
throw AgentRuntimeError.createError(AgentRuntimeErrorType.InvalidProviderAPIKey);

this.difyParams = {
baseUrl: baseUrl ?? 'https://api.dify.ai/v1',
conversation_id,
userId,
token,
}
this.client = new ChatClient(token, this.difyParams.baseUrl)
}

async chat(payload: ChatStreamPayload, options?: ChatCompetitionOptions) {
const { messages } = payload
// Get the last message as query
const query = messages.at(-1)
if (!query) {
throw new Error('[Dify]: No query')
}
let textQuery = ''
if (typeof query.content === 'string')
textQuery = query.content
else
throw new Error('[Dify]: Unsupport user message type')

console.log('[Dify]: textQuery', textQuery);

const response = await fetch(urlJoin(this.difyParams.baseUrl, '/chat-messages'), {
method: 'POST',
// signal: options?.signal,
headers: {
Authorization: `Bearer ${this.difyParams.token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
query: textQuery,
user: this.difyParams.userId,
conversation_id: this.difyParams?.conversation_id ?? '',
response_mode: 'streaming',
inputs: [],
files: [],
})
})
if (!response.body || !response.ok) {
throw AgentRuntimeError.chat({
error: {
status: response.status,
statusText: response.statusText,
},
errorType: AgentRuntimeErrorType.ProviderBizError,
provider: ModelProvider.Dify,
});
}
const [prod, _] = response.body.tee();
return StreamingResponse(DifyStream(prod), { headers: options?.headers });
}
}

export default LobeDify;
1 change: 1 addition & 0 deletions src/libs/agent-runtime/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@ export * from './types';
export { AgentRuntimeError } from './utils/createError';
export { LobeZeroOneAI } from './zeroone';
export { LobeZhipuAI } from './zhipu';
export { LobeDify } from './dify'
1 change: 1 addition & 0 deletions src/libs/agent-runtime/types/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export enum ModelProvider {
Wenxin = 'wenxin',
ZeroOne = 'zeroone',
ZhiPu = 'zhipu',
Dify = 'dify'
}

export type ModelProviderKey = Lowercase<keyof typeof ModelProvider>;
67 changes: 67 additions & 0 deletions src/libs/agent-runtime/utils/streams/dify.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { convertIterableToStream, createCallbacksTransformer, createSSEProtocolTransformer, StreamProtocolChunk, StreamStack } from ".";
import { ChatStreamCallbacks } from "../..";

interface DifyChunk {
event: string;
task_id?: string;
answer?: string;
message?: string; // 错误信息
message_id?: string;
id?: string
}

const processDifyData = (buffer: string): DifyChunk => {
try {
// Remove the prefix `data:`
if (buffer.startsWith('data:'))
return JSON.parse(buffer.slice(5).trim()) as DifyChunk
return JSON.parse(buffer.trim())
} catch (error) {
console.error('[Dify - transformDifyStream - processDifyData]: ', {
error,
buffer,
})
}
return { raw: buffer } as any
}

export const transformDifyStream = (buffer: Uint8Array): StreamProtocolChunk => {
const decoder = new TextDecoder()
const chunk = processDifyData(decoder.decode(buffer, { stream: true }))
// console.log('[transformDifyStrem]: ', chunk);
let type: StreamProtocolChunk['type'] = 'text'
switch(chunk.event){
case 'message_end':
type = 'stop'
break;
case 'message':
type = 'text'
break;
case 'message_replace':
type = 'text'
break;
case 'workflow_started':
type = 'tool_calls'
break;
case 'node_started':
type = 'tool_calls'
break;
case 'node_finished ':
type = 'tool_calls'
break
case 'workflow_finished':
type = 'tool_calls'
break;
}
return {
id: chunk?.task_id ?? chunk?.id,
data: chunk,
type: type,
}
}

export const DifyStream = (stream: ReadableStream, callbacks?: ChatStreamCallbacks) => {
return stream
.pipeThrough(createSSEProtocolTransformer(transformDifyStream))
.pipeThrough(createCallbacksTransformer(callbacks));
};
3 changes: 3 additions & 0 deletions src/server/modules/AgentRuntime/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,9 @@ const getLlmOptionsFromPayload = (provider: string, payload: JWTPayload) => {

return { apiKey };
}
case ModelProvider.Dify: {
return payload
}
}
};

Expand Down
9 changes: 9 additions & 0 deletions src/services/_auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,15 @@ export const getProviderAuthPayload = (provider: string) => {
return { endpoint: config?.baseURL };
}

case ModelProvider.Dify: {
const { token, baseUrl, userId } = keyVaultsConfigSelectors.difyConfig(useUserStore.getState())
return {
token,
baseUrl,
userId,
}
}

default: {
const config = keyVaultsConfigSelectors.getVaultByProvider(provider as GlobalLLMProviderKey)(
useUserStore.getState(),
Expand Down
8 changes: 8 additions & 0 deletions src/services/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,14 @@ export function initializeWithClientStore(provider: string, payload: any) {
case ModelProvider.ZeroOne: {
break;
}
case ModelProvider.Dify: {
providerOptions = {
token: providerAuthPayload?.apiKey,
baseUrl: providerAuthPayload?.endpoint,
userId: providerAuthPayload?.userId,
}
break;
}
}

/**
Expand Down
Loading

0 comments on commit ac00cba

Please sign in to comment.