閱讀效率提升300%:Dify+Markdown實(shí)現(xiàn)自動(dòng)化知識(shí)梳理全解析 原創(chuàng)
在早年閱讀網(wǎng)上的技術(shù)博客時(shí),我習(xí)慣一邊看文章一邊在語雀筆記中畫思維導(dǎo)圖。然而,回過頭來看,這種方式其實(shí)效率不高。有了AI后,我們可以先讓AI為我們生成相應(yīng)的思維導(dǎo)圖,以便我們對(duì)知識(shí)有個(gè)初步認(rèn)識(shí),再去深入閱讀文章,這樣會(huì)更有效。在這篇文章中,我將分享如何使用dify自動(dòng)生成文章的思維導(dǎo)圖,以提高我們吸收知識(shí)的速度。
安裝插件
先在dify的插件市場安裝如下兩個(gè)插件:
- Markdown轉(zhuǎn)換器:用于生成html文件
 - Agent策略插件:調(diào)用mcp server,將markdown轉(zhuǎn)成html
 
編寫mcp server
我們需要開發(fā)一個(gè) MCP 服務(wù)器,通過 HTTP 接口為 Dify 提供 Markdown 轉(zhuǎn)思維導(dǎo)圖的服務(wù)。該服務(wù)將使用 markmap-cli 工具實(shí)現(xiàn)核心轉(zhuǎn)換功能,要調(diào)用這個(gè)工具需要先安裝 Node.js 環(huán)境(包含 npm),然后通過命令 ??npm install -g markmap-cli?? 全局安裝這個(gè)必備工具。
sudo apt update
sudo apt install nodejs npm
npm install -g markmap-cli下面是對(duì)應(yīng)的mcp server代碼,運(yùn)行這個(gè)腳本之前需要先pip install mcp, 然后執(zhí)行python mcp.py --host 0.0.0.0 --port 27018,dify對(duì)應(yīng)的Agent節(jié)點(diǎn)配置的服務(wù)端地址是http://ip:27018/sse。
import asyncio
import tempfile
import os
import shutil
import sys
import argparse
import logging
from pathlib import Path
from mcp.server.fastmcp import FastMCP
# Configure logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S'
)
# Parse command line arguments
def parse_arguments():
    parser = argparse.ArgumentParser(description='MCP Server for converting Markdown to mindmaps')
    parser.add_argument('--return-type', choices=['html', 'filePath'], default='html',
                        help='Whether to return HTML content or file path. Default: html')
    parser.add_argument('--host', default='localhost',
                        help='Host address to bind the server. Default: localhost')
    parser.add_argument('--port', type=int, default=8000,
                        help='Port number to run the server. Default: 1100')
    return parser.parse_args()
# Global configuration
args = parse_arguments()
RETURN_TYPE = args.return_type
# Initialize FastMCP server
mcp = FastMCP("mindmap-server", host=args.host, port=args.port)
# Log server configuration
logging.info("Starting Mindmap Server with configuration:")
logging.info(f"Host: {args.host}")
logging.info(f"Port: {args.port}")
logging.info(f"Return Type: {args.return_type}")
async def create_temp_file(content: str, extension: str) -> str:
    """Create a temporary file with the given content and extension."""
    temp_dir = tempfile.mkdtemp(prefix='mindmap-')
    file_path = os.path.join(temp_dir, f"input{extension}")
    with open(file_path, mode='w') as f:
        f.write(content)
    return file_path
async def run_mindmap(input_file: str, output_file: str = None) -> str:
    """Run markmap-cli on the input file and return the path to the output file.
    Args:
        input_file: Path to the input markdown file
        output_file: Optional path for the output HTML file
    Returns:
        str: Path to the generated HTML file
    """
    if output_file is None:
        output_file = os.path.splitext(input_file)[0] + '.html'
    if sys.platform == 'win32':
        args = ['cmd', '/c', 'npm', 'exec', '--yes', 'markmap-cli', '--', input_file, '-o', output_file, '--no-open']
    else:
        args = ['npx', '-y', 'markmap-cli', input_file, '-o', output_file, '--no-open']
    try:
        process = await asyncio.create_subprocess_exec(
            *args,
            stdout=asyncio.subprocess.PIPE,
            stderr=asyncio.subprocess.PIPE
        )
        stdout, stderr = await process.communicate()
        if process.returncode != 0:
            error_msg = stderr.decode() if stderr else "Unknown error"
            raise RuntimeError(f"markmap-cli exited with code {process.returncode}: {error_msg}")
        return output_file
    except Exception as e:
        raise RuntimeError(f"Failed to run markmap-cli: {str(e)}")
async def get_html_content(file_path: str) -> str:
    """Read the HTML content from the given file."""
    with open(file_path, 'r', encoding='utf-8') as f:
        return f.read()
