
起源

Google 的 NotebookLM 上线后,不少人体验了它的文档问答和内容生成功能。它很好用,但有个问题:数据需要上传到 Google 的服务器。对于一些敏感的内部文档,这不太合适。
于是就想做一个开源版本:数据存在本地,想用什么模型就自己配置。一个元旦假期,基本功能就跑起来了。
项目叫 Notex,代码在 GitHub 上,今天聊聊它的实现。
本项目受open-notebook项目的启发。但是open-notebook缺少信息图、思维导图、幻灯片等高级功能。本项目弥补了这些功能
信息图采用最新的nano banana pro实现
幻灯片采用 宝玉的 《预订本年度最有价值提示词 —— 生成既有质感,又能随意修改文字的完美 PPT》 的方式,生成真正意义上的PPT
功能概览

Notex 是一个隐私优先的开源知识管理工具,基于 RAG(检索增强生成)技术。它支持多种文档格式,通过 AI 帮助你理解、总结和可视化文档内容。
核心功能
| 功能分类 |
具体能力 |
| 文档管理 |
支持 PDF、DOCX、PPTX、Markdown、TXT、HTML 等多种格式的文档上传和解析 |
| AI 对话 |
基于文档内容的智能问答,回答会标注来源引用,避免 AI 幻觉 |
| 内容转换 |
摘要、FAQ、学习指南、大纲、时间线、术语表、测验、播客脚本等 9 种预设类型 |
| 视觉生成 |
思维导图(Mermaid.js)、信息图(Gemini Nano Banana Pro)、幻灯片/PPT |
| 模型支持 |
OpenAI(含兼容 API)、Ollama 本地模型、Google Gemini |
特色功能详解
1. 幻灯片(PPT)生成
采用两阶段生成流程:
最终输出图文并茂的完整 PPT,而非简单的文字大纲。
2. 信息图生成
通过 Prompt Engineering 将文本内容"翻译"为结构化的视觉描述,再调用 Gemini Nano Banana Pro 生成手绘风格的信息图。
适用于:数据可视化、流程说明、概念解释等场景。
3. 思维导图生成
自动将文档结构提炼为 Mermaid.js mindmap 格式,支持:
- 中心主题、主要分支、细节节点的层级展示
- 自动缩放和交互式导航
- 导出为 SVG/PNG
4. 智能问答(RAG)
支持基于文档内容的自然语言问答:
- 中文/英文自适应分词和检索
- 回答会标注来源,可追溯原文
- 支持多轮对话历史
整体架构

技术栈选型很简单:
1 2 3
| 后端:Go 1.25 + Gin + SQLite 前端:原生 HTML/CSS/JavaScript AI:LangChainGo + OpenAI/Ollama + Gemini
|
为什么不选 Python?因为 Go 编译完就是一个二进制文件,部署起来省事。而且 Go 的并发性能好,后续如果需要处理大量文档也不会成为瓶颈。
目录结构也很清晰:
1 2 3 4 5 6 7 8 9
| notex/ ├── main.go # 入口 ├── backend/ │ ├── agent.go # AI 调用逻辑 │ ├── server.go # HTTP 服务 │ ├── vector.go # 文档索引 │ ├── store.go # 数据持久化 │ └── nanobanana.go # Gemini 图片生成 └── frontend/ # 单页应用(嵌入二进制)
|
前端通过 //go:embed 编译进二进制,一个文件就能跑起来。
核心模块
1. 文档索引(vector.go)

RAG 的第一步是把文档切成小块,建立索引。
这里有个问题:中文和英文的分词方式不一样。英文按空格分词就行,中文需要按字符切。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| cjkCount := 0 for _, r := range runes { if r >= 0x4E00 && r <= 0x9FFF { cjkCount++ } } cjkRatio := float64(cjkCount) / float64(len(runes)) if cjkRatio > 0.3 { for i := 0; i < len(runes); i += (chunkSize - chunkOverlap) { } } else { words := strings.Fields(text) }
|
检索部分目前用的是简单的关键词匹配,没有用向量 Embedding。原因?对于中小规模的文档(几万字以内),关键词匹配够用了,而且快。如果后续文档量大,可以接入向量数据库。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| score := 0.0 if strings.Contains(content, queryLower) { score += 10.0 } matchCount := 0 for _, r := range queryRunes { if strings.ContainsRune(content, r) { matchCount++ } } if matchCount > 0 { charMatchRatio := float64(matchCount) / float64(len(queryRunes)) score += charMatchRatio * 5.0 } queryWords := strings.Fields(queryLower) for _, word := range queryWords { if len(word) > 2 && strings.Contains(content, word) { score += 2.0 } }
|
2. AI 调用(agent.go)

