本文记录一次从 Milvus 容器反复重启、端口拒绝,到最终稳定运行并实现 LangChain RAG 成功检索的完整实战过程。适用于本地 RAG、私有大模型、知识库系统等场景。
一、背景与问题
在本地构建 RAG(Retrieval-Augmented Generation)时,开发者常遇到以下典型问题:
telnet 19530 连接被拒绝- Milvus 容器反复 Restarting (1)
- pymilvus 能连上,但 LangChain 查不到数据
- 不清楚 Milvus 中的「tag / metadata」到底存在哪里
本文基于 Milvus 2.4 + LangChain + Ollama(Qwen + bge-m3),逐步解决上述问题。
二、整体架构
LangChain
│
├─ Ollama Embedding (bge-m3)
│
├─ Milvus Vector Store
│ ├─ etcd
│ ├─ minio
│ └─ standalone
│
└─ Ollama LLM (qwen2.5)
Python 3.9 / 3.10 / 3.11 均可支持。
bash
pip install -U \
langchain \
langchain-core \
langchain-community \
langchain-text-splitters \
langchain-ollama \
pymilvus \
faiss-cpu
三、Milvus Docker Compose(稳定版)
注意:Milvus standalone 必须依赖 etcd + minio,不能直接使用 docker run milvusdb/milvus,否则 19530 端口不会监听。
docker-compose.yml
yaml
version: '3.5'
services:
rag-etcd:
container_name: rag-milvus-etcd
image: quay.io/coreos/etcd:v3.5.5
volumes:
- ./rag-volumes/etcd:/etcd
command: >
etcd
-advertise-client-urls=http://127.0.0.1:2379
-listen-client-urls http://0.0.0.0:2379
--data-dir /etcd
rag-minio:
container_name: rag-milvus-minio
image: minio/minio:RELEASE.2023-03-20T20-16-18Z
environment:
MINIO_ACCESS_KEY: minioadmin
MINIO_SECRET_KEY: minioadmin
ports:
- "19000:9000"
- "19001:9001"
volumes:
- ./rag-volumes/minio:/minio_data
command: minio server /minio_data --console-address ":9001"
rag-milvus:
container_name: rag-milvus-standalone
image: milvusdb/milvus:v2.4.0
command: ["milvus", "run", "standalone"]
environment:
ETCD_ENDPOINTS: rag-etcd:2379
MINIO_ADDRESS: rag-minio:9000
volumes:
- ./rag-volumes/milvus:/var/lib/milvus
ports:
- "19530:19530"
- "9091:9091"
depends_on:
- rag-etcd
- rag-minio
networks:
default:
name: rag-milvus-net
启动命令:
bash
docker compose up -d
四、验证 Milvus 是否正常
telnet 验证端口
bash
telnet 127.0.0.1 19530
若看到如下输出:
Connected to 127.0.0.1.
HTTP/1.1 400 Bad Request
这是正常现象,因为 19530 是 gRPC 端口,并非 HTTP 接口。
Python 连接 Milvus
python
from pymilvus import connections, utility, Collection
connections.connect(
host="127.0.0.1",
port="19530"
)
print("connected:", connections.has_connection("default"))
print("collections:", utility.list_collections())

五、理解 Milvus 里的 "tag / metadata"
Milvus 没有内置 tag 概念。所谓的 tag,本质是 Collection schema 中的 scalar 字段。
查看 collection schema:
python
col = Collection("rag_qwen_prod")
print(col.schema)

典型结构如下:
id (INT64, primary)
vector (FLOAT_VECTOR)
text (VARCHAR)
source (VARCHAR)
category (VARCHAR)
其中 source / category 即为 RAG 中的 tag。
六、LangChain + Ollama + Milvus 实战
Embedding(bge-m3)
python
from langchain_ollama import OllamaEmbeddings
embeddings = OllamaEmbeddings(
model="bge-m3",
base_url="http://192.168.31.161:11434"
)
构造文档
python
from langchain_core.documents import Document
docs = [
Document(page_content="RAG 是检索增强生成"),
Document(page_content="Milvus 是生产级向量数据库"),
Document(page_content="Qwen2.5 是高质量中文大模型"),
]
文本切分
python
from langchain_text_splitters import RecursiveCharacterTextSplitter
splitter = RecursiveCharacterTextSplitter(
chunk_size=300,
chunk_overlap=50
)
docs = splitter.split_documents(docs)
写入 Milvus
python
from langchain_community.vectorstores import Milvus
vectorstore = Milvus.from_documents(
docs,
embedding=embeddings,
collection_name="rag_qwen_prod",
connection_args={
"host": "localhost",
"port": "19530"
}
)
Retriever
python
retriever = vectorstore.as_retriever(
search_kwargs={"k": 3}
)
LLM(Qwen2.5)
python
from langchain_ollama import ChatOllama
llm = ChatOllama(
model="qwen2.5:7b",
temperature=0
)
Prompt & LCEL RAG Chain
python
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
prompt = ChatPromptTemplate.from_messages([
("system", "你只能基于上下文回答问题,不允许编造"),
("human", "上下文:\n{context}\n\n问题:{question}")
])
rag_chain = (
{
"context": retriever,
"question": RunnablePassthrough()
}
| prompt
| llm
)
查询验证
python
result = rag_chain.invoke("什么是 Milvus?")
print(result.content)
输出示例:
Milvus 是一个生产级向量数据库,用于高效存储和检索向量数据。
RAG 生效。
七、完整代码与常见问题总结
python
from langchain_ollama import ChatOllama, OllamaEmbeddings
from langchain_community.vectorstores import Milvus
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_core.documents import Document
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
# 1. Embedding
embeddings = OllamaEmbeddings(
model="bge-m3",
base_url="http://192.168.31.161:11434"
)
# 2. 文档
docs = [
Document(page_content="RAG 是检索增强生成"),
Document(page_content="Milvus 是生产级向量数据库"),
Document(page_content="Qwen2.5 是高质量中文大模型"),
]
# 3. 切分
splitter = RecursiveCharacterTextSplitter(
chunk_size=300,
chunk_overlap=50
)
docs = splitter.split_documents(docs)
# 4. Milvus Vector Store
vectorstore = Milvus.from_documents(
docs,
embedding=embeddings,
collection_name="rag_qwen_prod",
connection_args={
"host": "localhost",
"port": "19530"
}
)
retriever = vectorstore.as_retriever(search_kwargs={"k": 3})
# 5. LLM
llm = ChatOllama(
model="qwen2.5:7b",
temperature=0
)
# 6. Prompt
prompt = ChatPromptTemplate.from_messages([
("system", "你只能基于上下文回答问题,不允许编造"),
("human", "上下文:\n{context}\n\n问题:{question}")
])
# 7. LCEL RAG Chain
rag_chain = (
{
"context": retriever,
"question": RunnablePassthrough()
}
| prompt
| llm
)
# 8. Query
result = rag_chain.invoke("什么是 Milvus?")
print(result.content)
| 问题 | 原因 |
|---|---|
| 19530 连接被拒绝 | 直接 docker run Milvus |
| 容器重启 | 缺少 etcd / minio |
| telnet 返回 400 | 正常(gRPC) |
| 查不到 tag | schema 没定义 metadata |
| LangChain 无结果 | embedding 不一致 |
八、下一步可以做什么?
- 多 tag / 多租户 RAG
- expr 过滤(
category == 'ops') - Milvus IVF / HNSW 调优
- Streaming RAG
- Web UI + API 服务化
九、结语
Milvus + LangChain + Ollama 是目前本地 RAG 的黄金组合:
- Milvus:性能与扩展性
- Ollama:本地模型自由
- LangChain:链路清晰、可组合
如果你已经走到这一步,说明你已经不是"入门玩家"了。