@mcp.tool()
async def convert_markdown_to_mindmap(
    markdown_content: str,  # The Markdown content to convert
) -> str:
    """Convert Markdown content to a mindmap mind map.
    Args:
        markdown_content: The Markdown content to convert
    Returns:
        Either the HTML content or the file path to the generated HTML, 
        depending on the --return-type server argument
    """
    try:
        logging.info("Starting markdown to mindmap conversion")
        # Create a temporary markdown file
        input_file = await create_temp_file(markdown_content, '.md')
        logging.debug(f"Created temporary markdown file: {input_file}")
        # Run mindmap on it
        output_file = await run_mindmap(input_file)
        logging.debug(f"Generated mindmap file: {output_file}")
        # Check if the output file exists
        if not os.path.exists(output_file):
            error_msg = f"Output file was not created: {output_file}"
            logging.error(error_msg)
            raise RuntimeError(error_msg)
        # Return either the HTML content or the file path based on command line arg
        if RETURN_TYPE == 'html':
            html_content = await get_html_content(output_file)
            logging.info("Successfully converted markdown to HTML mindmap")
            return html_content
        else:
            logging.info(f"Successfully generated mindmap file at: {output_file}")
            return output_file
    except Exception as e:
        error_msg = f"Error converting Markdown to mindmap: {str(e)}"
        logging.error(error_msg)
        raise RuntimeError(error_msg)
    finally:
        # Clean up temporary files
        if 'input_file' in locals():
            temp_dir = os.path.dirname(input_file)
            try:
                shutil.rmtree(temp_dir, ignore_errors=True)
                logging.debug(f"Cleaned up temporary directory: {temp_dir}")
            except Exception as e:
                logging.warning(f"Failed to clean up temporary directory {temp_dir}: {str(e)}")
def main():
    """Entry point for the mindmap-mcp-server command."""
    global args, RETURN_TYPE
    # Parse arguments again to ensure parameters are captured when running as an entry point
    args = parse_arguments()
    RETURN_TYPE = args.return_type
    print(f"Starting mindmap-mcp-server with return type: {RETURN_TYPE}", file=sys.stderr)
    # Initialize and run the server
    mcp.run(transport='sse')
if __name__ == "__main__":
    main()搭建工作流
搭建好的簡略工作流如下:

下面對(duì)關(guān)鍵節(jié)點(diǎn)做如下說明:
LLM 生成markdown
我們利用gpt-4.1 對(duì)文件內(nèi)容轉(zhuǎn)換成markdown格式,對(duì)應(yīng)的prompt如下:
上下文內(nèi)容:{{#context#}}
## 核心任務(wù)
將上下文內(nèi)容轉(zhuǎn)化為符合以下標(biāo)準(zhǔn)的Markdown格式思維導(dǎo)圖框架:
1. **要素提取**:識(shí)別并提取關(guān)鍵實(shí)體、關(guān)系、流程三類核心要素
2. **邏輯重構(gòu)**:按「總-分」結(jié)構(gòu)重組信息,確保父子節(jié)點(diǎn)存在推導(dǎo)關(guān)系
## 格式規(guī)范
### 層級(jí)控制
- 主標(biāo)題 `#`(1級(jí)):文檔主題
- 章節(jié) `##`(2級(jí)):核心模塊(≥3個(gè))
- 子項(xiàng) `###`(3級(jí)):具體要素(每個(gè)父節(jié)點(diǎn)下≥2個(gè))
### 內(nèi)容標(biāo)記
- 關(guān)鍵術(shù)語:**加粗顯示** + (簡短釋義)
- 數(shù)據(jù)示例:```包裹的代碼塊```
## 質(zhì)量保障
1. 預(yù)檢機(jī)制(輸出前必須驗(yàn)證):
   - [ ] 無孤立節(jié)點(diǎn)(所有子項(xiàng)都有父節(jié)點(diǎn))
   - [ ] 無重復(fù)內(nèi)容(合并相似條目)Agent
添加Agent策略時(shí),我選擇了環(huán)境準(zhǔn)備安裝的Agent策略插件,并指定了FunctionCalling策略。同時(shí),對(duì)上述的MCP工具進(jìn)行了配置。

測(cè)試
我從網(wǎng)上找了一篇講解iphone15的文章,將其導(dǎo)入當(dāng)前的工作流系統(tǒng)。下面是生成的思維導(dǎo)圖,生成的內(nèi)容還是不錯(cuò)的:

總結(jié)
當(dāng)然,上面的處理還只是一個(gè)比較粗糙的demo,我們還可以繼續(xù)優(yōu)化。首先,在數(shù)據(jù)采集環(huán)節(jié),我們可以增加網(wǎng)頁內(nèi)容直接抓取功能;其次,針對(duì)大篇幅文檔,可考慮采用分塊處理的迭代機(jī)制;此外,還需完善對(duì)圖文混合文檔中視覺元素的處理能力。這些優(yōu)化方向?qū)@著提升工具的實(shí)用性和處理效率。感興趣的朋友可以自行嘗試。
本文轉(zhuǎn)載自??AI 博物院?? 作者:longyunfeigu


















