📜 refactor: Improve Skill Handling Logs (#13057)

* refactor: Streamline batch upload error handling in `uploadCodeEnvFile`
* refactor: Enhance session info error logging in `getSessionInfo`
* refactor: Update error logging to use `logAxiosError` in various agent handlers and skill file processing functions
* refactor: Consolidate missing resource checks in `createToolExecuteHandler` for better clarity
This commit is contained in:
Danny Avila 2026-05-11 02:15:51 -04:00 committed by GitHub
parent 763fab2e3e
commit 7129b1b1e4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 96 additions and 92 deletions

View file

@ -150,71 +150,62 @@ async function uploadCodeEnvFile({ req, stream, filename, kind, id, version }) {
* @throws {Error} If the batch upload fails entirely.
*/
async function batchUploadCodeEnvFiles({ req, files, kind, id, version, read_only = false }) {
try {
const form = new FormData();
appendCodeEnvFileIdentity(form, { kind, id, version });
if (read_only) {
form.append('read_only', 'true');
}
for (const file of files) {
appendCodeEnvFile(form, file.stream, file.filename);
}
const baseURL = getCodeBaseURL();
const authHeaders = await getCodeApiAuthHeaders(req);
/** @type {import('axios').AxiosRequestConfig} */
const options = {
headers: {
...form.getHeaders(),
'Content-Type': 'multipart/form-data',
'User-Agent': 'LibreChat/1.0',
'User-Id': req.user.id,
...authHeaders,
},
httpAgent: codeServerHttpAgent,
httpsAgent: codeServerHttpsAgent,
timeout: 120000,
maxContentLength: MAX_FILE_SIZE,
maxBodyLength: MAX_FILE_SIZE,
};
const response = await axios.post(`${baseURL}/upload/batch`, form, options);
/** @type {{ message: string; storage_session_id: string; files: Array<{ status: string; fileId?: string; filename: string; error?: string }>; succeeded: number; failed: number }} */
const result = response.data;
if (
!result ||
typeof result !== 'object' ||
!result.storage_session_id ||
!Array.isArray(result.files)
) {
throw new Error(`Unexpected batch upload response: ${JSON.stringify(result).slice(0, 200)}`);
}
if (result.message === 'error') {
throw new Error('All files in batch upload failed');
}
if (result.failed > 0) {
const failedNames = result.files
.filter((f) => f.status === 'error')
.map((f) => `${f.filename}: ${f.error || 'unknown'}`)
.join(', ');
logger.warn(`[batchUploadCodeEnvFiles] ${result.failed} file(s) failed: ${failedNames}`);
}
const successFiles = result.files
.filter((f) => f.status === 'success' && f.fileId)
.map((f) => ({ fileId: f.fileId, filename: f.filename }));
return { storage_session_id: result.storage_session_id, files: successFiles };
} catch (error) {
throw new Error(
logAxiosError({
message: `Error in batch upload to code environment: ${error instanceof Error ? error.message : String(error)}`,
error,
}),
);
const form = new FormData();
appendCodeEnvFileIdentity(form, { kind, id, version });
if (read_only) {
form.append('read_only', 'true');
}
for (const file of files) {
appendCodeEnvFile(form, file.stream, file.filename);
}
const baseURL = getCodeBaseURL();
const authHeaders = await getCodeApiAuthHeaders(req);
/** @type {import('axios').AxiosRequestConfig} */
const options = {
headers: {
...form.getHeaders(),
'Content-Type': 'multipart/form-data',
'User-Agent': 'LibreChat/1.0',
'User-Id': req.user.id,
...authHeaders,
},
httpAgent: codeServerHttpAgent,
httpsAgent: codeServerHttpsAgent,
timeout: 120000,
maxContentLength: MAX_FILE_SIZE,
maxBodyLength: MAX_FILE_SIZE,
};
const response = await axios.post(`${baseURL}/upload/batch`, form, options);
/** @type {{ message: string; storage_session_id: string; files: Array<{ status: string; fileId?: string; filename: string; error?: string }>; succeeded: number; failed: number }} */
const result = response.data;
if (
!result ||
typeof result !== 'object' ||
!result.storage_session_id ||
!Array.isArray(result.files)
) {
throw new Error(`Unexpected batch upload response: ${JSON.stringify(result).slice(0, 200)}`);
}
if (result.message === 'error') {
throw new Error('All files in batch upload failed');
}
if (result.failed > 0) {
const failedNames = result.files
.filter((f) => f.status === 'error')
.map((f) => `${f.filename}: ${f.error || 'unknown'}`)
.join(', ');
logger.warn(`[batchUploadCodeEnvFiles] ${result.failed} file(s) failed: ${failedNames}`);
}
const successFiles = result.files
.filter((f) => f.status === 'success' && f.fileId)
.map((f) => ({ fileId: f.fileId, filename: f.filename }));
return { storage_session_id: result.storage_session_id, files: successFiles };
}
module.exports = {

View file

@ -707,10 +707,9 @@ async function getSessionInfo(ref, req) {
return response.data?.lastModified;
} catch (error) {
logAxiosError({
message: `Error fetching session info: ${error.message}`,
error,
});
logger.debug(
`[getSessionInfo] session lookup failed (treating as cache miss): ${error?.message ?? String(error)}`,
);
return null;
}
}

View file

@ -14,10 +14,10 @@ import type { CodeEnvRef } from 'librechat-data-provider';
import type { StructuredToolInterface } from '@librechat/agents/langchain/tools';
import type { SkillFileRecord } from './skillFiles';
import type { ServerRequest } from '~/types';
import { logAxiosError, runOutsideTracing } from '~/utils';
import { buildSkillPrimeMessage } from './skills';
import { cleanCodeToolOutput } from './cleanup';
import { primeSkillFiles } from './skillFiles';
import { runOutsideTracing } from '~/utils';
export interface ToolEndCallbackData {
output: {
@ -753,10 +753,10 @@ async function handleReadFileCall(
if (file.isBinary == null && updateSkillFileContent) {
updateSkillFileContent(skill._id, relativePath, { isBinary: true }).catch(
(err: unknown) => {
logger.warn(
'[handleReadFileCall] cache write failed:',
err instanceof Error ? err.message : err,
);
logAxiosError({
message: '[handleReadFileCall] cache write failed',
error: err,
});
},
);
}
@ -793,10 +793,10 @@ async function handleReadFileCall(
if (file.content == null && updateSkillFileContent && buffer.length <= MAX_CACHE_BYTES) {
updateSkillFileContent(skill._id, relativePath, { content: text, isBinary: false }).catch(
(err: unknown) => {
logger.warn(
'[handleReadFileCall] cache write failed:',
err instanceof Error ? err.message : err,
);
logAxiosError({
message: '[handleReadFileCall] cache write failed',
error: err,
});
},
);
}
@ -1081,11 +1081,23 @@ export function createToolExecuteHandler(options: ToolExecuteOptions): EventHand
typeof f.storage_session_id === 'string' && !!f.storage_session_id,
hasVersion: typeof f.version === 'number',
}));
const missingResourceId = summary.filter((s) => !s.hasResourceId).length;
let missingResourceId = 0;
let missingStorageSessionId = 0;
let missingVersion = 0;
const kindCounts: Record<string, number> = {};
for (const s of summary) {
if (!s.hasResourceId) missingResourceId++;
if (!s.hasStorageSessionId) missingStorageSessionId++;
if (!s.hasVersion) missingVersion++;
const k = typeof s.kind === 'string' ? s.kind : 'unknown';
kindCounts[k] = (kindCounts[k] ?? 0) + 1;
}
logger.debug(
`[code-env:inject] tool=${tc.name} files=${refs.length} ` +
`missingResourceId=${missingResourceId}`,
{ summary },
`missingResourceId=${missingResourceId} ` +
`missingStorageSessionId=${missingStorageSessionId} ` +
`missingVersion=${missingVersion} ` +
`kinds=${JSON.stringify(kindCounts)}`,
);
if (missingResourceId > 0) {
logger.warn(

View file

@ -6,6 +6,7 @@ import type { CodeEnvRef } from 'librechat-data-provider';
import type { Types } from 'mongoose';
import type { ServerRequest } from '~/types';
import { extractInvokedSkillsFromPayload } from './run';
import { logAxiosError } from '~/utils';
export interface SkillFileRecord {
relativePath: string;
@ -288,20 +289,20 @@ export async function primeSkillFiles(
try {
await updateSkillFileCodeEnvIds(updates);
} catch (err: unknown) {
logger.warn(
'[primeSkillFiles] Failed to persist codeEnvRefs:',
err instanceof Error ? err.message : err,
);
logAxiosError({
message: `[primeSkillFiles] Failed to persist codeEnvRefs`,
error: err,
});
}
}
}
return { storage_session_id: result.storage_session_id, files };
} catch (error) {
logger.error(
`[primeSkillFiles] Batch upload failed for skill "${skill.name}":`,
error instanceof Error ? error.message : error,
);
logAxiosError({
message: `[primeSkillFiles] Batch upload failed for skill "${skill.name}"`,
error,
});
return null;
}
}

View file

@ -8,6 +8,7 @@ import type { Agent } from 'librechat-data-provider';
import type { Types } from 'mongoose';
import type { InitializeAgentDbMethods } from './initialize';
import { registerCodeExecutionTools } from './tools';
import { logAxiosError } from '~/utils';
const SKILL_CATALOG_LIMIT = 100;
/** Max pages scanned per run when filtering out inactive skills. */
@ -809,10 +810,10 @@ export async function resolveAlwaysApplySkills(
cursor,
});
} catch (err) {
logger.warn(
'[resolveAlwaysApplySkills] listAlwaysApplySkills failed:',
err instanceof Error ? err.message : err,
);
logAxiosError({
message: '[resolveAlwaysApplySkills] listAlwaysApplySkills failed',
error: err,
});
return resolved;
}