bioinformatics-mcp-server
Version:
🧬 生物信息学MCP服务器 - 专为ModelScope设计的智能生物数据分析工具
197 lines (196 loc) • 8.06 kB
JavaScript
import { calculateSimilarity } from '../utils/common.js';
/**
* 从Claude响应中提取Python脚本
*/
export function extractPythonScripts(text) {
const scripts = [];
// 匹配 ```python 代码块
const pythonCodeBlockRegex = /```python\n([\s\S]*?)```/g;
let match;
while ((match = pythonCodeBlockRegex.exec(text)) !== null) {
const code = match[1].trim();
// 跳过空代码块或过短的代码块
if (code.length < 10)
continue;
// 检查是否是生物信息学相关的代码
if (isBioinformaticsScript(code)) {
// 计算代码行数
const lineCount = code.split('\n').length;
// 尝试提取代码前的描述
const beforeCode = text.substring(0, match.index);
const description = extractScriptDescription(beforeCode);
// 提取依赖包
const dependencies = extractDependencies(code);
scripts.push({
code,
description: description || '自动检测的Python脚本',
dependencies,
lineCount
});
}
}
// 也尝试匹配没有语言标识的代码块,但包含生物信息学关键词
const genericCodeBlockRegex = /```\n([\s\S]*?)```/g;
while ((match = genericCodeBlockRegex.exec(text)) !== null) {
const code = match[1].trim();
if (code.length < 10)
continue;
// 检查是否看起来像Python代码且与生物信息学相关
if (looksLikePython(code) && isBioinformaticsScript(code)) {
const beforeCode = text.substring(0, match.index);
const description = extractScriptDescription(beforeCode);
// 避免重复添加已经通过python标签提取的代码
const alreadyExists = scripts.some(script => calculateSimilarity(script.code, code) > 0.8);
if (!alreadyExists) {
const lineCount = code.split('\n').length;
const dependencies = extractDependencies(code);
scripts.push({
code,
description: description || '检测到的Python脚本',
dependencies,
lineCount
});
}
}
}
return scripts;
}
/**
* 检查代码是否与生物信息学相关
*/
function isBioinformaticsScript(code) {
const bioKeywords = [
// 生物信息学库
'biopython', 'pandas', 'numpy', 'matplotlib', 'seaborn', 'scipy',
'sklearn', 'Bio', 'pysam', 'HTSeq', 'pybedtools', 'plotly', 'bokeh',
'scanpy', 'anndata', 'leiden', 'louvain', 'statsmodels', 'networkx',
// 可视化相关
'plt.', 'pyplot', 'figure', 'subplot', 'plot', 'scatter', 'histogram',
'heatmap', 'boxplot', 'violinplot', 'barplot', 'lineplot', 'scatterplot',
'clustermap', 'pairplot', 'distplot', 'jointplot', 'regplot',
'savefig', 'show()', 'imshow', 'contour', 'pie', 'bar(',
'sns.', 'seaborn', 'plotly', 'bokeh',
// 生物信息学术语
'fastq', 'fasta', 'vcf', 'bam', 'sam', 'bed', 'gtf', 'gff',
'sequence', 'genome', 'gene', 'protein', 'DNA', 'RNA',
'alignment', 'blast', 'annotation', 'expression',
'variant', 'mutation', 'phylogeny', 'assembly',
'differential', 'enrichment', 'pathway', 'ontology',
// 常见生物信息学操作
'SeqIO', 'read_csv', 'read_table', 'clustering', 'pca', 'differential',
'correlation', 'pathway', 'enrichment', 'ontology',
'normalize', 'filter', 'quality', 'control',
// 单细胞分析
'scanpy', 'anndata', 'adata', 'obs', 'var', 'obsm', 'varm',
'umap', 'tsne', 'leiden', 'louvain', 'highly_variable',
'sc.pp.', 'sc.tl.', 'sc.pl.',
// 基因组学
'pysam', 'vcf', 'bam', 'sam', 'bed', 'gtf', 'gff',
'variant', 'mutation', 'snp', 'indel',
// 蛋白质组学
'protein', 'peptide', 'proteomics', 'mass', 'spectrometry',
// 统计分析
't.test', 'wilcoxon', 'anova', 'chi.square', 'correlation',
'regression', 'linear', 'logistic',
// 机器学习
'sklearn', 'random.forest', 'svm', 'kmeans', 'dbscan',
'clustering', 'classification', 'regression',
// 文件扩展名
'.fastq', '.fasta', '.vcf', '.bam', '.sam', '.bed',
'.gtf', '.gff', '.csv', '.tsv', '.txt', '.h5ad', '.h5',
'.png', '.pdf', '.svg', '.jpg', '.jpeg', '.xlsx'
];
const codeUpper = code.toUpperCase();
return bioKeywords.some(keyword => codeUpper.includes(keyword.toUpperCase()));
}
/**
* 检查代码是否看起来像Python
*/
function looksLikePython(code) {
const pythonIndicators = [
'import ', 'from ', 'def ', 'class ', 'if __name__',
'print(', '.py', 'pandas', 'numpy', '#!/usr/bin/env python'
];
return pythonIndicators.some(indicator => code.includes(indicator));
}
/**
* 提取脚本描述
*/
function extractScriptDescription(beforeCode) {
// 提取代码块前最近的一段文字作为描述
const lines = beforeCode.split('\n').reverse();
const descriptionLines = [];
for (const line of lines) {
const trimmed = line.trim();
if (trimmed === '')
continue;
// 跳过markdown标记
if (trimmed.startsWith('#') || trimmed.startsWith('*') ||
trimmed.startsWith('-') || trimmed.startsWith('>')) {
descriptionLines.unshift(trimmed);
continue;
}
// 如果是普通文本,添加并停止
if (trimmed.length > 10 && trimmed.length < 200) {
descriptionLines.unshift(trimmed);
break;
}
}
return descriptionLines.join(' ').substring(0, 150);
}
/**
* 提取Python代码的依赖包
*/
function extractDependencies(code) {
const dependencies = new Set();
// 匹配 import 语句
const importRegex = /^(?:from\s+(\S+)|import\s+(\S+))/gm;
let match;
while ((match = importRegex.exec(code)) !== null) {
const packageName = match[1] || match[2];
if (packageName) {
// 提取主包名(去掉子模块)
const mainPackage = packageName.split('.')[0];
// 映射常见的包名到pip包名(只映射不同名的包)
const packageMapping = {
'Bio': 'biopython',
'cv2': 'opencv-python',
'sklearn': 'scikit-learn',
'PIL': 'Pillow',
'skimage': 'scikit-image',
'scanpy': 'scanpy',
'anndata': 'anndata',
'pysam': 'pysam',
'plotly': 'plotly',
'bokeh': 'bokeh',
'statsmodels': 'statsmodels',
'networkx': 'networkx',
'leidenalg': 'leidenalg',
'louvain': 'python-louvain',
'umap': 'umap-learn',
'tsne': 'tsne-learn'
};
const pipPackage = packageMapping[mainPackage] || mainPackage;
// 只添加非标准库的包
if (!isStandardLibrary(mainPackage)) {
dependencies.add(pipPackage);
}
}
}
return Array.from(dependencies);
}
/**
* 检查是否为Python标准库
*/
function isStandardLibrary(packageName) {
const standardLibraries = [
'os', 'sys', 'json', 'csv', 'sqlite3', 'datetime', 'time', 'random',
'math', 'statistics', 'collections', 'itertools', 'functools', 'operator',
'pathlib', 'glob', 'shutil', 'tempfile', 'zipfile', 'tarfile',
'urllib', 'http', 'email', 'html', 'xml', 'pickle', 'copy',
'threading', 'multiprocessing', 'subprocess', 'argparse', 'logging',
'unittest', 're', 'string', 'textwrap', 'unicodedata', 'struct',
'codecs', 'io', 'base64', 'binascii', 'hashlib', 'hmac', 'secrets'
];
return standardLibraries.includes(packageName);
}