mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-16 16:30:15 +01:00
✨ feat: Anthropic Agents Prompt Caching & UI Accessibility Enhancements (#6045)
* chore: remove auto-focus for now * refactor: move react-hook-form Controller Logic to AgentSelect from AgentPanel * fix: a11y focus issue with AgentSelect by never replacing it in its component tree * fix: maintain ComboBox focus and force re-render on agent ID change in AgentPanel * chore: `gemini-2.0-flash-lite-preview-02-05` (deprecated) * refactor: extract cache control logic and headers configuration to helper functions in AnthropicClient * feat: anthropic agents prompt caching * chore: bump @librechat/agents and related dependencies * fix: typo
This commit is contained in:
parent
d3d7d11ea8
commit
e14df5956a
12 changed files with 4460 additions and 1477 deletions
|
|
@ -17,6 +17,11 @@ const {
|
|||
parseParamFromPrompt,
|
||||
createContextHandlers,
|
||||
} = require('./prompts');
|
||||
const {
|
||||
getClaudeHeaders,
|
||||
configureReasoning,
|
||||
checkPromptCacheSupport,
|
||||
} = require('~/server/services/Endpoints/anthropic/helpers');
|
||||
const { getModelMaxTokens, getModelMaxOutputTokens, matchModelName } = require('~/utils');
|
||||
const { spendTokens, spendStructuredTokens } = require('~/models/spendTokens');
|
||||
const Tokenizer = require('~/server/services/Tokenizer');
|
||||
|
|
@ -101,8 +106,7 @@ class AnthropicClient extends BaseClient {
|
|||
const modelMatch = matchModelName(this.modelOptions.model, EModelEndpoint.anthropic);
|
||||
this.isClaude3 = modelMatch.includes('claude-3');
|
||||
this.isLegacyOutput = !modelMatch.includes('claude-3-5-sonnet');
|
||||
this.supportsCacheControl =
|
||||
this.options.promptCache && this.checkPromptCacheSupport(modelMatch);
|
||||
this.supportsCacheControl = this.options.promptCache && checkPromptCacheSupport(modelMatch);
|
||||
|
||||
if (
|
||||
this.isLegacyOutput &&
|
||||
|
|
@ -174,26 +178,9 @@ class AnthropicClient extends BaseClient {
|
|||
options.baseURL = this.options.reverseProxyUrl;
|
||||
}
|
||||
|
||||
if (
|
||||
this.supportsCacheControl &&
|
||||
requestOptions?.model &&
|
||||
requestOptions.model.includes('claude-3-5-sonnet')
|
||||
) {
|
||||
options.defaultHeaders = {
|
||||
'anthropic-beta': 'max-tokens-3-5-sonnet-2024-07-15,prompt-caching-2024-07-31',
|
||||
};
|
||||
} else if (
|
||||
this.supportsCacheControl &&
|
||||
requestOptions?.model &&
|
||||
requestOptions.model.includes('claude-3-7')
|
||||
) {
|
||||
options.defaultHeaders = {
|
||||
'anthropic-beta': 'output-128k-2025-02-19,prompt-caching-2024-07-31',
|
||||
};
|
||||
} else if (this.supportsCacheControl) {
|
||||
options.defaultHeaders = {
|
||||
'anthropic-beta': 'prompt-caching-2024-07-31',
|
||||
};
|
||||
const headers = getClaudeHeaders(requestOptions?.model, this.supportsCacheControl);
|
||||
if (headers) {
|
||||
options.defaultHeaders = headers;
|
||||
}
|
||||
|
||||
return new Anthropic(options);
|
||||
|
|
@ -684,27 +671,6 @@ class AnthropicClient extends BaseClient {
|
|||
: await client.completions.create(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} modelName
|
||||
* @returns {boolean}
|
||||
*/
|
||||
checkPromptCacheSupport(modelName) {
|
||||
const modelMatch = matchModelName(modelName, EModelEndpoint.anthropic);
|
||||
if (modelMatch.includes('claude-3-5-sonnet-latest')) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
modelMatch === 'claude-3-7-sonnet' ||
|
||||
modelMatch === 'claude-3-5-sonnet' ||
|
||||
modelMatch === 'claude-3-5-haiku' ||
|
||||
modelMatch === 'claude-3-haiku' ||
|
||||
modelMatch === 'claude-3-opus'
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
getMessageMapMethod() {
|
||||
/**
|
||||
* @param {TMessage} msg
|
||||
|
|
@ -761,7 +727,7 @@ class AnthropicClient extends BaseClient {
|
|||
topK: top_k,
|
||||
} = this.modelOptions;
|
||||
|
||||
const requestOptions = {
|
||||
let requestOptions = {
|
||||
model,
|
||||
stream: stream || true,
|
||||
stop_sequences,
|
||||
|
|
@ -780,40 +746,10 @@ class AnthropicClient extends BaseClient {
|
|||
requestOptions.max_tokens_to_sample = maxOutputTokens || legacy.maxOutputTokens.default;
|
||||
}
|
||||
|
||||
if (
|
||||
this.options.thinking &&
|
||||
requestOptions?.model &&
|
||||
requestOptions.model.includes('claude-3-7')
|
||||
) {
|
||||
requestOptions.thinking = {
|
||||
type: 'enabled',
|
||||
};
|
||||
}
|
||||
if (requestOptions.thinking != null && this.options.thinkingBudget != null) {
|
||||
requestOptions.thinking = {
|
||||
...requestOptions.thinking,
|
||||
budget_tokens: this.options.thinkingBudget,
|
||||
};
|
||||
}
|
||||
if (
|
||||
requestOptions.thinking != null &&
|
||||
(requestOptions.max_tokens == null ||
|
||||
requestOptions.thinking.budget_tokens > requestOptions.max_tokens)
|
||||
) {
|
||||
const maxTokens = anthropicSettings.maxOutputTokens.reset(requestOptions.model);
|
||||
requestOptions.max_tokens = requestOptions.max_tokens ?? maxTokens;
|
||||
|
||||
logger.warn(
|
||||
requestOptions.max_tokens === maxTokens
|
||||
? '[AnthropicClient] max_tokens is not defined while thinking is enabled. Setting max_tokens to model default.'
|
||||
: `[AnthropicClient] thinking budget_tokens (${requestOptions.thinking.budget_tokens}) exceeds max_tokens (${requestOptions.max_tokens}). Adjusting budget_tokens.`,
|
||||
);
|
||||
|
||||
requestOptions.thinking.budget_tokens = Math.min(
|
||||
requestOptions.thinking.budget_tokens,
|
||||
Math.floor(requestOptions.max_tokens * 0.9),
|
||||
);
|
||||
}
|
||||
requestOptions = configureReasoning(requestOptions, {
|
||||
thinking: this.options.thinking,
|
||||
thinkingBudget: this.options.thinkingBudget,
|
||||
});
|
||||
|
||||
if (this.systemMessage && this.supportsCacheControl === true) {
|
||||
requestOptions.system = [
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
/**
|
||||
* Anthropic API: Adds cache control to the appropriate user messages in the payload.
|
||||
* @param {Array<AnthropicMessage>} messages - The array of message objects.
|
||||
* @returns {Array<AnthropicMessage>} - The updated array of message objects with cache control added.
|
||||
* @param {Array<AnthropicMessage | BaseMessage>} messages - The array of message objects.
|
||||
* @returns {Array<AnthropicMessage | BaseMessage>} - The updated array of message objects with cache control added.
|
||||
*/
|
||||
function addCacheControl(messages) {
|
||||
if (!Array.isArray(messages) || messages.length < 2) {
|
||||
|
|
@ -13,7 +13,9 @@ function addCacheControl(messages) {
|
|||
|
||||
for (let i = updatedMessages.length - 1; i >= 0 && userMessagesModified < 2; i--) {
|
||||
const message = updatedMessages[i];
|
||||
if (message.role !== 'user') {
|
||||
if (message.getType != null && message.getType() !== 'human') {
|
||||
continue;
|
||||
} else if (message.getType == null && message.role !== 'user') {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -41,11 +41,11 @@
|
|||
"@keyv/mongo": "^2.1.8",
|
||||
"@keyv/redis": "^2.8.1",
|
||||
"@langchain/community": "^0.3.14",
|
||||
"@langchain/core": "^0.3.37",
|
||||
"@langchain/google-genai": "^0.1.7",
|
||||
"@langchain/google-vertexai": "^0.1.8",
|
||||
"@langchain/core": "^0.3.40",
|
||||
"@langchain/google-genai": "^0.1.9",
|
||||
"@langchain/google-vertexai": "^0.2.0",
|
||||
"@langchain/textsplitters": "^0.1.0",
|
||||
"@librechat/agents": "^2.1.2",
|
||||
"@librechat/agents": "^2.1.3",
|
||||
"@waylaidwanderer/fetch-event-source": "^3.0.1",
|
||||
"axios": "1.7.8",
|
||||
"bcryptjs": "^2.4.3",
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ const {
|
|||
} = require('librechat-data-provider');
|
||||
const {
|
||||
formatMessage,
|
||||
addCacheControl,
|
||||
formatAgentMessages,
|
||||
formatContentStrings,
|
||||
createContextHandlers,
|
||||
|
|
@ -589,7 +590,7 @@ class AgentClient extends BaseClient {
|
|||
* @param {number} [i]
|
||||
* @param {TMessageContentParts[]} [contentData]
|
||||
*/
|
||||
const runAgent = async (agent, messages, i = 0, contentData = []) => {
|
||||
const runAgent = async (agent, _messages, i = 0, contentData = []) => {
|
||||
config.configurable.model = agent.model_parameters.model;
|
||||
if (i > 0) {
|
||||
this.model = agent.model_parameters.model;
|
||||
|
|
@ -622,12 +623,21 @@ class AgentClient extends BaseClient {
|
|||
}
|
||||
|
||||
if (noSystemMessages === true && systemContent?.length) {
|
||||
let latestMessage = messages.pop().content;
|
||||
let latestMessage = _messages.pop().content;
|
||||
if (typeof latestMessage !== 'string') {
|
||||
latestMessage = latestMessage[0].text;
|
||||
}
|
||||
latestMessage = [systemContent, latestMessage].join('\n');
|
||||
messages.push(new HumanMessage(latestMessage));
|
||||
_messages.push(new HumanMessage(latestMessage));
|
||||
}
|
||||
|
||||
let messages = _messages;
|
||||
if (
|
||||
agent.model_parameters?.clientOptions?.defaultHeaders?.['anthropic-beta']?.includes(
|
||||
'prompt-caching',
|
||||
)
|
||||
) {
|
||||
messages = addCacheControl(messages);
|
||||
}
|
||||
|
||||
run = await createRun({
|
||||
|
|
|
|||
110
api/server/services/Endpoints/anthropic/helpers.js
Normal file
110
api/server/services/Endpoints/anthropic/helpers.js
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
const { EModelEndpoint, anthropicSettings } = require('librechat-data-provider');
|
||||
const { matchModelName } = require('~/utils');
|
||||
const { logger } = require('~/config');
|
||||
|
||||
/**
|
||||
* @param {string} modelName
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function checkPromptCacheSupport(modelName) {
|
||||
const modelMatch = matchModelName(modelName, EModelEndpoint.anthropic);
|
||||
if (
|
||||
modelMatch.includes('claude-3-5-sonnet-latest') ||
|
||||
modelMatch.includes('claude-3.5-sonnet-latest')
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
modelMatch === 'claude-3-7-sonnet' ||
|
||||
modelMatch === 'claude-3-5-sonnet' ||
|
||||
modelMatch === 'claude-3-5-haiku' ||
|
||||
modelMatch === 'claude-3-haiku' ||
|
||||
modelMatch === 'claude-3-opus' ||
|
||||
modelMatch === 'claude-3.7-sonnet' ||
|
||||
modelMatch === 'claude-3.5-sonnet' ||
|
||||
modelMatch === 'claude-3.5-haiku'
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the appropriate headers for Claude models with cache control
|
||||
* @param {string} model The model name
|
||||
* @param {boolean} supportsCacheControl Whether the model supports cache control
|
||||
* @returns {AnthropicClientOptions['extendedOptions']['defaultHeaders']|undefined} The headers object or undefined if not applicable
|
||||
*/
|
||||
function getClaudeHeaders(model, supportsCacheControl) {
|
||||
if (!supportsCacheControl) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (/claude-3[-.]5-sonnet/.test(model)) {
|
||||
return {
|
||||
'anthropic-beta': 'max-tokens-3-5-sonnet-2024-07-15,prompt-caching-2024-07-31',
|
||||
};
|
||||
} else if (/claude-3[-.]7/.test(model)) {
|
||||
return {
|
||||
'anthropic-beta': 'output-128k-2025-02-19,prompt-caching-2024-07-31',
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
'anthropic-beta': 'prompt-caching-2024-07-31',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures reasoning-related options for Claude models
|
||||
* @param {AnthropicClientOptions & { max_tokens?: number }} anthropicInput The request options object
|
||||
* @param {Object} extendedOptions Additional client configuration options
|
||||
* @param {boolean} extendedOptions.thinking Whether thinking is enabled in client config
|
||||
* @param {number|null} extendedOptions.thinkingBudget The token budget for thinking
|
||||
* @returns {Object} Updated request options
|
||||
*/
|
||||
function configureReasoning(anthropicInput, extendedOptions = {}) {
|
||||
const updatedOptions = { ...anthropicInput };
|
||||
const currentMaxTokens = updatedOptions.max_tokens ?? updatedOptions.maxTokens;
|
||||
if (
|
||||
extendedOptions.thinking &&
|
||||
updatedOptions?.model &&
|
||||
/claude-3[-.]7/.test(updatedOptions.model)
|
||||
) {
|
||||
updatedOptions.thinking = {
|
||||
type: 'enabled',
|
||||
};
|
||||
}
|
||||
|
||||
if (updatedOptions.thinking != null && extendedOptions.thinkingBudget != null) {
|
||||
updatedOptions.thinking = {
|
||||
...updatedOptions.thinking,
|
||||
budget_tokens: extendedOptions.thinkingBudget,
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
updatedOptions.thinking != null &&
|
||||
(currentMaxTokens == null || updatedOptions.thinking.budget_tokens > currentMaxTokens)
|
||||
) {
|
||||
const maxTokens = anthropicSettings.maxOutputTokens.reset(updatedOptions.model);
|
||||
updatedOptions.max_tokens = currentMaxTokens ?? maxTokens;
|
||||
|
||||
logger.warn(
|
||||
updatedOptions.max_tokens === maxTokens
|
||||
? '[AnthropicClient] max_tokens is not defined while thinking is enabled. Setting max_tokens to model default.'
|
||||
: `[AnthropicClient] thinking budget_tokens (${updatedOptions.thinking.budget_tokens}) exceeds max_tokens (${updatedOptions.max_tokens}). Adjusting budget_tokens.`,
|
||||
);
|
||||
|
||||
updatedOptions.thinking.budget_tokens = Math.min(
|
||||
updatedOptions.thinking.budget_tokens,
|
||||
Math.floor(updatedOptions.max_tokens * 0.9),
|
||||
);
|
||||
}
|
||||
|
||||
return updatedOptions;
|
||||
}
|
||||
|
||||
module.exports = { checkPromptCacheSupport, getClaudeHeaders, configureReasoning };
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
const { HttpsProxyAgent } = require('https-proxy-agent');
|
||||
const { anthropicSettings, removeNullishValues } = require('librechat-data-provider');
|
||||
const { checkPromptCacheSupport, getClaudeHeaders } = require('./helpers');
|
||||
|
||||
/**
|
||||
* Generates configuration options for creating an Anthropic language model (LLM) instance.
|
||||
|
|
@ -20,6 +21,14 @@ const { anthropicSettings, removeNullishValues } = require('librechat-data-provi
|
|||
* @returns {Object} Configuration options for creating an Anthropic LLM instance, with null and undefined values removed.
|
||||
*/
|
||||
function getLLMConfig(apiKey, options = {}) {
|
||||
const systemOptions = {
|
||||
thinking: options.modelOptions.thinking ?? anthropicSettings.thinking.default,
|
||||
promptCache: options.modelOptions.promptCache ?? anthropicSettings.promptCache.default,
|
||||
thinkingBudget: options.modelOptions.thinkingBudget ?? anthropicSettings.thinkingBudget.default,
|
||||
};
|
||||
for (let key in systemOptions) {
|
||||
delete options.modelOptions[key];
|
||||
}
|
||||
const defaultOptions = {
|
||||
model: anthropicSettings.model.default,
|
||||
maxOutputTokens: anthropicSettings.maxOutputTokens.default,
|
||||
|
|
@ -29,7 +38,7 @@ function getLLMConfig(apiKey, options = {}) {
|
|||
const mergedOptions = Object.assign(defaultOptions, options.modelOptions);
|
||||
|
||||
/** @type {AnthropicClientOptions} */
|
||||
const requestOptions = {
|
||||
let requestOptions = {
|
||||
apiKey,
|
||||
model: mergedOptions.model,
|
||||
stream: mergedOptions.stream,
|
||||
|
|
@ -42,6 +51,13 @@ function getLLMConfig(apiKey, options = {}) {
|
|||
clientOptions: {},
|
||||
};
|
||||
|
||||
const supportsCacheControl =
|
||||
systemOptions.promptCache === true && checkPromptCacheSupport(requestOptions.model);
|
||||
const headers = getClaudeHeaders(requestOptions.model, supportsCacheControl);
|
||||
if (headers) {
|
||||
requestOptions.clientOptions.defaultHeaders = headers;
|
||||
}
|
||||
|
||||
if (options.proxy) {
|
||||
requestOptions.clientOptions.httpAgent = new HttpsProxyAgent(options.proxy);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { Plus } from 'lucide-react';
|
||||
import React, { useMemo, useCallback } from 'react';
|
||||
import { useWatch, useForm, FormProvider } from 'react-hook-form';
|
||||
import { useGetModelsQuery } from 'librechat-data-provider/react-query';
|
||||
import { Controller, useWatch, useForm, FormProvider } from 'react-hook-form';
|
||||
import {
|
||||
Tools,
|
||||
SystemRoles,
|
||||
|
|
@ -201,10 +201,6 @@ export default function AgentPanel({
|
|||
user?.role,
|
||||
]);
|
||||
|
||||
if (agentQuery.isInitialLoading) {
|
||||
return <AgentPanelSkeleton />;
|
||||
}
|
||||
|
||||
return (
|
||||
<FormProvider {...methods}>
|
||||
<form
|
||||
|
|
@ -214,19 +210,13 @@ export default function AgentPanel({
|
|||
>
|
||||
<div className="mx-1 mt-2 flex w-full flex-wrap gap-2">
|
||||
<div className="w-full">
|
||||
<Controller
|
||||
name="agent"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<AgentSelect
|
||||
reset={reset}
|
||||
value={field.value}
|
||||
agentQuery={agentQuery}
|
||||
setCurrentAgentId={setCurrentAgentId}
|
||||
selectedAgentId={current_agent_id ?? null}
|
||||
createMutation={create}
|
||||
/>
|
||||
)}
|
||||
<AgentSelect
|
||||
createMutation={create}
|
||||
agentQuery={agentQuery}
|
||||
setCurrentAgentId={setCurrentAgentId}
|
||||
// The following is required to force re-render the component when the form's agent ID changes
|
||||
// Also maintains ComboBox Focus for Accessibility
|
||||
selectedAgentId={agentQuery.isInitialLoading ? null : (current_agent_id ?? null)}
|
||||
/>
|
||||
</div>
|
||||
{/* Create + Select Button */}
|
||||
|
|
@ -240,6 +230,7 @@ export default function AgentPanel({
|
|||
reset(defaultAgentFormValues);
|
||||
setCurrentAgentId(undefined);
|
||||
}}
|
||||
disabled={agentQuery.isInitialLoading}
|
||||
>
|
||||
<Plus className="mr-1 h-4 w-4" />
|
||||
{localize('com_ui_create') +
|
||||
|
|
@ -250,7 +241,7 @@ export default function AgentPanel({
|
|||
</Button>
|
||||
<Button
|
||||
variant="submit"
|
||||
disabled={!agent_id}
|
||||
disabled={!agent_id || agentQuery.isInitialLoading}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
handleSelectAgent();
|
||||
|
|
@ -262,7 +253,8 @@ export default function AgentPanel({
|
|||
</div>
|
||||
)}
|
||||
</div>
|
||||
{!canEditAgent && (
|
||||
{agentQuery.isInitialLoading && <AgentPanelSkeleton />}
|
||||
{!canEditAgent && !agentQuery.isInitialLoading && (
|
||||
<div className="flex h-[30vh] w-full items-center justify-center">
|
||||
<div className="text-center">
|
||||
<h2 className="text-token-text-primary m-2 text-xl font-semibold">
|
||||
|
|
@ -272,7 +264,7 @@ export default function AgentPanel({
|
|||
</div>
|
||||
</div>
|
||||
)}
|
||||
{canEditAgent && activePanel === Panel.model && (
|
||||
{canEditAgent && !agentQuery.isInitialLoading && activePanel === Panel.model && (
|
||||
<ModelPanel
|
||||
setActivePanel={setActivePanel}
|
||||
agent_id={agent_id}
|
||||
|
|
@ -280,7 +272,7 @@ export default function AgentPanel({
|
|||
models={models}
|
||||
/>
|
||||
)}
|
||||
{canEditAgent && activePanel === Panel.builder && (
|
||||
{canEditAgent && !agentQuery.isInitialLoading && activePanel === Panel.builder && (
|
||||
<AgentConfig
|
||||
actions={actions}
|
||||
setAction={setAction}
|
||||
|
|
|
|||
|
|
@ -3,81 +3,67 @@ import { Skeleton } from '~/components/ui';
|
|||
|
||||
export default function AgentPanelSkeleton() {
|
||||
return (
|
||||
<div className="scrollbar-gutter-stable h-auto w-full flex-shrink-0 overflow-x-hidden">
|
||||
<div className="mx-1 mt-2 flex w-full flex-wrap gap-2">
|
||||
{/* Agent Select Dropdown */}
|
||||
<div className="w-full">
|
||||
<Skeleton className="h-[40px] w-full rounded-md" />
|
||||
<div className="h-auto bg-white px-4 pb-8 pt-3 dark:bg-transparent">
|
||||
{/* Avatar */}
|
||||
<div className="mb-4">
|
||||
<div className="flex w-full items-center justify-center gap-4">
|
||||
<Skeleton className="relative h-20 w-20 rounded-full" />
|
||||
</div>
|
||||
{/* Create and Select Buttons */}
|
||||
<div className="flex w-full gap-2">
|
||||
<Skeleton className="h-[40px] w-3/4 rounded-md" /> {/* Create Button */}
|
||||
<Skeleton className="h-[40px] w-1/4 rounded-md" /> {/* Select Button */}
|
||||
{/* Name */}
|
||||
<Skeleton className="mb-2 h-5 w-1/5 rounded-lg" />
|
||||
<Skeleton className="mb-1 h-[40px] w-full rounded-lg" />
|
||||
<Skeleton className="h-3 w-1/4 rounded-lg" />
|
||||
</div>
|
||||
|
||||
{/* Description */}
|
||||
<div className="mb-4">
|
||||
<Skeleton className="mb-2 h-5 w-1/4 rounded-lg" />
|
||||
<Skeleton className="h-[40px] w-full rounded-lg" />
|
||||
</div>
|
||||
|
||||
{/* Instructions */}
|
||||
<div className="mb-6">
|
||||
<Skeleton className="mb-2 h-5 w-1/4 rounded-lg" />
|
||||
<Skeleton className="h-[100px] w-full rounded-lg" />
|
||||
</div>
|
||||
|
||||
{/* Model and Provider */}
|
||||
<div className="mb-6">
|
||||
<Skeleton className="mb-2 h-5 w-1/4 rounded-lg" />
|
||||
<Skeleton className="h-[40px] w-full rounded-lg" />
|
||||
</div>
|
||||
|
||||
{/* Capabilities */}
|
||||
<div className="mb-6">
|
||||
<Skeleton className="mb-2 h-5 w-1/4 rounded-lg" />
|
||||
<Skeleton className="mb-2 h-5 w-36 rounded-lg" />
|
||||
<Skeleton className="mb-4 h-[35px] w-full rounded-lg" />
|
||||
<Skeleton className="mb-2 h-5 w-24 rounded-lg" />
|
||||
<Skeleton className="h-[35px] w-full rounded-lg" />
|
||||
</div>
|
||||
|
||||
{/* Tools & Actions */}
|
||||
<div className="mb-6">
|
||||
<Skeleton className="mb-2 h-5 w-1/4 rounded-lg" />
|
||||
<Skeleton className="mb-2 h-[35px] w-full rounded-lg" />
|
||||
<Skeleton className="mb-2 h-[35px] w-full rounded-lg" />
|
||||
<div className="flex space-x-2">
|
||||
<Skeleton className="h-8 w-1/2 rounded-lg" />
|
||||
<Skeleton className="h-8 w-1/2 rounded-lg" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="h-auto bg-white px-4 pb-8 pt-3 dark:bg-transparent">
|
||||
{/* Avatar */}
|
||||
<div className="mb-4">
|
||||
<div className="flex w-full items-center justify-center gap-4">
|
||||
<Skeleton className="relative h-20 w-20 rounded-full" />
|
||||
</div>
|
||||
{/* Name */}
|
||||
<Skeleton className="mb-2 h-5 w-1/5 rounded-lg" />
|
||||
<Skeleton className="mb-1 h-[40px] w-full rounded-lg" />
|
||||
<Skeleton className="h-3 w-1/4 rounded-lg" />
|
||||
</div>
|
||||
{/* Admin Settings */}
|
||||
<div className="mb-6">
|
||||
<Skeleton className="h-[35px] w-full rounded-lg" />
|
||||
</div>
|
||||
|
||||
{/* Description */}
|
||||
<div className="mb-4">
|
||||
<Skeleton className="mb-2 h-5 w-1/4 rounded-lg" />
|
||||
<Skeleton className="h-[40px] w-full rounded-lg" />
|
||||
</div>
|
||||
|
||||
{/* Instructions */}
|
||||
<div className="mb-6">
|
||||
<Skeleton className="mb-2 h-5 w-1/4 rounded-lg" />
|
||||
<Skeleton className="h-[100px] w-full rounded-lg" />
|
||||
</div>
|
||||
|
||||
{/* Model and Provider */}
|
||||
<div className="mb-6">
|
||||
<Skeleton className="mb-2 h-5 w-1/4 rounded-lg" />
|
||||
<Skeleton className="h-[40px] w-full rounded-lg" />
|
||||
</div>
|
||||
|
||||
{/* Capabilities */}
|
||||
<div className="mb-6">
|
||||
<Skeleton className="mb-2 h-5 w-1/4 rounded-lg" />
|
||||
<Skeleton className="mb-2 h-5 w-36 rounded-lg" />
|
||||
<Skeleton className="mb-4 h-[35px] w-full rounded-lg" />
|
||||
<Skeleton className="mb-2 h-5 w-24 rounded-lg" />
|
||||
<Skeleton className="h-[35px] w-full rounded-lg" />
|
||||
</div>
|
||||
|
||||
{/* Tools & Actions */}
|
||||
<div className="mb-6">
|
||||
<Skeleton className="mb-2 h-5 w-1/4 rounded-lg" />
|
||||
<Skeleton className="mb-2 h-[35px] w-full rounded-lg" />
|
||||
<Skeleton className="mb-2 h-[35px] w-full rounded-lg" />
|
||||
<div className="flex space-x-2">
|
||||
<Skeleton className="h-8 w-1/2 rounded-lg" />
|
||||
<Skeleton className="h-8 w-1/2 rounded-lg" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Admin Settings */}
|
||||
<div className="mb-6">
|
||||
<Skeleton className="h-[35px] w-full rounded-lg" />
|
||||
</div>
|
||||
|
||||
{/* Bottom Buttons */}
|
||||
<div className="flex items-center justify-end gap-2">
|
||||
<Skeleton className="h-[35px] w-16 rounded-lg" />
|
||||
<Skeleton className="h-[35px] w-16 rounded-lg" />
|
||||
<Skeleton className="h-[35px] w-16 rounded-lg" />
|
||||
<Skeleton className="h-[35px] w-full rounded-lg" />
|
||||
</div>
|
||||
{/* Bottom Buttons */}
|
||||
<div className="flex items-center justify-end gap-2">
|
||||
<Skeleton className="h-[35px] w-16 rounded-lg" />
|
||||
<Skeleton className="h-[35px] w-16 rounded-lg" />
|
||||
<Skeleton className="h-[35px] w-16 rounded-lg" />
|
||||
<Skeleton className="h-[35px] w-full rounded-lg" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,28 +1,23 @@
|
|||
import { EarthIcon } from 'lucide-react';
|
||||
import { useCallback, useEffect, useRef } from 'react';
|
||||
import { useFormContext, Controller } from 'react-hook-form';
|
||||
import { AgentCapabilities, defaultAgentFormValues } from 'librechat-data-provider';
|
||||
import type { UseMutationResult, QueryObserverResult } from '@tanstack/react-query';
|
||||
import type { Agent, AgentCreateParams } from 'librechat-data-provider';
|
||||
import type { UseFormReset } from 'react-hook-form';
|
||||
import type { TAgentCapabilities, AgentForm, TAgentOption } from '~/common';
|
||||
import type { TAgentCapabilities, AgentForm } from '~/common';
|
||||
import { useListAgentsQuery, useGetStartupConfig } from '~/data-provider';
|
||||
import { cn, createProviderOption, processAgentOption } from '~/utils';
|
||||
import ControlCombobox from '~/components/ui/ControlCombobox';
|
||||
import { useLocalize } from '~/hooks';
|
||||
|
||||
const keys = new Set(Object.keys(defaultAgentFormValues));
|
||||
const SELECT_ID = 'agent-builder-combobox';
|
||||
|
||||
export default function AgentSelect({
|
||||
reset,
|
||||
agentQuery,
|
||||
value: currentAgentValue,
|
||||
selectedAgentId = null,
|
||||
setCurrentAgentId,
|
||||
createMutation,
|
||||
}: {
|
||||
reset: UseFormReset<AgentForm>;
|
||||
value?: TAgentOption;
|
||||
selectedAgentId: string | null;
|
||||
agentQuery: QueryObserverResult<Agent>;
|
||||
setCurrentAgentId: React.Dispatch<React.SetStateAction<string | undefined>>;
|
||||
|
|
@ -30,6 +25,7 @@ export default function AgentSelect({
|
|||
}) {
|
||||
const localize = useLocalize();
|
||||
const lastSelectedAgent = useRef<string | null>(null);
|
||||
const { control, reset } = useFormContext();
|
||||
|
||||
const { data: startupConfig } = useGetStartupConfig();
|
||||
const { data: agents = null } = useListAgentsQuery(undefined, {
|
||||
|
|
@ -121,9 +117,6 @@ export default function AgentSelect({
|
|||
}
|
||||
|
||||
resetAgentForm(agent);
|
||||
setTimeout(() => {
|
||||
document.getElementById(SELECT_ID)?.focus();
|
||||
}, 5);
|
||||
},
|
||||
[agents, createMutation, setCurrentAgentId, agentQuery.data, resetAgentForm, reset],
|
||||
);
|
||||
|
|
@ -158,34 +151,39 @@ export default function AgentSelect({
|
|||
const createAgent = localize('com_ui_create') + ' ' + localize('com_ui_agent');
|
||||
|
||||
return (
|
||||
<ControlCombobox
|
||||
selectId={SELECT_ID}
|
||||
containerClassName="px-0"
|
||||
selectedValue={(currentAgentValue?.value ?? '') + ''}
|
||||
displayValue={currentAgentValue?.label ?? ''}
|
||||
selectPlaceholder={createAgent}
|
||||
iconSide="right"
|
||||
searchPlaceholder={localize('com_agents_search_name')}
|
||||
SelectIcon={currentAgentValue?.icon}
|
||||
setValue={onSelect}
|
||||
items={
|
||||
agents?.map((agent) => ({
|
||||
label: agent.name ?? '',
|
||||
value: agent.id ?? '',
|
||||
icon: agent.icon,
|
||||
})) ?? [
|
||||
{
|
||||
label: 'Loading...',
|
||||
value: '',
|
||||
},
|
||||
]
|
||||
}
|
||||
className={cn(
|
||||
'z-50 flex h-[40px] w-full flex-none items-center justify-center truncate rounded-md bg-transparent font-bold',
|
||||
<Controller
|
||||
name="agent"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<ControlCombobox
|
||||
containerClassName="px-0"
|
||||
selectedValue={(field?.value?.value ?? '') + ''}
|
||||
displayValue={field?.value?.label ?? ''}
|
||||
selectPlaceholder={createAgent}
|
||||
iconSide="right"
|
||||
searchPlaceholder={localize('com_agents_search_name')}
|
||||
SelectIcon={field?.value?.icon}
|
||||
setValue={onSelect}
|
||||
items={
|
||||
agents?.map((agent) => ({
|
||||
label: agent.name ?? '',
|
||||
value: agent.id ?? '',
|
||||
icon: agent.icon,
|
||||
})) ?? [
|
||||
{
|
||||
label: 'Loading...',
|
||||
value: '',
|
||||
},
|
||||
]
|
||||
}
|
||||
className={cn(
|
||||
'z-50 flex h-[40px] w-full flex-none items-center justify-center truncate rounded-md bg-transparent font-bold',
|
||||
)}
|
||||
ariaLabel={localize('com_ui_agent')}
|
||||
isCollapsed={false}
|
||||
showCarat={true}
|
||||
/>
|
||||
)}
|
||||
ariaLabel={localize('com_ui_agent')}
|
||||
isCollapsed={false}
|
||||
showCarat={true}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -143,7 +143,7 @@
|
|||
"com_endpoint_thinking": "Thinking",
|
||||
"com_endpoint_thinking_budget": "Thinking Budget",
|
||||
"com_endpoint_anthropic_thinking": "Enables internal reasoning for supported Claude models (3.7 Sonnet). Note: requires \"Thinking Budget\" to be set and lower than \"Max Output Tokens\"",
|
||||
"com_endpoint_anthropic_thinking_budget": "Determines the max number of tokens Claude is allowed use for its internal reasoning process. Larger budgets can improve response quality by enabling more thorough analysis for complex problems, although Claude may not use the entire budget allocated, especially at ranges above 32K. This setting must be lower than \"Max Output Tokens.\"",
|
||||
"com_endpoint_anthropic_thinking_budget": "Determines the max number of tokens Claude is allowed to use for its internal reasoning process. Larger budgets can improve response quality by enabling more thorough analysis for complex problems, although Claude may not use the entire budget allocated, especially at ranges above 32K. This setting must be lower than \"Max Output Tokens.\"",
|
||||
"com_endpoint_anthropic_temp": "Ranges from 0 to 1. Use temp closer to 0 for analytical / multiple choice, and closer to 1 for creative and generative tasks. We recommend altering this or Top P but not both.",
|
||||
"com_endpoint_anthropic_topk": "Top-k changes how the model selects tokens for output. A top-k of 1 means the selected token is the most probable among all tokens in the model's vocabulary (also called greedy decoding), while a top-k of 3 means that the next token is selected from among the 3 most probable tokens (using temperature).",
|
||||
"com_endpoint_anthropic_topp": "Top-p changes how the model selects tokens for output. Tokens are selected from most K (see topK parameter) probable to least until the sum of their probabilities equals the top-p value.",
|
||||
|
|
|
|||
5447
package-lock.json
generated
5447
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -709,7 +709,7 @@ export const defaultModels = {
|
|||
// Gemini 2.0 Models
|
||||
'gemini-2.0-flash-001',
|
||||
'gemini-2.0-flash-exp',
|
||||
'gemini-2.0-flash-lite-preview-02-05',
|
||||
'gemini-2.0-flash-lite',
|
||||
'gemini-2.0-pro-exp-02-05',
|
||||
// Gemini 1.5 Models
|
||||
'gemini-1.5-flash-001',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue