在这里插入图片描述

用户等待超过3秒就会Alt+F4!本文将撕开LangGraph流式输出的黑箱,从"节点级幻灯片"跨越到"Token级打字机",手把手教你搭建一个让用户欲罢不能的实时交互 pipeline。拒绝白屏焦虑,让每一个字符都精准跳动在用户的视网膜上!

Token级流式输出:实现ChatGPT般的打字机效果

1. 流式本质:为什么非流式不可

2. API全景:stream与stream_events

3. 核心鸿沟:节点级 vs Token级

4. 后端实战:搭建Token级管道

5. 前端对接:字符跳动术

6. 避坑指南:断流与乱码

感知性能原理

用户流失陷阱

stream方法

astream_events事件

Node Streaming

Token Streaming

StateGraph设计

Event过滤逻辑

SSE ReadableStream

缓冲渲染策略

AbortController

竞态与乱码

文字目录:

  1. 流式本质:为什么非流式不可?
  2. API全景:stream与stream_events
  3. 核心鸿沟:节点级 vs Token级
  4. 后端实战:搭建Token级管道
  5. 前端对接:字符跳动术
  6. 避坑指南:断流与乱码

嗨,大家好呀,我是你的老朋友精通代码大仙。接下来我们一起学习 《LangChain核心技术与LLM项目实践》

“天下武功,唯快不破。” 但在大模型应用开发里,这句话得改一改——模型推理的速度你我说了不算,它取决于算力和参数规模。可用户才不关心你的GPU有多贵,他们只关心一个问题:“我字都打完三秒了,这破屏幕怎么还不动?”

你是不是也这样?好不容易用LangGraph搭了一个看起来很牛的Agent,RAG检索、工具调用、多轮记忆样样俱全。结果前端一测试,用户提问后整个页面像死了一样白屏五秒钟,然后"啪"地一下弹出一篇小作文。这体验别说跟ChatGPT比了,就连十年前的网页客服都不如。别慌,这章我们要啃的硬骨头,就是Token级流式输出。这玩意儿不搞定,你的Agent永远像个"断网版Siri"。


1. 流式本质:为什么非流式不可?

点题

流式输出,说白了就是让大模型像说话一样,想出一个字就吐一个字,而不是在脑子里把整篇论文都构思完了才一次性念给你听。在工程上,这叫Streaming。它解决的不是模型推理"更快"的问题,而是让用户"感觉更快"的问题——专业名词叫感知性能

用户输入

非流式

白屏等待5秒

瞬间输出全文

用户感受:卡死了

流式输出

第1秒看到第一个字

持续输出中...

用户感受:它在努力思考

痛点分析

新手最容易踩的坑,就是觉得"流式不就是把完整结果切片发送吗?我拿到结果再分成一段一段发给前端不就行了?" 大错特错!这叫"伪流式",后端该等多久还是等多久,用户前端该白屏还是白屏。

我见过太多同学这么干:先用graph.invoke()拿到完整字符串,然后写个for循环,每隔50ms往前端发两个字,模拟打字机效果。这简直就是掩耳盗铃!后端那五秒钟的延迟纹丝不动,万一模型卡在第4.9秒,你的前端连第一个假字符都发不出去。更惨的是,这种做法还会占用双倍内存——模型输出一份,你自己切片又存一份。

还有一个思维误区:认为只有大段文本才需要流式。错!哪怕模型只输出50个字,流式和非流式的体验也是云泥之别。非流式是"0到50的跳跃",流式是"0到1到2…的连续"。人眼对运动的敏感度远高于静止物体,这是写在我们基因里的。

更深层的心理学原理是进度条效应。有实验证明,让用户看到一个持续更新的进度指示,即使总时长不变,主观等待时间也会缩短40%以上。流式输出本质上就是一个不断向前滚动的文字进度条。没有它,用户面对白屏时,每一秒都像一年那么长。

解决方案

真正的流式,是从模型生成第一个Token开始就立刻往管道里塞,中间不做任何"攒够一波再发"的批处理。LangGraph底层对接的是LangChain的异步回调系统,当LLM每生成一个Token,就会触发一次on_chat_model_stream事件。我们要做的,就是把这个事件里的chunk.content像接力棒一样,第一时间传给前端。

这样做的好处立竿见影。首先,**首Token延迟(Time to First Token, TTFT)**被降到了理论最低,用户敲下回车的瞬间就能看到反馈,焦虑感瞬间瓦解。其次,用户会下意识地跟随文字流动进行阅读,整体阅读理解和留存率会显著提升。说白了,流式输出的本质不是技术炫技,而是对用户注意力的温柔劫持。

小结

流式不是可选项,是LLM交互的默认项。别再让用户对着白屏发呆,第一个Token到达的时间,决定了你的产品是否像个活物。


2. API全景:stream、astream与stream_events

点题

LangGraph给我们准备了三把流式武器:streamastreamastream_events。但很多新手从invoke毕业之后,就只会机械地把invoke改成stream,然后发现"咦,怎么还是一块一块的?" 因为你拿错了枪。

痛点分析

我见过最典型的错误用法长这样:

# 错误示范:拿着invoke的思维用stream
result = graph.invoke({"messages": [("user", "你好")]})
for char in result["messages"][-1].content:
    print(char, end="", flush=True)

这算哪门子流式?这分明是"全部生成完之后再假装打字"。后端等待的时间一点没少,你只是把输出的动作拖长了而已。更有人直接用graph.stream(),发现返回的是一个迭代器,就以为万事大吉,却不知道stream默认是按**节点(Node)**粒度返回的。

啥意思?就是你的图里有A、B、C三个节点,stream给你的是"A节点算完了,给你这一大块结果"、“B节点算完了,给你这一大块结果”。如果B节点是那个调用GPT-4的LLM节点,你依然要等到GPT-4把一整段话说完,才能收到B节点这一整块数据。前端体验就是:卡两秒,蹦一块;再卡三秒,再蹦一块。跟幻灯片似的。

还有人去研究streammode参数,发现mode="updates"只返回变更的节点状态,mode="values"返回完整状态快照,就以为mode="values"能拿到更细的东西。别试了,无论哪个mode,都是节点执行完毕后才触发一次状态推送。模型在节点内部吭哧吭哧生成Token的过程,对外是完全不可见的。

解决方案

要打通Token级流式,你必须认识astream_events。这是LangChain事件系统暴露出来的"显微镜",能让我们看到模型内部每一根毛细血管的跳动。

# 正确姿势:astream_events
async for event in graph.astream_events(
    {"messages": [("user", "讲个笑话")]},
    version="v2",
    include_names=["my_llm"]  # 关键:指定要深挖的组件名
):
    if event["event"] == "on_chat_model_stream":
        chunk = event["data"]["chunk"].content
        print(chunk, end="", flush=True)

注意这里的version="v2"include_namesastream_events会吐出大量事件:on_chain_starton_tool_starton_chat_model_start等等。如果你不设过滤,就像打开了消防栓喝水,直接被信息洪流呛死。include_namesinclude_types就是我们的筛子,只保留LLM相关的流事件。

还有个细节:streamastream是同步与异步的区别,但在Token级场景下,异步astream_events几乎是唯一选择。因为网络IO和模型推理都是慢操作,不用异步你的服务吞吐量会直接崩盘。想象一下,十个用户同时提问,你的服务端如果阻塞在第一个模型的推理上,剩下九个用户连第一个字都看不到。

小结

invoke是手写信,stream是寄包裹,astream_events才是发微信消息。想让用户看到逐字蹦的打字机,只有astream_events能帮你把管道铺到模型最深处。


3. 核心鸿沟:节点级流式与Token级流式

点题

这是本文的"天眼",也是新手和最让老手翻车的分水岭。LangGraph默认的流式,是节点级(Node-level Streaming);而ChatGPT那种丝滑的打字机,是Token级(Token-level Streaming)。两者之间的鸿沟,比从北京到纽约还宽。

痛点分析

让我给你画个图,直观感受一下什么叫"节点级幻灯片":

等待2秒

等待5秒

用户提问

Retriever节点

输出检索文档整块

LLM节点

输出回答整块

用户看到:两块跳变

用户提问

Token级流式

T1

T2

T3

...持续跳动

用户看到:打字机效果

假设你做了一个RAG客服机器人。用户问:“你们支持退款吗?”

节点级流式的体验是:用户提问后,屏幕空白了整整2秒钟(这时候Retriever在向量数据库里狂搜),然后"啪"地出来三块文档摘要。接着又空白了5秒钟(LLM在组织语言),最后"啪"地出来一大段退款政策。

用户的内心OS:“这玩意儿是不是卡死了?哦,出来了。等等,又卡死了?哦,又出来了。” 这种"抽风式"输出,比直接等7秒还折磨人。因为人在等待时最怕的不是长延迟,而是不确定的等待。你不知道下一秒它会不会动,所以每一毫秒都是煎熬。

