AI中台
-
date_range 20/01/2023 00:48
点击量:次infosortAI中台label
LLM 本地栈安装手册(最终实战版)
栈组成:Ollama(模型)+ LiteLLM(网关)+ Langfuse(可观测)+ Milvus(向量库)+ Dify(应用编排) 目标机:MacBook Pro 16” 2019 · Intel i9 · 64GB · macOS Sonoma · Docker Desktop 定位:学习用 AI 中台,非生产环境 状态:本手册基于一次完整的从零搭建实战,所有配置均已验证可跑通,并标注了实际踩过的每一个坑。
0. 一分钟速览:最终架构与核心教训
0.1 五个组件如何协作
浏览器 / 你的代码
│
┌─────────────────┼──────────────────────┐
│ │ │
Dify Web :80 Langfuse UI :3000 │
│ ▲ │
│ 模型调用 │ trace 上报 │
▼ │ │
┌─────────┐ ┌──────┴────┐ │
│ Dify │────▶│ LiteLLM │ success_callback │
│ (编排) │ │ :4000 │──────▶ Langfuse │
└────┬────┘ └─────┬─────┘ │
│ 向量 │ 模型后端 │
▼ :19530 ▼ :11434 │
┌─────────┐ ┌──────────┐ │
│ Milvus │ │ Ollama │ qwen2.5:0.5b │
│(向量库) │ │ (原生跑) │ nomic-embed-text │
└─────────┘ └──────────┘ │
│
跨栈通信全部走【宿主机真实局域网 IP】(见下方核心教训)
0.2 五条用血泪换来的核心教训(务必先读)
教训 1 —— vLLM 别在 Intel Mac 上碰。
vLLM 官方镜像只支持 NVIDIA CUDA;vllm-metal 只支持 Apple Silicon。Intel Mac 的 AMD 核显两者都不支持,只能纯 CPU 源码编译,龟速且折腾。学中台用 Ollama,需要学 vLLM 本身就租 GPU。
教训 2 —— 跨栈通信用「真实局域网 IP」,不要用 host.docker.internal。
这是全程最大的坑。host.docker.internal 在 Docker Desktop 里理论支持,但 Dify 的 api/worker/plugin_daemon 容器解析不一致,会出现”添加模型时测试通过(api 发的)、真跑嵌入却卡死(plugin_daemon 发的)”的诡异现象。直接用 ipconfig getifaddr en0 拿到的真实 IP,一招绕开所有容器的解析问题。
教训 3 —— 五个单机栈挤一台宿主机,端口必然冲突。
每个官方 compose 都假设自己独占机器,把一堆端口往宿主机暴露。原则:每个栈只保留一个对外端口,其余数据库/MinIO/etcd 的 ports 全部注释掉(它们只需在自己 compose 的内网互通)。
教训 4 —— 改了 .env/config.yaml 必须 --force-recreate,restart 不重读配置。
docker compose restart 用旧环境变量重启,不会加载新的 .env。改配置后必须 docker compose up -d --force-recreate <服务>,并用 docker compose exec <服务> env | grep XXX 亲眼确认变量进了容器。
教训 5 —— Ollama 用原生装(brew),不进 Docker。
Intel Mac 上 Ollama 无论如何都是 CPU(AMD 核显不支持),但原生跑省掉虚拟化开销、不挤占容器内存、用宿主机全部 64GB。且必须设 OLLAMA_HOST=0.0.0.0 让容器能连进来。
1. 宿主机端口分配(最终版)
只暴露必要端口,数据库/MinIO 全留栈内网:
| 端口 | 服务 | 谁访问 |
|---|---|---|
11434 |
Ollama(原生) | LiteLLM |
19530 |
Milvus gRPC | Dify / 你的代码 |
9091 |
Milvus metrics(可选) | 你 |
3000 |
Langfuse Web UI + API | 你 / LiteLLM / Dify |
4000 |
LiteLLM 网关 | Dify / 你的代码 |
80 |
Dify Nginx | 你 |
关键:以下全部不映射宿主机(避免 9000/9091/5432/6379 连环冲突): Milvus 的 MinIO/etcd、Langfuse 的 ClickHouse/Redis/Postgres/MinIO 控制台、LiteLLM 的 Postgres、Dify 的 Postgres/Redis。
2. 前置条件
2.1 Docker Desktop 资源
Settings → Resources:Memory ≥ 24GB(建议 32GB)、CPUs ≥ 6、Disk ≥ 120GB。
2.2 国内镜像加速(拉 Dify 大镜像必备)
Settings → Docker Engine,加:
{
"registry-mirrors": [
"https://docker.1ms.run",
"https://docker.xuanyuan.me",
"https://dockerproxy.net"
]
}
公共加速器时好时坏,失效就换。Dify 的
dify-api镜像 900MB+,直连 Docker Hub 必 TLS 超时。镜像分层有缓存,中途失败反复docker compose up -d断点续传即可。
2.3 拿到宿主机真实 IP(贯穿全程)
ipconfig getifaddr en0 # Wi-Fi;有线试 en1
# 记下这个 IP,本手册后续统一记为 <HOST_IP>,如 192.168.1.23
⚠️ IP 随网络变化。换 Wi-Fi/到公司后 IP 变了,需回来更新所有引用处(见 §9 汇总表)。学习环境可给 Mac 设静态局域网 IP 一劳永逸。
2.4 目录结构
~/Desktop/dockersofts/docker_llm-stack/
├── milvus/ # 官方 standalone compose
├── langfuse/ # git clone 官方仓库
├── litellm/ # 手写:compose + config.yaml
└── dify/docker/ # git clone 官方仓库
# Ollama 原生安装,不在此目录
3. 模型层:Ollama(原生安装)
# 1. 安装并启动
brew install ollama
brew services start ollama
# 2. 关键:让容器能连进来(默认只听 127.0.0.1,容器连不到)
launchctl setenv OLLAMA_HOST "0.0.0.0:11434"
brew services restart ollama
# 3. 拉模型(对话 + 嵌入,Dify RAG 两个都要)
ollama pull qwen2.5:0.5b # 对话
ollama pull nomic-embed-text # 嵌入
# 4. 验证监听在 0.0.0.0(不是 127.0.0.1)
lsof -nP -iTCP:11434 -sTCP:LISTEN # 应显示 *:11434
坑:不设
OLLAMA_HOST=0.0.0.0是后续所有”容器连不上模型”问题的根源之一。原生 Ollama 默认只听 localhost。
4. Milvus(向量库,standalone)
4.1 下载官方 compose
cd ~/Desktop/dockersofts/docker_llm-stack/milvus
curl -L https://github.com/milvus-io/milvus/releases/download/v2.5.6/milvus-standalone-docker-compose.yml \
-o docker-compose.yml
4.2 关键改动:注释掉 MinIO 的宿主机端口映射
编辑 docker-compose.yml,找到 minio 服务,注释掉 ports:
minio:
container_name: milvus-minio
image: minio/minio:RELEASE.2023-03-20T20-16-18Z
environment:
MINIO_ACCESS_KEY: minioadmin
MINIO_SECRET_KEY: minioadmin
# ports: # ← 注释掉:只在容器网络内用 minio:9000,无需暴露
# - "9001:9001" # 否则会和 Langfuse 的 ClickHouse 抢宿主机 9000
# - "9000:9000"
volumes:
- ${DOCKER_VOLUME_DIRECTORY:-.}/volumes/minio:/minio_data
command: minio server /minio_data --console-address ":9001"
# ... healthcheck 不变(其 localhost:9000 是容器内部,不受影响)
坑:这是第一个爆出来的端口冲突。Milvus 的 MinIO 默认占宿主机 9000,Langfuse 的 ClickHouse 原生端口也是 9000,后启动的绑不上。Milvus 的 MinIO 纯内部使用,摘掉映射无害。
注:v2.5.6 默认不开鉴权,standalone 服务无需加
authorizationEnabled。保持默认,Dify 端用空 token 连接即可。
4.3 启动
docker compose up -d
docker compose ps # etcd/minio/standalone 三个 healthy(standalone start_period 90s,耐心等)
curl http://localhost:9091/healthz # 返回 OK
5. Langfuse v3(可观测,官方六容器栈)
5.1 克隆官方仓库
cd ~/Desktop/dockersofts/docker_llm-stack
git clone https://github.com/langfuse/langfuse.git
cd langfuse
5.2 生成密钥(可选但建议)
openssl rand -hex 32 # NEXTAUTH_SECRET
openssl rand -hex 32 # SALT
openssl rand -hex 32 # ENCRYPTION_KEY
填入 docker-compose.yml 里 langfuse-web 对应的 CHANGEME 环境变量。学习环境用默认值也能跑。
5.3 关键改动:注释掉 MinIO 控制台的端口映射
编辑 docker-compose.yml,找到 minio 服务的 ports:
minio:
# ...
ports:
- 9090:9000 # 保留:media 上传/batch export 内部依赖 localhost:9090
# - 127.0.0.1:9091:9001 # 删除:控制台,撞 Milvus 的 9091 metrics,学习用不到
# ...
坑:Langfuse 的 MinIO 控制台默认绑宿主机 9091,和 Milvus 的 metrics 9091 冲突。注意
9090:9000(S3 API)要保留——它被LANGFUSE_S3_MEDIA_UPLOAD_ENDPOINT内部依赖,官方也点名要留。其余 ClickHouse/Redis/Postgres 端口都绑了 127.0.0.1 且已错开,无需动。UTC 铁律:Langfuse 的 ClickHouse/Postgres 必须跑 UTC(官方镜像默认),别改时区,否则查询返回空数据。
5.4 启动并拿密钥
docker compose up -d
docker compose logs -f langfuse-web # 等约 2-3 min 出现 "Ready"
- 打开
http://localhost:3000—— 第一个注册的账号即管理员 - New Organization → New Project
- 项目 → Settings → API Keys → Create —— 记下
pk-lf-*和sk-lf-*(Secret 只显示一次,当场复制)
6. LiteLLM(统一网关 + Langfuse 回调,胶水核心)
6.1 config.yaml
~/Desktop/dockersofts/docker_llm-stack/litellm/config.yaml:
model_list:
- model_name: local-llm # 下游调用时用的名字
litellm_params:
model: openai/qwen2.5:0.5b # 必须和 Ollama 里的名字逐字一致
api_base: http://<HOST_IP>:11434/v1 # 用真实 IP,不用 host.docker.internal
api_key: dummy
- model_name: local-embed
litellm_params:
model: openai/nomic-embed-text:latest # 注意 :latest 后缀要和 Ollama 一致
api_base: http://<HOST_IP>:11434/v1
api_key: dummy
litellm_settings:
success_callback: ["langfuse"]
failure_callback: ["langfuse"]
drop_params: true
坑:模型名必须和
ollama list完全匹配,包括:0.5b、:latest这种 tag。写错会报Invalid model name。openai/前缀保留(告诉 LiteLLM 走 OpenAI 兼容协议)。
6.2 docker-compose.yml
~/Desktop/dockersofts/docker_llm-stack/litellm/docker-compose.yml:
services:
litellm:
image: ghcr.io/berriai/litellm:main-stable
container_name: litellm
ports:
- "4000:4000"
environment:
LITELLM_MASTER_KEY: sk-master-1234 # 下游用它当 api_key
DATABASE_URL: postgresql://litellm:litellm@litellm-db:5432/litellm
LANGFUSE_PUBLIC_KEY: pk-lf-xxxxxxxx # 填 §5 拿到的
LANGFUSE_SECRET_KEY: sk-lf-xxxxxxxx # 填 §5 拿到的
LANGFUSE_HOST: http://<HOST_IP>:3000 # 真实 IP
volumes:
- ./config.yaml:/app/config.yaml
command: ["--config", "/app/config.yaml", "--port", "4000"]
depends_on:
- litellm-db
litellm-db:
image: postgres:16
container_name: litellm-db
environment:
POSTGRES_USER: litellm
POSTGRES_PASSWORD: litellm
POSTGRES_DB: litellm
volumes:
- litellm-pg:/var/lib/postgresql/data
# 注意:litellm-db 不暴露 ports,天然不和其他栈的 5432 冲突
volumes:
litellm-pg:
坑:
command必须带--config /app/config.yaml,否则 LiteLLM 以纯数据库模式启动,/v1/models返回空列表(不报错,但没有你的模型)。改 config 后必须--force-recreate(config 在启动时一次性加载,restart不重读)。
6.3 启动并验证(含 Langfuse 回调)
cd ~/Desktop/dockersofts/docker_llm-stack/litellm
docker compose up -d
# 等约 20 秒迁移完成(首次启动会跑数据库迁移,期间 curl 会 Connection reset,属正常)
# 1. 确认模型加载了
curl http://localhost:4000/v1/models -H "Authorization: Bearer sk-master-1234"
# 应看到 local-llm、local-embed
# 2. 对话测试(同时验证 → Langfuse 回调)
curl http://localhost:4000/v1/chat/completions \
-H "Authorization: Bearer sk-master-1234" \
-H "Content-Type: application/json" \
-d '{"model":"local-llm","messages":[{"role":"user","content":"你好"}]}'
调用成功后回 Langfuse UI 的 Tracing,几秒内应出现这条 trace = 「Ollama → LiteLLM → Langfuse」三段全通。
坑:日志出现
register_model: ... not in built-in cost map的 WARNING 是正常的——只是本地模型没有官方定价,成本算 0,不影响功能,忽略。
7. Dify(应用编排层)
7.1 克隆并复制配置
cd ~/Desktop/dockersofts/docker_llm-stack
git clone https://github.com/langgenius/dify.git
cd dify/docker
cp .env.example .env
7.2 关键改动 A:.env 配置
编辑 .env,改动如下:
# 会话密钥(首次启动前设)
SECRET_KEY=<openssl rand -base64 42 生成的值>
# 向量库:指向独立 Milvus,用真实 IP
VECTOR_STORE=milvus
# COMPOSE_PROFILES 手动写死,去掉自动带入的 milvus,防止 Dify 起自带向量库
COMPOSE_PROFILES=postgresql,collaboration
# 端口(80 被占就改 8080)
EXPOSE_NGINX_PORT=80
# 文件末尾追加 Milvus 连接(这份精简 .env 默认没有这几项)
MILVUS_URI=http://<HOST_IP>:19530
MILVUS_TOKEN=
坑 1:
.env默认最后一行COMPOSE_PROFILES=${VECTOR_STORE:-weaviate},...会自动引用 VECTOR_STORE。一旦设VECTOR_STORE=milvus,它会自动拉起 Dify 捆绑的 milvus 容器,和你的独立 Milvus 重复。必须手动写死为postgresql,collaboration(不含 milvus)。坑 2:
MILVUS_URI用<HOST_IP>真实 IP,不要用host.docker.internal(见教训 2)。
7.3 关键改动 B:给容器加 extra_hosts(可选,但用真实 IP 后可省)
如果坚持用
host.docker.internal,必须给 api / worker / plugin_daemon 三个服务都加extra_hosts: ["host.docker.internal:host-gateway"],否则 worker/plugin_daemon 解析不了(报 exit 2/6),嵌入卡死。本手册推荐直接用真实 IP(§7.2 已采用),则无需改 extra_hosts。 这是更省心的路径。
7.4 启动
docker compose up -d
docker compose ps # 十来个容器,确认【没有】milvus-standalone/etcd/minio(否则 profile 没改干净)
Dify 镜像大,首次拉取慢,参考 §2.2 配加速器 + 断点续传。
api首次启动会跑迁移,可能要 1-2 分钟才 healthy。
7.5 UI 接线:接模型(关键)
访问 http://localhost/install → 第一个账号即管理员。
Settings → Model Provider → OpenAI-API-compatible → 添加两个模型:
对话模型:
| 字段 | 值 |
|——|—–|
| Model Name | local-llm |
| Model Type | LLM |
| API Key | sk-master-1234 |
| API endpoint URL | http://<HOST_IP>:4000/v1 |
| model name for API endpoint | local-llm |
嵌入模型(RAG 必需):
| 字段 | 值 |
|——|—–|
| Model Name | local-embed |
| Model Type | Text Embedding |
| API Key | sk-master-1234 |
| API endpoint URL | http://<HOST_IP>:4000/v1 |
| model name for API endpoint | local-embed |
其余高级开关(Vision/Function Call/Stream 等)全部保持默认 Not Support(0.5B 纯文本模型本就没这些能力)。
坑:endpoint 用
<HOST_IP>真实 IP。用host.docker.internal会报Failed to resolve(Dify 容器解析不了)。两个”模型名”字段:顶部 Model Name = Dify 显示名;底部 model name for API endpoint = 实际发给 LiteLLM 的名字,必须等于 config.yaml 里的
model_name(即local-llm)。
设完把 “Configure a default system model” 里的 System Model 设 local-llm、Embedding Model 设 local-embed。
8. 验证 RAG + Milvus 端到端
8.1 建知识库
左侧 Knowledge → 新建 → 上传文档 → Embedding Model 选 local-embed → Index 选 High Quality → Retrieval 选 Vector Search → Save。
8.2 排查嵌入卡死(如遇)
如果文档卡在”索引中 0%”、docker stats ollama 显示 CPU 0%:
# 确认 worker/plugin_daemon 能连到 LiteLLM(用真实 IP)
docker compose exec worker curl -s -m 10 http://<HOST_IP>:4000/v1/embeddings \
-H "Authorization: Bearer sk-master-1234" \
-H "Content-Type: application/json" \
-d '{"model":"local-embed","input":"测试"}'
# 返回 embedding 数组 = 通;报错/超时 = 网络问题,检查 IP 和 §7.2/§7.3
坑:嵌入请求实际由 plugin_daemon/worker 容器发出(不是 api)。这就是”UI 里添加模型测试通过、真跑嵌入却卡死”的原因——api 能连、worker/plugin_daemon 不能连。用真实 IP 统一解决。改完
docker compose up -d --force-recreate api worker plugin_daemon,再重传文档。
8.3 确认向量落库
pip install pymilvus
python3 -c "
from pymilvus import connections, utility, Collection
connections.connect(host='localhost', port='19530')
cols = utility.list_collections()
print('Collections:', cols)
for c in cols:
col = Collection(c); col.load()
print(f' {c}: {col.num_entities} 条向量')
"
看到 Vector_index_xxx 里有向量 = RAG + 独立 Milvus 端到端打通。
8.4 召回测试
知识库左侧 召回测试 → 输入与文档相关的问题 → 看能否检索出相关片段。能召回 = RAG 检索链路完整工作。
文档状态变绿色”可用” = 全部成功。
9. 接线总表(用真实 IP)
| 发起方 | 目标 | 地址 | 配在哪 |
|---|---|---|---|
| Dify → 模型 | LiteLLM | http://<HOST_IP>:4000/v1 |
Dify UI Model Provider |
| Dify → 向量库 | Milvus | http://<HOST_IP>:19530 |
Dify .env MILVUS_URI |
| LiteLLM → 模型 | Ollama | http://<HOST_IP>:11434/v1 |
LiteLLM config.yaml api_base |
| LiteLLM → 观测 | Langfuse | http://<HOST_IP>:3000 |
LiteLLM env LANGFUSE_HOST |
| 你的代码 → 一切 | LiteLLM 网关 | http://localhost:4000/v1(key=master key) |
你的 SDK |
IP 变更时:改这三处即可 —— Dify
.env(MILVUS_URI)、LiteLLMconfig.yaml(api_base)、Dify UI 里模型的 endpoint。然后--force-recreate对应容器。
10. 完整踩坑速查表
| 症状 | 根因 | 解法 |
|---|---|---|
| vLLM 镜像起不来 | Intel Mac 无 CUDA/Metal | 改 Ollama 原生(§3) |
bind ... 9000 address already in use |
Milvus MinIO 撞 Langfuse ClickHouse | 注释 Milvus MinIO 的 ports(§4.2) |
bind ... 9091 address already in use |
Langfuse MinIO 控制台撞 Milvus metrics | 删 Langfuse MinIO 的 9091 映射,保留 9090(§5.3) |
Connection reset by peer(LiteLLM) |
启动窗口期误打(迁移未完成) | 等 20-30 秒重试 |
/v1/models 返回空 |
config.yaml 没加载 | command 加 --config;--force-recreate(§6.2) |
Invalid model name |
模型名和 Ollama 不一致 | 逐字对齐 tag(:0.5b/:latest) |
register_model ... cost map WARNING |
本地模型无官方定价 | 无害,忽略 |
| Docker Hub 拉镜像 TLS 超时 | 国内直连 Docker Hub | 配镜像加速器 + 断点续传(§2.2) |
| Dify 起了两个 Milvus | COMPOSE_PROFILES 自动带入 milvus | 写死 postgresql,collaboration(§7.2) |
Failed to resolve host.docker.internal |
Dify 容器解析不一致 | 全面改用真实 IP(教训 2) |
worker getent host.docker.internal exit 2/6 |
worker 无 extra_hosts | 用真实 IP,或给三容器加 extra_hosts |
MILVUS_USER is required |
Dify 1.15 校验器要该字段 / .env 没进容器 | 补 MILVUS_USER/PASSWORD 或改用 Dify 自带 Milvus;务必 --force-recreate 验证变量进容器 |
| 嵌入卡 0%、Ollama CPU 0% | plugin_daemon/worker 连不上 LiteLLM | 用真实 IP,--force-recreate 三容器(§8.2) |
| 改配置不生效 | restart 不重读 .env |
用 up -d --force-recreate,env |grep 验证 |
11. 启动顺序与一键脚本
依赖顺序
1. Ollama(brew services) → 2. Milvus → 3. Langfuse
↓ 拿 pk/sk 填进 litellm
4. LiteLLM → 5. Dify
Makefile
~/Desktop/dockersofts/docker_llm-stack/Makefile(recipe 用 Tab 缩进):
.PHONY: up down ps milvus langfuse litellm dify
up: milvus langfuse litellm dify
@echo "✅ 全栈已拉起(Ollama 由 brew services 常驻)"
@echo " Dify:http://localhost Langfuse:http://localhost:3000 LiteLLM:http://localhost:4000"
milvus:
cd milvus && docker compose up -d
langfuse:
cd langfuse && docker compose up -d
litellm:
cd litellm && docker compose up -d
dify:
cd dify/docker && docker compose up -d
down:
-cd dify/docker && docker compose down
-cd litellm && docker compose down
-cd langfuse && docker compose down
-cd milvus && docker compose down
ps:
@for d in milvus langfuse litellm dify/docker; do echo "── $$d ──"; (cd $$d && docker compose ps); done
日常:make up 起全栈 / make ps 看状态 / make down 停全栈。Ollama 单独用 brew services start/stop ollama 管。
12. 冒烟测试清单
# 1 Ollama
curl http://localhost:11434/api/tags
# 2 Milvus
curl http://localhost:9091/healthz
# 3 Langfuse
open http://localhost:3000
# 4 LiteLLM(含 Langfuse 回调)
curl http://localhost:4000/v1/chat/completions \
-H "Authorization: Bearer sk-master-1234" -H "Content-Type: application/json" \
-d '{"model":"local-llm","messages":[{"role":"user","content":"ping"}]}'
# 5 Dify
open http://localhost/install
全绿 = Dify 发起对话 → LiteLLM 路由到 Ollama → RAG 命中 Milvus → 全链路 trace 落在 Langfuse。
13. 后续学习方向
- RAG 调优:分段长度、Top-K、混合检索(向量+全文)、rerank
- Chatbot 挂知识库:Studio 建 Chatbot,接入知识库,做文档问答机器人
- Langfuse 观测:给 Chatbot 开 Langfuse(App 的 Monitoring 里填 pk/sk/Host),看完整 RAG trace
- 工作流编排:Dify Workflow,把检索/判断/多步调用串起来
附:设计取舍说明
- 为什么用真实 IP 而非 host.docker.internal:Dify 各容器(api/worker/plugin_daemon)对
host.docker.internal的解析在本环境不一致,导致最隐蔽的”嵌入卡死”坑。真实 IP 对所有容器一致可达,是踩遍坑后的最稳方案。代价是 IP 变更需更新(§9)。 - 为什么 Langfuse/Dify/Milvus 用官方 compose:这些栈迭代快,官方 compose 是持续维护的正确源,手写副本迅速过时。你真正拥有和维护的只有 LiteLLM 那份胶水配置 + 各
.env的少量改动。 - 为什么 Ollama 而非 vLLM:学习目标是中台编排,模型层越省心越好;vLLM 的价值在 GPU 高并发吞吐,Intel Mac CPU 场景完全体现不出,反而徒增编译折腾。vLLM 值得学,但换个场景(租 GPU)学。
- 为什么用独立 Milvus 而非 Dify 自带:便于未来多个应用(Dify + 你的 pymilvus 代码)共享一个向量库。若只跑 Dify,也可让 Dify 用自带 Milvus(
COMPOSE_PROFILES加milvus+MILVUS_URI=http://milvus-standalone:19530)更省事。
评论:
技术文章推送
手机、电脑实用软件分享
微信公众号:AndrewYG的算法世界