news 2026/9/7 5:31:37

Langflow 前端代码质量规则深度解析:cn()、设计令牌体系与状态管理规范

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Langflow 前端代码质量规则深度解析:cn()、设计令牌体系与状态管理规范

Langflow 前端代码质量规则深度解析:cn()、设计令牌体系与状态管理规范

【免费下载链接】langflowLangflow is a powerful tool for building and deploying AI-powered agents and workflows.项目地址: https://gitcode.com/GitHub_Trending/la/langflow

本文基于 Langflow 仓库中的前端代码质量规则目录 code-quality.md 展开,系统讲解 Langflow 前端(React + TypeScript + Tailwind CSS)的 12 条代码质量规范:从cn()条件类名、设计令牌(Design Token)色彩体系,到 Zustand / React Query 状态管理分工、Biome 格式化与命名目录约定。读完本文后,你将掌握一套可直接落地到 Langflow 或同类 Tailwind + shadcn 技术栈项目中的前端代码审查标准与实操依据,并能对照仓库源码验证每一条规则的底层实现。

规则总览

该规则目录为前端代码审查(code review)提供了机器可读的规则清单,每条规则标注了紧急级别(IsUrgent)与类别(Code Quality)。仓库要求:在增删改任何代码质量规则时,必须同步更新此文件以保持目录准确。完整规则清单如下:

规则紧急级别核心要求
条件类名一律使用cn()True禁止字符串拼接 / 模板字面量 / 裸三元表达式构造 class
Tailwind 优先(Tailwind-first)True不新增 CSS Modules / 自定义 CSS 文件
使用设计令牌替代原生 Tailwind 色值True禁用text-red-500等原始色板类
可覆写组件的 className 顺序False外部传入的className放在cn()最后
严格 TypeScript 类型,禁用anyTrueunknown+ 类型守卫或具体泛型
遵循 Biome 格式化约定False双引号、2 空格缩进、尾逗号;不用 ESLint/Prettier
优先使用 Radix UI 原语False交互组件走components/ui/的 shadcn 封装
状态管理分工:Zustand 管客户端,React Query 管服务端True禁止把服务端数据存进 Zustand
生产代码不留console.logTrue用错误边界 / toast 等正式手段
优先const与不可变模式False禁用var,用 spread / map / filter 代替突变
只用函数组件 + HooksTrue禁用类组件与生命周期方法
守卫子句提前返回False用 early return 压平嵌套
图标统一使用 Lucide ReactFalse不引入 heroicons / react-icons 等
遵循命名与目录约定True新代码 kebab-case,禁止index.tsx

条件类名必须使用cn()工具函数

规则要求:所有条件性 CSS 类逻辑必须使用来自@/utilscn()工具函数,而不是手写字符串拼接、模板字面量或裸三元表达式。cn()内部组合了clsxtailwind-merge,保证 Tailwind 类的正确去重与冲突消解。

仓库中的实际实现位于 utils.ts:

export function cn(...inputs: ClassValue[]): string { return twMerge(clsx(inputs)); }

两个依赖均在 package.json 中锁定:clsx ^2.1.1tailwind-merge ^2.3.0clsx负责把布尔值、对象、数组等条件参数折叠成类名字符串;tailwind-merge则按 Tailwind 语义对冲突类做后写覆盖(例如p-2 p-4只保留p-4)。

规则中给出的正反例:

// 错误 -- 手动拼接 <div className={`px-4 ${isActive ? "text-primary" : "text-muted-foreground"}`}> // 错误 -- 三元表达式直接拼 className,不走 cn() <div className={isActive ? "px-4 text-primary" : "px-4 text-muted-foreground"}> // 正确 import { cn } from "@/utils/utils"; <div className={cn("px-4", isActive ? "text-primary" : "text-muted-foreground")}>

为什么必须走cn()?关键在于冲突消解:当组件默认类与外部传入类在语义上重叠(如bg-backgroundbg-destructive)时,只有tailwind-merge能按“后者优先”的确定性规则裁决,而裸字符串拼接会导致两类同时生效、由 CSS 层叠顺序“碰运气”决定最终样式。

Tailwind 优先:不新增 CSS Modules

