🔧 refactor: Centralize Default Agent Capabilities and Better Logging (#7598)

* refactor: Simplify grid column calculation in SourcesGroup component

* refactor: Centralize default agent capabilities and simplify capability assignment

* Edge case: use defined/fallback capabilities for ephemeral agents when the "agents" endpoint is not enabled

* refactor: consolidate gemini 2 vision check

* feat: enhance capability check logging for agents

* chore: update librechat-data-provider version to 0.7.86

* refactor: import default agent capabilities for enhanced capability management

* chore: standardize quotes in error message check for consistency

* fix: improve error logging both client and api-side for mistral ocr upload errors

* ci: update error handling in MistralOCR tests to use specific error message
This commit is contained in:
Danny Avila 2025-05-27 15:48:43 -04:00 committed by GitHub
parent 077b7e7e79
commit 2f462c9b3c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 57 additions and 44 deletions

View file

@ -283,6 +283,10 @@ router.post('/', async (req, res) => {
message += ': ' + error.message;
}
if (error.message?.includes('Invalid file format')) {
message = error.message;
}
// TODO: delete remote file if it exists
try {
await fs.unlink(req.file.path);

View file

@ -47,7 +47,6 @@ async function uploadDocumentToMistral({
})
.then((res) => res.data)
.catch((error) => {
logger.error('Error uploading document to Mistral:', error.message);
throw error;
});
}
@ -217,8 +216,16 @@ const uploadMistralOCR = async ({ req, file, file_id, entity_id }) => {
images,
};
} catch (error) {
const message = 'Error uploading document to Mistral OCR API';
throw new Error(logAxiosError({ error, message }));
let message = 'Error uploading document to Mistral OCR API';
const detail = error?.response?.data?.detail;
if (detail && detail !== '') {
message = detail;
}
const responseMessage = error?.response?.data?.message;
throw new Error(
`${logAxiosError({ error, message })}${responseMessage && responseMessage !== '' ? ` - ${responseMessage}` : ''}`,
);
}
};

View file

@ -124,13 +124,7 @@ describe('MistralOCR Service', () => {
fileName: 'test.pdf',
apiKey: 'test-api-key',
}),
).rejects.toThrow();
const { logger } = require('~/config');
expect(logger.error).toHaveBeenCalledWith(
expect.stringContaining('Error uploading document to Mistral:'),
expect.any(String),
);
).rejects.toThrow(errorMessage);
});
});

View file

