做健身推廣網(wǎng)站龍南城市建設(shè)局網(wǎng)站
鶴壁市浩天電氣有限公司
2026/01/24 13:06:16
做健身推廣網(wǎng)站,龍南城市建設(shè)局網(wǎng)站,免費購物網(wǎng)站源碼,自己做網(wǎng)站哪家好7.4 綜合實戰(zhàn)#xff1a;視覺代理MCP服務器在本節(jié)的實例中#xff0c;實現(xiàn)了一個輕量級的MCP 服務器#xff0c;由Landing AI團隊開發(fā)#xff0c;能將來自兼容 MCP 協(xié)議的客戶端#xff08;如Claude Desktop、Cursor、Cline等#xff09;的每個工具調(diào)用#xff0c;轉(zhuǎn)換…7.4 綜合實戰(zhàn)視覺代理MCP服務器在本節(jié)的實例中實現(xiàn)了一個輕量級的MCP 服務器由Landing AI團隊開發(fā)能將來自兼容 MCP 協(xié)議的客戶端如Claude Desktop、Cursor、Cline等的每個工具調(diào)用轉(zhuǎn)換為對Landing AI的VisionAgent REST API的認證HTTPS請求并將包含圖像或掩碼的響應JSON流回模型讓用戶無需編寫自定義 REST代碼或加載額外SDK就能在編輯器中發(fā)出自然語言的計算機視覺和文檔分析命令。實例7-2執(zhí)行計算機視覺和文檔分析任務的MCP服務器源碼路徑codes7vision-agent-mcp7.4.1 項目介紹本項實現(xiàn)了一個MCP服務器允許用戶通過自然語言命令在編輯器中執(zhí)行計算機視覺和文檔分析任務。本項目的主要功能模塊如下所示。agentic-document-analysis解析PDF/圖像提取文本、表格、圖表和diagrams同時考慮布局和其他視覺線索有Web版本text-to-object-detection使用OWLv2、CountGD、Florence-2、AgenticObjectDetection檢測自由形式的提示如“所有交通燈”輸出邊界框有Web版本text-to-instance-segmentation 通過Florence-2Segment-Anything-v2(SAM-2)實現(xiàn)像素級完美掩碼activity-recognition 識別視頻中的多個活動并提供開始/結(jié)束時間戳depth-pro 對單張圖像進行高分辨率單目深度估計7.4.2 通用工具1文件src/utils/errors.ts定義了一個錯誤處理模塊用于在Node.js應用程序中創(chuàng)建和管理自定義錯誤類型。它擴展了JavaScript的內(nèi)置Error 類創(chuàng)建了幾種特定于應用程序的錯誤類型如 VisionAgentError、ValidationError、FileProcessingError、ApiRequestError和ConfigurationError。每種錯誤類型都包含一個錯誤代碼、可選的狀態(tài)碼和詳細信息。此外還實現(xiàn)了一些輔助函數(shù)用于從錯誤對象創(chuàng)建API 錯誤響應、創(chuàng)建驗證結(jié)果以及格式化用戶友好的錯誤消息。/** * 視覺代理的基礎(chǔ)錯誤類繼承自原生Error */ export class VisionAgentError extends Error { public readonly code: string; public readonly status?: number; public readonly details?: Recordstring, unknown; /** * 創(chuàng)建一個VisionAgentError實例 * param message 錯誤消息 * param code 錯誤代碼 * param status HTTP狀態(tài)碼可選 * param details 錯誤詳情可選 */ constructor(message: string, code: string, status?: number, details?: Recordstring, unknown) { super(message); this.name VisionAgentError; this.code code; this.status status; this.details details; } } /** * 驗證錯誤類表示數(shù)據(jù)驗證失敗 */ export class ValidationError extends VisionAgentError { /** * 創(chuàng)建一個ValidationError實例 * param message 錯誤消息 * param details 錯誤詳情可選 */ constructor(message: string, details?: Recordstring, unknown) { super(message, VALIDATION_ERROR, 400, details); this.name ValidationError; } } /** * 文件處理錯誤類表示文件處理過程中發(fā)生的錯誤 */ export class FileProcessingError extends VisionAgentError { /** * 創(chuàng)建一個FileProcessingError實例 * param message 錯誤消息 * param details 錯誤詳情可選 */ constructor(message: string, details?: Recordstring, unknown) { super(message, FILE_PROCESSING_ERROR, 400, details); this.name FileProcessingError; } } /** * API請求錯誤類表示API請求過程中發(fā)生的錯誤 */ export class ApiRequestError extends VisionAgentError { /** * 創(chuàng)建一個ApiRequestError實例 * param message 錯誤消息 * param status HTTP狀態(tài)碼 * param details 錯誤詳情可選 */ constructor(message: string, status: number, details?: Recordstring, unknown) { super(message, API_REQUEST_ERROR, status, details); this.name ApiRequestError; } } /** * 配置錯誤類表示配置相關(guān)的錯誤 */ export class ConfigurationError extends VisionAgentError { /** * 創(chuàng)建一個ConfigurationError實例 * param message 錯誤消息 * param details 錯誤詳情可選 */ constructor(message: string, details?: Recordstring, unknown) { super(message, CONFIGURATION_ERROR, 500, details); this.name ConfigurationError; } } /** * 將錯誤轉(zhuǎn)換為ApiError對象 * param error 要轉(zhuǎn)換的錯誤 * returns 轉(zhuǎn)換后的ApiError對象 */ export function createApiError(error: unknown): ApiError { if (error instanceof VisionAgentError) { return { code: error.code, message: error.message, status: error.status, details: error.details }; } if (error instanceof Error) { return { code: UNKNOWN_ERROR, message: error.message, status: 500 }; } return { code: UNKNOWN_ERROR, message: 發(fā)生未知錯誤, status: 500, details: { originalError: error } }; } /** * 創(chuàng)建驗證結(jié)果對象成功情況 * param success 表示驗證是否成功此處為true * param data 驗證成功時返回的數(shù)據(jù) * returns 驗證結(jié)果對象 */ export function createValidationResultT(success: true, data: T): ValidationResultT; /** * 創(chuàng)建驗證結(jié)果對象失敗情況 * param success 表示驗證是否成功此處為false * param error 驗證失敗時的錯誤消息 * returns 驗證結(jié)果對象 */ export function createValidationResultT(success: false, error: string): ValidationResultT; /** * 創(chuàng)建驗證結(jié)果對象的實現(xiàn) * param success 表示驗證是否成功 * param dataOrError 驗證成功時的數(shù)據(jù)或驗證失敗時的錯誤消息 * returns 驗證結(jié)果對象 */ export function createValidationResultT(success: boolean, dataOrError: T | string): ValidationResultT { if (success) { return { success: true, data: dataOrError as T }; } else { return { success: false, error: dataOrError as string }; } } /** * 判斷錯誤是否為Axios錯誤 * param error 要檢查的錯誤 * returns 如果是Axios錯誤則返回true否則返回false */ export function isAxiosError(error: unknown): error is { isAxiosError: true; response?: { status: number; data: unknown } } { return typeof error object error ! null isAxiosError in error error.isAxiosError true; } /** * 格式化錯誤信息使其更適合展示給用戶 * param error 要格式化的ApiError對象 * returns 格式化后的錯誤字符串 */ export function formatErrorForUser(error: ApiError): string { let message 錯誤 ${error.code}; if (error.status) { message (${error.status}); } message : ${error.message}; if (error.details Object.keys(error.details).length 0) { const detailsStr Object.entries(error.details) .map(([key, value]) ${key}: ${String(value)}) .join(, ); message (${detailsStr}); } return message; }2文件src/utils/file.ts是一個文件處理相關(guān)的工具函數(shù)集合主要功能包括檢測文件類型驗證文件大小和類型是否符合要求將文件轉(zhuǎn)換為Base64編碼支持從本地路徑或遠程URL獲取文件對圖像還會進行裁剪、格式轉(zhuǎn)換等處理從Base64字符串加載文件并確定內(nèi)容類型和文件名以及處理工具參數(shù)中的文件包括下載遠程文件到臨時目錄、轉(zhuǎn)換為 Base64 等最后清理臨時文件為視覺分析等操作提供文件預處理支持。下面這段代碼的功能是將文件轉(zhuǎn)換為Base64編碼支持從遠程URL或本地路徑獲取文件對圖像文件會進行裁剪、格式轉(zhuǎn)換等處理同時會驗證文件類型和大小是否符合要求。原理是先判斷文件來源遠程或本地獲取文件緩沖區(qū)后根據(jù)文件類型進行相應處理圖像文件用sharp庫處理后轉(zhuǎn)換為Base64其他類型直接轉(zhuǎn)換。export async function fileToBase64(input: string, options: LoadFileOptions {}): Promisestring {try {//處理文件前驗證文件類型const fileType options.fileType || detectFileType(input);validateFileType(input, fileType);let buffer: Buffer;if (input.startsWith(http://) || input.startsWith(https://)) {//從遠程URL獲取文件const response await axios.get(input, {responseType: arraybuffer,timeout: FILE_LIMITS.DOWNLOAD_TIMEOUT_MS,maxContentLength: getMaxSizeForFileType(fileType)});buffer Buffer.from(response.data, binary);} else {//驗證本地文件是否存在且可讀await fs.promises.access(input, fs.constants.R_OK);buffer await fs.promises.readFile(input);}//驗證文件大小validateFileSize(buffer, fileType);switch (fileType) {case image://對圖像進行處理移除透明度、調(diào)整大小、轉(zhuǎn)換為png格式const processedImage await sharp(buffer).removeAlpha().resize({width: options.sharpOptions?.formatOptions?.width as number || undefined,height: options.sharpOptions?.formatOptions?.height as number || undefined,fit: inside,withoutEnlargement: true}).toFormat(png).toBuffer();return processedImage.toString(base64);default://非圖像文件直接轉(zhuǎn)換為Base64return buffer.toString(base64);}} catch (err) {if (err instanceof Error) {throw new Error(處理文件${input}失敗: ${err.message});} else {throw new Error(處理文件${input}失敗:未知錯誤);}}}下面這段代碼的功能是從Base64字符串加載文件確定文件的緩沖區(qū)、文件名、內(nèi)容類型等信息。原理是先處理Base64字符串去除數(shù)據(jù)頭轉(zhuǎn)換為緩沖區(qū)然后驗證文件大小如果指定了文件類型再確定內(nèi)容類型和文件名最后返回包含這些信息的對象。export function loadFileFromBase64(base64String: string,options: LoadFileOptions): LoadedFile {//檢查Base64字符串是否有效if (!base64String || typeof base64String ! string) {throw new Error(提供的Base64字符串無效);}//移除Base64字符串的數(shù)據(jù)頭const base64Data base64String.replace(/^data:([^;]);base64,/, );const buffer Buffer.from(base64Data, base64);//如果指定了文件類型驗證文件大小if (options.fileType) {validateFileSize(buffer, options.fileType);}//確定內(nèi)容類型let contentType options.contentType;if (!contentType) {contentType detectContentTypeFromBase64(base64Data, options.fileType);}//確定文件名let filename options.filename || file;if (!filename.includes(.)) {const extension getExtensionFromContentType(contentType);filename ${filename}.${extension};}return {buffer,filename,contentType,originalSize: buffer.length};}下面這段代碼的功能是處理工具參數(shù)中的文件包括下載遠程文件到臨時目錄、將文件轉(zhuǎn)換為 Base64等處理完成后清理臨時文件。原理是遍歷工具參數(shù)中的不同文件類型對每個文件進行來源判斷本地或遠程遠程文件下載到臨時目錄后轉(zhuǎn)換為Base64處理完成后通過finally塊清理臨時文件。export async function processFileArgs(toolArgs: JsonObject): PromiseJsonObject {const fileTypeMap {images: image,pdfs: pdf,videos: video} as const;const allFileTypes [image, video, pdf, images, pdfs, videos] as const;if (!toolArgs[requestBody] || typeof toolArgs[requestBody] ! object) {return toolArgs;}//跟蹤臨時文件以便清理const tempFiles: string[] [];try {for (const fileType of allFileTypes) {if (fileType in toolArgs[requestBody]) {const isSingular [image, video, pdf].includes(fileType);const isPluralArray Array.isArray(toolArgs[requestBody][fileType]);if (isSingular || !isPluralArray) {let url toolArgs[requestBody][fileType] as string;if (url?.startsWith()) {url url.slice(1);}//處理遠程URLif (url?.startsWith(http://) || url?.startsWith(https://)) {url await downloadToTemp(url, fileType);tempFiles.push(url);}if (url) {const normalizedPath path.normalize(url);if (!path.isAbsolute(normalizedPath)) {throw new Error(請為${fileType}提供全局絕對文件路徑而不是本地路徑。);}const conversionFileType isSingular ? fileType as FileType : fileTypeMap[fileType as keyof typeof fileTypeMap];const fileBase64 await fileToBase64(url, { fileType: conversionFileType });toolArgs[requestBody][fileType] fileBase64;}} else if (isPluralArray) {const files toolArgs[requestBody][fileType] as string[];//驗證數(shù)組大小if (files.length FILE_LIMITS.MAX_FILES_IN_ARRAY) {throw new Error(在${fileType}數(shù)組中文件數(shù)量過多。允許的最大數(shù)量${FILE_LIMITS.MAX_FILES_IN_ARRAY}提供的數(shù)量${files.length});}const conversionFileType fileTypeMap[fileType as keyof typeof fileTypeMap];const processedFiles: string[] [];for (let i 0; i files.length; i) {let url files[i];if (url?.startsWith()) {url url.slice(1);}//處理遠程URLif (url?.startsWith(http://) || url?.startsWith(https://)) {url await downloadToTemp(url, fileType);tempFiles.push(url);}if (url) {const normalizedPath path.normalize(url);if (!path.isAbsolute(normalizedPath)) {throw new Error(請為${fileType}[${i}]提供全局絕對文件路徑而不是本地路徑。);}const fileBase64 await fileToBase64(url, { fileType: conversionFileType as FileType });processedFiles.push(fileBase64);}}toolArgs[requestBody][fileType] processedFiles;}}}return toolArgs;} finally {//清理臨時文件await Promise.allSettled(tempFiles.map(async (tempFile) {try {await fs.promises.unlink(tempFile);} catch (error) {console.warn(清理臨時文件失敗${tempFile}, error);}}));}}下面這段代碼的功能是從遠程URL下載文件到臨時目錄。原理是使用fetch方法獲取遠程文件驗證下載文件的大小是否符合對應文件類型的限制然后將文件內(nèi)容寫入臨時目錄返回臨時文件路徑。async function downloadToTemp(url: string, fileType: string): Promisestring {try {//從遠程URL獲取文件const response await fetch(url, {signal: AbortSignal.timeout(FILE_LIMITS.DOWNLOAD_TIMEOUT_MS)});//檢查響應是否成功if (!response.ok) {throw new Error(HTTP ${response.status}: ${response.statusText});}//獲取文件大小并驗證const contentLength response.headers.get(content-length);if (contentLength) {const size parseInt(contentLength, 10);const detectedFileType detectFileType(url);const maxSize getMaxSizeForFileType(detectedFileType);if (size maxSize) {throw new Error(下載大小${(size / (1024 * 1024)).toFixed(2)}MB超過了${detectedFileType}文件的限制);}}//將響應轉(zhuǎn)換為緩沖區(qū)const buffer await response.arrayBuffer();validateFileSize(Buffer.from(buffer), detectFileType(url));//確定臨時文件擴展名const ext getFileExtension(fileType);const tempPath path.join(os.tmpdir(), vision_agent_temp_${Date.now()}_${Math.random().toString(36).substr(2, 9)}${ext});//將文件寫入臨時目錄await fs.promises.writeFile(tempPath, Buffer.from(buffer));return tempPath;} catch (error) {if (error instanceof Error) {throw new Error(下載${url}失敗: ${error.message});}throw new Error(下載${url}失敗:未知錯誤);}}3文件src/utils/http.ts的功能是對HTTP請求或響應中的數(shù)據(jù)進行安全處理主要用于日志記錄時脫敏敏感信息如API密鑰、令牌、密碼等同時限制日志內(nèi)容的長度防止敏感數(shù)據(jù)泄露并控制日志體積。處理邏輯會根據(jù)數(shù)據(jù)類型字符串、對象或其他類型采取不同的脫敏策略確保關(guān)鍵信息被替換為[REDACTED]。if (typeof data string) { // 移除潛在的敏感信息 return data .replace(/Bearers[A-Za-z0-9._-]/g, Bearer [REDACTED]) // 脫敏Bearer令牌 .replace(/Basics[A-Za-z0-9/]/g, Basic [REDACTED]) // 脫敏Basic認證信息 .replace(/apiKeys*:s*[^]/g, apiKey: [REDACTED]) // 脫敏apiKey字段 .substring(0, HTTP_LIMITS.MAX_RESPONSE_LOG_LENGTH); // 限制日志長度 } if (typeof data object data ! null) { try { // 序列化對象時脫敏敏感字段包含key、token、password、secret的字段 const sanitized JSON.stringify(data, (key, value) { if (typeof key string (key.toLowerCase().includes(key) || key.toLowerCase().includes(token) || key.toLowerCase().includes(password) || key.toLowerCase().includes(secret))) { return [REDACTED]; } return value; }); return sanitized.substring(0, HTTP_LIMITS.MAX_RESPONSE_LOG_LENGTH); // 限制日志長度 } catch { return [無法序列化對象]; } } // 其他類型數(shù)據(jù)轉(zhuǎn)為字符串后限制長度 return String(data).substring(0, HTTP_LIMITS.MAX_RESPONSE_LOG_LENGTH);