请先登录

保存至FAISS

1278 字

我有这样一个诉求,请给我出出主意。我有大量的MD文件,涉及到区块链和AI领域的,通过x网站筛选的每天相关的这两个领域的代表性人物以及代表性项目,他们的最新动态。目前大约积累了数千份这样的Markdown文档,这些文档都是最宝贵的原始的研究素材。现在我想发挥这些原始素材它的作用,提炼出有价值的知识信息。我想请你对这个事给出你的解决方案。怎么样去让这数千个文档,它的一些信息被研究被读取出来。同时这个方案又是比较经济实惠的,不要消耗太多的TOKEN。适合于这样一个个人的用户去实践
方案概览
把 “数千份 Markdown 原始素材 → 可搜索、可聚合、可持续更新的知识库” 的转化流程,全部在本地或低成本云上完成,几乎不需要付费 LLM 的 Token (只在关键的聚合/摘要阶段少量调用),并且易于个人用户自行维护。下面给出 技术栈、工作流、实现步骤、成本估算,以及 后续迭代建议。
1️⃣ 核心技术选型(低成本、开源)
功能
推荐工具(开源)
说明
文本读取 & 预处理
Python + markdown、pathlib
批量遍历目录、去除 Markdown 语法、抽取标题、时间、标签
向量化(Embedding)
句子向量模型:sentence‑transformers(如 all‑mpnet‑base‑v2)或国产模型 bge‑large‑zh(中文)
本地推理,GPU/CPU 都可运行,生成 384‑768 维向量;一次性离线算完后无需再消耗 Token
向量检索
FAISS(本地)或 Milvus (开源社区版)
支持大规模向量(数千文档轻松 ≤ 1 M 向量),提供相似度搜索、过滤
元数据存储
SQLite(轻量)或 TinyDB
保存文档路径、标题、发布时间、关键词、标签、向量 ID
增量摘要 / 关键信息抽取
小型本地 LLM(如 GLM‑4‑Chat‑int4、Qwen‑1.5‑7B‑Chat)或 OpenAI GPT‑3.5‑turbo(只在需要时调用)
用于对搜索结果进行“二次加工”,输出摘要、要点、时间线
1/2

全部或大部分组件都可以在一台普通笔记本(CPU)或低配云服务器(2 CPU + 4 GB RAM)上跑,无需租用昂贵的专用向量数据库或高价 LLM API。
2️⃣ 工作流细节
2.1 数据准备(一次性)
统一目录结构
批量读取
抽取元信息(标题、发布时间、人物/项目关键词)
使用正则或 spaCy/jieba 提取人名、项目名、日期。
将结果写入 SQLite 表 docs(id, path, title, date, tags, …)。
2.2 向量化(离线)
from sentence_transformers import SentenceTransformer
model = SentenceTransformer('bge-large-zh')
vectors = model.encode(list_of_texts, batch_size=64, show_progress_bar=True)

保存至FAISS

import faiss, numpy as np
dim = vectors.shape[1]
index = faiss.IndexFlatIP(dim) # 内积相似度
index.add(vectors.astype('float32'))
faiss.write_index(index, 'knowledge.faiss')
一次完成,之后只要有新增文档,重复 embed → add 即可(FAISS 支持增量)。
2.3 检索 + 二次摘要
检索(用户输入查询):
q_vec = model.encode([user_query])
D, I = index.search(q_vec.astype('float32'), k=10) # 返回前10条最相似文档

二次摘要(可选,仅对检索到的文档做一次本地 LLM 归纳):

只取前 3 条文档,拼接成 1500 字左右的上下文

context = "\n---\n".join([docs[i].text for i in I[:3]])
prompt = f"""请根据以下内容提取要点、最新进展、涉及的人物与项目,输出 3‑5 行简洁摘要(中文)。\n\n{context}"""
summary = local_llm_chat(prompt) # 只消耗一条短对话的 token

这样 每次查询只调用一次 LLM(几百 token),而核心相似度搜索完全不花 Token。
2.4 前端展示
Obsidian 插件:在 Obsidian 中添加 “知识库搜索” 命令,弹出输入框,显示 Markdown 摘要和原文链接。
Streamlit / Gradio:一行代码搞定网页版 UI,支持搜索、过滤标签、时间轴等。
import streamlit as st
query = st.text_input("查询:")
if query:
results = search(query) # 上面实现的检索+摘要
for r in results:
st.markdown(f"### {r['title']}")
st.markdown(r['summary'])
st.caption(f"路径:{r['path']}")
2.5 增量更新(每日/每周)
把 x 网站(RSS、爬虫或手动数据)中新增的 MD 文件放入对应文件夹。
定时脚本读取新文件 → 抽取元信息 → 编码 → index.add() → 更新 SQLite。
只需 几分钟(几千文档≈10 GB)即可完成。
3️⃣ 成本估算(个人用户)
项目
费用(人民币)
说明
总体
≈0 ~ 100 元/月(不含硬件)
完全使用本地模型时几乎为零
2/2

关键点:向量检索 + 本地 embedding 完全不消耗 Token;只有在需要高质量自然语言摘要时才调用一次性 LLM,成本极低。
4️⃣ 实施步骤(分阶段)
阶段 1:准备与初始索引(1 天)
安装 Python 环境(3.10+),pip install sentence-transformers faiss-cpu markdown sqlite3.
编写脚本 build_index.py(读取、抽取、向量化、保存)。
运行一次,生成 knowledge.faiss 与 docs.db。
阶段 2:交互原型(2 天)
选一个轻量前端(Obsidian 插件或 Streamlit)。
实现 搜索 → 本地向量检索 → 本地 LLM 摘要。
测试 10‑20 次查询,观察响应时间(≈1 秒)与摘要质量。
阶段 3:增量更新自动化(1 周)
编写 watch_new.py,监听目录或读取 RSS/爬虫结果。
每日/每周通过 cron 调用:python watch_new.py.
验证新增文档能即时被检索。
阶段 4:优化 & 费用监控(持续)
若摘要质量不满意,可 微调 小模型或换更强的本地模型(如 glm-4-9b-chat)。
开启 缓存:对同一查询的向量搜索结果缓存,避免重复调用 LLM。
使用系统监控(htop、nvidia-smi)确保资源不超标。
5️⃣ 后续扩展方向(可选)
知识图谱:把抽取的「人物‑项目‑时间」关系存入 Neo4j,支持图查询。
自动标签:利用 Zero‑Shot 分类(本地模型)为每篇文档生成统一标签(如「Layer‑2」「大语言模型」),便于筛选。
多语言:如果文档中混有英文,可使用双语模型 sentence‑transformers/paraphrase-multilingual-MiniLM,保证跨语言检索。
移动端:把 Streamlit 部署为 PWA,手机随时搜索。
6️⃣ 小结(要点)
核心思路:一次性离线向量化所有 MD,使用本地向量检索实现低成本搜索;只在需要聚合/摘要时调用一次性 LLM,极大压缩 Token 消耗。
技术栈:Python + Sentence‑Transformers + FAISS + SQLite + 本地 LLM(或小额 OpenAI)+ Streamlit/Obsidian 前端。
成本:硬件一次性投入,日常运行几乎为零;若偶尔使用云 LLM,费用也在几元/月量级。
实现难度:中等(主要是脚本编写),适合有一定 Python 基础的个人用户。
如果你准备好开始,可以先从 build_index.py 示例脚本开始,我可以直接把脚本模板发给你,后续再根据实际目录结构微调。只要一步步跟进,几千篇 Markdown 的价值就能在几分钟内被检索、聚合、复用。祝顺利!

我昨日已按你介绍的方法,搭建了完整的“原始素材 → 向量索引 → 快速检索 → 可选摘要” 工作流,随时能够从数千份 Markdown 中根据关键字找到内容,但存在问题:1、每次查询到的内容只有六条 2、许多内容查询不到 3、这种方法是能查到标准为关键字,还有内容中有关键字的结果吗?4、怎样提高查询的精准度?还有能对文档中内容进行总结分析:比如,找到最近一个月有相关内容的资料等; 请给出这套工具的进阶使用方法?

下面给出 进阶使用指南,帮助你提升检索条数、召回率和准确度,并实现基于时间的摘要、分析等功能。
1️⃣ 常见瓶颈的根源
症状
可能原因
每次只返回 6 条
top_k 参数默认是 6,检索器没有被显式调高。
有的文档根本找不到
① 分词/切分粒度不够细; ② 关键字在文档中出现频率低,向量相似度不足;③ 索引没有覆盖全部文档(未重新加载)。
只能匹配 “关键字”,找不到“包含关键字的上下文”
向量检索仅基于整体语义,未结合 混合检索(向量 + BM25)或 元数据过滤。
查询精准度不高
① 向量模型不适配中文长文本;② 召回阈值 (score_threshold) 设得太宽;③ 未使用 rerank 进行二次排序。
2️⃣ 把“返回条数”调高

假设你使用的是 LangChain + FAISS

from langchain.vectorstores import FAISS
from langchain.embeddings import OpenAIEmbeddings

已有向量库 vector_store

results = vector_store.similarity_search(
query="你的关键字",
k=20, # ← 把 6 改成 20(甚至 50)
# 可选:限制相似度阈值,只保留高分条目
# score_threshold=0.7
)
k 越大召回越多,后面再通过 rerank 或 过滤 剔除噪声。
若使用 Chroma、Pinecone、Milvus 等后端,同样支持 top_k 参数,只要在查询时显式传入。
3️⃣ 提升召回率(让更多文档被命中)
3.1 调整文本切分(Chunking)
参数
建议值
说明
chunk_size
400‑600 token
兼顾上下文完整性与向量质量
chunk_overlap
50‑100 token
防止关键词跨块丢失
separator
"\n\n"、"\n"、" "
依据 Markdown 结构选择
from langchain.text_splitter import RecursiveCharacterTextSplitter

splitter = RecursiveCharacterTextSplitter(
chunk_size=500,
chunk_overlap=80,
separators=["\n\n", "\n", " "]
)
chunks = splitter.split_documents(raw_documents)
3.2 使用 混合检索(Hybrid Search)
向量检索返回候选集合(如 top_k=30)。
对这批候选再用 BM25(或 TF‑IDF)进行二次排序。

1. 向量检索

vec_results = vector_store.similarity_search(query, k=30)

2. 加载 BM25 检索器(这里以 rank_bm25 为例)

from rank_bm25 import BM25Okapi
bm25 = BM25Okapi([doc.page_content.split() for doc in raw_documents])

bm25_scores = bm25.get_scores(query.split())

3. 合并两套分数(加权)

final_scores = {doc.id: 0.7vec_score + 0.3bm25_score for doc, vec_score, bm25_score in zip(vec_results, vec_scores, bm25_scores)}
sorted_docs = sorted(final_scores.items(), key=lambda x: x[1], reverse=True)[:10]
大多数向量数据库(Pinecone、Qdrant、Milvus)已经内置混合搜索,只需在查询时添加 hybrid=True 并提供 filter。
3.3 添加 元数据(Metadata)过滤
在构建索引时,把文档的 发布时间、标签、作者、类别 等信息写入 metadata。查询时通过过滤只检索最近 30 天或特定标签的文档。

索引时

vector_store.add_documents(chunks, metadatas=[{"date":"2026-03-10","tag":"产品"}])

查询时

results = vector_store.similarity_search(
query,
k=20,
filter={"date": {"$gte": "2026-02-20"}} # 只取近 30 天
)
4️⃣ 提高查询精准度的技巧
方法
使用场景
实现要点
Rerank(二次排序)
初始向量召回后
采用 cross‑encoder(如 bge‑reranker‑large)对 top‑k 再打分,取前 N 条。
自定义提示词
LLM 生成摘要/回答
在 ChatPromptTemplate 中加入 “只返回包含关键字的段落”,让模型聚焦。
多轮检索
“先粗查 → 再细查”
第一次查询宽松返回 30 条,第二次在这些结果中加上 过滤词(如日期、类别)再做向量检索。
使用更强的嵌入模型
中文长文本
推荐 bge‑large‑zh、text‑embedding‑3‑large(OpenAI)或 jina‑embeddings‑v2‑zh。
召回阈值调节
排除噪声
score_threshold 设在 0.75 ~ 0.85(根据模型返回值范围调试)。
示例:Rerank + Cross‑Encoder
from transformers import AutoModelForSequenceClassification, AutoTokenizer
import torch

reranker = AutoModelForSequenceClassification.from_pretrained("bge-reranker-base")
tokenizer = AutoTokenizer.from_pretrained("bge-reranker-base")

def rerank(query, docs, top_n=10):
pairs = [(query, doc.page_content) for doc in docs]
inputs = tokenizer(pairs, padding=True, truncation=True, return_tensors="pt")
with torch.no_grad():
scores = reranker(**inputs).logits.squeeze(-1).numpy()
ranked = sorted(zip(docs, scores), key=lambda x: x[1], reverse=True)[:top_n]
return [doc for doc, _ in ranked]

使用流程

vec_hits = vector_store.similarity_search(query, k=30)
final_hits = rerank(query, vec_hits, top_n=10)
5️⃣ 基于时间的 摘要 & 分析 工作流
5.1 为每篇 Markdown 添加 date 元数据(ISO 8601)

示例 Markdown 头部


title: "XX 项目需求"
date: 2026-02-28
tags: [需求, 2026-Q1]


在 加载文档 时读取 FrontMatter 并写入向量库的 metadata.
5.2 检索最近 30 天的文档
from datetime import datetime, timedelta

today = datetime.now()
thirty_days_ago = (today - timedelta(days=30)).strftime("%Y-%m-%d")

recent_docs = vector_store.similarity_search(
query="关键字",
k=20,
filter={"date": {"$gte": thirty_days_ago}}
)

5.3 对检索到的文档做 批量摘要(一次性生成)

```python
from langchain.chains import LLMChain
from langchain.prompts import PromptTemplate
summary_prompt = PromptTemplate(
input_variables=["contents"],
template="""请对下面的多段中文内容做 200 字以内的综合摘要,重点突出与“{keyword}”相关的要点。
{contents}
"""
)
chain = LLMChain(llm=ChatOpenAI(model="gpt-4o-mini"), prompt=summary_prompt)

合并所有检索结果的文本

all_text = "\n---\n".join([doc.page_content for doc in recent_docs])
summary = chain.run({"contents": all_text, "keyword": "你的关键字"})
print(summary)
5.4 进一步的 分析(趋势、频次、情感)
词频统计:使用 collections.Counter 统计检索集合中出现的高频词。
情感分析:调用中文情感模型(如 nlptown/bert-base-multilingual-uncased-sentiment)对每篇摘要打分,生成整体情感概览。
趋势图:把每篇文档的 date 与关键字出现次数对应,使用 matplotlib 绘制时间序列。
import matplotlib.pyplot as plt
from collections import Counter
import re

dates = []
counts = Counter()

for doc in recent_docs:
dates.append(doc.metadata["date"])
cnt = len(re.findall(r"关键字", doc.page_content))
counts[doc.metadata["date"]] += cnt

按日期排序绘图

sorted_dates = sorted(counts.keys())
plt.plot(sorted_dates, [counts[d] for d in sorted_dates], marker='o')
plt.title("关键字出现次数趋势(最近 30 天)")
plt.xlabel("日期")
plt.ylabel("出现次数")
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
6️⃣ 完整的 进阶工作流示例(Python)
import os, json, datetime
from langchain.document_loaders import DirectoryLoader, UnstructuredMarkdownLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.embeddings import HuggingFaceEmbeddings
from langchain.vectorstores import FAISS
from langchain.chains import LLMChain
from langchain.prompts import PromptTemplate
from transformers import AutoModelForSequenceClassification, AutoTokenizer
import torch, matplotlib.pyplot as plt, re
from collections import Counter

1️⃣ 加载文档(带 FrontMatter)

loader = DirectoryLoader("data/md", glob="*/.md", loader_cls=UnstructuredMarkdownLoader)
raw_docs = loader.load()

2️⃣ 解析 FrontMatter 并写入 metadata

def extract_meta(doc):
# 简单示例:假设第一行是 "---" 包围的 YAML
if doc.page_content.startswith("---"):
end = doc.page_content.find("---", 3)
yaml = doc.page_content[3:end]
meta = json.loads(yaml) # 用 PyYAML 更稳
doc.metadata.update(meta)
doc.page_content = doc.page_content[end+3:].strip()
return doc

docs = [extract_meta(d) for d in raw_docs]

3️⃣ 切块

splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=80)
chunks = splitter.split_documents(docs)

4️⃣ 向量化 & 建索引(使用中文 BGE 大模型)

embeddings = HuggingFaceEmbeddings(model_name="BAAI/bge-large-zh")
vector_store = FAISS.from_documents(chunks, embeddings)

5️⃣ 检索(示例查询)

query = "机器学习最新进展"
today = datetime.datetime.now().strftime("%Y-%m-%d")
thirty_days_ago = (datetime.datetime.now() - datetime.timedelta(days=30)).strftime("%Y-%m-%d")

hits = vector_store.similarity_search(
query,
k=30,
filter={"date": {"$gte": thirty_days_ago}} # 只看近 30 天
)

6️⃣ Rerank(可选)

reranker = AutoModelForSequenceClassification.from_pretrained("BAAI/bge-reranker-base")
tokenizer = AutoTokenizer.from_pretrained("BAAI/bge-reranker-base")

def rerank(query, docs, top_n=10):
pairs = [(query, d.page_content) for d in docs]
inputs = tokenizer(pairs, padding=True, truncation=True, return_tensors="pt")
with torch.no_grad():
scores = reranker(**inputs).logits.squeeze(-1).numpy()
ranked = sorted(zip(docs, scores), key=lambda x: x[1], reverse=True)[:top_n]
return [d for d, _ in ranked]

final_hits = rerank(query, hits, top_n=10)

7️⃣ 批量摘要

summary_prompt = PromptTemplate(
input_variables=["contents"],
template="""请对以下内容做 150 字以内的中文摘要,突出与“{keyword}”相关的要点。

{contents}
"""
)
chain = LLMChain(llm=ChatOpenAI(model="gpt-4o-mini"), prompt=summary_prompt)
all_text = "\n---\n".join([d.page_content for d in final_hits])
summary = chain.run({"contents": all_text, "keyword": "机器学习"})

print("=== 综合摘要 ===")
print(summary)
8️⃣ 趋势分析(词频随时间)
date_counter = Counter()
for d in final_hits:
cnt = len(re.findall(r"机器学习", d.page_content))
date_counter[d.metadata["date"]] += cnt

sorted_dates = sorted(date_counter.keys())
plt.plot(sorted_dates, [date_counter[d] for d in sorted_dates], marker='o')
plt.title("机器学习关键词出现次数趋势(最近 30 天)")
plt.xlabel("日期")
plt.ylabel("出现次数")
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
要点提炼
1. k 调大 → 返回更多候选。
2. 细化切块 + 适当重叠,防止关键字跨块丢失。
3. 混合检索(向量 + BM25)或 元数据过滤,兼顾语义和精准匹配。
4. 二次排序(Rerank) 提高质量。
5. 为文档添加时间/标签元数据,便于 时间范围检索趋势分析
6. 批量摘要 + 可视化,快速获取近段时间的全局概况。
按上面步骤改进后,你的系统就能:
- 一次返回 20‑30 条 甚至更多结果。
- 覆盖更多文档(关键字出现频次低的也能被召回)。
- 提供基于时间的摘要/趋势,帮助发现最近一个月的热点。
如有进一步的实现细节(如在 Feishu Bot 中集成这些函数、或者调参建议),随时告诉我!祝调优顺利