Ladybird 浏览器模糊测试实战:从 libFuzzer 本地构建到 OSS-Fuzz 持续运行的完整指南
Lagom(Ladybird 的独立构建体系)内置了一套完整的模糊测试(Fuzzing)基础设施:18+ 个基于 LLVM libFuzzer 的 Fuzzer 覆盖 JS 引擎、图像解码器、CSS/JSON/XML 解析器、Wasm 与正则表达式等浏览器核心组件,并且同一套构建配置可以无缝接入 OSS-Fuzz 持续运行。读完本文,你将掌握 Ladybird 中模糊测试的三种构建模式(libFuzzer 本地构建、standalone 无插桩构建、OSS-Fuzz 构建)的完整操作命令,理解 CMake 构建配置中每种模式的链接与插桩差异,并能独立复现、调试和符号化 Fuzzer 捕获的崩溃。
一、Fuzzer 全景:目标清单与构建系统
1.1 Fuzzer 目标清单
所有 Fuzzer 目标集中声明在 Meta/Fuzzers/fuzzers.cmake 中。当前仓库注册了以下 18 个基础 Fuzzer(另有一个条件编译的 CSSParser):
| Fuzzer 目标 | 被测库(依赖) | 覆盖能力 |
|---|---|---|
FuzzASN1 |
LibCrypto、LibTLS | ASN.1 证书/密钥解析 |
FuzzBase64Roundtrip |
— | Base64 编码往返 |
FuzzBMPLoader |
LibGfx、LibImageDecoders | BMP 图像解码 |
FuzzGIFLoader |
LibGfx、LibImageDecoders | GIF 图像解码 |
FuzzICOLoader |
LibGfx、LibImageDecoders | ICO 图像解码 |
FuzzJs |
LibJS、LibGC | JavaScript 解析与执行 |
FuzzJsonParser |
— | JSON 解析 |
FuzzMatroskaReader |
LibMedia | Matroska/WebM 容器解析 |
FuzzPEM |
LibCrypto | PEM 密钥解析 |
FuzzPNGLoader |
LibGfx、LibImageDecoders | PNG 图像解码 |
FuzzRegexECMA262 |
LibRegex | ECMA-262 正则表达式 |
FuzzRSAKeyParsing |
LibCrypto | RSA 密钥解析 |
FuzzTextDecoder |
LibTextCodec | 文本编码解码 |
FuzzURL |
LibURL | URL 解析 |
FuzzWasmParser |
LibWasm | Wasm 字节码解析 |
FuzzWOFF |
LibGfx | WOFF 字体解码 |
FuzzXML |
LibXML | XML 解析 |
FuzzCSSParser(条件) |
LibWeb | CSS 解析(仅当 LibWeb 目标存在时追加) |
CSSParser 的注册方式是按需追加的,fuzzers.cmake 中的逻辑是:
if (TARGET LibWeb)
list(APPEND FUZZER_TARGETS CSSParser)
endif()
也就是说,如果构建配置没有编译 LibWeb,这个 Fuzzer 会自动缺席,其余 18 个仍然可用。
1.2 三种构建模式在 CMake 中的实现
Meta/Fuzzers/CMakeLists.txt 定义了一个 add_simple_fuzzer() 函数,它是所有 Fuzzer 的统一入口,内部按 CMake 选项分三个分支:
function(add_simple_fuzzer name)
add_executable(${name} "${name}.cpp")
if (ENABLE_FUZZERS_OSSFUZZ)
# OSS-Fuzz:引擎由外部 -DFUZZER_DICTIONARY_DIRECTORY 与 $LIB_FUZZING_ENGINE 提供
if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${name}.dict")
configure_file("${name}.dict" "${FUZZER_DICTIONARY_DIRECTORY}/${name}.dict" COPYONLY)
endif()
target_link_libraries(${name} PUBLIC ${ARGN} AK LibCore)
elseif (ENABLE_FUZZERS_LIBFUZZER)
# 本地 libFuzzer:-g -O1 -fsanitize=fuzzer
target_compile_options(${name} PRIVATE $<$<CXX_COMPILER_ID:Clang>:-g -O1 -fsanitize=fuzzer>)
target_link_libraries(${name}
PUBLIC ${ARGN} AK LibCore
PRIVATE $<$<CXX_COMPILER_ID:Clang>:-fsanitize=fuzzer>)
else()
# standalone:链接 EntryShim.cpp 提供普通 main()
target_sources(${name} PRIVATE "EntryShim.cpp")
target_link_libraries(${name} PUBLIC ${ARGN} AK LibCore)
endif()
# ...
endfunction()
三个分支对应三种使用场景:
ENABLE_FUZZERS_OSSFUZZ:面向 OSS-Fuzz 容器环境。注意这里会把同目录的.dict词典文件(如 FuzzJs.dict)原样拷贝到FUZZER_DICTIONARY_DIRECTORY,供 libFuzzer 的种子词典机制使用;实际的 fuzzing 引擎由 OSS-Fuzz 通过$LIB_FUZZING_ENGINE链接器标志注入。ENABLE_FUZZERS_LIBFUZZER:本地开发常用路径。编译时加-g -O1 -fsanitize=fuzzer,链接时同样加-fsanitize=fuzzer,得到自带main()、可无限循环运行并自动落盘 crash 输入的完整 libFuzzer 可执行文件。- 默认(standalone):不注入任何插桩,改为把 EntryShim.cpp 编入目标,提供一个简单的
main()。
此外,在 ENABLE_FUZZERS_LIBFUZZER 下,链接阶段还会统一追加 Address Sanitizer:
if (ENABLE_FUZZERS_LIBFUZZER)
set(CMAKE_EXE_LINKER_FLAGS "${ORIGINAL_CMAKE_EXE_LINKER_FLAGS} -fsanitize=address")
set(CMAKE_SHARED_LINKER_FLAGS "${ORIGINAL_CMAKE_SHARED_LINKER_FLAGS} -fsanitize=address")
set(CMAKE_MODULE_LINKER_FLAGS "${ORIGINAL_CMAKE_MODULE_LINKER_FLAGS} -fsanitize=address")
这正是 Meta/Fuzzers/README.md 所说“Fuzzers work best with Address Sanitizer enabled”的落地实现。同文件末尾还单独构建了一个 FuzzilliJs 目标——它不链接完整 libFuzzer 引擎,而是用 -fsanitize-coverage=trace-pc-guard 生成覆盖率插桩,作为 Fuzzilli 覆盖引导 fuzzer 的被测二进制(配套 FuzzilliJs.dockerfile 与 FuzzilliJsInstructions.md)。
1.3 顶层 CMake 的开关与前置校验
CMakeLists.txt(仓库根目录)中的关键逻辑:
if (ENABLE_FUZZERS_LIBFUZZER OR ENABLE_FUZZERS_OSSFUZZ)
set(ENABLE_FUZZERS ON)
endif()
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang$")
if (ENABLE_FUZZERS_LIBFUZZER)
add_cxx_compile_options(-fsanitize=fuzzer)
set(LINKER_FLAGS "${LINKER_FLAGS} -fsanitize=fuzzer")
endif()
elseif (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
if (ENABLE_FUZZERS_LIBFUZZER)
message(FATAL_ERROR
"Fuzzer Sanitizer (-fsanitize=fuzzer) is only supported for Fuzzer targets with LLVM. ...")
endif()
endif()
# ...
if (ENABLE_FUZZERS)
add_subdirectory(Meta/Fuzzers)
endif()
由此可以得到三条构建约束:
- 只要打开
ENABLE_FUZZERS_LIBFUZZER或ENABLE_FUZZERS_OSSFUZZ任一开关,就会自动置位ENABLE_FUZZERS,进而把Meta/Fuzzers子目录加入构建; - 使用 GCC 工具链却打开
ENABLE_FUZZERS_LIBFUZZER会被message(FATAL_ERROR ...)直接拒绝,因为-fsanitize=fuzzer仅 LLVM 支持——这解释了为什么本地 fuzz 构建必须用 clang; - 打开 libFuzzer 开关后,
-fsanitize=fuzzer会通过add_cxx_compile_options全局生效,这也是 README 强调“fuzzer build requires code generators to be pre-built without fuzzing in a two stage build”的原因:代码生成阶段与 fuzz 阶段需要隔离,避免生成器被 fuzzer 插桩污染。
本地构建的 CMake preset 定义在 Meta/CMake/presets/CMakeBasePresets.json,名为 Fuzzers,其缓存变量为:
"binaryDir": "$env{LADYBIRD_SOURCE_DIR}/Build/fuzzers",
"cacheVariables": {
"BUILD_SHARED_LIBS": "OFF",
"CMAKE_BUILD_TYPE": "RelWithDebInfo",
"VCPKG_OVERLAY_TRIPLETS": "...distribution-triplets",
"ENABLE_FUZZERS_LIBFUZZER": "ON",
"ENABLE_ADDRESS_SANITIZER": "ON"
}
二、本地运行 Fuzzer(libFuzzer 模式)
2.1 标准构建流程
按 README 的指引,本地 fuzzing 需要 clang(仓库要求 clang >= 14),并建议使用独立的构建目录。完整命令:
./BuildFuzzers.sh
./Build/lagom-fuzzers/FuzzSomething # The full list can be found in Fuzzers/CMakeLists.txt
BuildFuzzers.sh 的默认分支实际执行:
cmake -S "$LADYBIRD_SOURCE_DIR" -GNinja --preset Fuzzers -B "$LADYBIRD_SOURCE_DIR"/Build/lagom-fuzzers \
-DCMAKE_C_COMPILER="${CC}" \
-DCMAKE_CXX_COMPILER="${CXX}"
ninja -C "$LADYBIRD_SOURCE_DIR"/Build/lagom-fuzzers
也就是说:Ninja 生成器 + Fuzzers preset(即上一节列出的缓存变量),产物落在 Build/lagom-fuzzers/。脚本开头通过 Meta/Utils/find_compiler.sh 的 pick_host_compiler --clang-only 挑选 clang 工具链(README 中提到的 pick_clang() 在仓库演进中已改为这个 find_compiler 工具函数,作用一致:从系统预定义路径中寻找满足版本要求的 clang),并把选中的 CC/CXX 显式传给 CMake,确保 fuzz 目标由 clang 编译。
2.2 一个 Fuzzer 长什么样
以最简单的 Meta/Fuzzers/FuzzBMPLoader.cpp 为例,整个文件只有 19 行:
#include <LibGfx/ImageFormats/BMPLoader.h>
#include <stdio.h>
extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size)
{
AK::set_debug_enabled(false);
auto decoder_or_error = Gfx::BMPImageDecoderPlugin::create({ data, size });
if (decoder_or_error.is_error())
return 0;
auto decoder = decoder_or_error.release_value();
(void)decoder->frame(0);
return 0;
}
这体现了仓库中所有 Fuzzer 的统一模式:
- 导出 C 符号
LLVMFuzzerTestOneInput(uint8_t const* data, size_t size)作为唯一入口; - 首行
AK::set_debug_enabled(false)关闭 AK 的调试断言噪声,让 sanitizer 报告成为唯一的崩溃信号; - 用原始字节构造解码器并强制解码第 0 帧,任何越界读、非法分配、UB 都会被 ASan 捕获。
以 Meta/Fuzzers/FuzzJs.cpp 为例则是“解析 + 执行”的更重模式:先 Utf8View(js).validate() 校验 UTF-8,再 JS::VM::create() 创建虚拟机、解析 JS::Script 并 vm->run(...) 执行,从而用随机字节驱动整个 JS 引擎链路。
2.3 运行时的实用技巧(README 全部要点)
以下均为 Meta/Fuzzers/README.md 给出的可操作建议,逐条说明:
- fuzzing 结果落盘在当前目录:crash、slow 输入等结果文件(如
crash-27480a...、slow-...)会直接写进你运行 Fuzzer 的工作目录。 - 喂语料(corpus):给 Fuzzer 一个初始语料能显著提升覆盖率:
./Fuzzers/FuzzBMPLoader ../Base/res/html/misc/bmpsuite_files/rgba32-61754.bmp。需要说明的是,README 引用的Base/res/html/misc图像套件在仓库当前结构中已不存在;同类测试输入现在集中在 Tests/LibGfx/test-inputs/ 目录下,例如Tests/LibGfx/test-inputs/bmp/中就有bitmap.bmp、too-many-palette-colors.bmp以及来自 OSS-Fuzz 的oss-fuzz-testcase-62541.bmp,可直接作为语料来源。 - LLVM 引擎也会创建新文件(如
crash-*、slow-*、语料库文件),README 特别提醒不要盲目提交它们。 - 并行:
-jobs=24 -workers=24让 libFuzzer 分叉 24 个 worker 并行探索。 - 降噪:
-close_fd_mask=3同时关闭 stdout/stderr 以减少日志输出,但会连断言信息一起隐藏;-close_fd_mask=1只关 stdout,是折中方案。README 同时建议把话痨式的日志输出挪到FOO_DEBUG宏后面,从源头降噪。
2.4 无插桩构建:--standalone
如果只想“对单个输入文件跑一遍被测代码”(例如 CI 中对某个复现文件做回归验证),可以用:
./BuildFuzzers.sh --standalone
# 从给定文件(或 stdin)读取单个输入并执行后退出
./Build/lagom-fuzzers-standalone/Fuzzers/FuzzSomething
对应脚本分支:
cmake -S "$LADYBIRD_SOURCE_DIR" -GNinja -B "$LADYBIRD_SOURCE_DIR"/Build/lagom-fuzzers-standalone \
-DENABLE_FUZZERS=ON
注意这里只打开 ENABLE_FUZZERS,不打开 ENABLE_FUZZERS_LIBFUZZER,于是 add_simple_fuzzer() 走第三个分支,把 EntryShim.cpp 编进目标。EntryShim 的实现很直白:
argc > 1时调用fuzz_from_file(argv[1]):stat取文件大小 →malloc缓冲 →read读满 →LLVMFuzzerTestOneInput;- 无参数时调用
fuzz_from_stdin():以 4096 字节为块循环realloc+read直到 EOF,再交给同一个入口。
这就是 README 所说“read a single test input from a given filename (or, if no filename is given, from stdin) and exit”的全部实现。
2.5 微调 fuzz 构建的 CMake 缓存
README 允许直接操纵 fuzzing 构建的 CMake 缓存,例如:
cmake -B Build/fuzzers -S . -DENABLE_LAGOM_CCACHE=OFF
因为 preset 的 binaryDir 就是 Build/fuzzers,这条命令改的是与 BuildFuzzers.sh 相同的构建目录(ccache 对某些 sanitizer 组合不友好时尤其有用)。同理,你也能在这个目录上追加任意 -D 选项来调整 fuzz 构建。另外 README 提到:想换用其他 fuzzing 引擎(OSS-Fuzz 构建即为示范),大概率需要在第二阶段 CMake 构建或环境变量中显式设置 CFLAGS/CXXFLAGS。
三、管理有价值的测试用例
README 专门用一节讨论“如何跟踪那些命中大量边界条件的怪异文件”,其结论和现状值得完整梳理:
- 仓库中确实存在一批历史积累的图像测试套件(bmp suite、jpg suite 等),但它们带有 GPL 许可,与仓库其余部分的许可兼容性不佳,因此团队选择把“自产的”有趣测试用例与 GPL 套件分离;
- 由于 fuzzing 会不断产生更多更大的文件,README 的结论是:不要把实际测试用例堆在主仓库里,而是放进独立的 fuzz corpora 仓库(README 原文引用的是 SerenityOS 时代的
serenity-fuzz-corpora),鼓励往那里“upload lots and lots files”; - 从当前仓库的结构可以印证这一策略:崩溃复现输入以
oss-fuzz-testcase-*.bmp等命名保存在Tests/LibGfx/test-inputs/这类测试输入目录中,作为回归测试资产,而非膨胀主代码树。
对贡献者的实操含义:当你本地 fuzz 出一个 crash 输入文件后,合理做法是把它压缩/最小化后提交为测试输入或上传到 corpora 仓库,同时按项目流程报告问题——而不是把几十 MB 的语料直接 commit 进来。
四、在 OSS-Fuzz 上持续运行 Fuzzer
4.1 OSS-Fuzz 侧的机制
按 README 说明:oss-fuzz.com 会自动运行 Fuzzers/ 子目录下所有名字以 Fuzz 开头、且注册进了构建(即进入 FUZZER_TARGETS 清单)的 Fuzzer——前提是配置了 ENABLE_FUZZERS_OSSFUZZ。OSS-Fuzz 上的项目配置会调用 BuildFuzzers.sh 并带上 --oss-fuzz 参数,在官方 Docker 容器内构建。
--oss-fuzz 分支的完整 cmake 调用(摘自 BuildFuzzers.sh)值得逐参数解读:
cmake -S "$LADYBIRD_SOURCE_DIR" -GNinja -B "$LADYBIRD_SOURCE_DIR"/Build/fuzzers \
-DBUILD_SHARED_LIBS=OFF \
-DENABLE_FUZZERS_OSSFUZZ=ON \
-DFUZZER_DICTIONARY_DIRECTORY="$OUT" \
-DCMAKE_C_COMPILER="${CC}" \
-DCMAKE_CXX_COMPILER="${CXX}" \
-DCMAKE_CXX_FLAGS="$CXXFLAGS -DOSS_FUZZ=ON" \
-DLINKER_FLAGS="$LIB_FUZZING_ENGINE"
ninja -C "$LADYBIRD_SOURCE_DIR"/Build/fuzzers
cp "$LADYBIRD_SOURCE_DIR"/Build/fuzzers/bin/Fuzz* "$OUT"/
-DENABLE_FUZZERS_OSSFUZZ=ON:走add_simple_fuzzer()的第一分支——不自己加-fsanitize=fuzzer,把引擎留给$LIB_FUZZING_ENGINE(OSS-Fuzz 容器注入的 libFuzzer 静态库,通过-DLINKER_FLAGS="$LIB_FUZZING_ENGINE"接入链接阶段);-DFUZZER_DICTIONARY_DIRECTORY="$OUT":$OUT是 OSS-Fuzz 约定的产物输出目录,.dict词典会被拷贝到那里与二进制并排,libFuzzer 运行时即可自动发现;-DOSS_FUZZ=ON:全局宏定义,供源码在 OSS-Fuzz 环境下调整行为;- 最后
cp .../bin/Fuzz* "$OUT"/把所有 Fuzzer 二进制收集到 OSS-Fuzz 要求的位置。
4.2 在本地复现 OSS-Fuzz 构建
README 给出的完整命令序列(需先获取 google/oss-fuzz 仓库,此处不展开外部地址):
cd oss-fuzz
python3 infra/helper.py build_image serenity
python3 infra/helper.py build_fuzzers serenity
产物位于 oss-fuzz 仓库的 build/out/serenity,可逐个手动运行,或直接:
python3 infra/helper.py run_fuzzer serenity FUZZER_NAME
两个进阶用法:
- 用 OSS-Fuzz 的构建流程、但对着本地 checkout 构建(调试本地改动时非常有用):
python3 infra/helper.py build_fuzzers serenity $HOME/src/serenity/(把路径替换为你本地的 Ladybird 检出目录); - 直接进容器手动折腾:
docker run -it gcr.io/oss-fuzz/serenity bash。
五、分析一个 crash:复现、gdb 与符号化
README 的“Analyzing a crash”一节包含几个非常实用的坑位提示,全部继承如下:
5.1 libFuzzer 的怪异接口
LLVM fuzzer 的帮助需要用 -help=1 查看,--help 和 -help 都会被忽略。
5.2 复现与 gdb 调试
复现某个 crash 输入,直接把文件作为参数传给 Fuzzer:
MyFuzzer crash-27480a219572aa5a11b285968a3632a4cf25388e
在 gdb 中复现时,需要关闭 libFuzzer 自带的信号处理器,否则 gdb 看到的是信号处理后的现场而不是真正崩溃点。README 给出的完整示例(-handle_abrt=0 -handle_segv=0):
$ gdb ./Fuzzers/FuzzBMP
<... SNIP some output ...>
(gdb) run -handle_abrt=0 -handle_segv=0 crash-27480a219572aa5a11b285968a3632a4cf25388e
<... SNIP some output ...>
FuzzBMP: ../../Libraries/LibGfx/Bitmap.cpp:84: Gfx::Bitmap::Bitmap(...): Assertion `m_data && m_data != (void*)-1' failed.
Thread 1 "FuzzBMP" received signal SIGABRT, Aborted.
__GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:50
(gdb)
要点:加两个 -handle_* 参数后,断言/段错误会以原始信号形式到达 gdb,backtrace 才能给出真实栈。
5.3 UBSan 与符号化两个常见坑
- UBSan 默认输出经常没用:设置
export UBSAN_OPTIONS=print_stacktrace=1让每个 UBSan 报告都带完整堆栈; - external symbolizer 报错:如果你看到
WARNING: invalid path to external symbolizer!/Failed to use and restart external symbolizer!, 意味着 sanitizer 找不到llvm-symbolizer可执行文件。它通常随系统的llvm软件包提供;注意带版本号后缀的llvm-symbolizer-11这类名字不会被 sanitizer 识别,需要用符号链接或 PATH 调整让裸名llvm-symbolizer可被找到。
六、速查:常用命令汇总
| 场景 | 命令 |
|---|---|
| 本地 libFuzzer 构建 | ./BuildFuzzers.sh |
| 运行某个 Fuzzer | ./Build/lagom-fuzzers/FuzzSomething |
| 带语料运行 | ./Fuzzers/FuzzBMPLoader <corpus-file.bmp> |
| 无插桩 standalone 构建 | ./BuildFuzzers.sh --standalone |
| 单文件复现(standalone) | ./Build/lagom-fuzzers-standalone/Fuzzers/FuzzSomething input.bin(或从 stdin 读入) |
| 调整 fuzz 构建缓存 | cmake -B Build/fuzzers -S . -DENABLE_LAGOM_CCACHE=OFF |
| 并行 fuzz | 追加 -jobs=24 -workers=24 |
| 降低日志输出 | 追加 -close_fd_mask=3(会隐藏断言信息)或 -close_fd_mask=1 |
| 复现 crash | MyFuzzer crash-<hash> |
| gdb 中复现 | run -handle_abrt=0 -handle_segv=0 crash-<hash> |
| UBSan 打印堆栈 | export UBSAN_OPTIONS=print_stacktrace=1 |
适用前提与限制:本地 libFuzzer 构建要求 clang(>= 14)工具链与 Ninja,且因 -fsanitize=fuzzer 仅 LLVM 支持,GCC 构建该模式会被顶层 CMake 直接报错拒绝;standalone 模式则对编译器无此限制。Fuzzer 清单以 Meta/Fuzzers/fuzzers.cmake 为准,OSS-Fuzz 侧只会运行其中注册的 Fuzz* 目标。
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 StartedRust0624
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