规则要求:所有样式优先使用 Tailwind 工具类;除非 Tailwind 的组合无法实现所需样式,否则不得引入新的 CSS Modules 或自定义 CSS 文件。项目使用 Tailwind v3 并带自定义主题配置。

规则给出了这条约束的工程理由:自定义 CSS 文件会形成与 Tailwind 令牌(token)体系并行的第二套样式系统。当设计团队调整某个边框色时,改的是index.css里的 CSS 变量,而你的 CSS Module 里仍是旧的硬编码色值,两者从此失步。自定义 CSS 还绕过了 Tailwind 的 purging 机制,增大产物体积。在一个有 300+ 组件共享设计系统的产品里(如 Langflow 的画布、节点、面板组件群),样式不一致会快速复利放大。

// 错误 -- 为一个简单样式新增 CSS Module import styles from "./Button.module.css"; <div className={styles.container}> // 正确 -- 直接用 Tailwind 工具类 <div className="flex items-center gap-2 rounded-md border px-4 py-2">

设计令牌体系:语义色板与四类令牌

这是整个规则目录中信息密度最高的一条:永远不要使用原生 Tailwind 色板类(如text-red-500bg-blue-100border-gray-300)。项目通过 index.css 中的 CSS 自定义属性定义了完整的设计令牌系统,并在 tailwind.config.mjs 中把它们映射成 Tailwind 类。这些令牌同时维护:root.dark两套值,因此天然支持明暗双主题。直接使用原始色板会绕过主题系统、破坏暗色模式、造成视觉不一致。

从源码结构看,这套体系是三层联动:

  1. CSS 变量层:index.css 的:root中定义 HSL 或 hex 值,例如--accent-emerald: 149 80% 90%--success-background: #f0fdf4--error-foreground: #991b1b等;
  2. Tailwind 映射层:tailwind.config.mjs 的theme.extend.colors把每个变量注册为颜色,如error: { DEFAULT: "var(--error)", background: "var(--error-background)", foreground: "var(--error-foreground)" },从而生成bg-errorbg-error-backgroundtext-error-foreground这类工具类;
  3. 静态检查层:Biome 插件 no-hardcoded-tailwind-palette.grit 用 GritQL 正则匹配bg-zinc-900text-slate-500border-gray-200等硬编码色板类并直接报诊断,提示改用src/style/index.css中的语义令牌。也就是说,这条规则不只是文档约定,而是有 lint 级强制手段的。

可用的令牌类清单

核心语义色(一般 UI 使用):

用途文字背景边框
主文字/背景text-foregroundbg-backgroundborder-border
主操作text-primarybg-primary
主操作文字text-primary-foreground
次要text-secondary-foregroundbg-secondary
弱化/次级text-muted-foregroundbg-muted
危险/破坏性text-destructivebg-destructive
强调text-accent-foregroundbg-accent
卡片text-card-foregroundbg-card
弹出层text-popover-foregroundbg-popover
工具提示text-tooltip-foregroundbg-tooltip
输入框border-input
焦点环ring-ring
占位符text-placeholder-foreground

状态色(构建状态、节点状态、告警):

状态类名
成功bg-success-backgroundtext-success-foreground
错误bg-error-backgroundtext-error-foregroundbg-error
信息bg-info-backgroundtext-info-foreground
警告bg-warningtext-warning-foreground
状态指示点bg-status-greenbg-status-redbg-status-yellowbg-status-bluebg-status-gray

强调色(高亮、徽章、分类):

强调色背景前景
Emeraldbg-accent-emeraldtext-accent-emerald-foreground
Indigobg-accent-indigotext-accent-indigo-foreground
Bluebg-accent-bluetext-accent-blue-foreground
Pinkbg-accent-pinktext-accent-pink-foreground
Amberbg-accent-ambertext-accent-amber-foreground

数据类型色(流程节点字段类型):

类型背景前景
Stringbg-datatype-yellowtext-datatype-yellow-foreground
Numberbg-datatype-bluetext-datatype-blue-foreground
Booleanbg-datatype-limetext-datatype-lime-foreground
Listbg-datatype-purpletext-datatype-purple-foreground
Dictbg-datatype-indigotext-datatype-indigo-foreground
Objectbg-datatype-graytext-datatype-gray-foreground
Errorbg-datatype-redtext-datatype-red-foreground
Messagebg-datatype-emeraldtext-datatype-emerald-foreground

