Redux 基础教程实战:用中间件与 Redux Thunk 实现异步逻辑和数据获取
本篇内容基于 Redux 官方基金教程第 6 篇(part-6-async-logic.md),讲解当应用数据不再只存在于客户端、而需要通过与服务器进行 HTTP 请求来读写时,Redux 是如何通过**中间件(middleware)**与 Thunk 函数来承载异步逻辑的:Redux 数据流在引入异步后会多出哪些环节、为什么 reducer 中不能写副作用、applyMiddleware 在源码层面如何把多个中间件组装成一条 dispatch 管线,以及如何用 thunk 函数从服务端拉取待办列表并保存新待办。读完本篇,你将能够配置带 thunk 中间件的 store、编写"接收 dispatch 与 getState 的异步函数"、在组件中 dispatch thunk 完成真实的数据获取流程,并在源码与测试层面验证这套机制的每个环节。
前置知识与你将学到的内容
本教程第 6 篇建立在 第 5 篇:UI 和 React 的基础上:你已经会用 react-redux 让组件与 Redux store 交互——调用 useSelector 读取状态、调用 useDispatch 拿到 dispatch 函数、并用 <Provider> 组件让 hooks 访问到 store。
本篇学习目标:
- Redux 数据流在处理异步数据时是如何工作的;
- 如何用 Redux 中间件编写异步逻辑;
- 处理异步请求状态(request state)的模式。
前置要求:
- 熟悉使用 HTTP 请求从服务器获取和更新数据;
- 理解 JS 中的异步逻辑,包括 Promise。
值得先了解的一点:Redux Toolkit 提供了专门的数据获取与缓存方案 RTK Query,它可以消除为数据获取编写任何 thunk 或 reducer 的需要。Redux 官方教程将 RTK Query 作为数据获取的默认推荐方案,而 RTK Query 正是建立在本篇讲解的同一套模式之上,参见 RTK Query 概述 与 Redux Essentials, Part 7: RTK Query Basics。本篇讲的是这些底层机制本身。
示例 REST API 与 HTTP 客户端
为了让示例项目既隔离又贴近现实,教程配套的示例应用(redux-fundamentals-example-app)在初始搭建时就内置了一个内存版的假 REST API(使用 Mirage.js 这个 mock API 工具配置)。该 API 以 /fakeApi 作为所有端点的基础 URL,并对 /fakeApi/todos 支持常见的 GET/POST/PUT/DELETE HTTP 方法,定义在示例应用的 src/api/server.js 中。
项目还包含一个小型 HTTP API 客户端对象,暴露 client.get() 和 client.post() 方法,用法类似 axios 等常见 HTTP 库,定义在示例应用的 src/api/client.js 中。本节的 HTTP 调用都通过这个 client 对象发往内存中的假 REST API。
Redux 中间件与副作用
单看一个 Redux store,它对异步逻辑一无所知:它只会同步地 dispatch 动作、通过调用根 reducer 函数更新状态,并通知 UI 发生了变化。任何异步性都必须发生在 store 之外。
在前面的教程中我们说过,Redux reducer 绝不能包含"副作用"。"副作用"指的是任何能够被函数返回值之外的外部世界观察到的状态或行为改变。常见的副作用包括:
- 向控制台打印日志;
- 保存文件;
- 设置异步定时器;
- 发起 HTTP 请求;
- 修改函数外部存在的某个状态,或直接改变(mutate)函数参数;
- 生成随机数或唯一随机 ID(如
Math.random()或Date.now())。
然而,任何真实应用都总要在某个地方做这些事情。既然 reducer 里不能放副作用,那副作用应该放在哪里?
Redux 中间件正是为了让你可以编写带副作用的逻辑而设计的。
正如 第 4 篇 中所述,Redux 中间件在"看到"一个被 dispatch 的动作时可以做任何事:打印日志、修改动作、延迟动作、发起异步调用,等等。而且,由于中间件构成了包裹在真实 store.dispatch 函数外的一整条管线,这意味着你实际上可以向 dispatch 传递不是一个纯 action 对象的值——只要某个中间件拦截了这个值、并且不让它一路到达 reducer 就行。
中间件还能访问 dispatch 和 getState。也就是说,你可以在中间件里编写异步逻辑,并且依然能够通过 dispatch 动作与 Redux store 保持交互。
Redux 作者在 StackOverflow 上多次解释过"为什么异步流需要中间件",其核心论点与本节一致:状态更新必须同步、可预测,而副作用必须被移出 reducer 到 dispatch 管线上。
用中间件启用异步逻辑
下面看两个例子,展示中间件如何让我们编写能与 Redux store 交互的异步逻辑。
一种可能是写一个查找特定 action 类型的中间件,在"看到"这些动作时运行异步逻辑:
import { client } from '../api/client'
const delayedActionMiddleware = storeAPI => next => action => {
if (action.type === 'todos/todoAdded') {
setTimeout(() => {
// 延迟这个 action 一秒
next(action)
}, 1000)
return
}
return next(action)
}
const fetchTodosMiddleware = storeAPI => next => action => {
if (action.type === 'todos/fetchTodos') {
// 发起 API 调用,从服务器拉取 todos
client.get('todos').then(todos => {
// 用接收到的 todos dispatch 一个 action
storeAPI.dispatch({ type: 'todos/todosLoaded', payload: todos })
})
}
return next(action)
}
注意这两个中间件的写法:都是"三层嵌套函数"——最外层接收 storeAPI,中间层接收 next,最内层接收 action。这与本仓库 src/types/middleware.ts 中 Middleware 类型的定义完全对应:
// src/types/middleware.ts(节选)
export interface MiddlewareAPI<D extends Dispatch = Dispatch, S = any> {
dispatch: D
getState: () => S
}
export interface Middleware<
_DispatchExt = {},
S = any,
D extends Dispatch = Dispatch
> {
(
api: MiddlewareAPI<D, S>
): (next: (action: unknown) => unknown) => (action: unknown) => unknown
}
也就是说,中间件的契约就是 (api) => (next) => (action) => 任意返回值,其中最内层函数的参数类型是 unknown——这从类型层面就承认了:dispatch 进来的一切都不保证是普通 action 对象,拦截非对象值(比如函数)正是中间件的合法职责。
源码视角:applyMiddleware 如何组装中间件管线
以上"三层嵌套函数"最终是由 applyMiddleware 串成管线的。看本仓库的实现 src/applyMiddleware.ts:
export default function applyMiddleware(...middlewares: Middleware[]): StoreEnhancer<any> {
return createStore => (reducer, preloadedState) => {
const store = createStore(reducer, preloadedState)
let dispatch: Dispatch = () => {
throw new Error(
'Dispatching while constructing your middleware is not allowed. ' +
'Other middleware would not be applied to this dispatch.'
)
}
const middlewareAPI: MiddlewareAPI = {
getState: store.getState,
dispatch: (action, ...args) => dispatch(action, ...args)
}
const chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose<typeof dispatch>(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
从这段源码可以读出几个关键事实:
applyMiddleware本身是一个 store enhancer:它返回createStore => (reducer, preloadedState) => store这样的高阶函数,最终返回一个"覆盖了dispatch的新 store"。这也解释了教程第 4 篇中"中间件是构建在一个非常特殊的内建 store enhancer 之上的"这句话。函数注释中还特别提示:由于中间件可能是异步的,applyMiddleware应当是 enhancer 组合链中的第一个。- 每个中间件拿到的
dispatch是"指向管线入口"的递归引用:middlewareAPI.dispatch调用的是闭包中会被重新赋值的dispatch变量,最终被赋值为组合后的完整管线。因此中间件内部再 dispatch 的动作,会从第一个中间件重新走完整条管线,而不会被漏掉。 - 管线的组装发生在构建期:
middlewares.map(middleware => middleware(middlewareAPI))先对每个中间件调用最外层函数,拿到各自的wrapDispatch;再compose(...chain)(store.dispatch)把它们从右向左嵌套,把原始的store.dispatch作为链尾(即最后一个中间件的next)。compose的实现见 src/compose.ts,即compose(f, g, h) === (...args) => f(g(h(...args)))——这决定了applyMiddleware(a, b)时,a是管线最外层、最先执行,b最后执行并紧邻真实store.dispatch。 - 构建期禁止 dispatch:在中间件装配完成之前,
dispatch被临时指向一个会抛出 "Dispatching while constructing your middleware is not allowed" 的函数(src/applyMiddleware.ts),防止你在中间件外层函数里 dispatch 时绕过尚未装配的管线。
这些行为都有测试背书,见 test/applyMiddleware.spec.ts:
warns when dispatching during middleware setup(L9-L20)验证了第 4 点的抛错行为;wraps dispatch method with middleware once(L22-L45)验证中间件外层函数只被调用一次,且拿到的 API 上确实有getState和dispatch;passes recursive dispatches through the middleware chain(L47-L65)验证"中间件内部 dispatch 的动作会重新经过整条管线"。
编写一个"异步函数"中间件
上一节的两个中间件都太具体,各只干一件事。我们当然希望有一种办法:把任意异步逻辑提前写成一个独立函数(与中间件定义解耦),同时这个函数还能访问 dispatch 和 getState 来与 store 交互。
如果我们写一个允许向 dispatch 传一个"函数"(而不是 action 对象)的中间件呢? 这个中间件检查一下"动作"是否其实是函数,如果是,就立刻调用它。这样异步逻辑就能写在中间件定义之外的独立函数里。这样的中间件大概长这样:
// 示例异步函数中间件
const asyncFunctionMiddleware = storeAPI => next => action => {
// 如果这个"action"其实是个函数……
if (typeof action === 'function') {
// 那就调用它,并把 `dispatch` 和 `getState` 作为参数传入
return action(storeAPI.dispatch, storeAPI.getState)
}
// 否则,它是一个普通 action——继续往下传
return next(action)
}
有了它,我们就可以这样使用:
const middlewareEnhancer = applyMiddleware(asyncFunctionMiddleware)
const store = createStore(rootReducer, middlewareEnhancer)
// 写一个以 `dispatch` 和 `getState` 为参数的函数
const fetchSomeData = (dispatch, getState) => {
// 发起一次异步 HTTP 请求
client.get('todos').then(todos => {
// 用接收到的 todos dispatch 一个 action
dispatch({ type: 'todos/todosLoaded', payload: todos })
// dispatch 之后读取更新后的 store 状态
const allTodos = getState().todos
console.log('Number of todos after loading: ', allTodos.length)
})
}
// 把上面写的"函数"传给 `dispatch`
store.dispatch(fetchSomeData)
// 日志输出:'Number of todos after loading: ###'
注意:这个"异步函数中间件"让我们向 dispatch 传入了一个函数。在函数内部,我们先写了一段异步逻辑(HTTP 请求),请求完成后再 dispatch 一个普通的 action 对象。
这里有一个容易忽略的边界:如果没有中间件拦截,把函数传给 dispatch 会在 store 的原始 dispatch 里直接失败。从源码看,src/createStore.ts 中对 action 的校验会检查 action.type 是否为字符串,非普通对象的值根本无法通过 reducer 校验流程;仓库也提供了 src/utils/isAction.ts 这样的工具函数,其判定标准就是"是纯对象且 type 为字符串"。换言之,函数型 dispatch 之所以可行,完全依赖于中间件先于 store 内部逻辑拦截了它——这正是文档所说"只要中间件拦截该值、不让它到达 reducer"的底层依据。
本仓库的测试辅助代码里就有一个与上面 asyncFunctionMiddleware 几乎一模一样的最小 thunk 实现,见 test/helpers/middleware.ts:
export const thunk: Middleware<{
<R>(thunk: (dispatch: Dispatch, getState: () => any) => R): R
}> =
({ dispatch, getState }) =>
next =>
action =>
typeof action === 'function' ? action(dispatch, getState) : next(action)
而 test/applyMiddleware.spec.ts 中名为 works with thunk middleware 的测试用例,就用它验证了完整闭环:dispatch 一个 thunk 函数后,thunk 内部 dispatch 的普通 action 能正确更新 store 状态。
Redux 的异步数据流
那么,中间件和异步逻辑对 Redux 应用的整体数据流有什么影响?
和普通 action 一样,我们首先要处理一个用户事件(比如点击按钮),然后调用 dispatch(),传入某种东西——一个纯 action 对象、一个函数,或其他可以被中间件查找识别的值。
这个被 dispatch 的值到达中间件后,中间件可以发起一次异步调用,等异步调用完成后再 dispatch 一个真正的 action 对象。
在 第 2 篇 中,我们见过表示常规同步 Redux 数据流的示意图。当 Redux 应用加入异步逻辑后,就多了中间件运行 HTTP 请求等逻辑、再 dispatch action 的额外环节。异步数据流看起来就是文章开头那张图:UI 事件触发 dispatch,值进入中间件管线,中间件在异步等待(HTTP 请求、定时器等)结束后 dispatch 真实 action,action 再经过剩余中间件到达 reducer,状态更新后通知 UI 重新渲染。
使用 Redux Thunk 中间件
实际上,Redux 官方早就有了那个"异步函数中间件"的标准版本,叫做 Redux "Thunk" 中间件(npm 包 redux-thunk)。thunk 中间件让我们编写以 dispatch 和 getState 为参数的函数。thunk 函数内部可以包含我们想要的任何异步逻辑,这些逻辑可以按需 dispatch action、读取 store 状态。
把异步逻辑写成 thunk 函数,让我们可以在事先不知道将使用哪个 Redux store 的情况下复用这些逻辑。
术语说明:"thunk" 是一个编程术语,指"一段执行延迟工作(delayed work)的代码"。更多用法可参考 Writing Logic with Thunks 使用指南。
配置 Store
Redux thunk 中间件作为 npm 包 redux-thunk 发布,需要先安装:
npm install redux-thunk
安装后,更新 todo 应用的 Redux store 以使用这个中间件:
import { createStore, applyMiddleware } from 'redux'
// highlight-next-line
import { thunk } from 'redux-thunk'
import { composeWithDevTools } from 'redux-devtools-extension'
import rootReducer from './reducer'
// highlight-next-line
const composedEnhancer = composeWithDevTools(applyMiddleware(thunk))
// store 现在具备了在 `dispatch` 中接受 thunk 函数的能力
const store = createStore(rootReducer, composedEnhancer)
export default store
composeWithDevTools(applyMiddleware(thunk)) 正是把 applyMiddleware 返回的 enhancer 交给 DevTools 组合函数再传给 createStore,对应 src/applyMiddleware.ts 中 "enhancer 包 enhancer" 的高阶结构。
从服务器获取 Todos
现在我们的 todo 条目只能存在于客户端浏览器里。我们首先需要一种方式,在应用启动时从服务器加载待办列表。
先写一个 thunk 函数:它发起 HTTP 调用请求 /fakeApi/todos 端点、取得 todo 对象数组,然后 dispatch 一个以该数组为 payload 的 action。由于这与 todos 功能整体相关,thunk 函数写在 todosSlice.js 里(示例应用文件):
import { client } from '../../api/client'
const initialState = []
export default function todosReducer(state = initialState, action) {
// 省略 reducer 逻辑
}
// Thunk 函数
// highlight-start
export async function fetchTodos(dispatch, getState) {
const response = await client.get('/fakeApi/todos')
dispatch({ type: 'todos/todosLoaded', payload: response.todos })
}
// highlight-end
这个 API 调用只希望在应用第一次加载时执行一次。可以放的地方有好几处:
- 在
<App>组件的useEffecthook 里; - 在
<TodoList>组件的useEffecthook 里; - 直接在
index.js里,紧跟在导入 store 之后。
这里先试放在 index.js 里:
import React from 'react'
import { createRoot } from 'react-dom/client'
import { Provider } from 'react-redux'
import './index.css'
import App from './App'
import './api/server'
// highlight-start
import store from './store'
import { fetchTodos } from './features/todos/todosSlice'
store.dispatch(fetchTodos)
// highlight-end
const root = createRoot(document.getElementById('root'))
root.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>
)
刷新页面后,UI 上没有可见变化。但如果打开 Redux DevTools 扩展,应该能看到一个 'todos/todosLoaded' 动作被 dispatch 了,其中包含由我们的假服务器 API 生成的一些 todo 对象:
注意:虽然我们已经 dispatch 了动作,但状态并没有发生任何变化。我们需要在 todos reducer 中处理这个动作,状态才会更新。
给 reducer 加一个 case 来把数据加载进 store。因为数据是从服务器取来的,我们要完全替换已有的 todos,所以直接返回 action.payload 数组,让它成为 todos 的新 state 值:
import { client } from '../../api/client'
const initialState = []
export default function todosReducer(state = initialState, action) {
switch (action.type) {
// 省略其他 reducer case
// highlight-start
case 'todos/todosLoaded': {
// 直接返回新值,整体替换现有 state
return action.payload
}
// highlight-end
default:
return state
}
}
export async function fetchTodos(dispatch, getState) {
const response = await client.get('/fakeApi/todos')
dispatch({ type: 'todos/todosLoaded', payload: response.todos })
}
由于 dispatch 一个动作会立刻更新 store,我们也可以在 thunk 里调用 getState,在 dispatch 之后读取更新后的状态值。例如,在 dispatch 'todos/todosLoaded' 动作前后各打印一次 todo 总数:
export async function fetchTodos(dispatch, getState) {
const response = await client.get('/fakeApi/todos')
// highlight-next-line
const stateBefore = getState()
console.log('Todos before dispatch: ', stateBefore.todos.length)
dispatch({ type: 'todos/todosLoaded', payload: response.todos })
// highlight-next-line
const stateAfter = getState()
console.log('Todos after dispatch: ', stateAfter.todos.length)
}
这正体现了中间件 API 中 getState 的价值:从 src/applyMiddleware.ts 可以看到,middlewareAPI.getState 直接引用了 store 的 getState,因此 thunk 内部读到的永远是当下最新的状态。
保存 Todo 条目
接下来,每当创建新待办条目时,我们也需要更新服务器。正确做法是:不要立即 dispatch 'todos/todoAdded' 动作,而是先向服务器发起一次携带初始数据的 API 调用,等待服务器返回新保存的 todo 条目副本,然后再用这个 todo 条目 dispatch 动作。
但如果直接把它写成一个 thunk 函数,会立刻遇到一个问题:thunk 是写在 todosSlice.js 里的独立函数,发起 API 调用的代码并不知道新 todo 的文本是什么:
async function saveNewTodo(dispatch, getState) {
// ❌ 我们需要新 todo 的文本,但它从哪来?
// highlight-next-line
const initialTodo = { text }
const response = await client.post('/fakeApi/todos', { todo: initialTodo })
dispatch({ type: 'todos/todoAdded', payload: response.todo })
}
我们需要一种方式:写一个接收 text 参数的函数,由它创建真正的 thunk 函数,使得 thunk 能利用 text 值发起 API 调用。外层函数返回这个 thunk 函数,让我们可以把它传给组件里的 dispatch:
// 写一个同步的外层函数,接收 `text` 参数:
export function saveNewTodo(text) {
// 然后创建并返回异步 thunk 函数:
return async function saveNewTodoThunk(dispatch, getState) {
// ✅ 现在可以用 text 值并把它发送到服务器
const initialTodo = { text }
const response = await client.post('/fakeApi/todos', { todo: initialTodo })
dispatch({ type: 'todos/todoAdded', payload: response.todo })
}
}
现在可以在 <Header> 组件中使用它:
import React, { useState } from 'react'
import { useDispatch } from 'react-redux'
// highlight-next-line
import { saveNewTodo } from '../todos/todosSlice'
const Header = () => {
const [text, setText] = useState('')
const dispatch = useDispatch()
const handleChange = e => setText(e.target.value)
const handleKeyDown = e => {
// 如果用户按下了回车键:
const trimmedText = text.trim()
if (e.which === 13 && trimmedText) {
// highlight-start
// 用用户输入的文本创建 thunk 函数
const saveNewTodoThunk = saveNewTodo(trimmedText)
// 然后把 thunk 函数本身 dispatch 出去
dispatch(saveNewTodoThunk)
// highlight-end
setText('')
}
}
// 省略渲染输出
}
由于我们清楚自己会立刻把 thunk 函数传给组件中的 dispatch,可以省去那个临时变量:直接调用 saveNewTodo(text),把得到的 thunk 函数原样传给 dispatch:
const handleKeyDown = e => {
// 如果用户按下了回车键:
const trimmedText = text.trim()
if (e.which === 13 && trimmedText) {
// highlight-start
// 创建 thunk 函数并立刻 dispatch
dispatch(saveNewTodo(trimmedText))
// highlight-end
setText('')
}
}
此时组件实际上并不知道自己在 dispatch 一个 thunk 函数——saveNewTodo 函数封装了真正发生的事情。<Header> 组件只知道:用户按回车时,它需要 dispatch 某个值。
这种"写一个函数来准备将要传给 dispatch 的东西"的模式,就是所谓的 "action creator" 模式,在 Part 7: 标准 Redux 模式 中会进一步展开。
现在可以看到更新后的 'todos/todoAdded' 动作被 dispatch:
最后需要修改的是 todos reducer。当我们向 /fakeApi/todos 发起 POST 请求时,服务器会返回一个全新的 todo 对象(包含新的 ID 值)。这意味着 reducer 不必自己计算新 ID、也不必填其他字段——它只需要构造一个包含新 todo 条目的新 state 数组:
const initialState = []
export default function todosReducer(state = initialState, action) {
switch (action.type) {
// highlight-start
case 'todos/todoAdded': {
// 返回一个新的 todos 状态数组,新 todo 条目追加在末尾
return [...state, action.payload]
}
// highlight-end
// 省略其他 case
default:
return state
}
}
这样,新增 todo 就完全正常工作了,状态 diff 如下:
提示:thunk 函数既能用于异步逻辑,也能用于同步逻辑。thunk 提供了一种方式来编写任何需要访问
dispatch和getState的可复用逻辑。
本教程小结
到目前为止,我们已经成功更新了 todo 应用:可以用 "thunk" 函数向假服务器 API 发起 HTTP 请求,从而获取待办列表并保存新的待办条目。
在这个过程中,我们看到了 Redux 中间件是如何让我们发起异步调用、并在异步调用完成后通过 dispatch 动作与 store 交互的。核心结论回顾:
- Redux 中间件被设计用于编写带副作用的逻辑
- "副作用"是改变函数外部状态/行为的代码,例如 HTTP 请求、修改函数参数、生成随机值;
- 中间件在标准 Redux 数据流中加了一个额外环节
- 中间件可以拦截被传给
dispatch的其他值; - 中间件可以访问
dispatch和getState,因此可以在异步逻辑中 dispatch 更多动作;
- 中间件可以拦截被传给
- Redux "Thunk" 中间件让我们可以向
dispatch传函数- "thunk" 函数让我们可以提前写好异步逻辑,而无需事先知道将使用哪个 Redux store;
- Redux thunk 函数接收
dispatch和getState作为参数,可以 dispatch "已从 API 响应中收到这些数据"之类的动作。
从本仓库源码看,这套机制的骨架非常紧凑:applyMiddleware(src/applyMiddleware.ts)以 enhancer 形式把中间件链组合成新的 dispatch,Middleware/MiddlewareAPI 类型(src/types/middleware.ts)定义了三层嵌套函数契约与 dispatch/getState 两个能力,compose(src/compose.ts)完成从右到左的管线嵌套,而 test/applyMiddleware.spec.ts 中的用例则逐一验证了构建期禁 dispatch、中间件仅装配一次、内部 dispatch 重走管线、以及与 thunk 配合的完整行为。
下一步
到这里,我们已经覆盖了使用 Redux 的所有核心部分:
- 编写根据 dispatch 的动作更新状态的 reducer;
- 用 reducer、enhancer 和中间件创建并配置 Redux store;
- 使用中间件编写会 dispatch 动作的异步逻辑。
在 Part 7: 标准 Redux 模式 中,我们将研究真实 Redux 应用常用的若干代码模式,让代码更一致,并随应用增长更好地扩展。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0622
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00



