MoreRSS

site iconGamea | 飘逝的风修改

游戏后台开发十多年, 目前就职于Tencent。
请复制 RSS 到你的阅读器,或快速订阅到 :

Inoreader Feedly Follow Feedbin Local Reader

Gamea | 飘逝的风的 RSS 预览

给内网穿透FRP套上坚固的盾牌

2025-05-10 08:00:00

Featured image of post 给内网穿透FRP套上坚固的盾牌

背景

有一天,我突然发现无法从外部连接家里的NAS了。我开始慌了,预感到不妙。莫非是公网IP被运营商回收了?我也成为一个大内网用户了。所幸已经有不少成熟的方案,而FRP就是其中之一。它开源免费,易上手,并且支持的协议还比较多(当然,部署服务器的费用得另算)。晚上回到家,我决定面对现实,好好折腾一番。虽然网上现有的FRP教程多数只完成了‘能用’的第一步,但距离‘好用易用’还有点距离。

本文简要描述一下我使用FRP的过程,并且看一下我们如何给FRP套上坚固的盾牌,配上易用的武器。我假定你已经知道FRP是什么,并且最基本的FRP使用已经了解。不了解也没关系,继续看你也大概能懂。

虽然咱数据或许对别人而言也没那么重要,但自我保护意识也不可松懈。

目标制定

frp 是一个专注于内网穿透的高性能的反向代理应用,支持 TCP、UDP、HTTP、HTTPS 等多种协议,且支持 P2P 通信。可以将内网服务以安全、便捷的方式通过具有公网 IP 节点的中转暴露到公网。

为了方便迁移和管理,在使用FRP时我首推容器化方案。不过似乎没看到官方的镜像,但dockerhub上有一个社区广泛使用且下载量很高的镜像,大抵错不了:snowdreamtech/frpcsnowdreamtech/frps

FRP是分客户端和服务端的,需要在不同的机器分别配置。frpc一般部署在内网,用于将内部需要对外暴露的服务定义出来。而frps一般部署在有公网IP的服务器上,用于接收外部连接并转发到内部服务。这里有几个安全事项需要关注:

  1. 内网frpc和公网frps之间需要建立安全的连接。
  2. 公网frps暴露的端口需要进一步限制连接来源。
  3. 公网frps暴露的端口仅在必要时开放。

很多分享的方案里基本不启用TLS,也对暴露的端口没有进一步的限制,这其实是不安全的。秉承这些目标,我们开始行动吧。

部署服务

内网部署frpc端

这里我直接使用docker-compose部署,并使用snowdreamtech/frpc镜像。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
version: '3.8'
services:
 frpc:
 image: snowdreamtech/frpc:debian
 container_name: frpc
 restart: always
 network_mode: host
 volumes:
 - ./frpc.toml:/etc/frp/frpc.toml
 - ./client.crt:/etc/frp/client.crt
 - ./client.key:/etc/frp/client.key
 - ./ca.crt:/etc/frp/ca.crt
 environment:
 - TZ=Asia/Shanghai
 env_file:
 - ./.env

这里需要的TLS证书一会我们再生成。这里的.env文件内容如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# 服务端连接信息
FRPC_SERVER_ADDR=<your-server-address>
FRPC_SERVER_PORT=<your-server-port>

# 服务器域名
FRPC_SERVER_NAME="<your-domain-for-tls>"

# 认证令牌 - 必须与服务端一致
FRPC_AUTH_TOKEN=<your-auth-token>

# 客户端仪表盘配置
FRPC_DASHBOARD_USER=admin
FRPC_DASHBOARD_PWD=admin

为了方便修改和对齐,我们将frpc.toml文件中的一部分配置放在.env文件中定义。而frpc.toml文件内容如下:

 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
user = "kevin"

serverAddr = "{{ .Envs.FRPC_SERVER_ADDR }}"
serverPort = {{ .Envs.FRPC_SERVER_PORT }}

loginFailExit = true

log.to = "./frpc.log"
log.level = "trace"
log.maxDays = 3
log.disablePrintColor = false

auth.method = "token"
auth.token = "{{ if .Envs.FRPC_AUTH_TOKEN }}{{ .Envs.FRPC_AUTH_TOKEN }}{{ else }}{{ .Envs.FRP_AUTH_TOKEN }}{{ end }}"

transport.poolCount = 5
transport.protocol = "tcp"
transport.connectServerLocalIP = "0.0.0.0"

transport.tls.enable = true
transport.tls.certFile = "/etc/frp/client.crt"
transport.tls.keyFile = "/etc/frp/client.key"
transport.tls.trustedCaFile = "/etc/frp/ca.crt"
transport.tls.serverName = "{{ .Envs.FRPC_SERVER_NAME }}"

udpPacketSize = 1500

webServer.addr = "127.0.0.1"
webServer.port = 7400
webServer.user = "{{ .Envs.FRPC_DASHBOARD_USER }}"
webServer.password = "{{ .Envs.FRPC_DASHBOARD_PWD }}"

[[proxies]]
name = "router-web"
type = "tcp"
localIP = "192.168.1.1"
localPort = 80
remotePort = 17603

[[proxies]]
name = "external-http"
type = "tcp"
localIP = "192.168.50.96"
localPort = 8080
remotePort = 9443

公网部署frps端

我们的服务端一般是部署在一台有公网IP的服务器上,用于我们从任何地方通过它连接回家里内网。这个服务器可以从国内国外各种云上买一台或者找机会白嫖一台。我是在腾讯云上有一台机器,安装好docker以及docker-compose后,使用snowdreamtech/frps镜像部署。部署在公网的服务,基于安全性考虑,我们希望即使frps被攻击,其它服务也是安全的,所以除了放在容器中,把网络也隔离出来。这里便不再使用network_mode: host,而是使用默认的network_mode: bridge,同时预留一些端口用于后续我们的服务暴露。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
version: '3.8'
services:
 frps:
 image: snowdreamtech/frps:debian
 container_name: frps
 restart: always
 ports:
 - "9443:9443"
 - "17600-17610:17600-17610" # TCP/UDP 代理端口范围 (allowPorts),视你需要开放一些端口
 volumes:
 - ./frps.toml:/etc/frp/frps.toml
 - ./server.crt:/etc/frp/server.crt
 - ./server.key:/etc/frp/server.key
 - ./ca.crt:/etc/frp/ca.crt
 environment:
 - TZ=Asia/Shanghai
 env_file:
 - ./.env

同样的,为了配置修改方便,我们将frps.toml文件中的一部分配置放在.env文件中定义:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# 仪表盘访问凭证
FRPS_DASHBOARD_USER=<your-dashboard-username>
FRPS_DASHBOARD_PWD=<your-dashboard-password>

# 认证令牌 - 必须与客户端一致
FRPS_AUTH_TOKEN=<your-auth-token>

# 绑定地址和端口配置
FRPS_BIND_ADDR=0.0.0.0
FRPS_BIND_PORT=17600
FRPS_KCP_BIND_PORT=17600
FRPS_DASHBOARD_PORT=17601

而frps.toml文件内容如下:

 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
bindAddr = "{{ .Envs.FRPS_BIND_ADDR }}"
bindPort = {{ .Envs.FRPS_BIND_PORT }}

kcpBindPort = {{ .Envs.FRPS_KCP_BIND_PORT }}

transport.maxPoolCount = 5
transport.tls.force = true
transport.tls.certFile = "/etc/frp/server.crt"
transport.tls.keyFile = "/etc/frp/server.key"
transport.tls.trustedCaFile = "/etc/frp/ca.crt"

webServer.addr = "0.0.0.0"
webServer.port = {{ .Envs.FRPS_DASHBOARD_PORT }}
webServer.user = "{{ .Envs.FRPS_DASHBOARD_USER }}"
webServer.password = "{{ .Envs.FRPS_DASHBOARD_PWD }}"
webServer.pprofEnable = false

# 开放的端口范围,这里可以配置适大一些,更多的映射(限制)在docker-compose中
allowPorts = [
 { start = 17000, end = 17999 },
 { single = 9443 }
]


enablePrometheus = true

log.to = "./frps.log"
log.level = "trace"
log.maxDays = 3
log.disablePrintColor = false

detailedErrorsToClient = true

auth.method = "token"
auth.token = "{{ if .Envs.FRPS_AUTH_TOKEN }}{{ .Envs.FRPS_AUTH_TOKEN }}{{ else }}{{ .Envs.FRP_AUTH_TOKEN }}{{ end }}"
auth.oidc.issuer = ""
auth.oidc.audience = ""
auth.oidc.skipExpiryCheck = false
auth.oidc.skipIssuerCheck = false

maxPortsPerClient = 0
udpPacketSize = 1500
natholeAnalysisDataReserveHours = 168

生成TLS证书

为了让FRP的连接更安全,我们使用TLS证书来加密连接。它确保我们的frpc和frps之间的连接是安全的,并且防止中间人攻击。我们使用自签名证书来生成TLS证书。以下提供了一段脚本,方便一键生成我们需要的证书。

 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
#!/bin/bash
# 脚本用于生成 FRP 配置所需的自签名证书

set -e # 任何命令失败立即退出

echo "开始生成 FRP 通信证书..."

# 设置默认值,但要求用户至少提供一个
SERVER_DOMAIN=""
SERVER_IP=""

# 仅重新生成服务器证书的选项
REGENERATE_SERVER_ONLY=false

# 显示帮助信息
show_help() {
 echo "用法: $0 [选项]"
 echo ""
 echo "选项:"
 echo " --server-only 仅重新生成服务器证书,保留现有CA证书"
 echo " --domain=<域名> 指定服务器域名"
 echo " --ip=<IP地址> 指定服务器IP地址"
 echo " --help 显示此帮助信息"
 echo ""
 echo "至少需要指定域名或IP地址中的一个"
 exit 1
}

# 解析命令行参数
while [[ $# -gt 0 ]]; do
 case $1 in
 --server-only)
 REGENERATE_SERVER_ONLY=true
 shift
 ;;
 --domain=*)
 SERVER_DOMAIN="${1#*=}"
 shift
 ;;
 --ip=*)
 SERVER_IP="${1#*=}"
 shift
 ;;
 --help)
 show_help
 ;;
 *)
 echo "未知选项: $1"
 show_help
 ;;
 esac
done

# 检查是否提供了至少一个参数
if [ -z "$SERVER_DOMAIN" ] && [ -z "$SERVER_IP" ]; then
 echo "错误: 必须至少指定域名(--domain)或IP地址(--ip)中的一个"
 show_help
fi

# 显示配置信息
echo "证书配置:"
if [ -n "$SERVER_DOMAIN" ]; then
 echo "- 域名: $SERVER_DOMAIN"
fi
if [ -n "$SERVER_IP" ]; then
 echo "- IP地址: $SERVER_IP"
fi

if [ "$REGENERATE_SERVER_ONLY" = false ]; then
 # 1. 生成 CA 根证书
 echo "生成 CA 根证书..."
 openssl genrsa -out ca.key 4096
 openssl req -x509 -new -nodes -key ca.key -sha256 -days 3650 -out ca.crt \
 -subj "/C=CN/ST=Beijing/L=Beijing/O=FRP-Private/OU=DevOps/CN=frp-ca"
else
 echo "跳过 CA 证书生成,使用现有 CA 证书..."
 # 检查CA证书是否存在
 if [ ! -f "ca.key" ] || [ ! -f "ca.crt" ]; then
 echo "错误: CA证书文件不存在。请先运行不带 --server-only 参数的脚本生成完整证书集。"
 exit 1
 fi
fi

# 2. 生成服务端证书
echo "生成服务端证书..."
openssl genrsa -out frps/server.key 4096

# 设置默认CN
CN_VALUE="${SERVER_DOMAIN}"
if [ -z "$CN_VALUE" ]; then
 CN_VALUE="frps"
fi

# 创建OpenSSL配置文件
cat > frps/openssl.cnf << EOF
[ req ]
default_bits = 4096
prompt = no
default_md = sha256
req_extensions = req_ext
distinguished_name = dn

[ dn ]
C = CN
ST = Beijing
L = Beijing
O = FRP-Private
OU = DevOps
CN = ${CN_VALUE}

[ req_ext ]
subjectAltName = @alt_names

[ alt_names ]
EOF

# 动态添加DNS和IP到配置文件
DNS_COUNT=1
if [ -n "$SERVER_DOMAIN" ]; then
 echo "DNS.${DNS_COUNT} = ${SERVER_DOMAIN}" >> frps/openssl.cnf
 DNS_COUNT=$((DNS_COUNT+1))
fi
echo "DNS.${DNS_COUNT} = localhost" >> frps/openssl.cnf

IP_COUNT=1
if [ -n "$SERVER_IP" ]; then
 echo "IP.${IP_COUNT} = ${SERVER_IP}" >> frps/openssl.cnf
 IP_COUNT=$((IP_COUNT+1))
fi
echo "IP.${IP_COUNT} = 127.0.0.1" >> frps/openssl.cnf

# 使用配置文件生成CSR
openssl req -new -key frps/server.key -out frps/server.csr -config frps/openssl.cnf

# 创建扩展配置文件 - 正确的格式
cat > frps/v3.ext << EOF
subjectAltName = @alt_names
[alt_names]
EOF

# 添加DNS和IP到扩展配置
if [ -n "$SERVER_DOMAIN" ]; then
 echo "DNS.1 = ${SERVER_DOMAIN}" >> frps/v3.ext
 echo "DNS.2 = localhost" >> frps/v3.ext
else
 echo "DNS.1 = localhost" >> frps/v3.ext
fi

if [ -n "$SERVER_IP" ]; then
 echo "IP.1 = ${SERVER_IP}" >> frps/v3.ext
 echo "IP.2 = 127.0.0.1" >> frps/v3.ext
else
 echo "IP.1 = 127.0.0.1" >> frps/v3.ext
fi

# 签署证书,应用SAN扩展
openssl x509 -req -in frps/server.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
 -out frps/server.crt -days 3650 -sha256 -extfile frps/v3.ext