这块是核心,负责和 LLM 打交道。
使用 LangChainGo 作为抽象层,支持 OpenAI 和 Ollama:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| func createLLM(cfg Config) (llms.Model, error) { if cfg.IsOllama() { return ollamallm.New( ollamallm.WithModel(cfg.OllamaModel), ollamallm.WithServerURL(cfg.OllamaBaseURL), ) } opts := []openai.Option{ openai.WithToken(cfg.OpenAIAPIKey), openai.WithModel(cfg.OpenAIModel), } if cfg.OpenAIBaseURL != "" { opts = append(opts, openai.WithBaseURL(cfg.OpenAIBaseURL)) } return openai.New(opts...) }
|
Prompt Engineering 是关键。不同的转换类型需要不同的 prompt 模板:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| case "mindmap": return `你是一位资深的信息架构师和知识管理专家。 请将【文本内容】提炼并转换为 Mermaid.js 的 mindmap 格式。 # 样式规范: 1. 中心主题:root((内容)) 2. 主要分支:(内容) 3. 细节节点:[内容] # 严格规范: - 严禁使用 graph, LR, --> 等字符 - 节点内容 10 字以内 - 只输出代码块,不解释 来源:{sources} `
|
注意这里的 prompt 设计:明确的输出格式要求,加上反面示例("严禁"),能有效减少 LLM 输出格式不稳定的问题。
3. PPT 生成

这是比较复杂的 feature。需要两个步骤:
第一步:生成大纲
用 Gemini Flash 生成 PPT 大纲,每页包含:
- 叙事目标(这张幻灯片要讲什么)
- 关键内容(标题、要点)
- 视觉元素(需要什么图)
- 布局(怎么排版)
第二步:解析和生成图片
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| func (a *Agent) ParsePPTSlides(content string) []Slide { styleStart := strings.Index(content, "<STYLE_INSTRUCTIONS>") styleEnd := strings.Index(content, "</STYLE_INSTRUCTIONS>") re := regexp.MustCompile(`(?m)^(?:\s*#{1,6}\s*)?(?:Slide|幻灯片)\s*\d+`) indices := re.FindAllStringIndex(content, -1) if strings.Contains(lower, "叙事目标") || strings.Contains(lower, "关键内容") { slides = append(slides, Slide{Style: style, Content: slideContent}) } }
|
然后为每张幻灯片调用 Gemini Pro Image 生成图片:
1 2 3 4 5
| for i, slide := range slides { prompt := fmt.Sprintf("Style: %s\n\nSlide Content: %s", slides[0].Style, slide.Content) imagePath, err := s.agent.GenerateImage(ctx, "gemini-3-pro-image-preview", prompt) slideURLs = append(slideURLs, "/uploads/"+filepath.Base(imagePath)) }
|
4. 信息图生成

信息图的核心是让 LLM 把文本内容"翻译"成视觉描述,然后把描述作为 prompt 喂给图像模型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| case "infograph": return `# Role 你是一位世界顶级的数据可视化设计师和信息图专家。 # Task 阅读所附文本,设计一张信息图。不要进行总结, 而是描述这张图应该长什么样。你的输出将被直接用作图像生成的 prompt。 # Design Guidelines 1. 核心信息提炼:找出最重要的 3-5 个数据点 2. 视觉隐喻:用形象的比喻(如网络安全用"盾牌和锁") 3. 布局结构:明确定义图的结构 4. 文本限制:极简,只保留标题和关键数据 5. 风格:插画或手绘感 # Output Format Start with "Infographic illustration created in a soft, hand-drawn digital art style..." [描述整体布局和背景风格] [详细描述主要视觉元素] ... `
|
这个 prompt 设计的要点是:明确告诉 LLM 它的输出会被用作另一个 prompt,这样 LLM 就会更注意输出的结构化和可用性。
5. 思维导图

