Post

HKUDS018: MiniRAG 作为 Lightweight Graph RAG 与 On-Device Knowledge Layer

HKUDS018: MiniRAG 作为 Lightweight Graph RAG 与 On-Device Knowledge Layer

这是 PENGYI_HKUDS_STUDYMAP 的第十九篇。

1
HKUDS018 -> MiniRAG

上一篇 HKUDS017 看的是 AnyTool

1
AnyTool = Universal Tool-Use Layer + Capability Routing Layer

AnyTool 解决的是 agent 怎么找到工具、调用工具、记录工具质量。

这一篇 MiniRAG 回到知识层:

1
MiniRAG = Lightweight Graph RAG + On-Device Knowledge Layer

如果说 LightRAG 是完整的 graph-based RAG 框架,那么 MiniRAG 更像是它的轻量化、端侧化、小模型友好版本。

1
2
3
LightRAG 关注强 graph RAG 能力。
RAG-Anything 关注多模态复杂文档入口。
MiniRAG 关注小模型、低存储、低复杂度、端侧 RAG。

这对我们的 Pengyi Research OS 很关键。因为不是所有知识任务都应该调用最重的模型、最复杂的图数据库、最长的上下文。

真正可持续的系统需要分层:

1
2
3
4
heavy research memory
lightweight local memory
task-specific temporary memory
agent working memory

MiniRAG 就是 lightweight local memory 的很强参考。

Local Snapshot

这次阅读的是本地 HKUDS 工作区里的 MiniRAG

ItemValue
repoMiniRAG
remotehttps://github.com/HKUDS/MiniRAG.git
branchmain
local heade204d23
full commite204d239421f45004852953679927fdf6733f236
latest local commit date2025-10-16 15:43:16 +0800
latest local commitUpdate README.md
statusclean, synced with origin/main after fetch
local tagsnone
licenseMIT
package nameminirag-hku
package version0.0.2
API version1.0.3
Python requirement>=3.9
tracked files by git ls-files77
Python files45
KG storage impl files14
LLM provider files12
default KV storageJsonKVStorage
default vector storageNanoVectorDBStorage
default graph storageNetworkXStorage
default doc status storageJsonDocStatusStorage
syntax checkpy -m compileall -q minirag main.py reproduce tests passed
metadata checkpyproject.toml parsed successfully with tomllib
import smoke testblocked in this environment because dependencies are not installed: missing python-dotenv

一句话先行:

1
2
MiniRAG 把 RAG 从“大模型理解长文本”转成“小模型借助异构图和轻量拓扑检索完成知识发现”。
它用 text chunks + named entities + relationships 构成异构图,再用 answer type、query entities、2-hop graph neighborhood、edge voting 和 chunk scoring 找到少量高价值上下文,减少对大模型语义能力和长上下文的依赖。

它解决什么问题

MiniRAG 的目标场景不是云端大模型 RAG,而是:

1
2
3
4
5
Small Language Models
on-device RAG
resource-constrained deployment
low storage
simple retrieval

README 里强调一个现实问题:

1
现有 RAG 框架放到 SLM 上会明显退化。

原因是小模型有三个短板:

SLM Limitation对 RAG 的影响
semantic understanding weaker直接向量召回容易漏掉复杂关系
text processing weaker长 context 容易压垮生成质量
instruction following weaker复杂 graph context 不一定会被正确利用

MiniRAG 的回答是:

1
2
不要把理解压力全部放在小模型上。
把更多结构信息提前放进 index 和 retrieval。

所以它提出两点:

InnovationMeaning
semantic-aware heterogeneous graph indexing把 text chunks 和 named entities 放进统一结构
lightweight topology-enhanced retrieval用图拓扑做知识发现,减少对高级语义能力的依赖

这就是 MiniRAG 的核心:

1
2
less semantic burden on the model
more structure in the retrieval layer

和 LightRAG 的关系

MiniRAG 明确基于 LightRAGnano-graphrag

但它不是简单复制 LightRAG,而是做了一个更轻的版本:

RepoSystem Position
LightRAGgraph-based RAG baseline,完整知识图谱检索框架
RAG-Anythingmultimodal document ingestion layer
MiniRAGSLM-friendly lightweight graph RAG layer
VideoRAGlong-context video understanding RAG

对我们来说可以这样理解:

1
2
3
LightRAG = Research OS 的主知识图谱层
RAG-Anything = 复杂文档入口层
MiniRAG = 本地轻量知识层 / 小模型知识层

MiniRAG 的意义不是替代 LightRAG,而是补一个更轻、更适合端侧或低成本场景的层。

Benchmark 结果

README 给出的核心实验表里,MiniRAG 在小模型上非常突出。

LiHua-World 上:

ModelNaiveRAG accLightRAG accMiniRAG acc
Phi-3.5-mini-instruct41.22%39.81%53.29%
GLM-Edge-1.5B-Chat42.79%35.74%52.51%
Qwen2.5-3B-Instruct43.73%39.18%48.75%
MiniCPM3-4B43.42%35.42%51.25%
gpt-4o-mini46.55%56.90%54.08%

MultiHop-RAG 上:

ModelNaiveRAG accLightRAG accMiniRAG acc
Phi-3.5-mini-instruct42.72%27.03%49.96%
GLM-Edge-1.5B-Chat44.44%/51.41%
Qwen2.5-3B-Instruct39.48%21.91%48.55%
MiniCPM3-4B39.24%19.48%47.77%
gpt-4o-mini53.60%64.91%68.43%

这说明一个重要结论:

1
MiniRAG 对小模型不是“缩水版”,而是专门为小模型重构 retrieval burden 的版本。

它不追求给模型塞最多上下文,而是尝试把上下文压成更结构化、更少、更有用。

LiHua-World

MiniRAG 还带了一个 benchmark:

1
dataset/LiHua-World

本地文件包括:

FileSize / Role
data/LiHuaWorld.zip548,314 bytes,一年聊天记录
qa/query_set.csv84,305 bytes,问题、标准答案、证据
qa/query_set.json154,099 bytes,JSON 格式问题集

LiHua-World 是一个本地 RAG 场景数据集:

1
one year of chat records from a virtual user named LiHua

问题类型:

TypeMeaning
Single-hop单跳事实问题
Multi-hop多时间点、多证据问题
Summary汇总型问题

一个样例问题是:

1
Did Adam Smith send a message to Li Hua about the upcoming building maintenance schedule before the administrators announced a temporary change in the construction schedule due to weather conditions?

这类问题对 RAG 很真实。因为它不是单纯语义搜索,而是:

1
2
3
4
找到两个事件
比较时间顺序
确认人物和动作
给出 yes/no

所以 MiniRAG 的 graph topology 有意义。它能帮助系统在碎片化聊天记录里连接人物、事件、时间和证据。

代码结构

核心目录:

1
2
3
4
5
6
7
8
9
minirag/
├── minirag.py
├── operate.py
├── base.py
├── prompt.py
├── utils.py
├── kg/
├── llm/
└── api/

主要文件:

