feat(ai): add Zhipu AI (BigModel) recipe (#59 split, part 2/2)

13th recipe. embedding-3 (current) + embedding-2; 1024 default dims
with Matryoshka options [256, 512, 1024, 2048].

OpenAI-compatible at open.bigmodel.cn. embedding-3 at 2048 dims exceeds
pgvector's HNSW cap of 2000 — those brains fall back to exact vector
scans via the existing chunkEmbeddingIndexSql policy at
src/core/vector-index.ts. Default stays at 1024 (HNSW-fast); users who
want maximum fidelity opt into 2048 via --embedding-dimensions and
accept the slower retrieval.

Tests pin the HNSW boundary: 1024 returns the index SQL, 2048 returns
the skip-index/exact-scan SQL.

Tests: bun test test/ai/ — 112/112 (6 new + 106 prior).

Plan: ~/.claude/plans/ok-lets-turn-this-enumerated-sonnet.md (commit 7
of 11). Reworked from #59. Together with DashScope (commit 6), closes
the China-region embedding gap users repeatedly reported (DashScope
covers Alibaba, Zhipu covers BigModel; both ship with international
endpoints by default).

Co-Authored-By: Magicray1217 <267836857+Magicray1217@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-05-09 23:54:18 -07:00
co-authored by Magicray1217 Claude Opus 4.7
parent ce05aef3de
commit ead9643e82
3 changed files with 113 additions and 0 deletions
+2
View File
@@ -18,6 +18,7 @@ import { together } from './together.ts';
import { llamaServer } from './llama-server.ts';
import { minimax } from './minimax.ts';
import { dashscope } from './dashscope.ts';
import { zhipu } from './zhipu.ts';
const ALL: Recipe[] = [
openai,
@@ -32,6 +33,7 @@ const ALL: Recipe[] = [
llamaServer,
minimax,
dashscope,
zhipu,
];
/** Map from `provider:id` key to recipe. */
+40
View File
@@ -0,0 +1,40 @@
import type { Recipe } from '../types.ts';
/**
* Zhipu AI (智谱AI) BigModel Open Platform. OpenAI-compatible /embeddings
* endpoint at open.bigmodel.cn. Hosts embedding-2 (1024d) and embedding-3
* (Matryoshka up to 2048d).
*
* embedding-3 at 2048 dims exceeds pgvector's HNSW cap of 2000 — those
* brains fall back to exact vector scans (see
* src/core/ai/vector-index.ts:PGVECTOR_HNSW_VECTOR_MAX_DIMS). v0.32 ships
* with `default_dims: 1024` (HNSW-compatible) and exposes 2048 via
* dims_options for users who want the full embedding fidelity at the
* cost of slower retrieval.
*
* Reference: https://open.bigmodel.cn/
*/
export const zhipu: Recipe = {
id: 'zhipu',
name: 'Zhipu AI (智谱AI BigModel)',
tier: 'openai-compat',
implementation: 'openai-compatible',
base_url_default: 'https://open.bigmodel.cn/api/paas/v4',
auth_env: {
required: ['ZHIPUAI_API_KEY'],
setup_url: 'https://open.bigmodel.cn/',
},
touchpoints: {
embedding: {
models: ['embedding-3', 'embedding-2'],
default_dims: 1024,
// 2048 exposed but breaks HNSW (exact-scan fallback). 1024/512/256
// stay HNSW-compatible.
dims_options: [256, 512, 1024, 2048],
max_batch_tokens: 8192,
chars_per_token: 2,
},
},
setup_hint:
'Get an API key at https://open.bigmodel.cn/, then `export ZHIPUAI_API_KEY=...`',
};
+71
View File
@@ -0,0 +1,71 @@
/**
* Zhipu AI (BigModel) recipe smoke (Commit 7 of the v0.32 wave).
*
* Coverage:
* - Recipe registered with expected shape
* - default auth: ZHIPUAI_API_KEY → "Bearer <key>"; missing → AIConfigError
* - dims_options exposes [256, 512, 1024, 2048]; default 1024 (HNSW-compatible)
* - 2048-dim path falls into exact-scan branch via chunkEmbeddingIndexSql
* from src/core/vector-index.ts
*/
import { describe, expect, test } from 'bun:test';
import { getRecipe } from '../../src/core/ai/recipes/index.ts';
import { defaultResolveAuth } from '../../src/core/ai/gateway.ts';
import { AIConfigError } from '../../src/core/ai/errors.ts';
import {
PGVECTOR_HNSW_VECTOR_MAX_DIMS,
chunkEmbeddingIndexSql,
} from '../../src/core/vector-index.ts';
describe('recipe: zhipu', () => {
test('registered with expected shape', () => {
const r = getRecipe('zhipu');
expect(r).toBeDefined();
expect(r!.id).toBe('zhipu');
expect(r!.tier).toBe('openai-compat');
expect(r!.implementation).toBe('openai-compatible');
expect(r!.base_url_default).toBe('https://open.bigmodel.cn/api/paas/v4');
expect(r!.auth_env?.required).toEqual(['ZHIPUAI_API_KEY']);
});
test('embedding touchpoint declares embedding-3 first + 1024 dims (HNSW-compatible default)', () => {
const r = getRecipe('zhipu')!;
expect(r.touchpoints.embedding).toBeDefined();
expect(r.touchpoints.embedding!.models[0]).toBe('embedding-3');
expect(r.touchpoints.embedding!.models).toContain('embedding-2');
expect(r.touchpoints.embedding!.default_dims).toBe(1024);
expect(r.touchpoints.embedding!.dims_options).toEqual([256, 512, 1024, 2048]);
// The default must stay HNSW-compatible.
expect(r.touchpoints.embedding!.default_dims).toBeLessThanOrEqual(
PGVECTOR_HNSW_VECTOR_MAX_DIMS,
);
});
test('default auth: ZHIPUAI_API_KEY set → "Bearer <key>"', () => {
const r = getRecipe('zhipu')!;
const auth = defaultResolveAuth(r, { ZHIPUAI_API_KEY: 'fake-zhipu-key' }, 'embedding');
expect(auth.headerName).toBe('Authorization');
expect(auth.token).toBe('Bearer fake-zhipu-key');
});
test('default auth: missing ZHIPUAI_API_KEY → AIConfigError', () => {
const r = getRecipe('zhipu')!;
expect(() => defaultResolveAuth(r, {}, 'embedding')).toThrow(AIConfigError);
});
test('2048-dim option from dims_options falls into exact-scan branch', () => {
// 2048d exceeds the HNSW cap, so chunkEmbeddingIndexSql returns the
// exact-scan-skip-index path. Users picking 2048 trade ANN speed for
// full embedding fidelity.
const sql = chunkEmbeddingIndexSql(2048);
expect(sql.toLowerCase()).toContain('skipped');
expect(sql.toLowerCase()).toContain('hnsw');
});
test('1024-dim default returns the HNSW index SQL (fast path)', () => {
const sql = chunkEmbeddingIndexSql(1024);
expect(sql.toLowerCase()).toContain('create index');
expect(sql.toLowerCase()).toContain('hnsw');
});
});