普通视图
🌰在 OpenLayers 中实现图层裁切
taro项目踩坑指南——(一)
小程序 Markdown 渲染血泪史:mp-html 组件从入门到放弃再到重生
从零构建本地AI应用:React与Ollama全栈开发实战
Cursor 最新发现:超大型项目 AI 也能做了,上百个 Agent 一起上
async/await : 一场生成器和 Promise的里应外合
Message组件和Vue3 进阶:手动挂载组件与 Diff 算法深度解析
Vue转React学习笔记(1): 关于useEffect的困惑和思考
前端路由不再难:React Router 从入门到工程化
5大核心分析维度+3种可视化方案:脑肿瘤大数据分析系统全解析 毕业设计 选题推荐 毕设选题 数据分析 机器学习
ArcGIS Pro 实现影像波段合成
2026 年 Node.js + TS 开发:别再纠结 nodemon 了,聊聊热编译的最优解
在开发 Node.js 服务端时,“修改代码 -> 自动生效”的开发体验(即热编译/热更新)是影响效率的关键。随着 Node.js 23+ 原生支持 TS 以及 Vite 5 的普及,我们的工具链已经发生了巨大的更迭。
今天我们深度拆解三种主流的 Node.js TS 开发实现方式,帮你选出最适合 2026 年架构的方案。
一、 方案对比大盘点
| 方案 | 核心原理 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| tsx (Watch Mode) | 基于 esbuild 的极速重启 | 零配置、性能强、生态位替代 nodemon | 每次修改重启整个进程,状态丢失 | 小型服务、工具脚本 |
| vite-node | 基于 Vite 的模块加载器 | 完美继承 Vite 配置、支持模块级 HMR | 配置相对复杂,需手动处理 HMR 逻辑 | 中大型 Vite 全栈项目 |
| Node.js 原生 | Node 23+ Type Stripping | 无需第三方依赖,官方标准 | 需高版本 Node,功能相对单一 | 追求极简、前瞻性实验 |
二、 方案详解
- 现代替代者:
tsx—— 告别 nodemon + ts-node
过去我们常用 nodemon --exec ts-node,但在 ESM 时代,这套组合经常报 ERR_UNKNOWN_FILE_EXTENSION 错误。
tsx 内部集成了 esbuild,它是目前 Node 18+ 环境下最稳健的方案。
-
实现热编译:
bash
npx tsx --watch src/index.ts请谨慎使用此类代码。
-
为什么选它: 它不需要额外的加载器配置(--loader),且
watch模式非常智能,重启速度在毫秒级。
- 开发者体验天花板:
vite-node—— 真正的 HMR
如果你已经在项目中使用 Vite 5,那么 vite-node 是不二之选。它不仅是“重启”,而是“热替换”。
-
核心优势:
-
共享配置:直接复用
vite.config.ts中的alias和插件。 - 按需编译:只编译当前运行到的模块,项目越大优势越明显。
-
共享配置:直接复用
-
实现热更新(不重启进程):
typescript
// src/index.ts import { app } from './app'; let server = app.listen(3000); if (import.meta.hot) { import.meta.hot.accept('./app', (newModule) => { server.close(); // 优雅关闭旧服务 server = newModule.app.listen(3000); // 启动新逻辑,DB连接可复用 }); }请谨慎使用此类代码。
- 官方正统:Node.js 原生支持
如果你能使用 Node.js 23.6+ ,那么可以摆脱所有构建工具。
-
运行:
node --watch src/index.ts - 点评: 这是未来的趋势,但在 2026 年,由于生产环境往往还停留在 Node 18/20 LTS,该方案目前更多用于本地轻量级开发。
三、 避坑指南:Vite 5 打包 Node 服务的报错
在实现热编译的过程中,如果你尝试用 Vite 打包 Node 服务,可能会遇到:
Invalid value for option "preserveEntrySignatures" - setting this option to false is not supported for "output.preserveModules"
原因: 当你开启 preserveModules: true 想保持源码目录结构输出时,Rollup 无法在“强制保留模块”的同时又“摇树优化(Tree Shaking)”掉入口导出。
修复方案:
在 vite.config.ts 中明确设置:
typescript
build: {
rollupOptions: {
preserveEntrySignatures: 'exports-only', // 显式声明保留导出
output: {
preserveModules: true
}
}
}
请谨慎使用此类代码。
四、 总结:我该选哪个?
-
如果你只想快速写个接口,不想折腾配置:请直接使用
tsx。它是 2026 年 nodemon 的完美继承者。 -
如果你在做复杂全栈项目,或者有大量的路径别名:请使用
vite-node。它能让你在 Node 端获得跟前端 React/Vue 编写时一样丝滑的 HMR 体验。 -
如果是为了部署生产环境:无论开发环境用什么,生产环境请务必通过
vite build产出纯净的 JS,并使用node dist/index.js运行。
使用 LangChain.js 在node端 连接glm大模型示例
使用 LangChain 在后端连接大模型:实践指南 🚀
本文以实战项目代码为例,包含完整后端接入、流式(SSE)实现、前端接收示例与调试方法,读者可直接复制运行。
介绍 ✨
随着大模型在各类应用中的普及,后端如何稳健地接入并把模型能力以 API/流式方式对外提供,成为常见需求。本文基于 LangChain(JS)演示如何在 Node.js 后端(Koa)中:
- 初始化并调用大模型(示例使用智谱 GLM 的接入方式)
- 支持普通请求与流式(Server-Sent Events,SSE)响应
- 在前端用 fetch 读取流并实现打字机效果
适用人群:熟悉 JS/TS、Node.js、前端基本知识,想把模型能力放到后端并对外提供 API 的工程师。
一、准备与依赖 🧩
环境:Node.js 16+。
安装依赖(Koa 示例):
npm install koa koa-router koa-bodyparser @koa/cors dotenv
npm install @langchain/openai @langchain/core
在项目根创建 .env:
ZHIPU_API_KEY=你的_api_key_here
PORT=3001
提示:不同模型提供方的 baseURL 与认证字段会不同,请根据提供方文档调整。
二、后端:服务封装(chatService)🔧
把模型调用封装到服务层,提供普通调用与流式调用接口:
// backend/src/services/chatService.js
import { ChatOpenAI } from "@langchain/openai";
import { HumanMessage, SystemMessage } from "@langchain/core/messages";
const chatService = {
llm: null,
init() {
if (!this.llm) {
this.llm = new ChatOpenAI({
openAIApiKey: process.env.ZHIPU_API_KEY,
modelName: "glm-4.5-flash",
temperature: 0.7,
configuration: { baseURL: "https://open.bigmodel.cn/api/paas/v4/" },
});
}
},
async sendMessage(message, conversationHistory = []) {
this.init();
const messages = [
new SystemMessage("你是一个有帮助的 AI 助手,使用中文回答问题。"),
...conversationHistory.map((msg) =>
msg.role === "user"
? new HumanMessage(msg.content)
: new SystemMessage(msg.content),
),
new HumanMessage(message),
];
try {
const response = await this.llm.invoke(messages);
return {
success: true,
content: response.content,
usage: response.usage_metadata,
};
} catch (error) {
console.error("聊天服务错误:", error);
return { success: false, error: error.message };
}
},
async sendMessageStream(message, conversationHistory = []) {
this.init();
const messages = [
new SystemMessage("你是一个有帮助的 AI 助手,使用中文回答问题。"),
...conversationHistory.map((msg) =>
msg.role === "user"
? new HumanMessage(msg.content)
: new SystemMessage(msg.content),
),
new HumanMessage(message),
];
try {
// 假设 llm.stream 返回异步迭代器,逐 chunk 返回 { content }
const stream = await this.llm.stream(messages);
return { success: true, stream };
} catch (error) {
console.error("流式聊天服务错误:", error);
return { success: false, error: error.message };
}
},
};
export default chatService;
说明:实际 SDK 接口名(如 invoke、stream)请依据你所用的 LangChain / provider 版本调整。
三、控制器:普通与 SSE 流式(Koa)🌊
SSE 要点:需要直接写原生 res,并设置 ctx.respond = false,防止 Koa 在中间件链结束时覆盖响应或返回 404。
// backend/src/controllers/chatController.js
import chatService from "../services/chatService.js";
const chatController = {
async sendMessage(ctx) {
try {
const { message, conversationHistory = [] } = ctx.request.body;
if (!message) {
ctx.status = 400;
ctx.body = { success: false, error: "消息内容不能为空" };
return;
}
const result = await chatService.sendMessage(
message,
conversationHistory,
);
if (result.success) ctx.body = result;
else {
ctx.status = 500;
ctx.body = result;
}
} catch (error) {
ctx.status = 500;
ctx.body = { success: false, error: "服务器内部错误" };
}
},
async sendMessageStream(ctx) {
try {
const { message, conversationHistory = [] } = ctx.request.body;
if (!message) {
ctx.status = 400;
ctx.body = { success: false, error: "消息内容不能为空" };
return;
}
const result = await chatService.sendMessageStream(
message,
conversationHistory,
);
if (!result.success) {
ctx.status = 500;
ctx.body = result;
return;
}
ctx.set("Content-Type", "text/event-stream");
ctx.set("Cache-Control", "no-cache");
ctx.set("Connection", "keep-alive");
ctx.status = 200;
// 关键:让我们直接操作 Node 原生 res
ctx.respond = false;
for await (const chunk of result.stream) {
const content = chunk.content;
if (content) ctx.res.write(`data: ${JSON.stringify({ content })}\n\n`);
}
ctx.res.write("data: [DONE]\n\n");
ctx.res.end();
} catch (error) {
console.error("流式控制器错误:", error);
ctx.status = 500;
ctx.body = { success: false, error: "服务器内部错误" };
}
},
};
export default chatController;
四、路由与启动 🌐
// backend/src/routes/index.js
import Router from "koa-router";
import chatController from "../controllers/chatController.js";
const router = new Router({ prefix: "/api" });
router.post("/chat", chatController.sendMessage);
router.post("/chat/stream", chatController.sendMessageStream);
export default router;
// backend/src/app.js
import dotenv from "dotenv";
import Koa from "koa";
import bodyParser from "koa-bodyparser";
import cors from "@koa/cors";
import router from "./routes/index.js";
dotenv.config();
const app = new Koa();
app.use(cors({ origin: "*", credentials: true }));
app.use(bodyParser());
app.use(router.routes());
app.use(router.allowedMethods());
app.listen(process.env.PORT || 3001, () => console.log("Server running"));
五、前端:接收 SSE 流并实现“打字机”效果 ⌨️
前端用 fetch + ReadableStream 读取 SSE 后端发送的 chunk(格式为 data: {...}\n\n)。下面给出简洁示例:
// frontend/src/services/chatService.ts (核心片段)
export const sendMessageStream = async (
message,
conversationHistory,
onChunk,
onComplete,
onError,
) => {
try {
const response = await fetch("/api/chat/stream", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ message, conversationHistory }),
});
if (!response.ok) throw new Error("网络请求失败");
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value);
// 简单按行解析 SSE data: 行
const lines = chunk.split("\n");
for (const line of lines) {
if (line.startsWith("data: ")) {
const data = line.slice(6);
if (data === "[DONE]") {
onComplete();
return;
}
try {
const parsed = JSON.parse(data);
if (parsed.content) onChunk(parsed.content);
} catch (e) {}
}
}
}
} catch (err) {
onError(err.message || "流式请求失败");
}
};
打字机效果思路(前端)📌:
- 后端 chunk 通常是按小段返回,前端把每个 chunk 追加到
buffer。 - 用一个定时器以固定速度(如 20–40ms/字符)把
buffer的字符逐个移动到展示内容,使文本逐字出现。 - onComplete 时快速显示剩余字符并停止定时器。
你可以参考项目中 App.tsx 的实现(已实现逐 chunk 追加与打字机渲染逻辑)。
App.tsx
import React, { useState } from "react";
import MessageList from "./components/MessageList";
import ChatInput from "./components/ChatInput";
import { chatService, Message } from "./services/chatService";
import "./App.css";
const App: React.FC = () => {
const [messages, setMessages] = useState<Message[]>([]);
const [isStreaming, setIsStreaming] = useState(false);
const handleSendMessage = async (message: string) => {
const userMessage: Message = { role: "user", content: message };
setMessages((prev) => [...prev, userMessage]);
setIsStreaming(true);
let assistantContent = "";
const conversationHistory = messages;
await chatService.sendMessageStream(
message,
conversationHistory,
(chunk: string) => {
assistantContent += chunk;
setMessages((prev) => {
const newMessages = [...prev];
const lastMessage = newMessages[newMessages.length - 1];
if (lastMessage && lastMessage.role === "assistant") {
lastMessage.content = assistantContent;
} else {
newMessages.push({ role: "assistant", content: assistantContent });
}
return newMessages;
});
},
() => {
setIsStreaming(false);
},
(error: string) => {
console.error("流式响应错误:", error);
setMessages((prev) => [
...prev,
{ role: "assistant", content: "抱歉,发生了错误,请稍后重试。" },
]);
setIsStreaming(false);
},
);
};
return (
<div className="app">
<header className="app-header">
<h1>🤖 LangChain + 智谱 GLM</h1>
<p>AI 聊天助手</p>
</header>
<main className="app-main">
<MessageList messages={messages} isStreaming={isStreaming} />
<ChatInput onSendMessage={handleSendMessage} disabled={isStreaming} />
</main>
</div>
);
};
export default App;
MessageList
import React, { useRef, useEffect } from "react";
import { Message } from "../services/chatService";
interface MessageListProps {
messages: Message[];
isStreaming: boolean;
}
const MessageList: React.FC<MessageListProps> = ({ messages, isStreaming }) => {
const messagesEndRef = useRef<HTMLDivElement>(null);
useEffect(() => {
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
}, [messages, isStreaming]);
return (
<div className="message-list">
{messages.length === 0 ? (
<div className="empty-state">
<p>👋 欢迎使用 LangChain + 智谱 GLM 聊天助手!</p>
<p>开始提问吧,我会尽力帮助你 💬</p>
</div>
) : (
messages.map((msg, index) => (
<div key={index} className={`message ${msg.role}`}>
<div className="message-avatar">
{msg.role === "user" ? "👤" : "🤖"}
</div>
<div className="message-content">
<div className="message-role">
{msg.role === "user" ? "用户" : "AI 助手"}
</div>
<div className="message-text">{msg.content}</div>
</div>
</div>
))
)}
{isStreaming && (
<div className="message assistant">
<div className="message-avatar">🤖</div>
<div className="message-content">
<div className="message-role">AI 助手</div>
<div className="message-text streaming">
<span className="typing-indicator">...</span>
</div>
</div>
</div>
)}
<div ref={messagesEndRef} />
</div>
);
};
export default MessageList;
六、调试建议与常见坑 ⚠️
- 404:确认前端请求路径
/api/chat/stream与路由前缀一致;开发时若使用 Vite,请在vite.config.ts配置 proxy 到后端端口。 - SSE 返回 404/空响应:确认控制器里
ctx.respond = false已设置,并且在设置 header 后立即开始ctx.res.write。 - headers already sent:不要在写入原生
res后再次设置ctx.body或ctx.status。 - CORS:若跨域,确保后端 CORS 配置允许
Content-Type、Authorization等必要 header。
七、快速上手测试命令 🧪
启动后端:
# 在 backend 目录下
node src/app.js
# 或使用 nodemon
npx nodemon src/app.js
用 curl 测试流式(查看快速返回流):
curl -N -H "Content-Type: application/json" -X POST http://localhost:3001/api/chat/stream -d '{"message":"你好","conversationHistory":[]} '
你应能看到类似:
data: {"content":"你"}
data: {"content":"好"}
data: [DONE]
推荐一个超好用的全栈通用瀑布流布局库 universal-waterfall-layout
在前端开发中,瀑布流布局(Masonry Layout)一直是一个让人又爱又恨的需求。爱它是因为它能极大提升图片类应用(如 Pinterest、小红书)的视觉体验和空间利用率;恨它是因为实现起来坑点满满——图片高度不固定、窗口缩放重排、框架兼容性问题等等。
今天给大家推荐一个最近发现的宝藏开源库:universal-waterfall-layout。
正如其名,不仅仅是给 Vue 用,也不仅仅是给 React 用,它是一个**通用(Universal)**的解决方案。
为什么选择它?
现在的市面上的瀑布流插件往往绑定特定框架,或者包体积过大。而 universal-waterfall-layout 有几个非常击中痛点的特性:
1. 🚀 全栈通用,一次学习
无论你是 Vue 3 爱好者,还是 React 拥趸,甚至是 原生 JS 的死忠粉,这个库都能无缝支持。它的核心逻辑是用原生 TypeScript 编写的,零依赖,仅仅在核心之上封装了轻量级的 Vue 和 React 组件适配层。
2. ⚡️ 内置"刚需"功能
很多瀑布流插件只管布局,不管体验。但这个库在 v1.0.3 版本中贴心地加入了很多实战刚需功能:
- 骨架屏 (Skeleton Loading): 数据还没回来?直接通过 props 开启加载状态,自带脉冲动画的骨架屏,拒绝白屏。
-
空状态 (Empty State): 列表为空时自动显示占位提示,不再需要自己写
v-if/v-else。 - 图片懒加载 (Lazy Load): 内置原生图片懒加载支持,性能直接拉满,不需要再引入额外的 lazyload 库。
3. 📐 两种核心布局策略
它不仅仅是简单的“排列”,还提供了两种最常用的响应式策略:
-
固定列宽 (Fixed Column Width): 指定每列
250px,容器会自动计算能放下几列,并自动居中。适合大屏展示。 - 固定列数 (Fixed Column Count): 指定“我要 3 列”,无论屏幕多大,宽度都会自动拉伸填满。适合移动端 H5。
快速上手
安装
非常简单,体积也很小:
npm install universal-waterfall-layout
# 或者
pnpm add universal-waterfall-layout
via Vue 3
在 Vue 中使用非常丝滑,你可以直接利用 slot 来自定义加载中和空状态的 UI:
<template>
<Waterfall
:gap="20"
:columnWidth="250"
:loading="isLoading"
:lazyload="true"
>
<!-- 数据列表 -->
<div v-for="item in items" :key="item.id" class="card">
<img :src="item.image" alt="" />
<p>{{ item.title }}</p>
</div>
<!-- 自定义加载骨架 -->
<template #loading>
<div class="my-skeleton">正在加载精彩内容...</div>
</template>
<!-- 自定义空状态 -->
<template #empty>
<div class="empty-box">暂无相关数据</div>
</template>
</Waterfall>
</template>
<script setup>
import { Waterfall } from 'universal-waterfall-layout/vue';
import { ref } from 'vue';
const isLoading = ref(true);
const items = ref([]);
// 模拟请求
setTimeout(() => {
items.value = [{/*...*/}, {/*...*/}];
isLoading.value = false;
}, 1000);
</script>
via React
React 版本同样拥有优秀的类型提示(TypeScript Friendly):
import { Waterfall } from 'universal-waterfall-layout/react';
const MyGallery = ({ data, loading }) => {
return (
<Waterfall
gap={15}
columnCount={3} // 移动端常用:固定3列
loading={loading}
lazyload={true}
loadingComponent={<div>Loading Skeleton...</div>}
emptyComponent={<div>Nothing here yet!</div>}
>
{data.map(item => (
<div key={item.id} className="card">
<img src={item.url} />
</div>
))}
</Waterfall>
);
};
via Vanilla JS (原生 HTML)
如果你不使用任何框架,或者在传统的 HTML 页面中使用,也完全没问题。核心库提供了直接操作 DOM 的能力:
<div id="waterfall-container">
<div class="item"><img src="..." /></div>
<div class="item"><img src="..." /></div>
<!-- ... -->
</div>
<script type="module">
import { WaterFallCore } from 'universal-waterfall-layout';
const container = document.getElementById('waterfall-container');
const waterfall = new WaterFallCore({
container: container, // 必填:容器 DOM 元素
gap: 15, // 间距
columnWidth: 220, // 固定列宽策略
lazyload: true // 开启懒加载
});
// 如果后续通过 JS 动态添加了元素,可以手动触发布局更新
// waterfall.updateItems();
// waterfall.layout();
</script>
结语
在造轮子泛滥的今天,找到一个克制且好用的库并不容易。universal-waterfall-layout 没有过度封装,但把最核心的布局算法、响应式处理和加载体验做得非常扎实。
如果你正在开发图片流、商品墙或者作品集展示页面,强烈推荐试一试!
🔗 NPM 地址: universal-waterfall-layout
React从入门到出门第九章 资源加载新特性Suspense 原生协调原理与实战
通过英伟达平台免费调用 GLM4.7 教程
前言
最近在折腾 AI Agent 和模型接入相关的事情时,意外发现英伟达居然提供了一个面向开发者、可以免费调用模型 API 的平台。更关键的是,这个平台上不仅能用到一些主流开源模型,还能直接使用最近热度很高,号称开源 Coding 能力最强的 GLM4.7,以及综合表现相当稳的 minimax-m2.1。
说实话,在如今 API 几乎全面 token 计费、随便一个复杂任务就轻松几十万甚至上百万 token 的背景下,这种可以正经做开发实验、不用一上来就烧钱的平台,对个人开发者和学习阶段的人来说非常友好。所以这篇文章主要做三件事:
- 介绍 NVIDIA Build 平台本身能做什么
- 记录从注册到实际调用 API 的完整流程
- 分享一段真实使用下来的模型体验与限制
整体偏实践,结论也会尽量基于实际使用情况展开。
NVIDIA Build 模型平台
NVIDIA Build可以理解为英伟达官方提供的一个模型集成与调试平台。平台上已经部署了大量模型,涵盖文生文(Chat / Reasoning / Coding)、文生图 / 图生文、语音相关等模型。目前平台上可见的模型数量在 200+,基本覆盖了市面上主流的开源模型生态,例如:deepseek-R1 / deepseek-v3.x、qwen3 / qwen-coder、kimi-k2、minimax-m2.1、z-ai/glm4.7,平台本身还提供了在线 Playground(支持参数调节、tools 调用)、OpenAI 风格的 API 接口、模型示例代码一键生成等能力。
注册账号与 API Key 申请
账号注册说明
注意:疑似因为近期注册用户激增,新账号存在一定概率无法正常申请 API Key 的问题。在不影响账号合规性的前提下,比较稳妥的做法是使用非国内常见的邮箱注册,例如相对少见的邮箱(yeah.net),或国外邮箱(gmail.com)等,以及注册时使用浏览器无痕窗口,避免历史状态干扰。
创建账号
访问:build.nvidia.com/ ,点击左上角 Login。
![]()
在弹窗中输入邮箱并点击 Next,随后填写注册信息并完成人机验证。
这里需要注意:在“更多注册选项”中可以看到 QQ、微信等方式,但不建议使用第三方快捷登录。在当前阶段,使用这些方式注册后,账号更容易出现 API 权限受限的情况。
完成注册后,会进入一些偏个性化设置的步骤(例如名称、偏好选项),按需填写即可。
![]()
如果账号状态正常,稍等片刻后,页面顶部会出现提示:
Please verify your account to get API access
点击右上角的 Verify 进入验证流程。
手机号验证
在验证弹窗中,将默认的 +1 修改为 +86,输入国内手机号即可。这里不需要刻意规避,国内手机号是可以正常通过验证的。
![]()
点击 Send Code via SMS,完成验证码验证。
创建 API Key
验证完成后,点击右上角头像,进入 API Keys 管理页面。
![]()
如果账号状态正常,这里可以看到 Generate API Key 按钮。
点击后,输入一个 Key 名称(仅用于区分),过期时间选择 Never Expire。
![]()
生成完成后,复制并妥善保存该 API Key,后续调用只会展示一次。
![]()
如果在 API Keys 页面完全看不到生成按钮,而是类似下图所示的提示界面,基本可以确认该账号当前无法使用 API 功能,建议更换账号重新注册。
![]()
使用 API Key 调用
本地客户端配置
只要是支持 OpenAI 风格接口的客户端基本都可以直接使用,我这里以 Jan 为例。
进入设置页,添加一个新的模型提供商。
![]()
- Provider Name:自定义(例如
Nvidia) - API Key:填写刚刚生成的 Key
- Base URL:
https://integrate.api.nvidia.com/v1
完成后添加模型。
![]()
例如添加 GLM4.7:
z-ai/glm4.7
![]()
新建会话并选择该模型后,即可正常对话。从体感上看,在普通对话场景下 token 输出速度非常快。
![]()
获取模型列表
Jan 也支持直接调用 /models 接口获取模型列表,点击刷新即可自动拉取并添加。
![]()
需要注意的是:
-
/models返回的是平台全量模型列表 - 其中包含文生图、语音、多模态等模型
- 并非所有模型都支持 chat / text-to-text
因此,如果在客户端中直接选择不支持 chat 的模型发送消息,会直接报错,这是模型能力不匹配,不是接口问题。
Playground 与模型调试
在 NVIDIA Build 平台的 Models 页面中,可以通过搜索 Chat / Reasoning 筛选支持的模型,或者在 Playground 页面的左上角看到所有支持文生文的模型列表。
![]()
以 kimi-k2 为例,点击模型后可以进入在线调试界面。
![]()
- 左侧 Tools:可启用模型支持的工具
- 右侧 Parameters:控制温度、最大 token 等参数
![]()
点击右上角 View Code,可以直接看到对应的 API 调用示例,包括 Base URL、Model ID、Messages 结构等。
![]()
Tools 调用示例
在部分模型中可以直接启用 tools,这里以 minimax-m2 为例演示。
启用 get_current_weather 工具后,询问某地天气,模型会自动进行 tools 规划与调用,并返回结果。
![]()
再次点击 View Code,可以看到完整的 tools 调用示例代码。
![]()
模型与接口
NVIDIA Build 提供的是 OpenAI 风格 API,接口层面兼容 chat.completions / responses,是否支持 chat、tools、多模态,取决于模型本身。所以,最稳妥的方式仍然是在平台 Models 页面中筛选 chat / reasoning,再决定是否接入到本地客户端或代码中。
使用体验与限制
说一下 GLM4.7 这个模型。它并不是我第一次用,在刚发布不久时我就已经通过一些第三方 API 供应商接触过,这次算是第二次较完整地使用。综合两次实际开发体验,说实话体感并不算好。
首先一个比较明显的问题是,在我目前常用的模型里(比如 qwen-code、gpt-5.1、gpt-5.2、Claude 等),只有 GLM4.7 会在生成的文件头部插入 Markdown 的代码块标记。这个问题在代码编辑和文件生成场景下尤其影响体验,需要额外清理,看起来就很蠢。
其次是执行效率问题,这一点让我感觉很奇怪。纯对话场景下它的响应速度是很快的,但一旦进入干活模式,比如稍微复杂一点的任务编排、代码修改或多步执行,单个任务可能会跑十几甚至二十分钟。问题不在于我不能接受模型执行复杂任务耗时,而是过程中偶尔会出现明显的停顿或卡住一段时间再继续,节奏非常不稳定。
一开始我也怀疑是 API 调用频率或限流导致的,但后来在同样的客户端、同样的任务复杂度下切换到 minimax-m2,发现并不是这个原因。minimax 的整体执行节奏要顺畅得多,调用也更激进,甚至可以轻松跑到 40 次 / 分钟 的平台上限,当然代价就是一旦规划稍微激进,就很容易直接撞上限流,接口报错,任务中断。
从平台层面来看,这个平台整体体验其实是非常不错的:模型选择多、接入成本低、示例清晰,对学习和实验阶段的开发者非常友好。平台的限制也比较直观明确,比如 API 调用频率限制在 40 次 / 分钟,超出后直接返回错误,这一点在 minimax-m2 上体现得尤为明显。
回到 GLM4.7 本身,客观来说它的功能是完全正常的:工具调用没问题,代码编辑能用,对话速度也快,只是在复杂任务执行阶段明显偏慢,且稳定性不够好。相比之下,minimax-m2 在相同条件下执行节奏更线性、更听话,只是更容易触发平台限流 (当然了,因为频繁触发限流所以我也没深度使用 minimax)。
总结来说,GLM4.7 并不是不能干活,但实际开发体验一般,尤其是在需要长时间、连续执行任务的场景下,效率和节奏上的问题会被放大。
结语
实话说,这个平台在当前这个时间点,真的算是相当良心的存在。对想学习 AI Agent、工具调用、多模型编排的开发者来说,能够在不额外付费的情况下反复试错,本身就很有价值。
当然了,平台策略和风控状态可能随时变化,如果只是想白嫖一点体验,建议还是尽早注册,至少在账号状态正常的前提下,把 API Key 拿到手。
至于模型怎么选,建议多试、多对比,别迷信单一模型。能稳定把活干完的模型,才是好模型。
相关链接
【节点】[Integer节点]原理解析与实际应用
在Unity URP Shader Graph中,Integer节点是一个基础但功能强大的工具节点,它允许开发者在着色器程序中定义和使用整型常量。虽然着色器编程通常以浮点数运算为主,但整数在特定场景下具有不可替代的作用,特别是在控制流程、数组索引、循环计数和条件判断等方面。
Integer节点的基本概念
Integer节点在Shader Graph中代表一个整型常量值。与其他节点不同,Integer节点不接收输入,而是直接输出一个用户定义的整数值。这个特性使得它成为着色器中的固定参数或控制变量的理想选择。
Integer节点的核心特点:
- 输出值为整型,但在着色器运算中会自动转换为浮点数
- 可用于控制着色器中的离散状态和条件分支
- 适合用于数组索引、循环计数和枚举类型的表示
- 在性能优化方面,整数运算通常比浮点数运算更高效
在Shader Graph中的定位:
Integer节点属于Shader Graph的Input类别,与其他的常量节点如Float、Vector2、Vector3等并列。它提供了在可视化着色器编程中处理整数数据的能力,弥补了传统节点图主要以浮点数为中心的设计局限。
节点属性和配置
![]()
端口配置
Integer节点只有一个输出端口,其配置如下:
- 名称:Out
- 方向:输出
- 类型:Float
- 绑定:无
- 描述:输出整数值,但在类型系统中作为Float处理
端口特性的深入理解:
虽然端口类型标记为Float,但实际上输出的是整数值。这种设计是因为HLSL和GLSL着色语言中,整数和浮点数在很多时候可以隐式转换,而且Shader Graph的内部数据类型系统主要以浮点数为基础。在实际着色器代码生成时,这个整数值会被正确地处理为整数类型。
控件参数
Integer节点提供了一个简单的控件用于配置其输出值:
- 名称:无(在节点上直接显示数值)
- 类型:Integer
- 选项:无
- 描述:定义节点输出的整数值
控件使用要点:
- 可以直接在节点上的输入框中输入整数值
- 支持正负整数,范围通常受着色语言限制但足够大多数应用场景
- 数值改变会实时更新节点预览和生成的着色器代码
生成的代码分析
根据官方文档,Integer节点生成的代码示例如下:
HLSL
float _Integer = 1;
代码生成机制深入解析:
虽然示例代码显示变量被声明为float类型,但在实际的HLSL编译中,当这个值用于整数上下文时(如数组索引、循环计数器),编译器会进行适当的优化和处理。在更复杂的使用场景中,生成的代码可能会有不同的表现形式:
HLSL
// 当Integer节点用于数组索引时
int index = 2;
float value = _MyArray[index];
// 当用于循环控制时
for (int i = 0; i < _IterationCount; i++)
{
// 循环体
}
变量命名规则:
在生成的代码中,Integer节点对应的变量名称会根据节点在Graph中的名称自动生成。如果节点被命名为"TileCount",则生成的变量可能是_TileCount或_Integer_TileCount,具体命名规则取决于Shader Graph的版本和配置。
Integer节点的实际应用
基础数值应用
Integer节点最直接的用途是提供整型常量值,用于控制着色器的各种参数:
- 平铺和偏移控制:指定纹理平铺次数
- 循环次数设置:控制for循环的迭代次数
- 数组大小定义:确定固定大小数组的维度
- 枚举状态表示:用整数代表不同的渲染状态或材质类型
纹理平铺示例:
在纹理采样节点中,使用Integer节点控制平铺参数:
Integer节点(值:4) → TilingAndOffset节点 → SampleTexture2D节点
这种配置可以实现纹理的精确平铺控制,比如确保纹理在模型表面重复恰好4次,而不是4.5次或其他非整数值。
条件逻辑控制
Integer节点在着色器条件逻辑中发挥重要作用,特别是在需要离散状态判断的场景:
- 多重材质切换:使用整数值选择不同的材质属性集
- LOD级别控制:根据整数距离值切换细节级别
- 特效强度分级:将连续的特效参数离散化为几个固定级别
状态机实现示例:
通过结合Branch节点和Integer节点,可以实现简单的着色器状态机:
Integer节点(状态值) → Branch节点 → 不同的颜色/纹理输出
数组和循环操作
在高级着色器编程中,数组和循环是常见的编程结构,Integer节点在其中扮演关键角色:
- 数组索引:安全地访问数组元素
- 循环计数器:控制固定次数的循环迭代
- 多维数组处理:计算行主序或列主序数组的索引
数组访问模式:
For循环节点(使用Integer节点作为最大值) → 数组索引计算 → 数组元素访问
这种模式常见于图像处理效果,如卷积核操作、多光源累积计算等。
与其他节点的协同工作
与数学节点的配合
Integer节点可以与各种数学节点结合,实现更复杂的数值计算:
- 算术运算:与Add、Subtract、Multiply、Divide节点配合进行整数运算
- 比较运算:与Equal、NotEqual、Greater Than、Less Than节点结合实现条件判断
- 插值运算:虽然整数本身不插值,但可以控制插值参数
运算精度注意事项:
当Integer节点参与浮点数运算时,会自动提升为浮点类型。在需要保持整数精度的场景,应尽量避免与浮点数进行混合运算,或确保在关键步骤中使用适当的舍入函数。
与控制流节点的集成
Integer节点与Shader Graph的控制流节点紧密结合,实现动态的着色器行为:
- Branch节点:使用整数值作为条件输入
- For循环节点:提供循环次数和索引值
- Switch节点:作为选择器输入,决定执行哪个分支
性能优化提示:
在Shader Graph中使用整数控制流通常比使用浮点数更高效,因为整数比较和分支操作在GPU上的开销较小。特别是在移动平台上,这种优化更为明显。
高级应用技巧
动态整数参数
虽然Integer节点本身表示常量,但可以通过多种方式实现动态的整数参数:
- 脚本驱动:通过C#脚本在运行时修改材质属性
- 动画控制:使用Unity动画系统或时间节点驱动整数值变化
- 顶点数据:从顶点颜色或UV通道中提取整数值
脚本集成示例:
CSHARP
// C#脚本中设置整数值
material.SetInt("_IntegerParameter", 5);
数组和数据结构模拟
在着色器中模拟复杂数据结构时,Integer节点用于索引和管理:
- 查找表索引:访问预计算的查找表
- 状态矩阵:管理多维状态数组
- 有限状态机:实现复杂的着色器行为切换
多维度索引计算:
通过组合多个Integer节点和数学运算,可以计算多维数组的线性索引:
行索引 × 列数 + 列索引 = 线性索引
性能优化策略
合理使用Integer节点可以显著提升着色器性能:
- 循环展开优化:使用小的整数值作为循环次数,促进编译器自动展开循环
- 常量传播:整型常量的优化效果通常比浮点数更好
- 分支预测:整数条件语句的预测效率通常更高
平台特定考虑:
不同GPU架构对整数运算的支持程度不同。在编写跨平台着色器时,应了解目标平台的整数运算特性,特别是在移动设备上的性能表现。
实际案例研究
案例一:离散化颜色调色板
创建一个使用Integer节点选择预定义颜色的着色器:
- 设置Integer节点作为颜色索引
- 使用Branch节点或数组索引选择对应颜色
- 应用选中的颜色到材质表面
这种技术常用于低多边形风格游戏或需要特定颜色方案的应用程序。
案例二:多纹理混合系统
实现一个根据整数值混合多个纹理的系统:
- 使用Integer节点选择基础纹理、细节纹理和遮罩纹理
- 根据整数值决定混合模式和强度
- 创建可配置的多材质系统
案例三:程序化几何生成
在曲面细分或几何着色器中使用Integer节点控制细节级别:
- 根据距离或重要性设置细分因子
- 使用整数值确保对称和一致的几何分布
- 优化性能的同时保持视觉质量
故障排除和最佳实践
常见问题解决
整数精度问题:
- 问题:大整数导致精度丢失或意外行为
- 解决:确保使用的整数值在合理范围内,通常0-255对于大多数应用足够
类型转换错误:
- 问题:整数到浮点的隐式转换导致意外结果
- 解决:在关键计算中显式处理类型转换,使用Round、Floor或Ceiling节点
性能问题:
- 问题:使用整数节点后着色器变慢
- 解决:检查是否创建了复杂的依赖关系,简化节点网络
最佳实践建议
- 命名规范:为Integer节点使用描述性名称,提高可读性
- 数值范围:限制整数值在必要的最小范围内,避免不必要的内存占用
- 文档注释:在Shader Graph中使用注释节点说明Integer节点的用途和预期值范围
- 测试验证:在不同平台和设备上测试整数相关功能,确保一致的行为
【Unity Shader Graph 使用与特效实现】专栏-直达 (欢迎点赞留言探讨,更多人加入进来能更加完善这个探索的过程,🙏)