更隐蔽的坑在于:很多新手以为graph.stream(mode="values")就能拿到细粒度数据。确实,mode="values"能给你状态更新,但如果状态更新是在节点执行结束后才触发的,你拿到的依然是节点最终产物,而不是Token。

解决方案

跨越鸿沟的唯一途径,是理解LangChain的回调传播机制。当LLM节点内部调用ChatModel时,模型每生成一个Token,会通过回调系统向外广播on_chat_model_stream事件。这个事件不经过StateGraph的状态快照,而是直接从模型层"渗漏"出来的。

我们要做的,就是在图的执行过程中"偷听"这些广播:

# 核心逻辑:在事件流中捕捉Token
async for event in graph.astream_events(input_state, version="v2"):
    kind = event["event"]
    if kind == "on_chat_model_stream":
        token = event["data"]["chunk"].content
        # 这里的token就是一个字或一个词!
        await send_to_frontend(token)

关键点在于:这个事件发生在节点内部,而不是节点边界。也就是说,LLM节点还在执行中,状态还没有写回StateGraph,但Token已经流出来了。这才是真正的"实时"。

为了更精准地捕获,你还需要了解事件的命名空间。比如如果你用的是ChatOpenAI,事件名可能是on_chat_model_stream;如果你在LLM外面包了一个自定义节点,可能需要通过metadata打上标签,再用include_tags过滤。这就好比你要在嘈杂的无线电波段里,精确找到模型发报员的频率。

小结

节点级流式是"按幕放映",Token级流式是"逐帧播放"。做产品不是做PPT,别让用户体验"节点级幻灯片",要的是丝滑的"Token级电影"。


4. 后端实战:搭建Token级流式管道

点题

原理听懂了,手开始痒了吧?这一节我们把后端整个pipeline搭起来。从StateGraph的定义,到FastAPI的SSE推送,给你一个完整的"Token级流式发动机"。

痛点分析

新手在这一步最常犯的错,是事件过滤不干净。一运行astream_events,控制台直接被刷屏,各种on_chain_starton_chain_endon_tool_start乱飞,根本找不到哪个才是Token。有人干脆不用过滤,在前端自己判断,结果前端代码臃肿得像屎山。

还有人搞不清chunk的结构。有时候event["data"]["chunk"]是个AIMessageChunk,有时候是个字典,直接.content会报AttributeError。更惨的是,有些Token是空的字符串""或者是空格,不做过滤直接发给前端,导致前端莫名其妙地闪烁或排版错乱。

另一个经典翻车现场是:在流式传输过程中,如果某个中间节点抛了异常,整个流直接断开,前端卡在半截,用户看着一段不完整的回答干瞪眼,连错误提示都没有。

如果你用了工具调用(Tool Calling),坑更深。模型在决定调用工具时,输出的不是content,而是tool_calls字段。如果你只监听chunk.content,会发现某个时刻突然不输出文字了,但其实模型正在"思考"要调哪个工具。

解决方案

我们先定义一个最简单的图,里面只有一个LLM节点,方便看清流式脉络:

from langgraph.graph import StateGraph, MessagesState, END
from langchain_openai import ChatOpenAI

# 1. 定义模型,开启流式
model = ChatOpenAI(model="gpt-4o", streaming=True)

# 2. 构建图
def call_model(state: MessagesState):
    # 这里返回的是 AIMessage,但在 astream_events 里我们偷听的是中间流
    response = model.invoke(state["messages"])
    return {"messages": [response]}

builder = StateGraph(MessagesState)
builder.add_node("llm", call_model)
builder.set_entry_point("llm")
builder.add_edge("llm", END)
graph = builder.compile()

注意,这里的model.invoke在节点内部其实会触发流式回调,即使我们用invoke,在astream_events的监听下也能抓到Token。但在生产环境中,更推荐让节点本身也异步化,比如用await model.ainvoke

接下来是FastAPI + SSE的核心推送逻辑。SSE(Server-Sent Events)是Web环境下推送流式数据的最佳拍档,比WebSocket轻量,且天然支持HTTP/1.1:

from fastapi import FastAPI
from fastapi.responses import StreamingResponse
import json
import asyncio

app = FastAPI()

async def token_generator(user_input: str):
    inputs = {"messages": [("human", user_input)]}
    
    async for event in graph.astream_events(inputs, version="v2"):
        if event["event"] == "on_chat_model_stream":
            chunk = event["data"]["chunk"]
            if hasattr(chunk, "content"):
                token = chunk.content
                # 过滤无效Token,避免空包扰动前端
                if token:
                    # SSE 协议要求 data: 开头,两个换行结束
                    yield f"data: {json.dumps({'token': token})}\n\n"
            # 如果是工具调用,也可以在这里处理 tool_calls
            elif hasattr(chunk, "tool_calls") and chunk.tool_calls:
                yield f"data: {json.dumps({'tool_call': chunk.tool_calls})}\n\n"
    
    # 发送结束标记,让前端知道可以关闭流了
    yield f"data: {json.dumps({'token': '[DONE]'})}\n\n"

@app.post("/chat")
async def chat_stream(request: dict):
    user_input = request.get("message", "")
    return StreamingResponse(
        token_generator(user_input),
        media_type="text/event-stream",
        headers={"X-Accel-Buffering": "no"}  # 禁用Nginx缓冲
    )

看到没?这里的关键是StreamingResponse,它会把我们的异步生成器变成一个持续的HTTP响应流。yield出来的每一行都遵循SSE格式:data: {...}\n\n。前端只要建立一个EventSource连接,就能像接自来水一样接到一个个Token。

还有个小技巧:如果你的图里有工具调用,一定要给LLM节点取个名字,比如name="agent_llm",然后在astream_events里用include_names=["agent_llm"],这样就不会被工具节点的噪音干扰。

另外注意headers={"X-Accel-Buffering": "no"},这是给Nginx看的。很多生产环境用Nginx做反向代理,默认会缓冲后端响应,导致流式变批处理。这个Header告诉Nginx:“别攒了,来一点发一点!”

小结

后端搭建的核心就三件事:StateGraph定义好LLM节点、astream_events精准过滤on_chat_model_stream、SSE协议格式化推送。发动机装好了,接下来就看前端怎么把这股Token流酿成美酒。


5. 前端对接:把SSE流变成屏幕上跳动的字符

点题

后端的水管铺好了,前端的任务就是把这股"字符水流"翻译成人类视觉能享受的"打字机动画"。这一步做不好,后端的努力全白费。

痛点分析

我见过最粗暴的前端代码是这样的:

// 错误示范:直面暴力
const evtSource = new EventSource("/chat");
evtSource.onmessage = (event) => {
    const data = JSON.parse(event.data);
    document.getElementById("chat-box").innerHTML += data.token;
};

这代码问题大了去了。第一,直接拼innerHTML,如果模型输出里有<>,直接被浏览器当成HTML标签解析,轻则排版错乱,重则XSS攻击。第二,没有任何打字机效果,因为SSE来一个Token就渲染一个,如果Token只是一个标点符号,用户几乎感知不到跳动。第三,Markdown格式符号(比如**)会直接暴露出来,因为没做实时渲染。

还有人用fetch拿到ReadableStream之后,不会处理UTF-8解码,结果中文被截断成半个字符,显示成"锟斤拷"或者乱码方块。更有人每次收到Token都触发React的setState,结果组件疯狂重渲染,页面卡成PPT。

解决方案

现代浏览器里,我更推荐用fetch + ReadableStream,比EventSource更灵活(比如可以发POST请求带复杂JSON体):

function escapeHtml(text) {
    const div = document.createElement("div");
    div.textContent = text;
    return div.innerHTML;
}

function renderMarkdown(text) {
    // 这里用你项目里的Markdown解析器,如marked.parse
    // 注意:marked.parse 是同步的,实时渲染无压力
    return marked.parse(text);
}

async function streamChat(userInput) {
    const response = await fetch("/chat", {
        method: "POST",
        headers: {"Content-Type": "application/json"},
        body: JSON.stringify({message: userInput})
    });
    
    const reader = response.body.getReader();
    const decoder = new TextDecoder("utf-8");
    let buffer = "";
    let displayBuffer = "";
    const chatBox = document.getElementById("chat-box");
    
    // 创建一个专门放当前回答的容器
    const messageDiv = document.createElement("div");
    messageDiv.className = "message assistant";
    chatBox.appendChild(messageDiv);
    
    while (true) {
        const {done, value} = await reader.read();
        if (done) break;
        
        // 关键:使用stream模式解码,处理多字节字符截断
        buffer += decoder.decode(value, {stream: true});
        
        // SSE 格式是 data: {...}\n\n,可能一次收到多行
        const lines = buffer.split("\n\n");
        buffer = lines.pop(); // 最后一行可能不完整,留到下次
        
        for (const line of lines) {
            if (line.startsWith("data: ")) {
                const jsonStr = line.slice(6);
                if (jsonStr === "[DONE]") continue;
                
                const parsed = JSON.parse(jsonStr);
                const token = parsed.token;
                displayBuffer += token;
            }
        }
        
        // 实时渲染Markdown,转义防XSS后再解析
        messageDiv.innerHTML = renderMarkdown(escapeHtml(displayBuffer));
        messageDiv.scrollIntoView({behavior: "smooth", block: "end"});
    }
}