FileRole
minirag/minirag.pyMiniRAG 主类,负责初始化 storage、insert、query
minirag/operate.pychunking、entity extraction、query modes、MiniRAG retrieval
minirag/base.pyQueryParam、storage abstract classes、doc status
minirag/prompt.pyentity extraction、keyword extraction、RAG response prompts
minirag/utils.pyhashing、tiktoken、context combine、path scoring、metrics
minirag/kg/*_impl.pyJSON、NetworkX、NanoVectorDB、Neo4j、Postgres、Oracle、Mongo、Redis、Weaviate 等存储
minirag/llm/*.pyOpenAI、Azure、Ollama、HF、Bedrock、Jina、Zhipu 等 LLM/embedding wrappers
minirag/api/minirag_server.pyFastAPI server、document endpoints、Ollama-compatible API
reproduce/Step_0_index.py索引 LiHua-World
reproduce/Step_1_QA.py跑 QA 实验

MiniRAG 主类

入口是:

1
from minirag import MiniRAG, QueryParam

MiniRAG 默认存储:

1
2
3
4
kv_storage = "JsonKVStorage"
vector_storage = "NanoVectorDBStorage"
graph_storage = "NetworkXStorage"
doc_status_storage = "JsonDocStatusStorage"

它初始化了几个核心 storage:

StorageMeaning
full_docs原始文档
text_chunkschunk KV store
chunk_entity_relation_graphentity-relation graph
entities_vdbentity + description vector DB
entity_name_vdbentity name vector DB
relationships_vdbrelationship vector DB
chunks_vdbchunk vector DB
llm_response_cacheLLM response cache
doc_statuspending / processing / processed / failed

这说明 MiniRAG 的知识层其实不是单一向量库,而是:

1
KV storage + vector storage + graph storage + doc status + LLM cache

只是默认实现都很轻:

1
2
3
JSON files
NetworkX graphml
NanoVectorDB json

这和它的 lightweight 目标一致。

Insert Pipeline

rag.insert() 最终调用 ainsert()

流程是:

1
2
3
4
5
6
7
8
9
10
input documents
-> apipeline_enqueue_documents
-> apipeline_process_enqueue_documents
-> chunking
-> upsert chunks/full_docs/text_chunks
-> mark doc as processed
-> extract_entities
-> upsert graph nodes/edges
-> upsert entity/name/relationship vectors
-> index_done callbacks

apipeline_enqueue_documents() 做:

StepMeaning
validate ids如果用户传 ids,检查长度和唯一性
clean and hash docs默认用 md5 生成 doc- id
deduplicate content文档内容去重
create status初始状态 PENDING
filter processed docs已处理文档不重复进队
upsert doc_status写入状态存储

apipeline_process_enqueue_documents() 做:

1
2
3
4
5
6
7
PENDING / FAILED / PROCESSING docs
-> batches by max_parallel_insert
-> chunking_by_token_size
-> upsert chunks_vdb
-> upsert full_docs
-> upsert text_chunks
-> mark PROCESSED

之后 extract_entities() 才会从 processed docs 里重新取 chunk,抽取 entity / relationship。

这就是 MiniRAG 的 indexing 核心:

1
document -> chunks -> entities -> relationships -> graph + vector stores

Entity Extraction

extract_entities()operate.py

它用 LLM 从每个 chunk 抽:

1
2
3
entity
relationship
content_keywords

默认 entity types:

1
["organization", "person", "location", "event"]

每个 entity 包含:

1
2
3
4
entity_name
entity_type
entity_description
source_id

每条 relationship 包含:

1
2
3
4
5
6
src_id
tgt_id
description
keywords
weight
source_id

然后它会:

1
2
3
4
5
6
7
merge duplicate nodes
merge duplicate edges
upsert graph nodes
upsert graph edges
upsert entity vectors
upsert entity name vectors
upsert relationship vectors

这里有一个很 MiniRAG 的点:

1
entity_name_vdb

它不是用实体描述做 retrieval,而是单独存实体名字,用来把 query 里的实体 phrase 对齐到图里的 entity nodes。

这对小模型很重要。小模型可能不擅长复杂语义判断,但名字匹配、局部拓扑、类型池可以降低难度。

QueryParam

QueryParam 支持三个 mode:

1
mode: Literal["light", "naive", "mini"] = "mini"

主要参数:

ParamDefaultMeaning
modemini查询模式
top_kenv TOP_K or 60top-k retrieval
max_token_for_text_unit4000chunk context token budget
max_token_for_global_context4000relationship context budget
max_token_for_local_context4000entity context budget
max_token_for_node_context500mini mode node context budget
only_need_contextfalse只返回检索上下文
only_need_promptfalse保留字段,但当前主路径里没看到核心使用
conversation_history[]conversation history support
history_turns3历史轮数

max_token_for_node_context = 500 非常关键。

代码注释写得很直接:

1
For Mini, if too long, SLM may be fail to generate any response

这就是 MiniRAG 的小模型约束意识:

1
2
context 不是越多越好。
对 SLM 来说,少而准更重要。

三种 Query Mode

MiniRAG 有三个查询模式:

ModeFunctionMeaning
naivenaive_query直接 chunk vector retrieval
lighthybrid_queryLightRAG 风格 high/low keyword graph retrieval
miniminirag_queryMiniRAG 的 lightweight topology retrieval

naive

naive_query() 很简单:

1
2
3
4
5
query chunks_vdb
-> get chunk ids
-> load text chunks
-> truncate by token budget
-> RAG response

它是 baseline。

light

hybrid_query() 更接近 LightRAG:

1
2
3
4
5
6
7
LLM extracts high-level and low-level keywords
-> low-level keywords retrieve entities
-> high-level keywords retrieve relationships
-> build local context
-> build global context
-> combine contexts
-> RAG response

也就是:

1
local entity context + global relationship context

mini

minirag_query() 是重点。

它不是抽 high/low keywords,而是抽:

1
2
answer_type_keywords
entities_from_query

prompt 叫:

1
minirag_query2kwd

它会先从图里取 type pool:

1
TYPE_POOL, TYPE_POOL_w_CASE = await knowledge_graph_inst.get_types()

然后让模型在这个 type pool 里选择答案类型,并抽 query 里的具体实体。

这一步非常关键:

1
query -> answer type + query entities

这比单纯关键词更适合小模型。因为很多问题问的是“答案应该属于哪类东西”。

比如:

1
2
3
4
5
6
7
8
When was ...
-> DATE AND TIME

Where is ...
-> LOCATION

Who ...
-> PERSON

MiniRAG 把这个 answer type 当成 retrieval signal。

Mini Retrieval Path

_build_mini_query_context() 是 MiniRAG 的核心。

简化后的流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
entities_from_query
-> query entity_name_vdb
-> candidate entity nodes
-> get 2-hop neighbors from graph
-> get nodes matching answer type
-> score paths toward answer-type candidates
-> query relationship vectors with original query
-> keep edges touching important entities
-> edge voting over paths
-> path2chunk
-> direct chunk vector retrieval
-> merge/scored chunk ids
-> build compact context

这里有几个关键技巧。

1. 用 entity_name_vdb 做实体对齐

1
query entity phrase -> graph entity name

这一步避免小模型直接在长 chunk 里理解所有细节。

2. 用 2-hop graph neighborhood 找候选路径

代码里:

1
get_neighbors_within_k_hops(key, 2)

这对 multi-hop question 很重要。很多答案不是一个 chunk 里直接出现,而是几个实体/事件之间的路径。

3. 用 answer type 限制目标候选

1
get_node_from_types(type_keywords)

比如问题问时间,就优先找时间类节点;问地点,就优先找地点类节点。

这让 retrieval 更像:

1
从 query entities 出发,沿图找可能回答类型的节点。

4. 用 edge voting 修正路径

edge_vote_path()path2chunk() 会把 relationship retrieval 的结果投票到路径和 chunk 上。

也就是说:

1
graph path signal + relationship vector signal + chunk vector signal

最后一起决定哪些 chunk 进上下文。

5. 输出非常短的 context

mini context 只包含:

1
2
Entities
Sources

不像 LightRAG hybrid 那样输出 entities + relationships + sources 三段完整表。

这是为 SLM 降低负担。

Storage Layer

MiniRAG 默认是轻量本地存储:

1
2
3
4
JsonKVStorage
JsonDocStatusStorage
NetworkXStorage
NanoVectorDBStorage

STORAGES 也映射了更多后端:

1
2
3
4
5
6
7
8
9
10
Neo4J
Oracle
Milvus
Mongo
Redis
Chroma
Postgres
AGE
Gremlin
Weaviate

README 新闻里说已经支持 10+ heterogeneous graph databases。

从 Research OS 角度看,这个抽象很有价值:

1
2
3
4
开发期:JSON + NetworkX + NanoVectorDB
本地生产:Chroma / Redis / Mongo
团队部署:Postgres / Neo4j / Weaviate
企业场景:Oracle / AGE / Gremlin

也就是说,MiniRAG 的轻量不是只能本地玩具化,而是可以从简单存储逐步迁移到更重的后端。

API Server

MiniRAG 提供 FastAPI server:

1
minirag/api/minirag_server.py

安装后 entry point:

1
minirag-server

支持的主要 endpoint:

EndpointMeaning
POST /queryRAG query
POST /query/streamstreaming RAG query
POST /documents/text插入文本
POST /documents/file上传单文件
POST /documents/batch批量上传文件
POST /documents/scan扫描 input dir
DELETE /documents清空文档
GET /documents查看 indexed files
GET /health查看服务状态和配置
GET /api/versionOllama-compatible endpoint
GET /api/tagsOllama-compatible model list
POST /api/chatOllama-compatible chat
POST /api/generateOllama-compatible generate

它支持多种 LLM / embedding binding:

1
2
3
4
ollama
lollms
openai
azure_openai

这个 API 层的意义是:

1
MiniRAG 不只是 Python library,也可以作为本地 RAG 服务挂到 Open WebUI / Ollama-compatible frontend。

对我们未来的 Research OS 来说,这意味着 MiniRAG 可以作为一个本地 knowledge microservice。

Docker

仓库有:

1
2
Dockerfile
docker-compose.yml

Dockerfile 做两阶段构建:

1
2
3
4
builder installs requirements
final copies minirag and setup.py
pip install .
entrypoint python -m minirag.api.minirag_server

默认暴露端口:

1
9721

docker-compose.yml 里仍然有很多 LightRAG 命名遗留:

1
2
service: lightrag
network: lightrag_net

这也是后面可以提 PR 的小点。

Reproduce Flow

复现实验主要在:

1
2
reproduce/Step_0_index.py
reproduce/Step_1_QA.py

Step_0_index.py

1
2
3
4
5
load model config
load embedding model
initialize MiniRAG
find txt files under dataset path
rag.insert(file content)

Step_1_QA.py

1
2
3
4
5
load query_set.csv
resume from existing output csv
for each question:
    rag.query(question, QueryParam(mode="mini"))
    write question / gold answer / minirag answer

默认小模型选项:

FlagModel
PHImicrosoft/Phi-3.5-mini-instruct
GLMTHUDM/glm-edge-1.5b-chat
MiniCPMopenbmb/MiniCPM3-4B
qwenQwen/Qwen2.5-3B-Instruct

embedding model:

1
sentence-transformers/all-MiniLM-L6-v2

这进一步说明 MiniRAG 的目标就是小模型可用。

对 Pengyi Research OS 的启发

MiniRAG 对我们的 Research OS 有一个很具体的启发:

1
2
不是所有知识记忆都要进大而全的 RAG。
我们需要一个轻量本地知识层。

它适合存:

Knowledge TypeWhy MiniRAG Fits
personal notes规模不大,但经常查询
meeting records人物、时间、事件关系强
repo study notesproject、component、concept 之间有图关系
RA/PhD application material人、组、topic、deadline、材料之间有关联
daily research logs多时间点、多证据、多跳问题
paper reading snippetsconcept/entity/claim 之间有关系

Research OS 可以这样分层:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
LightRAG
-> long-term source-grounded research memory

RAG-Anything
-> multimodal document ingestion

MiniRAG
-> local lightweight memory for notes/logs/chat/repo summaries

AnyTool
-> capability routing and tool execution

UpSkill
-> skill distillation from traces

这形成一条很清楚的链:

1
2
3
4
5
documents -> RAG-Anything
knowledge graph -> LightRAG
lightweight local memory -> MiniRAG
actions -> AnyTool
skills -> UpSkill

对 Pengyi Quant Research OS 的启发

Quant Research OS 也需要轻量知识层。

很多量化知识不是大文档,而是碎片:

1
2
3
4
5
6
7
某个因子为什么有效
某个 backtest 失败在哪里
某个数据字段的含义
某个策略的参数变更
某个 PM 的 review comment
某次 meeting 的结论
某个市场 regime 的观察

这类信息天然像 LiHua-World:

1
2
3
4
时间序列化
碎片化
多人物/多事件/多证据
需要 multi-hop reasoning

所以 MiniRAG 可以成为:

1
Quant R&D Memory Lite

一个可能的设计:

Quant Memory ObjectMiniRAG Entity / Relation
factorentity
datasetentity
market regimeentity / event
backtest runevent
metricentity
PM feedbackevent / interaction
bias diagnosisrelationship
code artifactsource chunk

典型问题:

1
2
3
4
Which factors failed after the liquidity filter changed?
Which datasets were used before the last turnover anomaly?
Did the signal decay issue appear before or after the universe change?
Which PM feedback mentioned look-ahead bias?

这类问题很适合 graph + lightweight topology retrieval。

和前面 HKUDS 项目的组合

MiniRAG 可以接到我们已经学过的几个 repo:

Repo和 MiniRAG 的关系
LightRAG主图谱 RAG 层,MiniRAG 是轻量版
RAG-Anything负责把复杂文档解析成可进入 RAG 的内容
Vibe-Trading可以把策略研究日志写入 MiniRAG
AI-Researcher研究过程产物可以进入 MiniRAG
DeepResearch-Evalreport factuality / quality 结果可以进入 MiniRAG
AnyTool可以把 MiniRAG 暴露成一个 knowledge tool
UpSkill可以把 MiniRAG 查询失败的 trace 变成 better retrieval skill

一个很清楚的 Research OS flow:

1
2
3
4
5
6
7
8
read paper
-> RAG-Anything parse
-> LightRAG / MiniRAG index
-> AI-Researcher generates idea
-> AnyTool executes experiments
-> DeepResearch-Eval judges report
-> UpSkill distills workflow skill
-> MiniRAG stores distilled notes and trace summaries

MiniRAG 是这里的轻量长期记忆层。

可以提的 PR

这次读代码发现几个清楚的 PR opportunity。

PR 1: Fix README package name inconsistency

README news 里写:

1
pip install minirag-hku

但安装段写:

1
pip install lightrag-hku

API README 里也多处写:

1
2
3
lightrag-hku
lightrag-server
lightrag-server.service

这会让新用户困惑。可以统一成 minirag-hku / minirag-server,必要时加一句说明:

1
MiniRAG is based on LightRAG, but the package published by this repo is minirag-hku.

PR 2: Fix SearchMode.hybrid bug

API server 里:

1
2
3
4
class SearchMode(str, Enum):
    light = "light"
    naive = "naive"
    mini = "mini"

parse_query_mode() 默认返回:

1
return query, SearchMode.hybrid

SearchMode.hybrid 不存在。

这会影响 Ollama-compatible chat/generate 里没有 /light/naive/mini prefix 的默认路径。

合理修复:

1
default to SearchMode.mini

或者显式加 hybrid 并映射到 light,但当前 QueryParam 只支持 light/naive/mini,所以直接默认 mini 更一致。

PR 3: Fix API README query examples

API README 里示例写:

1
{"query": "Your question here", "mode": "hybrid"}

但当前 SearchMode 不支持 hybrid

应该改成:

1
{"query": "Your question here", "mode": "mini"}

或者说明可选值:

1
mini / light / naive

PR 4: Remove or implement missing graph endpoints

API 里有:

1
2
3
4
5
6
7
@app.get("/graph/label/list")
async def get_graph_labels():
    return await rag.get_graph_labels()

@app.get("/graphs")
async def get_graphs(label: str):
    return await rag.get_graps(nodel_label=label, max_depth=100)

MiniRAG 主类里没有看到 get_graph_labels()get_graps()

这类 endpoint 要么补实现,要么暂时移除/标记 unsupported。

PR 5: Check missing tidb_impl.py

STORAGES 里映射了:

1
2
3
"TiDBKVStorage": ".kg.tidb_impl"
"TiDBVectorDBStorage": ".kg.tidb_impl"
"TiDBGraphStorage": ".kg.tidb_impl"

但本地 minirag/kg 里没有 tidb_impl.py

如果 README 宣称支持 TiDB,这里需要补文件;如果暂时不支持,就应该从 mapping / docs 里移除,避免运行时 import error。

PR 6: Rename Docker Compose LightRAG leftovers

docker-compose.yml 里:

1
2
service: lightrag
network: lightrag_net

这不影响核心功能,但影响项目一致性。可以做一个很小的 docs/devops cleanup PR。

我们怎么吸收

MiniRAG 对我们的直接行动建议:

1
为 Pengyi Research OS 做一个 lightweight memory tier。

不要一上来把所有东西都塞进最重的 graph RAG。

可以分三层:

LayerTool
raw filesMarkdown / PDF / HTML / CSV
heavy knowledge graphLightRAG
lightweight local memoryMiniRAG

MiniRAG 可以优先吃:

1
2
3
4
5
6
我们的 repo study notes
HKUDS / LLMQuant study map
RA/PhD contact notes
quant research log
factor experiment notes
weekly planning notes

它的查询重点不是“长文档问答”,而是:

1
在我们的长期碎片记录里找到人、项目、时间、事件、结论之间的关系。

这正好适合我们现在的工作方式。

Next

下一篇继续 HKUDS 主线:

1
HKUDS019 -> Paper2Slides

MiniRAG 是轻量知识层,Paper2Slides 会进入科研表达和产出层:

1
2
MiniRAG helps remember.
Paper2Slides helps communicate.

Research OS 最后一定要把知识和实验变成可展示的 artifact。

This post is licensed under CC BY 4.0 by the author.