if [ "$REGENERATE_SERVER_ONLY" = false ]; then
 # 3. 生成客户端证书
 echo "生成客户端证书..."
 openssl genrsa -out frpc/client.key 4096
 openssl req -new -key frpc/client.key -out frpc/client.csr \
 -subj "/C=CN/ST=Beijing/L=Beijing/O=FRP-Private/OU=DevOps/CN=frpc"
 openssl x509 -req -in frpc/client.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
 -out frpc/client.crt -days 3650 -sha256

 # 4. 分发 CA 证书到 frps 和 frpc 目录
 echo "分发 CA 证书到各个目录..."
 cp ca.crt frps/ca.crt
 cp ca.crt frpc/ca.crt
else
 echo "跳过客户端证书生成,仅更新服务器证书..."
fi

# 5. 清理中间文件
echo "清理临时文件..."
rm -f frps/server.csr ca.srl frps/openssl.cnf frps/v3.ext
if [ "$REGENERATE_SERVER_ONLY" = false ]; then
 rm -f frpc/client.csr
fi

echo "设置文件权限..."
chmod 600 ca.key frps/server.key
if [ "$REGENERATE_SERVER_ONLY" = false ]; then
 chmod 600 frpc/client.key
fi

echo "证书生成完成!"
if [ "$REGENERATE_SERVER_ONLY" = true ]; then
 echo "已使用现有CA证书重新生成服务器证书"
else
 echo "注意:ca.key 是敏感文件,请妥善保管,建议不要上传到代码仓库。"
fi

# 输出证书信息和提示
echo ""
echo "===== 证书信息 ====="
if [ -n "$SERVER_DOMAIN" ]; then
 echo "服务器证书包含域名: ${SERVER_DOMAIN}"
 echo "如果使用域名连接,请在frpc.toml中设置: transport.tls.serverName = \"${SERVER_DOMAIN}\""
fi
if [ -n "$SERVER_IP" ]; then
 echo "服务器证书包含IP地址: ${SERVER_IP}"
fi

# 提示下一步操作
echo ""
echo "===== 下一步操作 ====="
echo "1. 复制证书文件到相应位置"
echo "2. 更新frpc.toml中的服务器地址和相关配置"
echo "3. 使用docker-compose restart重启服务"

# 验证证书内容
echo ""
echo "===== 验证证书 ====="
echo "查看证书内容(包括SAN扩展):"
openssl x509 -in frps/server.crt -text -noout | grep -A1 "Subject Alternative Name"

使用方式:

1
./generate-frp-certs.sh --domain=<your-domain> --ip=<your-ip>

这样我们的服务器与客户端双向认证,谁都不可被冒充。

安全加固

到这里,你的FRP连接已经通过TLS和双向认证得到了很好的保护,即使token不慎泄露,没有匹配的证书也无法建立连接。接下来,我们在此基础上更进一步,结合云主机的防火墙(安全组)策略,实现更精细的访问控制。我们希望达成:

  • 仅允许必要的端口放通
  • 仅允许必要的IP连接

相信很多人都明白这个道理,但手动操作实在繁琐,所以我们需要一个工具来帮忙。在我看来,最便捷的莫过于再次祭出alfred workflow来实现。于是我抽空写了一个,并开源在github上,感兴趣的可以看这里:alfred-workflow-sg-manager

我们基于frpc.toml中的一部分配置[[proxies]],来动态开启与关闭相关的端口。一点点前置工作还需要你做的,便是将你的服务器绑定一个安全组,至于安全组后续规则添加维护等就交给这个工具了。 比如先查看一下当前列表: frp list 你上面可以看到这些内网服务还未开放,我们选择一个,比如想从外部访问家里的Mac mini了,我们在alfred框中输入frp open可以看到可开放的列表: frp open 选中Mac mini,然后回车,这个通路便打开了。再次查看可以看到它开放并且绑定了本机的出口IP: frp list

现在你可以开心的从外部VNC到你家内网的Mac mini了,并且安全得多了。 用过之后想关闭的一些端口,比如Mac mini的VNC端口,我们输入frp close,然后选择对应的要关闭的服务回车即可: frp close

现在咱是不是对于FRP的安全使用更有信心了。有些人可能会说:何苦呢,有谁会看中咱攻击咱呢?或许可能Maybe:我们就是控制欲作祟而已:)

一些技巧

还有两个小窍门我也想让你知道,看在这么认真的份上小手点点赞不过份吧!

第一:如上面截图出现了external-http,我们可以借助于将内网服务统一在一个ingress服务(反向代理)下,然后通过这个ingress服务进一步路由,这样我们只需要穿透一个端口即可访问内网的各种服务,免去了配置FRP的手续。也通过让ingress服务走TLS,或者后端服务对接OAuth2等更安全的认证方式,可以进一步保护我们的内网服务。

第二:FRPS除了默认有Dashboard外,还支持Prometheus。我们可以复用这种生态。Prometheus用于收集监控数据,Grafana用于可视化数据和配置告警。比如我们可监控FRP的连接访问情况,并且添加告警,这样某个敏感服务有连接,我们便可以及时收到通知。

后记

本文整体到这里就结束了。我们尝试用docker来部署了FRP的客户端和服务端,并且基于安全的考虑,我们启用了TLS和创建了一个便捷的工具来快速修改云上的安全策略。这犹如一块坚固的盾牌,避免我们可能受到的攻击。

当我折腾好FRP,并且安全地将它保护起来后。有一天,我查看我家的外网IP,发现它居然是一个公网IP。我的天啦,我这折腾一番可是为了啥!我要不要重新回归到公网IP的路线呢?可是我却放不下这份安全了呢。

注:题图来自于互联网,我觉得画得挺棒,若有侵权请联系我删除。

我是个爱折腾技术的工程师,也乐于分享。欢迎点赞、关注、分享,更欢迎一起探讨技术问题,共同学习,共同进步。为了获得更及时的文章推送,欢迎关注我的公众号:爱折腾的风

扫码关注公众号

让你的小智AI机器人起飞

2025-05-04 08:00:00

Featured image of post 让你的小智AI机器人起飞

前阵子B站刷到小智AI机器人,入手了一个玩玩,官方的服务器显然不足以满足我的幻想,于是搭建了第三方服务器,顺便给它功能扩展了一下,到底做了点啥呢,似乎有一点意思了。

上手初体验

这个机器人似乎前阵子比较火,有不少硬件形态,有比较小巧可爱的,也有硬核直接的,但内核是不变的,如果你从来没玩过,好像长什么样,可以B站看看或淘宝啥的搜索一下。我家的比较抽象,长得像这样: 小智AI机器人

跟着电路图或者引脚说明接线,这步不是有手就行,还得有力气:)因为ESP32的针脚和面包板孔不完全匹配,需要用力强行按进去。看过B站有个哥们用锤子敲,也真是乐坏我了。供电的话我们可以随便拿个充电宝,倒也是很容易搞定、也有一定移动性。

最开始体验时,参考官方的DIY教程将设备刷好官方固件后,整体响应较快,对话流畅,不过模型数量有限,声音复刻收费,特别是如果这个不能联网做点啥,光和模型过时的数据聊天略有点无聊啊。结合当前较火的MCP功能,于是有了两个扩展目标:

  • 复刻个人的声音让AI用我的语气说话
  • 扩展MCP功能,让AI具备连接更多外界能力

消耗了一点闲暇时间,目前已经跑起来咯,下面简要分享一下过程。如果你也想折腾或者有好点子,欢迎一起交流。

部署专属服务器

小智官方并没有把服务器开源,但提供了交互协议等,有第三方开源服务器实现 xiaozhi-esp32-server,可供使用。要完成可以使用第三方服务器,我们做两件事:

  1. 将服务部署到自己的机器上。
  2. 修改小智的固件的两个地址,重新编译并烧录到设备上。

其实都很简单,我们需要动脑的机会不多,像我这样一步步来即可。

部署服务器

参考xiaozhi-esp32-server的Docker运行全模块教程。我推荐使用docker部署,主要是为了保持本地的干净。

准备配置文件.config.yaml,在这个服务器中,使用了多层config来互相覆盖,我们定义的.config.yaml会覆盖默认的config.yaml。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# 在开发中,请在项目根目录创建data目录,然后在data目录创建名称为【.config.yaml】的空文件
# 然后你想修改覆盖修改什么配置,就修改【.config.yaml】文件,而不是修改【config.yaml】文件
# 系统会优先读取【data/.config.yaml】文件的配置,如果【.config.yaml】文件里的配置不存在,系统会自动去读取【config.yaml】文件的配置。
# 这样做,可以最简化配置,保护您的密钥安全。
read_config_from_api: True

manager-api:
 # 你的manager-api的地址,最好使用局域网ip
 url: http://xiaozhi-esp32-server-web:8002/xiaozhi
 # 你的manager-api的token,就是刚才复制出来的server.secret
 secret: 8e0ee7db-3928-400f-9e93-261275f91cf0

# #####################################################################################
# #############################以下是服务器基本运行配置####################################
server:
 # 服务器监听地址和端口(Server listening address and port)
 ip: 0.0.0.0
 port: 8000

这里的配置不用太多,因为它的配置还有一层可以动态通过manager-api来下发更新,所以这里我们只需要配置一个manager-api的地址和secret即可。

然后我们使用docker-compose部署,在此之前你需要自行下载语音识别模型文件到models/SenseVoiceSmall/model.pt,这些细节上面链接都有提,不啰嗦了,最终yaml如下:

 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
# Docker安装全模块
services:
 # Server模块
 xiaozhi-esp32-server:
 image: ghcr.nju.edu.cn/xinnan-tech/xiaozhi-esp32-server:server_latest
 container_name: xiaozhi-esp32-server
 depends_on:
 - xiaozhi-esp32-server-db
 - xiaozhi-esp32-server-redis
 restart: always
 networks:
 - default
 ports:
 # ws服务端
 - "8000:8000"
 security_opt:
 - seccomp:unconfined
 environment:
 - TZ=Asia/Shanghai
 volumes:
 # 配置文件目录
 - ./data:/opt/xiaozhi-esp32-server/data
 # 模型文件挂接,很重要
 - ./models/SenseVoiceSmall/model.pt:/opt/xiaozhi-esp32-server/models/SenseVoiceSmall/model.pt

 # manager-api和manager-web模块
 xiaozhi-esp32-server-web:
 image: ghcr.nju.edu.cn/xinnan-tech/xiaozhi-esp32-server:web_latest
 container_name: xiaozhi-esp32-server-web
 restart: always
 networks:
 - default
 depends_on:
 xiaozhi-esp32-server-db:
 condition: service_healthy
 xiaozhi-esp32-server-redis:
 condition: service_healthy
 ports:
 # 智控台
 - "8002:8002"
 environment:
 - TZ=Asia/Shanghai
 - SPRING_DATASOURCE_DRUID_URL=jdbc:mysql://xiaozhi-esp32-server-db:3306/xiaozhi_esp32_server?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true&connectTimeout=30000&socketTimeout=30000&autoReconnect=true&failOverReadOnly=false&maxReconnects=10
 - SPRING_DATASOURCE_DRUID_USERNAME=root
 - SPRING_DATASOURCE_DRUID_PASSWORD=123456
 - SPRING_DATA_REDIS_HOST=xiaozhi-esp32-server-redis
 - SPRING_DATA_REDIS_PORT=6379
 xiaozhi-esp32-server-db:
 # image: mysql/mysql-server:latest
 image: mysql:latest
 container_name: xiaozhi-esp32-server-db
 healthcheck:
 test: [ "CMD", "mysqladmin" ,"ping", "-h", "localhost" ]
 timeout: 45s
 interval: 10s
 retries: 10
 restart: always
 networks:
 - default
 ports:
 - "3306:3306"
 volumes:
 - ./mysql/data:/var/lib/mysql
 environment:
 - TZ=Asia/Shanghai
 - MYSQL_ROOT_PASSWORD=123456
 - MYSQL_DATABASE=xiaozhi_esp32_server
 - MYSQL_INITDB_ARGS="--character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci"
 xiaozhi-esp32-server-redis:
 image: redis
 expose:
 - 6379
 container_name: xiaozhi-esp32-server-redis
 healthcheck:
 test: ["CMD", "redis-cli", "ping"]
 interval: 10s
 timeout: 5s
 retries: 3
 networks:
 - default
networks:
 default:

使用命令启动:

1
docker-compose up -d

等服务起来了之后,我们可以使用仓库自带的test页面测试,浏览器打开main/xiaozhi-server/test/test_page.html,可以看到如下页面: 测试页面 你最好是在这个网页测试通过,聊天、语音等都符合你的预期,再进行下一步。话说这个页面也是调试利器了,效率高不少。

这里还有个小技巧,因为我们接下来需要将ota/websocket地址烧录在固件上,如果你不确定服务未来会部署在哪台机器上,可以通过一个反向代理来转发。给个示意了解一下即可:

 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
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
 name: to-xiaozhi-web
spec:
 entryPoints:
 - websecure
 routes:
 - match: Host(`web-xiaozhi.mrlin.cc`)
 kind: Rule
 services:
 - name: web-xiaozhi
 port: 80
---
apiVersion: v1
kind: Service
metadata:
 name: web-xiaozhi
spec:
 ports:
 - protocol: TCP
 port: 80
 targetPort: 8002
---
apiVersion: v1
kind: Endpoints
metadata:
 name: web-xiaozhi
subsets:
 - addresses:
 - ip: 192.168.50.190
 ports:
 - port: 8002
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
 name: to-xiaozhi-api
spec:
 entryPoints:
 - websecure
 routes:
 - match: Host(`api-xiaozhi.mrlin.cc`)
 kind: Rule
 services:
 - name: api-xiaozhi
 port: 80
---
apiVersion: v1
kind: Service
metadata:
 name: api-xiaozhi
spec:
 ports:
 - protocol: TCP
 port: 80
 targetPort: 8000
---
apiVersion: v1
kind: Endpoints
metadata:
 name: api-xiaozhi
subsets:
 - addresses:
 - ip: 192.168.50.190
 ports:
 - port: 8000

上面我将api-xiaozhiweb-xiaozhi都指向了我的本地IP,这样我就可以通过api-xiaozhi.mrlin.ccweb-xiaozhi.mrlin.cc来访问了,未来迁移到其他机器上,只需要修改这个yaml文件即可。你不搞这一步一点问题也没有,继续用IP,注意那样就使用http/ws协议。

修改固件&编译&烧录

这里我们要进入到官方固件仓库来继续我们的操作。并且编译烧录等需要你在Windows下进行了。 我们可以参考这篇文章Windows搭建 ESP IDF 5.3.3开发环境以及编译小智大概了解整个过程;接着看这里简要而关键的固件构建过程。

简单来说,当前最新版本我们仅需要修改OTA_URL地址即可,以往要修改websocket地址,现在它已经通过OTA下发了。

当天我正在照着教程编译,发现不找到websocket的定义了,定睛一看刚好有提交重构了这一块,于是顺便向server那边也提了个PR,居然很快就被合并了。不过当时字段名取得不太好,我延用了固件那边的websocket_url,后面又被修改为server.websocket,这确实更符合项目的命名规范一些。

之后构建就几个命令:

1
2
3
4
5
6
# 终端命令行进入xiaozhi-esp32的根目录
cd xiaozhi-esp32
# 例如我使用的板子是esp32s3,所以设置编译目标为esp32s3,如果你的板子是其他型号,请替换成对应的型号
idf.py set-target esp32s3
# 进入菜单配置
idf.py menuconfig

之后是烧录,将你的小智AI设备通过串口连接到电脑,然后使用idf.py build构建,接着使用idf.py -p PORT flash烧录。 这里的PORT根据你连接的设备不同,可能有所不同,在电脑中我的设备查看串口,我的是COM4。如果嫌烧录慢,可以添加参数-b 2000000来加速。

一切成功后,同时也在上一步骤的服务器的server.websocket中填入wss://api-xiaozhi.mrlin.cc/xiaozhi/v1/,然后就可以打开设备连接自己的服务器开玩了。 server websocket修改 现在你可能发现有了更多的LLM选择,有了更多的TTS(声音)等,似乎是更开放了,或许可以玩得更嗨了。

个人声音复刻

之前在玩火山引擎时,送了几次声音复刻给我,体验了一下,当我想用在小智上时,发现已经过期了,居然只有十天有效期,这也太抠了。我去腾讯云找了一下,也有免费的额度,申请下来有3个月,那么就玩玩吧。不像火山引擎的声音复刻在网页上提交声音即可,腾讯云的复刻要原始得多,需要自己使用API,所幸相关功能也在它的tccli中,那么也就是敲命令的事,也不可能难倒我们。

命令行体验

我们可以参考文档声音复刻相关接口,使用命令行来把过程体验一下。至于如何安装tccli,可以参考腾讯云命令行工具

1. 获取训练文本

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
❯ tccli vrs GetTrainingText --TaskType 5 --Domain 1
{
 "Data": {
 "TrainingTextList": [
 {
 "TextId": "5f3ef960-4f11-4f4b-84e6-514692ebe0c5",
 "Text": "亲 默认是发韵达快递哦 您这边可以收到韵达的货吗 "
 }
 ]
 },
 "RequestId": "8c758bac-19fc-474d-9b12-2e813e834102"
}

这一步要我们根据提供的文本来提供一段音频,我盲猜是不是要规避用别人的声音来复刻呢?

2. 环境检测和音频质量检测

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
❯ tccli vrs DetectEnvAndSoundQuality --cli-unfold-argument --TextId "5f3ef960-4f11-4f4b-84e6-514692ebe0c5" --AudioData "$(base64 -i vrs.m4a | tr -d '\n')" --TypeId 2 --Codec m4a --TaskType 5


{
 "Data": {
 "AudioId": "audio0de472d6d96d488ba2159d83c10b440f",
 "DetectionCode": 0,
 "DetectionMsg": "Success",
 "DetectionTip": []
 },
 "RequestId": "6218aa6d-9d45-492c-9c18-dfa342d3bd5e"
}

这一步看名称感觉多余,但是下一步创建任务需要这个AudioId,所以还是得来一下。

3. 创建声音复刻任务

1
2
3
4
5
6
7
❯ tccli vrs CreateVRSTask --SessionId 1234 --VoiceName kevin --VoiceGender 1 --VoiceLanguage 1 --AudioIdList "[\"audio0de472d6d96d488ba2159d83c10b440f\"]" --TaskType 5
{
 "Data": {
 "TaskId": "fast7feffdeb04ca401583a317002dd14e7a"
 },
 "RequestId": "127ad8c6-a27d-470f-8dab-b9940c9594cb"
}

我们得到了一个任务ID。原因或许是这一步是异步的,服务器训练这个声音也需要时间。

4. 查询声音复刻任务结果

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
❯ tccli vrs DescribeVRSTaskStatus --TaskId fast7feffdeb04ca401583a317002dd14e7a
{
 "Data": {
 "TaskId": "fast7feffdeb04ca401583a317002dd14e7a",
 "Status": 2,
 "StatusStr": "Success",
 "VoiceType": 200000000,
 "ErrorMsg": "",
 "ExpireTime": "2025-08-02T00:00:00+08:00",
 "FastVoiceType": "WCHN-869b22949cbe4db6a344f1da8cf8a68c"
 },
 "RequestId": "3eb52b2d-3fa1-42f5-84f0-abf71bef32e2"
}

查到的结果里有几个比较关键的,VoiceTypeFastVoiceType,前者是音色ID,后者是快速复刻时使用的音色ID,请用小本本记录下来备用。

5. 查询复刻音色信息

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
❯ tccli vrs GetVRSVoiceTypeInfo --VoiceType 200000000 --TaskType 5 --FastVoiceType WCHN-869b22949cbe4db6a344f1da8cf8a68c
{
 "Data": {
 "VoiceType": 200000000,
 "VoiceName": "kevin",
 "VoiceGender": 1,
 "TaskType": 5,
 "TaskID": "fast7feffdeb04ca401583a317002dd14e7a",
 "DateCreated": "2025-05-03T15:17:35+08:00",
 "IsDeployed": true,
 "ExpireTime": "2025-08-02T00:00:00+08:00",
 "FastVoiceType": "WCHN-869b22949cbe4db6a344f1da8cf8a68c"
 },
 "RequestId": "db5709d2-4e2c-432d-8bd3-ed2eddcb6c21"
}

这一步可能真没啥用了,我们要的信息前面也有了,说有用的话,你大概知道这是一个男人名叫kevin的声音。

6. 使用复刻声音来合成语音

1
2
3
4
5
❯ tccli tts TextToVoice \
--Text "我今天带娃在meland玩,这家伙玩疯了呢" \
--SessionId 123 \
--VoiceType 200000000 \
--FastVoiceType WCHN-869b22949cbe4db6a344f1da8cf8a68c > result.json

返回的内容包括base64后的音频数据,我们使用jq来提取并保存到文件:

1
jq -r '.Audio' result.json | base64 -d > tts.wav

播放来听听吧,感受一下自己声音的魔力或者惊吓。

小智server支持使用复刻声音

当我使用复刻声音时,发现小智server并不支持,于是我给它添加了支持,并提交了PRfeat: 支持腾讯TTS声音一句话复刻后的合成,这次没有很快被合并,因为官方说某个字段要废弃了,又没说最新用什么方式,明儿就就腾讯云提工单。

如PR的修改所见,仅是增加了上述fast_voice_type参数的传递即可。不管上游合不合修改,反正我本地已经用上了。有个小发现,这里server中处理不同语音合成模型的参数,UI是基于fields的字段及其定义动态生成展示的,这还不错。顺手update一个DB,对应的UI就有新字段出来了。

1
2
3
4
UPDATE `ai_model_provider`
SET `fields` = '[{"key":"appid","label":"应用ID","type":"string"},{"key":"secret_id","label":"Secret ID","type":"string"},{"key":"secret_key","label":"Secret Key","type":"string"},{"key":"output_dir","label":"输出目录","type":"string"},{"key":"region","label":"区域","type":"string"},{"key":"voice","label":"音色ID","type":"string"},{"key":"fast_voice_type","label":"FastVoiceType","type":"string"}]',
 `update_date` = NOW()
WHERE id = 'SYSTEM_TTS_TencentTTS';

不知道当你发现你自己在和自己说话时是什么感受,反正我挺惊奇的,感觉自己像个神经病。明儿我还是复刻一个我孩子的声音吧,我就可以随时亲子教育了:D

MCP扩展

在这个第三方服务器的实现中,它支持了一部分MCP的能力,也即stdio模式的mcp server。这会因为使用容器的模式,限制了它的使用。容器内可没有npx/uvx啥的,更没有chrome了。我就一个小小愿望,问它谁是最美丽的人,不对,问它昨天曼联输了个几比几,它不能实时联网找到正确答案我就不满意了。

同样的,让我们扩展它,只需要几行代码的修改就可以啦,详见PRfeat: MCP server支持使用sse模式。一般来说宿主机上使用npx/uvx/chrome等,容器内我们通过http协议使用mcp-server还是更简单的。可是有一些mcp server人家就没实现sse咋办?一点都不困难,比如我们借助supergateway就可以让几个模式互转。

使用supergateway

Supergateway runs MCP stdio-based servers over SSE (Server-Sent Events) or WebSockets (WS) with one command. This is useful for remote access, debugging, or connecting to clients when your MCP server only supports stdio.

比如我有这样一个perplexity-ask的mcp server,它只支持stdio模式,那么我就可以使用supergateway来让它支持sse模式:

1
2
3
4
5
6
7
 "perplexity-ask": {
 "command": "/opt/homebrew/bin/node",
 "args": ["/Users/kevinlin/Source/modelcontextprotocol/perplexity-ask/dist/index.js"],
 "env": {
 "PERPLEXITY_API_KEY": "pplx-..."
 }
 }

使用supergateway来启动:

1
2
3
4
export PERPLEXITY_API_KEY=pplx-... &&
npx -y supergateway \
 --stdio "/opt/homebrew/bin/node .../perplexity-ask/dist/index.js" \
 --port 8123

于是我们给小智server的mcp config就可以这么简单了:

1
2
3
4
5
{
 "perplexity-ask": {
 "url": "http://your-ip:8123"
 }
}

现在你的小智就可以更好的使用MCP server了,我试着问了一下,它说曼联最近欧联13场不败!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
 "role": "assistant",
 "tool_calls": [
 {
 "id": "call_202505041955402e0c441c996740fb_0",
 "function": {
 "arguments": "{\"messages\": [{\"role\": \"user\", \"content\": \"曼联过去几场欧联杯比赛的对手和结果?\"}]}",
 "name": "perplexity_ask"
 },
 "type": "function",
 "index": 0
 }
 ]
 },
 {
 "role": "tool",
 "tool_call_id": "call_202505041955402e0c441c996740fb_0",
 "content": "曼联在2024-2025赛季欧联杯中表现出色,以下是他们最近几场欧联杯比赛的对手和结果:\n\n最近的欧联杯比赛,曼联在半决赛首回合客场3-0完胜西甲第四名的毕尔巴鄂竞技[1][2]。这场比赛中,卡塞米罗为曼联首开纪录,随后布鲁诺·费尔南德斯梅开二度,全部进球都是在上半场完成的[2]。比赛中,毕尔巴鄂竞技的后卫丹尼尔·维维安被罚下场[2]。\n\n在四分之一决赛中,曼联对阵法国球队里昂。首回合比赛两队2-2战平[5]。次回合在老特拉福德球场进行的比赛异常激烈,经过常规时间和加时赛的鏖战,曼联最终以5-4战胜里昂,总比分7-6晋级半决赛[5]。在这场比赛中,达洛特建功,布鲁诺·费尔南德斯点射破门,最后时刻马奎尔上演读秒绝杀[5]。\n\n值得一提的是,曼联本赛季在欧联杯中保持着13场不败的强势表现[1],与他们在英超联赛中的低迷形成了鲜明对比。新帅阿莫林有望复制穆里尼奥在2016-17赛季率领曼联首个赛季就夺得欧联杯冠军的成就[2]。\n\n曼联已经一只脚踏入欧联杯决赛,决赛将在本月下旬在毕尔巴鄂的圣马梅斯球场举行[2]。\n\nCitations:\n[1] https://www.163.com/dy/article/JUKFHV4C0530UTM3.html\n[2] https://www.163.com/dy/article/JUHUQC8L05567SBP.html\n[3] https://zh.wikipedia.org/zh-cn/%E6%9B%BC%E8%81%AF2024%E5%B9%B4%E8%87%B32025%E5%B9%B4%E7%90%83%E5%AD%A3\n[4] https://m.dongqiudi.com/article/5071872.html\n[5] https://news.sina.com.cn/zx/2025-04-18/doc-inetppfi2898601.shtml\n"
 },
 {
 "role": "assistant",
 "content": "曼联在2024-2025赛季欧联杯中的表现非常出色。以下是他们最近几场欧联杯比赛的对手和结果:\n\n- 在半决赛首回合,曼联客场以3-0完胜西甲第四名的毕尔巴鄂竞技。卡塞米罗为曼联首开纪录,随后布鲁诺·费尔南德斯梅开二度,全部进球都是在上半场完成的。\n- 在四分之一决赛中,曼联对阵法国球队里昂。首回合比赛两队2-2战平。次回合在老特拉福德球场进行的比赛异常激烈,经过常规时间和加时赛的鏖战,曼联最终以5-4战胜里昂,总比分7-6晋级半决赛。\n\n曼联在本赛季欧联杯中保持着13场不败的强势表现,与他们在英超联赛中的低迷形成了鲜明对比。他们已经一只脚踏入欧联杯决赛,决赛将在本月下旬在毕尔巴鄂的圣马梅斯球场举行。"
 }

使用云函数包装mcp server

像上面这样,它需要依赖机器安装相关的服务,换一台机器就歇菜了,并且为了转换为sse还是比较麻烦。前阵子在试用阿里云百炼平台时,发现有个有意思的扩展,对于它不支持的mcp server,可以通过云函数来自定义扩展它。我想何不借助于它来包装一下mcp server,这样就可以在任何地方使用它了。

