mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-18 09:20:15 +01:00
🅰️ feat: Azure OpenAI Assistants API Support (#1992)
* chore: rename dir from `assistant` to plural * feat: `assistants` field for azure config, spread options in AppService * refactor: rename constructAzureURL param for azure as `azureOptions` * chore: bump openai and bun * chore(loadDefaultModels): change naming of assistant -> assistants * feat: load azure settings with currect baseURL for assistants' initializeClient * refactor: add `assistants` flags to groups and model configs, add mapGroupToAzureConfig * feat(loadConfigEndpoints): initialize assistants endpoint if azure flag `assistants` is enabled * feat(AppService): determine assistant models on startup, throw Error if none * refactor(useDeleteAssistantMutation): send model along with assistant id for delete mutations * feat: support listing and deleting assistants with azure * feat: add model query to assistant avatar upload * feat: add azure support for retrieveRun method * refactor: update OpenAIClient initialization * chore: update README * fix(ci): tests passing * refactor(uploadOpenAIFile): improve logging and use more efficient REST API method * refactor(useFileHandling): add model to metadata to target Azure region compatible with current model * chore(files): add azure naming pattern for valid file id recognition * fix(assistants): initialize openai with first available assistant model if none provided * refactor(uploadOpenAIFile): add content type for azure, initialize formdata before azure options * refactor(sleep): move sleep function out of Runs and into `~/server/utils` * fix(azureOpenAI/assistants): make sure to only overwrite models with assistant models if `assistants` flag is enabled * refactor(uploadOpenAIFile): revert to old method * chore(uploadOpenAIFile): use enum for file purpose * docs: azureOpenAI update guide with more info, examples * feat: enable/disable assistant capabilities and specify retrieval models * refactor: optional chain conditional statement in loadConfigModels.js * docs: add assistants examples * chore: update librechat.example.yaml * docs(azure): update note of file upload behavior in Azure OpenAI Assistants * chore: update docs and add descriptive message about assistant errors * fix: prevent message submission with invalid assistant or if files loading * style: update Landing icon & text when assistant is not selected * chore: bump librechat-data-provider to 0.4.8 * fix(assistants/azure): assign req.body.model for proper azure init to abort runs
This commit is contained in:
parent
1b243c6f8c
commit
5cd5c3bef8
60 changed files with 1044 additions and 300 deletions
|
|
@ -1,17 +1,17 @@
|
|||
import { useState, useMemo, useEffect } from 'react';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { useGetModelsQuery } from 'librechat-data-provider/react-query';
|
||||
import { useForm, FormProvider, Controller, useWatch } from 'react-hook-form';
|
||||
import { useGetModelsQuery, useGetEndpointsQuery } from 'librechat-data-provider/react-query';
|
||||
import {
|
||||
Tools,
|
||||
QueryKeys,
|
||||
Capabilities,
|
||||
EModelEndpoint,
|
||||
actionDelimiter,
|
||||
supportsRetrieval,
|
||||
defaultAssistantFormValues,
|
||||
} from 'librechat-data-provider';
|
||||
import type { FunctionTool, TPlugin } from 'librechat-data-provider';
|
||||
import type { AssistantForm, AssistantPanelProps } from '~/common';
|
||||
import type { FunctionTool, TPlugin, TEndpointsConfig } from 'librechat-data-provider';
|
||||
import { useCreateAssistantMutation, useUpdateAssistantMutation } from '~/data-provider';
|
||||
import { SelectDropDown, Checkbox, QuestionMark } from '~/components/ui';
|
||||
import { useAssistantsMapContext, useToastContext } from '~/Providers';
|
||||
|
|
@ -42,7 +42,7 @@ export default function AssistantPanel({
|
|||
const queryClient = useQueryClient();
|
||||
const modelsQuery = useGetModelsQuery();
|
||||
const assistantMap = useAssistantsMapContext();
|
||||
const [showToolDialog, setShowToolDialog] = useState(false);
|
||||
const { data: endpointsConfig = {} as TEndpointsConfig } = useGetEndpointsQuery();
|
||||
const allTools = queryClient.getQueryData<TPlugin[]>([QueryKeys.tools]) ?? [];
|
||||
const { onSelect: onSelectAssistant } = useSelectAssistant();
|
||||
const { showToast } = useToastContext();
|
||||
|
|
@ -51,17 +51,43 @@ export default function AssistantPanel({
|
|||
const methods = useForm<AssistantForm>({
|
||||
defaultValues: defaultAssistantFormValues,
|
||||
});
|
||||
|
||||
const [showToolDialog, setShowToolDialog] = useState(false);
|
||||
|
||||
const { control, handleSubmit, reset, setValue, getValues } = methods;
|
||||
const assistant_id = useWatch({ control, name: 'id' });
|
||||
const assistant = useWatch({ control, name: 'assistant' });
|
||||
const functions = useWatch({ control, name: 'functions' });
|
||||
const assistant_id = useWatch({ control, name: 'id' });
|
||||
const model = useWatch({ control, name: 'model' });
|
||||
|
||||
const activeModel = useMemo(() => {
|
||||
return assistantMap?.[assistant_id]?.model;
|
||||
}, [assistantMap, assistant_id]);
|
||||
|
||||
const assistants = useMemo(() => endpointsConfig?.[EModelEndpoint.assistants], [endpointsConfig]);
|
||||
const retrievalModels = useMemo(() => new Set(assistants?.retrievalModels ?? []), [assistants]);
|
||||
const toolsEnabled = useMemo(
|
||||
() => assistants?.capabilities?.includes(Capabilities.tools),
|
||||
[assistants],
|
||||
);
|
||||
const actionsEnabled = useMemo(
|
||||
() => assistants?.capabilities?.includes(Capabilities.actions),
|
||||
[assistants],
|
||||
);
|
||||
const retrievalEnabled = useMemo(
|
||||
() => assistants?.capabilities?.includes(Capabilities.retrieval),
|
||||
[assistants],
|
||||
);
|
||||
const codeEnabled = useMemo(
|
||||
() => assistants?.capabilities?.includes(Capabilities.code_interpreter),
|
||||
[assistants],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (model && !supportsRetrieval.has(model)) {
|
||||
setValue('retrieval', false);
|
||||
if (model && !retrievalModels.has(model)) {
|
||||
setValue(Capabilities.retrieval, false);
|
||||
}
|
||||
}, [model, setValue]);
|
||||
}, [model, setValue, retrievalModels]);
|
||||
|
||||
/* Mutations */
|
||||
const update = useUpdateAssistantMutation({
|
||||
|
|
@ -300,78 +326,96 @@ export default function AssistantPanel({
|
|||
/>
|
||||
</div>
|
||||
{/* Knowledge */}
|
||||
<Knowledge assistant_id={assistant_id} files={files} />
|
||||
{(codeEnabled || retrievalEnabled) && (
|
||||
<Knowledge assistant_id={assistant_id} files={files} />
|
||||
)}
|
||||
{/* Capabilities */}
|
||||
<div className="mb-6">
|
||||
<div className="mb-1.5 flex items-center">
|
||||
<span>
|
||||
<label className="text-token-text-primary block font-medium">Capabilities</label>
|
||||
<label className="text-token-text-primary block font-medium">
|
||||
{localize('com_assistants_capabilities')}
|
||||
</label>
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex flex-col items-start gap-2">
|
||||
<div className="flex items-center">
|
||||
<Controller
|
||||
name={'code_interpreter'}
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<Checkbox
|
||||
{...field}
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
className="relative float-left mr-2 inline-flex h-4 w-4 cursor-pointer"
|
||||
value={field?.value?.toString()}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<label
|
||||
className="form-check-label text-token-text-primary w-full cursor-pointer"
|
||||
htmlFor="code_interpreter"
|
||||
onClick={() =>
|
||||
setValue('code_interpreter', !getValues('code_interpreter'), {
|
||||
shouldDirty: true,
|
||||
})
|
||||
}
|
||||
>
|
||||
<div className="flex items-center">
|
||||
{localize('com_assistants_code_interpreter')}
|
||||
<QuestionMark />
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<Controller
|
||||
name={'retrieval'}
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<Checkbox
|
||||
{...field}
|
||||
checked={field.value}
|
||||
disabled={!supportsRetrieval.has(model)}
|
||||
onCheckedChange={field.onChange}
|
||||
className="relative float-left mr-2 inline-flex h-4 w-4 cursor-pointer"
|
||||
value={field?.value?.toString()}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<label
|
||||
className={cn(
|
||||
'form-check-label text-token-text-primary w-full',
|
||||
!supportsRetrieval.has(model) ? 'cursor-no-drop opacity-50' : 'cursor-pointer',
|
||||
)}
|
||||
htmlFor="retrieval"
|
||||
onClick={() =>
|
||||
supportsRetrieval.has(model) &&
|
||||
setValue('retrieval', !getValues('retrieval'), { shouldDirty: true })
|
||||
}
|
||||
>
|
||||
{localize('com_assistants_retrieval')}
|
||||
</label>
|
||||
</div>
|
||||
{codeEnabled && (
|
||||
<div className="flex items-center">
|
||||
<Controller
|
||||
name={Capabilities.code_interpreter}
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<Checkbox
|
||||
{...field}
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
className="relative float-left mr-2 inline-flex h-4 w-4 cursor-pointer"
|
||||
value={field?.value?.toString()}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<label
|
||||
className="form-check-label text-token-text-primary w-full cursor-pointer"
|
||||
htmlFor={Capabilities.code_interpreter}
|
||||
onClick={() =>
|
||||
setValue(
|
||||
Capabilities.code_interpreter,
|
||||
!getValues(Capabilities.code_interpreter),
|
||||
{
|
||||
shouldDirty: true,
|
||||
},
|
||||
)
|
||||
}
|
||||
>
|
||||
<div className="flex items-center">
|
||||
{localize('com_assistants_code_interpreter')}
|
||||
<QuestionMark />
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
{retrievalEnabled && (
|
||||
<div className="flex items-center">
|
||||
<Controller
|
||||
name={Capabilities.retrieval}
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<Checkbox
|
||||
{...field}
|
||||
checked={field.value}
|
||||
disabled={!retrievalModels.has(model)}
|
||||
onCheckedChange={field.onChange}
|
||||
className="relative float-left mr-2 inline-flex h-4 w-4 cursor-pointer"
|
||||
value={field?.value?.toString()}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<label
|
||||
className={cn(
|
||||
'form-check-label text-token-text-primary w-full',
|
||||
!retrievalModels.has(model) ? 'cursor-no-drop opacity-50' : 'cursor-pointer',
|
||||
)}
|
||||
htmlFor={Capabilities.retrieval}
|
||||
onClick={() =>
|
||||
retrievalModels.has(model) &&
|
||||
setValue(Capabilities.retrieval, !getValues(Capabilities.retrieval), {
|
||||
shouldDirty: true,
|
||||
})
|
||||
}
|
||||
>
|
||||
{localize('com_assistants_retrieval')}
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{/* Tools */}
|
||||
<div className="mb-6">
|
||||
<label className={labelClass}>{localize('com_assistants_tools_section')}</label>
|
||||
<label className={labelClass}>
|
||||
{`${toolsEnabled ? localize('com_assistants_tools') : ''}
|
||||
${toolsEnabled && actionsEnabled ? ' + ' : ''}
|
||||
${actionsEnabled ? localize('com_assistants_actions') : ''}`}
|
||||
</label>
|
||||
<div className="space-y-1">
|
||||
{functions.map((func) => (
|
||||
<AssistantTool
|
||||
|
|
@ -388,39 +432,44 @@ export default function AssistantPanel({
|
|||
<AssistantAction key={i} action={action} onClick={() => setAction(action)} />
|
||||
);
|
||||
})}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowToolDialog(true)}
|
||||
className="btn border-token-border-light relative mx-1 mt-2 h-8 rounded-lg bg-transparent font-medium hover:bg-gray-100 dark:hover:bg-gray-800"
|
||||
>
|
||||
<div className="flex w-full items-center justify-center gap-2">
|
||||
{localize('com_assistants_add_tools')}
|
||||
</div>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
disabled={!assistant_id}
|
||||
onClick={() => {
|
||||
if (!assistant_id) {
|
||||
return showToast({
|
||||
message: localize('com_assistants_actions_disabled'),
|
||||
status: 'warning',
|
||||
});
|
||||
}
|
||||
setActivePanel(Panel.actions);
|
||||
}}
|
||||
className="btn border-token-border-light relative mt-2 h-8 rounded-lg bg-transparent font-medium hover:bg-gray-100 dark:hover:bg-gray-800"
|
||||
>
|
||||
<div className="flex w-full items-center justify-center gap-2">
|
||||
{localize('com_assistants_add_actions')}
|
||||
</div>
|
||||
</button>
|
||||
{toolsEnabled && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowToolDialog(true)}
|
||||
className="btn border-token-border-light relative mx-1 mt-2 h-8 rounded-lg bg-transparent font-medium hover:bg-gray-100 dark:hover:bg-gray-800"
|
||||
>
|
||||
<div className="flex w-full items-center justify-center gap-2">
|
||||
{localize('com_assistants_add_tools')}
|
||||
</div>
|
||||
</button>
|
||||
)}
|
||||
{actionsEnabled && (
|
||||
<button
|
||||
type="button"
|
||||
disabled={!assistant_id}
|
||||
onClick={() => {
|
||||
if (!assistant_id) {
|
||||
return showToast({
|
||||
message: localize('com_assistants_actions_disabled'),
|
||||
status: 'warning',
|
||||
});
|
||||
}
|
||||
setActivePanel(Panel.actions);
|
||||
}}
|
||||
className="btn border-token-border-light relative mt-2 h-8 rounded-lg bg-transparent font-medium hover:bg-gray-100 dark:hover:bg-gray-800"
|
||||
>
|
||||
<div className="flex w-full items-center justify-center gap-2">
|
||||
{localize('com_assistants_add_actions')}
|
||||
</div>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-end gap-2">
|
||||
{/* Context Button */}
|
||||
<ContextButton
|
||||
assistant_id={assistant_id}
|
||||
activeModel={activeModel}
|
||||
setCurrentAssistantId={setCurrentAssistantId}
|
||||
createMutation={create}
|
||||
/>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue