0.5 缩减重构

This commit is contained in:
lix-2026
2026-04-13 19:21:42 +08:00
parent af92c4b149
commit 71fb1aee7e
2023 changed files with 21113 additions and 394493 deletions
+1 -1
View File
@@ -48,4 +48,4 @@ wolai-frontend/public/documents/
wolai-frontend/public/documents/**
artifacts/
artifacts/**
tmp
+50 -27
View File
@@ -1,34 +1,57 @@
# 仓库协作指南(AGENTS
## 关键事实(防止误判)
- **当前已使用 Convex 完全替换 Supabase**:任何新功能/修复都应以 Convex 为唯一数据源与鉴权/权限基础。
- 仓库内可能仍存在 `supabase/``supabase.md``wolai-frontend/src/lib/supabase/``@supabase/*` 依赖等**历史遗留**:除非任务明确要求清理,否则不要基于它们继续扩展实现。
## 当前主线事实
## 项目结构与模块组织
- `wolai-frontend/`:主前端(Next.js),源码在 `wolai-frontend/src/`,静态资源在 `wolai-frontend/public/`
- `wolai-frontend/convex/`Convex functionsschema / query / mutation / action 等)
- `infra/convex/`Convex 自托管(Docker Compose + README + 本地 `.env`
- `wolai-backend/`:后端(FastAPI + Celery),主要用于辅助能力(如 OCR、OnlyOffice/集成类服务等)
- `services/`:配套服务与集成点(如 `services/mineru/` OCR、RAG 相关服务等)
- `pw-tests/`:端到端测试(Playwright + Python),脚本在 `pw-tests/scripts/`,产物在 `pw-tests/artifacts/`
- 根目录 `src/`:Next.js 相关代码与共享模块(可能为历史/镜像目录;优先改 `wolai-frontend/src/`)。
- 当前仓库已经做过精简,旧设计稿已移动到 `/mnt/Data1T/mnote/recycle/design/`
- 当前主前端在 `/mnt/Data1T/mnote/wolai-frontend/`,这是默认优先修改的位置
- 当前辅助后端在 `/mnt/Data1T/mnote/wolai-backend/`
- 当前 Convex 自托管配置在 `/mnt/Data1T/mnote/infra/convex/`
- 根目录历史 `/mnt/Data1T/mnote/src/` 已只保留 `/mnt/Data1T/mnote/src/components/onlyoffice/`,这里仍然属于运行相关资源,不要误删
- `/mnt/Data1T/mnote/ARCHITECTURE.md` 是当前仓库结构与嵌入关系的最新说明,涉及 BlockNote、Mindmap、OnlyOffice 时优先参考它
## 构建、测试与本地开发命令
- 一键热启动(推荐):在仓库根目录执行 `npm run desktop:hot`(启动 `wolai-frontend` + `wolai-backend`Redis 可用时自动启动 Celery)。
- 前端:`cd wolai-frontend && pnpm dev|build|start`;质量检查:`pnpm lint`;单测:`pnpm test`
- 后端:`cd wolai-backend && pip install -r requirements.txt`;运行:`uvicorn app.main:app --reload --port 8000`Worker`celery -A app.workers.celery_app worker --loglevel=info`
- E2E`cd pw-tests/scripts && python e2e_mindmap_sync.py`(更多脚本见 `pw-tests/README.md`)。
- Convex(自托管)启动:见 `infra/convex/README.md`
## 目录约定
## 编码风格与命名约定
- 文件编码:统一使用 UTF-8;缩进建议:TS/TSX 2 空格,Python 4 空格
- 命名示例:组件 `PascalCase.tsx`hooks `useXxx.ts`,测试文件 `*.test.ts(x)`Vitest 配置包含 `src/**/*.test.ts(x)`)。
- 优先遵循现有 ESLint/Vitest 配置(见 `eslint.config.mjs``vitest.config.ts`
- `/mnt/Data1T/mnote/wolai-frontend/src/`
当前前端主源码目录
- `/mnt/Data1T/mnote/wolai-frontend/convex/`
当前 Convex functions 目录
- `/mnt/Data1T/mnote/wolai-backend/app/`
当前后端主源码目录。
- `/mnt/Data1T/mnote/src/components/onlyoffice/`
当前保留的 OnlyOffice 静态资源、插件与相关数据目录。
- `/mnt/Data1T/mnote/recycle/`
已废弃或已下线内容的回收区,默认不要基于这里继续扩展实现。
## 协作边界(重要)
- 仅修改与目标相关的文件;遇到与任务无关的改动保持不动;若认为会影响任务,先与维护者确认。
- 严禁自行还原、覆盖或丢弃他人/用户已有改动(包括未提交文件)。如需清理或回滚,必须先得到明确同意。
- 不需要进行任何 git 操作(由维护者手动确认与提交)。
## 开发优先级
## 代码索引
- 更细的前后端代码层级索引见 `CODE_INDEX.md`(按路由/API/模块/职责整理)
1. 涉及页面、编辑器、块系统、思维导图、OnlyOffice 页面时,优先检查 `/mnt/Data1T/mnote/wolai-frontend/src/`
2. 涉及数据模型、查询、动作、任务编排时,优先检查 `/mnt/Data1T/mnote/wolai-frontend/convex/`
3. 涉及辅助服务、接口配合、异步处理时,检查 `/mnt/Data1T/mnote/wolai-backend/app/`
4. 只有在处理 OnlyOffice 静态资源、插件或兼容问题时,才进入根目录 `/mnt/Data1T/mnote/src/components/onlyoffice/`
## 关键架构认知
- `BlockNote` 是当前文档页主编辑器内核。
- `Mindmap` 是 BlockNote 的自定义 block,同时支持内嵌和独立全屏页,两者共用同一核心组件。
- `OnlyOffice` 是独立页面型编辑器,不直接嵌入 BlockNote 编辑画布;正文中通常通过附件块跳转进入。
## 协作边界
- 仅修改与当前任务直接相关的文件。
- 不要擅自恢复、覆盖、删除用户已有改动。
- 看到 `/mnt/Data1T/mnote/recycle/` 中的内容时,默认视为历史资料,不作为当前实现依据,除非任务明确要求。
- 若任务涉及架构判断,优先以当前代码与 `/mnt/Data1T/mnote/ARCHITECTURE.md` 为准,不要依赖旧文档记忆。
## 常用开发命令
- 根目录热启动:`npm run desktop:hot`
- 前端开发:`cd /mnt/Data1T/mnote/wolai-frontend && pnpm dev`
- 前端检查:`cd /mnt/Data1T/mnote/wolai-frontend && pnpm lint && pnpm test`
- 后端开发:`cd /mnt/Data1T/mnote/wolai-backend && uvicorn app.main:app --reload --port 8000`
- Convex 自托管:参考 `/mnt/Data1T/mnote/infra/convex/README.md`
## 文件编码与风格
- 所有新增或修改文件统一使用 UTF-8。
- TS/TSX 默认 2 空格缩进,Python 默认 4 空格缩进。
- 注释使用简体中文。
+760
View File
@@ -0,0 +1,760 @@
# MNOTE 当前架构梳理
本文只描述当前仓库中保留下来的主线结构,不再依赖已移动到 `/mnt/Data1T/mnote/recycle/` 的旧设计稿。
## 1. 当前保留的主线目录
- `/mnt/Data1T/mnote/wolai-frontend/`
当前主前端,Next.js App Router,页面、编辑器、思维导图、OnlyOffice 页面都在这里。
- `/mnt/Data1T/mnote/wolai-backend/`
当前辅助后端,FastAPI + Celery,主要提供补充服务能力。
- `/mnt/Data1T/mnote/infra/convex/`
Convex 自托管相关配置。
- `/mnt/Data1T/mnote/src/components/onlyoffice/`
根目录历史遗留,但当前仍保留,因为里面有 OnlyOffice 静态资源、插件和密钥数据。
- `/mnt/Data1T/mnote/scripts/`
启动与环境脚本。
## 2. 总体运行结构
当前主链路是:
1. 前端页面由 `/mnt/Data1T/mnote/wolai-frontend/src/app/**` 提供。
2. 文档主页面进入 `DocumentShell -> DocumentContent -> BlockNoteEditor`,对应源码分别位于 `/mnt/Data1T/mnote/wolai-frontend/src/components/editor/document-shell.tsx``/mnt/Data1T/mnote/wolai-frontend/src/components/editor/document-content.tsx``/mnt/Data1T/mnote/wolai-frontend/src/components/editor/blocknote-editor.tsx`
3. `BlockNoteEditor` 负责承载普通文档块、自定义块、思维导图块、在线表格块等。
4. 思维导图有两种形态:
- 作为 BlockNote 内嵌块存在于文档页面
- 作为独立全屏页面存在于路由 `/mindmap/[docId]/[mindmapId]`,对应源码 `/mnt/Data1T/mnote/wolai-frontend/src/app/mindmap/[docId]/[mindmapId]/page.tsx`
5. OnlyOffice 不嵌在 BlockNote 内部编辑,而是通过独立页面路由 `/onlyoffice` 打开,对应源码 `/mnt/Data1T/mnote/wolai-frontend/src/app/onlyoffice/page.tsx`
6. 文档中的附件块如果识别为 Office 文档,会跳转到路由 `/onlyoffice` 页面进行编辑,对应页面实现 `/mnt/Data1T/mnote/wolai-frontend/src/app/onlyoffice/OnlyOfficeClientPage.tsx`
## 3. 前端主入口
### 3.1 根布局
- `/mnt/Data1T/mnote/wolai-frontend/src/app/layout.tsx`
职责:
- 注入运行时配置到 `window.__MNOTE_RUNTIME_CONFIG__`
- 挂载 Convex Provider、Query Provider
- 作为整个前端 App Router 的根布局
### 3.2 文档页面入口
- `/mnt/Data1T/mnote/wolai-frontend/src/app/(app)/documents/[id]/page.tsx`
职责:
- 根据文档 id 读取元数据
- 组织只读、禁下载、禁复制等页面权限
- 把文档上下文传给 `DocumentShell`,对应源码 `/mnt/Data1T/mnote/wolai-frontend/src/components/editor/document-shell.tsx`
文档页真正的编辑器链路是:
`/mnt/Data1T/mnote/wolai-frontend/src/app/(app)/documents/[id]/page.tsx` -> `/mnt/Data1T/mnote/wolai-frontend/src/components/editor/document-shell.tsx` -> `/mnt/Data1T/mnote/wolai-frontend/src/components/editor/document-content.tsx` -> `/mnt/Data1T/mnote/wolai-frontend/src/components/editor/blocknote-editor.tsx`
## 4. BlockNote 位置与嵌入形式
### 4.1 BlockNote 主组件
- `/mnt/Data1T/mnote/wolai-frontend/src/components/editor/blocknote-editor.tsx`
这是当前文档编辑核心。
它负责:
- 创建 BlockNote 实例
- 加载自定义 schema
- 挂接 Slash Menu、Side Menu、TOC、评论、搜索引用、全屏表格桥接等
- 承载普通文本块和自定义块
### 4.2 文档壳与内容层
- `/mnt/Data1T/mnote/wolai-frontend/src/components/editor/document-shell.tsx`
- `/mnt/Data1T/mnote/wolai-frontend/src/components/editor/document-content.tsx`
嵌入方式:
- `DocumentShell` 用动态导入方式挂载 `DocumentContent`
- `DocumentContent` 再动态导入 `BlockNoteEditor`
- 所以 BlockNote 是“文档页面中的主编辑器内核”,不是一个独立页面
### 4.3 BlockNote schema
- `/mnt/Data1T/mnote/wolai-frontend/src/components/editor/schema.ts`
当前自定义 block 已明确注册在这里,包括:
- `mindmap`
- `onlineTable`
- `media`
- `pageReference`
- `blockReference`
- `advancedTodo`
- `progressMeter`
也就是说,思维导图和在线表格本质上都是 BlockNote 的自定义 block。
## 5. Mindmap 位置与嵌入形式
### 5.1 主组件
- `/mnt/Data1T/mnote/wolai-frontend/src/components/editor/blocks/MindmapBlock.tsx`
这是思维导图前端主实现。
它同时承担:
- 内嵌块视图
- 本地全屏视图
- 独立页面全屏视图复用
- 侧栏、工具栏、导航器、缩略图、计数器、右键菜单等整套 UI
### 5.2 作为 BlockNote 内嵌块
注册位置:
- `/mnt/Data1T/mnote/wolai-frontend/src/components/editor/schema.ts`
插入位置:
- `/mnt/Data1T/mnote/wolai-frontend/src/components/editor/blocknote-editor.tsx`
嵌入方式:
- 在文档页面中,思维导图作为 `type: "mindmap"` 的 block 插入到 BlockNote 内容流里
- 渲染时由 `mindmapBlock()` 对应到 `MindmapBlockView`
- 这个块是“嵌在页面正文里的一大块交互式画布”
视觉与交互形态:
- 默认是页面中的内嵌卡片画布
- 双击后可进入局部全屏模式
### 5.3 作为独立全屏页面
- `/mnt/Data1T/mnote/wolai-frontend/src/app/mindmap/[docId]/[mindmapId]/page.tsx`
嵌入方式:
- 这个页面不是重新实现一个思维导图编辑器
- 而是直接复用 `MindmapBlockView`
- 只用一个假的 `editorStub` 代替 BlockNote 编辑器上下文
- 因此“文档内嵌思维导图”和“独立思维导图页”本质是同一套前端组件
### 5.4 Mindmap 相关子组件
主要都在:
- `/mnt/Data1T/mnote/wolai-frontend/src/components/editor/blocks/`
关键文件包括:
- `/mnt/Data1T/mnote/wolai-frontend/src/components/editor/blocks/MindmapToolbar.tsx`
- `/mnt/Data1T/mnote/wolai-frontend/src/components/editor/blocks/MindmapSidebar.tsx`
- `/mnt/Data1T/mnote/wolai-frontend/src/components/editor/blocks/MindmapNavigator.tsx`
- `/mnt/Data1T/mnote/wolai-frontend/src/components/editor/blocks/MindmapMiniMap.tsx`
- `/mnt/Data1T/mnote/wolai-frontend/src/components/editor/blocks/MindmapCount.tsx`
- `/mnt/Data1T/mnote/wolai-frontend/src/components/editor/blocks/MindmapContextMenu.tsx`
### 5.5 当前结论
Mindmap 的前端定位不是独立编辑器系统,而是:
- 以 BlockNote 自定义块为主形态
- 以独立全屏页为辅助形态
- 两者共用同一核心组件 `MindmapBlockView`
## 6. OnlyOffice 位置与嵌入形式
### 6.1 OnlyOffice 页面入口
- `/mnt/Data1T/mnote/wolai-frontend/src/app/onlyoffice/page.tsx`
- `/mnt/Data1T/mnote/wolai-frontend/src/app/onlyoffice/OnlyOfficeClientPage.tsx`
当前 OnlyOffice 不是 BlockNote 内嵌编辑器,而是单独页面。
嵌入方式:
- 打开路由 `/onlyoffice`,对应入口文件 `/mnt/Data1T/mnote/wolai-frontend/src/app/onlyoffice/page.tsx`
- 页面内部加载 OnlyOffice 文档编辑器脚本
- 通过 URL 参数和运行时配置决定打开哪个文档、哪种模式
### 6.2 页面职责
`page.tsx` 负责:
- 在最早阶段注入 DOM 补丁
- 防止 OnlyOffice 某些 `NotFoundError` 导致整页白屏
`OnlyOfficeClientPage.tsx` 负责:
- 真正加载 OnlyOffice 编辑器
- 改写内部请求
- 处理代理地址
- 配置 callback、签名、强制保存
- 挂载 `OnlyOfficeAiAgentPanel`
### 6.3 与正文的关系
OnlyOffice 当前不是“BlockNote 中的一块可直接编辑的 Office 文档”。
当前模式是:
1. 文档正文里放的是附件块 `MediaBlock`
2. 如果附件扩展名属于 Office 文档类型
3. 点击后新开路由 `/onlyoffice` 页面,对应实现 `/mnt/Data1T/mnote/wolai-frontend/src/app/onlyoffice/OnlyOfficeClientPage.tsx`
4. 在独立页面内进行 OnlyOffice 编辑
对应主文件:
- `/mnt/Data1T/mnote/wolai-frontend/src/components/editor/blocks/MediaBlock.tsx`
这意味着:
- BlockNote 中只嵌入“文件入口”
- OnlyOffice 编辑器本身在独立页面运行
### 6.4 OnlyOffice 资源位置
当前仍保留的历史资源目录:
- `/mnt/Data1T/mnote/src/components/onlyoffice/onlyoffice-web-apps`
- `/mnt/Data1T/mnote/src/components/onlyoffice/onlyoffice-plugins`
- `/mnt/Data1T/mnote/src/components/onlyoffice/onlyoffice-data`
这里不是主 React 页面代码,但对 OnlyOffice 运行仍有意义,因为里面保留了:
- web apps 静态资源
- 插件
- key / data
### 6.5 OnlyOffice 代理与 API
关键接口:
- `/mnt/Data1T/mnote/wolai-frontend/src/app/api/onlyoffice/proxy/route.ts`
- `/mnt/Data1T/mnote/wolai-frontend/src/app/api/onlyoffice/callback/route.ts`
- `/mnt/Data1T/mnote/wolai-frontend/src/app/api/onlyoffice/forcesave/route.ts`
- `/mnt/Data1T/mnote/wolai-frontend/src/app/api/onlyoffice/sign/route.ts`
当前角色分工:
- `proxy`
负责把浏览器不可直达的 OnlyOffice / 文件回源 URL 包成同源代理,避免跨域、内网地址、token 参数冲突
- `callback`
接收 OnlyOffice 保存回调,再把内容写回存储
- `forcesave`
主动触发保存
- `sign`
提供签名能力
### 6.6 当前结论
OnlyOffice 的前端定位是:
- 独立页面型编辑器
- 不直接嵌入 BlockNote 编辑画布
- 通过附件块从文档页跳转进入
## 7. Online Table 的位置与嵌入形式
虽然你这次重点是 BlockNote / Mindmap / OnlyOffice,但当前结构里在线表格和它们关系紧密,也需要一起记住。
主文件:
- `/mnt/Data1T/mnote/wolai-frontend/src/components/editor/blocks/OnlineTableBlock.tsx`
嵌入方式:
- 在线表格是 BlockNote 的自定义 block
- 在正文中显示的是紧凑预览块
- 双击或操作按钮后,通过 editor bridge 打开全屏表格编辑器
所以它和 Mindmap 类似,都是:
- 正文里先以内嵌 block 形式出现
- 再切换到更大编辑视图
但它和 OnlyOffice 不同:
- Online Table 仍然属于 BlockNote 自定义 block 体系
- OnlyOffice 是独立页面体系
## 8. 四层结构图
下面用“组件层 / 页面层 / API 层 / 存储层”重新梳理一次当前主线。
### 8.1 组件层
组件层主要位于:
- `/mnt/Data1T/mnote/wolai-frontend/src/components/`
其中本次最关键的组件分组是:
- 文档编辑核心
- `/mnt/Data1T/mnote/wolai-frontend/src/components/editor/document-shell.tsx`
- `/mnt/Data1T/mnote/wolai-frontend/src/components/editor/document-content.tsx`
- `/mnt/Data1T/mnote/wolai-frontend/src/components/editor/blocknote-editor.tsx`
- BlockNote 自定义块
- `/mnt/Data1T/mnote/wolai-frontend/src/components/editor/blocks/MindmapBlock.tsx`
- `/mnt/Data1T/mnote/wolai-frontend/src/components/editor/blocks/OnlineTableBlock.tsx`
- `/mnt/Data1T/mnote/wolai-frontend/src/components/editor/blocks/MediaBlock.tsx`
- OnlyOffice 页面侧组件
- `/mnt/Data1T/mnote/wolai-frontend/src/components/onlyoffice/OnlyOfficeAiAgentPanel.tsx`
这一层的职责是:
- 渲染交互界面
- 承载编辑器实例
- 把页面数据转成可操作 UI
- 管理局部交互状态
### 8.2 页面层
页面层主要位于:
- `/mnt/Data1T/mnote/wolai-frontend/src/app/`
关键页面关系是:
1. 文档页
- 路由:`/documents/[id]`
- 源码:`/mnt/Data1T/mnote/wolai-frontend/src/app/(app)/documents/[id]/page.tsx`
- 负责把文档元数据交给文档编辑器宿主
2. 思维导图全屏页
- 路由:`/mindmap/[docId]/[mindmapId]`
- 源码:`/mnt/Data1T/mnote/wolai-frontend/src/app/mindmap/[docId]/[mindmapId]/page.tsx`
- 负责把 MindmapBlock 复用为独立编辑页面
3. OnlyOffice 页
- 路由:`/onlyoffice`
- 源码:`/mnt/Data1T/mnote/wolai-frontend/src/app/onlyoffice/page.tsx`
- 客户端实现:`/mnt/Data1T/mnote/wolai-frontend/src/app/onlyoffice/OnlyOfficeClientPage.tsx`
- 负责承载独立的文档编辑器
这一层的职责是:
- 定义访问路由
- 组织页面级权限和参数
- 决定使用哪套组件组合
- 把独立编辑器和正文编辑器区分开
### 8.3 API 层
API 层主要位于:
- `/mnt/Data1T/mnote/wolai-frontend/src/app/api/`
和本次主题最相关的 API 组是:
- 文档内容与页面数据
- `/mnt/Data1T/mnote/wolai-frontend/src/app/api/documents/`
- 思维导图
- `/mnt/Data1T/mnote/wolai-frontend/src/app/api/mindmap/`
- `/mnt/Data1T/mnote/wolai-frontend/src/app/api/mindmap-ai/`
- 媒体与附件
- `/mnt/Data1T/mnote/wolai-frontend/src/app/api/media/`
- OnlyOffice
- `/mnt/Data1T/mnote/wolai-frontend/src/app/api/onlyoffice/proxy/route.ts`
- `/mnt/Data1T/mnote/wolai-frontend/src/app/api/onlyoffice/callback/route.ts`
- `/mnt/Data1T/mnote/wolai-frontend/src/app/api/onlyoffice/forcesave/route.ts`
- `/mnt/Data1T/mnote/wolai-frontend/src/app/api/onlyoffice/sign/route.ts`
这一层的职责是:
- 做 BFF 转换
- 统一权限校验
- 代理前端和 Convex / 外部编辑器之间的请求
- 处理 OnlyOffice 的保存回写与代理回源
### 8.4 存储层
存储层当前不是单一系统,而是三部分组合:
1. Convex 主数据层
- 位置:`/mnt/Data1T/mnote/wolai-frontend/convex/`
- 负责文档元数据、业务记录、任务编排等主链路数据
2. 本地 / 历史文件资源层
- 位置:`/mnt/Data1T/mnote/src/components/onlyoffice/`
- 负责保留 OnlyOffice 相关静态资源、插件和密钥数据
3. 辅助后端层
- 位置:`/mnt/Data1T/mnote/wolai-backend/app/`
- 负责补充服务能力与异步处理配合
这一层的职责是:
- 保存业务主数据
- 保存编辑器所需静态资源
- 承载需要服务端参与的补充逻辑
### 8.5 三个核心能力在四层中的落点
#### A. BlockNote
- 组件层:`/mnt/Data1T/mnote/wolai-frontend/src/components/editor/blocknote-editor.tsx`
- 页面层:`/mnt/Data1T/mnote/wolai-frontend/src/app/(app)/documents/[id]/page.tsx`
- API 层:`/mnt/Data1T/mnote/wolai-frontend/src/app/api/documents/``/mnt/Data1T/mnote/wolai-frontend/src/app/api/blocks/`
- 存储层:`/mnt/Data1T/mnote/wolai-frontend/convex/`
#### B. Mindmap
- 组件层:`/mnt/Data1T/mnote/wolai-frontend/src/components/editor/blocks/MindmapBlock.tsx`
- 页面层:
- 文档内嵌页面宿主:`/mnt/Data1T/mnote/wolai-frontend/src/app/(app)/documents/[id]/page.tsx`
- 独立页:`/mnt/Data1T/mnote/wolai-frontend/src/app/mindmap/[docId]/[mindmapId]/page.tsx`
- API 层:
- `/mnt/Data1T/mnote/wolai-frontend/src/app/api/mindmap/`
- `/mnt/Data1T/mnote/wolai-frontend/src/app/api/mindmap-ai/`
- 存储层:
- Convex 主数据:`/mnt/Data1T/mnote/wolai-frontend/convex/`
- 本地 mindmap 辅助逻辑:`/mnt/Data1T/mnote/wolai-frontend/src/lib/mindmap/`
#### C. OnlyOffice
- 组件层:
- `/mnt/Data1T/mnote/wolai-frontend/src/app/onlyoffice/OnlyOfficeClientPage.tsx`
- `/mnt/Data1T/mnote/wolai-frontend/src/components/onlyoffice/OnlyOfficeAiAgentPanel.tsx`
- 页面层:
- `/mnt/Data1T/mnote/wolai-frontend/src/app/onlyoffice/page.tsx`
- API 层:
- `/mnt/Data1T/mnote/wolai-frontend/src/app/api/onlyoffice/`
- 存储层:
- OnlyOffice 静态资源:`/mnt/Data1T/mnote/src/components/onlyoffice/`
- 业务回写与数据协作:`/mnt/Data1T/mnote/wolai-frontend/convex/``/mnt/Data1T/mnote/wolai-backend/app/`
## 9. 页面树与文件树
这一部分专门说明当前左侧结构区里的两套树:页面树和文件树。
### 9.1 位置
它们当前都属于侧边栏系统,主要位于:
- `/mnt/Data1T/mnote/wolai-frontend/src/components/sidebar/sidebar.tsx`
其中:
- 页面树组件:
- `/mnt/Data1T/mnote/wolai-frontend/src/components/sidebar/private-tree.tsx`
- 文件树组件:
- `/mnt/Data1T/mnote/wolai-frontend/src/components/sidebar/file-tree.tsx`
也就是说,两者都在“组件层”的侧边栏模块里,但不是同一个组件。
### 9.2 来源
当前两套树的数据源都先汇总到侧边栏数据模型,再做不同投影。
主要数据入口是:
- `/mnt/Data1T/mnote/wolai-frontend/src/hooks/use-convex-sidebar-data.ts`
这个 hook 会从 Convex 拉取:
- 文档列表
- 回收站文档
- 思维导图记录
- 媒体资产
- 回收站媒体资产
- 在线表格记录
- 工作空间摘要
然后把这些数据组合成统一的 `SidebarInitialData` 形状,交给侧边栏 UI。
### 9.3 页面树
页面树的核心特点是:
- 只关心“文档节点”本身
- 表示页面之间的父子结构
- 不直接把附件、思维导图文件夹、在线表格文件作为树节点展开
主要实现链路:
1. `/mnt/Data1T/mnote/wolai-frontend/src/hooks/use-convex-sidebar-data.ts`
提供文档数据源
2. `/mnt/Data1T/mnote/wolai-frontend/src/lib/documents.ts`
负责把文档记录构造成 `DocumentNode`
3. `/mnt/Data1T/mnote/wolai-frontend/src/lib/sidebar-tree.ts`
负责 `flattenDocumentTree` 和 section 投影
4. `/mnt/Data1T/mnote/wolai-frontend/src/components/sidebar/private-tree.tsx`
负责真正渲染页面树
页面树所在层次:
- 组件层:`/mnt/Data1T/mnote/wolai-frontend/src/components/sidebar/private-tree.tsx`
- 页面层宿主:`/mnt/Data1T/mnote/wolai-frontend/src/components/sidebar/sidebar.tsx`
- 数据来源:`/mnt/Data1T/mnote/wolai-frontend/src/hooks/use-convex-sidebar-data.ts`
- 存储来源:`/mnt/Data1T/mnote/wolai-frontend/convex/`
页面树更像“文档导航树”。
### 9.4 文件树
文件树的核心特点是:
- 以文档为根
- 但会把文档下的索引页、附件、思维导图资产、在线表格资产一起展开
- 更像“资源管理器视图”
主要实现链路:
1. `/mnt/Data1T/mnote/wolai-frontend/src/hooks/use-convex-sidebar-data.ts`
提供文档、媒体、思维导图、表格等混合数据
2. `/mnt/Data1T/mnote/wolai-frontend/src/components/sidebar/sidebar.tsx`
把多类资源整理到 file-tree 视图所需的输入结构
3. `/mnt/Data1T/mnote/wolai-frontend/src/lib/file-tree/rows.ts`
把文档节点 + 资产组合成 `FileTreeRow[]`
4. `/mnt/Data1T/mnote/wolai-frontend/src/components/sidebar/file-tree.tsx`
负责真正渲染文件树
文件树所在层次:
- 组件层:`/mnt/Data1T/mnote/wolai-frontend/src/components/sidebar/file-tree.tsx`
- 页面层宿主:`/mnt/Data1T/mnote/wolai-frontend/src/components/sidebar/sidebar.tsx`
- 数据来源:`/mnt/Data1T/mnote/wolai-frontend/src/hooks/use-convex-sidebar-data.ts`
- 存储来源:`/mnt/Data1T/mnote/wolai-frontend/convex/`
文件树更像“页面 + 资源”的混合管理视图。
### 9.5 二者的关系
页面树和文件树不是两套独立后端数据模型,而是:
- 共用同一套侧边栏主数据源
- 在前端侧做两种不同投影
区别可以简单记成:
- 页面树:只看页面层级
- 文件树:看页面层级 + 页面下资源
### 9.6 当前结论
如果后续迁移到 `mnote-rust`,这两套树不应该混为一个组件重写,而应该拆成:
1. 统一侧边栏数据聚合层
2. 页面树投影层
3. 文件树投影层
4. 各自独立渲染组件
这样最符合当前实现,也最容易保持行为一致。
## 10. 数据保存与读写入口
### 10.1 BlockNote
核心文件:
- `/mnt/Data1T/mnote/wolai-frontend/src/components/editor/blocknote-editor.tsx`
- `/mnt/Data1T/mnote/wolai-frontend/src/app/api/documents/save/route.ts`
保存位置:
- 主保存目标是 Convex 文档内容字段,对应后端 mutation 为 `api.documents.updateContent`
- 实现入口位于 `/mnt/Data1T/mnote/wolai-frontend/src/app/api/documents/save/route.ts`
保存格式:
- 前端提交字段为 `{ documentId, content }`
- 其中 `content` 是 BlockNote 的 JSON 块数组
- 如果初始值不是纯数组,也会在 `/mnt/Data1T/mnote/wolai-frontend/src/components/editor/blocknote-editor.tsx` 中先提取 `blocks`
读写入口:
- 读入口:
- `/mnt/Data1T/mnote/wolai-frontend/src/app/(app)/documents/[id]/page.tsx`
- `/mnt/Data1T/mnote/wolai-frontend/src/components/editor/document-shell.tsx`
- `/mnt/Data1T/mnote/wolai-frontend/src/components/editor/document-content.tsx`
- `/mnt/Data1T/mnote/wolai-frontend/src/components/editor/blocknote-editor.tsx`
- 写入口:
- `/mnt/Data1T/mnote/wolai-frontend/src/components/editor/blocknote-editor.tsx`
- 其中 `saveContent()``POST /api/documents/save`
- `/mnt/Data1T/mnote/wolai-frontend/src/app/api/documents/save/route.ts` 再调用 `api.documents.updateContent`
补充说明:
- BlockNote 主内容走 Convex 持久化
- 同文件里还保留了 `HocuspocusProvider + Y.Doc` 协作接入,说明实时协作层与最终持久化层是分开的
- 思维导图自动保存缓存也会临时写到浏览器 `localStorage`,但那不是文档主存储
### 10.2 Mindmap
核心文件:
- `/mnt/Data1T/mnote/wolai-frontend/src/components/editor/blocks/MindmapBlock.tsx`
- `/mnt/Data1T/mnote/wolai-frontend/src/lib/mindmap/mindmapLocalStore.ts`
- `/mnt/Data1T/mnote/wolai-frontend/src/lib/server/local-paths.ts`
保存位置:
- 首选目录由 `/mnt/Data1T/mnote/wolai-frontend/src/lib/server/local-paths.ts``getDocumentsBaseDir()` 决定
- 如果设置了 `MNOTE_DATA_DIR`,目录是 `MNOTE_DATA_DIR/documents/<docId>/`
- 否则回退到 `/mnt/Data1T/mnote/wolai-frontend/public/documents/<docId>/`
- 兼容旧路径时,还会尝试 `getLegacyMindmapsBaseDir()`,即 `MNOTE_DATA_DIR/mindmaps/<docId>/``/mnt/Data1T/mnote/wolai-frontend/public/mindmaps/<docId>/`
保存格式:
- 保存为 JSON 文件
- 文件名规则在 `/mnt/Data1T/mnote/wolai-frontend/src/lib/mindmap/mindmapLocalStore.ts`
- `mindmap.json`
- `mindmap-${mindmapId}.json`
- 实际写入使用 `JSON.stringify(data, null, 2)`,即格式化 JSON 文本
读写入口:
- 读入口:
- `/mnt/Data1T/mnote/wolai-frontend/src/lib/mindmap/mindmapLocalStore.ts``readMindmapLocal(docId, mindmapId)`
- 写入口:
- `/mnt/Data1T/mnote/wolai-frontend/src/lib/mindmap/mindmapLocalStore.ts``writeMindmapLocal(docId, mindmapId, data, docTitle)`
- 相关服务侧复用:
- `/mnt/Data1T/mnote/wolai-frontend/src/lib/ai-agent/tools/builtins/mindmap/mindmapServerTools.ts`
补充说明:
- Mindmap 当前不是像 BlockNote 正文那样直接持久化到 Convex 内容字段
- 它更像“文档目录下的配套 JSON 资产”
- 页面中的思维导图块与独立全屏思维导图页,共用同一份数据结构
### 10.3 OnlyOffice
核心文件:
- `/mnt/Data1T/mnote/wolai-frontend/src/app/onlyoffice/OnlyOfficeClientPage.tsx`
- `/mnt/Data1T/mnote/wolai-frontend/src/app/api/onlyoffice/callback/route.ts`
- `/mnt/Data1T/mnote/wolai-frontend/src/app/api/onlyoffice/sign/route.ts`
保存位置:
- 当前保存目标不是 BlockNote 内容,而是附件资产本身
- Convex 模式下,OnlyOffice 回调会把编辑后的二进制文件重新上传到 Convex Files
- 然后更新 `media_assets.storage_id / file_url` 之类的附件存储指针
保存格式:
- 保存内容是 Office 原始文件二进制,不是 BlockNote JSON
- 文档格式取决于当前附件类型,例如 `docx / xlsx / pptx / pdf`
- 前端传给 OnlyOffice 的 `document.url` 是可下载原文件的地址,OnlyOffice 保存后再通过回调传回新的下载地址
读写入口:
- 读入口:
- `/mnt/Data1T/mnote/wolai-frontend/src/components/editor/blocks/MediaBlock.tsx` 负责从正文附件入口跳转
- `/mnt/Data1T/mnote/wolai-frontend/src/app/onlyoffice/page.tsx`
- `/mnt/Data1T/mnote/wolai-frontend/src/app/onlyoffice/OnlyOfficeClientPage.tsx`
- 写入口:
- `OnlyOfficeClientPage.tsx` 里组装 `callbackUrl`
- `/mnt/Data1T/mnote/wolai-frontend/src/app/api/onlyoffice/callback/route.ts` 接收 status `2/6`
- 回调路由下载 OnlyOffice 生成的新文件,再上传到 Convex Files,并调用 `api.mediaAssets.replaceStorageFromUpload`
补充说明:
- `/mnt/Data1T/mnote/wolai-frontend/src/app/api/onlyoffice/forcesave/route.ts` 用于主动触发保存
- `/mnt/Data1T/mnote/wolai-frontend/src/app/api/onlyoffice/proxy/route.ts` 用于同源代理与回源下载
- 根目录 `/mnt/Data1T/mnote/src/components/onlyoffice/` 保留的是 OnlyOffice 静态资源、插件与历史数据,不是正文主数据存储
### 10.4 OnlyOffice key 生成规则
这里要区分两种“key / 密钥”。
第一种是前端传给 OnlyOffice 的 `document.key`
- 定义位置:
- `/mnt/Data1T/mnote/wolai-frontend/src/app/onlyoffice/OnlyOfficeClientPage.tsx`
- 生成规则:
-`assetId` 时,优先生成 `assetId + "_" + hashKey(assetStorageId)`
- 如果还没拿到 `assetStorageId`,先退化为 `assetId`
- 没有 `assetId` 时,回退为 `hashKey(\`${effectiveFileUrl}-${fileName}\`)`
- 设计目的:
- 让同一附件多次打开时 `document.key` 尽量稳定,提升 OnlyOffice 内部缓存命中
- 当附件回写后 `storage_id` 变化,key 会自然变化,避免旧缓存污染新文件
- 字符集限制:
- 代码注释明确说明 OnlyOffice `document.key` 允许字符集接近 `[0-9a-zA-Z_.=-]`
- 所以这里对 `storage_id` 做 hash,而不是直接原样拼接
第二种是服务端签名与回调校验所需的密钥:
- `ONLYOFFICE_JWT_SECRET`
- 读取位置:`/mnt/Data1T/mnote/wolai-frontend/src/app/api/onlyoffice/sign/route.ts`
- 用途:给整体 config、`document``editorConfig` 生成 HS256 JWT
- 获得方式:这是你自己部署 OnlyOffice 时,在服务端环境变量里配置的一段共享密钥,不是前端算出来的值
- 如果未配置,当前实现会返回 `token: null`,兼容“服务端未开启 JWT 校验”的场景
- `ONLYOFFICE_CALLBACK_SECRET`
- 读取位置:`/mnt/Data1T/mnote/wolai-frontend/src/app/api/onlyoffice/callback/route.ts`
- 用途:校验 OnlyOffice 文档服务器回调到 `/api/onlyoffice/callback` 时附带的共享密钥
- 获得方式:同样是你在本项目环境变量和 OnlyOffice 侧约定的一段共享字符串,不是系统自动下发
- 如果未配置,当前实现保持兼容,不强制校验
当前结论:
- 如果你问“OnlyOffice 的 key 如何获得”,前端运行期真正用来区分文档实例的 key,是代码按 `assetId / storage_id / fileUrl / fileName` 现场生成的
- 如果你问“OnlyOffice 的密钥如何获得”,那指的是 `ONLYOFFICE_JWT_SECRET``ONLYOFFICE_CALLBACK_SECRET`,它们都应由部署者在环境变量中自行生成并在双方配置保持一致
## 11. 三者关系总结
### 11.1 BlockNote
定位:
- 主文档编辑器内核
- 页面正文的承载容器
表现形式:
- 文档主页面核心区域
- 挂载各种自定义 block
### 11.2 Mindmap
定位:
- BlockNote 内部的高级自定义块
表现形式:
- 文档页中可内嵌
- 也可进入全屏
- 独立路由 `/mindmap/[docId]/[mindmapId]` 页面与内嵌块共用同一核心组件,对应页面文件 `/mnt/Data1T/mnote/wolai-frontend/src/app/mindmap/[docId]/[mindmapId]/page.tsx`
### 11.3 OnlyOffice
定位:
- 独立页面型文档编辑器
表现形式:
- 从附件块跳转进入
- 运行在路由 `/onlyoffice`,对应页面文件 `/mnt/Data1T/mnote/wolai-frontend/src/app/onlyoffice/page.tsx`
- 不直接嵌入 BlockNote 正文编辑画布
## 12. 一句话记忆版
可以把当前架构记成下面这三句:
- `BlockNote` 是主页面正文编辑器容器
- `Mindmap` 是挂在 BlockNote 里的重交互 block,并可切到全屏
- `OnlyOffice` 是通过附件入口跳转出去的独立编辑页面
## 13. 后续重构建议
如果后面你要把这套能力迁到 `mnote-rust`,最值得优先抽象的是三层:
1. 文档宿主层
- 文档页面壳
- 文档权限
- 编辑器挂载流程
2. Block 能力层
- BlockNote schema
- `mindmap` / `onlineTable` / `media` 的块协议
3. 外部编辑器层
- OnlyOffice 独立页面
- 代理 / callback / sign / forcesave
这样拆之后会非常清楚:
- 哪些能力应继续做成“内嵌 block”
- 哪些能力应继续保留为“独立页面编辑器”
-124
View File
@@ -1,124 +0,0 @@
# CLAUDE.md
本文件用于指导 Claude Codeclaude.ai/code)在此仓库中进行开发与协作。
## 重要(防止误判)
- **当前已使用 Convex 完全替换 Supabase**:数据读写、鉴权与权限控制以 Convex 为主线。
- 仓库内若仍存在 `supabase/``supabase.md``wolai-frontend/src/lib/supabase/``@supabase/*` 依赖或相关代码,均应视为**历史遗留/兼容残留**;除非任务明确要求,否则不要基于它们继续扩展实现。
## 项目概览
MNOTE 是一个知识管理系统,结合类 Notion 的块编辑器、思维导图与 AI 工具,支持 Web/桌面(Electron)混合形态。
**技术栈(核心)**
- 前端:Next.jsApp Router)、React、BlockNote
- 数据层:Convex(自托管见 `infra/convex/`functions 见 `wolai-frontend/convex/`
- 后端(辅助能力):FastAPI + CeleryOCR / OnlyOffice / 集成服务等)
- 桌面端:Electron(内嵌 Next.js standalone
- AI:工具型 Agent 系统(30+ 内置工具)
## 常用命令
### 快速启动(推荐)
```bash
npm run desktop:hot # 启动前端 + 后端(Redis 可用时启 Celery
npm run desktop:local # 本地离线模式
npm run desktop # 桌面端生产模式
```
### 仅前端(wolai-frontend/
```bash
pnpm dev
pnpm build
pnpm start
pnpm lint
pnpm test
```
### 仅后端(wolai-backend/
```bash
pip install -r requirements.txt
uvicorn app.main:app --reload --port 8000
celery -A app.workers.celery_app worker --loglevel=info
```
### Convex(自托管)
`infra/convex/README.md` 启动(Docker Compose)。
### E2E 测试
```bash
cd pw-tests/scripts
python e2e_mindmap_sync.py
```
## 架构与目录
### 目录结构(关键)
```
wolai-frontend/ # 主前端(优先在这里改)
src/ # Next.js 源码
convex/ # Convex functionsschema/query/mutation/action
public/ # 静态资源 + mnote-env.json 等
infra/convex/ # Convex 自托管(compose + README
wolai-backend/ # FastAPI 后端(辅助能力)
services/ # 配套服务(OCR/RAG 等)
pw-tests/ # 端到端测试
desktop-electron/ # Electron 包装
supabase/ # 历史遗留(不要当作现行架构)
```
**注意:**仓库根目录的 `src/` 可能是历史/镜像目录;前端改动优先在 `wolai-frontend/src/`
### 关键模式
- **BFFBackend for Frontend**Next.js Route Handlers 负责鉴权与调用 Convexquery/mutation/action),前端页面通过这些 API/或 Convex provider 获取数据。
- **桌面端与 Web 共用代码**:桌面端通过运行期注入配置(`window.__MNOTE_RUNTIME_CONFIG__`)实现同一套前端在不同网络/环境下运行。
- **思维导图是一等公民**:以结构化 JSON 存储,支持节点引用(附件、URL + 页码)、AI 扩写等。
## 环境变量与配置
### 前端(使用仓库根目录 .env.all)
至少需要:
```bash
NEXT_PUBLIC_CONVEX_URL=... # 浏览器侧 Convex URL(云端或自托管)
NEXT_PUBLIC_USE_CONVEX=1 # 开启 Convex 模式(或 USE_CONVEX=1
NEXT_PUBLIC_BACKEND_URL=... # FastAPI(如启用 OCR/OnlyOffice 等)
NEXT_PUBLIC_ONLYOFFICE_BASE_URL=... # OnlyOffice(如启用)
```
### 服务端(仅在需要服务端直连/管理权限时)
```bash
CONVEX_SELF_HOSTED_URL=...
CONVEX_SELF_HOSTED_ADMIN_KEY=...
```
### 桌面端运行期配置
桌面端会读取/合并运行期配置(如 `public/mnote-env.json` 与桌面配置文件)。以 Convex 为主线配置即可;若看到 Supabase 字段,默认当作历史兼容残留处理。
## 编码约定
- 文件编码:UTF-8;代码注释允许中文(建议简体)。
- 缩进:TS/TSX 2 空格,Python 4 空格。
- 命名:组件 `PascalCase`hooks `useXxx`,测试 `*.test.ts(x)`
- 导入:使用 `@/` 作为绝对路径别名。
## 关键文件(快速上手)
- `wolai-frontend/src/app/layout.tsx`:根布局与运行期配置注入(Convex provider 挂载)。
- `wolai-frontend/src/components/providers/convex-provider.tsx`Convex React Client 初始化与 Provider。
- `wolai-frontend/src/lib/convex/`Convex client/server/route 封装。
- `wolai-frontend/convex/`Convex functions(数据模型与后端逻辑)。
- `wolai-frontend/src/lib/ai-agent/runtime/runAgent.ts`AI agent 引擎。
- `wolai-frontend/src/lib/ai-agent/tools/builtins/registryBuiltins.ts`:内置工具注册表。
- `scripts/desktop-hot.js`:开发编排脚本。
- `AGENTS.md`:仓库协作指南(中文)。
- `CODE_INDEX.md`:代码索引(中文)。
## 其他注意事项
- 不要提交密钥/Token/账号;本地/生产配置统一放在仓库根目录的 `.env.all`(UTF-8,且要求 key 唯一)。
- 路由目录含 `()``[]`(如 `(app)``[id]`),PowerShell 中操作建议用 `-LiteralPath` 避免通配符误匹配。
## AI Agent 工具开发
新增 AI 能力时,在 `wolai-frontend/src/lib/ai-agent/tools/builtins/` 注册工具,按 scope 组织:
- Globalsearch_web、docs_search/read
- Mindmapmindmap_get、mindmap_apply_ops
- Documentdoc_get、doc_insert_blocks
- OnlyOfficeoo_get_selection、oo_replace_selection
权限模型:`{ read: "allow" | "confirm", write: "allow" | "confirm" }`
-67
View File
@@ -1,67 +0,0 @@
# 代码索引(前后端)
本文用于后续开发“快速定位入口/职责/调用链”,按目录与功能点整理。路径以仓库根目录为基准。
## 关键事实(防止误判)
- **当前已使用 Convex 完全替换 Supabase**:数据读写、鉴权与权限控制以 Convex 为主线。
- 仓库内若仍出现 `supabase/``supabase.md``wolai-frontend/src/lib/supabase/``wolai-backend/app/services/supabase_rest.py` 等,均应视为**历史遗留**(除非任务明确要求,否则不要继续扩展)。
## 启动链路与关键入口
- 一键热启动脚本:`scripts/desktop-hot.js`(启动 `wolai-frontend` + `wolai-backend`Redis 可用时启动 Celery)。
- 前端入口(Next.js App Router):`wolai-frontend/src/app/layout.tsx`(注入 runtime config;挂载 Convex provider)。
- 运行期配置:`wolai-frontend/src/lib/runtime-config.ts`(读取 env / public 配置并注入到 `window.__MNOTE_RUNTIME_CONFIG__`)。
- Convex 自托管(基础设施):`infra/convex/docker-compose.yml``infra/convex/README.md`
- Convex functions(后端逻辑/数据模型):`wolai-frontend/convex/`
- 后端入口(FastAPI):`wolai-backend/app/main.py`(辅助能力与集成服务,例如 OCR / OnlyOffice 等)。
## 前端(wolai-frontend
### 页面路由(App Router
- 登录/鉴权:`wolai-frontend/src/app/(auth)/`(如 `wolai-frontend/src/app/(auth)/auth/page.tsx`)。
- 主应用布局:`wolai-frontend/src/app/(app)/layout.tsx`
- 文档页:`wolai-frontend/src/app/(app)/documents/[id]/page.tsx`
- 思维导图全屏页:`wolai-frontend/src/app/mindmap/[docId]/[mindmapId]/page.tsx`
- OnlyOffice`wolai-frontend/src/app/onlyoffice/`
- 调试页:`wolai-frontend/src/app/dev/`AI Agent / Mindmap 等)。
### APINext Route Handlers / BFF
这些接口承担“鉴权 + 调用 Convexquery/mutation/action+ 返回前端”的 BFF 职责:
- 文档相关:`wolai-frontend/src/app/api/documents/*/route.ts`
- 思维导图相关:`wolai-frontend/src/app/api/mindmap/**/route.ts`
- 思维导图 AI`wolai-frontend/src/app/api/mindmap-ai/*/route.ts`
- 媒体/附件:`wolai-frontend/src/app/api/media/*/route.ts`
- AI Agent 统一入口:`wolai-frontend/src/app/api/ai-agent/run/route.ts`
- OnlyOffice 代理:`wolai-frontend/src/app/api/onlyoffice/proxy/route.ts`
### Convex(前端接入层)
- Convex React Provider`wolai-frontend/src/components/providers/convex-provider.tsx`
- Convex API/Client 封装:`wolai-frontend/src/lib/convex/`(如 `api.ts``route.ts``server.ts``enabled.ts`)。
- SidebarConvex 数据来源):`wolai-frontend/src/hooks/use-convex-sidebar-data.ts``wolai-frontend/src/components/sidebar/sidebar.tsx`
### 编辑器与核心 UI
- 文档壳:`wolai-frontend/src/components/editor/document-shell.tsx`
- 文档内容:`wolai-frontend/src/components/editor/document-content.tsx`
- BlockNote 编辑器:`wolai-frontend/src/components/editor/blocknote-editor.tsx`
- 思维导图 Block`wolai-frontend/src/components/editor/blocks/MindmapBlock.tsx`
- 侧边栏:`wolai-frontend/src/components/sidebar/`
### AI Agent(工具系统)
- 运行时:`wolai-frontend/src/lib/ai-agent/runtime/runAgent.ts`
- 工具注册:`wolai-frontend/src/lib/ai-agent/tools/builtins/registryBuiltins.ts`
### 状态管理
- Zustand stores`wolai-frontend/src/store/*`
## 后端(wolai-backend
后端主要承担辅助能力与集成服务(如 OCR/异步任务/OnlyOffice 等),数据主链路以 Convex 为准:
- FastAPI 入口:`wolai-backend/app/main.py`
- 路由聚合:`wolai-backend/app/routers/__init__.py`
- 健康检查:`wolai-backend/app/routers/health.py`
- OCR/任务相关:`wolai-backend/app/routers/tasks.py``wolai-backend/app/services/*`
- Celery`wolai-backend/app/workers/*`
## 历史遗留(不要当作现行架构)
若你看到以下路径/模块,请默认它们不再是当前主线(除非任务明确要求处理):
- `supabase/``supabase.md`
- `wolai-frontend/src/lib/supabase/`
- `wolai-backend/app/services/supabase_rest.py`
-232
View File
@@ -1,232 +0,0 @@
# LightRAG Project Intelligence (.clinerules)
## Project Overview
LightRAG is a mature, production-ready Retrieval-Augmented Generation (RAG) system with comprehensive knowledge graph capabilities. The system has evolved from experimental to production-ready status with extensive functionality across all major components.
## Current System State (August 15, 2025)
- **Status**: Production Ready - Stable and Mature
- **Configuration**: Gemini 2.5 Flash + BAAI/bge-m3 embeddings via custom endpoints
- **Storage**: Default in-memory with file persistence (JsonKVStorage, NetworkXStorage, NanoVectorDBStorage)
- **Language**: Chinese for summaries
- **Workspace**: `space1` for data isolation
- **Authentication**: JWT-based with admin/user accounts
## Critical Implementation Patterns
### 1. Embedding Format Compatibility (CRITICAL)
**Pattern**: Always handle both base64 and raw array embedding formats
**Location**: `lightrag/llm/openai.py` - `openai_embed` function
**Issue**: Custom OpenAI-compatible endpoints return embeddings as raw arrays, not base64 strings
**Solution**:
```python
np.array(dp.embedding, dtype=np.float32) if isinstance(dp.embedding, list)
else np.frombuffer(base64.b64decode(dp.embedding), dtype=np.float32)
```
**Impact**: Document processing fails completely without this dual format support
### 2. Async Pattern Consistency (CRITICAL)
**Pattern**: Always await coroutines before calling methods on the result
**Common Error**: `coroutine.method()` instead of `(await coroutine).method()`
**Locations**: MongoDB implementations, Neo4j operations
**Example**: `await self._data.list_indexes()` then `await cursor.to_list()`
### 3. Storage Layer Data Compatibility (CRITICAL)
**Pattern**: Always filter deprecated/incompatible fields during deserialization
**Common Fields to Remove**: `content`, `_id` (MongoDB), database-specific fields
**Implementation**: `data.pop('field_name', None)` before creating dataclass objects
**Locations**: All storage implementations (JSON, Redis, MongoDB, PostgreSQL)
### 4. Lock Key Generation (CRITICAL)
**Pattern**: Always sort relationship pairs for consistent lock keys
**Implementation**: `sorted_key_parts = sorted([src, tgt])` then `f"{sorted_key_parts[0]}-{sorted_key_parts[1]}"`
**Impact**: Prevents deadlocks in concurrent relationship processing
### 5. Event Loop Management (CRITICAL)
**Pattern**: Handle event loop mismatches during shutdown gracefully
**Implementation**: Timeout + specific RuntimeError handling for "attached to a different loop"
**Location**: Neo4j storage finalization
**Impact**: Prevents application shutdown failures
### 6. Async Generator Lock Management (CRITICAL)
**Pattern**: Never hold locks across async generator yields - create snapshots instead
**Issue**: Holding locks while yielding causes deadlock when consumers need the same lock
**Location**: `lightrag/tools/migrate_llm_cache.py` - `stream_default_caches_json`
**Solution**: Create snapshot of data while holding lock, release lock, then iterate over snapshot
```python
# WRONG - Deadlock prone:
async with storage._storage_lock:
for key, value in storage._data.items():
batch[key] = value
if len(batch) >= batch_size:
yield batch # Lock still held!
# CORRECT - Snapshot approach:
async with storage._storage_lock:
matching_items = [(k, v) for k, v in storage._data.items() if condition]
# Lock released here
for key, value in matching_items:
batch[key] = value
if len(batch) >= batch_size:
yield batch # No lock held
```
**Impact**: Prevents deadlocks in Json→Json migrations and similar scenarios where source/target share locks
**Applicable To**: Any async generator that needs to access shared resources while yielding
## Architecture Patterns
### 1. Dependency Injection
**Pattern**: Pass configuration through object constructors, not direct imports
**Example**: OllamaAPI receives configuration through LightRAG object
**Benefit**: Better testability and modularity
### 2. Memory Bank Documentation
**Pattern**: Maintain comprehensive memory bank for development continuity
**Structure**: Core files (projectbrief.md, activeContext.md, progress.md, etc.)
**Purpose**: Essential for context preservation across development sessions
### 3. Configuration Management
**Pattern**: Centralize defaults in constants.py, use environment variables for runtime config
**Implementation**: Default values in constants, override via .env file
**Benefit**: Consistent configuration across components
## Development Workflow Patterns
### 1. Frontend Development (CRITICAL)
**Package Manager**: **ALWAYS USE BUN** - Never use npm or yarn unless Bun is unavailable
**Commands**:
- `bun install` - Install dependencies
- `bun run dev` - Start development server
- `bun run build` - Build for production
- `bun run lint` - Run linting
- `bun test` - Run tests
- `bun run preview` - Preview production build
**Pattern**: All frontend operations must use Bun commands
**Fallback**: Only use npm/yarn if Bun installation fails
**Testing**: Use `bun test` for all frontend testing
### 2. Bug Fix Approach
1. **Identify root cause** - Don't just fix symptoms
2. **Implement robust solution** - Handle edge cases and format variations
3. **Maintain backward compatibility** - Preserve existing functionality
4. **Add comprehensive error handling** - Graceful degradation
5. **Document the fix** - Update memory bank with technical details
### 3. Feature Implementation
1. **Follow existing patterns** - Maintain architectural consistency
2. **Use dependency injection** - Avoid direct imports between modules
3. **Implement comprehensive error handling** - Handle all failure modes
4. **Add proper logging** - Debug and warning messages
5. **Update documentation** - Memory bank and code comments
6. **Comment Language** - Use English for comments and documentation
### 4. Performance Optimization
1. **Profile before optimizing** - Identify actual bottlenecks
2. **Maintain algorithmic correctness** - Don't sacrifice functionality for speed
3. **Use appropriate data structures** - Match structure to access patterns
4. **Implement caching strategically** - Cache expensive operations
5. **Monitor memory usage** - Prevent memory leaks
## Technology Stack Intelligence
### 1. LLM Integration
- **Primary**: Gemini 2.5 Flash via custom endpoint
- **Embedding**: BAAI/bge-m3 via custom endpoint
- **Reranking**: BAAI/bge-reranker-v2-m3
- **Pattern**: Always handle multiple provider formats
### 2. Storage Backends
- **Default**: In-memory with file persistence
- **Production Options**: PostgreSQL, MongoDB, Redis, Neo4j
- **Pattern**: Abstract storage interface with multiple implementations
### 3. API Architecture
- **Framework**: FastAPI with Gunicorn for production
- **Authentication**: JWT-based with role support
- **Compatibility**: Ollama-compatible endpoints for easy integration
### 4. Frontend
- **Framework**: React with TypeScript
- **Package Manager**: **BUN (REQUIRED)** - Always use Bun for all frontend operations
- **Build Tool**: Vite with Bun runtime
- **Visualization**: Sigma.js for graph rendering
- **State Management**: React hooks with context
- **Internationalization**: i18next for multi-language support
## Common Pitfalls and Solutions
### 1. Embedding Format Issues
**Pitfall**: Assuming all endpoints return base64-encoded embeddings
**Solution**: Always check format and handle both base64 and raw arrays
### 2. Async/Await Patterns
**Pitfall**: Calling methods on coroutines instead of awaited results
**Solution**: Always await coroutines before accessing their methods
### 3. Data Model Evolution
**Pitfall**: Breaking changes when removing fields from dataclasses
**Solution**: Filter deprecated fields during deserialization, don't break storage
### 4. Concurrency Issues
**Pitfall**: Inconsistent lock key generation causing deadlocks
**Solution**: Always sort keys for deterministic lock ordering
### 5. Event Loop Management
**Pitfall**: Event loop mismatches during shutdown
**Solution**: Implement timeout and specific error handling for loop issues
## Performance Considerations
### 1. Query Context Building
- **Algorithm**: Linear gradient weighted polling for fair resource allocation
- **Optimization**: Round-robin merging to eliminate mode bias
- **Pattern**: Smart chunk selection based on cross-entity occurrence
### 2. Graph Operations
- **Optimization**: Batch operations where possible
- **Pattern**: Use appropriate indexing for large datasets
- **Consideration**: Memory usage with large graphs
### 3. LLM Request Management
- **Pattern**: Priority-based queue for request ordering
- **Optimization**: Connection pooling and retry mechanisms
- **Consideration**: Rate limiting and cost management
## Security Patterns
### 1. Authentication
- **Implementation**: JWT tokens with role-based access
- **Pattern**: Stateless authentication with configurable expiration
- **Security**: Proper token validation and refresh mechanisms
### 2. API Security
- **Pattern**: Input validation and sanitization
- **Implementation**: FastAPI dependency injection for auth
- **Consideration**: Rate limiting and abuse prevention
## Maintenance Guidelines
### 1. Memory Bank Updates
- **Trigger**: After significant changes or bug fixes
- **Pattern**: Update activeContext.md and progress.md
- **Purpose**: Maintain development continuity
### 2. Configuration Management
- **Pattern**: Environment-based configuration with sensible defaults
- **Implementation**: .env files with example templates
- **Consideration**: Security for production deployments
### 3. Error Handling
- **Pattern**: Comprehensive logging with appropriate levels
- **Implementation**: Graceful degradation where possible
- **Consideration**: User-friendly error messages
## Project Evolution Notes
The project has evolved from experimental to production-ready status. Key milestones:
- **Early 2025**: Basic RAG implementation
- **Mid 2025**: Multiple storage backends and LLM providers
- **July 2025**: Major query optimization and algorithm improvements
- **August 2025**: Production-ready stable state
The system now supports enterprise-level deployments with comprehensive functionality across all components.
-69
View File
@@ -1,69 +0,0 @@
# Python-related files and directories
__pycache__
.cache
# Virtual environment directories
*.venv
# Env
env/
*.env*
.env_example
# Distribution / build files
site
dist/
build/
.eggs/
*.egg-info/
*.tgz
*.tar.gz
# Exclude siles and folders
*.yml
.dockerignore
Dockerfile
Makefile
# Exclude other projects
/tests
/scripts
/data
/dickens
/reproduce
/output_complete
/rag_storage
/inputs
# Python version manager file
.python-version
# Reports
*.coverage/
*.log
log/
*.logfire
# Cache
.cache/
.mypy_cache
.pytest_cache
.ruff_cache
.gradio
.logfire
temp/
# MacOS-related files
.DS_Store
# VS Code settings (local configuration files)
.vscode
# file
TODO.md
# Exclude Git-related files
.git
.github
.gitignore
.pre-commit-config.yaml
-2
View File
@@ -1,2 +0,0 @@
lightrag/api/webui/** binary
lightrag/api/webui/** linguist-generated
-61
View File
@@ -1,61 +0,0 @@
name: Bug Report
description: File a bug report
title: "[Bug]:"
labels: ["bug", "triage"]
body:
- type: checkboxes
id: existingcheck
attributes:
label: Do you need to file an issue?
description: Please help us manage our time by avoiding duplicates and common bugs with the steps below.
options:
- label: I have searched the existing issues and this bug is not already filed.
- label: I believe this is a legitimate bug, not just a question or feature request.
- type: textarea
id: description
attributes:
label: Describe the bug
description: A clear and concise description of what the bug is.
placeholder: What went wrong?
- type: textarea
id: reproduce
attributes:
label: Steps to reproduce
description: Steps to reproduce the behavior.
placeholder: How can we replicate the issue?
- type: textarea
id: expected_behavior
attributes:
label: Expected Behavior
description: A clear and concise description of what you expected to happen.
placeholder: What should have happened?
- type: textarea
id: configused
attributes:
label: LightRAG Config Used
description: The LightRAG configuration used for the run.
placeholder: The settings content or LightRAG configuration
value: |
# Paste your config here
- type: textarea
id: screenshotslogs
attributes:
label: Logs and screenshots
description: If applicable, add screenshots and logs to help explain your problem.
placeholder: Add logs and screenshots here
- type: textarea
id: additional_information
attributes:
label: Additional Information
description: |
- LightRAG Version: e.g., v0.1.1
- Operating System: e.g., Windows 10, Ubuntu 20.04
- Python Version: e.g., 3.8
- Related Issues: e.g., #1
- Any other relevant information.
value: |
- LightRAG Version:
- Operating System:
- Python Version:
- Related Issues:
-1
View File
@@ -1 +0,0 @@
blank_issues_enabled: false
-26
View File
@@ -1,26 +0,0 @@
name: Feature Request
description: File a feature request
labels: ["enhancement"]
title: "[Feature Request]:"
body:
- type: checkboxes
id: existingcheck
attributes:
label: Do you need to file a feature request?
description: Please help us manage our time by avoiding duplicates and common feature request with the steps below.
options:
- label: I have searched the existing feature request and this feature request is not already filed.
- label: I believe this is a legitimate feature request, not just a question or bug.
- type: textarea
id: feature_request_description
attributes:
label: Feature Request Description
description: A clear and concise description of the feature request you would like.
placeholder: What this feature request add more or improve?
- type: textarea
id: additional_context
attributes:
label: Additional Context
description: Add any other context or screenshots about the feature request here.
placeholder: Any additional information
-26
View File
@@ -1,26 +0,0 @@
name: Question
description: Ask a general question
labels: ["question"]
title: "[Question]:"
body:
- type: checkboxes
id: existingcheck
attributes:
label: Do you need to ask a question?
description: Please help us manage our time by avoiding duplicates and common questions with the steps below.
options:
- label: I have searched the existing question and discussions and this question is not already answered.
- label: I believe this is a legitimate question, not just a bug or feature request.
- type: textarea
id: question
attributes:
label: Your Question
description: A clear and concise description of your question.
placeholder: What is your question?
- type: textarea
id: context
attributes:
label: Additional Context
description: Provide any additional context or details that might help us understand your question better.
placeholder: Add any relevant information here
-11
View File
@@ -1,11 +0,0 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
version: 2
updates:
- package-ecosystem: "pip" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "weekly"
-32
View File
@@ -1,32 +0,0 @@
<!--
Thanks for contributing to LightRAG!
Please ensure your pull request is ready for review before submitting.
About this template
This template helps contributors provide a clear and concise description of their changes. Feel free to adjust it as needed.
-->
## Description
[Briefly describe the changes made in this pull request.]
## Related Issues
[Reference any related issues or tasks addressed by this pull request.]
## Changes Made
[List the specific changes made in this pull request.]
## Checklist
- [ ] Changes tested locally
- [ ] Code reviewed
- [ ] Documentation updated (if necessary)
- [ ] Unit tests added (if applicable)
## Additional Notes
[Add any additional notes or context for the reviewer(s).]
-84
View File
@@ -1,84 +0,0 @@
name: Build Lite Docker Image
on:
workflow_dispatch:
inputs:
_notes_:
description: '⚠️ Create lite Docker images only after non-trivial version releases.'
required: false
type: boolean
default: false
permissions:
contents: read
packages: write
jobs:
build-and-push-lite:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Get latest tag
id: get_tag
run: |
LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
if [ -z "$LATEST_TAG" ]; then
LATEST_TAG="sha-$(git rev-parse --short HEAD)"
echo "No tags found, using commit SHA: $LATEST_TAG"
else
echo "Latest tag found: $LATEST_TAG"
fi
echo "tag=$LATEST_TAG" >> $GITHUB_OUTPUT
- name: Prepare lite tag
id: lite_tag
run: |
LITE_TAG="${{ steps.get_tag.outputs.tag }}-lite"
echo "Lite image tag: $LITE_TAG"
echo "lite_tag=$LITE_TAG" >> $GITHUB_OUTPUT
- name: Update version in __init__.py
run: |
sed -i "s/__version__ = \".*\"/__version__ = \"${{ steps.get_tag.outputs.tag }}\"/" lightrag/__init__.py
cat lightrag/__init__.py | grep __version__
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata for Docker
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository }}
tags: |
type=raw,value=${{ steps.lite_tag.outputs.lite_tag }}
type=raw,value=lite
- name: Build and push lite Docker image
uses: docker/build-push-action@v5
with:
context: .
file: ./Dockerfile.lite
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=min
- name: Output image details
run: |
echo "Lite Docker image built and pushed successfully!"
echo "Image tag: ghcr.io/${{ github.repository }}:${{ steps.lite_tag.outputs.lite_tag }}"
echo "Base Git tag used: ${{ steps.get_tag.outputs.tag }}"
-80
View File
@@ -1,80 +0,0 @@
name: Build Test Docker Image manually
on:
workflow_dispatch:
inputs:
_notes_:
description: '⚠️ Please create a new git tag before building the docker image.'
required: false
type: boolean
default: false
permissions:
contents: read
packages: write
jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0 # Fetch all history for tags
- name: Get latest tag
id: get_tag
run: |
# Get the latest tag, fallback to commit SHA if no tags exist
LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
if [ -z "$LATEST_TAG" ]; then
LATEST_TAG="sha-$(git rev-parse --short HEAD)"
echo "No tags found, using commit SHA: $LATEST_TAG"
else
echo "Latest tag found: $LATEST_TAG"
fi
echo "tag=$LATEST_TAG" >> $GITHUB_OUTPUT
echo "image_tag=$LATEST_TAG" >> $GITHUB_OUTPUT
- name: Update version in __init__.py
run: |
sed -i "s/__version__ = \".*\"/__version__ = \"${{ steps.get_tag.outputs.tag }}\"/" lightrag/__init__.py
echo "Updated __init__.py with version ${{ steps.get_tag.outputs.tag }}"
cat lightrag/__init__.py | grep __version__
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata for Docker
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository }}
tags: |
type=raw,value=${{ steps.get_tag.outputs.tag }}
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
file: ./Dockerfile
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Output image details
run: |
echo "Docker image built and pushed successfully!"
echo "Image tags:"
echo " - ghcr.io/${{ github.repository }}:${{ steps.get_tag.outputs.tag }}"
echo "Latest Git tag used: ${{ steps.get_tag.outputs.tag }}"
-75
View File
@@ -1,75 +0,0 @@
name: Build Latest Docker Image on Release
on:
release:
types: [published]
workflow_dispatch:
permissions:
contents: read
packages: write
jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0 # Fetch all history for tags
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Get latest tag
id: get_tag
run: |
TAG=$(git describe --tags --abbrev=0)
echo "Found tag: $TAG"
echo "tag=$TAG" >> $GITHUB_OUTPUT
- name: Check if pre-release
id: check_prerelease
run: |
TAG="${{ steps.get_tag.outputs.tag }}"
if [[ "$TAG" == *"rc"* ]] || [[ "$TAG" == *"dev"* ]]; then
echo "is_prerelease=true" >> $GITHUB_OUTPUT
echo "This is a pre-release version: $TAG"
else
echo "is_prerelease=false" >> $GITHUB_OUTPUT
echo "This is a stable release: $TAG"
fi
- name: Update version in __init__.py
run: |
sed -i "s/__version__ = \".*\"/__version__ = \"${{ steps.get_tag.outputs.tag }}\"/" lightrag/__init__.py
echo "Updated __init__.py with version ${{ steps.get_tag.outputs.tag }}"
cat lightrag/__init__.py | grep __version__
- name: Extract metadata for Docker
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository }}
tags: |
type=raw,value=${{ steps.get_tag.outputs.tag }}
type=raw,value=latest,enable=${{ steps.check_prerelease.outputs.is_prerelease == 'false' }}
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
file: ./Dockerfile
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
-30
View File
@@ -1,30 +0,0 @@
name: Linting and Formatting
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
lint-and-format:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.x'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install pre-commit
- name: Run pre-commit
run: pre-commit run --all-files --show-diff-on-failure
-92
View File
@@ -1,92 +0,0 @@
name: Upload LightRAG-hku Package
on:
release:
types: [published]
workflow_dispatch:
permissions:
contents: read
jobs:
release-build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Fetch all history for tags
# Build frontend WebUI
- name: Setup Bun
uses: oven-sh/setup-bun@v1
with:
bun-version: latest
- name: Build Frontend WebUI
run: |
cd lightrag_webui
bun install --frozen-lockfile
bun run build
cd ..
- name: Verify Frontend Build
run: |
if [ ! -f "lightrag/api/webui/index.html" ]; then
echo "❌ Error: Frontend build failed - index.html not found"
exit 1
fi
echo "✅ Frontend build verified"
echo "Frontend files:"
ls -lh lightrag/api/webui/ | head -10
- uses: actions/setup-python@v5
with:
python-version: "3.x"
- name: Get version from tag
id: get_version
run: |
TAG=$(git describe --tags --abbrev=0)
echo "Found tag: $TAG"
echo "Extracted version: $TAG"
echo "version=$TAG" >> $GITHUB_OUTPUT
- name: Update version in __init__.py
run: |
sed -i "s/__version__ = \".*\"/__version__ = \"${{ steps.get_version.outputs.version }}\"/" lightrag/__init__.py
echo "Updated __init__.py with version ${{ steps.get_version.outputs.version }}"
cat lightrag/__init__.py | grep __version__
- name: Build release distributions
run: |
python -m pip install build
python -m build
- name: Upload distributions
uses: actions/upload-artifact@v4
with:
name: release-dists
path: dist/
pypi-publish:
runs-on: ubuntu-latest
needs:
- release-build
permissions:
id-token: write
environment:
name: pypi
steps:
- name: Retrieve release distributions
uses: actions/download-artifact@v4
with:
name: release-dists
path: dist/
- name: Publish release distributions to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
packages-dir: dist/
-27
View File
@@ -1,27 +0,0 @@
# .github/workflows/stale.yml
name: Mark stale issues and pull requests
on:
schedule:
- cron: '30 22 * * *' # run at 22:30+08 every day
permissions:
issues: write
pull-requests: write
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v9
with:
days-before-stale: 90 # 90 days
days-before-close: 7 # 7 days after marked as stale
stale-issue-message: 'This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.'
close-issue-message: 'This issue has been automatically closed because it has not had recent activity. Please open a new issue if you still have this problem.'
stale-pr-message: 'This pull request has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs.'
close-pr-message: 'This pull request has been automatically closed because it has not had recent activity.'
# If there are specific labels, exempt them from being marked as stale, for example:
exempt-issue-labels: 'enhancement,tracked'
# exempt-pr-labels: 'bug,enhancement,help wanted'
repo-token: ${{ secrets.GITHUB_TOKEN }} # token provided by GitHub
-74
View File
@@ -1,74 +0,0 @@
# Python-related files
__pycache__/
*.py[cod]
*.egg-info/
.eggs/
*.tgz
*.tar.gz
*.ini
# Virtual Environment
.venv/
venv/
# Enviroment Variable Files
.env
# Build / Distribution
dist/
build/
site/
# Logs / Reports
*.log
*.log.*
*.logfire
*.coverage/
log/
# Caches
.cache/
.mypy_cache/
.pytest_cache/
.ruff_cache/
.gradio/
.history/
temp/
# IDE / Editor Files
.idea/
.vscode/
.vscode/settings.json
# Framework-specific files
local_neo4jWorkDir/
neo4jWorkDir/
# Data & Storage
inputs/
output/
rag_storage/
data/
# Evaluation results
lightrag/evaluation/results/
# Miscellaneous
.DS_Store
TODO.md
ignore_this.txt
*.ignore.*
# Project-specific files
/dickens*/
/book.txt
download_models_hf.py
# Frontend build output (built during PyPI release)
/lightrag/api/webui/
# temporary test files in project root
/test_*
# Cline files
memory-bank
-28
View File
@@ -1,28 +0,0 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: trailing-whitespace
exclude: ^lightrag/api/webui/
- id: end-of-file-fixer
exclude: ^lightrag/api/webui/
- id: requirements-txt-fixer
exclude: ^lightrag/api/webui/
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.6.4
hooks:
- id: ruff-format
exclude: ^lightrag/api/webui/
- id: ruff
args: [--fix, --ignore=E402]
exclude: ^lightrag/api/webui/
- repo: https://github.com/mgedmin/check-manifest
rev: "0.49"
hooks:
- id: check-manifest
stages: [manual]
exclude: ^lightrag/api/webui/
-39
View File
@@ -1,39 +0,0 @@
# Repository Guidelines
LightRAG is an advanced Retrieval-Augmented Generation (RAG) framework designed to enhance information retrieval and generation through graph-based knowledge representation.
## Project Structure & Module Organization
- `lightrag/`: Core Python package with orchestrators (`lightrag/lightrag.py`), storage adapters in `kg/`, LLM bindings in `llm/`, and helpers such as `operate.py` and `utils_*.py`.
- `lightrag-api/`: FastAPI service (`lightrag_server.py`) with routers under `routers/` and Gunicorn launcher `run_with_gunicorn.py`.
- `lightrag_webui/`: React 19 + TypeScript client driven by Bun + Vite; UI components live in `src/`.
- Tests live in `tests/` and root-level `test_*.py`. Working datasets stay in `inputs/`, `rag_storage/`, `temp/`; deployment collateral lives in `docs/`, `k8s-deploy/`, and `docker-compose.yml`.
## Build, Test, and Development Commands
- `python -m venv .venv && source .venv/bin/activate`: set up the Python runtime.
- `pip install -e .` / `pip install -e .[api]`: install the package and API extras in editable mode.
- `lightrag-server` or `uvicorn lightrag.api.lightrag_server:app --reload`: start the API locally; ensure `.env` is present.
- `python -m pytest tests` or `python test_graph_storage.py`: run the full suite or a targeted script.
- `ruff check .`: lint Python sources before committing.
- `bun install`, `bun run dev`, `bun run build`, `bun test`: manage the web UI workflow (Bun is mandatory).
## Coding Style & Naming Conventions
- Backend code follow PEP 8 with four-space indentation, annotate functions, and reach for dataclasses when modelling state.
- Use `lightrag.utils.logger` instead of `print`; respect logger configuration flags.
- Extend storage or pipeline abstractions via `lightrag.base` and keep reusable helpers in the existing `utils_*.py`.
- Python modules remain lowercase with underscores; React components use `PascalCase.tsx` and hooks-first patterns.
- Front-end code should remain in TypeScript with two-space indentation, rely on functional React components with hooks, and follow Tailwind utility style.
## Testing Guidelines
- Add pytest cases beside the affected module or the relevant `test_*.py`; functions should start with `test_`.
- Export required `LIGHTRAG_*` environment variables before running integration or storage tests.
- For UI updates, pair code with Vitest specs and run `bun test`.
## Commit & Pull Request Guidelines
- Use concise, imperative commit subjects (e.g., `Fix lock key normalization`) and add body context only when necessary.
- PRs should include a summary, operational impact, linked issues, and screenshots or API samples for user-facing work.
- Verify `ruff check .`, `python -m pytest`, and affected Bun commands succeed before requesting review; note the runs in the PR text.
## Security & Configuration Tips
- Copy `.env.example` and `config.ini.example`; never commit secrets or real connection strings.
- Configure storage backends through `LIGHTRAG_*` variables and validate them with `docker-compose` services when needed.
- Treat `lightrag.log*` as local artefacts; purge sensitive information before sharing logs or outputs.
-107
View File
@@ -1,107 +0,0 @@
# syntax=docker/dockerfile:1
# Frontend build stage
FROM oven/bun:1 AS frontend-builder
WORKDIR /app
# Copy frontend source code
COPY lightrag_webui/ ./lightrag_webui/
# Build frontend assets for inclusion in the API package
RUN --mount=type=cache,target=/root/.bun/install/cache \
cd lightrag_webui \
&& bun install --frozen-lockfile \
&& bun run build
# Python build stage - using uv for faster package installation
FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim AS builder
ENV DEBIAN_FRONTEND=noninteractive
ENV UV_SYSTEM_PYTHON=1
ENV UV_COMPILE_BYTECODE=1
WORKDIR /app
# Install system deps (Rust is required by some wheels)
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
curl \
build-essential \
pkg-config \
&& rm -rf /var/lib/apt/lists/* \
&& curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
ENV PATH="/root/.cargo/bin:/root/.local/bin:${PATH}"
# Ensure shared data directory exists for uv caches
RUN mkdir -p /root/.local/share/uv
# Copy project metadata and sources
COPY pyproject.toml .
COPY setup.py .
COPY uv.lock .
# Install base, API, and offline extras without the project to improve caching
RUN --mount=type=cache,target=/root/.local/share/uv \
uv sync --frozen --no-dev --extra api --extra offline --no-install-project --no-editable
# Copy project sources after dependency layer
COPY lightrag/ ./lightrag/
# Include pre-built frontend assets from the previous stage
COPY --from=frontend-builder /app/lightrag/api/webui ./lightrag/api/webui
# Sync project in non-editable mode and ensure pip is available for runtime installs
RUN --mount=type=cache,target=/root/.local/share/uv \
uv sync --frozen --no-dev --extra api --extra offline --no-editable \
&& /app/.venv/bin/python -m ensurepip --upgrade
# Prepare offline cache directory and pre-populate tiktoken data
# Use uv run to execute commands from the virtual environment
RUN mkdir -p /app/data/tiktoken \
&& uv run lightrag-download-cache --cache-dir /app/data/tiktoken || status=$?; \
if [ -n "${status:-}" ] && [ "$status" -ne 0 ] && [ "$status" -ne 2 ]; then exit "$status"; fi
# Final stage
FROM python:3.12-slim
WORKDIR /app
# Install uv for package management
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
ENV UV_SYSTEM_PYTHON=1
# Copy installed packages and application code
COPY --from=builder /root/.local /root/.local
COPY --from=builder /app/.venv /app/.venv
COPY --from=builder /app/lightrag ./lightrag
COPY pyproject.toml .
COPY setup.py .
COPY uv.lock .
# Ensure the installed scripts are on PATH
ENV PATH=/app/.venv/bin:/root/.local/bin:$PATH
# Install dependencies with uv sync (uses locked versions from uv.lock)
# And ensure pip is available for runtime installs
RUN --mount=type=cache,target=/root/.local/share/uv \
uv sync --frozen --no-dev --extra api --extra offline --no-editable \
&& /app/.venv/bin/python -m ensurepip --upgrade
# Create persistent data directories AFTER package installation
RUN mkdir -p /app/data/rag_storage /app/data/inputs /app/data/tiktoken
# Copy offline cache into the newly created directory
COPY --from=builder /app/data/tiktoken /app/data/tiktoken
# Point to the prepared cache
ENV TIKTOKEN_CACHE_DIR=/app/data/tiktoken
ENV WORKING_DIR=/app/data/rag_storage
ENV INPUT_DIR=/app/data/inputs
# Expose API port
EXPOSE 9621
ENTRYPOINT ["python", "-m", "lightrag.api.lightrag_server"]
-108
View File
@@ -1,108 +0,0 @@
# syntax=docker/dockerfile:1
# Frontend build stage
FROM oven/bun:1 AS frontend-builder
WORKDIR /app
# Copy frontend source code
COPY lightrag_webui/ ./lightrag_webui/
# Build frontend assets for inclusion in the API package
RUN --mount=type=cache,target=/root/.bun/install/cache \
cd lightrag_webui \
&& bun install --frozen-lockfile \
&& bun run build
# Python build stage - using uv for package installation
FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim AS builder
ENV DEBIAN_FRONTEND=noninteractive
ENV UV_SYSTEM_PYTHON=1
ENV UV_COMPILE_BYTECODE=1
WORKDIR /app
# Install system dependencies required by some wheels
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
curl \
build-essential \
pkg-config \
&& rm -rf /var/lib/apt/lists/* \
&& curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
ENV PATH="/root/.cargo/bin:/root/.local/bin:${PATH}"
# Ensure shared data directory exists for uv caches
RUN mkdir -p /root/.local/share/uv
# Copy project metadata and sources
COPY pyproject.toml .
COPY setup.py .
COPY uv.lock .
# Install project dependencies (base + API extras) without the project to improve caching
RUN --mount=type=cache,target=/root/.local/share/uv \
uv sync --frozen --no-dev --extra api --no-install-project --no-editable
# Copy project sources after dependency layer
COPY lightrag/ ./lightrag/
# Include pre-built frontend assets from the previous stage
COPY --from=frontend-builder /app/lightrag/api/webui ./lightrag/api/webui
# Sync project in non-editable mode and ensure pip is available for runtime installs
RUN --mount=type=cache,target=/root/.local/share/uv \
uv sync --frozen --no-dev --extra api --no-editable \
&& /app/.venv/bin/python -m ensurepip --upgrade
# Prepare tiktoken cache directory and pre-populate tokenizer data
# Ignore exit code 2 which indicates assets already cached
RUN mkdir -p /app/data/tiktoken \
&& uv run lightrag-download-cache --cache-dir /app/data/tiktoken || status=$?; \
if [ -n "${status:-}" ] && [ "$status" -ne 0 ] && [ "$status" -ne 2 ]; then exit "$status"; fi
# Final stage
FROM python:3.12-slim
WORKDIR /app
# Install uv for package management
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
ENV UV_SYSTEM_PYTHON=1
# Copy installed packages and application code
COPY --from=builder /root/.local /root/.local
COPY --from=builder /app/.venv /app/.venv
COPY --from=builder /app/lightrag ./lightrag
COPY pyproject.toml .
COPY setup.py .
COPY uv.lock .
# Ensure the installed scripts are on PATH
ENV PATH=/app/.venv/bin:/root/.local/bin:$PATH
# Sync dependencies inside the final image using uv
# And ensure pip is available for runtime installs
RUN --mount=type=cache,target=/root/.local/share/uv \
uv sync --frozen --no-dev --extra api --no-editable \
&& /app/.venv/bin/python -m ensurepip --upgrade
# Create persistent data directories
RUN mkdir -p /app/data/rag_storage /app/data/inputs /app/data/tiktoken
# Copy cached tokenizer assets prepared in the builder stage
COPY --from=builder /app/data/tiktoken /app/data/tiktoken
# Docker data directories
ENV TIKTOKEN_CACHE_DIR=/app/data/tiktoken
ENV WORKING_DIR=/app/data/rag_storage
ENV INPUT_DIR=/app/data/inputs
# Expose API port
EXPOSE 9621
# Set entrypoint
ENTRYPOINT ["python", "-m", "lightrag.api.lightrag_server"]
-21
View File
@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2025 LightRAG Team
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
-4
View File
@@ -1,4 +0,0 @@
include requirements.txt
include lightrag/api/requirements.txt
recursive-include lightrag/api/webui *
recursive-include lightrag/api/static *
File diff suppressed because it is too large Load Diff
Binary file not shown.

Before

Width:  |  Height:  |  Size: 471 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 401 KiB

-1978
View File
File diff suppressed because it is too large Load Diff
-18
View File
@@ -1,18 +0,0 @@
# Reporting Security Issues
The LightRAG team and community take security bugs seriously. We appreciate your efforts to responsibly disclose your findings, and will make every effort to acknowledge your contributions.
To report a security issue, please use the GitHub Security Advisory: [Report a Vulnerability](https://github.com/HKUDS/LightRAG/security/advisories/new)
The LightRAG team will send a response indicating the next steps in handling your report. After the initial reply to your report, the security team will keep you informed of the progress towards a fix and full announcement, and may ask for additional information or guidance.
Report security bugs in third-party modules to the person or team maintaining the module.
### Supported Versions
The following versions currently being supported with security updates.
| Version | Supported |
| ------- | ------------------ |
| 1.2.x | :x: |
| 1.3.x | :white_check_mark: |
Binary file not shown.

Before

Width:  |  Height:  |  Size: 179 KiB

-37
View File
@@ -1,37 +0,0 @@
[neo4j]
uri = neo4j+s://xxxxxxxx.databases.neo4j.io
username = neo4j
password = your-password
connection_pool_size = 100
connection_timeout = 30.0
connection_acquisition_timeout = 30.0
max_transaction_retry_time = 30.0
max_connection_lifetime = 300.0
liveness_check_timeout = 30.0
keep_alive = true
[mongodb]
uri = mongodb+srv://name:password@your-cluster-address
database = lightrag
[redis]
uri=redis://localhost:6379/1
[qdrant]
uri = http://localhost:16333
[postgres]
host = localhost
port = 5432
user = your_username
password = your_password
database = your_database
# workspace = default
max_connections = 12
vector_index_type = HNSW # HNSW or IVFFLAT
hnsw_m = 16
hnsw_ef = 64
ivfflat_lists = 100
[memgraph]
uri = bolt://localhost:7687
-77
View File
@@ -1,77 +0,0 @@
#!/bin/bash
set -e
# Configuration
IMAGE_NAME="ghcr.io/hkuds/lightrag"
DOCKERFILE="Dockerfile"
TAG="latest"
# Get version from git tags
VERSION=$(git describe --tags --abbrev=0 2>/dev/null || echo "dev")
echo "=================================="
echo " Multi-Architecture Docker Build"
echo "=================================="
echo "Image: ${IMAGE_NAME}:${TAG}"
echo "Version: ${VERSION}"
echo "Platforms: linux/amd64, linux/arm64"
echo "=================================="
echo ""
# Check Docker login status (skip if CR_PAT is set for CI/CD)
if [ -z "$CR_PAT" ]; then
if ! docker info 2>/dev/null | grep -q "Username"; then
echo "⚠️ Warning: Not logged in to Docker registry"
echo "Please login first: docker login ghcr.io"
echo "Or set CR_PAT environment variable for automated login"
echo ""
read -p "Continue anyway? (y/n) " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
exit 1
fi
fi
else
echo "Using CR_PAT environment variable for authentication"
fi
# Check if buildx builder exists, create if not
if ! docker buildx ls | grep -q "desktop-linux"; then
echo "Creating buildx builder..."
docker buildx create --name desktop-linux --use
docker buildx inspect --bootstrap
else
echo "Using existing buildx builder: desktop-linux"
docker buildx use desktop-linux
fi
echo ""
echo "Building and pushing multi-architecture image..."
echo ""
# Build and push
docker buildx build \
--platform linux/amd64,linux/arm64 \
--file ${DOCKERFILE} \
--tag ${IMAGE_NAME}:${TAG} \
--tag ${IMAGE_NAME}:${VERSION} \
--push \
.
echo ""
echo "✓ Build and push complete!"
echo ""
echo "Images pushed:"
echo " - ${IMAGE_NAME}:${TAG}"
echo " - ${IMAGE_NAME}:${VERSION}"
echo ""
echo "Verifying multi-architecture manifest..."
echo ""
# Verify
docker buildx imagetools inspect ${IMAGE_NAME}:${TAG}
echo ""
echo "✓ Verification complete!"
echo ""
echo "Pull with: docker pull ${IMAGE_NAME}:${TAG}"
-23
View File
@@ -1,23 +0,0 @@
services:
lightrag:
container_name: lightrag
image: ghcr.io/hkuds/lightrag:latest
build:
context: .
dockerfile: Dockerfile
tags:
- ghcr.io/hkuds/lightrag:latest
ports:
- "7777:7777"
volumes:
- ./data/rag_storage:/app/data/rag_storage
- ./data/inputs:/app/data/inputs
- ./config.ini:/app/config.ini
- ../.env.all:/app/.env:ro
env_file:
- ../.env.all
environment:
- POSTGRES_HOST=host.docker.internal
restart: unless-stopped
extra_hosts:
- "host.docker.internal:host-gateway"
-4
View File
@@ -1,4 +0,0 @@
![LightRAG Indexing Flowchart](https://learnopencv.com/wp-content/uploads/2024/11/LightRAG-VectorDB-Json-KV-Store-Indexing-Flowchart-scaled.jpg)
*Figure 1: LightRAG Indexing Flowchart - Img Caption : [Source](https://learnopencv.com/lightrag/)*
![LightRAG Retrieval and Querying Flowchart](https://learnopencv.com/wp-content/uploads/2024/11/LightRAG-Querying-Flowchart-Dual-Level-Retrieval-Generation-Knowledge-Graphs-scaled.jpg)
*Figure 2: LightRAG Retrieval and Querying Flowchart - Img Caption : [Source](https://learnopencv.com/lightrag/)*
-135
View File
@@ -1,135 +0,0 @@
# LightRAG Docker Deployment
A lightweight Knowledge Graph Retrieval-Augmented Generation system with multiple LLM backend support.
## 🚀 Preparation
### Clone the repository:
```bash
# Linux/MacOS
git clone https://github.com/HKUDS/LightRAG.git
cd LightRAG
```
```powershell
# Windows PowerShell
git clone https://github.com/HKUDS/LightRAG.git
cd LightRAG
```
### Configure your environment:
```bash
# Linux/MacOS
cp .env.example .env
# Edit .env with your preferred configuration
```
```powershell
# Windows PowerShell
Copy-Item .env.example .env
# Edit .env with your preferred configuration
```
LightRAG can be configured using environment variables in the `.env` file:
**Server Configuration**
- `HOST`: Server host (default: 0.0.0.0)
- `PORT`: Server port (default: 9621)
**LLM Configuration**
- `LLM_BINDING`: LLM backend to use (lollms/ollama/openai)
- `LLM_BINDING_HOST`: LLM server host URL
- `LLM_MODEL`: Model name to use
**Embedding Configuration**
- `EMBEDDING_BINDING`: Embedding backend (lollms/ollama/openai)
- `EMBEDDING_BINDING_HOST`: Embedding server host URL
- `EMBEDDING_MODEL`: Embedding model name
**RAG Configuration**
- `MAX_ASYNC`: Maximum async operations
- `MAX_TOKENS`: Maximum token size
- `EMBEDDING_DIM`: Embedding dimensions
## 🐳 Docker Deployment
Docker instructions work the same on all platforms with Docker Desktop installed.
### Build Optimization
The Dockerfile uses BuildKit cache mounts to significantly improve build performance:
- **Automatic cache management**: BuildKit is automatically enabled via `# syntax=docker/dockerfile:1` directive
- **Faster rebuilds**: Only downloads changed dependencies when `uv.lock` or `bun.lock` files are modified
- **Efficient package caching**: UV and Bun package downloads are cached across builds
- **No manual configuration needed**: Works out of the box in Docker Compose and GitHub Actions
### Start LightRAG server:
```bash
docker compose up -d
```
LightRAG Server uses the following paths for data storage:
```
data/
├── rag_storage/ # RAG data persistence
└── inputs/ # Input documents
```
### Updates
To update the Docker container:
```bash
docker compose pull
docker compose down
docker compose up
```
### Offline deployment
Software packages requiring `transformers`, `torch`, or `cuda` will is not preinstalled in the dokcer images. Consequently, document extraction tools such as Docling, as well as local LLM models like Hugging Face and LMDeploy, can not be used in an off line enviroment. These high-compute-resource-demanding services should not be integrated into LightRAG. Docling will be decoupled and deployed as a standalone service.
## 📦 Build Docker Images
### For local development and testing
```bash
# Build and run with Docker Compose (BuildKit automatically enabled)
docker compose up --build
# Or explicitly enable BuildKit if needed
DOCKER_BUILDKIT=1 docker compose up --build
```
**Note**: BuildKit is automatically enabled by the `# syntax=docker/dockerfile:1` directive in the Dockerfile, ensuring optimal caching performance.
### For production release
**multi-architecture build and push**:
```bash
# Use the provided build script
./docker-build-push.sh
```
**The build script will**:
- Check Docker registry login status
- Create/use buildx builder automatically
- Build for both AMD64 and ARM64 architectures
- Push to GitHub Container Registry (ghcr.io)
- Verify the multi-architecture manifest
**Prerequisites**:
Before building multi-architecture images, ensure you have:
- Docker 20.10+ with Buildx support
- Sufficient disk space (20GB+ recommended for offline image)
- Registry access credentials (if pushing images)
-207
View File
@@ -1,207 +0,0 @@
# Frontend Build Guide
## Overview
The LightRAG project includes a React-based WebUI frontend. This guide explains how frontend building works in different scenarios.
## Key Principle
- **Git Repository**: Frontend build results are **NOT** included (kept clean)
- **PyPI Package**: Frontend build results **ARE** included (ready to use)
- **Build Tool**: Uses **Bun** (not npm/yarn)
## Installation Scenarios
### 1. End Users (From PyPI) ✨
**Command:**
```bash
pip install lightrag-hku[api]
```
**What happens:**
- Frontend is already built and included in the package
- No additional steps needed
- Web interface works immediately
---
### 2. Development Mode (Recommended for Contributors) 🔧
**Command:**
```bash
# Clone the repository
git clone https://github.com/HKUDS/LightRAG.git
cd LightRAG
# Install in editable mode (no frontend build required yet)
pip install -e ".[api]"
# Build frontend when needed (can be done anytime)
cd lightrag_webui
bun install --frozen-lockfile
bun run build
cd ..
```
**Advantages:**
- Install first, build later (flexible workflow)
- Changes take effect immediately (symlink mode)
- Frontend can be rebuilt anytime without reinstalling
**How it works:**
- Creates symlinks to source directory
- Frontend build output goes to `lightrag/api/webui/`
- Changes are immediately visible in installed package
---
### 3. Normal Installation (Testing Package Build) 📦
**Command:**
```bash
# Clone the repository
git clone https://github.com/HKUDS/LightRAG.git
cd LightRAG
# ⚠️ MUST build frontend FIRST
cd lightrag_webui
bun install --frozen-lockfile
bun run build
cd ..
# Now install
pip install ".[api]"
```
**What happens:**
- Frontend files are **copied** to site-packages
- Post-build modifications won't affect installed package
- Requires rebuild + reinstall to update
**When to use:**
- Testing complete installation process
- Verifying package configuration
- Simulating PyPI user experience
---
### 4. Creating Distribution Package 🚀
**Command:**
```bash
# Build frontend first
cd lightrag_webui
bun install --frozen-lockfile --production
bun run build
cd ..
# Create distribution packages
python -m build
# Output: dist/lightrag_hku-*.whl and dist/lightrag_hku-*.tar.gz
```
**What happens:**
- `setup.py` checks if frontend is built
- If missing, installation fails with helpful error message
- Generated package includes all frontend files
---
## GitHub Actions (Automated Release)
When creating a release on GitHub:
1. **Automatically builds frontend** using Bun
2. **Verifies** build completed successfully
3. **Creates Python package** with frontend included
4. **Publishes to PyPI** using existing trusted publisher setup
**No manual intervention required!**
---
## Quick Reference
| Scenario | Command | Frontend Required | Can Build After |
|----------|---------|-------------------|-----------------|
| From PyPI | `pip install lightrag-hku[api]` | Included | No (already installed) |
| Development | `pip install -e ".[api]"` | No | ✅ Yes (anytime) |
| Normal Install | `pip install ".[api]"` | ✅ Yes (before) | No (must reinstall) |
| Create Package | `python -m build` | ✅ Yes (before) | N/A |
---
## Bun Installation
If you don't have Bun installed:
```bash
# macOS/Linux
curl -fsSL https://bun.sh/install | bash
# Windows
powershell -c "irm bun.sh/install.ps1 | iex"
```
Official documentation: https://bun.sh
---
## File Structure
```
LightRAG/
├── lightrag_webui/ # Frontend source code
│ ├── src/ # React components
│ ├── package.json # Dependencies
│ └── vite.config.ts # Build configuration
│ └── outDir: ../lightrag/api/webui # Build output
├── lightrag/
│ └── api/
│ └── webui/ # Frontend build output (gitignored)
│ ├── index.html # Built files (after running bun run build)
│ └── assets/ # Built assets
├── setup.py # Build checks
├── pyproject.toml # Package configuration
└── .gitignore # Excludes lightrag/api/webui/* (except .gitkeep)
```
---
## Troubleshooting
### Q: I installed in development mode but the web interface doesn't work
**A:** Build the frontend:
```bash
cd lightrag_webui && bun run build
```
### Q: I built the frontend but it's not in my installed package
**A:** You probably used `pip install .` after building. Either:
- Use `pip install -e ".[api]"` for development
- Or reinstall: `pip uninstall lightrag-hku && pip install ".[api]"`
### Q: Where are the built frontend files?
**A:** In `lightrag/api/webui/` after running `bun run build`
### Q: Can I use npm or yarn instead of Bun?
**A:** The project is configured for Bun. While npm/yarn might work, Bun is recommended per project standards.
---
## Summary
**PyPI users**: No action needed, frontend included
**Developers**: Use `pip install -e ".[api]"`, build frontend when needed
**CI/CD**: Automatic build in GitHub Actions
**Git**: Frontend build output never committed
For questions or issues, please open a GitHub issue.
@@ -1,114 +0,0 @@
## LightRAG Multi-Document Processing: Concurrent Control Strategy
LightRAG employs a multi-layered concurrent control strategy when processing multiple documents. This article provides an in-depth analysis of the concurrent control mechanisms at document level, chunk level, and LLM request level, helping you understand why specific concurrent behaviors occur.
### 1. Document-Level Concurrent Control
**Control Parameter**: `max_parallel_insert`
This parameter controls the number of documents processed simultaneously. The purpose is to prevent excessive parallelism from overwhelming system resources, which could lead to extended processing times for individual files. Document-level concurrency is governed by the `max_parallel_insert` attribute within LightRAG, which defaults to 2 and is configurable via the `MAX_PARALLEL_INSERT` environment variable. `max_parallel_insert` is recommended to be set between 2 and 10, typically `llm_model_max_async/3`. Setting this value too high can increase the likelihood of naming conflicts among entities and relationships across different documents during the merge phase, thereby reducing its overall efficiency.
### 2. Chunk-Level Concurrent Control
**Control Parameter**: `llm_model_max_async`
This parameter controls the number of chunks processed simultaneously in the extraction stage within a document. The purpose is to prevent a high volume of concurrent requests from monopolizing LLM processing resources, which would impede the efficient parallel processing of multiple files. Chunk-Level Concurrent Control is governed by the `llm_model_max_async` attribute within LightRAG, which defaults to 4 and is configurable via the `MAX_ASYNC` environment variable. The purpose of this parameter is to fully leverage the LLM's concurrency capabilities when processing individual documents.
In the `extract_entities` function, **each document independently creates** its own chunk semaphore. Since each document independently creates chunk semaphores, the theoretical chunk concurrency of the system is:
$$
ChunkConcurrency = Max Parallel Insert × LLM Model Max Async
$$
For example:
- `max_parallel_insert = 2` (process 2 documents simultaneously)
- `llm_model_max_async = 4` (maximum 4 chunk concurrency per document)
- Theoretical chunk-level concurrent: 2 × 4 = 8
### 3. Graph-Level Concurrent Control
**Control Parameter**: `llm_model_max_async * 2`
This parameter controls the number of entities and relations processed simultaneously in the merging stage within a document. The purpose is to prevent a high volume of concurrent requests from monopolizing LLM processing resources, which would impede the efficient parallel processing of multiple files. Graph-level concurrency is governed by the `llm_model_max_async` attribute within LightRAG, which defaults to 4 and is configurable via the `MAX_ASYNC` environment variable. Graph-level parallelism control parameters are equally applicable to managing parallelism during the entity relationship reconstruction phase after document deletion.
Given that the entity relationship merging phase doesn't necessitate LLM interaction for every operation, its parallelism is set at double the LLM's parallelism. This optimizes machine utilization while concurrently preventing excessive queuing resource contention for the LLM.
### 4. LLM-Level Concurrent Control
**Control Parameter**: `llm_model_max_async`
This parameter governs the **concurrent volume** of LLM requests dispatched by the entire LightRAG system, encompassing the document extraction stage, merging stage, and user query handling.
LLM request prioritization is managed via a global priority queue, which **systematically prioritizes user queries** over merging-related requests, and merging-related requests over extraction-related requests. This strategic prioritization **minimizes user query latency**.
LLM-level concurrency is governed by the `llm_model_max_async` attribute within LightRAG, which defaults to 4 and is configurable via the `MAX_ASYNC` environment variable.
### 5. Complete Concurrent Hierarchy Diagram
```mermaid
graph TD
classDef doc fill:#e6f3ff,stroke:#5b9bd5,stroke-width:2px;
classDef chunk fill:#fbe5d6,stroke:#ed7d31,stroke-width:1px;
classDef merge fill:#e2f0d9,stroke:#70ad47,stroke-width:2px;
A["Multiple Documents<br>max_parallel_insert = 2"] --> A1
A --> B1
A1[DocA: split to n chunks] --> A_chunk;
B1[DocB: split to m chunks] --> B_chunk;
subgraph A_chunk[Extraction Stage]
A_chunk_title[Entity Relation Extraction<br>llm_model_max_async = 4];
A_chunk_title --> A_chunk1[Chunk A1]:::chunk;
A_chunk_title --> A_chunk2[Chunk A2]:::chunk;
A_chunk_title --> A_chunk3[Chunk A3]:::chunk;
A_chunk_title --> A_chunk4[Chunk A4]:::chunk;
A_chunk1 & A_chunk2 & A_chunk3 & A_chunk4 --> A_chunk_done([Extraction Complete]);
end
subgraph B_chunk[Extraction Stage]
B_chunk_title[Entity Relation Extraction<br>llm_model_max_async = 4];
B_chunk_title --> B_chunk1[Chunk B1]:::chunk;
B_chunk_title --> B_chunk2[Chunk B2]:::chunk;
B_chunk_title --> B_chunk3[Chunk B3]:::chunk;
B_chunk_title --> B_chunk4[Chunk B4]:::chunk;
B_chunk1 & B_chunk2 & B_chunk3 & B_chunk4 --> B_chunk_done([Extraction Complete]);
end
A_chunk -.->|LLM Request| LLM_Queue;
A_chunk --> A_merge;
B_chunk --> B_merge;
subgraph A_merge[Merge Stage]
A_merge_title[Entity Relation Merging<br>llm_model_max_async * 2 = 8];
A_merge_title --> A1_entity[Ent a1]:::merge;
A_merge_title --> A2_entity[Ent a2]:::merge;
A_merge_title --> A3_entity[Rel a3]:::merge;
A_merge_title --> A4_entity[Rel a4]:::merge;
A1_entity & A2_entity & A3_entity & A4_entity --> A_done([Merge Complete])
end
subgraph B_merge[Merge Stage]
B_merge_title[Entity Relation Merging<br>llm_model_max_async * 2 = 8];
B_merge_title --> B1_entity[Ent b1]:::merge;
B_merge_title --> B2_entity[Ent b2]:::merge;
B_merge_title --> B3_entity[Rel b3]:::merge;
B_merge_title --> B4_entity[Rel b4]:::merge;
B1_entity & B2_entity & B3_entity & B4_entity --> B_done([Merge Complete])
end
A_merge -.->|LLM Request| LLM_Queue["LLM Request Prioritized Queue<br>llm_model_max_async = 4"];
B_merge -.->|LLM Request| LLM_Queue;
B_chunk -.->|LLM Request| LLM_Queue;
```
> The extraction and merge stages share a global prioritized LLM queue, regulated by `llm_model_max_async`. While numerous entity and relation extraction and merging operations may be "actively processing", **only a limited number will concurrently execute LLM requests** the remainder will be queued and awaiting their turn.
### 6. Performance Optimization Recommendations
* **Increase LLM Concurrent Setting based on the capabilities of your LLM server or API provider**
During the file processing phase, the performance and concurrency capabilities of the LLM are critical bottlenecks. When deploying LLMs locally, the service's concurrency capacity must adequately account for the context length requirements of LightRAG. LightRAG recommends that LLMs support a minimum context length of 32KB; therefore, server concurrency should be calculated based on this benchmark. For API providers, LightRAG will retry requests up to three times if the client's request is rejected due to concurrent request limits. Backend logs can be used to determine if LLM retries are occurring, thereby indicating whether `MAX_ASYNC` has exceeded the API provider's limits.
* **Align Parallel Document Insertion Settings with LLM Concurrency Configurations**
The recommended number of parallel document processing tasks is 1/4 of the LLM's concurrency, with a minimum of 2 and a maximum of 10. Setting a higher number of parallel document processing tasks typically does not accelerate overall document processing speed, as even a small number of concurrently processed documents can fully utilize the LLM's parallel processing capabilities. Excessive parallel document processing can significantly increase the processing time for each individual document. Since LightRAG commits processing results on a file-by-file basis, a large number of concurrent files would necessitate caching a substantial amount of data. In the event of a system error, all documents in the middle stage would require reprocessing, thereby increasing error handling costs. For instance, setting `MAX_PARALLEL_INSERT` to 3 is appropriate when `MAX_ASYNC` is configured to 12.
-317
View File
@@ -1,317 +0,0 @@
# LightRAG Offline Deployment Guide
This guide provides comprehensive instructions for deploying LightRAG in offline environments where internet access is limited or unavailable.
If you deploy LightRAG using Docker, there is no need to refer to this document, as the LightRAG Docker image is pre-configured for offline operation.
> Software packages requiring `transformers`, `torch`, or `cuda` will not be included in the offline dependency group. Consequently, document extraction tools such as Docling, as well as local LLM models like Hugging Face and LMDeploy, are outside the scope of offline installation support. These high-compute-resource-demanding services should not be integrated into LightRAG. Docling will be decoupled and deployed as a standalone service.
## Table of Contents
- [Overview](#overview)
- [Quick Start](#quick-start)
- [Layered Dependencies](#layered-dependencies)
- [Tiktoken Cache Management](#tiktoken-cache-management)
- [Complete Offline Deployment Workflow](#complete-offline-deployment-workflow)
- [Troubleshooting](#troubleshooting)
## Overview
LightRAG uses dynamic package installation (`pipmaster`) for optional features based on file types and configurations. In offline environments, these dynamic installations will fail. This guide shows you how to pre-install all necessary dependencies and cache files.
### What Gets Dynamically Installed?
LightRAG dynamically installs packages for:
- **Storage Backends**: `redis`, `neo4j`, `pymilvus`, `pymongo`, `asyncpg`, `qdrant-client`
- **LLM Providers**: `openai`, `anthropic`, `ollama`, `zhipuai`, `aioboto3`, `voyageai`, `llama-index`, `lmdeploy`, `transformers`, `torch`
- **Tiktoken Models**: BPE encoding models downloaded from OpenAI CDN
**Note**: Document processing dependencies (`pypdf`, `python-docx`, `python-pptx`, `openpyxl`) are now pre-installed with the `api` extras group and no longer require dynamic installation.
## Quick Start
### Option 1: Using pip with Offline Extras
```bash
# Online environment: Install all offline dependencies
pip install lightrag-hku[offline]
# Download tiktoken cache
lightrag-download-cache
# Create offline package
pip download lightrag-hku[offline] -d ./offline-packages
tar -czf lightrag-offline.tar.gz ./offline-packages ~/.tiktoken_cache
# Transfer to offline server
scp lightrag-offline.tar.gz user@offline-server:/path/to/
# Offline environment: Install
tar -xzf lightrag-offline.tar.gz
pip install --no-index --find-links=./offline-packages lightrag-hku[offline]
export TIKTOKEN_CACHE_DIR=~/.tiktoken_cache
```
### Option 2: Using Requirements Files
```bash
# Online environment: Download packages
pip download -r requirements-offline.txt -d ./packages
# Transfer to offline server
tar -czf packages.tar.gz ./packages
scp packages.tar.gz user@offline-server:/path/to/
# Offline environment: Install
tar -xzf packages.tar.gz
pip install --no-index --find-links=./packages -r requirements-offline.txt
```
## Layered Dependencies
LightRAG provides flexible dependency groups for different use cases:
### Available Dependency Groups
| Group | Description | Use Case |
|-------|-------------|----------|
| `api` | API server + document processing | FastAPI server with PDF, DOCX, PPTX, XLSX support |
| `offline-storage` | Storage backends | Redis, Neo4j, MongoDB, PostgreSQL, etc. |
| `offline-llm` | LLM providers | OpenAI, Anthropic, Ollama, etc. |
| `offline` | Complete offline package | API + Storage + LLM (all features) |
**Note**: Document processing (PDF, DOCX, PPTX, XLSX) is included in the `api` extras group. The previous `offline-docs` group has been merged into `api` for better integration.
> Software packages requiring `transformers`, `torch`, or `cuda` will not be included in the offline dependency group.
### Installation Examples
```bash
# Install API with document processing
pip install lightrag-hku[api]
# Install API and storage backends
pip install lightrag-hku[api,offline-storage]
# Install all offline dependencies (recommended for offline deployment)
pip install lightrag-hku[offline]
```
### Using Individual Requirements Files
```bash
# Storage backends only
pip install -r requirements-offline-storage.txt
# LLM providers only
pip install -r requirements-offline-llm.txt
# All offline dependencies
pip install -r requirements-offline.txt
```
## Tiktoken Cache Management
Tiktoken downloads BPE encoding models on first use. In offline environments, you must pre-download these models.
### Using the CLI Command
After installing LightRAG, use the built-in command:
```bash
# Download to default location (~/.tiktoken_cache)
lightrag-download-cache
# Download to specific directory
lightrag-download-cache --cache-dir ./tiktoken_cache
# Download specific models only
lightrag-download-cache --models gpt-4o-mini gpt-4
```
### Default Models Downloaded
- `gpt-4o-mini` (LightRAG default)
- `gpt-4o`
- `gpt-4`
- `gpt-3.5-turbo`
- `text-embedding-ada-002`
- `text-embedding-3-small`
- `text-embedding-3-large`
### Setting Cache Location in Offline Environment
```bash
# Option 1: Environment variable (temporary)
export TIKTOKEN_CACHE_DIR=/path/to/tiktoken_cache
# Option 2: Add to ~/.bashrc or ~/.zshrc (persistent)
echo 'export TIKTOKEN_CACHE_DIR=~/.tiktoken_cache' >> ~/.bashrc
source ~/.bashrc
# Option 3: Copy to default location
cp -r /path/to/tiktoken_cache ~/.tiktoken_cache/
```
## Complete Offline Deployment Workflow
### Step 1: Prepare in Online Environment
```bash
# 1. Install LightRAG with offline dependencies
pip install lightrag-hku[offline]
# 2. Download tiktoken cache
lightrag-download-cache --cache-dir ./offline_cache/tiktoken
# 3. Download all Python packages
pip download lightrag-hku[offline] -d ./offline_cache/packages
# 4. Create archive for transfer
tar -czf lightrag-offline-complete.tar.gz ./offline_cache
# 5. Verify contents
tar -tzf lightrag-offline-complete.tar.gz | head -20
```
### Step 2: Transfer to Offline Environment
```bash
# Using scp
scp lightrag-offline-complete.tar.gz user@offline-server:/tmp/
# Or using USB/physical media
# Copy lightrag-offline-complete.tar.gz to USB drive
```
### Step 3: Install in Offline Environment
```bash
# 1. Extract archive
cd /tmp
tar -xzf lightrag-offline-complete.tar.gz
# 2. Install Python packages
pip install --no-index \
--find-links=/tmp/offline_cache/packages \
lightrag-hku[offline]
# 3. Set up tiktoken cache
mkdir -p ~/.tiktoken_cache
cp -r /tmp/offline_cache/tiktoken/* ~/.tiktoken_cache/
export TIKTOKEN_CACHE_DIR=~/.tiktoken_cache
# 4. Add to shell profile for persistence
echo 'export TIKTOKEN_CACHE_DIR=~/.tiktoken_cache' >> ~/.bashrc
```
### Step 4: Verify Installation
```bash
# Test Python import
python -c "from lightrag import LightRAG; print('✓ LightRAG imported')"
# Test tiktoken
python -c "from lightrag.utils import TiktokenTokenizer; t = TiktokenTokenizer(); print('✓ Tiktoken working')"
# Test optional dependencies (if installed)
python -c "import docling; print('✓ Docling available')"
python -c "import redis; print('✓ Redis available')"
```
## Troubleshooting
### Issue: Tiktoken fails with network error
**Problem**: `Unable to load tokenizer for model gpt-4o-mini`
**Solution**:
```bash
# Ensure TIKTOKEN_CACHE_DIR is set
echo $TIKTOKEN_CACHE_DIR
# Verify cache files exist
ls -la ~/.tiktoken_cache/
# If empty, you need to download cache in online environment first
```
### Issue: Dynamic package installation fails
**Problem**: `Error installing package xxx`
**Solution**:
```bash
# Pre-install the specific package you need
# For API with document processing:
pip install lightrag-hku[api]
# For storage backends:
pip install lightrag-hku[offline-storage]
# For LLM providers:
pip install lightrag-hku[offline-llm]
```
### Issue: Missing dependencies at runtime
**Problem**: `ModuleNotFoundError: No module named 'xxx'`
**Solution**:
```bash
# Check what you have installed
pip list | grep -i xxx
# Install missing component
pip install lightrag-hku[offline] # Install all offline deps
```
### Issue: Permission denied on tiktoken cache
**Problem**: `PermissionError: [Errno 13] Permission denied`
**Solution**:
```bash
# Ensure cache directory has correct permissions
chmod 755 ~/.tiktoken_cache
chmod 644 ~/.tiktoken_cache/*
# Or use a user-writable directory
export TIKTOKEN_CACHE_DIR=~/my_tiktoken_cache
mkdir -p ~/my_tiktoken_cache
```
## Best Practices
1. **Test in Online Environment First**: Always test your complete setup in an online environment before going offline.
2. **Keep Cache Updated**: Periodically update your offline cache when new models are released.
3. **Document Your Setup**: Keep notes on which optional dependencies you actually need.
4. **Version Pinning**: Consider pinning specific versions in production:
```bash
pip freeze > requirements-production.txt
```
5. **Minimal Installation**: Only install what you need:
```bash
# If you only need API with document processing
pip install lightrag-hku[api]
# Then manually add specific LLM: pip install openai
```
## Additional Resources
- [LightRAG GitHub Repository](https://github.com/HKUDS/LightRAG)
- [Docker Deployment Guide](./DockerDeployment.md)
- [API Documentation](../lightrag/api/README.md)
## Support
If you encounter issues not covered in this guide:
1. Check the [GitHub Issues](https://github.com/HKUDS/LightRAG/issues)
2. Review the [project documentation](../README.md)
3. Create a new issue with your offline deployment details
-170
View File
@@ -1,170 +0,0 @@
# uv.lock Update Guide
## What is uv.lock?
`uv.lock` is uv's lock file. It captures the exact version of every dependency, including transitive ones, much like:
- Node.js `package-lock.json`
- Rust `Cargo.lock`
- Python Poetry `poetry.lock`
Keeping `uv.lock` in version control guarantees that everyone installs the same dependency set.
## When does uv.lock change?
### Situations where it does *not* change automatically
- Running `uv sync --frozen`
- Building Docker images that call `uv sync --frozen`
- Editing source code without touching dependency metadata
### Situations where it will change
1. **`uv lock` or `uv lock --upgrade`**
```bash
uv lock # Resolve according to current constraints
uv lock --upgrade # Re-resolve and upgrade to the newest compatible releases
```
Use these commands after modifying `pyproject.toml`, when you want fresh dependency versions, or if the lock file was deleted or corrupted.
2. **`uv add`**
```bash
uv add requests # Adds the dependency and updates both files
uv add --dev pytest # Adds a dev dependency
```
`uv add` edits `pyproject.toml` and refreshes `uv.lock` in one step.
3. **`uv remove`**
```bash
uv remove requests
```
This removes the dependency from `pyproject.toml` and rewrites `uv.lock`.
4. **`uv sync` without `--frozen`**
```bash
uv sync
```
Normally this only installs what is already locked. However, if `pyproject.toml` and `uv.lock` disagree or the lock file is missing, uv will regenerate and update `uv.lock`. In CI and production builds you should prefer `uv sync --frozen` to prevent unintended updates.
## Example workflows
### Scenario 1: Add a new dependency
```bash
# Recommended: let uv handle both files
uv add fastapi
git add pyproject.toml uv.lock
git commit -m "Add fastapi dependency"
# Manual alternative
# 1. Edit pyproject.toml
# 2. Regenerate the lock file
uv lock
git add pyproject.toml uv.lock
git commit -m "Add fastapi dependency"
```
### Scenario 2: Relax or tighten a version constraint
```bash
# 1. Edit the requirement in pyproject.toml,
# e.g. openai>=1.0.0,<2.0.0 -> openai>=1.5.0,<2.0.0
# 2. Re-resolve the lock file
uv lock
# 3. Commit both files
git add pyproject.toml uv.lock
git commit -m "Update openai to >=1.5.0"
```
### Scenario 3: Upgrade everything to the newest compatible versions
```bash
uv lock --upgrade
git diff uv.lock
git add uv.lock
git commit -m "Upgrade dependencies to latest compatible versions"
```
### Scenario 4: Teammate syncing the project
```bash
git pull # Fetch latest code and lock file
uv sync --frozen # Install exactly what uv.lock specifies
```
## Using uv.lock in Docker
```dockerfile
RUN uv sync --frozen --no-dev --extra api
```
`--frozen` guarantees reproducible builds because uv will refuse to deviate from the locked versions.
`--extra api` install API server
## Generating a lock file that includes offline dependencies
If you need `uv.lock` to capture the optional offline stacks, regenerate it with the relevant extras enabled:
```bash
uv lock --extra api --extra offline
```
This command resolves the base project requirements plus both the `api` and `offline` optional dependency sets, ensuring downstream `uv sync --frozen --extra api --extra offline` installs work without further resolution.
## Frequently asked questions
- **`uv.lock` is almost 1MB. Does that matter?**
No. The file is read only during dependency resolution.
- **Should we commit `uv.lock`?**
Yes. Commit it so collaborators and CI jobs share the same dependency graph.
- **Deleted the lock file by accident?**
Run `uv lock` to regenerate it from `pyproject.toml`.
- **Can `uv.lock` and `requirements.txt` coexist?**
They can, but maintaining both is redundant. Prefer relying on `uv.lock` alone whenever possible.
- **How do I inspect locked versions?**
```bash
uv tree
grep -A5 'name = "openai"' uv.lock
```
## Best practices
### Recommended
1. Commit `uv.lock` alongside `pyproject.toml`.
2. Use `uv sync --frozen` in CI, Docker, and other reproducible environments.
3. Use plain `uv sync` during local development if you want uv to reconcile the lock for you.
4. Run `uv lock --upgrade` periodically to pick up the latest compatible releases.
5. Regenerate the lock file immediately after changing dependency constraints.
### Avoid
1. Running `uv sync` without `--frozen` in CI or production pipelines.
2. Editing `uv.lock` by hand—uv will overwrite manual edits.
3. Ignoring lock file diffs in code reviews—unexpected dependency changes can break builds.
## Summary
| Command | Updates `uv.lock` | Typical use |
|-----------------------|-------------------|-------------------------------------------|
| `uv lock` | ✅ Yes | After editing constraints |
| `uv lock --upgrade` | ✅ Yes | Upgrade to the newest compatible versions |
| `uv add <pkg>` | ✅ Yes | Add a dependency |
| `uv remove <pkg>` | ✅ Yes | Remove a dependency |
| `uv sync` | ⚠️ Maybe | Local development; can regenerate the lock |
| `uv sync --frozen` | ❌ No | CI/CD, Docker, reproducible builds |
Remember: `uv.lock` only changes when you run a command that tells it to. Keep it in sync with your project and commit it whenever it changes.
-464
View File
@@ -1,464 +0,0 @@
### This is sample file of .env
###########################
### Server Configuration
###########################
HOST=0.0.0.0
PORT=9621
WEBUI_TITLE='My Graph KB'
WEBUI_DESCRIPTION="Simple and Fast Graph Based RAG System"
# WORKERS=2
### gunicorn worker timeout(as default LLM request timeout if LLM_TIMEOUT is not set)
# TIMEOUT=150
# CORS_ORIGINS=http://localhost:3000,http://localhost:8080
### Optional SSL Configuration
# SSL=true
# SSL_CERTFILE=/path/to/cert.pem
# SSL_KEYFILE=/path/to/key.pem
### Directory Configuration (defaults to current working directory)
### Default value is ./inputs and ./rag_storage
# INPUT_DIR=<absolute_path_for_doc_input_dir>
# WORKING_DIR=<absolute_path_for_working_dir>
### Tiktoken cache directory (Store cached files in this folder for offline deployment)
# TIKTOKEN_CACHE_DIR=/app/data/tiktoken
### Ollama Emulating Model and Tag
# OLLAMA_EMULATING_MODEL_NAME=lightrag
OLLAMA_EMULATING_MODEL_TAG=latest
### Max nodes for graph retrieval (Ensure WebUI local settings are also updated, which is limited to this value)
# MAX_GRAPH_NODES=1000
### Logging level
# LOG_LEVEL=INFO
# VERBOSE=False
# LOG_MAX_BYTES=10485760
# LOG_BACKUP_COUNT=5
### Logfile location (defaults to current working directory)
# LOG_DIR=/path/to/log/directory
#####################################
### Login and API-Key Configuration
#####################################
# AUTH_ACCOUNTS='admin:admin123,user1:pass456'
# TOKEN_SECRET=Your-Key-For-LightRAG-API-Server
# TOKEN_EXPIRE_HOURS=48
# GUEST_TOKEN_EXPIRE_HOURS=24
# JWT_ALGORITHM=HS256
### API-Key to access LightRAG Server API
### Use this key in HTTP requests with the 'X-API-Key' header
### Example: curl -H "X-API-Key: your-secure-api-key-here" http://localhost:9621/query
# LIGHTRAG_API_KEY=your-secure-api-key-here
# WHITELIST_PATHS=/health,/api/*
######################################################################################
### Query Configuration
###
### How to control the context length sent to LLM:
### MAX_ENTITY_TOKENS + MAX_RELATION_TOKENS < MAX_TOTAL_TOKENS
### Chunk_Tokens = MAX_TOTAL_TOKENS - Actual_Entity_Tokens - Actual_Relation_Tokens
######################################################################################
# LLM response cache for query (Not valid for streaming response)
ENABLE_LLM_CACHE=true
# COSINE_THRESHOLD=0.2
### Number of entities or relations retrieved from KG
# TOP_K=40
### Maximum number or chunks for naive vector search
# CHUNK_TOP_K=20
### control the actual entities send to LLM
# MAX_ENTITY_TOKENS=6000
### control the actual relations send to LLM
# MAX_RELATION_TOKENS=8000
### control the maximum tokens send to LLM (include entities, relations and chunks)
# MAX_TOTAL_TOKENS=30000
### chunk selection strategies
### VECTOR: Pick KG chunks by vector similarity, delivered chunks to the LLM aligning more closely with naive retrieval
### WEIGHT: Pick KG chunks by entity and chunk weight, delivered more solely KG related chunks to the LLM
### If reranking is enabled, the impact of chunk selection strategies will be diminished.
# KG_CHUNK_PICK_METHOD=VECTOR
#########################################################
### Reranking configuration
### RERANK_BINDING type: null, cohere, jina, aliyun
### For rerank model deployed by vLLM use cohere binding
#########################################################
RERANK_BINDING=null
### Enable rerank by default in query params when RERANK_BINDING is not null
# RERANK_BY_DEFAULT=True
### rerank score chunk filter(set to 0.0 to keep all chunks, 0.6 or above if LLM is not strong enough)
# MIN_RERANK_SCORE=0.0
### For local deployment with vLLM
# RERANK_MODEL=BAAI/bge-reranker-v2-m3
# RERANK_BINDING_HOST=http://localhost:8000/v1/rerank
# RERANK_BINDING_API_KEY=your_rerank_api_key_here
### Default value for Cohere AI
# RERANK_MODEL=rerank-v3.5
# RERANK_BINDING_HOST=https://api.cohere.com/v2/rerank
# RERANK_BINDING_API_KEY=your_rerank_api_key_here
### Default value for Jina AI
# RERANK_MODEL=jina-reranker-v2-base-multilingual
# RERANK_BINDING_HOST=https://api.jina.ai/v1/rerank
# RERANK_BINDING_API_KEY=your_rerank_api_key_here
### Default value for Aliyun
# RERANK_MODEL=gte-rerank-v2
# RERANK_BINDING_HOST=https://dashscope.aliyuncs.com/api/v1/services/rerank/text-rerank/text-rerank
# RERANK_BINDING_API_KEY=your_rerank_api_key_here
########################################
### Document processing configuration
########################################
ENABLE_LLM_CACHE_FOR_EXTRACT=true
### Document processing output language: English, Chinese, French, German ...
SUMMARY_LANGUAGE=English
### PDF decryption password for protected PDF files
# PDF_DECRYPT_PASSWORD=your_pdf_password_here
### Entity types that the LLM will attempt to recognize
# ENTITY_TYPES='["Person", "Creature", "Organization", "Location", "Event", "Concept", "Method", "Content", "Data", "Artifact", "NaturalObject"]'
### Chunk size for document splitting, 500~1500 is recommended
# CHUNK_SIZE=1200
# CHUNK_OVERLAP_SIZE=100
### Number of summary segments or tokens to trigger LLM summary on entity/relation merge (at least 3 is recommended)
# FORCE_LLM_SUMMARY_ON_MERGE=8
### Max description token size to trigger LLM summary
# SUMMARY_MAX_TOKENS = 1200
### Recommended LLM summary output length in tokens
# SUMMARY_LENGTH_RECOMMENDED_=600
### Maximum context size sent to LLM for description summary
# SUMMARY_CONTEXT_SIZE=12000
### control the maximum chunk_ids stored in vector and graph db
# MAX_SOURCE_IDS_PER_ENTITY=300
# MAX_SOURCE_IDS_PER_RELATION=300
### control chunk_ids limitation method: FIFO, KEEP
### FIFO: First in first out
### KEEP: Keep oldest (less merge action and faster)
# SOURCE_IDS_LIMIT_METHOD=FIFO
# Maximum number of file paths stored in entity/relation file_path field (For displayed only, does not affect query performance)
# MAX_FILE_PATHS=100
### maximum number of related chunks per source entity or relation
### The chunk picker uses this value to determine the total number of chunks selected from KG(knowledge graph)
### Higher values increase re-ranking time
# RELATED_CHUNK_NUMBER=5
###############################
### Concurrency Configuration
###############################
### Max concurrency requests of LLM (for both query and document processing)
MAX_ASYNC=4
### Number of parallel processing documents(between 2~10, MAX_ASYNC/3 is recommended)
MAX_PARALLEL_INSERT=2
### Max concurrency requests for Embedding
# EMBEDDING_FUNC_MAX_ASYNC=8
### Num of chunks send to Embedding in single request
# EMBEDDING_BATCH_NUM=10
###########################################################################
### LLM Configuration
### LLM_BINDING type: openai, ollama, lollms, azure_openai, aws_bedrock, gemini
### LLM_BINDING_HOST: host only for Ollama, endpoint for other LLM service
### If LightRAG deployed in Docker:
### uses host.docker.internal instead of localhost in LLM_BINDING_HOST
###########################################################################
### LLM request timeout setting for all llm (0 means no timeout for Ollma)
# LLM_TIMEOUT=180
LLM_BINDING=openai
LLM_MODEL=gpt-4o
LLM_BINDING_HOST=https://api.openai.com/v1
LLM_BINDING_API_KEY=your_api_key
### Env vars for Azure openai
# AZURE_OPENAI_API_VERSION=2024-08-01-preview
# AZURE_OPENAI_DEPLOYMENT=gpt-4o
### Openrouter example
# LLM_MODEL=google/gemini-2.5-flash
# LLM_BINDING_HOST=https://openrouter.ai/api/v1
# LLM_BINDING_API_KEY=your_api_key
# LLM_BINDING=openai
### Gemini example
# LLM_BINDING=gemini
# LLM_MODEL=gemini-flash-latest
# LLM_BINDING_API_KEY=your_gemini_api_key
# LLM_BINDING_HOST=https://generativelanguage.googleapis.com
### use the following command to see all support options for OpenAI, azure_openai or OpenRouter
### lightrag-server --llm-binding gemini --help
### Gemini Specific Parameters
# GEMINI_LLM_MAX_OUTPUT_TOKENS=9000
# GEMINI_LLM_TEMPERATURE=0.7
### Enable Thinking
# GEMINI_LLM_THINKING_CONFIG='{"thinking_budget": -1, "include_thoughts": true}'
### Disable Thinking
# GEMINI_LLM_THINKING_CONFIG='{"thinking_budget": 0, "include_thoughts": false}'
### use the following command to see all support options for OpenAI, azure_openai or OpenRouter
### lightrag-server --llm-binding openai --help
### OpenAI Specific Parameters
# OPENAI_LLM_REASONING_EFFORT=minimal
### OpenRouter Specific Parameters
# OPENAI_LLM_EXTRA_BODY='{"reasoning": {"enabled": false}}'
### Qwen3 Specific Parameters deploy by vLLM
# OPENAI_LLM_EXTRA_BODY='{"chat_template_kwargs": {"enable_thinking": false}}'
### OpenAI Compatible API Specific Parameters
### Increased temperature values may mitigate infinite inference loops in certain LLM, such as Qwen3-30B.
# OPENAI_LLM_TEMPERATURE=0.9
### Set the max_tokens to mitigate endless output of some LLM (less than LLM_TIMEOUT * llm_output_tokens/second, i.e. 9000 = 180s * 50 tokens/s)
### Typically, max_tokens does not include prompt content
### For vLLM/SGLang deployed models, or most of OpenAI compatible API provider
# OPENAI_LLM_MAX_TOKENS=9000
### For OpenAI o1-mini or newer modles utilizes max_completion_tokens instead of max_tokens
OPENAI_LLM_MAX_COMPLETION_TOKENS=9000
### use the following command to see all support options for Ollama LLM
### lightrag-server --llm-binding ollama --help
### Ollama Server Specific Parameters
### OLLAMA_LLM_NUM_CTX must be provided, and should at least larger than MAX_TOTAL_TOKENS + 2000
OLLAMA_LLM_NUM_CTX=32768
### Set the max_output_tokens to mitigate endless output of some LLM (less than LLM_TIMEOUT * llm_output_tokens/second, i.e. 9000 = 180s * 50 tokens/s)
# OLLAMA_LLM_NUM_PREDICT=9000
### Stop sequences for Ollama LLM
# OLLAMA_LLM_STOP='["</s>", "<|EOT|>"]'
### Bedrock Specific Parameters
# BEDROCK_LLM_TEMPERATURE=1.0
#######################################################################################
### Embedding Configuration (Should not be changed after the first file processed)
### EMBEDDING_BINDING: ollama, openai, azure_openai, jina, lollms, aws_bedrock
### EMBEDDING_BINDING_HOST: host only for Ollama, endpoint for other Embedding service
### If LightRAG deployed in Docker:
### uses host.docker.internal instead of localhost in EMBEDDING_BINDING_HOST
#######################################################################################
# EMBEDDING_TIMEOUT=30
### Control whether to send embedding_dim parameter to embedding API
### IMPORTANT: Jina ALWAYS sends dimension parameter (API requirement) - this setting is ignored for Jina
### For OpenAI: Set to 'true' to enable dynamic dimension adjustment
### For OpenAI: Set to 'false' (default) to disable sending dimension parameter
### Note: Automatically ignored for backends that don't support dimension parameter (e.g., Ollama)
# Ollama embedding
# EMBEDDING_BINDING=ollama
# EMBEDDING_MODEL=bge-m3:latest
# EMBEDDING_DIM=1024
# EMBEDDING_BINDING_API_KEY=your_api_key
### If LightRAG deployed in Docker uses host.docker.internal instead of localhost
# EMBEDDING_BINDING_HOST=http://localhost:11434
### OpenAI compatible embedding
EMBEDDING_BINDING=openai
EMBEDDING_MODEL=text-embedding-3-large
EMBEDDING_DIM=3072
EMBEDDING_SEND_DIM=false
EMBEDDING_TOKEN_LIMIT=8192
EMBEDDING_BINDING_HOST=https://api.openai.com/v1
EMBEDDING_BINDING_API_KEY=your_api_key
### Optional for Azure
# AZURE_EMBEDDING_DEPLOYMENT=text-embedding-3-large
# AZURE_EMBEDDING_API_VERSION=2023-05-15
# AZURE_EMBEDDING_ENDPOINT=your_endpoint
# AZURE_EMBEDDING_API_KEY=your_api_key
### Gemini embedding
# EMBEDDING_BINDING=gemini
# EMBEDDING_MODEL=gemini-embedding-001
# EMBEDDING_DIM=1536
# EMBEDDING_TOKEN_LIMIT=2048
# EMBEDDING_BINDING_HOST=https://generativelanguage.googleapis.com
# EMBEDDING_BINDING_API_KEY=your_api_key
### Gemini embedding requires sending dimension to server
# EMBEDDING_SEND_DIM=true
### Jina AI Embedding
# EMBEDDING_BINDING=jina
# EMBEDDING_BINDING_HOST=https://api.jina.ai/v1/embeddings
# EMBEDDING_MODEL=jina-embeddings-v4
# EMBEDDING_DIM=2048
# EMBEDDING_BINDING_API_KEY=your_api_key
### Optional for Ollama embedding
OLLAMA_EMBEDDING_NUM_CTX=8192
### use the following command to see all support options for Ollama embedding
### lightrag-server --embedding-binding ollama --help
####################################################################
### WORKSPACE sets workspace name for all storage types
### for the purpose of isolating data from LightRAG instances.
### Valid workspace name constraints: a-z, A-Z, 0-9, and _
####################################################################
# WORKSPACE=space1
############################
### Data storage selection
############################
### Default storage (Recommended for small scale deployment)
# LIGHTRAG_KV_STORAGE=JsonKVStorage
# LIGHTRAG_DOC_STATUS_STORAGE=JsonDocStatusStorage
# LIGHTRAG_GRAPH_STORAGE=NetworkXStorage
# LIGHTRAG_VECTOR_STORAGE=NanoVectorDBStorage
### Redis Storage (Recommended for production deployment)
# LIGHTRAG_KV_STORAGE=RedisKVStorage
# LIGHTRAG_DOC_STATUS_STORAGE=RedisDocStatusStorage
### Vector Storage (Recommended for production deployment)
# LIGHTRAG_VECTOR_STORAGE=MilvusVectorDBStorage
# LIGHTRAG_VECTOR_STORAGE=QdrantVectorDBStorage
# LIGHTRAG_VECTOR_STORAGE=FaissVectorDBStorage
### Graph Storage (Recommended for production deployment)
# LIGHTRAG_GRAPH_STORAGE=Neo4JStorage
# LIGHTRAG_GRAPH_STORAGE=MemgraphStorage
### PostgreSQL
# LIGHTRAG_KV_STORAGE=PGKVStorage
# LIGHTRAG_DOC_STATUS_STORAGE=PGDocStatusStorage
# LIGHTRAG_GRAPH_STORAGE=PGGraphStorage
# LIGHTRAG_VECTOR_STORAGE=PGVectorStorage
### MongoDB (Vector storage only available on Atlas Cloud)
# LIGHTRAG_KV_STORAGE=MongoKVStorage
# LIGHTRAG_DOC_STATUS_STORAGE=MongoDocStatusStorage
# LIGHTRAG_GRAPH_STORAGE=MongoGraphStorage
# LIGHTRAG_VECTOR_STORAGE=MongoVectorDBStorage
### PostgreSQL Configuration
POSTGRES_HOST=localhost
POSTGRES_PORT=5432
POSTGRES_USER=your_username
POSTGRES_PASSWORD='your_password'
POSTGRES_DATABASE=your_database
POSTGRES_MAX_CONNECTIONS=12
# POSTGRES_WORKSPACE=forced_workspace_name
### PostgreSQL Vector Storage Configuration
### Vector storage type: HNSW, IVFFlat
POSTGRES_VECTOR_INDEX_TYPE=HNSW
POSTGRES_HNSW_M=16
POSTGRES_HNSW_EF=200
POSTGRES_IVFFLAT_LISTS=100
### PostgreSQL Connection Retry Configuration (Network Robustness)
### Number of retry attempts (1-10, default: 3)
### Initial retry backoff in seconds (0.1-5.0, default: 0.5)
### Maximum retry backoff in seconds (backoff-60.0, default: 5.0)
### Connection pool close timeout in seconds (1.0-30.0, default: 5.0)
# POSTGRES_CONNECTION_RETRIES=3
# POSTGRES_CONNECTION_RETRY_BACKOFF=0.5
# POSTGRES_CONNECTION_RETRY_BACKOFF_MAX=5.0
# POSTGRES_POOL_CLOSE_TIMEOUT=5.0
### PostgreSQL SSL Configuration (Optional)
# POSTGRES_SSL_MODE=require
# POSTGRES_SSL_CERT=/path/to/client-cert.pem
# POSTGRES_SSL_KEY=/path/to/client-key.pem
# POSTGRES_SSL_ROOT_CERT=/path/to/ca-cert.pem
# POSTGRES_SSL_CRL=/path/to/crl.pem
### PostgreSQL Server Settings (for Supabase Supavisor)
# Use this to pass extra options to the PostgreSQL connection string.
# For Supabase, you might need to set it like this:
# POSTGRES_SERVER_SETTINGS="options=reference%3D[project-ref]"
# Default is 100 set to 0 to disable
# POSTGRES_STATEMENT_CACHE_SIZE=100
### Neo4j Configuration
NEO4J_URI=neo4j+s://xxxxxxxx.databases.neo4j.io
NEO4J_USERNAME=neo4j
NEO4J_PASSWORD='your_password'
NEO4J_DATABASE=neo4j
NEO4J_MAX_CONNECTION_POOL_SIZE=100
NEO4J_CONNECTION_TIMEOUT=30
NEO4J_CONNECTION_ACQUISITION_TIMEOUT=30
NEO4J_MAX_TRANSACTION_RETRY_TIME=30
NEO4J_MAX_CONNECTION_LIFETIME=300
NEO4J_LIVENESS_CHECK_TIMEOUT=30
NEO4J_KEEP_ALIVE=true
# NEO4J_WORKSPACE=forced_workspace_name
### MongoDB Configuration
MONGO_URI=mongodb://root:root@localhost:27017/
#MONGO_URI=mongodb+srv://xxxx
MONGO_DATABASE=LightRAG
# MONGODB_WORKSPACE=forced_workspace_name
### Milvus Configuration
MILVUS_URI=http://localhost:19530
MILVUS_DB_NAME=lightrag
# MILVUS_USER=root
# MILVUS_PASSWORD=your_password
# MILVUS_TOKEN=your_token
# MILVUS_WORKSPACE=forced_workspace_name
### Qdrant
QDRANT_URL=http://localhost:6333
# QDRANT_API_KEY=your-api-key
# QDRANT_WORKSPACE=forced_workspace_name
### Redis
REDIS_URI=redis://localhost:6379
REDIS_SOCKET_TIMEOUT=30
REDIS_CONNECT_TIMEOUT=10
REDIS_MAX_CONNECTIONS=100
REDIS_RETRY_ATTEMPTS=3
# REDIS_WORKSPACE=forced_workspace_name
### Memgraph Configuration
MEMGRAPH_URI=bolt://localhost:7687
MEMGRAPH_USERNAME=
MEMGRAPH_PASSWORD=
MEMGRAPH_DATABASE=memgraph
# MEMGRAPH_WORKSPACE=forced_workspace_name
############################
### Evaluation Configuration
############################
### RAGAS evaluation models (used for RAG quality assessment)
### ⚠️ IMPORTANT: Both LLM and Embedding endpoints MUST be OpenAI-compatible
### Default uses OpenAI models for evaluation
### LLM Configuration for Evaluation
# EVAL_LLM_MODEL=gpt-4o-mini
### API key for LLM evaluation (fallback to OPENAI_API_KEY if not set)
# EVAL_LLM_BINDING_API_KEY=your_api_key
### Custom OpenAI-compatible endpoint for LLM evaluation (optional)
# EVAL_LLM_BINDING_HOST=https://api.openai.com/v1
### Embedding Configuration for Evaluation
# EVAL_EMBEDDING_MODEL=text-embedding-3-large
### API key for embeddings (fallback: EVAL_LLM_BINDING_API_KEY -> OPENAI_API_KEY)
# EVAL_EMBEDDING_BINDING_API_KEY=your_embedding_api_key
### Custom OpenAI-compatible endpoint for embeddings (fallback: EVAL_LLM_BINDING_HOST)
# EVAL_EMBEDDING_BINDING_HOST=https://api.openai.com/v1
### Performance Tuning
### Number of concurrent test case evaluations
### Lower values reduce API rate limit issues but increase evaluation time
# EVAL_MAX_CONCURRENT=2
### TOP_K query parameter of LightRAG (default: 10)
### Number of entities or relations retrieved from KG
# EVAL_QUERY_TOP_K=10
### LLM request retry and timeout settings for evaluation
# EVAL_LLM_MAX_RETRIES=5
# EVAL_LLM_TIMEOUT=180
-195
View File
@@ -1,195 +0,0 @@
################################################################################
# Autogenerated .env entries list for LightRAG binding options
#
# To generate run:
# $ python -m lightrag.llm.binding_options
################################################################################
# ollama_embedding -- Context window size (number of tokens)
# OLLAMA_EMBEDDING_NUM_CTX=4096
# ollama_embedding -- Maximum number of tokens to predict
# OLLAMA_EMBEDDING_NUM_PREDICT=128
# ollama_embedding -- Number of tokens to keep from the initial prompt
# OLLAMA_EMBEDDING_NUM_KEEP=0
# ollama_embedding -- Random seed for generation (-1 for random)
# OLLAMA_EMBEDDING_SEED=-1
# ollama_embedding -- Controls randomness (0.0-2.0, higher = more creative)
# OLLAMA_EMBEDDING_TEMPERATURE=0.8
# ollama_embedding -- Top-k sampling parameter (0 = disabled)
# OLLAMA_EMBEDDING_TOP_K=40
# ollama_embedding -- Top-p (nucleus) sampling parameter (0.0-1.0)
# OLLAMA_EMBEDDING_TOP_P=0.9
# ollama_embedding -- Tail free sampling parameter (1.0 = disabled)
# OLLAMA_EMBEDDING_TFS_Z=1.0
# ollama_embedding -- Typical probability mass (1.0 = disabled)
# OLLAMA_EMBEDDING_TYPICAL_P=1.0
# ollama_embedding -- Minimum probability threshold (0.0 = disabled)
# OLLAMA_EMBEDDING_MIN_P=0.0
# ollama_embedding -- Number of tokens to consider for repetition penalty
# OLLAMA_EMBEDDING_REPEAT_LAST_N=64
# ollama_embedding -- Penalty for repetition (1.0 = no penalty)
# OLLAMA_EMBEDDING_REPEAT_PENALTY=1.1
# ollama_embedding -- Penalty for token presence (-2.0 to 2.0)
# OLLAMA_EMBEDDING_PRESENCE_PENALTY=0.0
# ollama_embedding -- Penalty for token frequency (-2.0 to 2.0)
# OLLAMA_EMBEDDING_FREQUENCY_PENALTY=0.0
# ollama_embedding -- Mirostat sampling algorithm (0=disabled, 1=Mirostat 1.0, 2=Mirostat 2.0)
# OLLAMA_EMBEDDING_MIROSTAT=0
# ollama_embedding -- Mirostat target entropy
# OLLAMA_EMBEDDING_MIROSTAT_TAU=5.0
# ollama_embedding -- Mirostat learning rate
# OLLAMA_EMBEDDING_MIROSTAT_ETA=0.1
# ollama_embedding -- Enable NUMA optimization
# OLLAMA_EMBEDDING_NUMA=False
# ollama_embedding -- Batch size for processing
# OLLAMA_EMBEDDING_NUM_BATCH=512
# ollama_embedding -- Number of GPUs to use (-1 for auto)
# OLLAMA_EMBEDDING_NUM_GPU=-1
# ollama_embedding -- Main GPU index
# OLLAMA_EMBEDDING_MAIN_GPU=0
# ollama_embedding -- Optimize for low VRAM
# OLLAMA_EMBEDDING_LOW_VRAM=False
# ollama_embedding -- Number of CPU threads (0 for auto)
# OLLAMA_EMBEDDING_NUM_THREAD=0
# ollama_embedding -- Use half-precision for key/value cache
# OLLAMA_EMBEDDING_F16_KV=True
# ollama_embedding -- Return logits for all tokens
# OLLAMA_EMBEDDING_LOGITS_ALL=False
# ollama_embedding -- Only load vocabulary
# OLLAMA_EMBEDDING_VOCAB_ONLY=False
# ollama_embedding -- Use memory mapping for model files
# OLLAMA_EMBEDDING_USE_MMAP=True
# ollama_embedding -- Lock model in memory
# OLLAMA_EMBEDDING_USE_MLOCK=False
# ollama_embedding -- Only use for embeddings
# OLLAMA_EMBEDDING_EMBEDDING_ONLY=False
# ollama_embedding -- Penalize newline tokens
# OLLAMA_EMBEDDING_PENALIZE_NEWLINE=True
# ollama_embedding -- Stop sequences (comma-separated string)
# OLLAMA_EMBEDDING_STOP=
# ollama_llm -- Context window size (number of tokens)
# OLLAMA_LLM_NUM_CTX=4096
# ollama_llm -- Maximum number of tokens to predict
# OLLAMA_LLM_NUM_PREDICT=128
# ollama_llm -- Number of tokens to keep from the initial prompt
# OLLAMA_LLM_NUM_KEEP=0
# ollama_llm -- Random seed for generation (-1 for random)
# OLLAMA_LLM_SEED=-1
# ollama_llm -- Controls randomness (0.0-2.0, higher = more creative)
# OLLAMA_LLM_TEMPERATURE=0.8
# ollama_llm -- Top-k sampling parameter (0 = disabled)
# OLLAMA_LLM_TOP_K=40
# ollama_llm -- Top-p (nucleus) sampling parameter (0.0-1.0)
# OLLAMA_LLM_TOP_P=0.9
# ollama_llm -- Tail free sampling parameter (1.0 = disabled)
# OLLAMA_LLM_TFS_Z=1.0
# ollama_llm -- Typical probability mass (1.0 = disabled)
# OLLAMA_LLM_TYPICAL_P=1.0
# ollama_llm -- Minimum probability threshold (0.0 = disabled)
# OLLAMA_LLM_MIN_P=0.0
# ollama_llm -- Number of tokens to consider for repetition penalty
# OLLAMA_LLM_REPEAT_LAST_N=64
# ollama_llm -- Penalty for repetition (1.0 = no penalty)
# OLLAMA_LLM_REPEAT_PENALTY=1.1
# ollama_llm -- Penalty for token presence (-2.0 to 2.0)
# OLLAMA_LLM_PRESENCE_PENALTY=0.0
# ollama_llm -- Penalty for token frequency (-2.0 to 2.0)
# OLLAMA_LLM_FREQUENCY_PENALTY=0.0
# ollama_llm -- Mirostat sampling algorithm (0=disabled, 1=Mirostat 1.0, 2=Mirostat 2.0)
# OLLAMA_LLM_MIROSTAT=0
# ollama_llm -- Mirostat target entropy
# OLLAMA_LLM_MIROSTAT_TAU=5.0
# ollama_llm -- Mirostat learning rate
# OLLAMA_LLM_MIROSTAT_ETA=0.1
# ollama_llm -- Enable NUMA optimization
# OLLAMA_LLM_NUMA=False
# ollama_llm -- Batch size for processing
# OLLAMA_LLM_NUM_BATCH=512
# ollama_llm -- Number of GPUs to use (-1 for auto)
# OLLAMA_LLM_NUM_GPU=-1
# ollama_llm -- Main GPU index
# OLLAMA_LLM_MAIN_GPU=0
# ollama_llm -- Optimize for low VRAM
# OLLAMA_LLM_LOW_VRAM=False
# ollama_llm -- Number of CPU threads (0 for auto)
# OLLAMA_LLM_NUM_THREAD=0
# ollama_llm -- Use half-precision for key/value cache
# OLLAMA_LLM_F16_KV=True
# ollama_llm -- Return logits for all tokens
# OLLAMA_LLM_LOGITS_ALL=False
# ollama_llm -- Only load vocabulary
# OLLAMA_LLM_VOCAB_ONLY=False
# ollama_llm -- Use memory mapping for model files
# OLLAMA_LLM_USE_MMAP=True
# ollama_llm -- Lock model in memory
# OLLAMA_LLM_USE_MLOCK=False
# ollama_llm -- Only use for embeddings
# OLLAMA_LLM_EMBEDDING_ONLY=False
# ollama_llm -- Penalize newline tokens
# OLLAMA_LLM_PENALIZE_NEWLINE=True
# ollama_llm -- Stop sequences (comma-separated string)
# OLLAMA_LLM_STOP=
#
# End of .env entries for LightRAG binding options
################################################################################
-55
View File
@@ -1,55 +0,0 @@
from openai import OpenAI
# os.environ["OPENAI_API_KEY"] = ""
def openai_complete_if_cache(
model="gpt-4o-mini", prompt=None, system_prompt=None, history_messages=[], **kwargs
) -> str:
openai_client = OpenAI()
messages = []
if system_prompt:
messages.append({"role": "system", "content": system_prompt})
messages.extend(history_messages)
messages.append({"role": "user", "content": prompt})
response = openai_client.chat.completions.create(
model=model, messages=messages, **kwargs
)
return response.choices[0].message.content
if __name__ == "__main__":
description = ""
prompt = f"""
Given the following description of a dataset:
{description}
Please identify 5 potential users who would engage with this dataset. For each user, list 5 tasks they would perform with this dataset. Then, for each (user, task) combination, generate 5 questions that require a high-level understanding of the entire dataset.
Output the results in the following structure:
- User 1: [user description]
- Task 1: [task description]
- Question 1:
- Question 2:
- Question 3:
- Question 4:
- Question 5:
- Task 2: [task description]
...
- Task 5: [task description]
- User 2: [user description]
...
- User 5: [user description]
...
"""
result = openai_complete_if_cache(model="gpt-4o-mini", prompt=prompt)
file_path = "./queries.txt"
with open(file_path, "w") as file:
file.write(result)
print(f"Queries written to {file_path}")
@@ -1,34 +0,0 @@
import pipmaster as pm
if not pm.is_installed("pyvis"):
pm.install("pyvis")
if not pm.is_installed("networkx"):
pm.install("networkx")
import networkx as nx
from pyvis.network import Network
import random
# Load the GraphML file
G = nx.read_graphml("./dickens/graph_chunk_entity_relation.graphml")
# Create a Pyvis network
net = Network(height="100vh", notebook=True)
# Convert NetworkX graph to Pyvis network
net.from_nx(G)
# Add colors and title to nodes
for node in net.nodes:
node["color"] = "#{:06x}".format(random.randint(0, 0xFFFFFF))
if "description" in node:
node["title"] = node["description"]
# Add title to edges
for edge in net.edges:
if "description" in edge:
edge["title"] = edge["description"]
# Save and display the network
net.show("knowledge_graph.html")
@@ -1,186 +0,0 @@
import os
import json
import xml.etree.ElementTree as ET
from neo4j import GraphDatabase
# Constants
WORKING_DIR = "./dickens"
BATCH_SIZE_NODES = 500
BATCH_SIZE_EDGES = 100
# Neo4j connection credentials
NEO4J_URI = "bolt://localhost:7687"
NEO4J_USERNAME = "neo4j"
NEO4J_PASSWORD = "your_password"
def xml_to_json(xml_file):
try:
tree = ET.parse(xml_file)
root = tree.getroot()
# Print the root element's tag and attributes to confirm the file has been correctly loaded
print(f"Root element: {root.tag}")
print(f"Root attributes: {root.attrib}")
data = {"nodes": [], "edges": []}
# Use namespace
namespace = {"": "http://graphml.graphdrawing.org/xmlns"}
for node in root.findall(".//node", namespace):
node_data = {
"id": node.get("id").strip('"'),
"entity_type": node.find("./data[@key='d1']", namespace).text.strip('"')
if node.find("./data[@key='d1']", namespace) is not None
else "",
"description": node.find("./data[@key='d2']", namespace).text
if node.find("./data[@key='d2']", namespace) is not None
else "",
"source_id": node.find("./data[@key='d3']", namespace).text
if node.find("./data[@key='d3']", namespace) is not None
else "",
}
data["nodes"].append(node_data)
for edge in root.findall(".//edge", namespace):
edge_data = {
"source": edge.get("source").strip('"'),
"target": edge.get("target").strip('"'),
"weight": float(edge.find("./data[@key='d5']", namespace).text)
if edge.find("./data[@key='d5']", namespace) is not None
else 0.0,
"description": edge.find("./data[@key='d6']", namespace).text
if edge.find("./data[@key='d6']", namespace) is not None
else "",
"keywords": edge.find("./data[@key='d9']", namespace).text
if edge.find("./data[@key='d9']", namespace) is not None
else "",
"source_id": edge.find("./data[@key='d8']", namespace).text
if edge.find("./data[@key='d8']", namespace) is not None
else "",
}
data["edges"].append(edge_data)
# Print the number of nodes and edges found
print(f"Found {len(data['nodes'])} nodes and {len(data['edges'])} edges")
return data
except ET.ParseError as e:
print(f"Error parsing XML file: {e}")
return None
except Exception as e:
print(f"An error occurred: {e}")
return None
def convert_xml_to_json(xml_path, output_path):
"""Converts XML file to JSON and saves the output."""
if not os.path.exists(xml_path):
print(f"Error: File not found - {xml_path}")
return None
json_data = xml_to_json(xml_path)
if json_data:
with open(output_path, "w", encoding="utf-8") as f:
json.dump(json_data, f, ensure_ascii=False, indent=2)
print(f"JSON file created: {output_path}")
return json_data
else:
print("Failed to create JSON data")
return None
def process_in_batches(tx, query, data, batch_size):
"""Process data in batches and execute the given query."""
for i in range(0, len(data), batch_size):
batch = data[i : i + batch_size]
tx.run(query, {"nodes": batch} if "nodes" in query else {"edges": batch})
def main():
# Paths
xml_file = os.path.join(WORKING_DIR, "graph_chunk_entity_relation.graphml")
json_file = os.path.join(WORKING_DIR, "graph_data.json")
# Convert XML to JSON
json_data = convert_xml_to_json(xml_file, json_file)
if json_data is None:
return
# Load nodes and edges
nodes = json_data.get("nodes", [])
edges = json_data.get("edges", [])
# Neo4j queries
create_nodes_query = """
UNWIND $nodes AS node
MERGE (e:Entity {id: node.id})
SET e.entity_type = node.entity_type,
e.description = node.description,
e.source_id = node.source_id,
e.displayName = node.id
REMOVE e:Entity
WITH e, node
CALL apoc.create.addLabels(e, [node.id]) YIELD node AS labeledNode
RETURN count(*)
"""
create_edges_query = """
UNWIND $edges AS edge
MATCH (source {id: edge.source})
MATCH (target {id: edge.target})
WITH source, target, edge,
CASE
WHEN edge.keywords CONTAINS 'lead' THEN 'lead'
WHEN edge.keywords CONTAINS 'participate' THEN 'participate'
WHEN edge.keywords CONTAINS 'uses' THEN 'uses'
WHEN edge.keywords CONTAINS 'located' THEN 'located'
WHEN edge.keywords CONTAINS 'occurs' THEN 'occurs'
ELSE REPLACE(SPLIT(edge.keywords, ',')[0], '\"', '')
END AS relType
CALL apoc.create.relationship(source, relType, {
weight: edge.weight,
description: edge.description,
keywords: edge.keywords,
source_id: edge.source_id
}, target) YIELD rel
RETURN count(*)
"""
set_displayname_and_labels_query = """
MATCH (n)
SET n.displayName = n.id
WITH n
CALL apoc.create.setLabels(n, [n.entity_type]) YIELD node
RETURN count(*)
"""
# Create a Neo4j driver
driver = GraphDatabase.driver(NEO4J_URI, auth=(NEO4J_USERNAME, NEO4J_PASSWORD))
try:
# Execute queries in batches
with driver.session() as session:
# Insert nodes in batches
session.execute_write(
process_in_batches, create_nodes_query, nodes, BATCH_SIZE_NODES
)
# Insert edges in batches
session.execute_write(
process_in_batches, create_edges_query, edges, BATCH_SIZE_EDGES
)
# Set displayName and labels
session.run(set_displayname_and_labels_query)
except Exception as e:
print(f"Error occurred: {e}")
finally:
driver.close()
if __name__ == "__main__":
main()
-115
View File
@@ -1,115 +0,0 @@
import os
from lightrag import LightRAG
from lightrag.llm.openai import gpt_4o_mini_complete
#########
# Uncomment the below two lines if running in a jupyter notebook to handle the async nature of rag.insert()
# import nest_asyncio
# nest_asyncio.apply()
#########
WORKING_DIR = "./custom_kg"
if not os.path.exists(WORKING_DIR):
os.mkdir(WORKING_DIR)
rag = LightRAG(
working_dir=WORKING_DIR,
llm_model_func=gpt_4o_mini_complete, # Use gpt_4o_mini_complete LLM model
# llm_model_func=gpt_4o_complete # Optionally, use a stronger model
)
custom_kg = {
"entities": [
{
"entity_name": "CompanyA",
"entity_type": "Organization",
"description": "A major technology company",
"source_id": "Source1",
},
{
"entity_name": "ProductX",
"entity_type": "Product",
"description": "A popular product developed by CompanyA",
"source_id": "Source1",
},
{
"entity_name": "PersonA",
"entity_type": "Person",
"description": "A renowned researcher in AI",
"source_id": "Source2",
},
{
"entity_name": "UniversityB",
"entity_type": "Organization",
"description": "A leading university specializing in technology and sciences",
"source_id": "Source2",
},
{
"entity_name": "CityC",
"entity_type": "Location",
"description": "A large metropolitan city known for its culture and economy",
"source_id": "Source3",
},
{
"entity_name": "EventY",
"entity_type": "Event",
"description": "An annual technology conference held in CityC",
"source_id": "Source3",
},
],
"relationships": [
{
"src_id": "CompanyA",
"tgt_id": "ProductX",
"description": "CompanyA develops ProductX",
"keywords": "develop, produce",
"weight": 1.0,
"source_id": "Source1",
},
{
"src_id": "PersonA",
"tgt_id": "UniversityB",
"description": "PersonA works at UniversityB",
"keywords": "employment, affiliation",
"weight": 0.9,
"source_id": "Source2",
},
{
"src_id": "CityC",
"tgt_id": "EventY",
"description": "EventY is hosted in CityC",
"keywords": "host, location",
"weight": 0.8,
"source_id": "Source3",
},
],
"chunks": [
{
"content": "ProductX, developed by CompanyA, has revolutionized the market with its cutting-edge features.",
"source_id": "Source1",
"source_chunk_index": 0,
},
{
"content": "One outstanding feature of ProductX is its advanced AI capabilities.",
"source_id": "Source1",
"chunk_order_index": 1,
},
{
"content": "PersonA is a prominent researcher at UniversityB, focusing on artificial intelligence and machine learning.",
"source_id": "Source2",
"source_chunk_index": 0,
},
{
"content": "EventY, held in CityC, attracts technology enthusiasts and companies from around the globe.",
"source_id": "Source3",
"source_chunk_index": 0,
},
{
"content": "None",
"source_id": "UNKNOWN",
"source_chunk_index": 0,
},
],
}
rag.insert_custom_kg(custom_kg)
@@ -1,126 +0,0 @@
import os
import asyncio
from lightrag import LightRAG, QueryParam
from lightrag.utils import EmbeddingFunc
import numpy as np
from dotenv import load_dotenv
import logging
from openai import AzureOpenAI
from lightrag.kg.shared_storage import initialize_pipeline_status
logging.basicConfig(level=logging.INFO)
load_dotenv()
AZURE_OPENAI_API_VERSION = os.getenv("AZURE_OPENAI_API_VERSION")
AZURE_OPENAI_DEPLOYMENT = os.getenv("AZURE_OPENAI_DEPLOYMENT")
AZURE_OPENAI_API_KEY = os.getenv("AZURE_OPENAI_API_KEY")
AZURE_OPENAI_ENDPOINT = os.getenv("AZURE_OPENAI_ENDPOINT")
AZURE_EMBEDDING_DEPLOYMENT = os.getenv("AZURE_EMBEDDING_DEPLOYMENT")
AZURE_EMBEDDING_API_VERSION = os.getenv("AZURE_EMBEDDING_API_VERSION")
WORKING_DIR = "./dickens"
if os.path.exists(WORKING_DIR):
import shutil
shutil.rmtree(WORKING_DIR)
os.mkdir(WORKING_DIR)
async def llm_model_func(
prompt, system_prompt=None, history_messages=[], keyword_extraction=False, **kwargs
) -> str:
client = AzureOpenAI(
api_key=AZURE_OPENAI_API_KEY,
api_version=AZURE_OPENAI_API_VERSION,
azure_endpoint=AZURE_OPENAI_ENDPOINT,
)
messages = []
if system_prompt:
messages.append({"role": "system", "content": system_prompt})
if history_messages:
messages.extend(history_messages)
messages.append({"role": "user", "content": prompt})
chat_completion = client.chat.completions.create(
model=AZURE_OPENAI_DEPLOYMENT, # model = "deployment_name".
messages=messages,
temperature=kwargs.get("temperature", 0),
top_p=kwargs.get("top_p", 1),
n=kwargs.get("n", 1),
)
return chat_completion.choices[0].message.content
async def embedding_func(texts: list[str]) -> np.ndarray:
client = AzureOpenAI(
api_key=AZURE_OPENAI_API_KEY,
api_version=AZURE_EMBEDDING_API_VERSION,
azure_endpoint=AZURE_OPENAI_ENDPOINT,
)
embedding = client.embeddings.create(model=AZURE_EMBEDDING_DEPLOYMENT, input=texts)
embeddings = [item.embedding for item in embedding.data]
return np.array(embeddings)
async def test_funcs():
result = await llm_model_func("How are you?")
print("Resposta do llm_model_func: ", result)
result = await embedding_func(["How are you?"])
print("Resultado do embedding_func: ", result.shape)
print("Dimensão da embedding: ", result.shape[1])
asyncio.run(test_funcs())
embedding_dimension = 3072
async def initialize_rag():
rag = LightRAG(
working_dir=WORKING_DIR,
llm_model_func=llm_model_func,
embedding_func=EmbeddingFunc(
embedding_dim=embedding_dimension,
max_token_size=8192,
func=embedding_func,
),
)
await rag.initialize_storages()
await initialize_pipeline_status()
return rag
def main():
rag = asyncio.run(initialize_rag())
book1 = open("./book_1.txt", encoding="utf-8")
book2 = open("./book_2.txt", encoding="utf-8")
rag.insert([book1.read(), book2.read()])
query_text = "What are the main themes?"
print("Result (Naive):")
print(rag.query(query_text, param=QueryParam(mode="naive")))
print("\nResult (Local):")
print(rag.query(query_text, param=QueryParam(mode="local")))
print("\nResult (Global):")
print(rag.query(query_text, param=QueryParam(mode="global")))
print("\nResult (Hybrid):")
print(rag.query(query_text, param=QueryParam(mode="hybrid")))
if __name__ == "__main__":
main()
-217
View File
@@ -1,217 +0,0 @@
import asyncio
import os
import inspect
import logging
import logging.config
from lightrag import LightRAG, QueryParam
from lightrag.llm.ollama import ollama_model_complete, ollama_embed
from lightrag.utils import EmbeddingFunc, logger, set_verbose_debug
from lightrag.kg.shared_storage import initialize_pipeline_status
from dotenv import load_dotenv
load_dotenv(dotenv_path=".env", override=False)
WORKING_DIR = "./dickens"
def configure_logging():
"""Configure logging for the application"""
# Reset any existing handlers to ensure clean configuration
for logger_name in ["uvicorn", "uvicorn.access", "uvicorn.error", "lightrag"]:
logger_instance = logging.getLogger(logger_name)
logger_instance.handlers = []
logger_instance.filters = []
# Get log directory path from environment variable or use current directory
log_dir = os.getenv("LOG_DIR", os.getcwd())
log_file_path = os.path.abspath(os.path.join(log_dir, "lightrag_ollama_demo.log"))
print(f"\nLightRAG compatible demo log file: {log_file_path}\n")
os.makedirs(os.path.dirname(log_file_path), exist_ok=True)
# Get log file max size and backup count from environment variables
log_max_bytes = int(os.getenv("LOG_MAX_BYTES", 10485760)) # Default 10MB
log_backup_count = int(os.getenv("LOG_BACKUP_COUNT", 5)) # Default 5 backups
logging.config.dictConfig(
{
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"default": {
"format": "%(levelname)s: %(message)s",
},
"detailed": {
"format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s",
},
},
"handlers": {
"console": {
"formatter": "default",
"class": "logging.StreamHandler",
"stream": "ext://sys.stderr",
},
"file": {
"formatter": "detailed",
"class": "logging.handlers.RotatingFileHandler",
"filename": log_file_path,
"maxBytes": log_max_bytes,
"backupCount": log_backup_count,
"encoding": "utf-8",
},
},
"loggers": {
"lightrag": {
"handlers": ["console", "file"],
"level": "INFO",
"propagate": False,
},
},
}
)
# Set the logger level to INFO
logger.setLevel(logging.INFO)
# Enable verbose debug if needed
set_verbose_debug(os.getenv("VERBOSE_DEBUG", "false").lower() == "true")
if not os.path.exists(WORKING_DIR):
os.mkdir(WORKING_DIR)
async def initialize_rag():
rag = LightRAG(
working_dir=WORKING_DIR,
llm_model_func=ollama_model_complete,
llm_model_name=os.getenv("LLM_MODEL", "qwen2.5-coder:7b"),
summary_max_tokens=8192,
llm_model_kwargs={
"host": os.getenv("LLM_BINDING_HOST", "http://localhost:11434"),
"options": {"num_ctx": 8192},
"timeout": int(os.getenv("TIMEOUT", "300")),
},
embedding_func=EmbeddingFunc(
embedding_dim=int(os.getenv("EMBEDDING_DIM", "1024")),
max_token_size=int(os.getenv("MAX_EMBED_TOKENS", "8192")),
func=lambda texts: ollama_embed(
texts,
embed_model=os.getenv("EMBEDDING_MODEL", "bge-m3:latest"),
host=os.getenv("EMBEDDING_BINDING_HOST", "http://localhost:11434"),
),
),
)
await rag.initialize_storages()
await initialize_pipeline_status()
return rag
async def print_stream(stream):
async for chunk in stream:
print(chunk, end="", flush=True)
async def main():
try:
# Clear old data files
files_to_delete = [
"graph_chunk_entity_relation.graphml",
"kv_store_doc_status.json",
"kv_store_full_docs.json",
"kv_store_text_chunks.json",
"vdb_chunks.json",
"vdb_entities.json",
"vdb_relationships.json",
]
for file in files_to_delete:
file_path = os.path.join(WORKING_DIR, file)
if os.path.exists(file_path):
os.remove(file_path)
print(f"Deleting old file:: {file_path}")
# Initialize RAG instance
rag = await initialize_rag()
# Test embedding function
test_text = ["This is a test string for embedding."]
embedding = await rag.embedding_func(test_text)
embedding_dim = embedding.shape[1]
print("\n=======================")
print("Test embedding function")
print("========================")
print(f"Test dict: {test_text}")
print(f"Detected embedding dimension: {embedding_dim}\n\n")
with open("./book.txt", "r", encoding="utf-8") as f:
await rag.ainsert(f.read())
# Perform naive search
print("\n=====================")
print("Query mode: naive")
print("=====================")
resp = await rag.aquery(
"What are the top themes in this story?",
param=QueryParam(mode="naive", stream=True),
)
if inspect.isasyncgen(resp):
await print_stream(resp)
else:
print(resp)
# Perform local search
print("\n=====================")
print("Query mode: local")
print("=====================")
resp = await rag.aquery(
"What are the top themes in this story?",
param=QueryParam(mode="local", stream=True),
)
if inspect.isasyncgen(resp):
await print_stream(resp)
else:
print(resp)
# Perform global search
print("\n=====================")
print("Query mode: global")
print("=====================")
resp = await rag.aquery(
"What are the top themes in this story?",
param=QueryParam(mode="global", stream=True),
)
if inspect.isasyncgen(resp):
await print_stream(resp)
else:
print(resp)
# Perform hybrid search
print("\n=====================")
print("Query mode: hybrid")
print("=====================")
resp = await rag.aquery(
"What are the top themes in this story?",
param=QueryParam(mode="hybrid", stream=True),
)
if inspect.isasyncgen(resp):
await print_stream(resp)
else:
print(resp)
except Exception as e:
print(f"An error occurred: {e}")
finally:
if rag:
await rag.llm_response_cache.index_done_callback()
await rag.finalize_storages()
if __name__ == "__main__":
# Configure logging before running the main function
configure_logging()
asyncio.run(main())
print("\nDone!")
@@ -1,227 +0,0 @@
import os
import asyncio
import inspect
import logging
import logging.config
from lightrag import LightRAG, QueryParam
from lightrag.llm.openai import openai_complete_if_cache
from lightrag.llm.ollama import ollama_embed
from lightrag.utils import EmbeddingFunc, logger, set_verbose_debug
from lightrag.kg.shared_storage import initialize_pipeline_status
from dotenv import load_dotenv
load_dotenv(dotenv_path=".env", override=False)
WORKING_DIR = "./dickens"
def configure_logging():
"""Configure logging for the application"""
# Reset any existing handlers to ensure clean configuration
for logger_name in ["uvicorn", "uvicorn.access", "uvicorn.error", "lightrag"]:
logger_instance = logging.getLogger(logger_name)
logger_instance.handlers = []
logger_instance.filters = []
# Get log directory path from environment variable or use current directory
log_dir = os.getenv("LOG_DIR", os.getcwd())
log_file_path = os.path.abspath(
os.path.join(log_dir, "lightrag_compatible_demo.log")
)
print(f"\nLightRAG compatible demo log file: {log_file_path}\n")
os.makedirs(os.path.dirname(log_dir), exist_ok=True)
# Get log file max size and backup count from environment variables
log_max_bytes = int(os.getenv("LOG_MAX_BYTES", 10485760)) # Default 10MB
log_backup_count = int(os.getenv("LOG_BACKUP_COUNT", 5)) # Default 5 backups
logging.config.dictConfig(
{
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"default": {
"format": "%(levelname)s: %(message)s",
},
"detailed": {
"format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s",
},
},
"handlers": {
"console": {
"formatter": "default",
"class": "logging.StreamHandler",
"stream": "ext://sys.stderr",
},
"file": {
"formatter": "detailed",
"class": "logging.handlers.RotatingFileHandler",
"filename": log_file_path,
"maxBytes": log_max_bytes,
"backupCount": log_backup_count,
"encoding": "utf-8",
},
},
"loggers": {
"lightrag": {
"handlers": ["console", "file"],
"level": "INFO",
"propagate": False,
},
},
}
)
# Set the logger level to INFO
logger.setLevel(logging.INFO)
# Enable verbose debug if needed
set_verbose_debug(os.getenv("VERBOSE_DEBUG", "false").lower() == "true")
if not os.path.exists(WORKING_DIR):
os.mkdir(WORKING_DIR)
async def llm_model_func(
prompt, system_prompt=None, history_messages=[], keyword_extraction=False, **kwargs
) -> str:
return await openai_complete_if_cache(
os.getenv("LLM_MODEL", "deepseek-chat"),
prompt,
system_prompt=system_prompt,
history_messages=history_messages,
api_key=os.getenv("LLM_BINDING_API_KEY") or os.getenv("OPENAI_API_KEY"),
base_url=os.getenv("LLM_BINDING_HOST", "https://api.deepseek.com"),
**kwargs,
)
async def print_stream(stream):
async for chunk in stream:
if chunk:
print(chunk, end="", flush=True)
async def initialize_rag():
rag = LightRAG(
working_dir=WORKING_DIR,
llm_model_func=llm_model_func,
embedding_func=EmbeddingFunc(
embedding_dim=int(os.getenv("EMBEDDING_DIM", "1024")),
max_token_size=int(os.getenv("MAX_EMBED_TOKENS", "8192")),
func=lambda texts: ollama_embed(
texts,
embed_model=os.getenv("EMBEDDING_MODEL", "bge-m3:latest"),
host=os.getenv("EMBEDDING_BINDING_HOST", "http://localhost:11434"),
),
),
)
await rag.initialize_storages()
await initialize_pipeline_status()
return rag
async def main():
try:
# Clear old data files
files_to_delete = [
"graph_chunk_entity_relation.graphml",
"kv_store_doc_status.json",
"kv_store_full_docs.json",
"kv_store_text_chunks.json",
"vdb_chunks.json",
"vdb_entities.json",
"vdb_relationships.json",
]
for file in files_to_delete:
file_path = os.path.join(WORKING_DIR, file)
if os.path.exists(file_path):
os.remove(file_path)
print(f"Deleting old file:: {file_path}")
# Initialize RAG instance
rag = await initialize_rag()
# Test embedding function
test_text = ["This is a test string for embedding."]
embedding = await rag.embedding_func(test_text)
embedding_dim = embedding.shape[1]
print("\n=======================")
print("Test embedding function")
print("========================")
print(f"Test dict: {test_text}")
print(f"Detected embedding dimension: {embedding_dim}\n\n")
with open("./book.txt", "r", encoding="utf-8") as f:
await rag.ainsert(f.read())
# Perform naive search
print("\n=====================")
print("Query mode: naive")
print("=====================")
resp = await rag.aquery(
"What are the top themes in this story?",
param=QueryParam(mode="naive", stream=True),
)
if inspect.isasyncgen(resp):
await print_stream(resp)
else:
print(resp)
# Perform local search
print("\n=====================")
print("Query mode: local")
print("=====================")
resp = await rag.aquery(
"What are the top themes in this story?",
param=QueryParam(mode="local", stream=True),
)
if inspect.isasyncgen(resp):
await print_stream(resp)
else:
print(resp)
# Perform global search
print("\n=====================")
print("Query mode: global")
print("=====================")
resp = await rag.aquery(
"What are the top themes in this story?",
param=QueryParam(mode="global", stream=True),
)
if inspect.isasyncgen(resp):
await print_stream(resp)
else:
print(resp)
# Perform hybrid search
print("\n=====================")
print("Query mode: hybrid")
print("=====================")
resp = await rag.aquery(
"What are the top themes in this story?",
param=QueryParam(mode="hybrid", stream=True),
)
if inspect.isasyncgen(resp):
await print_stream(resp)
else:
print(resp)
except Exception as e:
print(f"An error occurred: {e}")
finally:
if rag:
await rag.finalize_storages()
if __name__ == "__main__":
# Configure logging before running the main function
configure_logging()
asyncio.run(main())
print("\nDone!")
-189
View File
@@ -1,189 +0,0 @@
import os
import asyncio
import logging
import logging.config
from lightrag import LightRAG, QueryParam
from lightrag.llm.openai import gpt_4o_mini_complete, openai_embed
from lightrag.kg.shared_storage import initialize_pipeline_status
from lightrag.utils import logger, set_verbose_debug
WORKING_DIR = "./dickens"
def configure_logging():
"""Configure logging for the application"""
# Reset any existing handlers to ensure clean configuration
for logger_name in ["uvicorn", "uvicorn.access", "uvicorn.error", "lightrag"]:
logger_instance = logging.getLogger(logger_name)
logger_instance.handlers = []
logger_instance.filters = []
# Get log directory path from environment variable or use current directory
log_dir = os.getenv("LOG_DIR", os.getcwd())
log_file_path = os.path.abspath(os.path.join(log_dir, "lightrag_demo.log"))
print(f"\nLightRAG demo log file: {log_file_path}\n")
os.makedirs(os.path.dirname(log_dir), exist_ok=True)
# Get log file max size and backup count from environment variables
log_max_bytes = int(os.getenv("LOG_MAX_BYTES", 10485760)) # Default 10MB
log_backup_count = int(os.getenv("LOG_BACKUP_COUNT", 5)) # Default 5 backups
logging.config.dictConfig(
{
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"default": {
"format": "%(levelname)s: %(message)s",
},
"detailed": {
"format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s",
},
},
"handlers": {
"console": {
"formatter": "default",
"class": "logging.StreamHandler",
"stream": "ext://sys.stderr",
},
"file": {
"formatter": "detailed",
"class": "logging.handlers.RotatingFileHandler",
"filename": log_file_path,
"maxBytes": log_max_bytes,
"backupCount": log_backup_count,
"encoding": "utf-8",
},
},
"loggers": {
"lightrag": {
"handlers": ["console", "file"],
"level": "INFO",
"propagate": False,
},
},
}
)
# Set the logger level to INFO
logger.setLevel(logging.INFO)
# Enable verbose debug if needed
set_verbose_debug(os.getenv("VERBOSE_DEBUG", "false").lower() == "true")
if not os.path.exists(WORKING_DIR):
os.mkdir(WORKING_DIR)
async def initialize_rag():
rag = LightRAG(
working_dir=WORKING_DIR,
embedding_func=openai_embed,
llm_model_func=gpt_4o_mini_complete,
)
await rag.initialize_storages()
await initialize_pipeline_status()
return rag
async def main():
# Check if OPENAI_API_KEY environment variable exists
if not os.getenv("OPENAI_API_KEY"):
print(
"Error: OPENAI_API_KEY environment variable is not set. Please set this variable before running the program."
)
print("You can set the environment variable by running:")
print(" export OPENAI_API_KEY='your-openai-api-key'")
return # Exit the async function
try:
# Clear old data files
files_to_delete = [
"graph_chunk_entity_relation.graphml",
"kv_store_doc_status.json",
"kv_store_full_docs.json",
"kv_store_text_chunks.json",
"vdb_chunks.json",
"vdb_entities.json",
"vdb_relationships.json",
]
for file in files_to_delete:
file_path = os.path.join(WORKING_DIR, file)
if os.path.exists(file_path):
os.remove(file_path)
print(f"Deleting old file:: {file_path}")
# Initialize RAG instance
rag = await initialize_rag()
# Test embedding function
test_text = ["This is a test string for embedding."]
embedding = await rag.embedding_func(test_text)
embedding_dim = embedding.shape[1]
print("\n=======================")
print("Test embedding function")
print("========================")
print(f"Test dict: {test_text}")
print(f"Detected embedding dimension: {embedding_dim}\n\n")
with open("./book.txt", "r", encoding="utf-8") as f:
await rag.ainsert(f.read())
# Perform naive search
print("\n=====================")
print("Query mode: naive")
print("=====================")
print(
await rag.aquery(
"What are the top themes in this story?", param=QueryParam(mode="naive")
)
)
# Perform local search
print("\n=====================")
print("Query mode: local")
print("=====================")
print(
await rag.aquery(
"What are the top themes in this story?", param=QueryParam(mode="local")
)
)
# Perform global search
print("\n=====================")
print("Query mode: global")
print("=====================")
print(
await rag.aquery(
"What are the top themes in this story?",
param=QueryParam(mode="global"),
)
)
# Perform hybrid search
print("\n=====================")
print("Query mode: hybrid")
print("=====================")
print(
await rag.aquery(
"What are the top themes in this story?",
param=QueryParam(mode="hybrid"),
)
)
except Exception as e:
print(f"An error occurred: {e}")
finally:
if rag:
await rag.finalize_storages()
if __name__ == "__main__":
# Configure logging before running the main function
configure_logging()
asyncio.run(main())
print("\nDone!")
@@ -1,107 +0,0 @@
import os
import asyncio
from lightrag import LightRAG, QueryParam
from lightrag.llm.openai import gpt_4o_mini_complete, openai_embed
from lightrag.utils import EmbeddingFunc
import numpy as np
from lightrag.kg.shared_storage import initialize_pipeline_status
#########
# Uncomment the below two lines if running in a jupyter notebook to handle the async nature of rag.insert()
# import nest_asyncio
# nest_asyncio.apply()
#########
WORKING_DIR = "./mongodb_test_dir"
if not os.path.exists(WORKING_DIR):
os.mkdir(WORKING_DIR)
os.environ["OPENAI_API_KEY"] = "sk-"
os.environ["MONGO_URI"] = "mongodb://0.0.0.0:27017/?directConnection=true"
os.environ["MONGO_DATABASE"] = "LightRAG"
os.environ["MONGO_KG_COLLECTION"] = "MDB_KG"
# Embedding Configuration and Functions
EMBEDDING_MODEL = os.environ.get("EMBEDDING_MODEL", "text-embedding-3-large")
EMBEDDING_MAX_TOKEN_SIZE = int(os.environ.get("EMBEDDING_MAX_TOKEN_SIZE", 8192))
async def embedding_func(texts: list[str]) -> np.ndarray:
return await openai_embed(
texts,
model=EMBEDDING_MODEL,
)
async def get_embedding_dimension():
test_text = ["This is a test sentence."]
embedding = await embedding_func(test_text)
return embedding.shape[1]
async def create_embedding_function_instance():
# Get embedding dimension
embedding_dimension = await get_embedding_dimension()
# Create embedding function instance
return EmbeddingFunc(
embedding_dim=embedding_dimension,
max_token_size=EMBEDDING_MAX_TOKEN_SIZE,
func=embedding_func,
)
async def initialize_rag():
embedding_func_instance = await create_embedding_function_instance()
rag = LightRAG(
working_dir=WORKING_DIR,
llm_model_func=gpt_4o_mini_complete,
embedding_func=embedding_func_instance,
graph_storage="MongoGraphStorage",
log_level="DEBUG",
)
await rag.initialize_storages()
await initialize_pipeline_status()
return rag
def main():
# Initialize RAG instance
rag = asyncio.run(initialize_rag())
with open("./book.txt", "r", encoding="utf-8") as f:
rag.insert(f.read())
# Perform naive search
print(
rag.query(
"What are the top themes in this story?", param=QueryParam(mode="naive")
)
)
# Perform local search
print(
rag.query(
"What are the top themes in this story?", param=QueryParam(mode="local")
)
)
# Perform global search
print(
rag.query(
"What are the top themes in this story?", param=QueryParam(mode="global")
)
)
# Perform hybrid search
print(
rag.query(
"What are the top themes in this story?", param=QueryParam(mode="hybrid")
)
)
if __name__ == "__main__":
main()
@@ -1,229 +0,0 @@
"""
Example of directly using modal processors
This example demonstrates how to use LightRAG's modal processors directly without going through MinerU.
"""
import asyncio
import argparse
from lightrag.llm.openai import openai_complete_if_cache, openai_embed
from lightrag.kg.shared_storage import initialize_pipeline_status
from lightrag import LightRAG
from lightrag.utils import EmbeddingFunc
from raganything.modalprocessors import (
ImageModalProcessor,
TableModalProcessor,
EquationModalProcessor,
)
WORKING_DIR = "./rag_storage"
def get_llm_model_func(api_key: str, base_url: str = None):
return (
lambda prompt,
system_prompt=None,
history_messages=[],
**kwargs: openai_complete_if_cache(
"gpt-4o-mini",
prompt,
system_prompt=system_prompt,
history_messages=history_messages,
api_key=api_key,
base_url=base_url,
**kwargs,
)
)
def get_vision_model_func(api_key: str, base_url: str = None):
return (
lambda prompt,
system_prompt=None,
history_messages=[],
image_data=None,
**kwargs: openai_complete_if_cache(
"gpt-4o",
"",
system_prompt=None,
history_messages=[],
messages=[
{"role": "system", "content": system_prompt} if system_prompt else None,
{
"role": "user",
"content": [
{"type": "text", "text": prompt},
{
"type": "image_url",
"image_url": {
"url": f"data:image/jpeg;base64,{image_data}"
},
},
],
}
if image_data
else {"role": "user", "content": prompt},
],
api_key=api_key,
base_url=base_url,
**kwargs,
)
if image_data
else openai_complete_if_cache(
"gpt-4o-mini",
prompt,
system_prompt=system_prompt,
history_messages=history_messages,
api_key=api_key,
base_url=base_url,
**kwargs,
)
)
async def process_image_example(lightrag: LightRAG, vision_model_func):
"""Example of processing an image"""
# Create image processor
image_processor = ImageModalProcessor(
lightrag=lightrag, modal_caption_func=vision_model_func
)
# Prepare image content
image_content = {
"img_path": "image.jpg",
"img_caption": ["Example image caption"],
"img_footnote": ["Example image footnote"],
}
# Process image
description, entity_info = await image_processor.process_multimodal_content(
modal_content=image_content,
content_type="image",
file_path="image_example.jpg",
entity_name="Example Image",
)
print("Image Processing Results:")
print(f"Description: {description}")
print(f"Entity Info: {entity_info}")
async def process_table_example(lightrag: LightRAG, llm_model_func):
"""Example of processing a table"""
# Create table processor
table_processor = TableModalProcessor(
lightrag=lightrag, modal_caption_func=llm_model_func
)
# Prepare table content
table_content = {
"table_body": """
| Name | Age | Occupation |
|------|-----|------------|
| John | 25 | Engineer |
| Mary | 30 | Designer |
""",
"table_caption": ["Employee Information Table"],
"table_footnote": ["Data updated as of 2024"],
}
# Process table
description, entity_info = await table_processor.process_multimodal_content(
modal_content=table_content,
content_type="table",
file_path="table_example.md",
entity_name="Employee Table",
)
print("\nTable Processing Results:")
print(f"Description: {description}")
print(f"Entity Info: {entity_info}")
async def process_equation_example(lightrag: LightRAG, llm_model_func):
"""Example of processing a mathematical equation"""
# Create equation processor
equation_processor = EquationModalProcessor(
lightrag=lightrag, modal_caption_func=llm_model_func
)
# Prepare equation content
equation_content = {"text": "E = mc^2", "text_format": "LaTeX"}
# Process equation
description, entity_info = await equation_processor.process_multimodal_content(
modal_content=equation_content,
content_type="equation",
file_path="equation_example.txt",
entity_name="Mass-Energy Equivalence",
)
print("\nEquation Processing Results:")
print(f"Description: {description}")
print(f"Entity Info: {entity_info}")
async def initialize_rag(api_key: str, base_url: str = None):
rag = LightRAG(
working_dir=WORKING_DIR,
embedding_func=EmbeddingFunc(
embedding_dim=3072,
max_token_size=8192,
func=lambda texts: openai_embed(
texts,
model="text-embedding-3-large",
api_key=api_key,
base_url=base_url,
),
),
llm_model_func=lambda prompt,
system_prompt=None,
history_messages=[],
**kwargs: openai_complete_if_cache(
"gpt-4o-mini",
prompt,
system_prompt=system_prompt,
history_messages=history_messages,
api_key=api_key,
base_url=base_url,
**kwargs,
),
)
await rag.initialize_storages()
await initialize_pipeline_status()
return rag
def main():
"""Main function to run the example"""
parser = argparse.ArgumentParser(description="Modal Processors Example")
parser.add_argument("--api-key", required=True, help="OpenAI API key")
parser.add_argument("--base-url", help="Optional base URL for API")
parser.add_argument(
"--working-dir", "-w", default=WORKING_DIR, help="Working directory path"
)
args = parser.parse_args()
# Run examples
asyncio.run(main_async(args.api_key, args.base_url))
async def main_async(api_key: str, base_url: str = None):
# Initialize LightRAG
lightrag = await initialize_rag(api_key, base_url)
# Get model functions
llm_model_func = get_llm_model_func(api_key, base_url)
vision_model_func = get_vision_model_func(api_key, base_url)
# Run examples
await process_image_example(lightrag, vision_model_func)
await process_table_example(lightrag, llm_model_func)
await process_equation_example(lightrag, llm_model_func)
if __name__ == "__main__":
main()
-286
View File
@@ -1,286 +0,0 @@
#!/usr/bin/env python
"""
Example script demonstrating the integration of MinerU parser with RAGAnything
This example shows how to:
1. Process parsed documents with RAGAnything
2. Perform multimodal queries on the processed documents
3. Handle different types of content (text, images, tables)
"""
import os
import argparse
import asyncio
import logging
import logging.config
from pathlib import Path
# Add project root directory to Python path
import sys
sys.path.append(str(Path(__file__).parent.parent))
from lightrag.llm.openai import openai_complete_if_cache, openai_embed
from lightrag.utils import EmbeddingFunc, logger, set_verbose_debug
from raganything import RAGAnything, RAGAnythingConfig
def configure_logging():
"""Configure logging for the application"""
# Get log directory path from environment variable or use current directory
log_dir = os.getenv("LOG_DIR", os.getcwd())
log_file_path = os.path.abspath(os.path.join(log_dir, "raganything_example.log"))
print(f"\nRAGAnything example log file: {log_file_path}\n")
os.makedirs(os.path.dirname(log_dir), exist_ok=True)
# Get log file max size and backup count from environment variables
log_max_bytes = int(os.getenv("LOG_MAX_BYTES", 10485760)) # Default 10MB
log_backup_count = int(os.getenv("LOG_BACKUP_COUNT", 5)) # Default 5 backups
logging.config.dictConfig(
{
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"default": {
"format": "%(levelname)s: %(message)s",
},
"detailed": {
"format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s",
},
},
"handlers": {
"console": {
"formatter": "default",
"class": "logging.StreamHandler",
"stream": "ext://sys.stderr",
},
"file": {
"formatter": "detailed",
"class": "logging.handlers.RotatingFileHandler",
"filename": log_file_path,
"maxBytes": log_max_bytes,
"backupCount": log_backup_count,
"encoding": "utf-8",
},
},
"loggers": {
"lightrag": {
"handlers": ["console", "file"],
"level": "INFO",
"propagate": False,
},
},
}
)
# Set the logger level to INFO
logger.setLevel(logging.INFO)
# Enable verbose debug if needed
set_verbose_debug(os.getenv("VERBOSE", "false").lower() == "true")
async def process_with_rag(
file_path: str,
output_dir: str,
api_key: str,
base_url: str = None,
working_dir: str = None,
):
"""
Process document with RAGAnything
Args:
file_path: Path to the document
output_dir: Output directory for RAG results
api_key: OpenAI API key
base_url: Optional base URL for API
working_dir: Working directory for RAG storage
"""
try:
# Create RAGAnything configuration
config = RAGAnythingConfig(
working_dir=working_dir or "./rag_storage",
mineru_parse_method="auto",
enable_image_processing=True,
enable_table_processing=True,
enable_equation_processing=True,
)
# Define LLM model function
def llm_model_func(prompt, system_prompt=None, history_messages=[], **kwargs):
return openai_complete_if_cache(
"gpt-4o-mini",
prompt,
system_prompt=system_prompt,
history_messages=history_messages,
api_key=api_key,
base_url=base_url,
**kwargs,
)
# Define vision model function for image processing
def vision_model_func(
prompt, system_prompt=None, history_messages=[], image_data=None, **kwargs
):
if image_data:
return openai_complete_if_cache(
"gpt-4o",
"",
system_prompt=None,
history_messages=[],
messages=[
{"role": "system", "content": system_prompt}
if system_prompt
else None,
{
"role": "user",
"content": [
{"type": "text", "text": prompt},
{
"type": "image_url",
"image_url": {
"url": f"data:image/jpeg;base64,{image_data}"
},
},
],
}
if image_data
else {"role": "user", "content": prompt},
],
api_key=api_key,
base_url=base_url,
**kwargs,
)
else:
return llm_model_func(prompt, system_prompt, history_messages, **kwargs)
# Define embedding function
embedding_func = EmbeddingFunc(
embedding_dim=3072,
max_token_size=8192,
func=lambda texts: openai_embed(
texts,
model="text-embedding-3-large",
api_key=api_key,
base_url=base_url,
),
)
# Initialize RAGAnything with new dataclass structure
rag = RAGAnything(
config=config,
llm_model_func=llm_model_func,
vision_model_func=vision_model_func,
embedding_func=embedding_func,
)
# Process document
await rag.process_document_complete(
file_path=file_path, output_dir=output_dir, parse_method="auto"
)
# Example queries - demonstrating different query approaches
logger.info("\nQuerying processed document:")
# 1. Pure text queries using aquery()
text_queries = [
"What is the main content of the document?",
"What are the key topics discussed?",
]
for query in text_queries:
logger.info(f"\n[Text Query]: {query}")
result = await rag.aquery(query, mode="hybrid")
logger.info(f"Answer: {result}")
# 2. Multimodal query with specific multimodal content using aquery_with_multimodal()
logger.info(
"\n[Multimodal Query]: Analyzing performance data in context of document"
)
multimodal_result = await rag.aquery_with_multimodal(
"Compare this performance data with any similar results mentioned in the document",
multimodal_content=[
{
"type": "table",
"table_data": """Method,Accuracy,Processing_Time
RAGAnything,95.2%,120ms
Traditional_RAG,87.3%,180ms
Baseline,82.1%,200ms""",
"table_caption": "Performance comparison results",
}
],
mode="hybrid",
)
logger.info(f"Answer: {multimodal_result}")
# 3. Another multimodal query with equation content
logger.info("\n[Multimodal Query]: Mathematical formula analysis")
equation_result = await rag.aquery_with_multimodal(
"Explain this formula and relate it to any mathematical concepts in the document",
multimodal_content=[
{
"type": "equation",
"latex": "F1 = 2 \\cdot \\frac{precision \\cdot recall}{precision + recall}",
"equation_caption": "F1-score calculation formula",
}
],
mode="hybrid",
)
logger.info(f"Answer: {equation_result}")
except Exception as e:
logger.error(f"Error processing with RAG: {str(e)}")
import traceback
logger.error(traceback.format_exc())
def main():
"""Main function to run the example"""
parser = argparse.ArgumentParser(description="MinerU RAG Example")
parser.add_argument("file_path", help="Path to the document to process")
parser.add_argument(
"--working_dir", "-w", default="./rag_storage", help="Working directory path"
)
parser.add_argument(
"--output", "-o", default="./output", help="Output directory path"
)
parser.add_argument(
"--api-key",
default=os.getenv("OPENAI_API_KEY"),
help="OpenAI API key (defaults to OPENAI_API_KEY env var)",
)
parser.add_argument("--base-url", help="Optional base URL for API")
args = parser.parse_args()
# Check if API key is provided
if not args.api_key:
logger.error("Error: OpenAI API key is required")
logger.error("Set OPENAI_API_KEY environment variable or use --api-key option")
return
# Create output directory if specified
if args.output:
os.makedirs(args.output, exist_ok=True)
# Process with RAG
asyncio.run(
process_with_rag(
args.file_path, args.output, args.api_key, args.base_url, args.working_dir
)
)
if __name__ == "__main__":
# Configure logging first
configure_logging()
print("RAGAnything Example")
print("=" * 30)
print("Processing document with multimodal RAG pipeline")
print("=" * 30)
main()
-232
View File
@@ -1,232 +0,0 @@
"""
LightRAG Rerank Integration Example
This example demonstrates how to use rerank functionality with LightRAG
to improve retrieval quality across different query modes.
Configuration Required:
1. Set your OpenAI LLM API key and base URL with env vars
LLM_MODEL
LLM_BINDING_HOST
LLM_BINDING_API_KEY
2. Set your OpenAI embedding API key and base URL with env vars:
EMBEDDING_MODEL
EMBEDDING_DIM
EMBEDDING_BINDING_HOST
EMBEDDING_BINDING_API_KEY
3. Set your vLLM deployed AI rerank model setting with env vars:
RERANK_MODEL
RERANK_BINDING_HOST
RERANK_BINDING_API_KEY
Note: Rerank is controlled per query via the 'enable_rerank' parameter (default: True)
"""
import asyncio
import os
import numpy as np
from lightrag import LightRAG, QueryParam
from lightrag.llm.openai import openai_complete_if_cache, openai_embed
from lightrag.utils import EmbeddingFunc, setup_logger
from lightrag.kg.shared_storage import initialize_pipeline_status
from functools import partial
from lightrag.rerank import cohere_rerank
# Set up your working directory
WORKING_DIR = "./test_rerank"
setup_logger("test_rerank")
if not os.path.exists(WORKING_DIR):
os.mkdir(WORKING_DIR)
async def llm_model_func(
prompt, system_prompt=None, history_messages=[], **kwargs
) -> str:
return await openai_complete_if_cache(
os.getenv("LLM_MODEL"),
prompt,
system_prompt=system_prompt,
history_messages=history_messages,
api_key=os.getenv("LLM_BINDING_API_KEY"),
base_url=os.getenv("LLM_BINDING_HOST"),
**kwargs,
)
async def embedding_func(texts: list[str]) -> np.ndarray:
return await openai_embed(
texts,
model=os.getenv("EMBEDDING_MODEL"),
api_key=os.getenv("EMBEDDING_BINDING_API_KEY"),
base_url=os.getenv("EMBEDDING_BINDING_HOST"),
)
rerank_model_func = partial(
cohere_rerank,
model=os.getenv("RERANK_MODEL"),
api_key=os.getenv("RERANK_BINDING_API_KEY"),
base_url=os.getenv("RERANK_BINDING_HOST"),
)
async def create_rag_with_rerank():
"""Create LightRAG instance with rerank configuration"""
# Get embedding dimension
test_embedding = await embedding_func(["test"])
embedding_dim = test_embedding.shape[1]
print(f"Detected embedding dimension: {embedding_dim}")
# Method 1: Using custom rerank function
rag = LightRAG(
working_dir=WORKING_DIR,
llm_model_func=llm_model_func,
embedding_func=EmbeddingFunc(
embedding_dim=embedding_dim,
max_token_size=8192,
func=embedding_func,
),
# Rerank Configuration - provide the rerank function
rerank_model_func=rerank_model_func,
)
await rag.initialize_storages()
await initialize_pipeline_status()
return rag
async def test_rerank_with_different_settings():
"""
Test rerank functionality with different enable_rerank settings
"""
print("\n\n🚀 Setting up LightRAG with Rerank functionality...")
rag = await create_rag_with_rerank()
# Insert sample documents
sample_docs = [
"Reranking improves retrieval quality by re-ordering documents based on relevance.",
"LightRAG is a powerful retrieval-augmented generation system with multiple query modes.",
"Vector databases enable efficient similarity search in high-dimensional embedding spaces.",
"Natural language processing has evolved with large language models and transformers.",
"Machine learning algorithms can learn patterns from data without explicit programming.",
]
print("📄 Inserting sample documents...")
await rag.ainsert(sample_docs)
query = "How does reranking improve retrieval quality?"
print(f"\n🔍 Testing query: '{query}'")
print("=" * 80)
# Test with rerank enabled (default)
print("\n📊 Testing with enable_rerank=True (default):")
result_with_rerank = await rag.aquery(
query,
param=QueryParam(
mode="naive",
top_k=10,
chunk_top_k=5,
enable_rerank=True, # Explicitly enable rerank
),
)
print(f" Result length: {len(result_with_rerank)} characters")
print(f" Preview: {result_with_rerank[:100]}...")
# Test with rerank disabled
print("\n📊 Testing with enable_rerank=False:")
result_without_rerank = await rag.aquery(
query,
param=QueryParam(
mode="naive",
top_k=10,
chunk_top_k=5,
enable_rerank=False, # Disable rerank
),
)
print(f" Result length: {len(result_without_rerank)} characters")
print(f" Preview: {result_without_rerank[:100]}...")
# Test with default settings (enable_rerank defaults to True)
print("\n📊 Testing with default settings (enable_rerank defaults to True):")
result_default = await rag.aquery(
query, param=QueryParam(mode="naive", top_k=10, chunk_top_k=5)
)
print(f" Result length: {len(result_default)} characters")
print(f" Preview: {result_default[:100]}...")
async def test_direct_rerank():
"""Test rerank function directly"""
print("\n🔧 Direct Rerank API Test")
print("=" * 40)
documents = [
"Vector search finds semantically similar documents",
"LightRAG supports advanced reranking capabilities",
"Reranking significantly improves retrieval quality",
"Natural language processing with modern transformers",
"The quick brown fox jumps over the lazy dog",
]
query = "rerank improve quality"
print(f"Query: '{query}'")
print(f"Documents: {len(documents)}")
try:
reranked_results = await rerank_model_func(
query=query,
documents=documents,
top_n=4,
)
print("\n✅ Rerank Results:")
i = 0
for result in reranked_results:
index = result["index"]
score = result["relevance_score"]
content = documents[index]
print(f" {index}. Score: {score:.4f} | {content}...")
i += 1
except Exception as e:
print(f"❌ Rerank failed: {e}")
async def main():
"""Main example function"""
print("🎯 LightRAG Rerank Integration Example")
print("=" * 60)
try:
# Test direct rerank
await test_direct_rerank()
# Test rerank with different enable_rerank settings
await test_rerank_with_different_settings()
print("\n✅ Example completed successfully!")
print("\n💡 Key Points:")
print(" ✓ Rerank is now controlled per query via 'enable_rerank' parameter")
print(" ✓ Default value for enable_rerank is True")
print(" ✓ Rerank function is configured at LightRAG initialization")
print(" ✓ Per-query enable_rerank setting overrides default behavior")
print(
" ✓ If enable_rerank=True but no rerank model is configured, a warning is issued"
)
print(" ✓ Monitor API usage and costs when using rerank services")
except Exception as e:
print(f"\n❌ Example failed: {e}")
import traceback
traceback.print_exc()
if __name__ == "__main__":
asyncio.run(main())
@@ -1,114 +0,0 @@
"""
Sometimes you need to switch a storage solution, but you want to save LLM token and time.
This handy script helps you to copy the LLM caches from one storage solution to another.
(Not all the storage impl are supported)
"""
import asyncio
import logging
import os
from dotenv import load_dotenv
from lightrag.kg.postgres_impl import PostgreSQLDB, PGKVStorage
from lightrag.kg.json_kv_impl import JsonKVStorage
from lightrag.namespace import NameSpace
load_dotenv()
ROOT_DIR = os.environ.get("ROOT_DIR")
WORKING_DIR = f"{ROOT_DIR}/dickens"
logging.basicConfig(format="%(levelname)s:%(message)s", level=logging.INFO)
if not os.path.exists(WORKING_DIR):
os.mkdir(WORKING_DIR)
# AGE
os.environ["AGE_GRAPH_NAME"] = "chinese"
postgres_db = PostgreSQLDB(
config={
"host": "localhost",
"port": 15432,
"user": "rag",
"password": "rag",
"database": "r2",
}
)
async def copy_from_postgres_to_json():
await postgres_db.initdb()
from_llm_response_cache = PGKVStorage(
namespace=NameSpace.KV_STORE_LLM_RESPONSE_CACHE,
global_config={"embedding_batch_num": 6},
embedding_func=None,
db=postgres_db,
)
to_llm_response_cache = JsonKVStorage(
namespace=NameSpace.KV_STORE_LLM_RESPONSE_CACHE,
global_config={"working_dir": WORKING_DIR},
embedding_func=None,
)
# Get all cache data using the new flattened structure
all_data = await from_llm_response_cache.get_all()
# Convert flattened data to hierarchical structure for JsonKVStorage
kv = {}
for flattened_key, cache_entry in all_data.items():
# Parse flattened key: {mode}:{cache_type}:{hash}
parts = flattened_key.split(":", 2)
if len(parts) == 3:
mode, cache_type, hash_value = parts
if mode not in kv:
kv[mode] = {}
kv[mode][hash_value] = cache_entry
print(f"Copying {flattened_key} -> {mode}[{hash_value}]")
else:
print(f"Skipping invalid key format: {flattened_key}")
await to_llm_response_cache.upsert(kv)
await to_llm_response_cache.index_done_callback()
print("Mission accomplished!")
async def copy_from_json_to_postgres():
await postgres_db.initdb()
from_llm_response_cache = JsonKVStorage(
namespace=NameSpace.KV_STORE_LLM_RESPONSE_CACHE,
global_config={"working_dir": WORKING_DIR},
embedding_func=None,
)
to_llm_response_cache = PGKVStorage(
namespace=NameSpace.KV_STORE_LLM_RESPONSE_CACHE,
global_config={"embedding_batch_num": 6},
embedding_func=None,
db=postgres_db,
)
# Get all cache data from JsonKVStorage (hierarchical structure)
all_data = await from_llm_response_cache.get_all()
# Convert hierarchical data to flattened structure for PGKVStorage
flattened_data = {}
for mode, mode_data in all_data.items():
print(f"Processing mode: {mode}")
for hash_value, cache_entry in mode_data.items():
# Determine cache_type from cache entry or use default
cache_type = cache_entry.get("cache_type", "extract")
# Create flattened key: {mode}:{cache_type}:{hash}
flattened_key = f"{mode}:{cache_type}:{hash_value}"
flattened_data[flattened_key] = cache_entry
print(f"\tConverting {mode}[{hash_value}] -> {flattened_key}")
# Upsert the flattened data
await to_llm_response_cache.upsert(flattened_data)
print("Mission accomplished!")
if __name__ == "__main__":
asyncio.run(copy_from_json_to_postgres())
@@ -1,59 +0,0 @@
"""
LightRAG meets Amazon Bedrock ⛰️
"""
import os
import logging
from lightrag import LightRAG, QueryParam
from lightrag.llm.bedrock import bedrock_complete, bedrock_embed
from lightrag.utils import EmbeddingFunc
from lightrag.kg.shared_storage import initialize_pipeline_status
import asyncio
import nest_asyncio
nest_asyncio.apply()
logging.getLogger("aiobotocore").setLevel(logging.WARNING)
WORKING_DIR = "./dickens"
if not os.path.exists(WORKING_DIR):
os.mkdir(WORKING_DIR)
async def initialize_rag():
rag = LightRAG(
working_dir=WORKING_DIR,
llm_model_func=bedrock_complete,
llm_model_name="Anthropic Claude 3 Haiku // Amazon Bedrock",
embedding_func=EmbeddingFunc(
embedding_dim=1024, max_token_size=8192, func=bedrock_embed
),
)
await rag.initialize_storages()
await initialize_pipeline_status()
return rag
def main():
rag = asyncio.run(initialize_rag())
with open("./book.txt", "r", encoding="utf-8") as f:
rag.insert(f.read())
for mode in ["naive", "local", "global", "hybrid"]:
print("\n+-" + "-" * len(mode) + "-+")
print(f"| {mode.capitalize()} |")
print("+-" + "-" * len(mode) + "-+\n")
print(
rag.query(
"What are the top themes in this story?", param=QueryParam(mode=mode)
)
)
if __name__ == "__main__":
main()
@@ -1,357 +0,0 @@
import asyncio
import os
import inspect
import logging
import logging.config
from lightrag import LightRAG, QueryParam
from lightrag.utils import EmbeddingFunc, logger, set_verbose_debug
from lightrag.kg.shared_storage import initialize_pipeline_status
import requests
import numpy as np
from dotenv import load_dotenv
"""This code is a modified version of lightrag_openai_demo.py"""
# ideally, as always, env!
load_dotenv(dotenv_path=".env", override=False)
""" ----========= IMPORTANT CHANGE THIS! =========---- """
cloudflare_api_key = "YOUR_API_KEY"
account_id = "YOUR_ACCOUNT ID" # This is unique to your Cloudflare account
# Authomatically changes
api_base_url = f"https://api.cloudflare.com/client/v4/accounts/{account_id}/ai/run/"
# choose an embedding model
EMBEDDING_MODEL = "@cf/baai/bge-m3"
# choose a generative model
LLM_MODEL = "@cf/meta/llama-3.2-3b-instruct"
WORKING_DIR = "../dickens" # you can change output as desired
# Cloudflare init
class CloudflareWorker:
def __init__(
self,
cloudflare_api_key: str,
api_base_url: str,
llm_model_name: str,
embedding_model_name: str,
max_tokens: int = 4080,
max_response_tokens: int = 4080,
):
self.cloudflare_api_key = cloudflare_api_key
self.api_base_url = api_base_url
self.llm_model_name = llm_model_name
self.embedding_model_name = embedding_model_name
self.max_tokens = max_tokens
self.max_response_tokens = max_response_tokens
async def _send_request(self, model_name: str, input_: dict, debug_log: str):
headers = {"Authorization": f"Bearer {self.cloudflare_api_key}"}
print(f"""
data sent to Cloudflare
~~~~~~~~~~~
{debug_log}
""")
try:
response_raw = requests.post(
f"{self.api_base_url}{model_name}", headers=headers, json=input_
).json()
print(f"""
Cloudflare worker responded with:
~~~~~~~~~~~
{str(response_raw)}
""")
result = response_raw.get("result", {})
if "data" in result: # Embedding case
return np.array(result["data"])
if "response" in result: # LLM response
return result["response"]
raise ValueError("Unexpected Cloudflare response format")
except Exception as e:
print(f"""
Cloudflare API returned:
~~~~~~~~~
Error: {e}
""")
input("Press Enter to continue...")
return None
async def query(self, prompt, system_prompt: str = "", **kwargs) -> str:
# since no caching is used and we don't want to mess with everything lightrag, pop the kwarg it is
kwargs.pop("hashing_kv", None)
message = [
{"role": "system", "content": system_prompt},
{"role": "user", "content": prompt},
]
input_ = {
"messages": message,
"max_tokens": self.max_tokens,
"response_token_limit": self.max_response_tokens,
}
return await self._send_request(
self.llm_model_name,
input_,
debug_log=f"\n- model used {self.llm_model_name}\n- system prompt: {system_prompt}\n- query: {prompt}",
)
async def embedding_chunk(self, texts: list[str]) -> np.ndarray:
print(f"""
TEXT inputted
~~~~~
{texts}
""")
input_ = {
"text": texts,
"max_tokens": self.max_tokens,
"response_token_limit": self.max_response_tokens,
}
return await self._send_request(
self.embedding_model_name,
input_,
debug_log=f"\n-llm model name {self.embedding_model_name}\n- texts: {texts}",
)
def configure_logging():
"""Configure logging for the application"""
# Reset any existing handlers to ensure clean configuration
for logger_name in ["uvicorn", "uvicorn.access", "uvicorn.error", "lightrag"]:
logger_instance = logging.getLogger(logger_name)
logger_instance.handlers = []
logger_instance.filters = []
# Get log directory path from environment variable or use current directory
log_dir = os.getenv("LOG_DIR", os.getcwd())
log_file_path = os.path.abspath(
os.path.join(log_dir, "lightrag_cloudflare_worker_demo.log")
)
print(f"\nLightRAG compatible demo log file: {log_file_path}\n")
os.makedirs(os.path.dirname(log_file_path), exist_ok=True)
# Get log file max size and backup count from environment variables
log_max_bytes = int(os.getenv("LOG_MAX_BYTES", 10485760)) # Default 10MB
log_backup_count = int(os.getenv("LOG_BACKUP_COUNT", 5)) # Default 5 backups
logging.config.dictConfig(
{
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"default": {
"format": "%(levelname)s: %(message)s",
},
"detailed": {
"format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s",
},
},
"handlers": {
"console": {
"formatter": "default",
"class": "logging.StreamHandler",
"stream": "ext://sys.stderr",
},
"file": {
"formatter": "detailed",
"class": "logging.handlers.RotatingFileHandler",
"filename": log_file_path,
"maxBytes": log_max_bytes,
"backupCount": log_backup_count,
"encoding": "utf-8",
},
},
"loggers": {
"lightrag": {
"handlers": ["console", "file"],
"level": "INFO",
"propagate": False,
},
},
}
)
# Set the logger level to INFO
logger.setLevel(logging.INFO)
# Enable verbose debug if needed
set_verbose_debug(os.getenv("VERBOSE_DEBUG", "false").lower() == "true")
if not os.path.exists(WORKING_DIR):
os.mkdir(WORKING_DIR)
async def initialize_rag():
cloudflare_worker = CloudflareWorker(
cloudflare_api_key=cloudflare_api_key,
api_base_url=api_base_url,
embedding_model_name=EMBEDDING_MODEL,
llm_model_name=LLM_MODEL,
)
rag = LightRAG(
working_dir=WORKING_DIR,
max_parallel_insert=2,
llm_model_func=cloudflare_worker.query,
llm_model_name=os.getenv("LLM_MODEL", LLM_MODEL),
summary_max_tokens=4080,
embedding_func=EmbeddingFunc(
embedding_dim=int(os.getenv("EMBEDDING_DIM", "1024")),
max_token_size=int(os.getenv("MAX_EMBED_TOKENS", "2048")),
func=lambda texts: cloudflare_worker.embedding_chunk(
texts,
),
),
)
await rag.initialize_storages()
await initialize_pipeline_status()
return rag
async def print_stream(stream):
async for chunk in stream:
print(chunk, end="", flush=True)
async def main():
try:
# Clear old data files
files_to_delete = [
"graph_chunk_entity_relation.graphml",
"kv_store_doc_status.json",
"kv_store_full_docs.json",
"kv_store_text_chunks.json",
"vdb_chunks.json",
"vdb_entities.json",
"vdb_relationships.json",
]
for file in files_to_delete:
file_path = os.path.join(WORKING_DIR, file)
if os.path.exists(file_path):
os.remove(file_path)
print(f"Deleting old file:: {file_path}")
# Initialize RAG instance
rag = await initialize_rag()
# Test embedding function
test_text = ["This is a test string for embedding."]
embedding = await rag.embedding_func(test_text)
embedding_dim = embedding.shape[1]
print("\n=======================")
print("Test embedding function")
print("========================")
print(f"Test dict: {test_text}")
print(f"Detected embedding dimension: {embedding_dim}\n\n")
# Locate the location of what is needed to be added to the knowledge
# Can add several simultaneously by modifying code
with open("./book.txt", "r", encoding="utf-8") as f:
await rag.ainsert(f.read())
# Perform naive search
print("\n=====================")
print("Query mode: naive")
print("=====================")
resp = await rag.aquery(
"What are the top themes in this story?",
param=QueryParam(mode="naive", stream=True),
)
if inspect.isasyncgen(resp):
await print_stream(resp)
else:
print(resp)
# Perform local search
print("\n=====================")
print("Query mode: local")
print("=====================")
resp = await rag.aquery(
"What are the top themes in this story?",
param=QueryParam(mode="local", stream=True),
)
if inspect.isasyncgen(resp):
await print_stream(resp)
else:
print(resp)
# Perform global search
print("\n=====================")
print("Query mode: global")
print("=====================")
resp = await rag.aquery(
"What are the top themes in this story?",
param=QueryParam(mode="global", stream=True),
)
if inspect.isasyncgen(resp):
await print_stream(resp)
else:
print(resp)
# Perform hybrid search
print("\n=====================")
print("Query mode: hybrid")
print("=====================")
resp = await rag.aquery(
"What are the top themes in this story?",
param=QueryParam(mode="hybrid", stream=True),
)
if inspect.isasyncgen(resp):
await print_stream(resp)
else:
print(resp)
""" FOR TESTING (if you want to test straight away, after building. Uncomment this part"""
"""
print("\n" + "=" * 60)
print("AI ASSISTANT READY!")
print("Ask questions about (your uploaded) regulations")
print("Type 'quit' to exit")
print("=" * 60)
while True:
question = input("\n🔥 Your question: ")
if question.lower() in ['quit', 'exit', 'bye']:
break
print("\nThinking...")
response = await rag.aquery(question, param=QueryParam(mode="hybrid"))
print(f"\nAnswer: {response}")
"""
except Exception as e:
print(f"An error occurred: {e}")
finally:
if rag:
await rag.llm_response_cache.index_done_callback()
await rag.finalize_storages()
if __name__ == "__main__":
# Configure logging before running the main function
configure_logging()
asyncio.run(main())
print("\nDone!")
@@ -1,82 +0,0 @@
import os
from lightrag import LightRAG, QueryParam
from lightrag.llm.hf import hf_model_complete, hf_embed
from lightrag.utils import EmbeddingFunc
from transformers import AutoModel, AutoTokenizer
from lightrag.kg.shared_storage import initialize_pipeline_status
import asyncio
import nest_asyncio
nest_asyncio.apply()
WORKING_DIR = "./dickens"
if not os.path.exists(WORKING_DIR):
os.mkdir(WORKING_DIR)
async def initialize_rag():
rag = LightRAG(
working_dir=WORKING_DIR,
llm_model_func=hf_model_complete,
llm_model_name="meta-llama/Llama-3.1-8B-Instruct",
embedding_func=EmbeddingFunc(
embedding_dim=384,
max_token_size=5000,
func=lambda texts: hf_embed(
texts,
tokenizer=AutoTokenizer.from_pretrained(
"sentence-transformers/all-MiniLM-L6-v2"
),
embed_model=AutoModel.from_pretrained(
"sentence-transformers/all-MiniLM-L6-v2"
),
),
),
)
await rag.initialize_storages()
await initialize_pipeline_status()
return rag
def main():
rag = asyncio.run(initialize_rag())
with open("./book.txt", "r", encoding="utf-8") as f:
rag.insert(f.read())
# Perform naive search
print(
rag.query(
"What are the top themes in this story?", param=QueryParam(mode="naive")
)
)
# Perform local search
print(
rag.query(
"What are the top themes in this story?", param=QueryParam(mode="local")
)
)
# Perform global search
print(
rag.query(
"What are the top themes in this story?", param=QueryParam(mode="global")
)
)
# Perform hybrid search
print(
rag.query(
"What are the top themes in this story?", param=QueryParam(mode="hybrid")
)
)
if __name__ == "__main__":
main()
@@ -1,143 +0,0 @@
import asyncio
import os
import nest_asyncio
from llama_index.embeddings.openai import OpenAIEmbedding
from llama_index.llms.openai import OpenAI
from lightrag import LightRAG, QueryParam
from lightrag.kg.shared_storage import initialize_pipeline_status
from lightrag.llm.llama_index_impl import (
llama_index_complete_if_cache,
llama_index_embed,
)
from lightrag.utils import EmbeddingFunc
nest_asyncio.apply()
# Configure working directory
WORKING_DIR = "./index_default"
print(f"WORKING_DIR: {WORKING_DIR}")
# Model configuration
LLM_MODEL = os.environ.get("LLM_MODEL", "gpt-4")
print(f"LLM_MODEL: {LLM_MODEL}")
EMBEDDING_MODEL = os.environ.get("EMBEDDING_MODEL", "text-embedding-3-large")
print(f"EMBEDDING_MODEL: {EMBEDDING_MODEL}")
EMBEDDING_MAX_TOKEN_SIZE = int(os.environ.get("EMBEDDING_MAX_TOKEN_SIZE", 8192))
print(f"EMBEDDING_MAX_TOKEN_SIZE: {EMBEDDING_MAX_TOKEN_SIZE}")
# OpenAI configuration
OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY", "your-api-key-here")
if not os.path.exists(WORKING_DIR):
print(f"Creating working directory: {WORKING_DIR}")
os.mkdir(WORKING_DIR)
# Initialize LLM function
async def llm_model_func(prompt, system_prompt=None, history_messages=[], **kwargs):
try:
# Initialize OpenAI if not in kwargs
if "llm_instance" not in kwargs:
llm_instance = OpenAI(
model=LLM_MODEL,
api_key=OPENAI_API_KEY,
temperature=0.7,
)
kwargs["llm_instance"] = llm_instance
response = await llama_index_complete_if_cache(
kwargs["llm_instance"],
prompt,
system_prompt=system_prompt,
history_messages=history_messages,
**kwargs,
)
return response
except Exception as e:
print(f"LLM request failed: {str(e)}")
raise
# Initialize embedding function
async def embedding_func(texts):
try:
embed_model = OpenAIEmbedding(
model=EMBEDDING_MODEL,
api_key=OPENAI_API_KEY,
)
return await llama_index_embed(texts, embed_model=embed_model)
except Exception as e:
print(f"Embedding failed: {str(e)}")
raise
# Get embedding dimension
async def get_embedding_dim():
test_text = ["This is a test sentence."]
embedding = await embedding_func(test_text)
embedding_dim = embedding.shape[1]
print(f"embedding_dim={embedding_dim}")
return embedding_dim
async def initialize_rag():
embedding_dimension = await get_embedding_dim()
rag = LightRAG(
working_dir=WORKING_DIR,
llm_model_func=llm_model_func,
embedding_func=EmbeddingFunc(
embedding_dim=embedding_dimension,
max_token_size=EMBEDDING_MAX_TOKEN_SIZE,
func=embedding_func,
),
)
await rag.initialize_storages()
await initialize_pipeline_status()
return rag
def main():
# Initialize RAG instance
rag = asyncio.run(initialize_rag())
# Insert example text
with open("./book.txt", "r", encoding="utf-8") as f:
rag.insert(f.read())
# Test different query modes
print("\nNaive Search:")
print(
rag.query(
"What are the top themes in this story?", param=QueryParam(mode="naive")
)
)
print("\nLocal Search:")
print(
rag.query(
"What are the top themes in this story?", param=QueryParam(mode="local")
)
)
print("\nGlobal Search:")
print(
rag.query(
"What are the top themes in this story?", param=QueryParam(mode="global")
)
)
print("\nHybrid Search:")
print(
rag.query(
"What are the top themes in this story?", param=QueryParam(mode="hybrid")
)
)
if __name__ == "__main__":
main()
@@ -1,145 +0,0 @@
import asyncio
import os
import nest_asyncio
from llama_index.embeddings.litellm import LiteLLMEmbedding
from llama_index.llms.litellm import LiteLLM
from lightrag import LightRAG, QueryParam
from lightrag.kg.shared_storage import initialize_pipeline_status
from lightrag.llm.llama_index_impl import (
llama_index_complete_if_cache,
llama_index_embed,
)
from lightrag.utils import EmbeddingFunc
nest_asyncio.apply()
# Configure working directory
WORKING_DIR = "./index_default"
print(f"WORKING_DIR: {WORKING_DIR}")
# Model configuration
LLM_MODEL = os.environ.get("LLM_MODEL", "gpt-4")
print(f"LLM_MODEL: {LLM_MODEL}")
EMBEDDING_MODEL = os.environ.get("EMBEDDING_MODEL", "text-embedding-3-large")
print(f"EMBEDDING_MODEL: {EMBEDDING_MODEL}")
EMBEDDING_MAX_TOKEN_SIZE = int(os.environ.get("EMBEDDING_MAX_TOKEN_SIZE", 8192))
print(f"EMBEDDING_MAX_TOKEN_SIZE: {EMBEDDING_MAX_TOKEN_SIZE}")
# LiteLLM configuration
LITELLM_URL = os.environ.get("LITELLM_URL", "http://localhost:4000")
print(f"LITELLM_URL: {LITELLM_URL}")
LITELLM_KEY = os.environ.get("LITELLM_KEY", "sk-1234")
if not os.path.exists(WORKING_DIR):
os.mkdir(WORKING_DIR)
# Initialize LLM function
async def llm_model_func(prompt, system_prompt=None, history_messages=[], **kwargs):
try:
# Initialize LiteLLM if not in kwargs
if "llm_instance" not in kwargs:
llm_instance = LiteLLM(
model=f"openai/{LLM_MODEL}", # Format: "provider/model_name"
api_base=LITELLM_URL,
api_key=LITELLM_KEY,
temperature=0.7,
)
kwargs["llm_instance"] = llm_instance
response = await llama_index_complete_if_cache(
kwargs["llm_instance"],
prompt,
system_prompt=system_prompt,
history_messages=history_messages,
)
return response
except Exception as e:
print(f"LLM request failed: {str(e)}")
raise
# Initialize embedding function
async def embedding_func(texts):
try:
embed_model = LiteLLMEmbedding(
model_name=f"openai/{EMBEDDING_MODEL}",
api_base=LITELLM_URL,
api_key=LITELLM_KEY,
)
return await llama_index_embed(texts, embed_model=embed_model)
except Exception as e:
print(f"Embedding failed: {str(e)}")
raise
# Get embedding dimension
async def get_embedding_dim():
test_text = ["This is a test sentence."]
embedding = await embedding_func(test_text)
embedding_dim = embedding.shape[1]
print(f"embedding_dim={embedding_dim}")
return embedding_dim
async def initialize_rag():
embedding_dimension = await get_embedding_dim()
rag = LightRAG(
working_dir=WORKING_DIR,
llm_model_func=llm_model_func,
embedding_func=EmbeddingFunc(
embedding_dim=embedding_dimension,
max_token_size=EMBEDDING_MAX_TOKEN_SIZE,
func=embedding_func,
),
)
await rag.initialize_storages()
await initialize_pipeline_status()
return rag
def main():
# Initialize RAG instance
rag = asyncio.run(initialize_rag())
# Insert example text
with open("./book.txt", "r", encoding="utf-8") as f:
rag.insert(f.read())
# Test different query modes
print("\nNaive Search:")
print(
rag.query(
"What are the top themes in this story?", param=QueryParam(mode="naive")
)
)
print("\nLocal Search:")
print(
rag.query(
"What are the top themes in this story?", param=QueryParam(mode="local")
)
)
print("\nGlobal Search:")
print(
rag.query(
"What are the top themes in this story?", param=QueryParam(mode="global")
)
)
print("\nHybrid Search:")
print(
rag.query(
"What are the top themes in this story?", param=QueryParam(mode="hybrid")
)
)
if __name__ == "__main__":
main()
@@ -1,156 +0,0 @@
import asyncio
import os
import nest_asyncio
from llama_index.embeddings.litellm import LiteLLMEmbedding
from llama_index.llms.litellm import LiteLLM
from lightrag import LightRAG, QueryParam
from lightrag.kg.shared_storage import initialize_pipeline_status
from lightrag.llm.llama_index_impl import (
llama_index_complete_if_cache,
llama_index_embed,
)
from lightrag.utils import EmbeddingFunc
nest_asyncio.apply()
# Configure working directory
WORKING_DIR = "./index_default"
print(f"WORKING_DIR: {WORKING_DIR}")
# Model configuration
LLM_MODEL = os.environ.get("LLM_MODEL", "gemma-3-4b")
print(f"LLM_MODEL: {LLM_MODEL}")
EMBEDDING_MODEL = os.environ.get("EMBEDDING_MODEL", "arctic-embed")
print(f"EMBEDDING_MODEL: {EMBEDDING_MODEL}")
EMBEDDING_MAX_TOKEN_SIZE = int(os.environ.get("EMBEDDING_MAX_TOKEN_SIZE", 8192))
print(f"EMBEDDING_MAX_TOKEN_SIZE: {EMBEDDING_MAX_TOKEN_SIZE}")
# LiteLLM configuration
LITELLM_URL = os.environ.get("LITELLM_URL", "http://localhost:4000")
print(f"LITELLM_URL: {LITELLM_URL}")
LITELLM_KEY = os.environ.get("LITELLM_KEY", "sk-4JdvGFKqSA3S0k_5p0xufw")
if not os.path.exists(WORKING_DIR):
os.mkdir(WORKING_DIR)
# Initialize LLM function
async def llm_model_func(prompt, system_prompt=None, history_messages=[], **kwargs):
try:
# Initialize LiteLLM if not in kwargs
if "llm_instance" not in kwargs:
llm_instance = LiteLLM(
model=f"openai/{LLM_MODEL}", # Format: "provider/model_name"
api_base=LITELLM_URL,
api_key=LITELLM_KEY,
temperature=0.7,
)
kwargs["llm_instance"] = llm_instance
chat_kwargs = {}
chat_kwargs["litellm_params"] = {
"metadata": {
"opik": {
"project_name": "lightrag_llamaindex_litellm_opik_demo",
"tags": ["lightrag", "litellm"],
}
}
}
response = await llama_index_complete_if_cache(
kwargs["llm_instance"],
prompt,
system_prompt=system_prompt,
history_messages=history_messages,
chat_kwargs=chat_kwargs,
)
return response
except Exception as e:
print(f"LLM request failed: {str(e)}")
raise
# Initialize embedding function
async def embedding_func(texts):
try:
embed_model = LiteLLMEmbedding(
model_name=f"openai/{EMBEDDING_MODEL}",
api_base=LITELLM_URL,
api_key=LITELLM_KEY,
)
return await llama_index_embed(texts, embed_model=embed_model)
except Exception as e:
print(f"Embedding failed: {str(e)}")
raise
# Get embedding dimension
async def get_embedding_dim():
test_text = ["This is a test sentence."]
embedding = await embedding_func(test_text)
embedding_dim = embedding.shape[1]
print(f"embedding_dim={embedding_dim}")
return embedding_dim
async def initialize_rag():
embedding_dimension = await get_embedding_dim()
rag = LightRAG(
working_dir=WORKING_DIR,
llm_model_func=llm_model_func,
embedding_func=EmbeddingFunc(
embedding_dim=embedding_dimension,
max_token_size=EMBEDDING_MAX_TOKEN_SIZE,
func=embedding_func,
),
)
await rag.initialize_storages()
await initialize_pipeline_status()
return rag
def main():
# Initialize RAG instance
rag = asyncio.run(initialize_rag())
# Insert example text
with open("./book.txt", "r", encoding="utf-8") as f:
rag.insert(f.read())
# Test different query modes
print("\nNaive Search:")
print(
rag.query(
"What are the top themes in this story?", param=QueryParam(mode="naive")
)
)
print("\nLocal Search:")
print(
rag.query(
"What are the top themes in this story?", param=QueryParam(mode="local")
)
)
print("\nGlobal Search:")
print(
rag.query(
"What are the top themes in this story?", param=QueryParam(mode="global")
)
)
print("\nHybrid Search:")
print(
rag.query(
"What are the top themes in this story?", param=QueryParam(mode="hybrid")
)
)
if __name__ == "__main__":
main()
@@ -1,110 +0,0 @@
import os
from lightrag import LightRAG, QueryParam
from lightrag.llm.lmdeploy import lmdeploy_model_if_cache
from lightrag.llm.hf import hf_embed
from lightrag.utils import EmbeddingFunc
from transformers import AutoModel, AutoTokenizer
from lightrag.kg.shared_storage import initialize_pipeline_status
import asyncio
import nest_asyncio
nest_asyncio.apply()
WORKING_DIR = "./dickens"
if not os.path.exists(WORKING_DIR):
os.mkdir(WORKING_DIR)
async def lmdeploy_model_complete(
prompt=None,
system_prompt=None,
history_messages=[],
keyword_extraction=False,
**kwargs,
) -> str:
model_name = kwargs["hashing_kv"].global_config["llm_model_name"]
return await lmdeploy_model_if_cache(
model_name,
prompt,
system_prompt=system_prompt,
history_messages=history_messages,
## please specify chat_template if your local path does not follow original HF file name,
## or model_name is a pytorch model on huggingface.co,
## you can refer to https://github.com/InternLM/lmdeploy/blob/main/lmdeploy/model.py
## for a list of chat_template available in lmdeploy.
chat_template="llama3",
# model_format ='awq', # if you are using awq quantization model.
# quant_policy=8, # if you want to use online kv cache, 4=kv int4, 8=kv int8.
**kwargs,
)
async def initialize_rag():
rag = LightRAG(
working_dir=WORKING_DIR,
llm_model_func=lmdeploy_model_complete,
llm_model_name="meta-llama/Llama-3.1-8B-Instruct", # please use definite path for local model
embedding_func=EmbeddingFunc(
embedding_dim=384,
max_token_size=5000,
func=lambda texts: hf_embed(
texts,
tokenizer=AutoTokenizer.from_pretrained(
"sentence-transformers/all-MiniLM-L6-v2"
),
embed_model=AutoModel.from_pretrained(
"sentence-transformers/all-MiniLM-L6-v2"
),
),
),
)
await rag.initialize_storages()
await initialize_pipeline_status()
return rag
def main():
# Initialize RAG instance
rag = asyncio.run(initialize_rag())
# Insert example text
with open("./book.txt", "r", encoding="utf-8") as f:
rag.insert(f.read())
# Test different query modes
print("\nNaive Search:")
print(
rag.query(
"What are the top themes in this story?", param=QueryParam(mode="naive")
)
)
print("\nLocal Search:")
print(
rag.query(
"What are the top themes in this story?", param=QueryParam(mode="local")
)
)
print("\nGlobal Search:")
print(
rag.query(
"What are the top themes in this story?", param=QueryParam(mode="global")
)
)
print("\nHybrid Search:")
print(
rag.query(
"What are the top themes in this story?", param=QueryParam(mode="hybrid")
)
)
if __name__ == "__main__":
main()
@@ -1,171 +0,0 @@
import os
import asyncio
import nest_asyncio
from lightrag import LightRAG, QueryParam
from lightrag.llm import (
openai_complete_if_cache,
nvidia_openai_embed,
)
from lightrag.utils import EmbeddingFunc
import numpy as np
from lightrag.kg.shared_storage import initialize_pipeline_status
# for custom llm_model_func
from lightrag.utils import locate_json_string_body_from_string
nest_asyncio.apply()
WORKING_DIR = "./dickens"
if not os.path.exists(WORKING_DIR):
os.mkdir(WORKING_DIR)
# some method to use your API key (choose one)
# NVIDIA_OPENAI_API_KEY = os.getenv("NVIDIA_OPENAI_API_KEY")
NVIDIA_OPENAI_API_KEY = "nvapi-xxxx" # your api key
# using pre-defined function for nvidia LLM API. OpenAI compatible
# llm_model_func = nvidia_openai_complete
# If you trying to make custom llm_model_func to use llm model on NVIDIA API like other example:
async def llm_model_func(
prompt, system_prompt=None, history_messages=[], keyword_extraction=False, **kwargs
) -> str:
result = await openai_complete_if_cache(
"nvidia/llama-3.1-nemotron-70b-instruct",
prompt,
system_prompt=system_prompt,
history_messages=history_messages,
api_key=NVIDIA_OPENAI_API_KEY,
base_url="https://integrate.api.nvidia.com/v1",
**kwargs,
)
if keyword_extraction:
return locate_json_string_body_from_string(result)
return result
# custom embedding
nvidia_embed_model = "nvidia/nv-embedqa-e5-v5"
async def indexing_embedding_func(texts: list[str]) -> np.ndarray:
return await nvidia_openai_embed(
texts,
model=nvidia_embed_model, # maximum 512 token
# model="nvidia/llama-3.2-nv-embedqa-1b-v1",
api_key=NVIDIA_OPENAI_API_KEY,
base_url="https://integrate.api.nvidia.com/v1",
input_type="passage",
trunc="END", # handling on server side if input token is longer than maximum token
encode="float",
)
async def query_embedding_func(texts: list[str]) -> np.ndarray:
return await nvidia_openai_embed(
texts,
model=nvidia_embed_model, # maximum 512 token
# model="nvidia/llama-3.2-nv-embedqa-1b-v1",
api_key=NVIDIA_OPENAI_API_KEY,
base_url="https://integrate.api.nvidia.com/v1",
input_type="query",
trunc="END", # handling on server side if input token is longer than maximum token
encode="float",
)
# dimension are same
async def get_embedding_dim():
test_text = ["This is a test sentence."]
embedding = await indexing_embedding_func(test_text)
embedding_dim = embedding.shape[1]
return embedding_dim
# function test
async def test_funcs():
result = await llm_model_func("How are you?")
print("llm_model_func: ", result)
result = await indexing_embedding_func(["How are you?"])
print("embedding_func: ", result)
# asyncio.run(test_funcs())
async def initialize_rag():
embedding_dimension = await get_embedding_dim()
print(f"Detected embedding dimension: {embedding_dimension}")
# lightRAG class during indexing
rag = LightRAG(
working_dir=WORKING_DIR,
llm_model_func=llm_model_func,
# llm_model_name="meta/llama3-70b-instruct", #un comment if
embedding_func=EmbeddingFunc(
embedding_dim=embedding_dimension,
max_token_size=512, # maximum token size, somehow it's still exceed maximum number of token
# so truncate (trunc) parameter on embedding_func will handle it and try to examine the tokenizer used in LightRAG
# so you can adjust to be able to fit the NVIDIA model (future work)
func=indexing_embedding_func,
),
)
await rag.initialize_storages()
await initialize_pipeline_status()
return rag
async def main():
try:
# Initialize RAG instance
rag = await initialize_rag()
# reading file
with open("./book.txt", "r", encoding="utf-8") as f:
await rag.ainsert(f.read())
# Perform naive search
print("==============Naive===============")
print(
await rag.aquery(
"What are the top themes in this story?", param=QueryParam(mode="naive")
)
)
# Perform local search
print("==============local===============")
print(
await rag.aquery(
"What are the top themes in this story?", param=QueryParam(mode="local")
)
)
# Perform global search
print("==============global===============")
print(
await rag.aquery(
"What are the top themes in this story?",
param=QueryParam(mode="global"),
)
)
# Perform hybrid search
print("==============hybrid===============")
print(
await rag.aquery(
"What are the top themes in this story?",
param=QueryParam(mode="hybrid"),
)
)
except Exception as e:
print(f"An error occurred: {e}")
if __name__ == "__main__":
asyncio.run(main())
@@ -1,112 +0,0 @@
import os
import asyncio
from lightrag import LightRAG, QueryParam
from lightrag.llm.ollama import ollama_embed, openai_complete_if_cache
from lightrag.utils import EmbeddingFunc
from lightrag.kg.shared_storage import initialize_pipeline_status
# WorkingDir
ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
WORKING_DIR = os.path.join(ROOT_DIR, "myKG")
if not os.path.exists(WORKING_DIR):
os.mkdir(WORKING_DIR)
print(f"WorkingDir: {WORKING_DIR}")
# redis
os.environ["REDIS_URI"] = "redis://localhost:6379"
# neo4j
BATCH_SIZE_NODES = 500
BATCH_SIZE_EDGES = 100
os.environ["NEO4J_URI"] = "neo4j://localhost:7687"
os.environ["NEO4J_USERNAME"] = "neo4j"
os.environ["NEO4J_PASSWORD"] = "12345678"
# milvus
os.environ["MILVUS_URI"] = "http://localhost:19530"
os.environ["MILVUS_USER"] = "root"
os.environ["MILVUS_PASSWORD"] = "Milvus"
os.environ["MILVUS_DB_NAME"] = "lightrag"
async def llm_model_func(
prompt, system_prompt=None, history_messages=[], keyword_extraction=False, **kwargs
) -> str:
return await openai_complete_if_cache(
"deepseek-chat",
prompt,
system_prompt=system_prompt,
history_messages=history_messages,
api_key="",
base_url="",
**kwargs,
)
embedding_func = EmbeddingFunc(
embedding_dim=768,
max_token_size=512,
func=lambda texts: ollama_embed(
texts, embed_model="shaw/dmeta-embedding-zh", host="http://117.50.173.35:11434"
),
)
async def initialize_rag():
rag = LightRAG(
working_dir=WORKING_DIR,
llm_model_func=llm_model_func,
summary_max_tokens=10000,
embedding_func=embedding_func,
chunk_token_size=512,
chunk_overlap_token_size=256,
kv_storage="RedisKVStorage",
graph_storage="Neo4JStorage",
vector_storage="MilvusVectorDBStorage",
doc_status_storage="RedisKVStorage",
)
await rag.initialize_storages()
await initialize_pipeline_status()
return rag
def main():
# Initialize RAG instance
rag = asyncio.run(initialize_rag())
with open("./book.txt", "r", encoding="utf-8") as f:
rag.insert(f.read())
# Perform naive search
print(
rag.query(
"What are the top themes in this story?", param=QueryParam(mode="naive")
)
)
# Perform local search
print(
rag.query(
"What are the top themes in this story?", param=QueryParam(mode="local")
)
)
# Perform global search
print(
rag.query(
"What are the top themes in this story?", param=QueryParam(mode="global")
)
)
# Perform hybrid search
print(
rag.query(
"What are the top themes in this story?", param=QueryParam(mode="hybrid")
)
)
if __name__ == "__main__":
main()
-191
View File
@@ -1,191 +0,0 @@
# LightRAG Helm Chart
这是用于在Kubernetes集群上部署LightRAG服务的Helm chart。
LightRAG有两种推荐的部署方法:
1. **轻量级部署**:使用内置轻量级存储,适合测试和小规模使用
2. **生产环境部署**:使用外部数据库(如PostgreSQL和Neo4J),适合生产环境和大规模使用
> 如果您想要部署过程的视频演示,可以查看[bilibili](https://www.bilibili.com/video/BV1bUJazBEq2/)上的视频教程,对于喜欢视觉指导的用户可能会有所帮助。
## 前提条件
确保安装和配置了以下工具:
* **Kubernetes集群**
* 需要一个运行中的Kubernetes集群。
* 对于本地开发或演示,可以使用[Minikube](https://minikube.sigs.k8s.io/docs/start/)(需要≥2个CPU,≥4GB内存,以及Docker/VM驱动支持)。
* 任何标准的云端或本地Kubernetes集群(EKS、GKE、AKS等)也可以使用。
* **kubectl**
* Kubernetes命令行工具,用于管理集群。
* 按照官方指南安装:[安装和设置kubectl](https://kubernetes.io/docs/tasks/tools/#kubectl)。
* **Helm**v3.x+
* Kubernetes包管理器,用于安装LightRAG。
* 通过官方指南安装:[安装Helm](https://helm.sh/docs/intro/install/)。
## 轻量级部署(无需外部数据库)
这种部署选项使用内置的轻量级存储组件,非常适合测试、演示或小规模使用场景。无需外部数据库配置。
您可以使用提供的便捷脚本或直接使用Helm命令部署LightRAG。两种方法都配置了`lightrag/values.yaml`文件中定义的相同环境变量。
### 使用便捷脚本(推荐):
```bash
export OPENAI_API_BASE=<您的OPENAI_API_BASE>
export OPENAI_API_KEY=<您的OPENAI_API_KEY>
bash ./install_lightrag_dev.sh
```
### 或直接使用Helm
```bash
# 您可以覆盖任何想要的环境参数
helm upgrade --install lightrag ./lightrag \
--namespace rag \
--set-string env.LIGHTRAG_KV_STORAGE=JsonKVStorage \
--set-string env.LIGHTRAG_VECTOR_STORAGE=NanoVectorDBStorage \
--set-string env.LIGHTRAG_GRAPH_STORAGE=NetworkXStorage \
--set-string env.LIGHTRAG_DOC_STATUS_STORAGE=JsonDocStatusStorage \
--set-string env.LLM_BINDING=openai \
--set-string env.LLM_MODEL=gpt-4o-mini \
--set-string env.LLM_BINDING_HOST=$OPENAI_API_BASE \
--set-string env.LLM_BINDING_API_KEY=$OPENAI_API_KEY \
--set-string env.EMBEDDING_BINDING=openai \
--set-string env.EMBEDDING_MODEL=text-embedding-ada-002 \
--set-string env.EMBEDDING_DIM=1536 \
--set-string env.EMBEDDING_BINDING_API_KEY=$OPENAI_API_KEY
```
### 访问应用程序:
```bash
# 1. 在终端中运行此端口转发命令:
kubectl --namespace rag port-forward svc/lightrag-dev 9621:9621
# 2. 当命令运行时,打开浏览器并导航到:
# http://localhost:9621
```
## 生产环境部署(使用外部数据库)
### 1. 安装数据库
> 如果您已经准备好了数据库,可以跳过此步骤。详细信息可以在:[README.md](databases%2FREADME.md)中找到。
我们推荐使用KubeBlocks进行数据库部署。KubeBlocks是一个云原生数据库操作符,可以轻松地在Kubernetes上以生产规模运行任何数据库。
首先,安装KubeBlocks和KubeBlocks-Addons(如已安装可跳过):
```bash
bash ./databases/01-prepare.sh
```
然后安装所需的数据库。默认情况下,这将安装PostgreSQL和Neo4J,但您可以修改[00-config.sh](databases%2F00-config.sh)以根据需要选择不同的数据库:
```bash
bash ./databases/02-install-database.sh
```
验证集群是否正在运行:
```bash
kubectl get clusters -n rag
# 预期输出:
# NAME CLUSTER-DEFINITION TERMINATION-POLICY STATUS AGE
# neo4j-cluster Delete Running 39s
# pg-cluster postgresql Delete Running 42s
kubectl get po -n rag
# 预期输出:
# NAME READY STATUS RESTARTS AGE
# neo4j-cluster-neo4j-0 1/1 Running 0 58s
# pg-cluster-postgresql-0 4/4 Running 0 59s
# pg-cluster-postgresql-1 4/4 Running 0 59s
```
### 2. 安装LightRAG
LightRAG及其数据库部署在同一Kubernetes集群中,使配置变得简单。
安装脚本会自动从KubeBlocks获取所有数据库连接信息,无需手动设置数据库凭证:
```bash
export OPENAI_API_BASE=<您的OPENAI_API_BASE>
export OPENAI_API_KEY=<您的OPENAI_API_KEY>
bash ./install_lightrag.sh
```
### 访问应用程序:
```bash
# 1. 在终端中运行此端口转发命令:
kubectl --namespace rag port-forward svc/lightrag 9621:9621
# 2. 当命令运行时,打开浏览器并导航到:
# http://localhost:9621
```
## 配置
### 修改资源配置
您可以通过修改`values.yaml`文件来配置LightRAG的资源使用:
```yaml
replicaCount: 1 # 副本数量,可根据需要增加
resources:
limits:
cpu: 1000m # CPU限制,可根据需要调整
memory: 2Gi # 内存限制,可根据需要调整
requests:
cpu: 500m # CPU请求,可根据需要调整
memory: 1Gi # 内存请求,可根据需要调整
```
### 修改持久存储
```yaml
persistence:
enabled: true
ragStorage:
size: 10Gi # RAG存储大小,可根据需要调整
inputs:
size: 5Gi # 输入数据存储大小,可根据需要调整
```
### 配置环境变量
`values.yaml`文件中的`env`部分包含LightRAG的所有环境配置,类似于`.env`文件。当使用helm upgrade或helm install命令时,可以使用--set标志覆盖这些变量。
```yaml
env:
HOST: 0.0.0.0
PORT: 9621
WEBUI_TITLE: Graph RAG Engine
WEBUI_DESCRIPTION: Simple and Fast Graph Based RAG System
# LLM配置
LLM_BINDING: openai # LLM服务提供商
LLM_MODEL: gpt-4o-mini # LLM模型
LLM_BINDING_HOST: # API基础URL(可选)
LLM_BINDING_API_KEY: # API密钥
# 嵌入配置
EMBEDDING_BINDING: openai # 嵌入服务提供商
EMBEDDING_MODEL: text-embedding-ada-002 # 嵌入模型
EMBEDDING_DIM: 1536 # 嵌入维度
EMBEDDING_BINDING_API_KEY: # API密钥
# 存储配置
LIGHTRAG_KV_STORAGE: PGKVStorage # 键值存储类型
LIGHTRAG_VECTOR_STORAGE: PGVectorStorage # 向量存储类型
LIGHTRAG_GRAPH_STORAGE: Neo4JStorage # 图存储类型
LIGHTRAG_DOC_STATUS_STORAGE: PGDocStatusStorage # 文档状态存储类型
```
## 注意事项
- 在部署前确保设置了所有必要的环境变量(API密钥和数据库密码)
- 出于安全原因,建议使用环境变量传递敏感信息,而不是直接写入脚本或values文件
- 轻量级部署适合测试和小规模使用,但数据持久性和性能可能有限
- 生产环境部署(PostgreSQL + Neo4J)推荐用于生产环境和大规模使用
- 有关更多自定义配置,请参考LightRAG官方文档
-191
View File
@@ -1,191 +0,0 @@
# LightRAG Helm Chart
This is the Helm chart for LightRAG, used to deploy LightRAG services on a Kubernetes cluster.
There are two recommended deployment methods for LightRAG:
1. **Lightweight Deployment**: Using built-in lightweight storage, suitable for testing and small-scale usage
2. **Production Deployment**: Using external databases (such as PostgreSQL and Neo4J), suitable for production environments and large-scale usage
> If you'd like a video walkthrough of the deployment process, feel free to check out this optional [video tutorial](https://youtu.be/JW1z7fzeKTw?si=vPzukqqwmdzq9Q4q) on YouTube. It might help clarify some steps for those who prefer visual guidance.
## Prerequisites
Make sure the following tools are installed and configured:
* **Kubernetes cluster**
* A running Kubernetes cluster is required.
* For local development or demos you can use [Minikube](https://minikube.sigs.k8s.io/docs/start/) (needs ≥ 2 CPUs, ≥ 4 GB RAM, and Docker/VM-driver support).
* Any standard cloud or on-premises Kubernetes cluster (EKS, GKE, AKS, etc.) also works.
* **kubectl**
* The Kubernetes command-line tool for managing your cluster.
* Follow the official guide: [Install and Set Up kubectl](https://kubernetes.io/docs/tasks/tools/#kubectl).
* **Helm** (v3.x+)
* Kubernetes package manager used to install LightRAG.
* Install it via the official instructions: [Installing Helm](https://helm.sh/docs/intro/install/).
## Lightweight Deployment (No External Databases Required)
This deployment option uses built-in lightweight storage components that are perfect for testing, demos, or small-scale usage scenarios. No external database configuration is required.
You can deploy LightRAG using either the provided convenience script or direct Helm commands. Both methods configure the same environment variables defined in the `lightrag/values.yaml` file.
### Using the convenience script (recommended):
```bash
export OPENAI_API_BASE=<YOUR_OPENAI_API_BASE>
export OPENAI_API_KEY=<YOUR_OPENAI_API_KEY>
bash ./install_lightrag_dev.sh
```
### Or using Helm directly:
```bash
# You can override any env param you want
helm upgrade --install lightrag ./lightrag \
--namespace rag \
--set-string env.LIGHTRAG_KV_STORAGE=JsonKVStorage \
--set-string env.LIGHTRAG_VECTOR_STORAGE=NanoVectorDBStorage \
--set-string env.LIGHTRAG_GRAPH_STORAGE=NetworkXStorage \
--set-string env.LIGHTRAG_DOC_STATUS_STORAGE=JsonDocStatusStorage \
--set-string env.LLM_BINDING=openai \
--set-string env.LLM_MODEL=gpt-4o-mini \
--set-string env.LLM_BINDING_HOST=$OPENAI_API_BASE \
--set-string env.LLM_BINDING_API_KEY=$OPENAI_API_KEY \
--set-string env.EMBEDDING_BINDING=openai \
--set-string env.EMBEDDING_MODEL=text-embedding-ada-002 \
--set-string env.EMBEDDING_DIM=1536 \
--set-string env.EMBEDDING_BINDING_API_KEY=$OPENAI_API_KEY
```
### Accessing the application:
```bash
# 1. Run this port-forward command in your terminal:
kubectl --namespace rag port-forward svc/lightrag-dev 9621:9621
# 2. While the command is running, open your browser and navigate to:
# http://localhost:9621
```
## Production Deployment (Using External Databases)
### 1. Install Databases
> You can skip this step if you've already prepared databases. Detailed information can be found in: [README.md](databases%2FREADME.md).
We recommend KubeBlocks for database deployment. KubeBlocks is a cloud-native database operator that makes it easy to run any database on Kubernetes at production scale.
First, install KubeBlocks and KubeBlocks-Addons (skip if already installed):
```bash
bash ./databases/01-prepare.sh
```
Then install the required databases. By default, this will install PostgreSQL and Neo4J, but you can modify [00-config.sh](databases%2F00-config.sh) to select different databases based on your needs:
```bash
bash ./databases/02-install-database.sh
```
Verify that the clusters are up and running:
```bash
kubectl get clusters -n rag
# Expected output:
# NAME CLUSTER-DEFINITION TERMINATION-POLICY STATUS AGE
# neo4j-cluster Delete Running 39s
# pg-cluster postgresql Delete Running 42s
kubectl get po -n rag
# Expected output:
# NAME READY STATUS RESTARTS AGE
# neo4j-cluster-neo4j-0 1/1 Running 0 58s
# pg-cluster-postgresql-0 4/4 Running 0 59s
# pg-cluster-postgresql-1 4/4 Running 0 59s
```
### 2. Install LightRAG
LightRAG and its databases are deployed within the same Kubernetes cluster, making configuration straightforward.
The installation script automatically retrieves all database connection information from KubeBlocks, eliminating the need to manually set database credentials:
```bash
export OPENAI_API_BASE=<YOUR_OPENAI_API_BASE>
export OPENAI_API_KEY=<YOUR_OPENAI_API_KEY>
bash ./install_lightrag.sh
```
### Accessing the application:
```bash
# 1. Run this port-forward command in your terminal:
kubectl --namespace rag port-forward svc/lightrag 9621:9621
# 2. While the command is running, open your browser and navigate to:
# http://localhost:9621
```
## Configuration
### Modifying Resource Configuration
You can configure LightRAG's resource usage by modifying the `values.yaml` file:
```yaml
replicaCount: 1 # Number of replicas, can be increased as needed
resources:
limits:
cpu: 1000m # CPU limit, can be adjusted as needed
memory: 2Gi # Memory limit, can be adjusted as needed
requests:
cpu: 500m # CPU request, can be adjusted as needed
memory: 1Gi # Memory request, can be adjusted as needed
```
### Modifying Persistent Storage
```yaml
persistence:
enabled: true
ragStorage:
size: 10Gi # RAG storage size, can be adjusted as needed
inputs:
size: 5Gi # Input data storage size, can be adjusted as needed
```
### Configuring Environment Variables
The `env` section in the `values.yaml` file contains all environment configurations for LightRAG, similar to a `.env` file. When using helm upgrade or helm install commands, you can override these with the --set flag.
```yaml
env:
HOST: 0.0.0.0
PORT: 9621
WEBUI_TITLE: Graph RAG Engine
WEBUI_DESCRIPTION: Simple and Fast Graph Based RAG System
# LLM Configuration
LLM_BINDING: openai # LLM service provider
LLM_MODEL: gpt-4o-mini # LLM model
LLM_BINDING_HOST: # API base URL (optional)
LLM_BINDING_API_KEY: # API key
# Embedding Configuration
EMBEDDING_BINDING: openai # Embedding service provider
EMBEDDING_MODEL: text-embedding-ada-002 # Embedding model
EMBEDDING_DIM: 1536 # Embedding dimension
EMBEDDING_BINDING_API_KEY: # API key
# Storage Configuration
LIGHTRAG_KV_STORAGE: PGKVStorage # Key-value storage type
LIGHTRAG_VECTOR_STORAGE: PGVectorStorage # Vector storage type
LIGHTRAG_GRAPH_STORAGE: Neo4JStorage # Graph storage type
LIGHTRAG_DOC_STATUS_STORAGE: PGDocStatusStorage # Document status storage type
```
## Notes
- Ensure all necessary environment variables (API keys and database passwords) are set before deployment
- For security reasons, it's recommended to pass sensitive information using environment variables rather than writing them directly in scripts or values files
- Lightweight deployment is suitable for testing and small-scale usage, but data persistence and performance may be limited
- Production deployment (PostgreSQL + Neo4J) is recommended for production environments and large-scale usage
- For more customized configurations, please refer to the official LightRAG documentation
@@ -1,21 +0,0 @@
#!/bin/bash
# Get the directory where this script is located
DATABASE_SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
source "$DATABASE_SCRIPT_DIR/scripts/common.sh"
# Namespace configuration
NAMESPACE="rag"
# version
KB_VERSION="1.0.0-beta.48"
ADDON_CLUSTER_CHART_VERSION="1.0.0-alpha.0"
# Helm repository
HELM_REPO="https://apecloud.github.io/helm-charts"
# Set to true to enable the database, false to disable
ENABLE_POSTGRESQL=true
ENABLE_REDIS=false
ENABLE_QDRANT=false
ENABLE_NEO4J=true
ENABLE_ELASTICSEARCH=false
ENABLE_MONGODB=false
@@ -1,33 +0,0 @@
#!/bin/bash
# Get the directory where this script is located
DATABASE_SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
# Load configuration file
source "$DATABASE_SCRIPT_DIR/00-config.sh"
check_dependencies
# Check if KubeBlocks is already installed, install it if it is not.
source "$DATABASE_SCRIPT_DIR/install-kubeblocks.sh"
# Create namespaces
print "Creating namespaces..."
kubectl create namespace $NAMESPACE 2>/dev/null || true
# Install database addons
print "Installing KubeBlocks database addons..."
# Add and update Helm repository
print "Adding and updating KubeBlocks Helm repository..."
helm repo add kubeblocks $HELM_REPO
helm repo update
# Install database addons based on configuration
[ "$ENABLE_POSTGRESQL" = true ] && print "Installing PostgreSQL addon..." && helm upgrade --install kb-addon-postgresql kubeblocks/postgresql --namespace kb-system --version $ADDON_CLUSTER_CHART_VERSION
[ "$ENABLE_REDIS" = true ] && print "Installing Redis addon..." && helm upgrade --install kb-addon-redis kubeblocks/redis --namespace kb-system --version $ADDON_CLUSTER_CHART_VERSION
[ "$ENABLE_ELASTICSEARCH" = true ] && print "Installing Elasticsearch addon..." && helm upgrade --install kb-addon-elasticsearch kubeblocks/elasticsearch --namespace kb-system --version $ADDON_CLUSTER_CHART_VERSION
[ "$ENABLE_QDRANT" = true ] && print "Installing Qdrant addon..." && helm upgrade --install kb-addon-qdrant kubeblocks/qdrant --namespace kb-system --version $ADDON_CLUSTER_CHART_VERSION
[ "$ENABLE_MONGODB" = true ] && print "Installing MongoDB addon..." && helm upgrade --install kb-addon-mongodb kubeblocks/mongodb --namespace kb-system --version $ADDON_CLUSTER_CHART_VERSION
[ "$ENABLE_NEO4J" = true ] && print "Installing Neo4j addon..." && helm upgrade --install kb-addon-neo4j kubeblocks/neo4j --namespace kb-system --version $ADDON_CLUSTER_CHART_VERSION
print_success "KubeBlocks database addons installation completed!"
print "Now you can run 02-install-database.sh to install database clusters"
@@ -1,62 +0,0 @@
#!/bin/bash
# Get the directory where this script is located
DATABASE_SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
# Load configuration file
source "$DATABASE_SCRIPT_DIR/00-config.sh"
print "Installing database clusters..."
# Install database clusters based on configuration
[ "$ENABLE_POSTGRESQL" = true ] && print "Installing PostgreSQL cluster..." && helm upgrade --install pg-cluster kubeblocks/postgresql-cluster -f "$DATABASE_SCRIPT_DIR/postgresql/values.yaml" --namespace $NAMESPACE --version $ADDON_CLUSTER_CHART_VERSION
[ "$ENABLE_REDIS" = true ] && print "Installing Redis cluster..." && helm upgrade --install redis-cluster kubeblocks/redis-cluster -f "$DATABASE_SCRIPT_DIR/redis/values.yaml" --namespace $NAMESPACE --version $ADDON_CLUSTER_CHART_VERSION
[ "$ENABLE_ELASTICSEARCH" = true ] && print "Installing Elasticsearch cluster..." && helm upgrade --install es-cluster kubeblocks/elasticsearch-cluster -f "$DATABASE_SCRIPT_DIR/elasticsearch/values.yaml" --namespace $NAMESPACE --version $ADDON_CLUSTER_CHART_VERSION
[ "$ENABLE_QDRANT" = true ] && print "Installing Qdrant cluster..." && helm upgrade --install qdrant-cluster kubeblocks/qdrant-cluster -f "$DATABASE_SCRIPT_DIR/qdrant/values.yaml" --namespace $NAMESPACE --version $ADDON_CLUSTER_CHART_VERSION
[ "$ENABLE_MONGODB" = true ] && print "Installing MongoDB cluster..." && helm upgrade --install mongodb-cluster kubeblocks/mongodb-cluster -f "$DATABASE_SCRIPT_DIR/mongodb/values.yaml" --namespace $NAMESPACE --version $ADDON_CLUSTER_CHART_VERSION
[ "$ENABLE_NEO4J" = true ] && print "Installing Neo4j cluster..." && helm upgrade --install neo4j-cluster kubeblocks/neo4j-cluster -f "$DATABASE_SCRIPT_DIR/neo4j/values.yaml" --namespace $NAMESPACE --version $ADDON_CLUSTER_CHART_VERSION
# Wait for databases to be ready
print "Waiting for databases to be ready..."
TIMEOUT=600 # Set timeout to 10 minutes
START_TIME=$(date +%s)
while true; do
CURRENT_TIME=$(date +%s)
ELAPSED=$((CURRENT_TIME - START_TIME))
if [ $ELAPSED -gt $TIMEOUT ]; then
print_error "Timeout waiting for databases to be ready. Please check database status manually and try again"
exit 1
fi
# Build wait conditions for enabled databases
WAIT_CONDITIONS=()
[ "$ENABLE_POSTGRESQL" = true ] && WAIT_CONDITIONS+=("kubectl wait --for=condition=ready pods -l app.kubernetes.io/instance=pg-cluster -n $NAMESPACE --timeout=10s")
[ "$ENABLE_REDIS" = true ] && WAIT_CONDITIONS+=("kubectl wait --for=condition=ready pods -l app.kubernetes.io/instance=redis-cluster -n $NAMESPACE --timeout=10s")
[ "$ENABLE_ELASTICSEARCH" = true ] && WAIT_CONDITIONS+=("kubectl wait --for=condition=ready pods -l app.kubernetes.io/instance=es-cluster -n $NAMESPACE --timeout=10s")
[ "$ENABLE_QDRANT" = true ] && WAIT_CONDITIONS+=("kubectl wait --for=condition=ready pods -l app.kubernetes.io/instance=qdrant-cluster -n $NAMESPACE --timeout=10s")
[ "$ENABLE_MONGODB" = true ] && WAIT_CONDITIONS+=("kubectl wait --for=condition=ready pods -l app.kubernetes.io/instance=mongodb-cluster -n $NAMESPACE --timeout=10s")
[ "$ENABLE_NEO4J" = true ] && WAIT_CONDITIONS+=("kubectl wait --for=condition=ready pods -l app.kubernetes.io/instance=neo4j-cluster -n $NAMESPACE --timeout=10s")
# Check if all enabled databases are ready
ALL_READY=true
for CONDITION in "${WAIT_CONDITIONS[@]}"; do
if ! eval "$CONDITION &> /dev/null"; then
ALL_READY=false
break
fi
done
if [ "$ALL_READY" = true ]; then
print "All database pods are ready, continuing with deployment..."
break
fi
print "Waiting for database pods to be ready (${ELAPSED}s elapsed)..."
sleep 10
done
print_success "Database clusters installation completed!"
print "Use the following command to check the status of installed clusters:"
print "kubectl get clusters -n $NAMESPACE"
@@ -1,20 +0,0 @@
#!/bin/bash
# Get the directory where this script is located
DATABASE_SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
# Load configuration file
source "$DATABASE_SCRIPT_DIR/00-config.sh"
print "Uninstalling database clusters..."
# Uninstall database clusters based on configuration
[ "$ENABLE_POSTGRESQL" = true ] && print "Uninstalling PostgreSQL cluster..." && helm uninstall pg-cluster --namespace $NAMESPACE 2>/dev/null || true
[ "$ENABLE_REDIS" = true ] && print "Uninstalling Redis cluster..." && helm uninstall redis-cluster --namespace $NAMESPACE 2>/dev/null || true
[ "$ENABLE_ELASTICSEARCH" = true ] && print "Uninstalling Elasticsearch cluster..." && helm uninstall es-cluster --namespace $NAMESPACE 2>/dev/null || true
[ "$ENABLE_QDRANT" = true ] && print "Uninstalling Qdrant cluster..." && helm uninstall qdrant-cluster --namespace $NAMESPACE 2>/dev/null || true
[ "$ENABLE_MONGODB" = true ] && print "Uninstalling MongoDB cluster..." && helm uninstall mongodb-cluster --namespace $NAMESPACE 2>/dev/null || true
[ "$ENABLE_NEO4J" = true ] && print "Uninstalling Neo4j cluster..." && helm uninstall neo4j-cluster --namespace $NAMESPACE 2>/dev/null || true
print_success "Database clusters uninstalled"
print "To uninstall database addons and KubeBlocks, run 04-cleanup.sh"
@@ -1,26 +0,0 @@
#!/bin/bash
# Get the directory where this script is located
DATABASE_SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
# Load configuration file
source "$DATABASE_SCRIPT_DIR/00-config.sh"
print "Uninstalling KubeBlocks database addons..."
# Uninstall database addons based on configuration
[ "$ENABLE_POSTGRESQL" = true ] && print "Uninstalling PostgreSQL addon..." && helm uninstall kb-addon-postgresql --namespace kb-system 2>/dev/null || true
[ "$ENABLE_REDIS" = true ] && print "Uninstalling Redis addon..." && helm uninstall kb-addon-redis --namespace kb-system 2>/dev/null || true
[ "$ENABLE_ELASTICSEARCH" = true ] && print "Uninstalling Elasticsearch addon..." && helm uninstall kb-addon-elasticsearch --namespace kb-system 2>/dev/null || true
[ "$ENABLE_QDRANT" = true ] && print "Uninstalling Qdrant addon..." && helm uninstall kb-addon-qdrant --namespace kb-system 2>/dev/null || true
[ "$ENABLE_MONGODB" = true ] && print "Uninstalling MongoDB addon..." && helm uninstall kb-addon-mongodb --namespace kb-system 2>/dev/null || true
[ "$ENABLE_NEO4J" = true ] && print "Uninstalling Neo4j addon..." && helm uninstall kb-addon-neo4j --namespace kb-system 2>/dev/null || true
print_success "Database addons uninstallation completed!"
source "$DATABASE_SCRIPT_DIR/uninstall-kubeblocks.sh"
kubectl delete namespace $NAMESPACE
kubectl delete namespace kb-system
print_success "KubeBlocks uninstallation completed!"
-170
View File
@@ -1,170 +0,0 @@
# Using KubeBlocks to Deploy and Manage Databases
Learn how to quickly deploy and manage various databases in a Kubernetes (K8s) environment through KubeBlocks.
## Introduction to KubeBlocks
KubeBlocks is a production-ready, open-source toolkit that runs any database--SQL, NoSQL, vector, or document--on Kubernetes.
It scales smoothly from quick dev tests to full production clusters, making it a solid choice for RAG workloads like FastGPT that need several data stores working together.
## Prerequisites
Make sure the following tools are installed and configured:
* **Kubernetes cluster**
* A running Kubernetes cluster is required.
* For local development or demos you can use [Minikube](https://minikube.sigs.k8s.io/docs/start/) (needs ≥ 2 CPUs, ≥ 4 GB RAM, and Docker/VM-driver support).
* Any standard cloud or on-premises Kubernetes cluster (EKS, GKE, AKS, etc.) also works.
* **kubectl**
* The Kubernetes command-line interface.
* Follow the official guide: [Install and Set Up kubectl](https://kubernetes.io/docs/tasks/tools/#kubectl).
* **Helm** (v3.x+)
* Kubernetes package manager used by the scripts below.
* Install it via the official instructions: [Installing Helm](https://helm.sh/docs/intro/install/).
## Installing
1. **Configure the databases you want**
Edit `00-config.sh` file. Based on your requirements, set the variable to `true` for the databases you want to install.
For example, to install PostgreSQL and Neo4j:
```bash
ENABLE_POSTGRESQL=true
ENABLE_REDIS=false
ENABLE_ELASTICSEARCH=false
ENABLE_QDRANT=false
ENABLE_MONGODB=false
ENABLE_NEO4J=true
```
2. **Prepare the environment and install KubeBlocks add-ons**
```bash
bash ./01-prepare.sh
```
*What the script does*
`01-prepare.sh` performs basic pre-checks (Helm, kubectl, cluster reachability), adds the KubeBlocks Helm repo, and installs any core CRDs or controllers that KubeBlocks itself needs. It also installs the addons for every database you enabled in `00-config.sh`, but **does not** create the actual database clusters yet.
3. **(Optional) Modify database settings**
Before deployment you can edit the `values.yaml` file inside each `<db>/` directory to change `version`, `replicas`, `CPU`, `memory`, `storage size`, etc.
4. **Install the database clusters**
```bash
bash ./02-install-database.sh
```
*What the script does*
`02-install-database.sh` **actually deploys the chosen databases to Kubernetes**.
When the script completes, confirm that the clusters are up. It may take a few minutes for all the clusters to become ready,
especially if this is the first time running the script as Kubernetes needs to pull container images from registries.
You can monitor the progress using the following commands:
```bash
kubectl get clusters -n rag
NAME CLUSTER-DEFINITION TERMINATION-POLICY STATUS AGE
es-cluster Delete Running 11m
mongodb-cluster mongodb Delete Running 11m
pg-cluster postgresql Delete Running 11m
qdrant-cluster qdrant Delete Running 11m
redis-cluster redis Delete Running 11m
```
You can see all the Database `Pods` created by KubeBlocks.
Initially, you might see pods in `ContainerCreating` or `Pending` status - this is normal while images are being pulled and containers are starting up.
Wait until all pods show `Running` status:
```bash
kubectl get po -n rag
NAME READY STATUS RESTARTS AGE
es-cluster-mdit-0 2/2 Running 0 11m
mongodb-cluster-mongodb-0 2/2 Running 0 11m
pg-cluster-postgresql-0 4/4 Running 0 11m
pg-cluster-postgresql-1 4/4 Running 0 11m
qdrant-cluster-qdrant-0 2/2 Running 0 11m
redis-cluster-redis-0 2/2 Running 0 11m
```
You can also check the detailed status of a specific pod if it's taking longer than expected:
```bash
kubectl describe pod <pod-name> -n rag
```
## Connect to Databases
To connect to your databases, follow these steps to identify available accounts, retrieve credentials, and establish connections:
### 1. List Available Database Clusters
First, view the database clusters running in your namespace:
```bash
kubectl get cluster -n rag
```
### 2. Retrieve Authentication Credentials
For PostgreSQL, retrieve the username and password from Kubernetes secrets:
```bash
# Get PostgreSQL username
kubectl get secrets -n rag pg-cluster-postgresql-account-postgres -o jsonpath='{.data.username}' | base64 -d
# Get PostgreSQL password
kubectl get secrets -n rag pg-cluster-postgresql-account-postgres -o jsonpath='{.data.password}' | base64 -d
```
If you have trouble finding the correct secret name, list all secrets:
```bash
kubectl get secrets -n rag
```
### 3. Port Forward to Local Machine
Use port forwarding to access PostgreSQL from your local machine:
```bash
# Forward PostgreSQL port (5432) to your local machine
# You can see all services with: kubectl get svc -n rag
kubectl port-forward -n rag svc/pg-cluster-postgresql-postgresql 5432:5432
```
### 4. Connect Using Database Client
Now you can connect using your preferred PostgreSQL client with the retrieved credentials:
```bash
# Example: connecting with psql
export PGUSER=$(kubectl get secrets -n rag pg-cluster-postgresql-account-postgres -o jsonpath='{.data.username}' | base64 -d)
export PGPASSWORD=$(kubectl get secrets -n rag pg-cluster-postgresql-account-postgres -o jsonpath='{.data.password}' | base64 -d)
psql -h localhost -p 5432 -U $PGUSER
```
Keep the port-forwarding terminal running while you're connecting to the database.
## Uninstalling
1. **Remove the database clusters**
```bash
bash ./03-uninstall-database.sh
```
The script deletes the database clusters that were enabled in `00-config.sh`.
2. **Clean up KubeBlocks add-ons**
```bash
bash ./04-cleanup.sh
```
This removes the addons installed by `01-prepare.sh`.
## Reference
* [Kubeblocks Documentation](https://kubeblocks.io/docs/preview/user_docs/overview/introduction)
@@ -1,36 +0,0 @@
## description: The version of ElasticSearch.
## default: 8.8.2
version: "8.8.2"
## description: Mode for ElasticSearch
## default: multi-node
## one of: [single-node, multi-node]
mode: single-node
## description: The number of replicas, for single-node mode, the replicas is 1, for multi-node mode, the default replicas is 3.
## default: 1
## minimum: 1
## maximum: 5
replicas: 1
## description: CPU cores.
## default: 1
## minimum: 0.5
## maximum: 64
cpu: 1
## description: Memory, the unit is Gi.
## default: 2
## minimum: 1
## maximum: 1000
memory: 2
## description: Storage size, the unit is Gi.
## default: 20
## minimum: 1
## maximum: 10000
storage: 5
extra:
terminationPolicy: Delete
disableExporter: true
@@ -1,52 +0,0 @@
#!/bin/bash
# Get the directory where this script is located
DATABASE_SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
# Load configuration file
source "$DATABASE_SCRIPT_DIR/00-config.sh"
# Check dependencies
check_dependencies
# Function for installing KubeBlocks
install_kubeblocks() {
print "Ready to install KubeBlocks."
# Install CSI Snapshotter CRDs
kubectl create -f https://raw.githubusercontent.com/kubernetes-csi/external-snapshotter/v8.2.0/client/config/crd/snapshot.storage.k8s.io_volumesnapshotclasses.yaml
kubectl create -f https://raw.githubusercontent.com/kubernetes-csi/external-snapshotter/v8.2.0/client/config/crd/snapshot.storage.k8s.io_volumesnapshots.yaml
kubectl create -f https://raw.githubusercontent.com/kubernetes-csi/external-snapshotter/v8.2.0/client/config/crd/snapshot.storage.k8s.io_volumesnapshotcontents.yaml
# Add and update Piraeus repository
helm repo add piraeus-charts https://piraeus.io/helm-charts/
helm repo update
# Install snapshot controller
helm install snapshot-controller piraeus-charts/snapshot-controller -n kb-system --create-namespace
kubectl wait --for=condition=ready pods -l app.kubernetes.io/name=snapshot-controller -n kb-system --timeout=60s
print_success "snapshot-controller installation complete!"
# Install KubeBlocks CRDs
kubectl create -f https://github.com/apecloud/kubeblocks/releases/download/v${KB_VERSION}/kubeblocks_crds.yaml
# Add and update KubeBlocks repository
helm repo add kubeblocks $HELM_REPO
helm repo update
# Install KubeBlocks
helm install kubeblocks kubeblocks/kubeblocks --namespace kb-system --create-namespace --version=${KB_VERSION}
# Verify installation
print "Waiting for KubeBlocks to be ready..."
kubectl wait --for=condition=ready pods -l app.kubernetes.io/instance=kubeblocks -n kb-system --timeout=120s
print_success "KubeBlocks installation complete!"
}
# Check if KubeBlocks is already installed
print "Checking if KubeBlocks is already installed in kb-system namespace..."
if kubectl get namespace kb-system &>/dev/null && kubectl get deployment kubeblocks -n kb-system &>/dev/null; then
print_success "KubeBlocks is already installed in kb-system namespace."
else
# Call the function to install KubeBlocks
install_kubeblocks
fi
@@ -1,34 +0,0 @@
## description: Cluster version.
## default: 6.0.16
## one of: [8.0.8, 8.0.6, 8.0.4, 7.0.19, 7.0.16, 7.0.12, 6.0.22, 6.0.20, 6.0.16, 5.0.30, 5.0.28, 4.4.29, 4.2.24, 4.0.28]
version: 6.0.16
## description: Cluster topology mode.
## default: standalone
## one of: [standalone, replicaset]
mode: standalone
## description: CPU cores.
## default: 0.5
## minimum: 0.5
## maximum: 64
cpu: 1
## description: Memory, the unit is Gi.
## default: 0.5
## minimum: 0.5
## maximum: 1000
memory: 1
## description: Storage size, the unit is Gi.
## default: 20
## minimum: 1
## maximum: 10000
storage: 20
## default: enabled
## one of: [enabled, disabled]
hostnetwork: "disabled"
extra:
terminationPolicy: Delete
@@ -1,46 +0,0 @@
# Version
# description: Cluster version.
# default: 5.26.5
# one of: [5.26.5, 4.4.42]
version: 5.26.5
# Mode
# description: Cluster topology mode.
# default: singlealone
# one of: [singlealone]
mode: singlealone
# CPU
# description: CPU cores.
# default: 2
# minimum: 2
# maximum: 64
cpu: 2
# Memory(Gi)
# description: Memory, the unit is Gi.
# default: 2
# minimum: 2
# maximum: 1000
memory: 4
# Storage(Gi)
# description: Storage size, the unit is Gi.
# default: 20
# minimum: 1
# maximum: 10000
storage: 20
# Replicas
# description: The number of replicas, for standalone mode, the replicas is 1, for replicaset mode, the default replicas is 3.
# default: 1
# minimum: 1
# maximum: 5
replicas: 1
# Storage Class Name
# description: Storage class name of the data volume
storageClassName: ""
extra:
terminationPolicy: Delete
@@ -1,33 +0,0 @@
## description: service version.
## default: 15.7.0
version: 16.4.0
## mode postgresql cluster topology mode replication
mode: replication
## description: The number of replicas, for standalone mode, the replicas is 1, for replication mode, the default replicas is 2.
## default: 1
## minimum: 1
## maximum: 5
replicas: 2
## description: CPU cores.
## default: 0.5
## minimum: 0.5
## maximum: 64
cpu: 1
## description: Memory, the unit is Gi.
## default: 0.5
## minimum: 0.5
## maximum: 1000
memory: 1
## description: Storage size, the unit is Gi.
## default: 20
## minimum: 1
## maximum: 10000
storage: 5
## terminationPolicy define Cluster termination policy. One of DoNotTerminate, Delete, WipeOut.
terminationPolicy: Delete
@@ -1,31 +0,0 @@
## description: The version of Qdrant.
## default: 1.10.0
version: 1.10.0
## description: The number of replicas.
## default: 1
## minimum: 1
## maximum: 16
replicas: 1
## description: CPU cores.
## default: 1
## minimum: 0.5
## maximum: 64
cpu: 1
## description: Memory, the unit is Gi.
## default: 2
## minimum: 0.5
## maximum: 1000
memory: 1
## description: Storage size, the unit is Gi.
## default: 20
## minimum: 1
## maximum: 10000
storage: 20
## customized default values to override kblib chart's values
extra:
terminationPolicy: Delete
@@ -1,34 +0,0 @@
## description: Cluster version.
## default: 7.2.7
version: 7.2.7
## description: Cluster topology mode.
## default: replication
## one of: [standalone, replication, cluster, replication-twemproxy]
mode: standalone
## description: The number of replicas, for standalone mode, the replicas is 1, for replication mode, the default replicas is 2.
## default: 1
## minimum: 1
## maximum: 5
replicas: 1
## description: CPU cores.
## default: 0.5
## minimum: 0.5
## maximum: 64
cpu: 0.5
## description: Memory, the unit is Gi.
## default: 0.5
## minimum: 0.5
## maximum: 1000
memory: 1
## description: Storage size, the unit is Gi.
## default: 20
## minimum: 1
## maximum: 10000
storage: 20
extra:
disableExporter: true
@@ -1,43 +0,0 @@
#!/bin/bash
print_title() {
echo "============================================"
echo "$1"
echo "============================================"
}
print_success() {
echo "$1"
}
print_error() {
echo "$1"
}
print_warning() {
echo "⚠️ $1"
}
print_info() {
echo "🔹 $1"
}
print() {
echo "$1"
}
# Check dependencies
check_dependencies(){
print "Checking dependencies..."
command -v kubectl >/dev/null 2>&1 || { print "Error: kubectl command not found"; exit 1; }
command -v helm >/dev/null 2>&1 || { print "Error: helm command not found"; exit 1; }
# Check if Kubernetes is available
print "Checking if Kubernetes is available..."
kubectl cluster-info &>/dev/null
if [ $? -ne 0 ]; then
print "Error: Kubernetes cluster is not accessible. Please ensure you have proper access to a Kubernetes cluster."
exit 1
fi
print_success "Kubernetes cluster is accessible."
}
@@ -1,51 +0,0 @@
#!/bin/bash
# Get the directory where this script is located
DATABASE_SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
# Load configuration file
source "$DATABASE_SCRIPT_DIR/00-config.sh"
# Check dependencies
print "Checking dependencies..."
command -v kubectl >/dev/null 2>&1 || { print "Error: kubectl command not found"; exit 1; }
command -v helm >/dev/null 2>&1 || { print "Error: helm command not found"; exit 1; }
print "Checking if Kubernetes is available..."
if ! kubectl cluster-info &>/dev/null; then
print "Error: Kubernetes cluster is not accessible. Please ensure you have proper access to a Kubernetes cluster."
exit 1
fi
print "Checking if KubeBlocks is installed in kb-system namespace..."
if ! kubectl get namespace kb-system &>/dev/null; then
print "KubeBlocks is not installed in kb-system namespace."
exit 0
fi
# Function for uninstalling KubeBlocks
uninstall_kubeblocks() {
print "Uninstalling KubeBlocks..."
# Uninstall KubeBlocks Helm chart
helm uninstall kubeblocks -n kb-system
# Uninstall snapshot controller
helm uninstall snapshot-controller -n kb-system
# Delete KubeBlocks CRDs
kubectl delete -f https://github.com/apecloud/kubeblocks/releases/download/v${KB_VERSION}/kubeblocks_crds.yaml --ignore-not-found=true
# Delete CSI Snapshotter CRDs
kubectl delete -f https://raw.githubusercontent.com/kubernetes-csi/external-snapshotter/v8.2.0/client/config/crd/snapshot.storage.k8s.io_volumesnapshotclasses.yaml --ignore-not-found=true
kubectl delete -f https://raw.githubusercontent.com/kubernetes-csi/external-snapshotter/v8.2.0/client/config/crd/snapshot.storage.k8s.io_volumesnapshots.yaml --ignore-not-found=true
kubectl delete -f https://raw.githubusercontent.com/kubernetes-csi/external-snapshotter/v8.2.0/client/config/crd/snapshot.storage.k8s.io_volumesnapshotcontents.yaml --ignore-not-found=true
# Delete the kb-system namespace
print "Waiting for resources to be removed..."
kubectl delete namespace kb-system --timeout=180s
print "KubeBlocks has been successfully uninstalled!"
}
# Call the function to uninstall KubeBlocks
uninstall_kubeblocks
-95
View File
@@ -1,95 +0,0 @@
#!/bin/bash
NAMESPACE=rag
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
if [ -z "$OPENAI_API_KEY" ]; then
echo "OPENAI_API_KEY environment variable is not set"
read -s -p "Enter your OpenAI API key: " OPENAI_API_KEY
if [ -z "$OPENAI_API_KEY" ]; then
echo "Error: OPENAI_API_KEY must be provided"
exit 1
fi
export OPENAI_API_KEY=$OPENAI_API_KEY
fi
if [ -z "$OPENAI_API_BASE" ]; then
echo "OPENAI_API_BASE environment variable is not set, will use default value"
read -p "Enter OpenAI API base URL (press Enter to skip if not needed): " OPENAI_API_BASE
export OPENAI_API_BASE=$OPENAI_API_BASE
fi
# Install KubeBlocks (if not already installed)
bash "$SCRIPT_DIR/databases/01-prepare.sh"
# Install database clusters
bash "$SCRIPT_DIR/databases/02-install-database.sh"
# Create vector extension in PostgreSQL if enabled
print "Waiting for PostgreSQL pods to be ready..."
if kubectl wait --for=condition=ready pods -l kubeblocks.io/role=primary,app.kubernetes.io/instance=pg-cluster -n $NAMESPACE --timeout=300s; then
print "Creating vector extension in PostgreSQL..."
kubectl exec -it $(kubectl get pods -l kubeblocks.io/role=primary,app.kubernetes.io/instance=pg-cluster -n $NAMESPACE -o name) -n $NAMESPACE -- psql -c "CREATE EXTENSION vector;"
print_success "Vector extension created successfully."
else
print "Warning: PostgreSQL pods not ready within timeout. Vector extension not created."
fi
# Get database passwords from Kubernetes secrets
echo "Retrieving database credentials from Kubernetes secrets..."
POSTGRES_PASSWORD=$(kubectl get secrets -n rag pg-cluster-postgresql-account-postgres -o jsonpath='{.data.password}' | base64 -d)
if [ -z "$POSTGRES_PASSWORD" ]; then
echo "Error: Could not retrieve PostgreSQL password. Make sure PostgreSQL is deployed and the secret exists."
exit 1
fi
export POSTGRES_PASSWORD=$POSTGRES_PASSWORD
NEO4J_PASSWORD=$(kubectl get secrets -n rag neo4j-cluster-neo4j-account-neo4j -o jsonpath='{.data.password}' | base64 -d)
if [ -z "$NEO4J_PASSWORD" ]; then
echo "Error: Could not retrieve Neo4J password. Make sure Neo4J is deployed and the secret exists."
exit 1
fi
export NEO4J_PASSWORD=$NEO4J_PASSWORD
#REDIS_PASSWORD=$(kubectl get secrets -n rag redis-cluster-redis-account-default -o jsonpath='{.data.password}' | base64 -d)
#if [ -z "$REDIS_PASSWORD" ]; then
# echo "Error: Could not retrieve Redis password. Make sure Redis is deployed and the secret exists."
# exit 1
#fi
#export REDIS_PASSWORD=$REDIS_PASSWORD
echo "Deploying production LightRAG (using external databases)..."
if ! kubectl get namespace rag &> /dev/null; then
echo "creating namespace 'rag'..."
kubectl create namespace rag
fi
helm upgrade --install lightrag $SCRIPT_DIR/lightrag \
--namespace $NAMESPACE \
--set-string env.POSTGRES_PASSWORD=$POSTGRES_PASSWORD \
--set-string env.NEO4J_PASSWORD=$NEO4J_PASSWORD \
--set-string env.LLM_BINDING=openai \
--set-string env.LLM_MODEL=gpt-4o-mini \
--set-string env.LLM_BINDING_HOST=$OPENAI_API_BASE \
--set-string env.LLM_BINDING_API_KEY=$OPENAI_API_KEY \
--set-string env.EMBEDDING_BINDING=openai \
--set-string env.EMBEDDING_MODEL=text-embedding-ada-002 \
--set-string env.EMBEDDING_DIM=1536 \
--set-string env.EMBEDDING_BINDING_API_KEY=$OPENAI_API_KEY
# --set-string env.REDIS_URI="redis://default:${REDIS_PASSWORD}@redis-cluster-redis-redis:6379"
# Wait for LightRAG pod to be ready
echo ""
echo "Waiting for lightrag pod to be ready..."
kubectl wait --for=condition=ready pod -l app.kubernetes.io/instance=lightrag --timeout=300s -n rag
echo "lightrag pod is ready"
echo ""
echo "Running Port-Forward:"
echo " kubectl --namespace rag port-forward svc/lightrag 9621:9621"
echo "==========================================="
echo ""
echo "✅ You can visit LightRAG at: http://localhost:9621"
echo ""
kubectl --namespace rag port-forward svc/lightrag 9621:9621
@@ -1,81 +0,0 @@
#!/bin/bash
NAMESPACE=rag
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
check_dependencies(){
echo "Checking dependencies..."
command -v kubectl >/dev/null 2>&1 || { echo "Error: kubectl command not found"; exit 1; }
command -v helm >/dev/null 2>&1 || { echo "Error: helm command not found"; exit 1; }
# Check if Kubernetes is available
echo "Checking if Kubernetes is available..."
kubectl cluster-info &>/dev/null
if [ $? -ne 0 ]; then
echo "Error: Kubernetes cluster is not accessible. Please ensure you have proper access to a Kubernetes cluster."
exit 1
fi
echo "Kubernetes cluster is accessible."
}
check_dependencies
if [ -z "$OPENAI_API_KEY" ]; then
echo "OPENAI_API_KEY environment variable is not set"
read -s -p "Enter your OpenAI API key: " OPENAI_API_KEY
if [ -z "$OPENAI_API_KEY" ]; then
echo "Error: OPENAI_API_KEY must be provided"
exit 1
fi
export OPENAI_API_KEY=$OPENAI_API_KEY
fi
if [ -z "$OPENAI_API_BASE" ]; then
echo "OPENAI_API_BASE environment variable is not set, will use default value"
read -p "Enter OpenAI API base URL (press Enter to skip if not needed): " OPENAI_API_BASE
export OPENAI_API_BASE=$OPENAI_API_BASE
fi
required_env_vars=("OPENAI_API_BASE" "OPENAI_API_KEY")
for var in "${required_env_vars[@]}"; do
if [ -z "${!var}" ]; then
echo "Error: $var environment variable is not set"
exit 1
fi
done
if ! kubectl get namespace rag &> /dev/null; then
echo "creating namespace 'rag'..."
kubectl create namespace rag
fi
helm upgrade --install lightrag-dev $SCRIPT_DIR/lightrag \
--namespace rag \
--set-string env.LIGHTRAG_KV_STORAGE=JsonKVStorage \
--set-string env.LIGHTRAG_VECTOR_STORAGE=NanoVectorDBStorage \
--set-string env.LIGHTRAG_GRAPH_STORAGE=NetworkXStorage \
--set-string env.LIGHTRAG_DOC_STATUS_STORAGE=JsonDocStatusStorage \
--set-string env.LLM_BINDING=openai \
--set-string env.LLM_MODEL=gpt-4o-mini \
--set-string env.LLM_BINDING_HOST=$OPENAI_API_BASE \
--set-string env.LLM_BINDING_API_KEY=$OPENAI_API_KEY \
--set-string env.EMBEDDING_BINDING=openai \
--set-string env.EMBEDDING_MODEL=text-embedding-ada-002 \
--set-string env.EMBEDDING_DIM=1536 \
--set-string env.EMBEDDING_BINDING_API_KEY=$OPENAI_API_KEY
# Wait for LightRAG pod to be ready
echo ""
echo "Waiting for lightrag-dev pod to be ready..."
kubectl wait --for=condition=ready pod -l app.kubernetes.io/instance=lightrag-dev --timeout=300s -n rag
echo "lightrag-dev pod is ready"
echo ""
echo "Running Port-Forward:"
echo " kubectl --namespace rag port-forward svc/lightrag-dev 9621:9621"
echo "==========================================="
echo ""
echo "✅ You can visit LightRAG at: http://localhost:9621"
echo ""
kubectl --namespace rag port-forward svc/lightrag-dev 9621:9621
-23
View File
@@ -1,23 +0,0 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*.orig
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/
-10
View File
@@ -1,10 +0,0 @@
apiVersion: v2
name: lightrag
description: A Helm chart for LightRAG, an efficient and lightweight RAG system
type: application
version: 0.1.1
appVersion: "1.0.0"
maintainers:
- name: LightRAG Team
- name: earayu
email: earayu@gmail.com
@@ -1,38 +0,0 @@
===========================================
LightRAG has been successfully deployed!
===========================================
View application logs:
kubectl logs -f --namespace {{ .Release.Namespace }} deploy/{{ include "lightrag.fullname" . }}
===========================================
Access the application:
{{- if contains "NodePort" .Values.service.type }}
Run these commands to get access information:
-----------------------------------------
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "lightrag.fullname" . }})
export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
echo "LightRAG is accessible at: http://$NODE_IP:$NODE_PORT"
-----------------------------------------
{{- else if contains "LoadBalancer" .Values.service.type }}
Run these commands to get access information (external IP may take a minute to assign):
-----------------------------------------
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "lightrag.fullname" . }} --template "{{ "{{ range (index .status.loadBalancer.ingress 0) }}{{ . }}{{ end }}" }}")
echo "LightRAG is accessible at: http://$SERVICE_IP:{{ .Values.service.port }}"
-----------------------------------------
If SERVICE_IP is empty, retry the command or check service status with:
kubectl get svc --namespace {{ .Release.Namespace }} {{ include "lightrag.fullname" . }}
{{- else if contains "ClusterIP" .Values.service.type }}
For development environments, to access LightRAG from your local machine:
1. Run this port-forward command in your terminal:
kubectl --namespace {{ .Release.Namespace }} port-forward svc/{{ include "lightrag.fullname" . }} {{ .Values.service.port }}:{{ .Values.env.PORT }}
2. While the command is running, open your browser and navigate to:
http://localhost:{{ .Values.service.port }}
Note: To stop port-forwarding, press Ctrl+C in the terminal.
{{- end }}
===========================================
@@ -1,42 +0,0 @@
{{/*
Application name
*/}}
{{- define "lightrag.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Full application name
*/}}
{{- define "lightrag.fullname" -}}
{{- default .Release.Name .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "lightrag.labels" -}}
app.kubernetes.io/name: {{ include "lightrag.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{/*
Selector labels
*/}}
{{- define "lightrag.selectorLabels" -}}
app.kubernetes.io/name: {{ include "lightrag.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{/*
.env file content
*/}}
{{- define "lightrag.envContent" -}}
{{- $first := true -}}
{{- range $key, $val := .Values.env -}}
{{- if not $first -}}{{- "\n" -}}{{- end -}}
{{- $first = false -}}
{{ $key }}={{ $val }}
{{- end -}}
{{- end -}}
@@ -1,81 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "lightrag.fullname" . }}
labels:
{{- include "lightrag.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
{{- include "lightrag.selectorLabels" . | nindent 6 }}
template:
metadata:
annotations:
checksum/config: {{ include "lightrag.envContent" . | sha256sum }}
labels:
{{- include "lightrag.selectorLabels" . | nindent 8 }}
spec:
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: {{ .Values.env.PORT }}
protocol: TCP
readinessProbe:
httpGet:
path: /health
port: http
initialDelaySeconds: 10
periodSeconds: 5
timeoutSeconds: 2
successThreshold: 1
failureThreshold: 3
resources:
{{- toYaml .Values.resources | nindent 12 }}
volumeMounts:
- name: rag-storage
mountPath: /app/data/rag_storage
- name: inputs
mountPath: /app/data/inputs
- name: env-file
mountPath: /app/.env
subPath: .env
{{- $envFrom := default (dict) .Values.envFrom }}
{{- $envFromEntries := list }}
{{- range (default (list) (index $envFrom "secrets")) }}
{{- $envFromEntries = append $envFromEntries (dict "secretRef" (dict "name" .name)) }}
{{- end }}
{{- range (default (list) (index $envFrom "configmaps")) }}
{{- $envFromEntries = append $envFromEntries (dict "configMapRef" (dict "name" .name)) }}
{{- end }}
{{- if gt (len $envFromEntries) 0 }}
envFrom:
{{- toYaml $envFromEntries | nindent 12 }}
{{- end }}
{{- with .Values.image.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
volumes:
- name: env-file
secret:
secretName: {{ include "lightrag.fullname" . }}-env
{{- if .Values.persistence.enabled }}
- name: rag-storage
persistentVolumeClaim:
claimName: {{ include "lightrag.fullname" . }}-rag-storage
- name: inputs
persistentVolumeClaim:
claimName: {{ include "lightrag.fullname" . }}-inputs
{{- else }}
- name: rag-storage
emptyDir: {}
- name: inputs
emptyDir: {}
{{- end }}
strategy:
{{- toYaml .Values.updateStrategy | nindent 4 }}
@@ -1,28 +0,0 @@
{{- if .Values.persistence.enabled }}
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: {{ include "lightrag.fullname" . }}-rag-storage
labels:
{{- include "lightrag.labels" . | nindent 4 }}
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: {{ .Values.persistence.ragStorage.size }}
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: {{ include "lightrag.fullname" . }}-inputs
labels:
{{- include "lightrag.labels" . | nindent 4 }}
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: {{ .Values.persistence.inputs.size }}
{{- end }}
@@ -1,10 +0,0 @@
apiVersion: v1
kind: Secret
metadata:
name: {{ include "lightrag.fullname" . }}-env
labels:
{{- include "lightrag.labels" . | nindent 4 }}
type: Opaque
stringData:
.env: |-
{{- include "lightrag.envContent" . | nindent 4 }}
@@ -1,15 +0,0 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "lightrag.fullname" . }}
labels:
{{- include "lightrag.labels" . | nindent 4 }}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.port }}
targetPort: {{ .Values.env.PORT }}
protocol: TCP
name: http
selector:
{{- include "lightrag.selectorLabels" . | nindent 4 }}
-82
View File
@@ -1,82 +0,0 @@
replicaCount: 1
image:
repository: ghcr.io/hkuds/lightrag
tag: latest
# Optionally specify imagePullSecrets if your image is in a private registry
# example:
# imagePullSecrets:
# - name: my-registry-secret
imagePullSecrets: []
# Specify a deployment strategy
# example:
# updateStrategy:
# type: RollingUpdate
# rollingUpdate:
# maxUnavailable: 25%
# maxSurge: 25%
# Default for now should be Recreate as any RollingUpdate will cause issues with
# multiple instances trying to access the same persistent storage if not using RWX volumes.
updateStrategy:
type: Recreate
service:
type: ClusterIP
port: 9621
resources:
limits:
cpu: 1000m
memory: 2Gi
requests:
cpu: 500m
memory: 1Gi
persistence:
enabled: true
ragStorage:
size: 10Gi
inputs:
size: 5Gi
# Allow specifying additional environment variables from ConfigMaps or Secrets created outside of this chart
envFrom:
configmaps: []
# - name: my-shiny-configmap-1
secrets: []
# - name: my-shiny-secret-1
env:
HOST: 0.0.0.0
PORT: 9621
WEBUI_TITLE: Graph RAG Engine
WEBUI_DESCRIPTION: Simple and Fast Graph Based RAG System
LLM_BINDING: openai
LLM_MODEL: gpt-4o-mini
LLM_BINDING_HOST:
LLM_BINDING_API_KEY:
EMBEDDING_BINDING: openai
EMBEDDING_MODEL: text-embedding-ada-002
EMBEDDING_DIM: 1536
EMBEDDING_BINDING_API_KEY:
LIGHTRAG_KV_STORAGE: PGKVStorage
LIGHTRAG_VECTOR_STORAGE: PGVectorStorage
# LIGHTRAG_KV_STORAGE: RedisKVStorage
# LIGHTRAG_VECTOR_STORAGE: QdrantVectorDBStorage
LIGHTRAG_GRAPH_STORAGE: Neo4JStorage
LIGHTRAG_DOC_STATUS_STORAGE: PGDocStatusStorage
# Replace with your POSTGRES credentials
POSTGRES_HOST: pg-cluster-postgresql-postgresql
POSTGRES_PORT: 5432
POSTGRES_USER: postgres
POSTGRES_PASSWORD:
POSTGRES_DATABASE: postgres
POSTGRES_WORKSPACE: default
# Replace with your NEO4J credentials
NEO4J_URI: neo4j://neo4j-cluster-neo4j:7687
NEO4J_USERNAME: neo4j
NEO4J_PASSWORD:
# Replace with your Qdrant credentials
QDRANT_URL: http://qdrant-cluster-qdrant-qdrant:6333
# REDIS_URI: redis://default:${REDIS_PASSWORD}@redis-cluster-redis-redis:6379
@@ -1,4 +0,0 @@
#!/bin/bash
NAMESPACE=rag
helm uninstall lightrag --namespace $NAMESPACE
@@ -1,4 +0,0 @@
#!/bin/bash
NAMESPACE=rag
helm uninstall lightrag-dev --namespace $NAMESPACE
-30
View File
@@ -1,30 +0,0 @@
[Unit]
Description=LightRAG XYJ Service
After=network.target
[Service]
Type=simple
User=netman
# Memory settings
MemoryHigh=8G
MemoryMax=12G
# Set the LightRAG installation directory (change this to match your installation path)
Environment="LIGHTRAG_HOME=/home/netman/lightrag-xyj"
# Set Environment to your Python virtual environment
Environment="PATH=${LIGHTRAG_HOME}/.venv/bin"
WorkingDirectory=${LIGHTRAG_HOME}
ExecStart=${LIGHTRAG_HOME}/.venv/bin/lightrag-server
# ExecStart=${LIGHTRAG_HOME}/.venv/bin/lightrag-gunicorn
# Kill mode require ExecStart must be gunicorn or unvicorn main process
KillMode=process
ExecStop=/bin/kill -s TERM $MAINPID
TimeoutStopSec=60
Restart=always
RestartSec=30
[Install]
WantedBy=multi-user.target
-5
View File
@@ -1,5 +0,0 @@
from .lightrag import LightRAG as LightRAG, QueryParam as QueryParam
__version__ = "1.4.9.9"
__author__ = "Zirui Guo"
__url__ = "https://github.com/HKUDS/LightRAG"
-2
View File
@@ -1,2 +0,0 @@
inputs
rag_storage
-626
View File
@@ -1,626 +0,0 @@
# LightRAG 服务器和 WebUI
LightRAG 服务器旨在提供 Web 界面和 API 支持。Web 界面便于文档索引、知识图谱探索和简单的 RAG 查询界面。LightRAG 服务器还提供了与 Ollama 兼容的接口,旨在将 LightRAG 模拟为 Ollama 聊天模型。这使得 AI 聊天机器人(如 Open WebUI)可以轻松访问 LightRAG。
![image-20250323122538997](./README.assets/image-20250323122538997.png)
![image-20250323122754387](./README.assets/image-20250323122754387.png)
![image-20250323123011220](./README.assets/image-20250323123011220.png)
## 入门指南
### 安装
* 从 PyPI 安装
```bash
# 使用 uv (推荐)
uv pip install "lightrag-hku[api]"
# 或使用 pip
# pip install "lightrag-hku[api]"
```
* 从源代码安装
```bash
# 克隆仓库
git clone https://github.com/HKUDS/lightrag.git
# 进入仓库目录
cd lightrag
# 使用 uv (推荐)
# 注意: uv sync 会自动在 .venv/ 目录创建虚拟环境
uv sync --extra api
source .venv/bin/activate # 激活虚拟环境 (Linux/macOS)
# Windows 系统: .venv\Scripts\activate
# 或使用 pip 与虚拟环境
# python -m venv .venv
# source .venv/bin/activate # Windows: .venv\Scripts\activate
# pip install -e ".[api]"
# 构建前端代码
cd lightrag_webui
bun install --frozen-lockfile
bun run build
cd ..
```
### 启动 LightRAG 服务器前的准备
LightRAG 需要同时集成 LLM(大型语言模型)和嵌入模型以有效执行文档索引和查询操作。在首次部署 LightRAG 服务器之前,必须配置 LLM 和嵌入模型的设置。LightRAG 支持绑定到各种 LLM/嵌入后端:
* ollama
* lollms
* openai 或 openai 兼容
* azure_openai
* aws_bedrock
建议使用环境变量来配置 LightRAG 服务器。项目根目录中有一个名为 `env.example` 的示例环境变量文件。请将此文件复制到启动目录并重命名为 `.env`。之后,您可以在 `.env` 文件中修改与 LLM 和嵌入模型相关的参数。需要注意的是,LightRAG 服务器每次启动时都会将 `.env` 中的环境变量加载到系统环境变量中。**LightRAG 服务器会优先使用系统环境变量中的设置**。
> 由于安装了 Python 扩展的 VS Code 可能会在集成终端中自动加载 .env 文件,请在每次修改 .env 文件后打开新的终端会话。
以下是 LLM 和嵌入模型的一些常见设置示例:
* OpenAI LLM + Ollama 嵌入
```
LLM_BINDING=openai
LLM_MODEL=gpt-4o
LLM_BINDING_HOST=https://api.openai.com/v1
LLM_BINDING_API_KEY=your_api_key
EMBEDDING_BINDING=ollama
EMBEDDING_BINDING_HOST=http://localhost:11434
EMBEDDING_MODEL=bge-m3:latest
EMBEDDING_DIM=1024
# EMBEDDING_BINDING_API_KEY=your_api_key
```
* Ollama LLM + Ollama 嵌入
```
LLM_BINDING=ollama
LLM_MODEL=mistral-nemo:latest
LLM_BINDING_HOST=http://localhost:11434
# LLM_BINDING_API_KEY=your_api_key
### Ollama 服务器上下文 token 数(必须大于 MAX_TOTAL_TOKENS+2000
OLLAMA_LLM_NUM_CTX=8192
EMBEDDING_BINDING=ollama
EMBEDDING_BINDING_HOST=http://localhost:11434
EMBEDDING_MODEL=bge-m3:latest
EMBEDDING_DIM=1024
# EMBEDDING_BINDING_API_KEY=your_api_key
```
> **重要提示**:在文档索引前必须确定使用的Embedding模型,且在文档查询阶段必须沿用与索引阶段相同的模型。有些存储(例如PostgreSQL)在首次建立数表的时候需要确定向量维度,因此更换Embedding模型后需要删除向量相关库表,以便让LightRAG重建新的库表。
### 启动 LightRAG 服务器
LightRAG 服务器支持两种运行模式:
* 简单高效的 Uvicorn 模式
```
lightrag-server
```
* 多进程 Gunicorn + Uvicorn 模式(生产模式,不支持 Windows 环境)
```
lightrag-gunicorn --workers 4
```
启动LightRAG的时候,当前工作目录必须含有`.env`配置文件。**要求将.env文件置于启动目录中是经过特意设计的**。 这样做的目的是支持用户同时启动多个LightRAG实例,并为不同实例配置不同的.env文件。**修改.env文件后,您需要重新打开终端以使新设置生效**。 这是因为每次启动时,LightRAG Server会将.env文件中的环境变量加载至系统环境变量,且系统环境变量的设置具有更高优先级。
启动时可以通过命令行参数覆盖`.env`文件中的配置。常用的命令行参数包括:
- `--host`:服务器监听地址(默认:0.0.0.0)
- `--port`:服务器监听端口(默认:9621
- `--timeout`:LLM 请求超时时间(默认:150 秒)
- `--log-level`:日志级别(默认:INFO
- `--working-dir`:数据库持久化目录(默认:./rag_storage
- `--input-dir`:上传文件存放目录(默认:./inputs)
- `--workspace`: 工作空间名称,用于逻辑上隔离多个LightRAG实例之间的数据(默认:空)
### 使用 Docker 启动 LightRAG 服务器
使用 Docker Compose 是部署和运行 LightRAG Server 最便捷的方式。
- 创建一个项目目录。
- 将 LightRAG 仓库中的 `docker-compose.yml` 文件复制到您的项目目录中。
- 准备 `.env` 文件:复制示例文件 [`env.example`](https://ai.znipower.com:5013/c/env.example) 创建自定义的 `.env` 文件,并根据您的具体需求配置 LLM 和嵌入参数。
* 通过以下命令启动 LightRAG 服务器:
```shell
docker compose up
# 如果希望启动后让程序退到后台运行,需要在命令的最后添加 -d 参数
```
> 可以通过以下链接获取官方的docker compose文件:[docker-compose.yml]( https://raw.githubusercontent.com/HKUDS/LightRAG/refs/heads/main/docker-compose.yml) 。如需获取LightRAG的历史版本镜像,可以访问以下链接: [LightRAG Docker Images]( https://github.com/HKUDS/LightRAG/pkgs/container/lightrag). 如需获取更多关于docker部署的信息,请参阅 [DockerDeployment.md](./../../docs/DockerDeployment.md).
### 离线部署
官方的 LightRAG Docker 镜像完全兼容离线或隔离网络环境。如需搭建自己的离线部署环境,请参考 [离线部署指南](./../../docs/OfflineDeployment.md)。
### 启动多个LightRAG实例
有两种方式可以启动多个LightRAG实例。第一种方式是为每个实例配置一个完全独立的工作环境。此时需要为每个实例创建一个独立的工作目录,然后在这个工作目录上放置一个当前实例专用的`.env`配置文件。不同实例的配置文件中的服务器监听端口不能重复,然后在工作目录上执行 lightrag-server 启动服务即可。
第二种方式是所有实例共享一套相同的`.env`配置文件,然后通过命令行参数来为每个实例指定不同的服务器监听端口和工作空间。你可以在同一个工作目录中通过不同的命令行参数启动多个LightRAG实例。例如:
```
# 启动实例1
lightrag-server --port 9621 --workspace space1
# 启动实例2
lightrag-server --port 9622 --workspace space2
```
工作空间的作用是实现不同实例之间的数据隔离。因此不同实例之间的`workspace`参数必须不同,否则会导致数据混乱,数据将会被破坏。
通过 Docker Compose 启动多个 LightRAG 实例时,只需在 `docker-compose.yml` 中为每个容器指定不同的 `WORKSPACE``PORT` 环境变量即可。即使所有实例共享同一个 `.env` 文件,Compose 中定义的容器环境变量也会优先覆盖 `.env` 文件中的同名设置,从而确保每个实例拥有独立的配置。
### LightRAG实例间的数据隔离
每个实例配置一个独立的工作目录和专用`.env`配置文件通常能够保证内存数据库中的本地持久化文件保存在各自的工作目录,实现数据的相互隔离。LightRAG默认存储全部都是内存数据库,通过这种方式进行数据隔离是没有问题的。但是如果使用的是外部数据库,如果不同实例访问的是同一个数据库实例,就需要通过配置工作空间来实现数据隔离,否则不同实例的数据将会出现冲突并被破坏。
命令行的 workspace 参数和`.env`文件中的环境变量`WORKSPACE` 都可以用于指定当前实例的工作空间名字,命令行参数的优先级别更高。下面是不同类型的存储实现工作空间的方式:
- **对于本地基于文件的数据库,数据隔离通过工作空间子目录实现:** JsonKVStorage, JsonDocStatusStorage, NetworkXStorage, NanoVectorDBStorage, FaissVectorDBStorage。
- **对于将数据存储在集合(collection)中的数据库,通过在集合名称前添加工作空间前缀来实现:** RedisKVStorage, RedisDocStatusStorage, MilvusVectorDBStorage, QdrantVectorDBStorage, MongoKVStorage, MongoDocStatusStorage, MongoVectorDBStorage, MongoGraphStorage, PGGraphStorage。
- **对于关系型数据库,数据隔离通过向表中添加 `workspace` 字段进行数据的逻辑隔离:** PGKVStorage, PGVectorStorage, PGDocStatusStorage。
* **对于Neo4j图数据库,通过label来实现数据的逻辑隔离**Neo4JStorage
为了保持对遗留数据的兼容,在未配置工作空间时PostgreSQL的默认工作空间为`default`Neo4j的默认工作空间为`base`。对于所有的外部存储,系统都提供了专用的工作空间环境变量,用于覆盖公共的 `WORKSPACE`环境变量配置。这些适用于指定存储类型的工作空间环境变量为:`REDIS_WORKSPACE`, `MILVUS_WORKSPACE`, `QDRANT_WORKSPACE`, `MONGODB_WORKSPACE`, `POSTGRES_WORKSPACE`, `NEO4J_WORKSPACE`
### Gunicorn + Uvicorn 的多工作进程
LightRAG 服务器可以在 `Gunicorn + Uvicorn` 预加载模式下运行。Gunicorn 的多工作进程(多进程)功能可以防止文档索引任务阻塞 RAG 查询。使用 CPU 密集型文档提取工具(如 docling)在纯 Uvicorn 模式下可能会导致整个系统被阻塞。
虽然 LightRAG 服务器使用一个工作进程来处理文档索引流程,但通过 Uvicorn 的异步任务支持,可以并行处理多个文件。文档索引速度的瓶颈主要在于 LLM。如果您的 LLM 支持高并发,您可以通过增加 LLM 的并发级别来加速文档索引。以下是几个与并发处理相关的环境变量及其默认值:
```
### 工作进程数,数字不大于 (2 x 核心数) + 1
WORKERS=2
### 一批中并行处理的文件数
MAX_PARALLEL_INSERT=2
# LLM 的最大并发请求数
MAX_ASYNC=4
```
### 将 Lightrag 安装为 Linux 服务
从示例文件 `lightrag.service.example` 创建您的服务文件 `lightrag.service`。修改服务文件中的服务启动定义:
```text
# Set Enviroment to your Python virtual enviroment
Environment="PATH=/home/netman/lightrag-xyj/venv/bin"
WorkingDirectory=/home/netman/lightrag-xyj
# ExecStart=/home/netman/lightrag-xyj/venv/bin/lightrag-server
ExecStart=/home/netman/lightrag-xyj/venv/bin/lightrag-gunicorn
```
> ExecStart命令必须是 lightrag-gunicorn 或 lightrag-server 中的一个,不能使用其它脚本包裹它们。因为停止服务必须要求主进程必须是这两个进程。
安装 LightRAG 服务。如果您的系统是 Ubuntu,以下命令将生效:
```shell
sudo cp lightrag.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl start lightrag.service
sudo systemctl status lightrag.service
sudo systemctl enable lightrag.service
```
## Ollama 模拟
我们为 LightRAG 提供了 Ollama 兼容接口,旨在将 LightRAG 模拟为 Ollama 聊天模型。这使得支持 Ollama 的 AI 聊天前端(如 Open WebUI)可以轻松访问 LightRAG。
### 将 Open WebUI 连接到 LightRAG
启动 lightrag-server 后,您可以在 Open WebUI 管理面板中添加 Ollama 类型的连接。然后,一个名为 `lightrag:latest` 的模型将出现在 Open WebUI 的模型管理界面中。用户随后可以通过聊天界面向 LightRAG 发送查询。对于这种用例,最好将 LightRAG 安装为服务。
Open WebUI 使用 LLM 来执行会话标题和会话关键词生成任务。因此,Ollama 聊天补全 API 会检测并将 OpenWebUI 会话相关请求直接转发给底层 LLM。Open WebUI 的截图:
![image-20250323194750379](./README.assets/image-20250323194750379.png)
### 在聊天中选择查询模式
如果您从 LightRAG 的 Ollama 接口发送消息(查询),默认查询模式是 `hybrid`。您可以通过发送带有查询前缀的消息来选择查询模式。
查询字符串中的查询前缀可以决定使用哪种 LightRAG 查询模式来生成响应。支持的前缀包括:
```
/local
/global
/hybrid
/naive
/mix
/bypass
/context
/localcontext
/globalcontext
/hybridcontext
/naivecontext
/mixcontext
```
例如,聊天消息 "/mix 唐僧有几个徒弟" 将触发 LightRAG 的混合模式查询。没有查询前缀的聊天消息默认会触发混合模式查询。
"/bypass" 不是 LightRAG 查询模式,它会告诉 API 服务器将查询连同聊天历史直接传递给底层 LLM。因此用户可以使用 LLM 基于聊天历史回答问题。如果您使用 Open WebUI 作为前端,您可以直接切换到普通 LLM 模型,而不是使用 /bypass 前缀。
"/context" 也不是 LightRAG 查询模式,它会告诉 LightRAG 只返回为 LLM 准备的上下文信息。您可以检查上下文是否符合您的需求,或者自行处理上下文。
### 在聊天中添加用户提示词
使用LightRAG进行内容查询时,应避免将搜索过程与无关的输出处理相结合,这会显著影响查询效果。用户提示(user prompt)正是为解决这一问题而设计 -- 它不参与RAG检索阶段,而是在查询完成后指导大语言模型(LLM)如何处理检索结果。我们可以在查询前缀末尾添加方括号,从而向LLM传递用户提示词:
```
/[使用mermaid格式画图] 请画出 Scrooge 的人物关系图谱
/mix[使用mermaid格式画图] 请画出 Scrooge 的人物关系图谱
```
## API 密钥和认证
默认情况下,LightRAG 服务器可以在没有任何认证的情况下访问。我们可以使用 API 密钥或账户凭证配置服务器以确保其安全。
* API 密钥
```
LIGHTRAG_API_KEY=your-secure-api-key-here
WHITELIST_PATHS=/health,/api/*
```
> 健康检查和 Ollama 模拟端点默认不进行 API 密钥检查。为了安全原因,如果不需要提供Ollama服务,应该把`/api/*`从WHITELIST_PATHS中移除。
API Key使用的请求头是 `X-API-Key` 。以下是使用API访问LightRAG Server的一个例子:
```
curl -X 'POST' \
'http://localhost:9621/documents/scan' \
-H 'accept: application/json' \
-H 'X-API-Key: your-secure-api-key-here-123' \
-d ''
```
* 账户凭证(Web 界面需要登录后才能访问)
LightRAG API 服务器使用基于 HS256 算法的 JWT 认证。要启用安全访问控制,需要以下环境变量:
```bash
# JWT 认证
AUTH_ACCOUNTS='admin:admin123,user1:pass456'
TOKEN_SECRET='your-key'
TOKEN_EXPIRE_HOURS=4
```
> 目前仅支持配置一个管理员账户和密码。尚未开发和实现完整的账户系统。
如果未配置账户凭证,Web 界面将以访客身份访问系统。因此,即使仅配置了 API 密钥,所有 API 仍然可以通过访客账户访问,这仍然不安全。因此,要保护 API,需要同时配置这两种认证方法。
## Azure OpenAI 后端配置
可以使用以下 Azure CLI 命令创建 Azure OpenAI API(您需要先从 [https://docs.microsoft.com/en-us/cli/azure/install-azure-cli](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli) 安装 Azure CLI):
```bash
# 根据需要更改资源组名称、位置和 OpenAI 资源名称
RESOURCE_GROUP_NAME=LightRAG
LOCATION=swedencentral
RESOURCE_NAME=LightRAG-OpenAI
az login
az group create --name $RESOURCE_GROUP_NAME --location $LOCATION
az cognitiveservices account create --name $RESOURCE_NAME --resource-group $RESOURCE_GROUP_NAME --kind OpenAI --sku S0 --location swedencentral
az cognitiveservices account deployment create --resource-group $RESOURCE_GROUP_NAME --model-format OpenAI --name $RESOURCE_NAME --deployment-name gpt-4o --model-name gpt-4o --model-version "2024-08-06" --sku-capacity 100 --sku-name "Standard"
az cognitiveservices account deployment create --resource-group $RESOURCE_GROUP_NAME --model-format OpenAI --name $RESOURCE_NAME --deployment-name text-embedding-3-large --model-name text-embedding-3-large --model-version "1" --sku-capacity 80 --sku-name "Standard"
az cognitiveservices account show --name $RESOURCE_NAME --resource-group $RESOURCE_GROUP_NAME --query "properties.endpoint"
az cognitiveservices account keys list --name $RESOURCE_NAME -g $RESOURCE_GROUP_NAME
```
最后一个命令的输出将提供 OpenAI API 的端点和密钥。您可以使用这些值在 `.env` 文件中设置环境变量。
```
# .env 中的 Azure OpenAI 配置
LLM_BINDING=azure_openai
LLM_BINDING_HOST=your-azure-endpoint
LLM_MODEL=your-model-deployment-name
LLM_BINDING_API_KEY=your-azure-api-key
### API Version可选,默认为最新版本
AZURE_OPENAI_API_VERSION=2024-08-01-preview
### 如果使用 Azure OpenAI 进行嵌入
EMBEDDING_BINDING=azure_openai
EMBEDDING_MODEL=your-embedding-deployment-name
```
## LightRAG 服务器详细配置
API 服务器可以通过三种方式配置(优先级从高到低):
* 命令行参数
* 环境变量或 .env 文件
* Config.ini(仅用于存储配置)
大多数配置都有默认设置,详细信息请查看示例文件:`.env.example`。数据存储配置也可以通过 config.ini 设置。为方便起见,提供了示例文件 `config.ini.example`
### 支持的 LLM 和嵌入后端
LightRAG 支持绑定到各种 LLM/嵌入后端:
* ollama
* openai (含openai 兼容)
* azure_openai
* lollms
* aws_bedrock
使用环境变量 `LLM_BINDING` 或 CLI 参数 `--llm-binding` 选择 LLM 后端类型。使用环境变量 `EMBEDDING_BINDING` 或 CLI 参数 `--embedding-binding` 选择嵌入后端类型。
LLM和Embedding配置例子请查看项目根目录的 env.example 文件。OpenAI和Ollama兼容LLM接口的支持的完整配置选型可以通过一下命令查看:
```
lightrag-server --llm-binding openai --help
lightrag-server --llm-binding ollama --help
lightrag-server --embedding-binding ollama --help
```
> 请使用openai兼容方式访问OpenRouter、vLLM或SLang部署的LLM。可以通过 `OPENAI_LLM_EXTRA_BODY` 环境变量给OpenRouter、vLLM或SGLang推理框架传递额外的参数,实现推理模式的关闭或者其它个性化控制。
设置 `max_tokens` 参数旨在**防止在实体关系提取阶段出现LLM 响应输出过长或无休止的循环输出的问题**。设置 `max_tokens` 参数的目的是在超时发生之前截断 LLM 输出,从而防止文档提取失败。这解决了某些包含大量实体和关系的文本块(例如表格或引文)可能导致 LLM 产生过长甚至无限循环输出的问题。此设置对于本地部署的小参数模型尤为重要。`max_tokens` 值可以通过以下公式计算:
```
# For vLLM/SGLang doployed models, or most of OpenAI compatible API provider
OPENAI_LLM_MAX_TOKENS=9000
# For Ollama Deployed Modeles
OLLAMA_LLM_NUM_PREDICT=9000
# For OpenAI o1-mini or newer modles
OPENAI_LLM_MAX_COMPLETION_TOKENS=9000
```
### 实体提取配置
* ENABLE_LLM_CACHE_FOR_EXTRACT:为实体提取启用 LLM 缓存(默认:true)
在测试环境中将 `ENABLE_LLM_CACHE_FOR_EXTRACT` 设置为 true 以减少 LLM 调用成本是很常见的做法。
### 支持的存储类型
LightRAG 使用 4 种类型的存储用于不同目的:
* KV_STORAGE:llm 响应缓存、文本块、文档信息
* VECTOR_STORAGE:实体向量、关系向量、块向量
* GRAPH_STORAGE:实体关系图
* DOC_STATUS_STORAGE:文档索引状态
每种存储类型都有多种存储实现方式。LightRAG Server默认的存储实现为内存数据库,数据通过文件持久化保存到WORKING_DIR目录。LightRAG还支持PostgreSQL、MongoDB、FAISS、Milvus、Qdrant、Neo4j、Memgraph和Redis等存储实现方式。详细的存储支持方式请参考根目录下的`README.md`文件中关于存储的相关内容。
您可以通过环境变量选择存储实现。例如,在首次启动 API 服务器之前,您可以将以下环境变量设置为特定的存储实现名称:
```
LIGHTRAG_KV_STORAGE=PGKVStorage
LIGHTRAG_VECTOR_STORAGE=PGVectorStorage
LIGHTRAG_GRAPH_STORAGE=PGGraphStorage
LIGHTRAG_DOC_STATUS_STORAGE=PGDocStatusStorage
```
在向 LightRAG 添加文档后,您不能更改存储实现选择。目前尚不支持从一个存储实现迁移到另一个存储实现。更多配置信息请阅读示例 `env.exampl`e文件。
### 在不同存储类型之间迁移LLM缓存
当LightRAG更换存储实现方式的时候,可以LLM缓存从就的存储迁移到新的存储。先以后在新的存储上重新上传文件时,将利用利用原有存储的LLM缓存大幅度加快文件处理的速度。LLM缓存迁移工具的使用方法请参考[README_MIGRATE_LLM_CACHE.md](../tools/README_MIGRATE_LLM_CACHE.md)
### LightRag API 服务器命令行选项
| 参数 | 默认值 | 描述 |
|-----------|---------|-------------|
| --host | 0.0.0.0 | 服务器主机 |
| --port | 9621 | 服务器端口 |
| --working-dir | ./rag_storage | RAG 存储的工作目录 |
| --input-dir | ./inputs | 包含输入文档的目录 |
| --max-async | 4 | 最大异步操作数 |
| --log-level | INFO | 日志级别(DEBUG、INFO、WARNING、ERROR、CRITICAL |
| --verbose | - | 详细调试输出(True、False |
| --key | None | 用于认证的 API 密钥。保护 lightrag 服务器免受未授权访问 |
| --ssl | False | 启用 HTTPS |
| --ssl-certfile | None | SSL 证书文件路径(如果启用 --ssl 则必需) |
| --ssl-keyfile | None | SSL 私钥文件路径(如果启用 --ssl 则必需) |
| --llm-binding | ollama | LLM 绑定类型(lollms、ollama、openai、openai-ollama、azure_openai、aws_bedrock |
| --embedding-binding | ollama | 嵌入绑定类型(lollms、ollama、openai、azure_openai、aws_bedrock |
### Reranking 配置
Reranking 查询召回的块可以显著提高检索质量,它通过基于优化的相关性评分模型对文档重新排序。LightRAG 目前支持以下 rerank 提供商:
- **Cohere / vLLM**:提供与 Cohere AI 的 `v2/rerank` 端点的完整 API 集成。由于 vLLM 提供了与 Cohere 兼容的 reranker API,因此也支持所有通过 vLLM 部署的 reranker 模型。
- **Jina AI**:提供与所有 Jina rerank 模型的完全实现兼容性。
- **阿里云**:具有旨在支持阿里云 rerank API 格式的自定义实现。
Rerank 提供商通过 `.env` 文件进行配置。以下是使用 vLLM 本地部署的 rerank 模型的示例配置:
```
RERANK_BINDING=cohere
RERANK_MODEL=BAAI/bge-reranker-v2-m3
RERANK_BINDING_HOST=http://localhost:8000/v1/rerank
RERANK_BINDING_API_KEY=your_rerank_api_key_here
```
以下是使用阿里云提供的 Reranker 服务的示例配置:
```
RERANK_BINDING=aliyun
RERANK_MODEL=gte-rerank-v2
RERANK_BINDING_HOST=https://dashscope.aliyuncs.com/api/v1/services/rerank/text-rerank/text-rerank
RERANK_BINDING_API_KEY=your_rerank_api_key_here
```
有关完整的 reranker 配置示例,请参阅 `env.example` 文件。
### 启用 Reranking
可以按查询启用或禁用 Reranking。
`/query``/query/stream` API 端点包含一个 `enable_rerank` 参数,默认设置为 `true`,用于控制当前查询是否激活 reranking。要将 `enable_rerank` 参数的默认值更改为 `false`,请设置以下环境变量:
```
RERANK_BY_DEFAULT=False
```
### .env 文件示例
```bash
### Server Configuration
# HOST=0.0.0.0
PORT=9621
WORKERS=2
### Settings for document indexing
ENABLE_LLM_CACHE_FOR_EXTRACT=true
SUMMARY_LANGUAGE=Chinese
MAX_PARALLEL_INSERT=2
### LLM Configuration (Use valid host. For local services installed with docker, you can use host.docker.internal)
TIMEOUT=150
MAX_ASYNC=4
LLM_BINDING=openai
LLM_MODEL=gpt-4o-mini
LLM_BINDING_HOST=https://api.openai.com/v1
LLM_BINDING_API_KEY=your-api-key
### Embedding Configuration (Use valid host. For local services installed with docker, you can use host.docker.internal)
EMBEDDING_MODEL=bge-m3:latest
EMBEDDING_DIM=1024
EMBEDDING_BINDING=ollama
EMBEDDING_BINDING_HOST=http://localhost:11434
### For JWT Auth
# AUTH_ACCOUNTS='admin:admin123,user1:pass456'
# TOKEN_SECRET=your-key-for-LightRAG-API-Server-xxx
# TOKEN_EXPIRE_HOURS=48
# LIGHTRAG_API_KEY=your-secure-api-key-here-123
# WHITELIST_PATHS=/api/*
# WHITELIST_PATHS=/health,/api/*
```
#### 使用 ollama 默认本地服务器作为 llm 和嵌入后端运行 Lightrag 服务器
Ollama 是 llm 和嵌入的默认后端,因此默认情况下您可以不带参数运行 lightrag-server,将使用默认值。确保已安装 ollama 并且正在运行,且默认模型已安装在 ollama 上。
```bash
# 使用 ollama 运行 lightragllm 使用 mistral-nemo:latest,嵌入使用 bge-m3:latest
lightrag-server
# 使用认证密钥
lightrag-server --key my-key
```
#### 使用 lollms 默认本地服务器作为 llm 和嵌入后端运行 Lightrag 服务器
```bash
# 使用 lollms 运行 lightragllm 使用 mistral-nemo:latest,嵌入使用 bge-m3:latest
# 在 .env 或 config.ini 中配置 LLM_BINDING=lollms 和 EMBEDDING_BINDING=lollms
lightrag-server
# 使用认证密钥
lightrag-server --key my-key
```
#### 使用 openai 服务器作为 llm 和嵌入后端运行 Lightrag 服务器
```bash
# 使用 openai 运行 lightragllm 使用 GPT-4o-mini,嵌入使用 text-embedding-3-small
# 在 .env 或 config.ini 中配置:
# LLM_BINDING=openai
# LLM_MODEL=GPT-4o-mini
# EMBEDDING_BINDING=openai
# EMBEDDING_MODEL=text-embedding-3-small
lightrag-server
# 使用认证密钥
lightrag-server --key my-key
```
#### 使用 azure openai 服务器作为 llm 和嵌入后端运行 Lightrag 服务器
```bash
# 使用 azure_openai 运行 lightrag
# 在 .env 或 config.ini 中配置:
# LLM_BINDING=azure_openai
# LLM_MODEL=your-model
# EMBEDDING_BINDING=azure_openai
# EMBEDDING_MODEL=your-embedding-model
lightrag-server
# 使用认证密钥
lightrag-server --key my-key
```
**重要说明:**
- 对于 LoLLMs:确保指定的模型已安装在您的 LoLLMs 实例中
- 对于 Ollama:确保指定的模型已安装在您的 Ollama 实例中
- 对于 OpenAI:确保您已设置 OPENAI_API_KEY 环境变量
- 对于 Azure OpenAI:按照先决条件部分所述构建和配置您的服务器
要获取任何服务器的帮助,使用 --help 标志:
```bash
lightrag-server --help
```
注意:如果您不需要 API 功能,可以使用以下命令安装不带 API 支持的基本包:
```bash
pip install lightrag-hku
```
## 文档和块处理逻辑说明
LightRAG 中的文档处理流程有些复杂,分为两个主要阶段:提取阶段(实体和关系提取)和合并阶段(实体和关系合并)。有两个关键参数控制流程并发性:并行处理的最大文件数(`MAX_PARALLEL_INSERT`)和最大并发 LLM 请求数(`MAX_ASYNC`)。工作流程描述如下:
1. `MAX_ASYNC` 限制系统中并发 LLM 请求的总数,包括查询、提取和合并的请求。LLM 请求具有不同的优先级:查询操作优先级最高,其次是合并,然后是提取。
2. `MAX_PARALLEL_INSERT` 控制提取阶段并行处理的文件数量。`MAX_PARALLEL_INSERT`建议设置为210之间,通常设置为 `MAX_ASYNC/3`,设置太大会导致合并阶段不同文档之间实体和关系重名的机会增大,降低合并阶段的效率。
3. 在单个文件中,来自不同文本块的实体和关系提取是并发处理的,并发度由 `MAX_ASYNC` 设置。只有在处理完 `MAX_ASYNC` 个文本块后,系统才会继续处理同一文件中的下一批文本块。
4. 当一个文件完成实体和关系提后,将进入实体和关系合并阶段。这一阶段也会并发处理多个实体和关系,其并发度同样是由 `MAX_ASYNC` 控制。
5. 合并阶段的 LLM 请求的优先级别高于提取阶段,目的是让进入合并阶段的文件尽快完成处理,并让处理结果尽快更新到向量数据库中。
6. 为防止竞争条件,合并阶段会避免并发处理同一个实体或关系,当多个文件中都涉及同一个实体或关系需要合并的时候他们会串行执行。
7. 每个文件在流程中被视为一个原子处理单元。只有当其所有文本块都完成提取和合并后,文件才会被标记为成功处理。如果在处理过程中发生任何错误,整个文件将被标记为失败,并且必须重新处理。
8. 当由于错误而重新处理文件时,由于 LLM 缓存,先前处理的文本块可以快速跳过。尽管 LLM 缓存在合并阶段也会被利用,但合并顺序的不一致可能会限制其在此阶段的有效性。
9. 如果在提取过程中发生错误,系统不会保留任何中间结果。如果在合并过程中发生错误,已合并的实体和关系可能会被保留;当重新处理同一文件时,重新提取的实体和关系将与现有实体和关系合并,而不会影响查询结果。
10. 在合并阶段结束时,所有实体和关系数据都会在向量数据库中更新。如果此时发生错误,某些更新可能会被保留。但是,下一次处理尝试将覆盖先前结果,确保成功重新处理的文件不会影响未来查询结果的完整性。
大型文件应分割成较小的片段以启用增量处理。可以通过在 Web UI 上按“扫描”按钮来启动失败文件的重新处理。
## API 端点
所有服务器(LoLLMs、Ollama、OpenAI 和 Azure OpenAI)都为 RAG 功能提供相同的 REST API 端点。当 API 服务器运行时,访问:
- Swagger UIhttp://localhost:9621/docs
- ReDochttp://localhost:9621/redoc
您可以使用提供的 curl 命令或通过 Swagger UI 界面测试 API 端点。确保:
1. 启动适当的后端服务(LoLLMs、Ollama 或 OpenAI
2. 启动 RAG 服务器
3. 使用文档管理端点上传一些文档
4. 使用查询端点查询系统
5. 如果在输入目录中放入新文件,触发文档扫描
## 异步文档索引与进度跟踪
LightRAG采用异步文档索引机制,便于前端监控和查询文档处理进度。用户通过指定端点上传文件或插入文本时,系统将返回唯一的跟踪ID,以便实时监控处理进度。
**支持生成跟踪ID的API端点:**
* `/documents/upload`
* `/documents/text`
* `/documents/texts`
**文档处理状态查询端点:**
* `/track_status/{track_id}`
该端点提供全面的状态信息,包括:
* 文档处理状态(待处理/处理中/已处理/失败)
* 内容摘要和元数据
* 处理失败时的错误信息
* 创建和更新时间戳

Some files were not shown because too many files have changed in this diff Show More