我们可以打开https://cap.console.aliyun.com/explore?lang=MCP+Server,这里以高德地图为例,我们只要在云端部署它之后。 使用云函数部署MCP server 本地只需要一个url(填上面公网访问地址)即可使用这个mcp server了,它自动提供了sse接口。至此,你居然就将mcp server “上云”了:)审计和日志也一应俱全呢。现在你可以试一下再和小智聊天,对于地图信息,它比我们还清楚了。小声的说,地图API都提供了天气查询能力,咱也不用再找一个天气服务了呢。

尾声

这个机器人应该还有不少待开发的东西,无论软件还是硬件上。比如如果能再添加几个外设,预期会更有意思了。它本身也支持如Home Assistant等智能家居平台,可以实现如开关灯、开关窗帘等操作。 技术上也有不少地方可以进一步聊聊,比如MCP的使用姿势问题等,以及如何进一步提升对话的响应速度等。 记录一张图留念,本想放视频的,第一不好录制,第二声音这东西,你懂的。

记录一张图留念

考虑到篇幅问题,今天就到这里吧。如果你对某些地方感兴趣,欢迎留言讨论。如果你没什么想说的,点赞、分享、关注,都是对我最好的鼓励。我努力创作有价值的内容,期待未来与你再会。

我是个爱折腾技术的工程师,也乐于分享。欢迎点赞、关注、分享,更欢迎一起探讨技术问题,共同学习,共同进步。为了获得更及时的文章推送,欢迎关注我的公众号:

扫码关注公众号

PaintBot Hub: 一站式AI绘画平台大集成

2025-04-24 08:00:00

Featured image of post PaintBot Hub: 一站式AI绘画平台大集成

背景

在ChatGPT出现后,聊天相关的开源工具多如牛毛,但是一直以来没找到一个能集成多平台绘图能力的开源项目。有时为了给博客文章配一个图,辗转于多个平台,这也太难受了。于是闲暇时间我在lovable.dev平台上搭建了一个集成式网页。

今天恰逢强大到令人瞠目的gpt-image-1模型发布,我想是时候把这个小小项目给介绍一下,方便大家填上API就可以直接使用。使用它你不需要安装任何软件,即用即走,我保证。但你不妨点个赞再走,我感谢:)

功能介绍

这个叫PaintBot Hub的项目,旨在提供一个统一的操作界面,集成多个AI绘画平台的API,让你的创意更加触手可及。当前算是个小Demo,但未来会持续添加更多平台,并增加图片编辑、图生图、提示词辅助等功能。

我们看一下它长得什么样:

PaintBot Hub

​还是挺简洁的是不?当前支持的AI平台:

  • OpenAI 文生图
    • GPT-Image-1 (高/中/低质量)
    • DALL·E 3 (HD/标准质量)
    • DALL·E 2
  • 智谱AI CogView
    • CogView-4-250304
    • CogView-4
    • CogView-3-Flash
    • CogView-3
  • 百度千帆
    • irag-1.0
    • flux.1-schnell
  • 火山引擎豆包(开发中)
  • 阿里云通义万相(卡壳中)
  • 腾讯云文生图(计划中)

核心功能

  • 多平台支持:目前已集成OpenAI、智谱AI CogView、百度千帆等主流AI绘画平台。
  • 文本生成图片:支持中英文等多语言提示词。提示词如果有长度限制,下面会提示你用量。
  • 自定义图片尺寸和比例:支持多种常用比例。如果模型有限制,也会温馨提示你。
  • 批量生成:一次可生成最多6张图片。随意挑选吧,也要注意成本哟。
  • 历史记录:查看和复用历史生成记录。你可以跳回去修改提示词,或者回顾一下过去的创作。

开箱即用

你可以直接使用在线版本,只需要配置自己的API密钥(安全存储在本地浏览器中),即可立即开始创作。

访问链接:https://paintbot-hub.lovable.app/

为了解决使用者的后顾之忧,所有的交互都在你本地浏览器完成的,数据不会上传到任何地方。当然你的文生图提供商可能是知道的:) 你需要填上自己使用对应平台的API密钥等信息,这只能你自己去申请了。抱歉,这个我帮不了你呢:)

好消息是智谱AI CogView3模型生图还免费呢,不妨一试,更好一点的模型就要收钱啦。作为用户,每张图大概多少钱,UI中都给你标注了,作为参考咯。我可是太贴心了,会不会是我成本意识太强,说人话(太抠了哇)。

一键部署你自己的实例

或许访问上面链接还不够快,又或者你更喜欢折腾可控,项目提供了私有化部署方案。私有化部署版本也提供了额外的好处:

  • 通过环境变量预设API密钥,用户无需手动输入
  • 后续计划增加鉴权功能,支持多用户管理和访问控制

使用Docker快速部署

你只需要在本地安装Docker,然后执行以下命令:

1
docker run -d -p 8080:8080 kevinlin86/paintbot-hub:latest

接着打开浏览器访问 http://localhost:8080 即可。

使用docker-compose

你可以本地编辑一个这样的文件,或者直接打开Github代码仓库中的docker-compose.yml文件并复制过来。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
version: '3'

services:
 paintbot:
 image: kevinlin86/paintbot-hub:latest
 container_name: paintbot
 env_file:
 - .env
 ports:
 - "${PORT:-8080}:8080"
 restart: unless-stopped

然后执行命令docker-compose up -d,接着打开浏览器访问 http://localhost:8080 即可。 更多的使用以及未来可能的更新,可以参考Github项目

后记

PaintBot Hub是一个开源项目,欢迎大家参与共建,让这个工具变得更加强大和完善。如果你有好用的类似工具,不妨推荐给我,感谢啦~

这个项目最开始是基于lovable.dev平台创建和开发的,不过随着功能变多,似乎lovable不容易驾驭,我都生气了,仿佛会员白充了,那些吹这个平台有多厉害的人,你们到底有没有深度用过啊(假装发怒)。最后更多的功能还是在Cursor指导下开发完成。但不得不说平台提供免费的托管和一键的发布,这还是很方便的。上面公开的网站就是平台托管的,我没有额外成本付出。

最后,因为个人时间和精力有限,这个项目当前只支持了三四个平台,未来希望能将主流平台都集成进来。

  • 如果你是个开发者,有兴趣的话期待你也参与贡献。我考虑在UI中放个贡献者列表,你的名字将会出现在那里。
  • 如果你是个使用者,祝你玩得愉快,有任何意见都可以在Github提issue。

GitHub项目源码,千万别给我Star哦。啊哈~~~

我是个爱折腾技术的工程师,也乐于分享。欢迎点赞、关注、分享,更欢迎一起探讨技术问题,共同学习,共同进步。为了获得更及时的文章推送,欢迎关注我的公众号:

扫码关注公众号

RAG之关键Embedding模型国内外大PK

2025-04-02 08:00:00

Featured image of post RAG之关键Embedding模型国内外大PK

虽然大模型支持的上下文是越来越大,但不论出于知识库过大还是基于安全考虑,我们还是希望向模型提供适当的上下文即可。这其中选择合适的embedding模型就至关重要了。如何才能找到效果更好的embedding型呢,希望本文能提供一些参考。

背景

我们不能为技术而技术,最好是解决某项具体问题而进行探索。我为何想去了解embedding这块呢?缘于最近MCP比较火,而我工作中经常需要分析一些仓库的提交历史,以发现某些内容的引入或修改历史,即我想和git历史进行交谈。虽然有时咱传统方式也能做,但写个MCP可以用自然语言获得诸如:

  • XX玩法是谁负责的
  • A最近开发的了哪些内容
  • 最近一个月主要有哪些功能在开发
  • 今年3月份有哪些功能
  • 某个文件最近有哪些修改

这一些问题的答案那自然是极好的。这些信息或许可基于git log等进一步检索,而我们一个大项目是由几十个小仓组成的,难度就上升了一层。不过完整的解决方案已经开发得差不多了,今天就先聊一下如何解决第一个挑战,embedding!我计划了一场PK赛,看看哪个模型更适合我的场景。

先叠一层甲,我本人非AI领域人员,基于爱好和专用场景测试,受于个人知识限制,可能存在理解偏差,欢迎指正。

国内外模型介绍

什么是embedding呢?wikipedia的描述比较抽象,以下是腾讯混元T1的解释:

Embedding模型是一种将高维数据(如文本、图像)映射到低维向量空间的技术,通过保留原始数据的语义和特征信息,实现高效计算与相似性分析。其核心原理是通过神经网络训练,将相似的数据点映射到向量空间中的相近位置,例如"猫"和"狗"的向量比"猫"和"苹果"的更接近,从而捕捉语义关联。

在huggingface上有一个排行榜,可以查看不同模型的效果。用于了解有哪些模型还不错,但我们具体使用上还是实测可能更靠谱。

我计划选择免费开源的一些模型,同时也测试一些闭源模型看其提升有多大,是否值得咱付费使用。而这个测试场景,大概有如下几步:

  1. 由AI生成一些git commit message。
  2. 基于这些message交给待测试的各个embedding模型来向量化。
  3. 通过输入Query问题进行相似度(余弦相似度)检索,获得Top5的commit message。
  4. 交给AI对各个embedding模型进行打分(有点重复工作量,我们看看AI表现),看Query出的质量如何?

实测上有一些意想不到的结果呢,让我们拭目以待。

开源embedding模型介绍

在网上查看了一些资料后,我选择了如下几个被推荐较多的模型用于后续测试。

模型名称 描述 维度 最大token 支持语言
text-embedding-gte-large-zh GTE大型中文嵌入模型(本地) 1024 512 中文
text-embedding-bge-large-zh-v1.5 百度开源的中英双语大型嵌入模型(本地) 1024 512 中文、英文
text-embedding-m3e-base M3E基础嵌入模型(本地) 768 512 中文、英文
text-embedding-granite-embedding-278m-multilingual Granite多语言嵌入模型(本地) 768 512 多语言(英文、德文、西班牙文、法文、日文、葡萄牙文、阿拉伯文、捷克文、意大利文、韩文、荷兰文、中文等)
text-embedding-multilingual-e5-large-instruct E5大型多语言嵌入模型 1024 512 多语言

原本jina-embeddings系列模型也想一并参赛的,无奈在LM Studio中支持得不太好,可能缘分未到,暂时跳过。若有朋友有使用经验,不妨留言分享一下实际效果。

闭源大厂embedding模型介绍

以OpenAI为首的如text-embedding-3系列,以及国内各个大厂BAT以及字节等都有自己的embedding模型都获得了参赛资格。这取决于我之前在OneAPI提到过收集的模型提供商了,只要他们有embedding模型,都跃跃欲试进组PK。

模型名称 描述 维度 最大词元数 支持语言
text-embedding-3-large OpenAI第三代大型嵌入模型 3072 8191 多语言
hunyuan-embedding 腾讯混元嵌入模型 1024 1024 中文、英文
doubao-embedding-large-text-240915 豆包嵌入模型 1024 4096 中文、英文
Baichuan-Text-Embedding 百川嵌入模型 1024 512 中文、英文
text-embedding-v3 通义千问嵌入模型 1024 8192 中文、英文
Embedding-V1 百度嵌入模型 1024 384 中文、英文

可以发现:收费的模型虽然咱还没有开赛,但肉眼一看,三围(维度、最大token、支持语言)上已经领先了:)果然没点特色,还真不敢收费。额,那个百度,百川你们咋回事?

Embedding竞技场

为了公平公正,本次PK全过程已经记录在Github仓库: https://github.com/kevin1sMe/embedding-selector,欢迎大家围观。

先公布考题吧,我让AI生成了如下的测试数据以及Query的问题:

 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
"""
测试数据集:中文和中英文混合的commit messages
"""

# 各种风格的commit messages作为测试数据
COMMIT_MESSAGES = [
 # 纯中文commit messages
 "修复首页加载速度慢的问题",
 "优化用户登录流程",
 "新增数据导出功能",
 "修复了用户反馈的崩溃问题",
 "更新文档说明",
 "重构了代码结构,提高了可维护性",
 "删除了废弃的API调用",
 "添加单元测试用例",
 "修改了配置文件中的默认设置",
 "解决了在iOS设备上的兼容性问题",

 # 中英文混合的commit messages
 "fix: 修复了登录页面的bug",
 "feat: 添加了新的payment接口",
 "docs: 更新API文档",
 "refactor: 重构用户认证模块",
 "test: 增加了对checkout流程的测试",
 "style: 调整了UI组件的样式",
 "perf: 优化了数据库查询性能",
 "chore: 更新了package依赖",
 "fix(ui): modal组件关闭按钮失效问题",
 "feat(api): 新增用户数据同步endpoint",

 # 技术专业术语混合的commit messages
 "修复Redis连接池泄露问题",
 "优化React组件的渲染性能",
 "新增Elasticsearch索引管理功能",
 "重构JWT认证逻辑,提高安全性",
 "解决了Docker容器内存占用过高的问题",
 "添加GraphQL查询缓存机制",
 "更新了Webpack配置,提高构建速度",
 "修复了多线程并发导致的数据不一致问题",
 "添加了对WebSocket连接的心跳检测",
 "优化了MongoDB聚合查询的执行效率",

 # 团队协作相关的commit messages
 "根据Code Review反馈修改代码",
 "合并develop分支的最新更改",
 "准备v2.0.0版本发布",
 "修复QA团队报告的regression问题",
 "实现了产品经理提出的新需求",
 "临时提交,WIP:用户管理模块",
 "协同后端API调整相应的前端代码",
 "根据UI设计稿更新组件样式",
 "添加了新功能的feature flag",
 "解决合并冲突,保留双方更改",
]

# 用于测试的查询语句
TEST_QUERIES = [
 # 功能相关查询
 "如何修复bug",
 "添加新功能",
 "更新文档",
 "优化性能",
 "重构代码",

 # 技术相关查询
 "关于React组件的提交",
 "数据库优化",
 "API开发",
 "UI界面调整",
 "Docker相关问题",

 # 过程相关查询
 "代码审查后的修改",
 "版本发布准备",
 "修复测试中发现的问题",
 "合并分支",
 "解决冲突"
]

