UNPKG

structurize-mcp

Version:

Anthropic MCP Server for generating structured CSV files from natural language descriptions

186 lines (159 loc) 5.88 kB
import { GoogleGenerativeAI, GenerativeModel } from '@google/generative-ai'; // 移除硬编码的 API_KEY // const API_KEY = 'AIzaSyCJyhrKIZ7J9to3tJo_zLQRkRcTN5z3fRA'; /** * Gemini 客户端类,处理与 Google Gemini 模型的交互 */ export class GeminiClient { private model: GenerativeModel; private static instance: GeminiClient; private static apiKey: string; /** * 私有构造函数,初始化 Gemini 模型 * @param apiKey Gemini API Key */ private constructor(apiKey: string) { const genAI = new GoogleGenerativeAI(apiKey); this.model = genAI.getGenerativeModel({ model: 'gemini-pro' }); } /** * 获取 GeminiClient 单例 * @param apiKey Gemini API Key * @returns GeminiClient 实例 */ public static getInstance(apiKey?: string): GeminiClient { if (apiKey) { GeminiClient.apiKey = apiKey; } if (!GeminiClient.apiKey) { throw new Error('Gemini API Key 未设置。请通过 --api-key 参数提供有效的 API Key'); } if (!GeminiClient.instance) { GeminiClient.instance = new GeminiClient(GeminiClient.apiKey); } return GeminiClient.instance; } /** * 根据提供的结构和描述生成 CSV 内容 * * @param title CSV 文件标题 * @param structure CSV 结构描述 * @param data 要填充的数据描述 * @returns 解析后的列名和数据行 */ public async generateCSVContent( title: string, structure: string, data: string ): Promise<{ columns: string[]; rows: Record<string, string>[] }> { try { // 构建提示词 const prompt = ` 我需要你帮我生成一个 CSV 文件的内容。 文件标题: ${title} 结构描述: ${structure} 数据描述: ${data} 请按照以下步骤回复: 1. 首先,分析并列出 CSV 的列名(用逗号分隔) 2. 然后,生成多行数据,每行包含所有列的值(用逗号分隔) 3. 确保数据合理且与列名匹配 4. 不要包含任何额外的解释,只输出以下格式: 列名: column1,column2,column3 数据: value1_1,value1_2,value1_3 value2_1,value2_2,value2_3 ...等等 `; // 调用 Gemini 模型 const result = await this.model.generateContent(prompt); const text = result.response.text(); // 解析模型返回的结果 return this.parseModelResponse(text); } catch (error) { console.error('调用 Gemini 模型出错:', error); throw new Error(`Gemini 模型调用失败: ${(error as Error).message}`); } } /** * 解析模型返回的数据,提取列名和行数据 * * @param response 模型响应文本 * @returns 解析后的列名和数据行 */ private parseModelResponse(response: string): { columns: string[]; rows: Record<string, string>[] } { // 默认列名和行数据 let columns: string[] = []; let dataRows: Record<string, string>[] = []; try { // 提取列名部分 const columnsMatch = response.match(/列名:\s*(.+?)(?:\n|$)/i); if (columnsMatch && columnsMatch[1]) { columns = columnsMatch[1].split(',').map(col => col.trim()); } // 如果未找到列名,尝试其他格式 if (columns.length === 0) { const altColumnsMatch = response.match(/columns?:\s*(.+?)(?:\n|$)/i); if (altColumnsMatch && altColumnsMatch[1]) { columns = altColumnsMatch[1].split(',').map(col => col.trim()); } } // 提取数据行部分 let dataSection = ''; const dataSectionMatch = response.match(/数据:\s*\n([\s\S]+?)(?:$|(?:\n\n))/i); if (dataSectionMatch && dataSectionMatch[1]) { dataSection = dataSectionMatch[1]; } else { // 尝试匹配英文格式 const altDataSectionMatch = response.match(/data:\s*\n([\s\S]+?)(?:$|(?:\n\n))/i); if (altDataSectionMatch && altDataSectionMatch[1]) { dataSection = altDataSectionMatch[1]; } } // 如果仍然没有找到格式化的数据,尝试从整个响应中提取 if (!dataSection) { // 找到第一个非列名的行,假设后面的都是数据行 const lines = response.split('\n'); let dataStarted = false; const dataLines: string[] = []; for (const line of lines) { if (!dataStarted && (line.includes('数据:') || line.includes('data:'))) { dataStarted = true; continue; } if (dataStarted && line.trim() && !line.includes('列名:') && !line.includes('columns:')) { dataLines.push(line); } } dataSection = dataLines.join('\n'); } // 处理数据行 if (dataSection) { const rows = dataSection.split('\n').filter(line => line.trim()); dataRows = rows.map(row => { const values = row.split(',').map(val => val.trim()); const rowObj: Record<string, string> = {}; columns.forEach((column, index) => { rowObj[column] = index < values.length ? values[index] : ''; }); return rowObj; }); } // 如果列名为空,但有数据行,尝试从数据行中提取列名 if (columns.length === 0 && dataRows.length > 0) { const firstRow = Object.keys(dataRows[0]); columns = firstRow; } // 验证结果有效性 if (columns.length === 0) { throw new Error('无法从模型响应中提取列名'); } if (dataRows.length === 0) { throw new Error('无法从模型响应中提取数据行'); } return { columns, rows: dataRows }; } catch (error) { console.error('解析模型响应出错:', error); throw new Error(`解析模型响应失败: ${(error as Error).message}`); } } }