news 2026/9/21 8:09:58

(7-4-02)基于MCP实现的金融投资Agent(2)视觉代理MCP服务器:图像处理+数据验证

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
(7-4-02)基于MCP实现的金融投资Agent(2)视觉代理MCP服务器:图像处理+数据验证

7.4.3 图像处理

(1)文件src/image/processing.ts是图像处理相关的工具函数集合,主要用于图像颜色空间转换、掩码处理、图像旋转和画布操作等功能。具体包括:HSL 到 RGBA 颜色空间的转换;将编码的掩码数据解码为像素数组;将位图顺时针旋转 90 度并翻转;将位图应用到图像数据上并赋予指定颜色;加载图像缓冲区为图像对象;创建新的画布等。这些功能共同支持了视觉分析结果(如目标分割掩码)的处理和可视化。

/** * 将HSL颜色值转换为RGBA颜色值 * @param h 色相(0-360) * @param s 饱和度(0-100) * @param l 亮度(0-100) * @param a 透明度(0-1,默认值为1) * @returns RGBA颜色对象 */ export function HSLToRGB(h: number, s: number, l: number, a = 1): ColorRGBA { s /= 100; l /= 100; const c = (1 - Math.abs(2 * l - 1)) * s; const x = c * (1 - Math.abs((h / 60) % 2 - 1)); const m = l - c / 2; let r, g, b; if (h >= 0 && h < 60) { [r, g, b] = [c, x, 0]; } else if (h >= 60 && h < 120) { [r, g, b] = [x, c, 0]; } else if (h >= 120 && h < 180) { [r, g, b] = [0, c, x]; } else if (h >= 180 && h < 240) { [r, g, b] = [0, x, c]; } else if (h >= 240 && h < 300) { [r, g, b] = [x, 0, c]; } else { [r, g, b] = [c, 0, x]; } return { r: Math.round((r + m) * 255), g: Math.round((g + m) * 255), b: Math.round((b + m) * 255), a: a }; } /** * 将编码的掩码数据解码为位图 * @param counts 编码的掩码计数数组 * @param size 图像尺寸,格式为[高度, 宽度] * @returns 解码后的位图(Uint8Array) */ export function decodeMask(counts: string | any[], size: [any, any]): Uint8Array { const [height, width] = size; const bitmap = new Uint8Array(width * height); let pixel = 0; let value = 0; for (let i = 0; i < counts.length; i++) { const count = counts[i]; for (let j = 0; j < count; j++) { if (pixel < bitmap.length) { bitmap[pixel++] = value; } } value = 1 - value; } return bitmap; } /** * 将位图顺时针旋转90度并翻转 * @param bitmap 原始位图数据 * @param width 原始宽度 * @param height 原始高度 * @returns 处理后的位图 */ export function rotateBitmap90ClockwiseAndFlip( bitmap: string | any[] | Uint8Array, width: number, height: number ): Uint8Array { const resultBitmap = new Uint8Array(bitmap.length); for (let y = 0; y < height; y++) { for (let x = 0; x < width; x++) { const originalIndex = y * width + x; const newX = y; const newY = x; const newIndex = newY * height + newX; resultBitmap[newIndex] = bitmap[originalIndex]; } } return resultBitmap; } /** * 将位图应用到图像数据上,使用指定颜色 * @param bitmap 位图数据 * @param imgData 图像数据对象 * @param color 应用的颜色 */ export function applyBitmapToImageData( bitmap: Uint8Array, imgData: { data: Uint8ClampedArray }, color: ColorRGBA ): void { const data = imgData.data; for (let i = 0; i < bitmap.length; i++) { if (bitmap[i] === 1) { const idx = i * 4; data[idx] = color.r; data[idx + 1] = color.g; data[idx + 2] = color.b; data[idx + 3] = Math.round(color.a * 255); } } } /** * 从缓冲区加载图像 * @param buffer 图像缓冲区 * @returns 加载后的图像对象 */ export async function loadImage(buffer: Buffer): Promise<NodeImage> { return nodeLoadImage(buffer); } /** * 创建新的画布 * @param width 画布宽度 * @param height 画布高度 * @returns 新创建的画布对象 */ export function createNewCanvas(width: number, height: number): Canvas { return createCanvas(width, height); }

