LangGraph 本地 build 频频翻车,最终在服务器跑通流式对话

文章目录

    • 前言
    • [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的朋友,否则看看零散的博文就够了。

相关推荐
Flynn Liang29 分钟前
同一需求三套写法:用 Spring AI、LangChain4j、AgentScope Java 各实现一个带工具调用的 RAG 助手(实战对比)
java·人工智能·大模型·架构设计·spring ai·langchain4j·agentscope
汤姆宝库40 分钟前
构建一个能用的RAG系统:切块、嵌入、混合检索与融合排序
人工智能
Dawson Zhu43 分钟前
【LLM推理与Serving⑪】MoE推理与MLA:DeepSeek-V3架构下的显存与通信权衡
人工智能·语言模型·架构·aigc·agi
github_is_geeksKai1 小时前
50 个 AI 与开源工具怎么选:从实际用途到实现边界
人工智能
IT_陈寒1 小时前
Vite的HMR把我整不会了:一个生产环境的热更新坑
前端·人工智能·后端
github_is_geeksKai1 小时前
浏览器 AI 小工具怎么选?40 个项目与 5 类商业任务的参考指南
人工智能
styshoo1 小时前
[snapshot]7-placeholder与恢复流程
人工智能
这个DBA有点耶2 小时前
库内计算推理:一条SQL背后的算子融合,和外挂方案差了几次网络往返
数据库·人工智能·sql
SelectDB2 小时前
AI Agent 的分析数据存在哪里?Trace、Prompt 与会话数据的存储选型
人工智能