便利贴颜色bg-note-amberbg-note-neutralbg-note-rosebg-note-bluebg-note-lime

其他令牌bg-canvas(流程画布背景)、text-node-ring(节点选中环)、bg-code-background/text-code-foreground(代码块)、bg-hover(悬停态)、text-hard-zincbg-smooth-redtext-placeholder

正反例对照

// 错误 -- 原生 Tailwind 色板(破坏暗色模式、破坏主题一致性) <span className="text-red-500">Error</span> <div className="bg-gray-100 border-gray-300"> <span className="text-blue-600">Link</span> <div className="bg-green-50 text-green-800">Success</div> // 正确 -- 设计令牌(自动适配暗色模式、主题一致) <span className="text-destructive">Error</span> <div className="bg-muted border-border"> <span className="text-accent-blue-foreground">Link</span> <div className="bg-success-background text-success-foreground">Success</div> // 错误 -- 硬编码 hex 或 rgb <div style={{ color: "#ef4444" }}> <div className="text-[#2563eb]"> // 正确 -- 若没有对应 Tailwind 类,使用 CSS 变量语义类 <div className="text-status-red"> <div className="text-status-blue">

何时允许使用原始色板

规则明确了三种例外场景:

  1. 搭建一次性 demo 或原型(需标注// TODO: replace with design token);
  2. 颜色确实是静态的、与主题无关(极其罕见);
  3. 设计稿精确要求某个没有对应令牌的颜色——此时应当新增 CSS 变量到 index.css(:root.dark两处都加),并在 tailwind.config.mjs 中注册,而不是在组件里内联原始色值。

可覆写组件的 className 顺序

对于接受classNameprop 的组件,永远把外部传入的className放在cn()的最后:组件自身类在前,外部传入类在后。

import { cn } from "@/utils/utils"; const Card = ({ className }: { className?: string }) => { return ( <div className={cn("rounded-lg border bg-background p-4 shadow-sm", className)}> {/* 内容 */} </div> ); };

原理:如果组件自身类写在className之后,消费方传入className='bg-destructive'会被组件的bg-background静默覆盖,消费意图丢失。放在cn()末尾时,tailwind-merge会按“后者覆盖前者”的语义把冲突裁决给消费方。这条规则之所以重要,正因为它依赖tailwind-merge的确定性冲突消解,而不是 CSS 层叠的偶然行为。

严格 TypeScript 类型:禁用any

规则要求:不得把any用作类型注解any会关闭 TypeScript 在编译期捕获类型不匹配的能力——本该被编译器拦截的 bug 会一路带到生产环境崩溃。Langflow 的流程数据结构嵌套极深(NodeDataType.node.template[fieldName].value),任何一层出现any,都会静默放行对不存在属性的访问,最终在画布上以 “Cannot read properties of undefined” 的形式爆发。

仓库的 Biome 配置印证了这条规则的强制级别:biome.json 中suspicious.noExplicitAny被设为"error"

// 错误 function processData(data: any) { ... } // 正确 function processData(data: Record<string, unknown>) { ... } // 正确 -- 类型守卫 function processData(data: unknown) { if (isFlowData(data)) { ... } }

对真正来源未知的外部数据用unknown+ 类型守卫收窄;其余场景一律使用具体类型或泛型。

Biome:格式化与静态检查的单一事实来源

项目使用Biome 而非 ESLint/Prettier做 lint 与格式化,约定如下:字符串用双引号、2 空格缩进、多行结构加尾逗号。运行biome checkbiome format校验合规性,且不得添加任何 ESLint 或 Prettier 配置文件

biome.json 中的实际配置与规则目录一一对应:

  • formatterindentStyle: "space"indentWidth: 2
  • linter.rules.suspicious.noExplicitAny"error"(对应“禁用 any”规则);
  • linter.rules.suspicious.noConsole"warn"级别,options.allow仅保留["error", "warn"]——即console.log会被直接告警(对应“不留 console.log”规则);
  • plugins挂载了 no-hardcoded-tailwind-palette.grit 插件,专门拦截硬编码 Tailwind 色板类(对应“设计令牌”规则)。
// 错误 -- 单引号、4 空格缩进 const name = 'flow' const config = { key: 'value' } // 正确 -- 双引号、2 空格缩进、尾逗号 const name = "flow"; const config = { key: "value", };

生产代码不留 console 语句

规则要求:不要在生产代码中留下console.logconsole.warnconsole.error,由 Biome 在 lint 阶段拦截。面向用户的错误应通过正式的错误处理、错误边界或 toast 通知来表达;确需调试的日志必须在提交前移除。

规则给出的安全理由非常具体:Langflow 中组件数据常包含存储在全局变量中的 API Key——一句无心的console.log(data)就会把凭据泄露给任何打开 DevTools 的人。需要指出的是,对照 biome.json 的实际配置,noConsole目前设为warn且允许console.error/console.warn,即 Biome 硬性拦截的是console.log这类调试输出;而规则目录对“不留下任何 console 语句”的要求更严格——可以理解为 lint 是底线,评审标准高于 lint 基线。

优先使用 Radix UI 原语(shadcn 封装)

构建对话框、下拉菜单、工具提示、弹出层、标签页等交互元素时,应使用 components/ui/ 下基于 Radix UI 的 shadcn 封装,而不是手写交互逻辑。规则指出,自定义实现几乎总会漏掉四样东西:键盘导航(下拉菜单的方向键)、焦点陷阱(Tab 键留在对话框内)、屏幕阅读器播报(ARIA live 区域)和 Escape 键处理。每重新造一个下拉菜单,都是对键盘用户和读屏用户的一次无障碍回归。

// 错误 -- 手写下拉菜单 const [open, setOpen] = useState(false); <div onBlur={() => setOpen(false)}> <button onClick={() => setOpen(!open)}>Menu</button> {open && <div className="absolute">{/* 菜单项 */}</div>} </div> // 正确 -- 使用 shadcn/Radix DropdownMenu import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; <DropdownMenu> <DropdownMenuTrigger>Menu</DropdownMenuTrigger> <DropdownMenuContent> <DropdownMenuItem>Item 1</DropdownMenuItem> </DropdownMenuContent> </DropdownMenu>

先查components/ui/:可用的 39+ 基础组件

动手写任何自定义交互 UI 之前,先确认 src/frontend/src/components/ui/ 里是否已有现成组件。规则目录给出的组件分类清单如下:

  • 布局与容器accordioncarddialogdialog-with-no-closedisclosurepopoverseparatorsidebarsimple-sidebartabstabs-button
  • 表单与输入buttoncheckboxinputlabelradio-groupselectselect-customswitchtextarea
  • 反馈与浮层alertbadgecommandcontext-menudropdown-menuloadingskeletonskeletonGrouptooltip
  • 视觉增强animated-closebackground-gradientcheckmarkdot-backgroundrefreshButtontabletext-looptextAnimationTextShimmerxmark

对照仓库实际目录,components/ui/ 下确实存在这些封装,此外还有calendarempty-statestop-canvas-key-propagationuse-closed-trigger-aria-controlsuse-inert-for-aria-hidden等文件。规则要求:不要手工复刻这些模式,统一从@/components/ui/导入。

状态管理分工:Zustand 管客户端,React Query 管服务端

这条规则划定了清晰的状态边界:

  • Zustandstore(位于 src/frontend/src/stores/)负责客户端 UI 状态
  • TanStack React Query(通过 src/frontend/src/controllers/API/ 下的 hooks)负责服务端状态(数据获取、缓存、mutation)。

两条禁令:不要把拉取到的服务端数据存进 Zustand;不要用 React Query 管理纯客户端状态。

// 错误 -- 把拉取的数据存进 Zustand const useFlowStore = create((set) => ({ flows: [], fetchFlows: async () => { const data = await api.getFlows(); set({ flows: data }); }, })); // 正确 -- 服务端状态交给 React Query const useFlows = () => { return useQueryFunctionType<FlowType[]>(["flows"], api.getFlows); }; // 正确 -- 客户端 UI 状态用 Zustand const useFlowStore = create((set) => ({ selectedNodeId: null, setSelectedNodeId: (id: string | null) => set({ selectedNodeId: id }), }));

仓库结构印证了这套分工:stores/ 目录下是flowStore.tsflowsManagerStore.tsalertStore.tsplaygroundStore.ts等纯 UI 状态 store;而 controllers/API/queries/ 下按领域(如knowledge-bases/)组织了一大组use-*.ts查询 hook(use-get-ingestion-job-status.tsuse-get-kb-metadata-keys.ts等),统一经useQueryFunctionType包装 React Query。依赖版本上,package.json 锁定了zustand ^4.5.2@tanstack/react-query ^5.49.2

const 优先与不可变更新模式

默认使用const;仅在确实需要重新赋值时用let永远不用var。处理数组和对象(尤其是状态更新)时,优先使用不可变更新模式(spread、mapfilter)而非原地突变。

// 错误 let items = getItems(); items.push(newItem); setState({ items }); // 正确 const items = getItems(); setState({ items: [...items, newItem] });

规则给出的理由:突变会在代码路径之间制造不可见的依赖——若函数 A 突变了函数 B 读取的数组,A 的行为变更会无编译错误地悄悄破坏 B。更直接的是 React 机制层面:React 依赖引用相等判断是否重渲染,原地突变状态对象时oldObj === newObj仍为true,组件不会重新渲染;而 spread / map / filter 产生新引用,React 才能检测到变化。

只用函数组件与 Hooks

所有 React 组件必须是使用 Hooks 的函数组件:不使用类组件、React.Component或生命周期方法(componentDidMount等),统一使用useEffectuseStateuseMemouseCallback和自定义 hook。

规则理由:类组件 API 面更大(生命周期方法、this绑定、构造函数),更难测试和推理;Hooks 的组合性更好——一个自定义 hook 可以同时组合状态、副作用与 context,无需层层嵌套 HOC。Langflow 整个代码库都是函数组件,引入一个类组件会破坏一致性,迫使其他开发者在不同范式之间来回切换心智模型。

守卫子句:用 early return 压平嵌套

在函数和组件开头处理边界条件与守卫逻辑,减少嵌套、突出主逻辑路径。规则从认知负荷角度论证:每多一层缩进,读者就要在工作记忆中多持有一个条件;嵌套 3 层以上的代码缺陷率显著更高。把守卫子句放在顶部,可以让前置条件显式化,主逻辑保持在最低缩进层级——“快乐路径”在视觉上清晰可辨。

// 错误 -- 深度嵌套 function NodeComponent({ data }: NodeProps) { if (data) { if (data.node) { if (data.node.template) { return <div>{/* 主内容 */}</div>; } } } return null; } // 正确 -- 提前返回 function NodeComponent({ data }: NodeProps) { if (!data?.node?.template) return null; return <div>{/* 主内容 */}</div>; }

图标统一使用 Lucide React

图标库标准化为 Lucide React。除非 Lucide 没有等价图标,否则不要从其他库(heroicons、react-icons、font-awesome 等)导入图标;引入新图标依赖前先查 Lucide 官方图标集确认。

理由:多图标库并存会膨胀产物体积(每个库都携带自己的 SVG 集);Lucide 支持 tree-shaking,未使用的图标会在构建期被移除;混用库还会造成视觉不一致(描边粗细、尺寸约定、视觉重量各不相同)。package.json 中锁定的是lucide-react ^0.575.0

// 错误 import { AiOutlineCheck } from "react-icons/ai"; // 正确 import { Check } from "lucide-react";

命名与目录约定:kebab-case 与禁止 index.tsx

这是紧急级别为 True 的规则:所有新前端代码必须使用 kebab-case 文件名。遗留代码库中存在index.tsx、camelCase、PascalCase 混合的历史约定——新代码不得沿用这些模式。

新代码标准

  • 所有文件:kebab-case,用能描述文件职责的名字命名。永远不要用index.tsx命名文件——这是遗留模式,会让导航和搜索变困难;
  • 文件夹:kebab-case,以功能或组件命名;
  • Hooks:kebab-case 且加use-前缀(如use-add-flow.tsuse-debounce.ts);
  • Stores:camelCase 且以 "Store" 结尾(如flowStore.tsalertStore.ts)——这是既有约定,保持不动;
  • 类型文件:kebab-case 文件夹 + 描述性文件名。
对象约定示例
组件文件kebab-case、描述性flow-settings-panel.tsxnode-input-field.tsx
组件文件夹kebab-caseflow-settings/node-toolbar/
Hook 文件kebab-case、use-前缀use-save-flow.tsuse-debounce.ts
辅助文件kebab-case、描述性column-defs.tsformat-data.ts
类型文件kebab-caseflow-types.tsapi-types.ts
Store 文件camelCase + "Store"flowStore.ts(既有约定)
UI 组件(shadcn)kebab-casedropdown-menu.tsx(shadcn 标准)

推荐的新组件目录形态

my-component/ ├── my-component.tsx ← 清晰、可搜索、自描述 ├── components/ │ ├── sub-part.tsx │ └── another-part.tsx ├── helpers/ │ ├── column-defs.ts │ └── format-data.ts ├── hooks/ │ └── use-local-state.ts └── types/ └── my-component-types.ts

(反例是my-component/index.tsx——在编辑器标签页和搜索结果中无法辨识属于哪个组件。)

关键规则:helpers 是局部的,不是中心化的。每个组件拥有自己的helpers/文件夹,不要在不相关的目录里创建共享 helper 文件:

// 错误 -- index.tsx(遗留模式) // src/frontend/src/components/core/myComponent/index.tsx // 错误 -- 中心化 helper // src/frontend/src/helpers/flowHelpers.ts // 正确 -- kebab-case、描述性命名、局部 helpers // src/frontend/src/components/core/my-component/my-component.tsx // src/frontend/src/pages/FlowPage/components/flow-sidebar/helpers/format-flow.ts

仓库现状佐证了“Store 例外”:stores/ 目录下的flowStore.tsalertStore.tsauthStore.tsflowsManagerStore.ts均为 camelCase + Store 后缀,说明该约定是刻意保留的历史惯例。

小结

Langflow 的前端代码质量规则目录不是一份泛泛的“风格建议”,而是一套与工具链深度咬合的工程规范:cn()的实现可在 utils.ts 中逐行核对;设计令牌由 index.css 的 CSS 变量、tailwind.config.mjs 的颜色映射与 no-hardcoded-tailwind-palette.grit 的 GritQL 静态检查三层共同支撑;any禁用与console.log告警则由 biome.json 中的noExplicitAny: "error"noConsole规则兜底。对参与 Langflow 贡献或复刻同类技术栈(React + TypeScript + Tailwind + shadcn + Zustand + React Query + Biome)的团队而言,这份规则目录及其对应的仓库证据链,本身就是一份可直接迁移的代码审查清单。

【免费下载链接】langflowLangflow is a powerful tool for building and deploying AI-powered agents and workflows.项目地址: https://gitcode.com/GitHub_Trending/la/langflow

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/7 5:31:25

Lensfun开源镜头数据库深度解析:原理、实操与踩坑经验

简介&#xff1a;Lensfun是一套面向摄影师、后期处理开发者和光学爱好者的开源镜头校正数据库与工具包&#xff0c;主要用于矫正广角畸变、色差、暗角等由镜头光学缺陷引起的画质问题。它内置了大量相机与镜头的实测参数&#xff0c;可通过接口集成到RawTherapee、Darktable、G…

作者头像 李华
网站建设 2026/9/7 5:31:20

从单卡到万卡:分布式训练核心范式与PyTorch DDP实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/7 5:28:31

Uber如何用AI Agent接管70%代码评审:技术拆解与落地指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/7 5:23:24

用FastAPI搭建统一LLM网关:一个Key接入所有免费模型

先说一个挺常见的场景&#xff1a;你同时想用多个平台的免费模型做应用&#xff0c;于是注册账号、申请 API Key、看文档、配 SDK、写代理代码……一个接一个折腾下来&#xff0c;代码里躺着十几把 Key&#xff0c;每把 Key 对应的请求地址还不一样。更难受的是&#xff0c;等模…

作者头像 李华