(2)文件src/image/visualization.ts是图像可视化处理的工具函数集合,用于展示视觉分析结果。主要功能包括:HSL与RGBA颜色空间转换(为图像元素提供色彩支持);掩码数据解码(将压缩的掩码数据转换为像素级位图);位图旋转与翻转(调整掩码方向以匹配原图);将位图应用到图像数据(用指定颜色标记掩码区域,实现目标分割可视化);以及图像加载和画布创建(为可视化提供基础图像和绘图环境)。这些函数共同支持了目标检测、分割等视觉任务结果的图形化展示。

import { createCanvas, loadImage as nodeLoadImage, Image as NodeImage, Canvas } from 'canvas'; import { ColorRGBA } from '../types.js'; /** * 将HSL颜色值转换为RGBA颜色对象 * @param h 色相(0-360) * @param s 饱和度(0-100) * @param l 亮度(0-100) * @param a 透明度(0-1,默认值为1) * @returns 包含r、g、b、a通道值的RGBA对象 */ export function HSLToRGB(h: number, s: number, l: number, a = 1): ColorRGBA { s /= 100; l /= 100; const c = (1 - Math.abs(2 * l - 1)) * s; const x = c * (1 - Math.abs((h / 60) % 2 - 1)); const m = l - c / 2; let r, g, b; if (h >= 0 && h < 60) { [r, g, b] = [c, x, 0]; } else if (h >= 60 && h < 120) { [r, g, b] = [x, c, 0]; } else if (h >= 120 && h < 180) { [r, g, b] = [0, c, x]; } else if (h >= 180 && h < 240) { [r, g, b] = [0, x, c]; } else if (h >= 240 && h < 300) { [r, g, b] = [x, 0, c]; } else { [r, g, b] = [c, 0, x]; } return { r: Math.round((r + m) * 255), g: Math.round((g + m) * 255), b: Math.round((b + m) * 255), a: a }; } /** * 将编码的掩码计数数组解码为位图 * @param counts 掩码计数数组(通过连续相同像素数编码) * @param size 图像尺寸,格式为[高度, 宽度] * @returns 解码后的位图(Uint8Array,像素值为0或1) */ export function decodeMask(counts: string | any[], size: [any, any]): Uint8Array { const [height, width] = size; const bitmap = new Uint8Array(width * height); let pixel = 0; let value = 0; for (let i = 0; i < counts.length; i++) { const count = counts[i]; for (let j = 0; j < count; j++) { if (pixel < bitmap.length) { bitmap[pixel++] = value; } } value = 1 - value; // 每段计数结束后翻转像素值(0变1,1变0) } return bitmap; } /** * 将位图顺时针旋转90度并翻转 * @param bitmap 原始位图数据 * @param width 原始图像宽度 * @param height 原始图像高度 * @returns 旋转并翻转后的位图 */ export function rotateBitmap90ClockwiseAndFlip( bitmap: string | any[] | Uint8Array, width: number, height: number ): Uint8Array { const resultBitmap = new Uint8Array(bitmap.length); for (let y = 0; y < height; y++) { for (let x = 0; x < width; x++) { const originalIndex = y * width + x; // 原始像素索引 // 计算旋转翻转后的新坐标及索引 const newX = y; const newY = x; const newIndex = newY * height + newX; resultBitmap[newIndex] = bitmap[originalIndex]; } } return resultBitmap; } /** * 将位图应用到图像数据,用指定颜色标记掩码区域 * @param bitmap 位图数据(像素值为1的区域将被标记) * @param imgData 原始图像数据对象(包含像素数组) * @param color 用于标记的RGBA颜色 */ export function applyBitmapToImageData( bitmap: Uint8Array, imgData: { data: Uint8ClampedArray }, color: ColorRGBA ): void { const data = imgData.data; for (let i = 0; i < bitmap.length; i++) { if (bitmap[i] === 1) { // 仅处理掩码为1的像素 const idx = i * 4; // 每个像素占4个通道(RGBA) data[idx] = color.r; // 红色通道 data[idx + 1] = color.g; // 绿色通道 data[idx + 2] = color.b; // 蓝色通道 data[idx + 3] = Math.round(color.a * 255); // 透明度通道(转为0-255范围) } } } /** * 从缓冲区加载图像 * @param buffer 图像文件的二进制缓冲区 * @returns 加载后的图像对象 */ export async function loadImage(buffer: Buffer): Promise<NodeImage> { return nodeLoadImage(buffer); } /** * 创建新的画布 * @param width 画布宽度 * @param height 画布高度 * @returns 新创建的画布对象 */ export function createNewCanvas(width: number, height: number): Canvas { return createCanvas(width, height); }

7.4.5 数据验证

文件src/validation/schema.ts是一个基于Zod库的数据验证工具,主要功能是将JSON Schema转换为Zod验证模式,并使用该模式验证工具调用的参数。它支持多种数据类型(字符串、数字、布尔值、数组、对象等)的验证规则转换,能够处理长度限制、数值范围、正则匹配等约束条件,最终实现对工具参数的有效性检查并返回清晰的错误信息。

import { z, ZodError } from 'zod'; /** * 从JSON Schema生成Zod验证模式 * @param jsonSchema JSON Schema对象 * @param toolName 工具名称,用于错误提示 * @returns 生成的Zod验证模式 */ export function getZodSchemaFromJsonSchema( jsonSchema: Record<string, unknown>, toolName: string ): z.ZodTypeAny { if (typeof jsonSchema !== 'object' || jsonSchema === null) { return z.object({}).passthrough(); } try { // 安全转换(不使用eval())- 直接从JSON schema构建Zod模式 return buildZodSchemaFromJson(jsonSchema); } catch (err: unknown) { console.error(`为'${toolName}'生成Zod模式失败:`, err); return z.object({}).passthrough(); } } /** * 从JSON对象递归构建Zod验证模式 * @param schema JSON Schema片段 * @returns 对应的Zod验证模式 */ function buildZodSchemaFromJson(schema: Record<string, unknown>): z.ZodTypeAny { const type = schema.type as string; switch (type) { case 'string': return buildStringSchema(schema); case 'number': case 'integer': return buildNumberSchema(schema); case 'boolean': return z.boolean(); case 'array': return buildArraySchema(schema); case 'object': return buildObjectSchema(schema); default: // 如果未指定类型或类型未知,采用宽松但安全的策略 return z.unknown(); } } /** * 构建字符串类型的Zod验证模式 * @param schema JSON Schema中的字符串约束 * @returns 字符串类型的Zod验证模式 */ function buildStringSchema(schema: Record<string, unknown>): z.ZodString { let stringSchema = z.string(); if (typeof schema.minLength === 'number') { stringSchema = stringSchema.min(schema.minLength); } if (typeof schema.maxLength === 'number') { stringSchema = stringSchema.max(schema.maxLength); } if (typeof schema.pattern === 'string') { try { stringSchema = stringSchema.regex(new RegExp(schema.pattern)); } catch { // 无效的正则表达式,忽略该约束 } } return stringSchema; } /** * 构建数字类型的Zod验证模式 * @param schema JSON Schema中的数字约束 * @returns 数字类型的Zod验证模式 */ function buildNumberSchema(schema: Record<string, unknown>): z.ZodNumber { let numberSchema = z.number(); if (typeof schema.minimum === 'number') { numberSchema = numberSchema.min(schema.minimum); } if (typeof schema.maximum === 'number') { numberSchema = numberSchema.max(schema.maximum); } return numberSchema; } /** * 构建数组类型的Zod验证模式 * @param schema JSON Schema中的数组约束 * @returns 数组类型的Zod验证模式 */ function buildArraySchema(schema: Record<string, unknown>): z.ZodArray<any> { const items = schema.items as Record<string, unknown> | undefined; if (items && typeof items === 'object') { const itemSchema = buildZodSchemaFromJson(items); return z.array(itemSchema); } return z.array(z.unknown()); } /** * 构建对象类型的Zod验证模式 * @param schema JSON Schema中的对象约束 * @returns 对象类型的Zod验证模式 */ function buildObjectSchema(schema: Record<string, unknown>): z.ZodObject<any> { const properties = schema.properties as Record<string, Record<string, unknown>> | undefined; const required = schema.required as string[] | undefined; if (!properties || typeof properties !== 'object') { return z.object({}).passthrough(); } const zodProperties: Record<string, z.ZodTypeAny> = {}; for (const [key, propSchema] of Object.entries(properties)) { if (typeof propSchema === 'object' && propSchema !== null) { let propZodSchema = buildZodSchemaFromJson(propSchema); // 如果不在必填数组中,则设为可选 if (!required?.includes(key)) { propZodSchema = propZodSchema.optional(); } zodProperties[key] = propZodSchema; } } return z.object(zodProperties).passthrough(); } /** * 验证工具调用的参数是否符合指定的Zod模式 * @param schema Zod验证模式 * @param args 待验证的参数 * @param toolName 工具名称,用于错误提示 * @returns 验证结果,包含成功标识和数据或错误信息 */ export function validateToolArguments( schema: z.ZodTypeAny, args: unknown, toolName: string ): { success: true; data: any } | { success: false; error: string } { try { const argsToParse = (typeof args === 'object' && args !== null) ? args : {}; const validatedArgs = schema.parse(argsToParse); return { success: true, data: validatedArgs }; } catch (error) { if (error instanceof ZodError) { const errorMessage = `工具'${toolName}'的参数无效: ${ error.errors.map(e => `${e.path.join('.')} (${e.code}): ${e.message}`).join(', ') }`; return { success: false, error: errorMessage }; } else { const errorMessage = error instanceof Error ? error.message : String(error); return { success: false, error: `验证过程中发生内部错误: ${errorMessage}` }; } } }
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/21 3:25:21

OpenSpec标准兼容性测试:Wan2.2-T2V-5B能否通过工业级认证?

Wan2.2-T2V-5B能否通过工业级认证&#xff1f;OpenSpec兼容性深度评估 在短视频内容呈指数级增长的今天&#xff0c;创作者和企业对“一键生成动态视频”的需求从未如此迫切。然而&#xff0c;大多数文本到视频&#xff08;T2V&#xff09;模型仍停留在实验室阶段——参数动辄百…

作者头像 李华
网站建设 2026/9/21 1:59:27

LeetCode热题100--121. 买卖股票的最佳时机--简单

题目 给定一个数组 prices &#xff0c;它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。 你只能选择 某一天 买入这只股票&#xff0c;并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。 返回你可以从这笔交易中获取的最大…

作者头像 李华
网站建设 2026/9/21 15:08:31

多中心研究术语冲突 后来用SNOMEDCT编码统一才对齐数据

&#x1f4dd; 博客主页&#xff1a;jaxzheng的CSDN主页 目录医疗数据科学&#xff1a;当Excel表格遇上听诊器 一、现状&#xff1a;医生的Excel表格比患者的血糖还高 1.1 政策驱动下的“数字化狂潮” 1.2 技术爆炸&#xff1f;先别急着给AI发诺贝尔奖 二、真实案例&#xff1…

作者头像 李华
网站建设 2026/9/21 6:58:54

Markdown TOC目录生成:提升长篇PyTorch博客可读性

Markdown TOC目录生成&#xff1a;提升长篇PyTorch博客可读性 在撰写深度学习技术文档时&#xff0c;你是否曾遇到这样的困扰&#xff1f;一篇长达数千字的 PyTorch 教程发布后&#xff0c;读者反馈“内容详实但找不到重点”&#xff0c;或是“翻了好几屏才看到想看的配置步骤”…

作者头像 李华
网站建设 2026/9/22 0:34:03

Qwen3-14B编程能力评测:代码生成、调试与逻辑推理全面考察

Qwen3-14B编程能力评测&#xff1a;代码生成、调试与逻辑推理全面考察 在现代软件开发节奏日益加快的今天&#xff0c;开发者面对的挑战早已不止是“写代码”本身。从理解遗留系统、快速定位 bug&#xff0c;到自动生成测试用例和集成外部工具链&#xff0c;整个研发流程正呼唤…

作者头像 李华
网站建设 2026/9/20 1:52:29

如何在7天内构建企业级应用?这个低代码平台的5大颠覆性优势

如何在7天内构建企业级应用&#xff1f;这个低代码平台的5大颠覆性优势 【免费下载链接】vite-vue3-lowcode vue3.x vite2.x vant element-plus H5移动端低代码平台 lowcode 可视化拖拽 可视化编辑器 visual editor 类似易企秀的H5制作、建站工具、可视化搭建工具 项目地址…

作者头像 李华