mirror of
https://github.com/garrytan/gbrain.git
synced 2026-07-30 11:22:34 +00:00
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:
co-authored by
Magicray1217
Claude Opus 4.7
parent
ce05aef3de
commit
ead9643e82
@@ -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. */
|
||||
|
||||
@@ -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=...`',
|
||||
};
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user