@ -5,6 +5,7 @@ const { Calculator } = require('@langchain/community/tools/calculator');
const { tool: toolFn, Tool, DynamicStructuredTool } = require('@langchain/core/tools');
const {
Tools,
Constants,
ErrorTypes,
ContentTypes,
imageGenTools,
@ -14,6 +15,7 @@ const {
ImageVisionTool,
openapiToFunction,
AgentCapabilities,
defaultAgentCapabilities,
validateAndParseOpenAPISpec,
} = require('librechat-data-provider');
const {
@ -501,8 +503,22 @@ async function loadAgentTools({ req, res, agent, tool_resources, openAIApiKey })
}
const endpointsConfig = await getEndpointsConfig(req);
const enabledCapabilities = new Set(endpointsConfig?.[EModelEndpoint.agents]?.capabilities ?? []);
const checkCapability = (capability) => enabledCapabilities.has(capability);
let enabledCapabilities = new Set(endpointsConfig?.[EModelEndpoint.agents]?.capabilities ?? []);
/** Edge case: use defined/fallback capabilities when the "agents" endpoint is not enabled */
if (enabledCapabilities.size === 0 && agent.id === Constants.EPHEMERAL_AGENT_ID) {
enabledCapabilities = new Set(
req.app?.locals?.[EModelEndpoint.agents]?.capabilities ?? defaultAgentCapabilities,
);
}
const checkCapability = (capability) => {
const enabled = enabledCapabilities.has(capability);
if (!enabled) {
logger.warn(
`Capability "${capability}" disabled${capability === AgentCapabilities.tools ? '.' : ' despite configured tool.'} User: ${req.user.id} | Agent: ${agent.id}`,
);
}
return enabled;
};
const areToolsEnabled = checkCapability(AgentCapabilities.tools);
let includesWebSearch = false;

View file

@ -4,10 +4,10 @@ const {
Capabilities,
EModelEndpoint,
isAgentsEndpoint,
AgentCapabilities,
isAssistantsEndpoint,
defaultRetrievalModels,
defaultAssistantsVersion,
defaultAgentCapabilities,
} = require('librechat-data-provider');
const { Providers } = require('@librechat/agents');
const partialRight = require('lodash/partialRight');
@ -197,16 +197,7 @@ function generateConfig(key, baseURL, endpoint) {
}
if (agents) {
config.capabilities = [
AgentCapabilities.execute_code,
AgentCapabilities.file_search,
AgentCapabilities.web_search,
AgentCapabilities.artifacts,
AgentCapabilities.actions,
AgentCapabilities.tools,
AgentCapabilities.chain,
AgentCapabilities.ocr,
];
config.capabilities = defaultAgentCapabilities;
}
if (assistants && endpoint === EModelEndpoint.azureAssistants) {

View file

@ -29,7 +29,7 @@ const logAxiosError = ({ message, error }) => {
requestInfo: { method, url },
stack,
});
} else if (error?.message?.includes('Cannot read properties of undefined (reading \'status\')')) {
} else if (error?.message?.includes("Cannot read properties of undefined (reading 'status')")) {
logMessage = `${message} It appears the request timed out or was unsuccessful: ${error.message}`;
logger.error(logMessage, { stack });
} else {

View file

@ -1,7 +1,7 @@
import * as Ariakit from '@ariakit/react';
import React, { useRef, useState, useMemo } from 'react';
import { EToolResources, EModelEndpoint } from 'librechat-data-provider';
import { FileSearch, ImageUpIcon, TerminalSquareIcon, FileType2Icon } from 'lucide-react';
import { EToolResources, EModelEndpoint, defaultAgentCapabilities } from 'librechat-data-provider';
import { FileUpload, TooltipAnchor, DropdownPopup, AttachmentIcon } from '~/components';
import { useGetEndpointsQuery } from '~/data-provider';
import { useLocalize, useFileHandling } from '~/hooks';
@ -22,6 +22,10 @@ const AttachFile = ({ disabled }: AttachFileProps) => {
overrideEndpoint: EModelEndpoint.agents,
});
/** TODO: Ephemeral Agent Capabilities
* Allow defining agent capabilities on a per-endpoint basis
* Use definition for agents endpoint for ephemeral agents
* */
const capabilities = useMemo(
() => endpointsConfig?.[EModelEndpoint.agents]?.capabilities ?? [],
[endpointsConfig],

View file

@ -190,12 +190,8 @@ function SourcesGroup({ sources, limit = 3 }: { sources: ValidSource[]; limit?:
const remainingSources = sources.slice(limit);
const hasMoreSources = remainingSources.length > 0;
/** Calculate grid columns based on number of items (including the +X sources button if present) */
const totalItems = hasMoreSources ? visibleSources.length + 1 : visibleSources.length;
const gridCols = `grid-cols-${Math.min(totalItems, 4)}`;
return (
<div className={`grid ${gridCols} scrollbar-none w-full gap-2 overflow-x-auto`}>
<div className="scrollbar-none grid w-full grid-cols-4 gap-2 overflow-x-auto">
<OGDialog>
{visibleSources.map((source, i) => (
<div key={`source-${i}`} className="w-full min-w-[120px]">

2
package-lock.json generated
View file

@ -45274,7 +45274,7 @@
},
"packages/data-provider": {
"name": "librechat-data-provider",
"version": "0.7.85",
"version": "0.7.86",
"license": "ISC",
"dependencies": {
"axios": "^1.8.2",

View file

@ -1,6 +1,6 @@
{
"name": "librechat-data-provider",
"version": "0.7.85",
"version": "0.7.86",
"description": "data services for librechat apps",
"main": "dist/index.js",
"module": "dist/index.es.js",

View file

@ -233,6 +233,17 @@ export const assistantEndpointSchema = baseEndpointSchema.merge(
export type TAssistantEndpoint = z.infer<typeof assistantEndpointSchema>;
export const defaultAgentCapabilities = [
AgentCapabilities.execute_code,
AgentCapabilities.file_search,
AgentCapabilities.web_search,
AgentCapabilities.artifacts,
AgentCapabilities.actions,
AgentCapabilities.tools,
AgentCapabilities.chain,
AgentCapabilities.ocr,
];
export const agentsEndpointSChema = baseEndpointSchema.merge(
z.object({
/* agents specific */
@ -243,16 +254,7 @@ export const agentsEndpointSChema = baseEndpointSchema.merge(
capabilities: z
.array(z.nativeEnum(AgentCapabilities))
.optional()
.default([
AgentCapabilities.execute_code,
AgentCapabilities.file_search,
AgentCapabilities.web_search,
AgentCapabilities.artifacts,
AgentCapabilities.actions,
AgentCapabilities.tools,
AgentCapabilities.chain,
AgentCapabilities.ocr,
]),
.default(defaultAgentCapabilities),
}),
);
@ -950,8 +952,7 @@ export const visionModels = [
'gemma',
'gemini-exp',
'gemini-1.5',
'gemini-2.0',
'gemini-2.5',
'gemini-2',
'gemini-3',
'moondream',
'llama3.2-vision',