思维导图的实现最简单,核心是生成 Mermaid.js 的语法:
1 2 3 4 5 6 7 8 9 10 11 12 13
| case "mindmap": return `将文本转换为 Mermaid.js mindmap 格式。 # 样式规范: root((中心主题)) # 圆圈 (主要分支) # 圆角矩形 [细节节点] # 矩形 # 严格规范: - 仅限 mindmap 语法 - 节点内容 10 字以内 - 严禁包含引号 `
|
前端用 Mermaid.js 自动渲染:
1 2
| import mermaid from 'https://s4.zstatic.net/ajax/libs/mermaid/11.4.0/mermaid.min.js'; mermaid.initialize({ startOnLoad: true });
|
数据流

整个系统的数据流如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| 用户上传文档 ↓ markitdown 转换为 Markdown(如果需要) ↓ 文本分块并索引 ↓ 存储到 SQLite ↓ 用户选择转换类型(PPT/信息图/思维导图) ↓ 根据类型构建 prompt ↓ 调用 LLM 生成内容 ↓ 如果需要图片,调用 Gemini 生成 ↓ 保存为 Note ↓ 前端渲染展示
|
部署和使用

本地运行
1 2 3 4 5 6 7 8 9 10 11 12
| git clone https://github.com/smallnest/notex.git cd notex go mod tidy export OPENAI_API_KEY=your_key go run . -server export OLLAMA_BASE_URL=http://localhost:11434 export OLLAMA_MODEL=qwen2.5:7b go run . -server
|
Docker 部署
环境变量
| 变量 |
说明 |
默认值 |
| OPENAI_API_KEY |
OpenAI API 密钥 |
- |
| OPENAI_BASE_URL |
自定义 API 地址 |
- |
| OPENAI_MODEL |
模型名称 |
gpt-4o-mini |
| OLLAMA_BASE_URL |
Ollama 地址 |
- |
| OLLAMA_MODEL |
Ollama 模型 |
- |
| GOOGLE_API_KEY |
Gemini API(PPT/信息图) |
- |
| SERVER_PORT |
服务端口 |
8080 |
扩展性
代码结构支持几种扩展方式:
1. 添加新的转换类型
在 agent.go 的 getTransformationPrompt 添加新 case:
1 2
| case "new_type": return `你的 prompt 模板...`
|
2. 支持新的 LLM
LangChainGo 支持多种模型,只需在 createLLM 添加新的分支。
3. 接入向量数据库
VectorStore 目前是内存实现,可以替换成 Qdrant、Milvus 等。
4. 添加新的文档格式
在 vector.go 的 needsMarkitdown 添加新的文件扩展名。
代码量和学习价值
项目核心代码大约 2000 行左右,适合学习:
- RAG 的完整实现:从文档处理到检索到生成
- Prompt Engineering:如何设计稳定的 prompt
- Go Web 开发:Gin 框架、SQLite、并发处理
- 前后端分离:单页应用 + REST API
- LLM 应用架构:如何组织一个 AI 应用的代码
如果你想学习如何构建 AI 应用,或者想找一个 NotebookLM 的替代方案,这个项目可以作为一个起点。
总结
Notex 不是什么高大上的项目,代码也写得朴素。但它解决了实际问题:让你在本地使用 AI 处理文档,数据不外泄。
核心功能就几个:
- 文档问答(RAG)
- 多种内容转换(摘要、FAQ、大纲等)
- 视觉内容生成(PPT、信息图、思维导图)
技术选型务实地选择了 Go 和原生 JS,没有过多的依赖。部署简单,维护成本低。
如果你感兴趣,可以看看代码,提提 PR,或者直接 fork 改成你自己的工具。
项目地址: https://github.com/smallnest/notex
欢迎 Star、Issue、PR。