文章目录
-
- 前言
- [1. 本地这一步,先认了](#1. 本地这一步,先认了)
-
- [1.1 好的那半和坏的那半](#1.1 好的那半和坏的那半)
- [1.2 我换源,换到怀疑人生](#1.2 我换源,换到怀疑人生)
- [1.3 所以别修车了,换辆车](#1.3 所以别修车了,换辆车)
- [2. 让官方的命令替我写文件](#2. 让官方的命令替我写文件)
-
- [2.1 一条命令,两个文件](#2.1 一条命令,两个文件)
- [2.2 这里有个坑,得写在明面上](#2.2 这里有个坑,得写在明面上)
- [3. 到能开的地方去开](#3. 到能开的地方去开)
-
- [3.1 传上去,然后构建](#3.1 传上去,然后构建)
- [4. compose 里住着四个人](#4. compose 里住着四个人)
-
- [4.1 四大板块](#4.1 四大板块)
- [4.2 合租最容易出事的地方不是谁懒,是有人偷偷改门锁](#4.2 合租最容易出事的地方不是谁懒,是有人偷偷改门锁)
- [5. 一键起来](#5. 一键起来)
- [6. 前端就三个文件](#6. 前端就三个文件)
-
- [6.1 一个客厅,一个厨房,一个物业](#6.1 一个客厅,一个厨房,一个物业)
- [6.2 浏览器的洁癖](#6.2 浏览器的洁癖)
- [6.3 就是这一行 ws](#6.3 就是这一行 ws)
- [7. 把门锁上,自己在屋里点外卖](#7. 把门锁上,自己在屋里点外卖)
-
- [7.1 只允许本机访问](#7.1 只允许本机访问)
- [7.2 那句客气话](#7.2 那句客气话)
- [7.3 别攒,来一句给一句](#7.3 别攒,来一句给一句)
- [8. 它开始劝我加衣服](#8. 它开始劝我加衣服)
- [9. 说到底](#9. 说到底)
P.S. 目前国内还是很缺AI人才的,希望更多人能真正加入到AI行业,共同促进行业进步,增强我国的AI竞争力。想要系统学习AI知识的朋友可以看看我精心打磨的教程传送门http://blog.csdn.net/jiangjunshow,教程通俗易懂,高中生都能看懂,还有各种段子风趣幽默,从深度学习基础原理到各领域实战应用都有讲解,我22年的AI积累全在里面了。注意,教程仅限真正想入门AI的朋友,否则看看零散的博文就够了。
前言
先说结论:我本地那个 langgraph build,一次都没成功过。三次,三次,全挂在拉镜像上。
而 langgraph dev 顺得像刚上过油。
这就很像我高中那辆自行车:骑着能走,一上锁就坏。
那天晚上我盯着终端里那行红字看了很久,想的不是技术方案,想的是------为什么同一个工具,可以一边让我觉得自己很行,一边让我觉得自己不太行。
1. 本地这一步,先认了
1.1 好的那半和坏的那半
我本地是 Mac。Mac 有个副作用,它会让你产生错觉:世界上所有东西都能一键装好。
langgraph dev 就是这种。它跑起来不解释,跑不起来也不解释。你不问,它就一直在那儿。
build 不一样。build 要下镜像。下镜像这件事,在国内开发者心里有个专用抽屉,平时不打开,打开就想关。
1.2 我换源,换到怀疑人生
第一反应是改镜像源。改完重试,失败。
第二反应是再改一个源。再重试,还是失败。
第三个源是我在一篇 2021 年的博客里翻出来的。那位博主写得特别有信心,我猜他后来转行了。
三个源,三次超时。这感觉就像你连着问三个窗口同一件事,三个人都让你去隔壁。
中间我还试过换网络。公司试了,家里试了,咖啡店也试了。那杯咖啡三十八,镜像一个字节都没下来。那是我那年喝过最贵的一杯失败。
我甚至重启过 Docker Desktop。它重启得非常优雅,问题也优雅地留在了原地。
后来我想通一件事:这不怪我,也不怪源,怪距离。有些东西就是拉不过来。
1.3 所以别修车了,换辆车
导航软件管这个叫"重新规划路线",程序员管这个叫"算了"。
我决定不在本地下手了,去服务器上构建。服务器上不用装 langgraph------这句话我读了三遍,意思是:你负责把它造出来,服务器负责把它跑起来,谁也别打听对方的业务。
这是我今年听过最体面的一次分工。
2. 让官方的命令替我写文件
2.1 一条命令,两个文件
Dockerfile 我没有手写。langgraph 自己会生成,连 docker-compose 一起给你。
bash
langgraph dockerfile ./Dockerfile --add-docker-compose
这条命令是那天唯一一次让我觉得被善待:它不问我要干什么,直接把东西摆我面前。
生成完我第一反应是------原来官方也知道没人想手写这个。
2.2 这里有个坑,得写在明面上
只要 langgraph.json 变了------加依赖、改图入口、动一下运行时------你必须把这条命令重新跑一遍。
因为 Dockerfile 不是活的,它是那一刻的一张照片。你后来长胖了,照片不会跟着胖。你改了配置,镜像还是按旧照片长。
更麻烦的是:翻车的时候你不会怀疑照片,你会怀疑自己胖了。
它也有点像手机里那个三年没改的备注名。人都换了,名字还留着,你还以为叫得对。
3. 到能开的地方去开
3.1 传上去,然后构建
代码传到服务器,构建就一行。
bash
docker build -t xxx:vxx .
镜像名我起得很草率。后来在 docker images 列表里看见 xxx:vxx,有种在毕业照里找自己的感觉:知道在,但不确定哪个是我。
成功那一下没有音乐,没有动画,就一行 Successfully tagged。我在终端前站了三秒------像刚拿驾照的人,钥匙在手里,车在楼下,不太敢开。
4. compose 里住着四个人
4.1 四大板块
docker-compose.yml 就四块:redis、postgresql、镜像本身、环境变量。按实际情况改就行,官方写的结构已经够用。
这四块住在一个文件里,像四个人合租。
redis 是脑子最快的那个室友。什么都先记在脑子里,反应极快,一断电全忘。你不能怪它,它本来就不负责长久。
postgres 是写日记的那个。慢一点,啰嗦一点,但你三个月后问它上周二的事,它真能翻出来。
镜像是你自己。
环境变量是房租。最不起眼,少了谁都别想住。
4.2 合租最容易出事的地方不是谁懒,是有人偷偷改门锁
门锁就是端口。
我本地一直是 2024。到服务器我改成了 8123,理由很朴素:2024 这个数字太像一个年份,我怕它以为自己正在过年。
结果前端 vite 的 proxy 那头还写着 2024。
页面一开,502。我第一反应服务器挂了,第二反应容器挂了,第三反应才是------哦,是我自己在骗我自己。
这大概就是部署最经典的体验:你排查了三层,发现毛病在第一层,还是你自己写的那一层。
5. 一键起来
bash
docker-compose up -d
一行命令,三个容器起来了。docker ps 那一片绿色的 Up,比任何周报都好看。
但注意那个状态:health: starting。
这个状态最像周一早上的我:已经在岗,但还不算健康。
给它几秒。它不是装,它真的在检查自己。
6. 前端就三个文件
6.1 一个客厅,一个厨房,一个物业
用 vite 起了个项目,干活的一共三个文件:index.html、main.js、vite.config.js。一个聊天界面,一段逻辑,一份跨域配置。
代码是 AI 生成的,我改了几处。这个我不掩饰,现在没人一个字一个字敲表单。
这三个文件的关系特别像家里那三个人:
index.html 是客厅。谁来了都先看见它,它负责好看,不负责干活。
main.js 是厨房。真正炒菜的地方,脏活都在这儿。
vite.config.js 是物业。平时没人提起,一出事所有人都在找它。
6.2 浏览器的洁癖
跨域这玩意儿,是浏览器的一种洁癖。同一个 IP,换个端口,它就觉得你们不是一家人。
你不能跟浏览器讲道理。你只能加个 proxy,让它以为你们从出生就是一家人。
js
import { defineConfig } from 'vite'
export default defineConfig({
server: {
port: 8080,
proxy: {
'/lg/': {
target: 'http://127.0.0.1:2024',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/lg/, ''),
ws: true, // 流式输出要 websocket,升级这块建议加上
},
},
},
})
6.3 就是这一行 ws
这一行我折腾最久。
没有它,流式会变成什么样?就像你听一个人讲话,但他每说完一整段才张一次嘴。你等的时候屏幕是静的;你一走神,它已经说完了。
加上它,字才一个一个往外漏。那才像在说话。人讲话是有节奏的,原来模型讲话也需要节奏。
7. 把门锁上,自己在屋里点外卖
7.1 只允许本机访问
绑域名不是必须的,是我喜欢折腾。打好的 dist 传上去,nginx 配一下。
但有一句前提:为了安全,模型接口只允许服务器本机访问,也就是只有我自己的前端能请求它。
翻译过来就是:把门锁上,然后自己在屋里点外卖。
不是我不大方,是 token 要钱。
nginx
server {
listen 80;
server_name xxxxxx.com;
# 429 兜底放 server 内部
error_page 429 /@err_429;
location = /@err_429 {
internal;
return 429 '{"error":"请求过于频繁,请稍后再试"}\n';
add_header Content-Type application/json;
}
# 拦截扫描器常敲的门
location ~* (master\.key|bootstrap\.properties|private-key|trace\.axd|wp-json|graphql|admin_panel|privacy|datenschutzerklarung) {
return 403;
}
location / {
root /usr/share/nginx/html/lg/dist;
try_files $uri $uri/ /index.html;
}
location /lg/ {
# 目标是本机的 8123,因为那边只监听 127.0.0.1
proxy_pass http://127.0.0.1:8123/;
# 保住 Host(对应 changeOrigin: true),超时也补上
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# 流式的关键
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_buffering off;
proxy_cache off;
proxy_set_header X-Accel-Buffering no;
chunked_transfer_encoding on;
# 超时,按业务调
proxy_connect_timeout 600s;
proxy_send_timeout 600s;
proxy_read_timeout 600s;
# 全局开了 gzip 的话这里关掉
gzip off;
}
}
7.2 那句客气话
429 我留了一句"请求过于频繁,请稍后再试"。写得特别客气。
但我心里想的是:别问了,再问我就破产了。
至于那些扫站的机器人,它们比亲戚还关心我,一天来八趟,专敲 master.key、wp-json 这些门。我一个都没有,我还是全给 403 了。
就像家里明明没保险柜,你还是把每个抽屉都上了锁。锁的不是钱,是心情。
7.3 别攒,来一句给一句
proxy_buffering off、proxy_cache off、X-Accel-Buffering: no、chunked_transfer_encoding on,这四个凑一起只有一个意思:别攒。
nginx 默认是个爱攒事的人,它觉得凑够一批再发更划算。这习惯放在静态文件上叫优化,放在流式对话上叫灾难。
特别像家族群里那位把八条语音合成一条六十秒的亲戚。信息一个字没少,体验全没了。
超时我给了 600 秒。不是模型真会慢到十分钟,是我先把耐心值拉满,免得它慢的时候我先崩。
gzip 我关了。压缩是要攒的,一攒就没"实时"了。省下的那点流量,换不回一个正在等字的人。
8. 它开始劝我加衣服
页面跑起来了。我问它明天北京天气,它给了温度,还补了一句:长城在山里,还得再低三五度,风大,体感更冷。
一个模型,开始劝我加衣服。
我写代码这么多年,第一次被一段 JSON 关心。
那天晚上我看它一个字一个字往外漏,忽然觉得这件事挺神:我在这边敲了几行,另一边机器上的容器里,有个东西正在替我说话,还说得比我耐心。
9. 说到底
回头看,整套流程没什么技术含量。核心就一句:本地造不出来,就去能造的地方造。
我原以为部署是修车。后来发现不是,部署是打车。车坏了别修,换一辆。
但有一点要说清楚:能换,是因为 Dockerfile 在我手上。它不依赖我那台 Mac,不依赖我家宽带,也不依赖我当天心情好不好。只要这个文件是对的,在哪儿 build 出来都一样。
这才是容器这件事真正体面的地方:它让"在我这儿能跑"这句话,变得没那么重要了。
以后谁再问我本地能不能 build,我会说:能,在服务器上。
P.S. 目前国内还是很缺AI人才的,希望更多人能真正加入到AI行业,共同促进行业进步,增强我国的AI竞争力。想要系统学习AI知识的朋友可以看看我精心打磨的教程 传送门http://blog.csdn.net/jiangjunshow,教程通俗易懂,高中生都能看懂,还有各种段子风趣幽默,从深度学习基础原理到各领域实战应用都有讲解,我22年的AI积累全在里面了。注意,教程仅限真正想入门AI的朋友,否则看看零散的博文就够了。