开源赛区

开源赛区的模型,我是使用的本地LM Studio部署的,已经尽量选择了当前(2025-3-29)最新版本。 LM Studio 本次参赛的5大选手,我们就叫他们F5吧,比赛开始!

1
2
3
4
5
6
7
python3 scripts/run_test.py -m \
 text-embedding-m3e-base \
 text-embedding-bge-large-zh-v1.5 \
 text-embedding-gte-large-zh \
 text-embedding-granite-embedding-278m-multilingual \
 text-embedding-multilingual-e5-large-instruct \
 -o results/open-source-f5.json

虽然是本地部署,就这点计算量,分秒就拿捏了,我们看一下他们的成绩:

模型 处理时间(秒) 数据量
text-embedding-m3e-base 0.7 40
text-embedding-bge-large-zh-v1.5 1.18 40
text-embedding-gte-large-zh 1.12 40
text-embedding-granite-embedding-278m-multilingual 0.68 40
text-embedding-multilingual-e5-large-instruct 1.23 40

我们查看open-source-f5.json的输出:

 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
[
 {
 "model_name": "text-embedding-m3e-base",
 "precision@1": 0.0,
 "precision@3": 0.0,
 "precision@5": 0.0,
 "processing_time": 0.6969938278198242,
 "query_results": [
 {
 "query": "如何修复bug",
 "top_results": [
 {
 "rank": 1,
 "message": "fix: 修复了登录页面的bug",
 "score": 0.837168656326853
 },
 {
 "rank": 2,
 "message": "修复了用户反馈的崩溃问题",
 "score": 0.8329215028808162
 },
 {
 "rank": 3,
 "message": "根据Code Review反馈修改代码",
 "score": 0.8251477839600121
 },
// 省略后续行

内容很多,眼花缭乱,我们先让AI来评测打分,选择了当前号称地表最强的Google新模型Gemini-2.5-Pro experimental 03-25来打分,看看效果如何?"

模型检索结果对比表

注: 限于篇幅只截取一部分,完整内容查看代码仓库。

查询语句 text-embedding-m3e-base text-embedding-bge-large-zh-v1.5 text-embedding-gte-large-zh text-embedding-granite-embedding-278m-multilingual text-embedding-multilingual-e5-large-instruct
如何修复bug 1. fix: 修复了登录页面的bug (0.837)2. 修复了用户反馈的崩溃问题 (0.833)3. 根据Code Review反馈修改代码 (0.825)4. 修复了多线程并发导致的数据不一致问题 (0.807)5. 修复QA团队报告的regression问题 (0.791) 1. 修复了用户反馈的崩溃问题 (0.599)2. fix: 修复了登录页面的bug (0.581)3. 修复了多线程并发导致的数据不一致问题 (0.576)4. 根据Code Review反馈修改代码 (0.541)5. 修复Redis连接池泄露问题 (0.532) 1. fix: 修复了登录页面的bug (0.623)2. 修复了用户反馈的崩溃问题 (0.608)3. 修复首页加载速度慢的问题 (0.592)4. 修复Redis连接池泄露问题 (0.555)5. 协同后端API调整相应的前端代码 (0.527) 1. fix: 修复了登录页面的bug (0.770)2. 修复了用户反馈的崩溃问题 (0.724)3. 修复Redis连接池泄露问题 (0.688)4. 修复首页加载速度慢的问题 (0.687)5. 修复QA团队报告的regression问题 (0.682) 1. 修复QA团队报告的regression问题 (0.918)2. 修复了用户反馈的崩溃问题 (0.916)3. fix: 修复了登录页面的bug (0.914)4. 根据Code Review反馈修改代码 (0.907)5. 重构了代码结构,提高了可维护性 (0.895)
添加新功能 1. 新增数据导出功能 (0.859)2. 添加了新功能的feature flag (0.845)3. feat: 添加了新的payment接口 (0.822)4. 新增Elasticsearch索引管理功能 (0.815)5. 更新了Webpack配置,提高构建速度 (0.812) 1. 新增数据导出功能 (0.710)2. 添加了新功能的feature flag (0.653)3. feat: 添加了新的payment接口 (0.637)4. 实现了产品经理提出的新需求 (0.631)5. 优化用户登录流程 (0.625) 1. 新增数据导出功能 (0.627)2. 添加了新功能的feature flag (0.602)3. 实现了产品经理提出的新需求 (0.548)4. feat: 添加了新的payment接口 (0.524)5. 根据UI设计稿更新组件样式 (0.511) 1. 添加了新功能的feature flag (0.875)2. 新增数据导出功能 (0.804)3. feat: 添加了新的payment接口 (0.792)4. 新增Elasticsearch索引管理功能 (0.702)5. 实现了产品经理提出的新需求 (0.687) 1. 添加了新功能的feature flag (0.954)2. 新增数据导出功能 (0.944)3. 实现了产品经理提出的新需求 (0.933)4. 更新文档说明 (0.931)5. 合并develop分支的最新更改 (0.924)
更新文档 1. 更新文档说明 (0.957)2. docs: 更新API文档 (0.888)3. chore: 更新了package依赖 (0.791)4. 更新了Webpack配置,提高构建速度 (0.785)5. 合并develop分支的最新更改 (0.774) 1. 更新文档说明 (0.857)2. docs: 更新API文档 (0.772)3. 新增数据导出功能 (0.580)4. 根据UI设计稿更新组件样式 (0.577)5. 修改了配置文件中的默认设置 (0.558) 1. 更新文档说明 (0.871)2. docs: 更新API文档 (0.791)3. 合并develop分支的最新更改 (0.586)4. 新增数据导出功能 (0.582)5. 添加了新功能的feature flag (0.541) 1. 更新文档说明 (0.930)2. docs: 更新API文档 (0.804)3. chore: 更新了package依赖 (0.691)4. 合并develop分支的最新更改 (0.667)5. 准备v2.0.0版本发布 (0.653) 1. 更新文档说明 (0.980)2. docs: 更新API文档 (0.953)3. 新增数据导出功能 (0.920)4. 准备v2.0.0版本发布 (0.919)5. 合并develop分支的最新更改 (0.914)
优化性能 1. 优化React组件的渲染性能 (0.841)2. perf: 优化了数据库查询性能 (0.817)3. 修改了配置文件中的默认设置 (0.800)4. 解决了Docker容器内存占用过高的问题 (0.798)5. 修复首页加载速度慢的问题 (0.794) 1. 优化React组件的渲染性能 (0.632)2. perf: 优化了数据库查询性能 (0.595)3. 优化用户登录流程 (0.586)4. 重构了代码结构,提高了可维护性 (0.564)5. 修复了多线程并发导致的数据不一致问题 (0.554) 1. 优化React组件的渲染性能 (0.645)2. perf: 优化了数据库查询性能 (0.611)3. 更新了Webpack配置,提高构建速度 (0.581)4. 修复了用户反馈的崩溃问题 (0.572)5. 解决了在iOS设备上的兼容性问题 (0.567) 1. perf: 优化了数据库查询性能 (0.726)2. 优化React组件的渲染性能 (0.719)3. 优化用户登录流程 (0.684)4. 修复首页加载速度慢的问题 (0.644)5. 更新文档说明 (0.631) 1. perf: 优化了数据库查询性能 (0.931)2. 优化React组件的渲染性能 (0.925)3. 优化用户登录流程 (0.913)4. 修复首页加载速度慢的问题 (0.907)5. 优化了MongoDB聚合查询的执行效率 (0.905)

评估和分析

从上面的表格中,我们可以看到不同模型在不同查询语句下的表现。总体来看:

  • text-embedding-multilingual-e5-large-instruct 在所有查询语句下都给出了相对较高的分数,并且结果的相关性也比较高。这表明该模型在理解中文和中英文混合的commit messages以及查询意图方面表现出色。
  • text-embedding-m3e-base 在很多查询中也能给出较高分数, 但是部分结果相关性存在一定问题。
  • text-embedding-granite-embedding-278m-multilingual 的整体表现相对均衡,但分数普遍低于text-embedding-multilingual-e5-large-instruct,可能意味着它在语义理解的深度上稍逊一筹。
  • text-embedding-bge-large-zh-v1.5text-embedding-gte-large-zh 的分数普遍偏低,可能表明它们更适合特定类型的任务,或者在处理commit messages这类混合语言和专业术语的数据时,效果不佳。

判断依据

  1. 相关性: 模型返回的结果是否与查询语句的意图高度相关。例如,对于"如何修复bug"的查询,返回的结果应该集中在bug修复相关的commit messages上。
  2. 准确性: 模型返回的结果是否真实反映了commit message的内容。
  3. 排序: 相关性高的结果是否排在前面,也就是考察模型的排序能力。
  4. 分数: 虽然分数不能完全代表模型的质量,但一般来说,分数越高,代表模型对结果的置信度越高。
  5. 整体表现的稳定性: 模型在不同类型的查询语句下是否都能保持较好的表现。

结论

综合以上分析, text-embedding-multilingual-e5-large-instruct 模型最适合我们的commit message检索任务。它的分数更高,结果相关性也更高,表明它能够更好地理解查询意图,并返回更准确、更有用的结果。 在检索的准确性,覆盖范围和稳定性上都更好,能够胜任commit message检索这类任务。 虽然其他模型在某些特定查询下可能表现良好,但整体来看,text-embedding-multilingual-e5-large-instruct 在所有查询类型下都更加稳定和可靠。

我们再看一下上面各模型的大小,text-embedding-multilingual-e5-large明显比其它都大一些,或为其中原因,当然也可能大只是因为支持多语言。不过在这个模型参赛前,其它4个模型偷偷比武了一番,结果text-embedding-m3e-base这个尺寸最小的模型夺魁,这么说来,也并不是“底大一级压死人”啊:)

闭源赛区

我们照葫芦画瓢的测试方式,这次将这些付费模型拉出来遛遛。我们先让国内各家来个PK,再看看和国外差距是否明显。国内“获得”测试资格的模型之前已经提过,我们直接看成绩(限于篇幅只截取一部分,完整内容查看代码仓库):

查询语句 hunyuan baidu qwen doubao baichuan
如何修复bug 1. 修复了用户反馈的崩溃问题2. 修复首页加载速度慢的问题3. fix: 修复了登录页面的bug4. 修复了多线程并发导致的数据不一致问题5. 修复Redis连接池泄露问题 1. fix: 修复了登录页面的bug2. 修复了用户反馈的崩溃问题3. 修复了多线程并发导致的数据不一致问题4. 修复首页加载速度慢的问题5. 修复Redis连接池泄露问题 1. 修复了用户反馈的崩溃问题2. fix: 修复了登录页面的bug3. 修复Redis连接池泄露问题4. 修复QA团队报告的regression问题5. 根据Code Review反馈修改代码 1. 修复QA团队报告的regression问题2. fix: 修复了登录页面的bug3. 修复了用户反馈的崩溃问题4. 根据Code Review反馈修改代码5. 修复了多线程并发导致的数据不一致问题 1. 修复了用户反馈的崩溃问题2. fix: 修复了登录页面的bug3. 修复了多线程并发导致的数据不一致问题4. 修复首页加载速度慢的问题5. 根据Code Review反馈修改代码
添加新功能 1. 新增数据导出功能2. 实现了产品经理提出的新需求3. 添加了新功能的feature flag4. feat: 添加了新的payment接口5. chore: 更新了package依赖 1. 新增数据导出功能2. 添加了新功能的feature flag3. 新增Elasticsearch索引管理功能4. 更新文档说明5. 实现了产品经理提出的新需求 1. 添加了新功能的feature flag2. 新增数据导出功能3. feat(api): 新增用户数据同步endpoint4. 更新文档说明5. 实现了产品经理提出的新需求 1. 更新文档说明2. 添加了新功能的feature flag3. 优化用户登录流程4. 删除了废弃的API调用5. 重构了代码结构,提高了可维护性 1. 新增数据导出功能2. 添加了新功能的feature flag3. 优化用户登录流程4. 实现了产品经理提出的新需求5. feat: 添加了新的payment接口
更新文档 1. 更新文档说明2. docs: 更新API文档3. chore: 更新了package依赖4. 更新了Webpack配置,提高构建速度5. 根据Code Review反馈修改代码 1. 更新文档说明2. docs: 更新API文档3. 根据UI设计稿更新组件样式4. chore: 更新了package依赖5. 新增数据导出功能 1. 更新文档说明2. docs: 更新API文档3. 新增数据导出功能4. chore: 更新了package依赖5. 根据UI设计稿更新组件样式 1. 更新文档说明2. docs: 更新API文档3. 删除了废弃的API调用4. 优化用户登录流程5. 重构了代码结构,提高了可维护性 1. 更新文档说明2. docs: 更新API文档3. 新增数据导出功能4. 根据UI设计稿更新组件样式5. 优化用户登录流程
优化性能 1. 修改了配置文件中的默认设置2. 修复了多线程并发导致的数据不一致问题3. 添加GraphQL查询缓存机制4. 更新了Webpack配置,提高构建速度5. perf: 优化了数据库查询性能 1. perf: 优化了数据库查询性能2. 优化React组件的渲染性能3. 优化用户登录流程4. 修复首页加载速度慢的问题5. 优化了MongoDB聚合查询的执行效率 1. 优化用户登录流程2. perf: 优化了数据库查询性能3. 优化React组件的渲染性能4. 修复首页加载速度慢的问题5. 重构了代码结构,提高了可维护性 1. 修复首页加载速度慢的问题2. 优化用户登录流程3. perf: 优化了数据库查询性能4. 修复了用户反馈的崩溃问题5. 删除了废弃的API调用 1. perf: 优化了数据库查询性能2. 优化用户登录流程3. 优化React组件的渲染性能4. 更新了Webpack配置,提高构建速度5. 优化了MongoDB聚合查询的执行效率

我们再请Gemini-2.5-pro来分析一下: google-embedding-results

综合评判:

基于以上对所有查询的分析,各个模型的召回正确率表现如下:

  1. Baichuan: 表现最为出色和稳定。在大多数查询中,尤其是涉及具体技术(React、数据库、UI)和开发流程(重构、Code Review后修改、解决冲突)的查询,其召回结果的相关性最高,Top 5结果中强相关或相关的内容最多。它对中英文混合、专业术语的理解似乎最为到位。
  2. Baidu: 整体表现也非常好,紧随Baichuan之后。在API开发、UI调整、修复Bug等查询上表现突出。但在个别查询上(如数据库优化)表现有失水准,可能在某些语义理解上存在偏差或过度依赖关键词。
  3. Qwen: 表现中等偏上。在一些查询上表现不错,但在需要更深层次语义理解或区分具体技术领域时,召回结果的相关性有时会下降,会混入一些弱相关或泛化的结果。
  4. Hunyuan: 表现中规中矩。在简单、明确的查询(如修复bug)上表现尚可,但在更复杂或技术性更强的查询中,召回的相关性往往不如Baichuan和Baidu。
  5. Doubao: 表现最差。在多个查询中(如添加新功能、更新文档、优化性能、React组件),其召回结果中包含了大量不相关的内容,显示其在理解查询意图和commit message语义方面存在明显不足。

最终结论: Baichuan 模型在召回上正确率最高,最适合这类任务。

判断依据与示例:

  • Baichuan 的优势 (示例):

    • 对技术术语和领域的精准理解: 在查询 “数据库优化” 时,Baichuan 召回了所有5个直接相关的 commit(perf: 优化了数据库查询性能, 优化了MongoDB聚合查询..., 添加GraphQL查询缓存机制, 新增Elasticsearch索引管理功能, 修复Redis连接池泄露问题),覆盖了性能优化、缓存、索引、连接池等多个数据库相关方面。这显示了它对数据库领域术语和优化手段的深刻理解。
    • 对UI/组件相关内容的准确把握: 在查询 “关于React组件的提交” 和 “UI界面调整” 时,Baichuan 都能准确召回 优化React组件..., style: 调整了UI组件..., 根据UI设计稿..., fix(ui): modal... 等高度相关的 commit,表现优于其他多数模型。
    • 稳定性: 在大部分查询中都保持了较高的召回质量,很少出现召回大量完全不相关结果的情况。
  • 不太合适的模型 (Doubao) 的劣势 (示例):

    • 语义理解能力差,易召回不相关结果: 在查询 “添加新功能” 时,Doubao 的 Top 5 结果仅有 1 个强相关 (添加了新功能的feature flag),其余 4 个是 更新文档说明, 优化用户登录流程, 删除了废弃的API调用, 重构了代码结构...,这些都与“添加新功能”的意图相去甚远。
    • 过度泛化或关键词匹配: 在查询 “优化性能” 时,Doubao 召回了 修复首页加载速度慢的问题, 优化用户登录流程, perf: 优化了数据库查询性能 这三个相关的,但也召回了 修复了用户反馈的崩溃问题删除了废弃的API调用,后者与性能优化关联不大,可能是因为看到了“修复”、“优化”等词就简单匹配了。
    • 一致性差: 在多个查询中都表现出召回不相关结果的问题,表明其在理解 commit message 这类特定文本的语义方面存在普遍困难。

因此,基于对所有查询结果的综合分析,Baichuan 模型在本次评测中展现了最高的召回正确率和最好的语义理解能力,是完成该任务的最佳选择。兼听则明?这个结果同时也让Deepseek-R1来分析了一下,都认证了Doubao在这轮里面垫底的事实(也可能注这个场景人家不行?),但第一名一个是Baichuan,一个是Baidu。

国外的embedding模型原本打算把除OpenAI外,Google家和Anthropic家请来的,无奈后两家模型要么主打模型不支持中文,要么我没API KEY(这是我的问题),都纷纷表示上不了场,于是咱就把OpenAI自家三姐妹一起端上来品评下。以下是它们的结果(限于篇幅只截取一部分,完整内容查看代码仓库):

查询语句 text-embedding-3-small text-embedding-3-large text-embedding-ada-002
如何修复bug 1. 修复了登录页面的bug2. 修复了用户反馈的崩溃问题3. 根据Code Review反馈修改代码4. 修复QA团队报告的regression问题5. 修复首页加载速度慢的问题 1. 修复了登录页面的bug2. 修复了用户反馈的崩溃问题3. 修复QA团队报告的regression问题4. 修复了多线程并发导致的数据不一致问题5. 修复Redis连接池泄露问题 1. 修复了用户反馈的崩溃问题2. 修复QA团队报告的regression问题3. 修复了登录页面的bug4. 修复了多线程并发导致的数据不一致问题5. 修复首页加载速度慢的问题
添加新功能 1. 添加了新功能的feature flag2. feat: 添加了新的payment接口3. 合并develop分支的最新更改4. 新增数据导出功能5. 重构了代码结构,提高了可维护性 1. 添加了新功能的feature flag2. 新增数据导出功能3. feat: 添加了新的payment接口4. 新增Elasticsearch索引管理功能5. 添加单元测试用例 1. 添加了新功能的feature flag2. 新增数据导出功能3. 新增Elasticsearch索引管理功能4. 添加单元测试用例5. feat: 添加了新的payment接口
更新文档 1. 更新文档说明2. docs: 更新API文档3. 合并develop分支的最新更改4. chore: 更新了package依赖5. 根据UI设计稿更新组件样式 1. 更新文档说明2. docs: 更新API文档3. 新增数据导出功能4. chore: 更新了package依赖5. 准备v2.0.0版本发布 1. 更新文档说明2. docs: 更新API文档3. 修改了配置文件中的默认设置4. chore: 更新了package依赖5. 准备v2.0.0版本发布
优化性能 1. perf: 优化了数据库查询性能2. 优化React组件的渲染性能3. 优化了MongoDB聚合查询的执行效率4. 重构了代码结构,提高了可维护性5. 优化用户登录流程 1. perf: 优化了数据库查询性能2. 优化React组件的渲染性能3. 优化用户登录流程4. 优化了MongoDB聚合查询的执行效率5. 重构了代码结构,提高了可维护性 1. 优化React组件的渲染性能2. perf: 优化了数据库查询性能3. 优化了MongoDB聚合查询的执行效率4. 优化用户登录流程5. 重构了代码结构,提高了可维护性

综合评判:

  1. text-embedding-3-large: 表现最佳。它在大多数查询中都提供了最高度相关的结果,尤其是在需要理解具体技术动作(如 API 开发、React 组件相关、重构)的查询上表现突出。虽然在某些查询的填充结果上仍有不足,但其核心召回的相关性和准确性是三者中最高的。它似乎对 commit message 中的术语和隐含意图有更强的捕捉能力。
  2. text-embedding-3-small: 表现良好,是强力的竞争者。在许多查询中,其表现非常接近 large,有时甚至在 UI 调整等个别查询上略优。考虑到它是 “small” 模型,其性能令人印象深刻。它主要的弱点是在某些查询中会比 large 混入更多不相关或弱相关的结果。
  3. text-embedding-ada-002: 表现相对最弱。虽然在一些直接的查询(如修复 bug、优化性能)上表现尚可,但在需要更细致区分和理解的查询中(如数据库优化、API 开发)明显落后于 largesmall,召回了更多不相关的结果。似乎更容易受到表面关键词的影响,而对深层语义的把握不如新一代的 text-embedding-3 系列。

腾讯元宝网页版给了如下总结:

评估维度 text-embedding-3-large text-embedding-ada-002 text-embedding-3-small
技术召回率 92% 85% 78%
语义边界准确率 89% 76% 68%
混合文本处理 94% 83% 72%
过程任务召回 86% 91% 88%

多个AI一致投票给了: text-embedding-3-large 模型。它在召回上正确率最高、最适合这类任务。

这其实和我预想的有点出入,本以为small便宜点可能只是维度小一些(1536 vs 3072),对这种场景没啥影响,但是却在多个召回上弱于large模型,或许维度高确实有用吧?不过这个场景原来旧的ada模型明显落后了,那些用旧模型的小伙伴要不要考虑升级一下呢?

那我们最后国内国外一起看一下当前的推荐(来源于Gemini2.5 pro): 推荐层级:

第一梯队:强烈推荐 (Overall Best Performance)

  1. text-embedding-3-large:
    • 理由: 在所有模型中展现出最强的综合实力。它不仅在多数查询中提供了高度相关的结果,并且在理解特定技术动作(如 API 开发细节、具体 UI 修复 fix(ui))和细微语义差别方面表现最佳。其召回结果的相关性排序和准确性通常最高,混入的不相关结果最少。是追求最佳召回效果的首选。
    • 示例优势: API 开发查询中召回最全面;React 组件查询中能捕捉到 fix(ui) 细节。

第二梯队:优秀选择 (Strong Contenders)

  1. Baichuan:

    • 理由: 在第一批模型中表现最佳,整体实力非常接近 text-embedding-3-large。尤其在理解数据库优化、UI/组件相关术语方面表现突出,显示出可能针对中文技术领域有良好的优化。对于侧重这些领域的场景,它可能是与 large 并驾齐驱的选择。
    • 示例优势: 数据库优化查询中召回最全;UI/组件查询中表现优异。
  2. text-embedding-3-small:

    • 理由: 作为 large 的小型版本,其性能表现惊人地好,远超 ada-002 和第一批的大部分模型。在多数查询中紧随 largeBaichuan,召回相关性高。考虑到其可能更优的成本效益和更快的速度,如果对极致性能要求稍低,或者成本是重要因素,small 是一个极具吸引力的选择。
    • 示例优势: 整体表现稳定,与 large 高度相似,性价比可能更高。

第三梯队:可以考虑 (Good / Acceptable)

  1. Baidu:
    • 理由: 在第一批模型中表现第二好,整体召回能力不错,特别是在 API 开发、修复 Bug 等查询上表现良好。但其缺点在于稳定性稍逊于 Baichuan,并且在个别查询(如数据库优化)上表现不佳,可能在某些语义理解上存在短板或过度依赖关键词。
    • 示例优势: API 开发、修复 Bug 召回较好。 劣势: 数据库优化查询表现差。

第四梯队:谨慎使用 (Fair / Use with Caution)

  1. Qwen:
    • 理由: 表现中等,能处理一些查询,但在需要更精细区分或深入理解语义时,准确性不如前三梯队,更容易召回弱相关或泛化的结果。
  2. text-embedding-ada-002:
    • 理由: 作为上一代 OpenAI 模型,其表现已被 text-embedding-3 系列显著超越。虽然在简单查询上还行,但在复杂或需要区分技术领域的查询(如数据库优化、API 开发)中表现较差,召回结果混杂。除非有特定原因(如兼容性),否则不建议优先选择。

第五梯队:不推荐 (Not Recommended)

  1. Hunyuan:
    • 理由: 整体表现平平,仅在最直接的查询中效果尚可,处理复杂或专业性查询时召回准确率较低。
  2. Doubao:
    • 理由: 在本次评测中表现最差,多次在查询中返回大量完全不相关的结果,显示其在理解 commit message 语义方面存在明显困难。

总结建议:

  • 追求最佳效果: 优先选择 text-embedding-3-large
  • 效果与成本平衡: text-embedding-3-small 是极佳的选择,性能接近顶尖且可能更经济。
  • 特定领域优势: 如果你的应用场景高度集中在数据库、基础设施或特定中文 UI 组件方面,Baichuan 也值得重点考虑和测试,它在这些方面展现了特长。
  • 备选方案: 如果前三者不可用,Baidu 是一个可以考虑的选项,但需注意其潜在的稳定性问题。
  • 避免使用: 尽量避免使用 HunyuanDoubao 用于此类需要较高语义理解准确性的任务。text-embedding-ada-002 也应被视为过时选项。

这个场景对于Hunyuan和Doubao我也很抱歉,不是你不好,可能是我们不适合:P

后记

因为测试数据较多,文章显得较长,对于如何找到适合的embedding模型这个问题,虽然可以看到一些模型的成色如何,但也可能有所偏颇,建议你根据自己的具体应用场景和数据上进行测试验证。你也看到我这个测试是针对这种特殊场景的,而你可能要考虑很多因素:

  • 模型的维度,不同的维度在语义上能表达的也不一样。听说也不是越高维越好,反而要看你的数据涉及的面。
  • 模型支持的语言,有不少模型不支持你所希望使用的语言,那自然直接淘汰。
  • 数据的安全性,基于这个考量,我们或许要借助开源模型自建服务。

虽然Google今天没有参赛,江湖传闻它也有特别的能力,在Embedding时指定任务类别,可能可以更精准使用,详见: Google Embedding Task Types

最终我会选择哪一个呢?你猜。

我是个爱折腾技术的工程师,也乐于分享。欢迎点赞、关注、分享,更欢迎一起探讨技术问题,共同学习,共同进步。为了获得更及时的文章推送,欢迎关注我的公众号:

扫码关注公众号

MCP Server的几种使用姿势分享

2025-03-16 08:00:00

Featured image of post MCP Server的几种使用姿势分享

突然MCP就很火了,这里闲聊一点使用经验(开发经验计划下一篇),面对变化,不变的是什么呢?同时借自己文章畅想一下未来,这或许是技术人的浪漫吧:)

前言

大模型相关技术真是日新月异,还记得我在2个月前写了一篇关于Cursor的文章,当时还对比说Cline的MCP有不错的想象空间。于是稍作研究,年前积累了几篇相关文章因为家事忙都没空整理发出来,感觉再不理出来就过时了呢。但是我们有时并不应该简单看技术本身,而是应该思考技术背后的价值,比如它是什么生态位的?

自从有了function call之后,给模型的能力赋予了更多的想象空间。但略遗憾的事这个功能并没有很快大红大紫,其原因可能有多种。比如要很好的支持function call,需要模型本身具备一定的能力,咱们很多很火的大模型都不能很好的支持(或者完全不支持),比如说deepseek(注:最新Google开源的Gemma3在很小模型下支持了function call,有空测试一下,成立的话咱能节省不少成本呢,请关注后续文章)。同时function call 的使用门槛挺高,每家公司标准也不一样,OpenAI和Google对于function call的定义就不一样,这导致相关功能可复用性不强。在这种情况下,Claude模型背后的公司Anthropic提出了一种大模型的通用交互标准协议,这就是MCP。

关于MCP已经有很多文章介绍了,我们关注最终能干点啥,这就是今天的主角mcp-server。每天都有不少mcp-server被开发出来,本文主要从使用者角度聊一下如何用好MCP的能力。之后或许会从开发角度聊聊mcp-server的实现原理以及如何开发一些实用的mcp相关工具(不止mcp-server)。

MCP客户端选择

从最早Anthropic自己当家的Claude Desktop开始,目前已经有相当多使用MCP的应用了,比如:

  • Cursor
  • Windsurf
  • Cline(在vscode中使用)

不过多数是在代码编辑器中集成,看起来程序员对自动化、智能化的接受程度果然是更高的呀:) 各种APP对MCP协议支持程度不一,支持最好的当然要数Claude了。详情看这里:MCP Feature Support Matrix

image

因为国内网络问题,我不是很推荐使用Claude Desktop。 而Cursor最近0.46/0.47虽然疯狂修复MCP的支持bug,但到目前为止依然问题多多。如果你是在Cursor中编辑和调试mcp-server,你要准备好随时掉进坑里。 依托于VCode的Cline使用会简单方便得多,配置一个模型,然后就可以开始愉快地使用MCP了。特别是现在它有了Marketplace,可以很方便的安装和使用各种mcp-server。从对于MCP支持的稳定性上来说,cline还是合格的,基本能按预期调起。比如以下是我用cline通过perplexity API检索一些信息。 Cline中使用Perplexity工具

Windsurf在使用MCP的体验也很不错,后文有截图Windsurf的过程呈现(UI)更漂亮易懂。 Windsurf中使用MCP

但有时我们也不是想要写代码,只是用工具找点乐子,我发现一个更纯粹更小巧的工具mcphost(https://github.com/mark3labs/mcphost)。

A CLI host application that enables Large Language Models (LLMs) to interact with external tools through the Model Context Protocol (MCP). Currently supports both Claude 3.5 Sonnet and Ollama models.

使用mcphost

关于mcphost,最开始使用它是为了调试mcp-server,后来发现它其实是一个很纯粹的MCP客户端,可以很方便的调用各种mcp-server。它的功能简单描述:

  • 与Claude 3.5 Sonnet或Ollama模型进行互动对话
  • 支持多个并发的MCP服务器
  • 动态工具发现与集成
  • 对两种模型类型的工具调用能力
  • 可配置的MCP服务器位置和参数
  • 跨模型类型的一致命令接口
  • 可配置的消息历史窗口以进行上下文管理

我们可以一个命令搞定mcphost安装(前提是已经安装了go):

1
go install github.com/mark3labs/mcphost@latest

之后我们配置一个简单的mcp-server来玩一下,创建一个mcp.json文件,内容如下:

1
2
3
4
5
6
7
8
{
 "mcpServers": {
 "time": {
 "command": "uvx",
 "args": ["mcp-server-time", "--local-timezone=Asia/Shanghai"]
 }
 }
}

这个mcp-server是基于uvx来运行服务的,所以你若本地没安装最好提前安装一下。

uvx 是一个轻量级的工具,主要用于在临时环境中运行 Python 工具和脚本。通过其即用即走的特性和临时虚拟环境的使用,解决了环境管理的复杂性和工具版本管理的问题,特别适合快速试用工具或在 CI/CD 环境中执行临时任务。

然后运行mcphost:

1
2
3
export OPENAI_API_KEY=sk-xxx

mcphost --openai-api-key=$OPENAI_API_KEY -m openai:gpt-4o --config mcp.json

成功加载完mcp-server后,就进入了交互模式:

 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
2025/03/15 17:33:04 INFO Model loaded provider=openai model=gpt-4o
2025/03/15 17:33:04 INFO Initializing server... name=time
2025/03/15 17:33:11 INFO Server connected name=time
2025/03/15 17:33:11 INFO Tools loaded server=time count=2



time
 • get_current_time
 • Get current time in a specific timezones
 • convert_time
 • Convert time between timezones



 You: 现在几点了

 Assistant:

2025/03/15 17:33:22 INFO 🔧 Using tool name=time__get_current_time
2025/03/15 17:33:22 INFO Usage statistics input_tokens=221 output_tokens=20 total_tokens=241

 Assistant:


 现在是北京时间 17:33(下午5点33分)。


┃ Enter your prompt (Type /help for commands, Ctrl+C to quit)

本来没有啥时间概念的大模型,现在很清楚的掌握了当前时间。接下来我多问了一嘴:

1
2
3
4
5
6
7
8
9
 You: 那当前时间的农历日期是?

 Assistant:


 当前时间是公历2025年3月15日,我暂时无法直接计算出对应的农历日期。但你可以使用农历转换工具或者查询相关日历来获取对应的农历日期。如果你需要我帮忙查找其他相关信息,请告诉我!


┃ Enter your prompt (Type /help for commands, Ctrl+C to quit)

工具不够用了,是时候进入下一个环节。

PS: 当然你可以使用/help查看到mcphost支持的一些命令如tools/servers等,试用一下呗。

部分好用的mcp-server介绍

Tavily搜索

如上面展示,我想知道农历怎么办,可能有某个mcp-server能回答这个问题,不过我相信只要大模型能联网,它或许也能回答。我们继续在上面mcp.json中添加一个mcp-server,我们选择了tavily,一个搜索引擎。需要申请一个tavily的API key

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
{
 "mcpServers": {
 "time": {
 "command": "uvx",
 "args": ["mcp-server-time", "--local-timezone=Asia/Shanghai"]
 },
 "tavily": {
 "command": "npx",
 "args": ["-y", "@mcptools/mcp-tavily"],
 "env": {
 "TAVILY_API_KEY": "{{你需要去申请tavily的API key,免费的够用}}"
 }
 }
 }
}

接下来再问一次:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
 You: 那当前时间的农历日期是?

 Assistant:

2025/03/15 17:47:03 INFO 🔧 Using tool name=time__get_current_time
2025/03/15 17:47:03 INFO Usage statistics input_tokens=945 output_tokens=20 total_tokens=965

 Assistant:

2025/03/15 17:47:04 INFO 🔧 Using tool name=tavily__searchQNA
2025/03/15 17:47:04 INFO Usage statistics input_tokens=1002 output_tokens=30 total_tokens=1032

 Assistant:


 当前时间的农历日期是:2025年二月十六,属于乙巳年、己卯月、癸未日。

 如果您需要更详细的信息,可以查看相关的黄历和农历查询网站,例如:水墨先生 https://m.smxs.com/hl/nongli_2025-3-15.html。


┃ Enter your prompt (Type /help for commands, Ctrl+C to quit)

它居然把出处也写上了呢,还真是有效的一个链接,有联网后幻觉的病也好多了。

Perplexity搜索

如果你像我一样平常已经习惯使用Perplexity来搜索信息,那么我们也可以使用Perplexity的MCP-Server来随便调起搜索。只是它这个API是使用Sonar API在线搜索的,或许和官网可用的几种模式略有区别,比如深度研究等估计是没有了,期待未来更完善,当前也能满足日常使用。同样的,我们要注册一个API KEY,但木有免费额度,所以可能要借助信用卡或Google pay等注册充值后才可以调用。这个mcp-server的安装稍麻烦一些,没有上传到官方仓库等,我们可能要手动编译构建一下。

1
2
3
4
git clone https://github.com/ppl-ai/modelcontextprotocol.git
cd modelcontextprotocol/perplexity-ask
npm install
npm run build

之后会在对应dist目录下会有一个index.js文件,我们就可以修改mcp.json让node运行它。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
{
 "mcpServers": {
 "time": {
 "command": "uvx",
 "args": ["mcp-server-time", "--local-timezone=Asia/Shanghai"]
 },
 "perplexity-ask": {
 "command": "/opt/homebrew/bin/node",
 "args": ["{{你本地代码路径}}/modelcontextprotocol/perplexity-ask/dist/index.js"],
 "env": {
 "PERPLEXITY_API_KEY": "{{你的Perplexity API KEY}}"
 }
 }
 }
}

使用呈现:

 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
time
 • get_current_time
 • Get current time in a specific timezones
 • convert_time
 • Convert time between timezones
 • perplexity-ask
 • perplexity_ask
 • Engages in a conversation using the Sonar API. Accepts an array of messages (each with a role and content) and returns a ask completion response from the Perplexity model.



 You: 用500个字内简单总结一下最近deepseek开源的几个项目及作用

 Assistant:

2025/03/15 19:27:54 INFO 🔧 Using tool name=perplexity-ask__perplexity_ask
2025/03/15 19:27:54 INFO Usage statistics input_tokens=348 output_tokens=49 total_tokens=397

 Assistant:


 最近,DeepSeek开源了几个重要项目,旨在提升人工智能(AI)模型的训练和推理效率,具体包括:

 1. FlashMLA:这是一个针对Hopper GPU优化的高效多头注意力解码内核。它可以显著提高显存利用率和推理速度,让老旧显卡焕发新生,从而提升整个模型的运行效率。
 2. DeepEP:专为混合专家(MoE)模型设计的高效通信库。通过优化节点间的通信,它能够加速大型模型的训练和推理,减少了通信延迟,提高了整体性能。
 3. DeepGEMM:这是一个轻量级高性能的矩阵乘法库,支持FP8精度,旨在提升大规模矩阵运算的效率。这个库为深度学习模型的计算提供了基础支持,尤其是在处理大规模数据时。
 4. DualPipe与EPLB:这两个项目是并行训练的优化方案,包含双向流水线并行算法和专家并行负载均衡,能够大幅提高分布式训练的效率,帮助研究人员更快地进行模型训练。
 5. 3FS:这是一个为AI训练和推理设计的高性能分布式文件系统,旨在解决海量数据的高速存取与管理问题。它使得数据管理更加高效,为AI模型提供了快速的数据访问能力。

 这些项目覆盖了AI开发的整个链路,从底层硬件优化到模型训练和推理加速,再到数据存储和通信效率的提升。它们的目标是降低AI开发的门槛,提高资源利用率,加速模型的训练与推理,进而推动AI技术的普及和创新。这一系列开源举措被视为AI基础设施领域的重要突破,有望重塑行业格局。


┃ Enter your prompt (Type /help for commands, Ctrl+C to quit)

我也不知道对不对,但看起来就很厉害。还有一些搜索工具也很推荐:

  • Brave Search,你可以很方便使用它(比上面这个容易用得多)。
  • Firecrawl,你可以在这里查看firecrawl-mcp-server,它支持深度研究等搜索。据说网页抓取成功率非常高。免费注册有一些次数可使用。

当然这些都需要一个Key,看自己喜好去注册吧,成人也可以不做选择。

server-filesystem文件系统操作

过往我们除了手动一个个文件去找东西外,也就能用命令行或一些字符串匹配的方式来找东西。有了这个mcp-server可以让我们很方便智能的和文件系统互动了。同样的添加一个配置就可以玩了:

1
2
3
4
5
6
7
8
9
 "filesystem": {
 "command": "npx",
 "args": [
 "-y",
 "@modelcontextprotocol/server-filesystem",
 "{{你本地文件系统路径1}}",
 "{{你本地文件系统路径2}}"
 ]
 }

比如我们可以这样聊

 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
 You: 我的Source目录有哪些项目

 Assistant:

2025/03/16 06:47:42 INFO 🔧 Using tool name=filesystem__list_allowed_directories
2025/03/16 06:47:42 INFO Usage statistics input_tokens=2433 output_tokens=16 total_tokens=2449

 Assistant:

2025/03/16 06:47:44 INFO 🔧 Using tool name=filesystem__list_directory
2025/03/16 06:47:44 INFO Usage statistics input_tokens=2469 output_tokens=25 total_tokens=2494

 Assistant:


 在你的 Source 目录下,有以下项目:

 1. blog (目录)
 2. mcp-test (目录)
 3. modelcontextprotocol (目录)

...

 You: blog目录中在2024年发表了哪些文章呢

 Assistant:

2025/03/16 06:48:13 INFO 🔧 Using tool name=filesystem__list_directory
2025/03/16 06:48:13 INFO Usage statistics input_tokens=2595 output_tokens=26 total_tokens=2621

 Assistant:

2025/03/16 06:48:15 INFO 🔧 Using tool name=filesystem__list_directory
2025/03/16 06:48:15 INFO Usage statistics input_tokens=2734 output_tokens=27 total_tokens=2761

 Assistant:

2025/03/16 06:48:16 INFO 🔧 Using tool name=filesystem__list_directory
2025/03/16 06:48:16 INFO Usage statistics input_tokens=2789 output_tokens=28 total_tokens=2817

 Assistant:

2025/03/16 06:48:18 INFO 🔧 Using tool name=filesystem__list_directory
2025/03/16 06:48:18 INFO Usage statistics input_tokens=2886 output_tokens=31 total_tokens=2917

 Assistant:


 在你的 blog 目录中,2024 年发表的文章有:

 1. 2024-01-08-summarize
 2. 2024-01-21-using-vscode
 3. 2024-02-19-tl-r479gp-proxy
 4. 2024-02-24-speed-up-your-network
 5. 2024-03-05-using-webhook
 6. 2024-03-10-monitor-and-alert-traffic
 7. 2024-03-31-oneapi-for-all-models
 8. 2024-06-10-oneapi-and-models-tips
 9. 2024-06-15-ai-with-alfred
 10. 2024-07-07-secret-watcher
 11. 2024-07-21-remember-me
 12. 2024-07-29-talk-with-ai
 13. 2024-08-25-usg-to-mikrotik
 14. 2024-09-30-steamdeck-to-ps5
 15. 2024-10-16-create-union-k8s-cluster
 16. 2024-11-16-mac-mini-m4
 17. 2024-11-25-mac-mini-play-games

 这些都是在 2024 年发表的文章目录。

呃,一不小心暴露了我月更党的倾向。

Obsidian操作利器

Smithery.ai是一个mcp server平台,面向新手使用友好,我们可以用来它配置mcp-server。它还提供了交互式让你填写必要的配置。比如我们看这里: https://smithery.ai/server/obsidian-mcp,在右边可以基于自己的应用选择相应的安装方式。

Smithery.ai install mcp-server 比如我选择了Windsurf,拷贝命令在本地执行后:

1
2
3
4
❯ npx -y @smithery/cli@latest install obsidian-mcp --client windsurf
✔ Successfully resolved obsidian-mcp
? The absolute path to your Obsidian vault. (required) {{你本地Obsidian Vault绝对路径}}
obsidian-mcp successfully installed for windsurf

然后就可以直接在Windsurf中使用它了。我们不妨看一眼配置:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
 "obsidian-mcp": {
 "command": "npx",
 "args": [
 "-y",
 "@smithery/cli@latest",
 "run",
 "obsidian-mcp",
 "--config",
 "\"{\\\"vaultPath\\\":\\\"{{你本地Obsidian Vault绝对路径}}\\\"}\""
 ]
 }

我们随便问点东西,可以和你的Obsidian知识库互动了。 Windsurf中使用obsidian-mcp 之后就可以和AI聊聊你的个人知识库了,更多的细节实践未来有空再分享。关于Obsidian结合大模型的使用,最近正考虑另写一篇。

当前问题及未来畅想

在真实使用上,不论是支持MCP的应用,还是mcp-server本身成熟度,都还有一段路要走。比如:现在似乎正在野蛮生长期,

  • 相关应用还不够丰富,似乎还没太面向普通用户,让他们安装代码编辑器也太扯了,我们国内可没有Claude Desktop使用呀。
  • mcp-server本身,同一个功能也有好多个实现版本,先不说BUG多少,就光依赖管理,以及有些需要手工安装等,对于普通用户来说使用门槛较高。
  • 在使用mcp-server时,如文章所见我们得申请各种API KEY,这也增加了使用成本和复杂度。
  • MCP需要大模型支持,能够比较稳定的根据MCP协议调用工具,这块大模型本身也还需要时间提升自己。当前经验实惠的我用的是gpt-4o-mini,Deepseek时不时失灵,给他们时间吧。

但是这些不是我们否定MCP的理由,相反,我更愿意相信MCP会是一个趋势,就像他们所描述的,期待它能成为一个类似USB一样的标准,连接、连接、连接。

我们不妨想象一下,未来为了抢夺用户,各个AI应用如元宝、豆包等,它们或许也会支持MCP,让用户可以将智能应用于更多场景。不过大厂也不可能将各种功能都实现,所以还是会调用其它平台能力吧?如何整合到一起,想一想挺有意思。

我们不妨想象一下,未来我们的第二大脑是大模型,我们给的是需求,它自己决定调用哪些工具。作为被调用的工具方,在初期应该会有一波流量,因为大家在建设各自的智能体时(Agent),最终都需要终端能干活的“手”,但最终的发展可能还是看各个工具自己的品质和对需求的洞察与满足情况。

而在认识到MCP及有一些实际体验后,会发现在工作与日常中,目前就有很多场景可以应用。比如:

  • 看个聊天内容太多了就想让AI总结一下;有一些跟进和备忘的我只要提一句或@某个工具就自动帮我归类放在正确的地方。
  • 需求单那么多而BUG单也纷至沓来,哪些需要我处理的也必须得有AI来筛选,那些筛选规则太复杂了把我搞晕而我只会自然语言。
  • 常用的Alfred工具也必须得成为一个支持MCP的应用,还能自动交互式输入,别让我输错命令后反复再来一次了。

李开复先生的“所有行业都会被人工智能触及、改变、转型并提效。” 是有见地的,你看过高效而智能的做法,将再也回不去过往的流程了。

这篇文章就开个头,下一篇文章可能会聊聊开发mcp-server的经验,包括一些调试技巧。同时未来有更多AI实践经验觉得有意思有价值的话也会再分享,下回见喽。

一些MCP及mcp-server资源

  • MCP官方文档 很详尽的文档了,要了解原理或者进一步搞开发,不得不读。
  • MCP官方推荐的server 官方也在推广它的使用场景呢,这里的算是经过认证了吗?
  • Awesome MCP Servers 收录了相当多的mcp-server,可以去寻宝。
  • Smithery.ai 提供 2000+ 预配置 MCP Server,一键为各个APP配置mcp-server。
  • Glama.ai 当前也有1500+ servers。分类清晰,围观一下也挺不错。

我是个爱折腾技术的工程师,也乐于分享。欢迎点赞、关注、分享,更欢迎一起探讨技术问题,共同学习,共同进步。为了获得更及时的文章推送,欢迎关注我的公众号:

扫码关注公众号

一切皆会好的(一)

2025-02-20 08:00:00

Featured image of post 一切皆会好的(一)

我仍然有点恍惚,即便站在车水马龙的闹市中,依旧有些不真实感。不真实在从未想象的疾病会突然降临在我可爱的儿子身上,紧接着一系列的不安、恐惧、焦急,各种情绪找不到突破口,只在深夜,抓着媳妇的手,噙着泪花说,一切都会好起来的。

0203-突发

难得有像春节这般长的假期,一家人整整齐齐的每天在一起。看着3岁女儿和2岁的儿子,他们在老家的田间奔跑嬉戏,在楼上楼下床上床下反复蹦哒,在为谁先使用玩具而争吵哭闹。我为假期也准备了不少他们的手工书和玩具,希望一起开心玩玩。

谁料想,儿子在2月3号突然发烧,观察一天4号仍未好转伴食欲不振且半夜喝奶呕吐。下午带去乡镇卫生院检查,我们本以为只是一次简单的感冒,血常规检查时,护士惊讶了为什么血小板这么低,以为机器异常又让我们扎了一次手指,确认只有14后,同时白细胞、血红蛋白也都有下降。未等到找医生确认,她建议我们马上立即带去市里大医院看看。

0204-入院

我和媳妇意识到不对,立马回家收拾一点行李,借了邻居小伟的车,赶紧奔赴泉州妇幼保健院,也即儿童医院。车程有点长,60多公里的路,既要更少颠簸,又想尽快到达,我比平常多了很多的变道和超车,几十秒的红灯都比日常漫长多了。后视镜里,小家伙耷拉在妈妈肩膀似乎在睡觉。他可能啥也不知道,只是有点倦,而大人的内心满是担忧,但具体什么病却一无所知。

到了妇幼进入急诊,重新抽血去检查,等待,等待,着急的等待。娃的状态好点,大抵是饿了,娃妈妈给喂了一些面包。大概在17点半左右,血常规检验报告出来。考虑到血小板只有8(又降了),随时有身体内部器官出血风险,医生带我们上楼,直接住到了PCIU(儿童ICU)。娃哭着要妈妈,可是没有办法,不允许陪护,后续每天只能探望一次。ICU自动门关闭的那一刻,我仍然听到了撕心裂肺的哭喊。我们来不及悲伤于分离,便接到医生找我们谈话,在病危通知单上签字的手微微有些颤抖。仅仅3-4个小时,像过山车一样,但见不断上升上升,内心只有害怕,只有恐惧。

不能陪伴,我们在医院也没用。于是驱车回家,岳母问虎子人呢,媳妇都要哭了,几个人都红了眼睛。我们草草吃了一些饭菜,味同嚼蜡。原本想明早过去(60+公里远),媳妇放心不下,我也一样,于是晚上又借了婶婶的车。我们只觉得住靠近医院一点,离娃近一点,才能获得一丝心安。同时又放心不下女儿,把她也一起带上了。晚上酒店在918房,儿子的床位是917。在电梯我和媳妇开个玩笑,你看我们就住在儿子隔壁呢?她没有笑,我也没有,因为心底都压着沉沉的石头。

0205-求血

血小板减少症入院,具体原因还需进一步诊查。和医院沟通,当务之急是要尽快输血小板,但是医院说只报上去了,等血站调货,不确定什么时候能到。性命攸关之下,我们找了不少朋友,我的大学同学和媳妇的同学也尽力在帮忙中,让我们看到晚上十点多娃在ICU病床上安稳睡下了,我们稍微放心一些。晚上躺在酒店床上,难以入睡,我还能做点啥呢?我不能因为自己的难以启齿而放弃一些机会,我一定要尽快给娃搞到血小板!于是在公司内部乐问求助,也第一次注册小红书发本地求助贴,希望有人看到可以帮忙。最终确实也收到了一些热心朋友支招和安慰,比如凌晨3点公司同事还在给我解答疑问和安抚,也有人支招蹲血站,找黄牛,这些给人多了一些希望。

5号上午9点多,本来想和媳妇分头行动她去探视、我去血站求助和献血,看能否加速娃的供血。媳妇说不行,日常倔强的她显然此刻已乱了心神,我察觉到稍闲下来她已偷偷在抹眼泪。于是和她一起在医院,等统一的探视时间。媳妇出来后,听说虎子躺在床上被绑着(怕孩子磕碰),见到妈妈一直哭,我想大抵哭诉着“我要出去,我要妈妈”之类吧,媳妇在里面哄了一会,娃可能哭累了睡去,探视早已超时。

预约了下午两点采集血小板,中午要求吃了才能采,怕到时血不合格,只能强迫自己尽量多吃一点。血站离儿童医院只有1-2公里,提前到了便在外等爱心屋开门。血站工作人员说,小朋友是什么血型就需要献血的人是什么血型,这又是一个大坑:虎子血型到底是A还是O并不知晓。过往没专门查过,医院的几次检查里也没涉及血型,这可能使献血了因为血型不对派不上用场。只能想要不两者都献?遗憾的是媳妇的身体不便献血。恰逢有两个妹子过来献血,又恰好其中一个是A型血,我厚着脸皮靠近想开口,她没有开口,眼神和手势就赏了我一个闭门羹。确实,要献成分血更漫长,无亲无故谁会帮你呢?犯愁之际,连续来了两辆车,老婆家里叔叔伯伯家人以及邻居表弟都来了,看着他们一个个撸手袖子去测血型,只要能用上随时准备着,我的鼻子酸酸、异常感动。当时我便默默对自己和未来的虎子说,要记住他们、善待他们,这些是可以随时为你流血的人。虽然最终没有一个合适的A型,但所幸也得知了最终虎子是O型血,那么我的采集血小板就可以交换到一份O型供他使用了(注:国外成分血可血型不一致使用),天无绝人之路!看着血液从身体流出,想着或能帮助到儿子,有一点欣慰。拿到献血证后,马不停蹄敲开ICU找到护士,请求尽快申请输板。护士也告诉我们,今天做过骨穿检查了,板到了就会输。晚上大概十点多,和护士简单沟通,已经输过板了,终于放心一些。

晚上我们也联系了北京的专家,也问了上海的医学方面朋友,因为骨穿结果暂时没有,以仅有的一些报告,有说是再障的,也有说是急淋的。听起来都不像是很容易解决的病情。上海的朋友很给力的安排了医院留床,若是需要过去便可快速入院,考虑到这种危险程度,能尽快入院确实让我们安心不少,听说很难排到床位。凌晨1点多,有点撑不住了,大脑降频严重,睡了过去,不知道娃今晚在病房睡得可好。

0206-转院

6号一早,我们便赶往泉州儿童医院,血液科专家吴姓医生找我们聊了一下病情,从骨穿涂片等初步结果来看,应该是急性淋巴细胞白血病,简称急淋。医生也很自信的告诉我们,这个病是血液科常见的,儿童治愈率很高,80-90%以上可以治愈,但会是长期的过程,听到这里,我们高悬的心稍微放下了一些。那么去北京上海,或者还是留在泉州或者是回深圳治疗?如果在泉州的话,马上可以下一步大骨穿(可能要活检等),最快下周就可以开始上药。考虑到我们家实际情况,结合当前医生判断和对更换医院的建议,我和媳妇商量后决定在广州找医院进行治疗。

我继续找人联系广州的医院,当下这种情况,若过去后无法入院那是极危险的。公司的同事linda还有秘书都很帮忙,在我忙不开交时他们不停的帮联系人和沟通病情、寻找资源,很快收到电话推荐到广州妇女儿童医疗中心并且可入院,同学也通过关系找到这家医院的一个主任答应可以安排住院。在双重保障下,我们告诉泉州医院我们的决定:去广州下一步治疗。何时出动?事不宜迟,医院说上午安排了输血,那下午即刻动身。

亲戚们也陆续赶来,顺便把我们的行李带过来了,缺少的东西他们分头去采购。我们预定下午6点直飞广州白云机场,并且联系好在深圳的亲人来接送。安排妥当后,中午在医院等候椅上吃了个盒饭,这次的饭终于有了一点味道。离下午2点还有些时间,他们(老婆?)还在门口买了个虎子喜欢的大大的奥特曼气球,我在想象从ICU接虎子出来时,他看到后会不会开心的笑,一边在妈妈怀里一边用他那稚嫩的声音说:奥特曼。可是并没有-当护士呼唤我们拿些衣物进去,他们给换好后衣物后,门缓缓打开,虎子颤颤巍巍地走出来时,没有看到爸妈的开心,脸上呆呆,有点不知所措。我内心一阵酸楚,两天见不到亲人,浑身被绑,哭喊无助,定是伤心透了。媳妇很快把他抱入怀里,无论如何,宝贝总算回到爸妈身边了。原来有分离焦虑的不光是孩子,爹娘何偿不是呢?我轻摸了一下他的手和脚,他身体紧紧贴住妈妈。

小伟和几个亲戚送我们到机场,挥手告别。在飞往广州的飞机上,看着虎子迷糊在妈妈怀里,小家伙,未来可能会有些困难,但无论如何,爸妈都会尽一切力量给你治好的,你也会很努力的,对吗?我知道,你可以的!你每次摔倒第一件事就是告诉大家,“我没事”,然后自己爬起来。是的,你肯定没事!

我们相信,穿过这场突如其来的暴风雨,终将在阳光里重逢。