mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-05-13 07:46:47 +00:00
📜 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:
parent
763fab2e3e
commit
7129b1b1e4
5 changed files with 96 additions and 92 deletions
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue