本地 RAG 实战:用 Easysearch + Ollama SDK 半小时搭建检索增强问答系统

✅ 目标:只用两台服务器(或同一台)就跑通 “向量检索 + 本地大模型” 原型
✅ 特点:完全离线、依赖极少、部署脚本即文档
✅ 适合:快速 PoC、内网合规场景、想深挖 RAG 工作机理的开发者

0. 背景与动机

生成式 AI 聊天固然强大,但当问题依赖本地私有知识时,单靠 LLM 参数内的“世界记忆”往往答非所问。RAG(Retrieval-Augmented Generation) 的思路是:

  1. 把文档切片 → 向量化 → 入库
  2. 用户提问 → 同样向量化 → 检索
  3. 将召回片段拼进 prompt,让大模型“带着材料”再回答

多数教程直接用云端 Embedding+OpenAI GPT-4o,但一些团队因隐私、成本或离线环境无法这样做。
本文选用:

  • EasySearch (= OpenSearch + Elastiknn) 做向量存取
  • Ollama SDK 连接本地 LLM
  • Python + requests + ollama 三个依赖即可

1. 系统架构

1
2
3
4
用户问题 ──▶ 嵌入模型 (Ollama) ──▶ EasySearch 向量检索 ──▶ Top-k 片段
▲ │
│ │
LLM (Ollama Chat) ◀── 拼 Prompt + 生成答案 ◀───────────────┘
  • 嵌入模型nomic-embed-text(768 维,多语言通用)
  • 检索引擎:EasySearch 2.x + Elastiknn knn_dense_float_vector
  • 对话模型deepseek-r1:7b(轻量,好部署;可换 llama3 / qwen

2. 环境与依赖

1
2
3
4
import os, json, requests, warnings
from ollama import Client
from requests.packages.urllib3.exceptions import InsecureRequestWarning
warnings.filterwarnings("ignore", category=InsecureRequestWarning)
1
2
3
4
5
6
# Python 依赖
pip install ollama requests

# 拉取模型
ollama pull nomic-embed-text
ollama pull deepseek-r1:7b

3. 代码逐段拆解

3.1 全局配置

1
2
3
4
5
6
7
8
9
10
# ────────────── 配置区 ──────────────
ES_URL = os.getenv("ES_URL", "https://localhost:9200")
ES_AUTH = ("admin", "c59a759f31e901e8d279") # 无认证设为 None
INDEX = "rag_demo"

OLLAMA_HOST = os.getenv("OLLAMA_URL", "http://localhost:11434")
EMBED_MODEL = "nomic-embed-text" # 向量模型
CHAT_MODEL = "deepseek-r1:7b" # 对话模型
TOP_K = 4
NUM_CAND = 200

这段代码只是给脚本提前设定一些“连接参数”与“模型选择”,方便后面统一引用。逐行解释如下:

1
ES_URL   = "https://<es_host>:9200"
  • ES_URL:EasySearch / OpenSearch 集群的完整地址(含协议与端口)。

    • <es_host> 是占位符,实际部署时要替换成你的 IP 或域名。
    • 如果你的集群没开 TLS,可写成 http://10.0.0.8:9200
1
ES_AUTH  = ("elastic", "password")   # 无认证设为 None
  • ES_AUTH:连接集群的账号密码元组。

    • 脚本里传给 requestsauth= 参数,会自动加 Basic Auth 头。
    • 若集群关闭了安全认证或走内网匿名访问,就把它设成 None
1
INDEX    = "rag_demo"
  • INDEX:向量索引(或文档索引)的名称。

    • 脚本后面会对该索引做 create / bulk write / search 等操作。
    • 换成别的名字时记得保持一致,例如 "knowledge_base"
1
OLLAMA_HOST = "http://<ollama_host>:11434"
  • OLLAMA_HOST:本地 Ollama 服务的 HTTP 起始地址。

    • <ollama_host> 也是占位符;若脚本与 Ollama 在同一台机器,可写 http://localhost:11434
    • 端口 11434 是 Ollama 默认 REST 端口。
1
EMBED_MODEL = "nomic-embed-text"
  • EMBED_MODEL:用于生成文本向量(embeddings)的模型名。

    • 在脚本里会调用 client.embeddings(model=EMBED_MODEL, …)
    • 替换规则:先执行 ollama pull <模型名>,确保本地已下载。
1
CHAT_MODEL  = "deepseek-r1:7b"
  • CHAT_MODEL:负责最终回答的聊天 / 生成式模型。

    • 脚本会用 client.chat(model=CHAT_MODEL, …) 进行对话。
    • 同理,若想用 llama3:8b-chatqwen:7b-chat 等,先 ollama pull 再改这里。

3.2 嵌入与对话(Ollama SDK)

client 是连接 Ollama 模型服务的客户端,用来发请求。

session 是访问 Elasticsearch 用的请求会话,能提高网络效率。

用指定的嵌入模型(比如 nomic-embed-text)把文本转成向量,用于相似度搜索。

用指定的聊天模型(比如 deepseek-r1:7b)回答问题,返回回复文本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# ────────────── 初始化 ──────────────
client = Client(host=OLLAMA_HOST)
session = requests.Session()

# ────────────── Ollama 封装 ──────────────
def embed(text: str) -> list[float]:
"""返回文本向量 (list[float])"""
resp = client.embeddings(model=EMBED_MODEL, prompt=text)
return resp["embedding"]

def chat(prompt: str) -> str:
"""与聊天模型对话(完整回复)"""
resp = client.chat(
model=CHAT_MODEL,
messages=[{"role": "user", "content": prompt}],
# stream=False
)
return resp["message"]["content"]

3.3 建索引(Elastiknn)

在构建基于向量的 RAG(Retrieval-Augmented Generation)系统时,我们首先需要在向量数据库中创建一个支持向量检索的索引。本文使用 EasySearch 作为底层存储,向其中注册一个支持近似向量搜索的索引结构。

create_index 函数的作用是通过 RESTful API 创建一个名为 rag_demo 的索引,并定义字段结构如下:

content:文本内容字段,类型为 text,可用于全文搜索或作为上下文返回。

vec:向量字段,类型为 knn_dense_float_vector,支持高维向量的快速相似度搜索。
配置中使用了 LSH(局部敏感哈希)模型与 cosine 相似度度量,同时设定了近似参数 L 与 k,分别控制候选样本数量和返回结果数。

通过设定 “index.knn”: True,该索引支持使用 k-NN 查询来高效地检索与查询向量最相似的文档。在实际使用中,嵌入模型如 nomic-embed-text 可将输入文本转换为高维向量,存入此索引中,与用户查询语义对齐,实现高效的语义检索能力。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# ---------- ① 创建索引:Elastiknn 映射 ----------
def create_index(dim: int):
mapping = {
"settings": { "index.knn": True },
"mappings": {
"properties": {
"content": { "type": "text" },
"vec": {
"type": "knn_dense_float_vector",
"knn": {
"dims": dim,
"model": "lsh", # 也可 hnsw / exact
"similarity": "cosine",
"L": 99,
"k": 1
}
}
}
}
}
r = session.put(f"{ES_URL}/{INDEX}",
json=mapping, verify=False, auth=ES_AUTH)
if r.status_code not in (200, 201):
if "resource_already_exists_exception" not in r.text:
print("Create index error:\n", r.text)
r.raise_for_status()

3.4 写入文档

以下是对这段 bulk_upload 函数的简明解释,可用于博客正文或技术文档:

在 RAG 系统中,为了支持高效的语义检索,我们需要将原始文本与其对应的向量一起写入向量索引中。bulk_upload 函数正是完成这一任务的核心组件,它使用 Elasticsearch 的 _bulk 接口实现批量写入,显著提高写入效率。

  • 每条记录包含两个部分:

    1. index 元数据,指定目标索引(rag_demo)及文档 _id

    2. 实际文档内容,包括:

      • content:原始文本内容;
      • vec:对应的文本向量,必须使用 {"values": [...]} 的对象结构
  • 向量通过 embed(t) 获得,调用本地部署的 Ollama 模型(如 nomic-embed-text)生成。

  • 所有数据最终编码为 JSON,通过 Content-Type: application/x-ndjson 提交到 /_bulk API 接口,实现一次性批量写入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# ---------- ② 批量写入:向量必须包 {"values": …} ----------
def bulk_upload(texts: list[str]):
bulk = []
for i, t in enumerate(texts):
bulk.append({ "index": { "_index": INDEX, "_id": i } })
bulk.append({
"content": t,
"vec": { "values": embed(t) } # ★ 关键:对象格式
})
ndjson = "\n".join(json.dumps(d, ensure_ascii=False) for d in bulk) + "\n"
r = session.post(f"{ES_URL}/_bulk",
data=ndjson.encode("utf-8"),
headers={"Content-Type": "application/x-ndjson"},
verify=False, auth=ES_AUTH)
r.raise_for_status()

3.5 语义检索

这段 search 函数的作用是在 RAG 系统中执行基于向量的语义检索,以下是适合用于博客中的简明解释:

RAG 系统的核心是从向量索引中找到与用户问题最相近的语义片段。search 函数即完成了这个过程,它调用 EasySearch 的向量检索接口,返回最相似的文本内容。

  1. 文本向量化:通过 embed(question) 把用户输入的问题转换成向量 qvec

  2. 构造检索请求
    使用 knn_nearest_neighbors 查询

    • field: 向量字段名(本例中是 "vec");
    • vec: 查询向量,必须写成 { "values": [...] } 的对象结构;
    • model: 向量近似检索模型(如 "lsh");
    • similarity: 相似度度量方式(如 "cosine");
    • k: 返回的结果数;
    • candidates: 候选池大小,用于粗排优化检索效果。
  3. 发送请求并解析响应
    请求通过 Elasticsearch _search 接口提交,若返回不成功,则输出报错信息;成功后提取 _source["content"] 字段,返回给上层用于回答生成。

✅ 示例用途:

用户提问:“张三是谁”,系统会将该问题向量化,然后在已有文本向量中进行相似度匹配,从而返回如“张三是法律专家……”的片段,作为构建回答的上下文。

这段逻辑是 RAG 模型“Retriever”阶段的核心,让大模型在“有知识”的基础上作答,提升准确性和实用性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def search(question: str, top_k: int = TOP_K):
qvec = embed(question)
body = {
"size": top_k,
"query": {
"knn_nearest_neighbors": { # 若 Elastiknn 0.7.x 用 elastiknn_nearest_neighbors
"field": "vec",
"vec": { "values": qvec },
"model": "lsh",
"similarity": "cosine",
"k": top_k,
"candidates": 200
}
}
}
r = session.post(f"{ES_URL}/{INDEX}/_search",
json=body, verify=False, auth=ES_AUTH)
if not r.ok:
print("== ES response ==", r.text)
r.raise_for_status()
return [hit["_source"]["content"] for hit in r.json()["hits"]["hits"]]

3.6 主循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# ────────────── CLI 主逻辑 ──────────────
def main():
docs = [
# 原来的 3 条
"张三是法律专家,擅长合同法与知识产权。",
"李四在人机交互领域研究多年,尤其关注可用性测试。",
"王五是一名资深软件工程师,对云原生、DevOps 有丰富经验。",

# 新增 100 条
"赵六是一名数据科学家,专注机器学习模型调优。",
"孙七具有十年金融风控经验,熟悉巴塞尔协议。",
"周八是区块链开发者,擅长智能合约安全审计。",
"吴九长期研究边缘计算,在 IoT 网关架构方面有实践。",
"郑十是资深 DBA,精通 MySQL 性能调优与高可用。",
"钱十一擅长云原生安全治理,主导多家企业零信任落地。",
"蒋十二是 GPU 运维专家,对 CUDA 优化有深入研究。",
"沈十三专注深度学习推理加速,维护 TensorRT 插件。",
"韩十四是 FaaS 平台架构师,关注冷启动优化策略。",
"姚十五从事量化交易算法开发,对高频数据处理熟练。",
"邵十六是渗透测试工程师,擅长 Web 漏洞挖掘与利用。",
"汪十七主攻 AIGC 版权合规,为多家媒体机构提供方案。",
"孔十八研究联邦学习,解决数据孤岛隐私问题。",
"曹十九负责 SRE 团队,精通混沌工程与错误预算管理。",
"严二十是网络取证专家,参与多起重大案件分析。",
"华二一研发 AutoML 平台,降低模型训练门槛。",
"雷二二在 5G 边缘网协同计算领域发表多篇论文。",
"凌二三是 DevRel 经理,推动开源社区增长。",
"史二四对 RAG 架构有深入实践,优化检索召回率。",
"阮二五是 WebGPU 先行者,致力提升前端渲染性能。",
"杭二六主导多云成本治理项目,节省 30% 预算。",
"乔二七是一名 AIGC Prompt 工程师,专精多模态指令设计。",
"詹二八擅长大规模 AB 测试框架落地。",
"顾二九是 Serverless 架构布道者,编写多本技术书籍。",
"龚三十关注 DORA 指标,用数据驱动 DevOps 改进。",
"计三一是 API 网关专家,实现百万 QPS 低延迟。",
"蒲三二研究影像分割模型,用于医学辅助诊断。",
"邱三三是 Zig 语言早期贡献者,推行内存安全编码。",
"庄三四长期维护 Kafka 集群,擅长 Topic 规划。",
"宫三五是低代码平台架构师,关注插件生态。",
"蓝三六研究 ICEBERG 表格式,提升湖仓查询效率。",
"聂三七在安全编排 SOAR 产品设计上经验丰富。",
"陆三八主导 SaaS 产品国际化,本地化流程成熟。",
"温三九负责混合云 DR 方案,实现分钟级切换。",
"袁四十是语音合成工程师,优化多 speaker 适配。",
"贾四一深入研究 DDD,帮助团队理清领域边界。",
"伏四二从事实时风控大数据平台架构,处理亿级流量。",
"程四三是 ARM SoC 驱动工程师,对电源管理熟悉。",
"屈四四在 Federated GraphQL 网关治理方面有案例。",
"申四五带队实现 MLOps 自动化发布流程。",
"罗四六研究 VDBMS,支持 PB 级向量检索。",
"祝四七是 HTAP 数据库布道者,优化混合负载。",
"左四八在 IAM 与 RBAC 设计领域深耕。",
"冷四九是链路可观测性工程师,推广 OTEL 标准。",
"包五十投入异构计算调度框架研究。",
"滑五一精通 eBPF 在安全可观测性场景的落地。",
"柴五二研究量子安全算法,对国密迁移方案熟悉。",
"谈五三是内核安全研究员,发现多个 0-day 漏洞。",
"鄢五四主导 SaaS 计费系统重构,支持灵活套餐。",
"邸五五是绿色数据中心规划师,推动 PUE 降到 1.2。",
"候五六在自动驾驶 SLAM 算法具有专利。",
"古五七关注 CDP 架构,连接多源营销数据。",
"丁五八是 FPGA 加速工程师,实现低延迟推理。",
"靳五九研究 WASM 边缘运行时,降低冷启动。",
"柴六十在 DevSecOps 流水线集成方面经验丰富。",
"花六一策划大规模黑客马拉松,促成 500+ 项目孵化。",
"牛六二是边缘 AI 推理框架作者,重视功耗优化。",
"焦六三研究自监督学习在推荐系统的应用。",
"商六四是 Rust Web 开发者,推广零拷贝 JSON 解析。",
"阎六五投入数字孪生城市平台研发。",
"弓六六主攻 OTA 升级安全,覆盖汽车 ECU。",
"怀六七是 MAC 数据平面专家,优化转发性能。",
"宓六八参与多场灾备演练,完善演练脚本体系。",
"郝六九是 PKI 架构师,设计大规模证书生命周期。",
"嵇七十致力于多媒体编解码标准化。",
"邝七一研究 EDA 自动布线算法。",
"桑七二打造 AI 工厂流水线,实现模型快速迭代。",
"桂七三专注 DPU 加速网络虚拟化。",
"麻七四是 Supabase 中国社区维护者,推广 BaaS。",
"仇七五实现企业级 KYC 流程自动化。",
"薄七六研究多模态情感分析,用于客服质检。",
"谯七七是 SD-WAN 产品经理,聚焦海外专线优化。",
"巫七八负责 Kafka to Pulsar 迁移方案。",
"桑七九在 DAG 引擎优化 CPS 流水线。",
"邬八十研究端侧 LLM 蒸馏压缩。",
"臧八一是三维重建算法工程师,服务文博数字化。",
"禾八二专攻 S3 兼容对象存储网关。",
"原八三参与可信执行环境 TEE 方案落地。",
"淦八四是工业互联网安全规划顾问。",
"练八五实现 GPU 多租户 QoS 调度器。",
"禹八六关注跨境合规要求,精通 GDPR。",
"廉八七是 SDN 控制器开源贡献者。",
"亓八八专注高并发长连接网关。",
"宗八九打造零代码机器学习平台。",
"公冶九十研究 PIM 存内计算架构。",
"红九一是 MESH 网络性能调优专家。",
"眭九二致力于 AI 合成音频版权检测。",
"米九三推动碳排放数据平台建设。",
"隗九四是机器人运动规划算法专家。",
"拉九五研究语义分割在遥感图像的应用。",
"蔺九六负责 0-RTT QUIC 协议优化。",
"臧九七专注 BERT 在法律文本的细粒度实体抽取。",
"昝九八是 RPG 游戏 AI NPC 行为树作者。",
"贝九九研究差分隐私在广告数据的实践。",
"施一百主导云原生 API 安全监控平台。",
"伏一零一优化 Kafka Connect 大规模同步。",
"堵一零二研究车辆 V2X 协议栈实现。",
"莎一零三聚焦 A/B 决策平台可视化。",
]


dim = len(embed("维度探测")) # 动态探测向量维度
create_index(dim)
bulk_upload(docs)
print(f"[√] 初始化完成(向量维度 {dim})。开始提问,Ctrl+C 退出。\n")

try:
while True:
q = input("Q: ").strip()
if not q:
continue
passages = search(q)
context = "\n".join(f"资料{i+1}{p}" for i, p in enumerate(passages))
prompt = (f"已知资料如下:\n{context}\n\n请根据以上资料回答用户问题:{q}")
print("\nA:", chat(prompt))
print("引用:", passages, "\n")
except KeyboardInterrupt:
print("\nBye!")

if __name__ == "__main__":
main()

4. 运行效果

1
2
3
Q: 张三擅长什么
A: 张三是一名法律专家,专长领域为合同法与知识产权。
引用: ['张三是法律专家,擅长合同法与知识产权。']

结果如下:
在这里插入图片描述

完整代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
import os, json, requests, warnings
from ollama import Client
from requests.packages.urllib3.exceptions import InsecureRequestWarning
warnings.filterwarnings("ignore", category=InsecureRequestWarning)


# ────────────── 配置区 ──────────────
ES_URL = os.getenv("ES_URL", "https://localhost:9200")
ES_AUTH = ("admin", "c59a759f31e901e8d279") # 无认证设为 None
INDEX = "rag_demo"

OLLAMA_HOST = os.getenv("OLLAMA_URL", "http://localhost:11434")
EMBED_MODEL = "nomic-embed-text" # 向量模型
CHAT_MODEL = "deepseek-r1:7b" # 对话模型
TOP_K = 4
NUM_CAND = 200 # kNN 先粗召回条数


# ────────────── 初始化 ──────────────
client = Client(host=OLLAMA_HOST)
session = requests.Session()

# ────────────── Ollama 封装 ──────────────
def embed(text: str) -> list[float]:
"""返回文本向量 (list[float])"""
resp = client.embeddings(model=EMBED_MODEL, prompt=text)
return resp["embedding"]

def chat(prompt: str) -> str:
"""与聊天模型对话(完整回复)"""
resp = client.chat(
model=CHAT_MODEL,
messages=[{"role": "user", "content": prompt}],
# stream=False
)
return resp["message"]["content"]

# ---------- ① 创建索引:Elastiknn 映射 ----------
def create_index(dim: int):
mapping = {
"settings": { "index.knn": True },
"mappings": {
"properties": {
"content": { "type": "text" },
"vec": {
"type": "knn_dense_float_vector",
"knn": {
"dims": dim,
"model": "lsh", # 也可 hnsw / exact
"similarity": "cosine",
"L": 99,
"k": 1
}
}
}
}
}
r = session.put(f"{ES_URL}/{INDEX}",
json=mapping, verify=False, auth=ES_AUTH)
if r.status_code not in (200, 201):
if "resource_already_exists_exception" not in r.text:
print("Create index error:\n", r.text)
r.raise_for_status()

# ---------- ② 批量写入:向量必须包 {"values": …} ----------
def bulk_upload(texts: list[str]):
bulk = []
for i, t in enumerate(texts):
bulk.append({ "index": { "_index": INDEX, "_id": i } })
bulk.append({
"content": t,
"vec": { "values": embed(t) } # ★ 关键:对象格式
})
ndjson = "\n".join(json.dumps(d, ensure_ascii=False) for d in bulk) + "\n"
r = session.post(f"{ES_URL}/_bulk",
data=ndjson.encode("utf-8"),
headers={"Content-Type": "application/x-ndjson"},
verify=False, auth=ES_AUTH)
r.raise_for_status()

# ---------- ③ 查询:knn_nearest_neighbors ----------
def search(question: str, top_k: int = TOP_K):
qvec = embed(question)
body = {
"size": top_k,
"query": {
"knn_nearest_neighbors": { # 若 Elastiknn 0.7.x 用 elastiknn_nearest_neighbors
"field": "vec",
"vec": { "values": qvec },
"model": "lsh",
"similarity": "cosine",
"k": top_k,
"candidates": 200
}
}
}
r = session.post(f"{ES_URL}/{INDEX}/_search",
json=body, verify=False, auth=ES_AUTH)
if not r.ok:
print("== ES response ==", r.text)
r.raise_for_status()
return [hit["_source"]["content"] for hit in r.json()["hits"]["hits"]]


# ────────────── CLI 主逻辑 ──────────────
def main():
docs = [
# 原来的 3 条
"张三是法律专家,擅长合同法与知识产权。",
"李四在人机交互领域研究多年,尤其关注可用性测试。",
"王五是一名资深软件工程师,对云原生、DevOps 有丰富经验。",

# 新增 100 条
"赵六是一名数据科学家,专注机器学习模型调优。",
"孙七具有十年金融风控经验,熟悉巴塞尔协议。",
"周八是区块链开发者,擅长智能合约安全审计。",
"吴九长期研究边缘计算,在 IoT 网关架构方面有实践。",
"郑十是资深 DBA,精通 MySQL 性能调优与高可用。",
"钱十一擅长云原生安全治理,主导多家企业零信任落地。",
"蒋十二是 GPU 运维专家,对 CUDA 优化有深入研究。",
"沈十三专注深度学习推理加速,维护 TensorRT 插件。",
"韩十四是 FaaS 平台架构师,关注冷启动优化策略。",
"姚十五从事量化交易算法开发,对高频数据处理熟练。",
"邵十六是渗透测试工程师,擅长 Web 漏洞挖掘与利用。",
"汪十七主攻 AIGC 版权合规,为多家媒体机构提供方案。",
"孔十八研究联邦学习,解决数据孤岛隐私问题。",
"曹十九负责 SRE 团队,精通混沌工程与错误预算管理。",
"严二十是网络取证专家,参与多起重大案件分析。",
"华二一研发 AutoML 平台,降低模型训练门槛。",
"雷二二在 5G 边缘网协同计算领域发表多篇论文。",
"凌二三是 DevRel 经理,推动开源社区增长。",
"史二四对 RAG 架构有深入实践,优化检索召回率。",
"阮二五是 WebGPU 先行者,致力提升前端渲染性能。",
"杭二六主导多云成本治理项目,节省 30% 预算。",
"乔二七是一名 AIGC Prompt 工程师,专精多模态指令设计。",
"詹二八擅长大规模 AB 测试框架落地。",
"顾二九是 Serverless 架构布道者,编写多本技术书籍。",
"龚三十关注 DORA 指标,用数据驱动 DevOps 改进。",
"计三一是 API 网关专家,实现百万 QPS 低延迟。",
"蒲三二研究影像分割模型,用于医学辅助诊断。",
"邱三三是 Zig 语言早期贡献者,推行内存安全编码。",
"庄三四长期维护 Kafka 集群,擅长 Topic 规划。",
"宫三五是低代码平台架构师,关注插件生态。",
"蓝三六研究 ICEBERG 表格式,提升湖仓查询效率。",
"聂三七在安全编排 SOAR 产品设计上经验丰富。",
"陆三八主导 SaaS 产品国际化,本地化流程成熟。",
"温三九负责混合云 DR 方案,实现分钟级切换。",
"袁四十是语音合成工程师,优化多 speaker 适配。",
"贾四一深入研究 DDD,帮助团队理清领域边界。",
"伏四二从事实时风控大数据平台架构,处理亿级流量。",
"程四三是 ARM SoC 驱动工程师,对电源管理熟悉。",
"屈四四在 Federated GraphQL 网关治理方面有案例。",
"申四五带队实现 MLOps 自动化发布流程。",
"罗四六研究 VDBMS,支持 PB 级向量检索。",
"祝四七是 HTAP 数据库布道者,优化混合负载。",
"左四八在 IAM 与 RBAC 设计领域深耕。",
"冷四九是链路可观测性工程师,推广 OTEL 标准。",
"包五十投入异构计算调度框架研究。",
"滑五一精通 eBPF 在安全可观测性场景的落地。",
"柴五二研究量子安全算法,对国密迁移方案熟悉。",
"谈五三是内核安全研究员,发现多个 0-day 漏洞。",
"鄢五四主导 SaaS 计费系统重构,支持灵活套餐。",
"邸五五是绿色数据中心规划师,推动 PUE 降到 1.2。",
"候五六在自动驾驶 SLAM 算法具有专利。",
"古五七关注 CDP 架构,连接多源营销数据。",
"丁五八是 FPGA 加速工程师,实现低延迟推理。",
"靳五九研究 WASM 边缘运行时,降低冷启动。",
"柴六十在 DevSecOps 流水线集成方面经验丰富。",
"花六一策划大规模黑客马拉松,促成 500+ 项目孵化。",
"牛六二是边缘 AI 推理框架作者,重视功耗优化。",
"焦六三研究自监督学习在推荐系统的应用。",
"商六四是 Rust Web 开发者,推广零拷贝 JSON 解析。",
"阎六五投入数字孪生城市平台研发。",
"弓六六主攻 OTA 升级安全,覆盖汽车 ECU。",
"怀六七是 MAC 数据平面专家,优化转发性能。",
"宓六八参与多场灾备演练,完善演练脚本体系。",
"郝六九是 PKI 架构师,设计大规模证书生命周期。",
"嵇七十致力于多媒体编解码标准化。",
"邝七一研究 EDA 自动布线算法。",
"桑七二打造 AI 工厂流水线,实现模型快速迭代。",
"桂七三专注 DPU 加速网络虚拟化。",
"麻七四是 Supabase 中国社区维护者,推广 BaaS。",
"仇七五实现企业级 KYC 流程自动化。",
"薄七六研究多模态情感分析,用于客服质检。",
"谯七七是 SD-WAN 产品经理,聚焦海外专线优化。",
"巫七八负责 Kafka to Pulsar 迁移方案。",
"桑七九在 DAG 引擎优化 CPS 流水线。",
"邬八十研究端侧 LLM 蒸馏压缩。",
"臧八一是三维重建算法工程师,服务文博数字化。",
"禾八二专攻 S3 兼容对象存储网关。",
"原八三参与可信执行环境 TEE 方案落地。",
"淦八四是工业互联网安全规划顾问。",
"练八五实现 GPU 多租户 QoS 调度器。",
"禹八六关注跨境合规要求,精通 GDPR。",
"廉八七是 SDN 控制器开源贡献者。",
"亓八八专注高并发长连接网关。",
"宗八九打造零代码机器学习平台。",
"公冶九十研究 PIM 存内计算架构。",
"红九一是 MESH 网络性能调优专家。",
"眭九二致力于 AI 合成音频版权检测。",
"米九三推动碳排放数据平台建设。",
"隗九四是机器人运动规划算法专家。",
"拉九五研究语义分割在遥感图像的应用。",
"蔺九六负责 0-RTT QUIC 协议优化。",
"臧九七专注 BERT 在法律文本的细粒度实体抽取。",
"昝九八是 RPG 游戏 AI NPC 行为树作者。",
"贝九九研究差分隐私在广告数据的实践。",
"施一百主导云原生 API 安全监控平台。",
"伏一零一优化 Kafka Connect 大规模同步。",
"堵一零二研究车辆 V2X 协议栈实现。",
"莎一零三聚焦 A/B 决策平台可视化。",
]


dim = len(embed("维度探测")) # 动态探测向量维度
create_index(dim)
bulk_upload(docs)
print(f"[√] 初始化完成(向量维度 {dim})。开始提问,Ctrl+C 退出。\n")

try:
while True:
q = input("Q: ").strip()
if not q:
continue
passages = search(q)
context = "\n".join(f"资料{i+1}{p}" for i, p in enumerate(passages))
prompt = (f"已知资料如下:\n{context}\n\n请根据以上资料回答用户问题:{q}")
print("\nA:", chat(prompt))
print("引用:", passages, "\n")
except KeyboardInterrupt:
print("\nBye!")

if __name__ == "__main__":
main()

小结

EasySearch × Ollama 让我们在本地就能体验到“RAG 的爽点”:检索带来实时、可信的上下文,大模型负责自然语言表达,二者合体即是一个可交付的“企业私有知识助手”。如果你也想在内网快速验证 PoC,这份脚本拷过去改两个地址即可开跑。祝玩得开心!

本地 RAG 实战:用 Easysearch + Ollama SDK 半小时搭建检索增强问答系统

https://xu-hardy.github.io/本地-rag-实战:用-easysearch-ollama-sdk-半小时搭建检索增强问答系统/

作者

Xu

发布于

2025-07-02

更新于

2025-07-01

许可协议

评论