Ax:把 DSPy 的编程模型搬进 TypeScript,再编译到六种语言
The pretty much "official" DSPy framework for Typescript
秒懂
- 它是什么?
- Ax 以 TypeScript 为先实现了 DSPy 风格的签名、优化器和代理,并把同一套语义编译到 Python、Java 等语言。本文评估它的核心机制、上手方式与适用边界。
- 适合谁用?
- 适合已经用 DSPy 思路做结构化 LLM 输出、且主力栈是 TypeScript 的团队,他们能直接获得签名 DSL、流式解析和优化器。需要跨语言共享同一套程序模型的团队也可以认真考虑,因为生成代码已提交在仓库里,可以检查。
- 能商用吗?
- 可以。Apache-2.0 是宽松许可证:你可以使用、修改并销售基于它的软件,只需保留版权和许可证声明。
- 还在维护吗?
- 在维护。仓库最近一次提交在 6 天前。
- 用什么语言写的?
- 主要是 TypeScript(依据 GitHub 的语言统计)。
以上回答依据项目的 GitHub 数据(最近同步于 2026年9月15日)和我们的分析,不构成法律意见。
开源项目深度解析
它解决的问题:提示词工程的结构化替代
Ax 面对的是 LLM 应用里最常见的重复劳动:把用户输入拼进提示词,解析模型返回的文本,再手工校验字段类型。README 直接说 no prompt engineering,它的做法是把一次模型调用抽象成签名。签名用字符串 DSL 描述,比如 'review:string -> sentiment:class "positive, negative, neutral"',左边是输入字段,右边是输出字段,类型和约束都写在签名里。这个思路来自 DSPy,Ax 自称是 TypeScript 的 pretty much official 实现。目标用户很明确:用 TypeScript 写代理或结构化抽取、同时不想在每次调用时手写 JSON schema 和解析逻辑的工程师。它把类型安全推到编译期,输出被推断为字面量联合类型,比如 sentiment 的类型就是 'positive' | 'negative' | 'neutral'。
核心机制:签名、部署配置与流式默认
Ax 的架构分三层。最上层是签名,支持字符串 DSL、流式的 f() 构建器,或者任意 Standard Schema v1 校验器,包括 Zod、Valibot、ArkType。中间是 AxGen 生成器,它负责渲染签名、调用 provider、解析结果。最下层是命名部署配置,name 字段决定线上行为,模型 ID 只在配置内部解析。这个设计的关键在于 provider 切换不是换 URL,而是换整套规则。README 举例说,一个 DeepSeek 模型如果托管在 Together 上,走的是 Together 的端点和推理规则,而不是 DeepSeek 原生协议。流式是默认路径,理由是它能在模型完成前就开始解析字段、运行流式断言、发现无效输出就取消流并提前纠正,避免为已知错误的结果继续花 token。这个取舍很实际,但也意味着你的代码必须能处理增量事件,不能假设 forward() 是唯一入口。
跨语言矩阵:一个语义核心,六种包
Ax 最激进的部分不是 TypeScript 本身,而是声称同一套语义被编译成 Python、Java、C++、Go、Rust 的库。TypeScript 是源实现,其他语言的生成代码直接提交在仓库的 packages/<language> 目录下。Python 包叫 axllm,Java 是 dev.axllm:ax,C++ 用 CMake FetchContent,Go 用 go get,Rust 发布在 crates.io。README 强调生成源码已检查入库,所以你可以直接查看支持哪些 API,不用猜。仓库里有一个 runner,用 npm run example -- <language> <file> 就能跑各语言的示例,不需要记编译器命令。这个设计对多语言团队有吸引力,但要注意它依赖一个语言无关的 AxIR 中间表示,README 提到当 AxIR 变化时,需要运行 npm run axir:generate-packages 来刷新包。这意味着任何语义改动都会触发六处重新生成,维护节奏是跟着上游走的。
30 秒上手:真实命令与配置键
README 给出了最小可运行示例。安装 npm 包 @ax-llm/ax,然后引入 ai 和 ax。ai({ name: "openai", apiKey: process.env.OPENAI_APIKEY }) 创建模型实例,name 是部署配置名,apiKey 从环境变量读。接着用 ax() 定义签名,调用 classify.forward(llm, { review: "..." }),返回类型是字面量联合。切换 provider 只需把 name 改成 anthropic、google-gemini、meta、together、fireworks、deepseek、grok 等,代码不变。想测流式延迟,README 给了具体命令,例如 AX_STREAM_BENCH_PROVIDER=anthropic AX_STREAM_BENCH_MODEL=claude-sonnet-4-5-20250929 AX_STREAM_BENCH_RUNS=2 AX_STREAM_BENCH_WARMUP_RUNS=0 npm run tsx src/examples/streaming-latency.ts。这些环境变量键名是文档里直接出现的,不是推测。注意 apiKey 的键名是 OPENAI_APIKEY,不是常见的 OPENAI_API_KEY,这个细节容易踩坑。
真实限制:流式默认与多语言维护成本
Ax 的流式默认策略在低延迟场景是优势,但也是潜在的坑。如果你的应用只需要最终对象,forward() 仍然可用,但团队里一旦有人误用 streamingForward() 并假设一次性返回,就会得到事件流而不是对象。文档没有说明流事件的具体形状,这部分需要查源码或示例。另一个限制是跨语言支持并非完全对等。Go 的 actor 运行时是 opt-in,且依赖 goja,Rust 被描述为 protocol-first code runtime,C++ 只能源码构建。这些措辞暗示生成的库不是所有语言都达到 TypeScript 的成熟度。维护成本也很明确:AxIR 变化时,六种语言的生成包必须同步刷新,否则不同语言的 Ax 版本会漂移。对只用 TypeScript 的团队,这部分成本可以忽略,但对想靠 Ax 统一多语言后端的团队,这是一笔持续的开销。
替代方案:DSPy 本身与手写 provider SDK
最直接的替代是 Python 原版 DSPy。Ax 的签名 DSL 和优化器概念都源自 DSPy,但 Ax 是 TypeScript 优先,而 DSPy 是 Python 生态的原生公民。如果你已经在 Python 里用 DSPy,并且团队没有跨语言需求,迁移到 Ax 没有明显收益。另一个替代方案是直接使用 provider 的 SDK,比如 OpenAI 的 Node 客户端,配合 Zod 做校验。Ax 的 README 明确说自己会保持与直接 provider 调用相近的延迟,同时加上类型输出、校验、重试、工具、追踪和记忆。直接 SDK 的代价是你得自己实现结构化输出控制循环,包括解析、校验失败重试、流式字段提取。Ax 的价值是把这些循环封装进 AxGen,但代价是引入一层抽象,你需要信任它对 provider 行为的映射。两者之间的选择本质是:你愿意自己维护多少胶水代码,还是愿意接受 Ax 的部署配置作为事实标准。
采用前的验证点:部署配置与基准
Ax 的文档提供了两个具体验证途径。第一是查看 docs/AI_PROFILES.md,它列出部署配置和类迁移说明,你必须在里面找到你的 provider 和模型组合,否则 name 字段可能解析失败或走错端点。第二是跑仓库自带的流式延迟基准,README 给出了针对 anthropic 和 google-gemini 的完整命令,并说明最近的运行结果显示 provider 排队和模型生成占主导,AxGen 与原始 ai.chat() 路径接近。这个结论来自 README 的陈述,不是本评测的实测。你应该在自己的 provider 和模型上跑一遍,设置 AX_STREAM_BENCH_RUNS 和 AX_STREAM_BENCH_WARMUP_RUNS 来控制次数。如果延迟差距在你的场景下不可接受,Ax 的抽象就不值得。另外,Ax 使用 Apache-2.0 许可证,商用没有 copyleft 限制,但生成的多语言包各自发布在 npm、PyPI、Maven 等平台,版本号可能不同步,升级时要逐个检查。
编辑结论
适合已经用 DSPy 思路做结构化 LLM 输出、且主力栈是 TypeScript 的团队,他们能直接获得签名 DSL、流式解析和优化器。需要跨语言共享同一套程序模型的团队也可以认真考虑,因为生成代码已提交在仓库里,可以检查。不适合只调用一次模型、不关心输出结构的项目,也不适合对多语言生成代码的维护成本敏感的小团队。采用前先验证三件事:你的 provider 是否在 AI_PROFILES.md 的部署配置里,Standard Schema 校验器与签名 DSL 的配合是否符合你的数据约束,以及流式默认行为在你的网络环境下是否真的降低延迟而非增加开销。Ax 的定位是替代手写提示词和胶水代码,但它不是运行时无关的银弹,六种语言的包各自要跟随 AxIR 更新,这是一笔真实的长期成本。
社区笔记