看到那个decoder.decode(value, {stream: true})了吗?这就是防中文乱码的命门。网络传输切分数据包时,一个UTF-8中文字可能被切成两半。TextDecoderstream: true会帮你缓存不完整的字节,等下次数据包来了再拼起来。

但真正的"ChatGPT味"还不止于此。你有没有发现,ChatGPT的字不是来一个跳一个,而是像人打字一样有节奏感的?这背后通常是缓冲区+定时器策略。我们可以维护一个displayBuffer,每次收到新Token时更新它,但用requestAnimationFrame以固定的屏幕刷新率来刷新DOM:

let pendingText = "";
let isRendering = false;
const messageDiv = document.getElementById("current-message");

function startRenderLoop() {
    isRendering = true;
    const step = () => {
        if (pendingText && messageDiv) {
            messageDiv.innerHTML = renderMarkdown(escapeHtml(pendingText));
            messageDiv.scrollIntoView({behavior: "auto", block: "end"});
        }
        if (isRendering) requestAnimationFrame(step);
    };
    requestAnimationFrame(step);
}

// 在收到SSE时,只更新pendingText,不直接操作DOM
pendingText += newToken;

这样做的好处是:即使模型一秒内发了50个Token,DOM也只更新60次左右(按屏幕刷新率),既流畅又省性能。用户看到的,就是那种"嗒、嗒、嗒"的打字机节奏,而不是"唰"地一下闪过一行。

小结

前端的使命是翻译。把技术层面的SSE流,翻译成人类感官层面的叙事节奏。缓冲渲染、Markdown实时解析、UTF-8安全解码,三者缺一不可。


6. 避坑指南:断流、乱码、竞态与性能陷阱

点题

Demo在本地跑通了,别急着开香槟。生产环境是残酷的修罗场,用户的网络可能时断时续,手指可能疯狂连点,浏览器可能开着三十个标签页。流式输出的最后一公里,布满了暗雷。

痛点分析

最经典的灾难场景:竞态(Race Condition)。用户连珠炮似的问了三个问题,前两个还没回答完,第三个已经发出去了。因为三个SSE连接并发,前端的显示区域里,三个回答的字符像麻花一样拧在一起。A回答蹦出一个"我",B回答插进来一个"你",C回答又塞进来一个"它"。用户当场懵圈:“这AI精神分裂了?”

另一个常见事故是断流。用户WiFi闪断了一下,SSE连接静默死亡,前端既没有收到[DONE],也没有触发onerror,屏幕上永远停在一半的回答,像个没说完的悬念故事。

还有内存泄漏。新手容易在useEffect里开了EventSource,却忘了在组件卸载时关闭,或者忘了AbortController。用户聊了一下午,浏览器内存炸了,页面直接崩溃。

如果你部署在Nginx后面,还会遇到代理缓冲陷阱。Nginx默认会把后端响应攒到一定大小再发给客户端,你的流式输出在Nginx这里被硬生生憋成了批处理,本地测试好好的,一上生产又变回白屏等5秒。

解决方案

首先,必须给每个请求配一个AbortController,实现"一问一锁":

let controller = null;

async function sendMessage(text) {
    // 1. 取消上一个未完成的请求
    if (controller) controller.abort();
    controller = new AbortController();
    
    // 2. UI状态锁:防止重复提交
    setLoading(true);
    disableInput();
    
    try {
        const response = await fetch("/chat", {
            method: "POST",
            signal: controller.signal, // 绑定中断信号
            body: JSON.stringify({message: text})
        });
        
        const reader = response.body.getReader();
        // ... 读取逻辑
        
    } catch (err) {
        if (err.name === "AbortError") {
            console.log("用户主动中断或新请求覆盖");
        } else {
            showError("网络开小差了,请稍后重试");
        }
    } finally {
        setLoading(false);
        enableInput();
        controller = null;
    }
}

看到没?每次发新消息,先abort()掉老的。这保证了前端永远只处理"最新问题"的流。如果用户疯狂连点,只有最后一次点击生效,前面的都被干净利落地掐断。

后端也要配合。FastAPI的StreamingResponse在客户端断开时,会抛出异常或取消生成器。我们要捕获这个异常,避免服务端资源白白浪费:

async def token_generator(user_input: str):
    try:
        async for event in graph.astream_events(...):
            # ... yield token
            pass
    except asyncio.CancelledError:
        # 客户端断开了,清理资源
        print("客户端断开,流式生成已取消")
        raise

关于断线重连,SSE原生支持Last-Event-ID机制,但在大模型场景下,断流后很难从中间恢复(因为模型状态已经丢了)。更务实的做法是:前端检测到onerror或连接关闭时,不要自动重连继续输出,而是给用户一个"重试"按钮,或者直接丢弃不完整回答,重新发送请求。毕竟,让用户看一段从半截续上的AI胡话,比重生成更可怕。

性能上,记得给SSE加心跳包。如果模型思考时间很长(比如用了CoT,几十秒不输出Token),Nginx或CDN可能会认为连接死掉了,直接掐掉。我们可以在后端每隔15秒yield一个注释行(SSE注释以:开头):

yield ":heartbeat\n\n"

前端忽略注释行即可,但连接会被保活。

如果你是React用户,千万别忘了在useEffect的return函数里做cleanup:

useEffect(() => {
    const ctrl = new AbortController();
    streamChat(ctrl.signal);
    return () => ctrl.abort(); // 组件卸载时断开
}, []);

小结

流式体验的终点不是"能动",而是"稳如老狗"。竞态控制、中断传播、连接保活,这三大护法没就位之前,你的打字机效果只是个脆弱的花架子。


写在最后

学到这里,你已经掌握了LangGraph流式输出的全链路心法。从感知性能的心理学本质,到astream_events的事件过滤,再到FastAPI的SSE推送,最后到前端的缓冲渲染与竞态处理——这条链路打通,你的产品才真正有了"呼吸感"。

说实话,做LLM应用开发,模型能力决定了你的下限,但交互体验决定了你的上限。一个会打字机流式输出的普通模型,给人的观感往往比一个字一个字死等的顶级模型还要"聪明"。因为用户看得见过程,才愿意相信结果。

编程这条路,坑总是比平坦大道多。但每一次把白屏变成跳动字符的尝试,都是在给用户传递一个温柔的信号:“我在听,我在思考,我在努力回答你。” 保持这份对细节的执着,保持对用户体验的敬畏,你写出来的代码,就会带着温度。

天下武功,唯快不破;交互体验,唯流式不破。去让你的LangGraph活起来吧!

关注私信备注:“资料代找获取”,全网计算机学习资料代找:例如:
《课程:AI 大模型工程师系统课程 (22 章完整版 持续更新)》
《课程:AI 大模型系统实战课第四期 (2026 年开课 持续更新)》
《课程:2026 年 AGI 大模型系统课 23 期》
《课程:2026 年 AGI 大模型系统课 21 期》
《课程:AI 大模型实战课 8 期 (2026 年 2 月最新完结版)》
《课程:AI 大模型系统实战课三期》
《课程:AI 大模型系统课程 (2026 年 2 月开课 持续更新)》
《课程:AI 大模型全阶课程 (2025 年 12 月开课 2026 年 6 月结课)》
《课程:AI 大模型工程师全阶课程 (2025 年 10 月开课 2026 年 4 月结课)》
《课程:2026 年最新大模型 Agent 开发系统课 (持续更新)》
《课程:LLM 多模态视觉大模型系统课》
《课程:大模型 AI 应用开发企业级项目实战课 (2026 年 1 月开课)》
《课程:大模型智能体线上速成班 V2.0》
《课程:Java+AI 大模型智能应用开发全阶课》
《课程:Python+AI 大模型实战视频教程》
《书籍:软件工程 3.0: 大模型驱动的研发新范式.pdf》
《课程:人工智能大模型系统课 (2026 年 1 月底完结版)》
《课程:AI 大模型零基础到商业实战全栈课第五期》
《课程:Vue3.5+Electron + 大模型跨平台 AI 桌面聊天应用实战 (2025)》
《课程:AI 大模型实战训练营 从入门到实战轻松上手》
《课程:2026 年 AI 大模型 RAG 与 Agent 智能体项目实战开发课》
《课程:大模型训练营配套补充资料》

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