🎉 feat: Code Interpreter API and Agents Release (#4860)

* feat: Code Interpreter API & File Search Agent Uploads

chore: add back code files

wip: first pass, abstract key dialog

refactor: influence checkbox on key changes

refactor: update localization keys for 'execute code' to 'run code'

wip: run code button

refactor: add throwError parameter to loadAuthValues and getUserPluginAuthValue functions

feat: first pass, API tool calling

fix: handle missing toolId in callTool function and return 404 for non-existent tools

feat: show code outputs

fix: improve error handling in callTool function and log errors

fix: handle potential null value for filepath in attachment destructuring

fix: normalize language before rendering and prevent null return

fix: add loading indicator in RunCode component while executing code

feat: add support for conditional code execution in Markdown components

feat: attachments

refactor: remove bash

fix: pass abort signal to graph/run

refactor: debounce and rate limit tool call

refactor: increase debounce delay for execute function

feat: set code output attachments

feat: image attachments

refactor: apply message context

refactor: pass `partIndex`

feat: toolCall schema/model/methods

feat: block indexing

feat: get tool calls

chore: imports

chore: typing

chore: condense type imports

feat: get tool calls

fix: block indexing

chore: typing

refactor: update tool calls mapping to support multiple results

fix: add unique key to nav link for rendering

wip: first pass, tool call results

refactor: update query cache from successful tool call mutation

style: improve result switcher styling

chore: note on using \`.toObject()\`

feat: add agent_id field to conversation schema

chore: typing

refactor: rename agentMap to agentsMap for consistency

feat: Agent Name as chat input placeholder

chore: bump agents

📦 chore: update @langchain dependencies to latest versions to match agents package

📦 chore: update @librechat/agents dependency to version 1.8.0

fix: Aborting agent stream removes sender; fix(bedrock): completion removes preset name label

refactor: remove direct file parameter to use req.file, add `processAgentFileUpload` for image uploads

feat: upload menu

feat: prime message_file resources

feat: implement conversation access validation in chat route

refactor: remove file parameter from processFileUpload and use req.file instead

feat: add savedMessageIds set to track saved message IDs in BaseClient, to prevent unnecessary double-write to db

feat: prevent duplicate message saves by checking savedMessageIds in AgentController

refactor: skip legacy RAG API handling for agents

feat: add files field to convoSchema

refactor: update request type annotations from Express.Request to ServerRequest in file processing functions

feat: track conversation files

fix: resendFiles, addPreviousAttachments handling

feat: add ID validation for session_id and file_id in download route

feat: entity_id for code file uploads/downloads

fix: code file edge cases

feat: delete related tool calls

feat: add stream rate handling for LLM configuration

feat: enhance system content with attached file information

fix: improve error logging in resource priming function

* WIP: PoC, sequential agents

WIP: PoC Sequential Agents, first pass content data + bump agents package

fix: package-lock

WIP: PoC, o1 support, refactor bufferString

feat: convertJsonSchemaToZod

fix: form issues and schema defining erroneous model

fix: max length issue on agent form instructions, limit conversation messages to sequential agents

feat: add abort signal support to createRun function and AgentClient

feat: PoC, hide prior sequential agent steps

fix: update parameter naming from config to metadata in event handlers for clarity, add model to usage data

refactor: use only last contentData, track model for usage data

chore: bump agents package

fix: content parts issue

refactor: filter contentParts to include tool calls and relevant indices

feat: show function calls

refactor: filter context messages to exclude tool calls when no tools are available to the agent

fix: ensure tool call content is not undefined in formatMessages

feat: add agent_id field to conversationPreset schema

feat: hide sequential agents

feat: increase upload toast duration to 10 seconds

* refactor: tool context handling & update Code API Key Dialog

feat: toolContextMap

chore: skipSpecs -> useSpecs

ci: fix handleTools tests

feat: API Key Dialog

* feat: Agent Permissions Admin Controls

feat: replace label with button for prompt permission toggle

feat: update agent permissions

feat: enable experimental agents and streamline capability configuration

feat: implement access control for agents and enhance endpoint menu items

feat: add welcome message for agent selection in localization

feat: add agents permission to access control and update version to 0.7.57

* fix: update types in useAssistantListMap and useMentions hooks for better null handling

* feat: mention agents

* fix: agent tool resource race conditions when deleting agent tool resource files

* feat: add error handling for code execution with user feedback

* refactor: rename AdminControls to AdminSettings for clarity

* style: add gap to button in AdminSettings for improved layout

* refactor: separate agent query hooks and check access to enable fetching

* fix: remove unused provider from agent initialization options, creates issue with custom endpoints

* refactor: remove redundant/deprecated modelOptions from AgentClient processes

* chore: update @librechat/agents to version 1.8.5 in package.json and package-lock.json

* fix: minor styling issues + agent panel uniformity

* fix: agent edge cases when set endpoint is no longer defined

* refactor: remove unused cleanup function call from AppService

* fix: update link in ApiKeyDialog to point to pricing page

* fix: improve type handling and layout calculations in SidePanel component

* fix: add missing localization string for agent selection in SidePanel

* chore: form styling and localizations for upload filesearch/code interpreter

* fix: model selection placeholder logic in AgentConfig component

* style: agent capabilities

* fix: add localization for provider selection and improve dropdown styling in ModelPanel

* refactor: use gpt-4o-mini > gpt-3.5-turbo

* fix: agents configuration for loadDefaultInterface and update related tests

* feat: DALLE Agents support
This commit is contained in:
Danny Avila 2024-12-04 15:48:13 -05:00 committed by GitHub
parent affcebd48c
commit 1a815f5e19
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
189 changed files with 5056 additions and 1815 deletions

View file

@ -5,17 +5,17 @@ import {
useGetEndpointsQuery,
} from 'librechat-data-provider/react-query';
import {
getConfigDefaults,
EModelEndpoint,
alternateName,
EModelEndpoint,
getConfigDefaults,
isAssistantsEndpoint,
} from 'librechat-data-provider';
import type { AssistantsEndpoint, TAssistantsMap, TEndpointsConfig } from 'librechat-data-provider';
import type { TAssistantsMap, TEndpointsConfig } from 'librechat-data-provider';
import type { MentionOption } from '~/common';
import useAssistantListMap from '~/hooks/Assistants/useAssistantListMap';
import { useGetPresetsQuery, useListAgentsQuery } from '~/data-provider';
import { mapEndpoints, getPresetTitle } from '~/utils';
import { EndpointIcon } from '~/components/Endpoints';
import { useGetPresetsQuery } from '~/data-provider';
const defaultInterface = getConfigDefaults().interface;
@ -25,7 +25,7 @@ const assistantMapFn =
assistantMap,
endpointsConfig,
}: {
endpoint: AssistantsEndpoint;
endpoint: EModelEndpoint | string;
assistantMap: TAssistantsMap;
endpointsConfig: TEndpointsConfig;
}) =>
@ -65,6 +65,27 @@ export default function useMentions({
description,
})),
);
const { data: agentsList = null } = useListAgentsQuery(undefined, {
select: (res) => {
const { data } = res;
return data.map(({ id, name, avatar }) => ({
value: id,
label: name ?? '',
type: EModelEndpoint.agents,
icon: EndpointIcon({
conversation: {
agent_id: id,
endpoint: EModelEndpoint.agents,
iconURL: avatar?.filepath,
},
containerClassName: 'shadow-stroke overflow-hidden rounded-full',
endpointsConfig: endpointsConfig,
context: 'menu-item',
size: 20,
}),
}));
},
});
const assistantListMap = useMemo(
() => ({
[EModelEndpoint.assistants]: listMap[EModelEndpoint.assistants]
@ -101,7 +122,7 @@ export default function useMentions({
validEndpoints = endpoints.filter((endpoint) => !isAssistantsEndpoint(endpoint));
}
const mentions = [
...(modelSpecs?.length > 0 ? modelSpecs : []).map((modelSpec) => ({
...(modelSpecs.length > 0 ? modelSpecs : []).map((modelSpec) => ({
value: modelSpec.name,
label: modelSpec.label,
description: modelSpec.description,
@ -116,9 +137,9 @@ export default function useMentions({
}),
type: 'modelSpec' as const,
})),
...(interfaceConfig.endpointsMenu ? validEndpoints : []).map((endpoint) => ({
...(interfaceConfig.endpointsMenu === true ? validEndpoints : []).map((endpoint) => ({
value: endpoint,
label: alternateName[endpoint] ?? endpoint ?? '',
label: alternateName[endpoint as string] ?? endpoint ?? '',
type: 'endpoint' as const,
icon: EndpointIcon({
conversation: { endpoint },
@ -127,13 +148,14 @@ export default function useMentions({
size: 20,
}),
})),
...(agentsList ?? []),
...(endpointsConfig?.[EModelEndpoint.assistants] && includeAssistants
? assistantListMap[EModelEndpoint.assistants] || []
: []),
...(endpointsConfig?.[EModelEndpoint.azureAssistants] && includeAssistants
? assistantListMap[EModelEndpoint.azureAssistants] || []
: []),
...((interfaceConfig.presets ? presets : [])?.map((preset, index) => ({
...((interfaceConfig.presets === true ? presets : [])?.map((preset, index) => ({
value: preset.presetId ?? `preset-${index}`,
label: preset.title ?? preset.modelLabel ?? preset.chatGptLabel ?? '',
description: getPresetTitle(preset, true),
@ -154,6 +176,7 @@ export default function useMentions({
presets,
endpoints,
modelSpecs,
agentsList,
assistantMap,
endpointsConfig,
assistantListMap,
@ -166,6 +189,7 @@ export default function useMentions({
options,
presets,
modelSpecs,
agentsList,
modelsConfig,
endpointsConfig,
assistantListMap,

View file

@ -1,6 +1,6 @@
import { useCallback } from 'react';
import { useRecoilValue } from 'recoil';
import { EModelEndpoint, isAssistantsEndpoint } from 'librechat-data-provider';
import { EModelEndpoint, isAgentsEndpoint, isAssistantsEndpoint } from 'librechat-data-provider';
import type {
TPreset,
TModelSpec,
@ -64,7 +64,11 @@ export default function useSelectMention({
preset.endpointType = newEndpointType;
}
if (isAssistantsEndpoint(newEndpoint) && preset.assistant_id != null && !(preset.model ?? '')) {
if (
isAssistantsEndpoint(newEndpoint) &&
preset.assistant_id != null &&
!(preset.model ?? '')
) {
preset.model = assistantMap?.[newEndpoint]?.[preset.assistant_id]?.model;
}
@ -94,11 +98,19 @@ export default function useSelectMention({
keepAddedConvos: isModular,
});
},
[conversation, getDefaultConversation, modularChat, newConversation, endpointsConfig, assistantMap],
[
conversation,
getDefaultConversation,
modularChat,
newConversation,
endpointsConfig,
assistantMap,
],
);
type Kwargs = {
model?: string;
agent_id?: string;
assistant_id?: string;
};
@ -228,6 +240,10 @@ export default function useSelectMention({
assistant_id: key,
model: assistantMap?.[option.type]?.[key]?.model ?? '',
});
} else if (isAgentsEndpoint(option.type)) {
onSelectEndpoint(option.type, {
agent_id: key,
});
}
},
[modelSpecs, onSelectEndpoint, onSelectPreset, onSelectSpec, presets, assistantMap],

View file

@ -1,11 +1,12 @@
import debounce from 'lodash/debounce';
import { useEffect, useRef, useCallback } from 'react';
import { useRecoilValue, useRecoilState } from 'recoil';
import { isAssistantsEndpoint } from 'librechat-data-provider';
import { Constants } from 'librechat-data-provider';
import type { TEndpointOption } from 'librechat-data-provider';
import type { KeyboardEvent } from 'react';
import { forceResize, insertTextAtCursor, getAssistantName } from '~/utils';
import { forceResize, insertTextAtCursor, getEntityName, getEntity } from '~/utils';
import { useAssistantsMapContext } from '~/Providers/AssistantsMapContext';
import { useAgentsMapContext } from '~/Providers/AgentsMapContext';
import useGetSender from '~/hooks/Conversations/useGetSender';
import useFileHandling from '~/hooks/Files/useFileHandling';
import { useInteractionHealthCheck } from '~/data-provider';
@ -28,6 +29,7 @@ export default function useTextarea({
const localize = useLocalize();
const getSender = useGetSender();
const isComposing = useRef(false);
const agentsMap = useAgentsMapContext();
const { handleFiles } = useFileHandling();
const assistantMap = useAssistantsMapContext();
const checkHealth = useInteractionHealthCheck();
@ -44,19 +46,25 @@ export default function useTextarea({
} = useChatContext();
const [activePrompt, setActivePrompt] = useRecoilState(store.activePromptByIndex(index));
const { conversationId, jailbreak, endpoint = '', assistant_id } = conversation || {};
const { conversationId, jailbreak = false, endpoint = '' } = conversation || {};
const { entity, isAgent, isAssistant } = getEntity({
endpoint,
agentsMap,
assistantMap,
agent_id: conversation?.agent_id,
assistant_id: conversation?.assistant_id,
});
const entityName = entity?.name ?? '';
const isNotAppendable =
((latestMessage?.unfinished && !isSubmitting) || latestMessage?.error) &&
!isAssistantsEndpoint(endpoint);
(((latestMessage?.unfinished ?? false) && !isSubmitting) || (latestMessage?.error ?? false)) &&
!isAssistant;
// && (conversationId?.length ?? 0) > 6; // also ensures that we don't show the wrong placeholder
const assistant =
isAssistantsEndpoint(endpoint) && assistantMap?.[endpoint ?? '']?.[assistant_id ?? ''];
const assistantName = (assistant && assistant.name) || '';
useEffect(() => {
if (activePrompt && textAreaRef.current) {
insertTextAtCursor(textAreaRef.current, activePrompt);
const prompt = activePrompt ?? '';
if (prompt && textAreaRef.current) {
insertTextAtCursor(textAreaRef.current, prompt);
forceResize(textAreaRef.current);
setActivePrompt(undefined);
}
@ -64,16 +72,17 @@ export default function useTextarea({
// auto focus to input, when enter a conversation.
useEffect(() => {
if (!conversationId) {
const convoId = conversationId ?? '';
if (!convoId) {
return;
}
// Prevents Settings from not showing on new conversation, also prevents showing toneStyle change without jailbreak
if (conversationId === 'new' || !jailbreak) {
if (convoId === Constants.NEW_CONVO || !jailbreak) {
setShowBingToneSetting(false);
}
if (conversationId !== 'search') {
if (convoId !== Constants.SEARCH) {
textAreaRef.current?.focus();
}
// setShowBingToneSetting is a recoil setter, so it doesn't need to be in the dependency array
@ -89,7 +98,8 @@ export default function useTextarea({
}, [isSubmitting, textAreaRef]);
useEffect(() => {
if (textAreaRef.current?.value) {
const currentValue = textAreaRef.current?.value ?? '';
if (currentValue) {
return;
}
@ -98,10 +108,13 @@ export default function useTextarea({
return localize('com_endpoint_config_placeholder');
}
const currentEndpoint = conversation?.endpoint ?? '';
const currentAgentId = conversation?.agent_id ?? '';
const currentAssistantId = conversation?.assistant_id ?? '';
if (
isAssistantsEndpoint(currentEndpoint) &&
(!currentAssistantId || !assistantMap?.[currentEndpoint]?.[currentAssistantId ?? ''])
if (isAgent && (!currentAgentId || !agentsMap?.[currentAgentId])) {
return localize('com_endpoint_agent_placeholder');
} else if (
isAssistant &&
(!currentAssistantId || !assistantMap?.[currentEndpoint]?.[currentAssistantId])
) {
return localize('com_endpoint_assistant_placeholder');
}
@ -110,9 +123,10 @@ export default function useTextarea({
return localize('com_endpoint_message_not_appendable');
}
const sender = isAssistantsEndpoint(currentEndpoint)
? getAssistantName({ name: assistantName, localize })
: getSender(conversation as TEndpointOption);
const sender =
isAssistant || isAgent
? getEntityName({ name: entityName, isAgent, localize })
: getSender(conversation as TEndpointOption);
return `${localize('com_endpoint_message')} ${sender ? sender : 'AI'}`;
};
@ -137,15 +151,18 @@ export default function useTextarea({
return () => debouncedSetPlaceholder.cancel();
}, [
conversation,
isAgent,
localize,
disabled,
getSender,
agentsMap,
entityName,
textAreaRef,
isAssistant,
assistantMap,
conversation,
latestMessage,
isNotAppendable,
localize,
getSender,
assistantName,
textAreaRef,
assistantMap,
]);
const handleKeyDown = useCallback(
@ -181,7 +198,7 @@ export default function useTextarea({
}
if ((isNonShiftEnter || isCtrlEnter) && !isComposing.current) {
const globalAudio = document.getElementById(globalAudioId) as HTMLAudioElement;
const globalAudio = document.getElementById(globalAudioId) as HTMLAudioElement | undefined;
if (globalAudio) {
console.log('Unmuting global audio');
globalAudio.muted = false;
@ -207,14 +224,15 @@ export default function useTextarea({
return;
}
if (!e.clipboardData) {
const clipboardData = e.clipboardData as DataTransfer | undefined;
if (!clipboardData) {
return;
}
if (e.clipboardData.files.length > 0) {
if (clipboardData.files.length > 0) {
setFilesLoading(true);
const timestampedFiles: File[] = [];
for (const file of e.clipboardData.files) {
for (const file of clipboardData.files) {
const newFile = new File([file], `clipboard_${+new Date()}_${file.name}`, {
type: file.type,
});