@keo-ai/axiom 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +237 -4
- package/dist/embedding_search/embed.d.ts +1 -0
- package/dist/embedding_search/embed.js +52 -0
- package/dist/embedding_search/index.d.ts +27 -0
- package/dist/embedding_search/index.js +162 -0
- package/dist/embedding_search/pgvector.d.ts +19 -0
- package/dist/embedding_search/pgvector.js +116 -0
- package/dist/embedding_search/types.d.ts +26 -0
- package/dist/embedding_search/types.js +2 -0
- package/dist/index.d.ts +30 -1
- package/dist/index.js +33 -3
- package/dist/predict/config.d.ts +4 -7
- package/dist/predict/config.js +5 -10
- package/dist/predict/predict.d.ts +6 -14
- package/dist/predict/predict.js +3 -5
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -35,6 +35,8 @@ Axiom 是 **keo** 的 LLM 底座库。它不碰业务编排(意图路由、节
|
|
|
35
35
|
│ └──────────┘ ┌─────────────┐ │
|
|
36
36
|
│ │ RAG基础 │ │
|
|
37
37
|
│ │ Embedding │ │
|
|
38
|
+
│ │ pgvector │ │
|
|
39
|
+
│ │ rerank │ │
|
|
38
40
|
│ └─────────────┘ │
|
|
39
41
|
└────────────────────────────────────────────┘
|
|
40
42
|
```
|
|
@@ -45,7 +47,7 @@ Axiom 是 **keo** 的 LLM 底座库。它不碰业务编排(意图路由、节
|
|
|
45
47
|
- ✅ LLM 统一调用 + Provider 故障转移
|
|
46
48
|
- ✅ Function Call Loop 的驱动、记录、校验、副作用传播
|
|
47
49
|
- ✅ 记忆系统的抽象与基础实现
|
|
48
|
-
- ✅ RAG 基础(Embedding /
|
|
50
|
+
- ✅ RAG 基础(Embedding / pgvector 向量检索 / rerank)
|
|
49
51
|
- ✅ 基础配置中心
|
|
50
52
|
|
|
51
53
|
**Axiom 不做:**
|
|
@@ -60,6 +62,32 @@ Axiom 是 **keo** 的 LLM 底座库。它不碰业务编排(意图路由、节
|
|
|
60
62
|
|
|
61
63
|
---
|
|
62
64
|
|
|
65
|
+
## 安装
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
npm install @keo-ai/axiom pg
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
> `pg` 是 peer dependency,用于 EmbeddingSearch 模块的 pgvector 连接。
|
|
72
|
+
|
|
73
|
+
在你的项目根目录创建 `.env` 文件:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
BAILIAN_API_KEY=your-api-key
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
然后在你的项目入口文件顶部引入 `dotenv` 以加载环境变量:
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
import 'dotenv/config';
|
|
83
|
+
|
|
84
|
+
// 或指定路径
|
|
85
|
+
import { config } from 'dotenv';
|
|
86
|
+
config({ path: '.env.local' });
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
63
91
|
## Predict 模块(LLM 调用)
|
|
64
92
|
|
|
65
93
|
Predict 是 Axiom 的 LLM 调用层。它封装了 Provider 连接、请求组装、故障转移和返回解析,让上层只需关心「用什么模型、传什么消息」。
|
|
@@ -78,7 +106,7 @@ export BAILIAN_API_KEY="your-api-key"
|
|
|
78
106
|
import { LLM } from '@keo-ai/axiom';
|
|
79
107
|
|
|
80
108
|
const res = await LLM.predict({ model: 'qwen3.7-max', prompt: '你好' });
|
|
81
|
-
console.log(res
|
|
109
|
+
console.log(res);
|
|
82
110
|
```
|
|
83
111
|
|
|
84
112
|
### API 概览
|
|
@@ -96,9 +124,8 @@ console.log(res.content);
|
|
|
96
124
|
|
|
97
125
|
| `responseFormat` | 返回值 | 说明 |
|
|
98
126
|
|---|---|---|
|
|
99
|
-
| `'text'` | `string` | 直接返回模型输出的文本 |
|
|
127
|
+
| 未设置 / `'text'` | `string` | 直接返回模型输出的文本 |
|
|
100
128
|
| `'json'` | `any` | 自动 `JSON.parse` |
|
|
101
|
-
| 未设置 | `LLMResponse` | 完整响应(含 `content`、`usage`、`model`) |
|
|
102
129
|
|
|
103
130
|
```ts
|
|
104
131
|
// text → string
|
|
@@ -170,6 +197,212 @@ try {
|
|
|
170
197
|
|
|
171
198
|
---
|
|
172
199
|
|
|
200
|
+
## EmbeddingSearch 模块(向量检索)
|
|
201
|
+
|
|
202
|
+
EmbeddingSearch 是 Axiom 的 RAG 底座。它封装了 `query → embedding → pgvector 检索 → [可选 rerank]` 的完整链路,只需一行代码即可实现语义检索。
|
|
203
|
+
|
|
204
|
+
### 需要的列
|
|
205
|
+
|
|
206
|
+
使用 `EmbeddingSearch` 前,先创建 pgvector 表。**只有 `embedding` 列是必需的**(默认列名 `embedding`,可通过 `embeddingColumn` 改),其余列完全自由:
|
|
207
|
+
|
|
208
|
+
| 列名 | 类型 | 说明 |
|
|
209
|
+
|---|---|---|
|
|
210
|
+
| `embedding` | `vector(1024)` | 向量字段,必需,默认列名 `embedding`,可通过 `embeddingColumn` 自定义 |
|
|
211
|
+
| *(任意)* | *(任意)* | 你自己的业务列,可直接用于过滤和返回 |
|
|
212
|
+
|
|
213
|
+
**开 rerank 时的额外要求**:需指定 `rerankKey` 告诉系统哪一列是文本内容(默认 `'content'`)。
|
|
214
|
+
|
|
215
|
+
```sql
|
|
216
|
+
CREATE EXTENSION IF NOT EXISTS vector;
|
|
217
|
+
|
|
218
|
+
-- 最简表:不开 rerank,只有向量和业务列
|
|
219
|
+
CREATE TABLE products (
|
|
220
|
+
product_id SERIAL PRIMARY KEY,
|
|
221
|
+
name VARCHAR(100),
|
|
222
|
+
category VARCHAR(50),
|
|
223
|
+
price NUMERIC,
|
|
224
|
+
embedding VECTOR(1024)
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
-- 开 rerank 的表:有文本列供 rerank 使用
|
|
228
|
+
CREATE TABLE documents (
|
|
229
|
+
doc_id SERIAL PRIMARY KEY,
|
|
230
|
+
title VARCHAR(100),
|
|
231
|
+
body TEXT, -- rerankKey: 'body'
|
|
232
|
+
embedding VECTOR(1024)
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
CREATE INDEX ON documents USING ivfflat (embedding vector_cosine_ops);
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### 快速开始
|
|
239
|
+
|
|
240
|
+
不传 `select` 时默认返回所有列(`SELECT *`):
|
|
241
|
+
|
|
242
|
+
```ts
|
|
243
|
+
import { EmbeddingSearch } from '@keo-ai/axiom';
|
|
244
|
+
import { Pool } from 'pg';
|
|
245
|
+
|
|
246
|
+
const pool = new Pool({ connectionString: 'postgresql://...' });
|
|
247
|
+
|
|
248
|
+
// 默认 SELECT *,返回所有列
|
|
249
|
+
const results = await EmbeddingSearch.query('衣服质量怎么样', {
|
|
250
|
+
tableName: 'documents',
|
|
251
|
+
}, pool);
|
|
252
|
+
|
|
253
|
+
for (const r of results) {
|
|
254
|
+
console.log(r.id, r.content, r.embeddingScore, r.rerankScore);
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
指定 `select` 只返回需要的列:
|
|
259
|
+
|
|
260
|
+
```ts
|
|
261
|
+
const results = await EmbeddingSearch.query('衣服质量怎么样', {
|
|
262
|
+
tableName: 'documents',
|
|
263
|
+
select: ['id', 'category', 'price'],
|
|
264
|
+
}, pool);
|
|
265
|
+
|
|
266
|
+
// 只返回 id、category、price 三列,以及 embeddingScore 和 rerankScore
|
|
267
|
+
for (const r of results) {
|
|
268
|
+
console.log(r.category, r.price, r.embeddingScore);
|
|
269
|
+
}
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
文本列通过 `rerankKey` 指定:
|
|
273
|
+
|
|
274
|
+
```ts
|
|
275
|
+
const results = await EmbeddingSearch.query('衣服质量怎么样', {
|
|
276
|
+
tableName: 'documents',
|
|
277
|
+
rerankKey: 'body',
|
|
278
|
+
}, pool);
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### 返回结构
|
|
282
|
+
|
|
283
|
+
`SearchResult` 不固定字段,取决于你查询了哪些列,但始终包含两个 score:
|
|
284
|
+
|
|
285
|
+
```ts
|
|
286
|
+
interface SearchResult {
|
|
287
|
+
[key: string]: unknown; // 你 select 的列
|
|
288
|
+
embeddingScore: number; // cosine similarity,范围 -1 ~ 1
|
|
289
|
+
rerankScore?: number; // 仅 enableRerank 时存在
|
|
290
|
+
}
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### EmbeddingSearch.query 配置项
|
|
294
|
+
|
|
295
|
+
| 配置项 | 类型 | 默认值 | 说明 |
|
|
296
|
+
|---|---|---|---|
|
|
297
|
+
| `tableName` | `string` | — | 检索表名(必填) |
|
|
298
|
+
| `enableRerank` | `boolean` | `true` | 是否启用 rerank |
|
|
299
|
+
| `embeddingTopK` | `number` | `10` | pgvector 检索返回条数(rerank 的候选集) |
|
|
300
|
+
| `finalTopN` | `number` | `5` | 最终结果条数 |
|
|
301
|
+
| `embeddingThreshold` | `number` | `0.8` | embedding 相似度阈值过滤(cosine similarity) |
|
|
302
|
+
| `rerankThreshold` | `number` | `0.1` | rerank 分数阈值过滤 |
|
|
303
|
+
| `embeddingColumn` | `string` | `'embedding'` | 向量列名 |
|
|
304
|
+
| `rerankKey` | `string` | `'content'` | 启用 rerank 时,用于重排序的文本列名 |
|
|
305
|
+
| `dimensions` | `number` | `1024` | embedding 输出维度(1 ~ 1024) |
|
|
306
|
+
| `select` | `string[]` | `undefined` | 指定返回哪些列,不传则 `SELECT *` |
|
|
307
|
+
| `filter` | `Record<string, unknown>` | — | 对表独立列做等值/范围过滤 |
|
|
308
|
+
|
|
309
|
+
### 过滤
|
|
310
|
+
|
|
311
|
+
支持对表的任意独立列做等值和范围过滤:
|
|
312
|
+
|
|
313
|
+
```ts
|
|
314
|
+
// 等值过滤
|
|
315
|
+
const results = await EmbeddingSearch.query('query', {
|
|
316
|
+
filter: { category: '衣服', shop: '旗舰店' },
|
|
317
|
+
}, pool);
|
|
318
|
+
|
|
319
|
+
// 混合:等值 + 数值范围
|
|
320
|
+
const ranked = await EmbeddingSearch.query('query', {
|
|
321
|
+
filter: {
|
|
322
|
+
category: '衣服',
|
|
323
|
+
price: { $gt: 100, $lt: 500 },
|
|
324
|
+
rating: { $gte: 4 },
|
|
325
|
+
},
|
|
326
|
+
}, pool);
|
|
327
|
+
|
|
328
|
+
// 数组:IN 查询
|
|
329
|
+
const multi = await EmbeddingSearch.query('query', {
|
|
330
|
+
filter: {
|
|
331
|
+
category: ['衣服', '裤子', '鞋子'],
|
|
332
|
+
},
|
|
333
|
+
}, pool);
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
### 生成 Embedding
|
|
337
|
+
|
|
338
|
+
如果你只需要把文本转成向量,直接用 `embed`:
|
|
339
|
+
|
|
340
|
+
```ts
|
|
341
|
+
import { embed } from '@keo-ai/axiom';
|
|
342
|
+
|
|
343
|
+
const vector = await embed('衣服质量怎么样');
|
|
344
|
+
// vector: number[],默认 1024 维
|
|
345
|
+
|
|
346
|
+
// 指定维度(1 ~ 1024)
|
|
347
|
+
const vector256 = await embed('衣服质量怎么样', 256);
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
| 参数 | 类型 | 默认值 | 说明 |
|
|
351
|
+
|---|---|---|---|
|
|
352
|
+
| `text` | `string` | — | 要转成向量的文本(必填) |
|
|
353
|
+
| `dimensions` | `number` | `1024` | 输出维度(1 ~ 1024) |
|
|
354
|
+
|
|
355
|
+
### 纯向量检索(已有向量)
|
|
356
|
+
|
|
357
|
+
`EmbeddingSearch.query` 会自动把文本转成向量并做 rerank。如果你**已经有了向量**(比如自己生成的 embedding),只想做最原始的 pgvector 近邻搜索,用 `EmbeddingSearch.vectorSearch`:
|
|
358
|
+
|
|
359
|
+
```ts
|
|
360
|
+
import { EmbeddingSearch } from '@keo-ai/axiom';
|
|
361
|
+
|
|
362
|
+
const results = await EmbeddingSearch.vectorSearch({
|
|
363
|
+
pool,
|
|
364
|
+
tableName: 'documents',
|
|
365
|
+
embeddingColumn: 'vec',
|
|
366
|
+
vector: queryVector, // 你自己生成的向量
|
|
367
|
+
topK: 10,
|
|
368
|
+
threshold: 0.7,
|
|
369
|
+
filter: { category: '衣服' },
|
|
370
|
+
});
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
> `EmbeddingSearch.vectorSearch` **不做 embedding,也不做 rerank**,只负责拿你给的向量去 pgvector 里搜近邻。
|
|
374
|
+
|
|
375
|
+
| 参数 | 类型 | 默认值 | 说明 |
|
|
376
|
+
|---|---|---|---|
|
|
377
|
+
| `pool` | `Pool` | — | pg 连接池(必填) |
|
|
378
|
+
| `tableName` | `string` | — | 检索表名(必填) |
|
|
379
|
+
| `vector` | `number[]` | — | 查询向量(必填) |
|
|
380
|
+
| `topK` | `number` | — | 返回条数(必填) |
|
|
381
|
+
| `threshold` | `number` | — | 相似度阈值 |
|
|
382
|
+
| `embeddingColumn` | `string` | `'embedding'` | 向量列名 |
|
|
383
|
+
| `filter` | `Record<string, unknown>` | — | 对表独立列做过滤 |
|
|
384
|
+
| `select` | `string[]` | `undefined` | 指定返回哪些列 |
|
|
385
|
+
|
|
386
|
+
### 依赖
|
|
387
|
+
|
|
388
|
+
EmbeddingSearch 模块内部调用百炼的 embedding 和 rerank 服务,无需额外配置:
|
|
389
|
+
|
|
390
|
+
- **Embedding**:`text-embedding-v4`,1024 维,兼容 OpenAI 协议
|
|
391
|
+
- **Rerank**:`qwen3-rerank`,百炼原生 API
|
|
392
|
+
|
|
393
|
+
只需确保 `BAILIAN_API_KEY` 已设置。
|
|
394
|
+
|
|
395
|
+
### 错误处理
|
|
396
|
+
|
|
397
|
+
同 Predict 模块:**直接抛异常**。常见错误:
|
|
398
|
+
|
|
399
|
+
- `BAILIAN_API_KEY environment variable is not set` — 未配置 API Key
|
|
400
|
+
- `[embedding] HTTP 4xx/5xx` — embedding 接口异常
|
|
401
|
+
- `[rerank] HTTP 4xx/5xx` — rerank 接口异常
|
|
402
|
+
- `Invalid table name` — 表名包含非法字符
|
|
403
|
+
|
|
404
|
+
---
|
|
405
|
+
|
|
173
406
|
## 设计理念
|
|
174
407
|
|
|
175
408
|
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function embed(text: string, dimensions?: number): Promise<number[]>;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.embed = embed;
|
|
13
|
+
const EMBEDDING_URL = 'https://dashscope.aliyuncs.com/compatible-mode/v1/embeddings';
|
|
14
|
+
const EMBEDDING_MODEL = 'text-embedding-v4';
|
|
15
|
+
function embed(text_1) {
|
|
16
|
+
return __awaiter(this, arguments, void 0, function* (text, dimensions = 1024) {
|
|
17
|
+
var _a;
|
|
18
|
+
const apiKey = process.env.BAILIAN_API_KEY;
|
|
19
|
+
if (!apiKey) {
|
|
20
|
+
throw new Error('BAILIAN_API_KEY environment variable is not set');
|
|
21
|
+
}
|
|
22
|
+
const body = {
|
|
23
|
+
model: EMBEDDING_MODEL,
|
|
24
|
+
input: text,
|
|
25
|
+
dimensions,
|
|
26
|
+
};
|
|
27
|
+
let response;
|
|
28
|
+
try {
|
|
29
|
+
response = yield fetch(EMBEDDING_URL, {
|
|
30
|
+
method: 'POST',
|
|
31
|
+
headers: {
|
|
32
|
+
'Content-Type': 'application/json',
|
|
33
|
+
Authorization: `Bearer ${apiKey}`,
|
|
34
|
+
},
|
|
35
|
+
body: JSON.stringify(body),
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
catch (cause) {
|
|
39
|
+
throw new Error(`[embedding] ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
40
|
+
}
|
|
41
|
+
if (!response.ok) {
|
|
42
|
+
const text = yield response.text();
|
|
43
|
+
throw new Error(`[embedding] HTTP ${response.status}: ${text}`);
|
|
44
|
+
}
|
|
45
|
+
const data = (yield response.json());
|
|
46
|
+
const vector = (_a = data.data[0]) === null || _a === void 0 ? void 0 : _a.embedding;
|
|
47
|
+
if (!vector) {
|
|
48
|
+
throw new Error('[embedding] No embedding in response');
|
|
49
|
+
}
|
|
50
|
+
return vector;
|
|
51
|
+
});
|
|
52
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Pool } from 'pg';
|
|
2
|
+
import type { EmbeddingSearchConfig, SearchResult } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* 向量检索静态入口。
|
|
5
|
+
*
|
|
6
|
+
* 封装 embedding → pgvector 检索 → [可选 rerank] 的完整链路。
|
|
7
|
+
* PG 连接由外部传入,Axiom 不管理连接池生命周期。
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* const results = await EmbeddingSearch.query('query文本', {
|
|
12
|
+
* enableRerank: true,
|
|
13
|
+
* embeddingTopK: 20,
|
|
14
|
+
* finalTopN: 5,
|
|
15
|
+
* embeddingThreshold: 0.7,
|
|
16
|
+
* rerankThreshold: 0.5,
|
|
17
|
+
* }, pgPool);
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export declare class EmbeddingSearch {
|
|
21
|
+
static query(query: string, config: EmbeddingSearchConfig, pool: Pool): Promise<SearchResult[]>;
|
|
22
|
+
static vectorSearch(options: Omit<import('./pgvector').PgVectorSearchOptions, 'pool'> & {
|
|
23
|
+
pool: Pool;
|
|
24
|
+
}): Promise<SearchResult[]>;
|
|
25
|
+
}
|
|
26
|
+
export type { EmbeddingSearchConfig, SearchResult } from './types';
|
|
27
|
+
export { embed } from './embed';
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
12
|
+
var t = {};
|
|
13
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
14
|
+
t[p] = s[p];
|
|
15
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
16
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
17
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
18
|
+
t[p[i]] = s[p[i]];
|
|
19
|
+
}
|
|
20
|
+
return t;
|
|
21
|
+
};
|
|
22
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
|
+
exports.embed = exports.EmbeddingSearch = void 0;
|
|
24
|
+
const pgvector_1 = require("./pgvector");
|
|
25
|
+
const embed_1 = require("./embed");
|
|
26
|
+
const RERANK_URL = 'https://dashscope.aliyuncs.com/api/v1/services/rerank/text-rerank/text-rerank';
|
|
27
|
+
const RERANK_MODEL = 'qwen3-rerank';
|
|
28
|
+
function rerank(query, documents, topN, apiKey) {
|
|
29
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
30
|
+
const body = {
|
|
31
|
+
model: RERANK_MODEL,
|
|
32
|
+
input: {
|
|
33
|
+
query,
|
|
34
|
+
documents: documents.map((d) => d.content),
|
|
35
|
+
},
|
|
36
|
+
parameters: {
|
|
37
|
+
return_documents: true,
|
|
38
|
+
top_n: topN,
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
let response;
|
|
42
|
+
try {
|
|
43
|
+
response = yield fetch(RERANK_URL, {
|
|
44
|
+
method: 'POST',
|
|
45
|
+
headers: {
|
|
46
|
+
'Content-Type': 'application/json',
|
|
47
|
+
Authorization: `Bearer ${apiKey}`,
|
|
48
|
+
},
|
|
49
|
+
body: JSON.stringify(body),
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
catch (cause) {
|
|
53
|
+
throw new Error(`[rerank] ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
54
|
+
}
|
|
55
|
+
if (!response.ok) {
|
|
56
|
+
const text = yield response.text();
|
|
57
|
+
throw new Error(`[rerank] HTTP ${response.status}: ${text}`);
|
|
58
|
+
}
|
|
59
|
+
const data = (yield response.json());
|
|
60
|
+
return data.output.results.map((r) => {
|
|
61
|
+
var _a, _b, _c, _d;
|
|
62
|
+
const doc = documents[r.index];
|
|
63
|
+
return {
|
|
64
|
+
index: r.index,
|
|
65
|
+
id: (_a = doc === null || doc === void 0 ? void 0 : doc.id) !== null && _a !== void 0 ? _a : r.index,
|
|
66
|
+
content: (_d = (_c = (_b = r.document) === null || _b === void 0 ? void 0 : _b.text) !== null && _c !== void 0 ? _c : doc === null || doc === void 0 ? void 0 : doc.content) !== null && _d !== void 0 ? _d : '',
|
|
67
|
+
score: r.relevance_score,
|
|
68
|
+
};
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* 向量检索静态入口。
|
|
74
|
+
*
|
|
75
|
+
* 封装 embedding → pgvector 检索 → [可选 rerank] 的完整链路。
|
|
76
|
+
* PG 连接由外部传入,Axiom 不管理连接池生命周期。
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* ```ts
|
|
80
|
+
* const results = await EmbeddingSearch.query('query文本', {
|
|
81
|
+
* enableRerank: true,
|
|
82
|
+
* embeddingTopK: 20,
|
|
83
|
+
* finalTopN: 5,
|
|
84
|
+
* embeddingThreshold: 0.7,
|
|
85
|
+
* rerankThreshold: 0.5,
|
|
86
|
+
* }, pgPool);
|
|
87
|
+
* ```
|
|
88
|
+
*/
|
|
89
|
+
class EmbeddingSearch {
|
|
90
|
+
static query(query, config, pool) {
|
|
91
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
92
|
+
const { enableRerank = true, embeddingTopK = 10, finalTopN = 5, embeddingThreshold = 0.8, rerankThreshold = 0.1, tableName, embeddingColumn, rerankKey = 'content', dimensions, filter, select, } = config;
|
|
93
|
+
const idColumn = 'id';
|
|
94
|
+
const actualTopK = enableRerank
|
|
95
|
+
? Math.max(embeddingTopK, finalTopN)
|
|
96
|
+
: finalTopN;
|
|
97
|
+
const apiKey = process.env.BAILIAN_API_KEY;
|
|
98
|
+
if (!apiKey) {
|
|
99
|
+
throw new Error('BAILIAN_API_KEY environment variable is not set');
|
|
100
|
+
}
|
|
101
|
+
const queryVector = yield (0, embed_1.embed)(query, dimensions);
|
|
102
|
+
// 启用 rerank 时,确保能拿到 rerankKey 对应列的内容
|
|
103
|
+
let actualSelect = select;
|
|
104
|
+
if (enableRerank && actualSelect && !actualSelect.includes(rerankKey)) {
|
|
105
|
+
actualSelect = [...actualSelect, rerankKey];
|
|
106
|
+
}
|
|
107
|
+
const searchResults = yield (0, pgvector_1.vectorSearch)({
|
|
108
|
+
pool,
|
|
109
|
+
tableName,
|
|
110
|
+
vector: queryVector,
|
|
111
|
+
topK: actualTopK,
|
|
112
|
+
threshold: embeddingThreshold,
|
|
113
|
+
embeddingColumn,
|
|
114
|
+
filter,
|
|
115
|
+
select: actualSelect,
|
|
116
|
+
});
|
|
117
|
+
if (searchResults.length === 0) {
|
|
118
|
+
return [];
|
|
119
|
+
}
|
|
120
|
+
if (enableRerank) {
|
|
121
|
+
const missingRerank = searchResults.find((r) => r[rerankKey] === undefined);
|
|
122
|
+
if (missingRerank) {
|
|
123
|
+
throw new Error(`Rerank requires column "${rerankKey}" but it was not found. ` +
|
|
124
|
+
`Either create this column in your table, or set rerankKey to the correct column name.`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (!enableRerank) {
|
|
128
|
+
return searchResults.slice(0, finalTopN);
|
|
129
|
+
}
|
|
130
|
+
const rerankResults = yield rerank(query, searchResults.map((r) => ({
|
|
131
|
+
id: r[idColumn],
|
|
132
|
+
content: r[rerankKey],
|
|
133
|
+
})), finalTopN, apiKey);
|
|
134
|
+
let reranked = [];
|
|
135
|
+
for (const rr of rerankResults) {
|
|
136
|
+
if (rerankThreshold !== undefined && rr.score < rerankThreshold) {
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
const original = searchResults[rr.index];
|
|
140
|
+
if (original) {
|
|
141
|
+
reranked.push(Object.assign(Object.assign({}, original), { rerankScore: rr.score }));
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// 如果用户没选 rerankKey 对应列,从返回结果中剔除
|
|
145
|
+
if (select && !select.includes(rerankKey)) {
|
|
146
|
+
reranked = reranked.map((r) => {
|
|
147
|
+
const _a = r, _b = rerankKey, _ = _a[_b], rest = __rest(_a, [typeof _b === "symbol" ? _b : _b + ""]);
|
|
148
|
+
return rest;
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
return reranked;
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
static vectorSearch(options) {
|
|
155
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
156
|
+
return (0, pgvector_1.vectorSearch)(options);
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
exports.EmbeddingSearch = EmbeddingSearch;
|
|
161
|
+
var embed_2 = require("./embed");
|
|
162
|
+
Object.defineProperty(exports, "embed", { enumerable: true, get: function () { return embed_2.embed; } });
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Pool } from 'pg';
|
|
2
|
+
import type { SearchResult } from './types';
|
|
3
|
+
export interface PgVectorSearchOptions {
|
|
4
|
+
readonly pool: Pool;
|
|
5
|
+
readonly tableName: string;
|
|
6
|
+
readonly vector: number[];
|
|
7
|
+
readonly topK: number;
|
|
8
|
+
readonly threshold?: number;
|
|
9
|
+
readonly embeddingColumn?: string;
|
|
10
|
+
readonly filter?: Record<string, unknown>;
|
|
11
|
+
readonly select?: string[];
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* 使用 pgvector 执行 cosine similarity 检索。
|
|
15
|
+
*
|
|
16
|
+
* - `embedding <=> query` 为 cosine distance(范围 0~2,越小越相似)
|
|
17
|
+
* - `1 - cosine_distance` 为 cosine similarity(范围 -1~1,越大越相似)
|
|
18
|
+
*/
|
|
19
|
+
export declare function vectorSearch(options: PgVectorSearchOptions): Promise<SearchResult[]>;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
12
|
+
var t = {};
|
|
13
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
14
|
+
t[p] = s[p];
|
|
15
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
16
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
17
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
18
|
+
t[p[i]] = s[p[i]];
|
|
19
|
+
}
|
|
20
|
+
return t;
|
|
21
|
+
};
|
|
22
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
|
+
exports.vectorSearch = vectorSearch;
|
|
24
|
+
function validateIdentifier(name) {
|
|
25
|
+
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)) {
|
|
26
|
+
throw new Error(`Invalid identifier: ${name}`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* 使用 pgvector 执行 cosine similarity 检索。
|
|
31
|
+
*
|
|
32
|
+
* - `embedding <=> query` 为 cosine distance(范围 0~2,越小越相似)
|
|
33
|
+
* - `1 - cosine_distance` 为 cosine similarity(范围 -1~1,越大越相似)
|
|
34
|
+
*/
|
|
35
|
+
function vectorSearch(options) {
|
|
36
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
37
|
+
const { pool, tableName, vector, topK, threshold, embeddingColumn = 'embedding', filter, select } = options;
|
|
38
|
+
validateIdentifier(tableName);
|
|
39
|
+
validateIdentifier(embeddingColumn);
|
|
40
|
+
const vectorStr = JSON.stringify(vector);
|
|
41
|
+
const params = [vectorStr, topK];
|
|
42
|
+
const conditions = [];
|
|
43
|
+
let paramIndex = 3;
|
|
44
|
+
if (threshold !== undefined) {
|
|
45
|
+
conditions.push(`1 - ("${embeddingColumn}" <=> $1::vector) >= $${paramIndex}`);
|
|
46
|
+
params.push(threshold);
|
|
47
|
+
paramIndex++;
|
|
48
|
+
}
|
|
49
|
+
if (filter && Object.keys(filter).length > 0) {
|
|
50
|
+
for (const [key, value] of Object.entries(filter)) {
|
|
51
|
+
validateIdentifier(key);
|
|
52
|
+
if (Array.isArray(value)) {
|
|
53
|
+
const placeholders = value.map((_, i) => `$${paramIndex + i}`).join(', ');
|
|
54
|
+
conditions.push(`"${key}" IN (${placeholders})`);
|
|
55
|
+
params.push(...value);
|
|
56
|
+
paramIndex += value.length;
|
|
57
|
+
}
|
|
58
|
+
else if (value && typeof value === 'object') {
|
|
59
|
+
const ops = value;
|
|
60
|
+
for (const [op, opValue] of Object.entries(ops)) {
|
|
61
|
+
switch (op) {
|
|
62
|
+
case '$eq':
|
|
63
|
+
conditions.push(`"${key}" = $${paramIndex}`);
|
|
64
|
+
params.push(opValue);
|
|
65
|
+
paramIndex++;
|
|
66
|
+
break;
|
|
67
|
+
case '$gt':
|
|
68
|
+
conditions.push(`"${key}" > $${paramIndex}`);
|
|
69
|
+
params.push(opValue);
|
|
70
|
+
paramIndex++;
|
|
71
|
+
break;
|
|
72
|
+
case '$lt':
|
|
73
|
+
conditions.push(`"${key}" < $${paramIndex}`);
|
|
74
|
+
params.push(opValue);
|
|
75
|
+
paramIndex++;
|
|
76
|
+
break;
|
|
77
|
+
case '$gte':
|
|
78
|
+
conditions.push(`"${key}" >= $${paramIndex}`);
|
|
79
|
+
params.push(opValue);
|
|
80
|
+
paramIndex++;
|
|
81
|
+
break;
|
|
82
|
+
case '$lte':
|
|
83
|
+
conditions.push(`"${key}" <= $${paramIndex}`);
|
|
84
|
+
params.push(opValue);
|
|
85
|
+
paramIndex++;
|
|
86
|
+
break;
|
|
87
|
+
default:
|
|
88
|
+
throw new Error(`Unsupported filter operator: ${op}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
conditions.push(`"${key}" = $${paramIndex}`);
|
|
94
|
+
params.push(value);
|
|
95
|
+
paramIndex++;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
|
|
100
|
+
const selectClause = select && select.length > 0
|
|
101
|
+
? select.map((c) => { validateIdentifier(c); return `"${c}"`; }).join(', ')
|
|
102
|
+
: '*';
|
|
103
|
+
const sql = `
|
|
104
|
+
SELECT ${selectClause}, 1 - ("${embeddingColumn}" <=> $1::vector) as score
|
|
105
|
+
FROM "${tableName}"
|
|
106
|
+
${whereClause}
|
|
107
|
+
ORDER BY "${embeddingColumn}" <=> $1::vector
|
|
108
|
+
LIMIT $2
|
|
109
|
+
`;
|
|
110
|
+
const result = yield pool.query(sql, params);
|
|
111
|
+
return result.rows.map((row) => {
|
|
112
|
+
const { score } = row, rest = __rest(row, ["score"]);
|
|
113
|
+
return Object.assign(Object.assign({}, rest), { embeddingScore: score });
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface ComparisonOp {
|
|
2
|
+
readonly $eq?: unknown;
|
|
3
|
+
readonly $gt?: number;
|
|
4
|
+
readonly $lt?: number;
|
|
5
|
+
readonly $gte?: number;
|
|
6
|
+
readonly $lte?: number;
|
|
7
|
+
}
|
|
8
|
+
export type FilterValue = string | number | boolean | null | unknown[] | ComparisonOp;
|
|
9
|
+
export interface EmbeddingSearchConfig {
|
|
10
|
+
readonly enableRerank?: boolean;
|
|
11
|
+
readonly embeddingTopK?: number;
|
|
12
|
+
readonly finalTopN?: number;
|
|
13
|
+
readonly embeddingThreshold?: number;
|
|
14
|
+
readonly rerankThreshold?: number;
|
|
15
|
+
readonly tableName: string;
|
|
16
|
+
readonly embeddingColumn?: string;
|
|
17
|
+
readonly rerankKey?: string;
|
|
18
|
+
readonly dimensions?: number;
|
|
19
|
+
readonly filter?: Record<string, FilterValue>;
|
|
20
|
+
readonly select?: string[];
|
|
21
|
+
}
|
|
22
|
+
export interface SearchResult {
|
|
23
|
+
readonly [key: string]: unknown;
|
|
24
|
+
readonly embeddingScore: number;
|
|
25
|
+
readonly rerankScore?: number;
|
|
26
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Axiom — LLM 调用底座库。
|
|
3
|
+
*
|
|
4
|
+
* 使用前需确保环境变量已加载(推荐 dotenv):
|
|
5
|
+
* ```ts
|
|
6
|
+
* import 'dotenv/config';
|
|
7
|
+
* import { LLM } from '@keo-ai/axiom';
|
|
8
|
+
*
|
|
9
|
+
* const res = await LLM.predict({ model: 'qwen3.7-max', prompt: '你好' });
|
|
10
|
+
* ```
|
|
11
|
+
*
|
|
12
|
+
* 必需环境变量:
|
|
13
|
+
* - `BAILIAN_API_KEY` — 百炼 API Key
|
|
14
|
+
*
|
|
15
|
+
* 可选环境变量:
|
|
16
|
+
* - `BAILIAN_BASE_URL` — 自定义百炼 API 地址
|
|
17
|
+
* - `BAILIAN_DEFAULT_MODEL` — 默认模型
|
|
18
|
+
*
|
|
19
|
+
* @packageDocumentation
|
|
20
|
+
*/
|
|
21
|
+
/** LLM 静态入口类,封装 Provider 连接、故障转移和返回解析 */
|
|
1
22
|
export { LLM } from './predict';
|
|
2
23
|
export type { LLMRequest, LLMResponse, Message, ProviderConfig, StreamChunk, } from './predict';
|
|
3
24
|
export type { LLMProvider, PredictorOptions } from './predict';
|
|
4
|
-
|
|
25
|
+
/** 预测器核心类,负责模型路由与故障转移 */
|
|
26
|
+
export { Predictor } from './predict';
|
|
27
|
+
/** 百炼 Provider 实现 */
|
|
28
|
+
export { BailianProvider } from './predict';
|
|
29
|
+
/** 模型注册表,定义模型与 Provider 的映射关系 */
|
|
30
|
+
export { MODEL_REGISTRY } from './predict';
|
|
5
31
|
export type { Model, PredictConfig, PredictWithMessagesConfig } from './predict';
|
|
32
|
+
/** 向量检索(Embedding + pgvector + Rerank) */
|
|
33
|
+
export { EmbeddingSearch, embed } from './embedding_search';
|
|
34
|
+
export type { EmbeddingSearchConfig, SearchResult } from './embedding_search';
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,39 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Axiom — LLM 调用底座库。
|
|
4
|
+
*
|
|
5
|
+
* 使用前需确保环境变量已加载(推荐 dotenv):
|
|
6
|
+
* ```ts
|
|
7
|
+
* import 'dotenv/config';
|
|
8
|
+
* import { LLM } from '@keo-ai/axiom';
|
|
9
|
+
*
|
|
10
|
+
* const res = await LLM.predict({ model: 'qwen3.7-max', prompt: '你好' });
|
|
11
|
+
* ```
|
|
12
|
+
*
|
|
13
|
+
* 必需环境变量:
|
|
14
|
+
* - `BAILIAN_API_KEY` — 百炼 API Key
|
|
15
|
+
*
|
|
16
|
+
* 可选环境变量:
|
|
17
|
+
* - `BAILIAN_BASE_URL` — 自定义百炼 API 地址
|
|
18
|
+
* - `BAILIAN_DEFAULT_MODEL` — 默认模型
|
|
19
|
+
*
|
|
20
|
+
* @packageDocumentation
|
|
21
|
+
*/
|
|
2
22
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.MODEL_REGISTRY = exports.BailianProvider = exports.Predictor = exports.LLM = void 0;
|
|
23
|
+
exports.embed = exports.EmbeddingSearch = exports.MODEL_REGISTRY = exports.BailianProvider = exports.Predictor = exports.LLM = void 0;
|
|
24
|
+
/** LLM 静态入口类,封装 Provider 连接、故障转移和返回解析 */
|
|
4
25
|
var predict_1 = require("./predict");
|
|
5
26
|
Object.defineProperty(exports, "LLM", { enumerable: true, get: function () { return predict_1.LLM; } });
|
|
27
|
+
/** 预测器核心类,负责模型路由与故障转移 */
|
|
6
28
|
var predict_2 = require("./predict");
|
|
7
29
|
Object.defineProperty(exports, "Predictor", { enumerable: true, get: function () { return predict_2.Predictor; } });
|
|
8
|
-
|
|
9
|
-
|
|
30
|
+
/** 百炼 Provider 实现 */
|
|
31
|
+
var predict_3 = require("./predict");
|
|
32
|
+
Object.defineProperty(exports, "BailianProvider", { enumerable: true, get: function () { return predict_3.BailianProvider; } });
|
|
33
|
+
/** 模型注册表,定义模型与 Provider 的映射关系 */
|
|
34
|
+
var predict_4 = require("./predict");
|
|
35
|
+
Object.defineProperty(exports, "MODEL_REGISTRY", { enumerable: true, get: function () { return predict_4.MODEL_REGISTRY; } });
|
|
36
|
+
/** 向量检索(Embedding + pgvector + Rerank) */
|
|
37
|
+
var embedding_search_1 = require("./embedding_search");
|
|
38
|
+
Object.defineProperty(exports, "EmbeddingSearch", { enumerable: true, get: function () { return embedding_search_1.EmbeddingSearch; } });
|
|
39
|
+
Object.defineProperty(exports, "embed", { enumerable: true, get: function () { return embedding_search_1.embed; } });
|
package/dist/predict/config.d.ts
CHANGED
|
@@ -8,8 +8,6 @@ export interface PredictConfig {
|
|
|
8
8
|
readonly model: Model;
|
|
9
9
|
/** 用户输入的 prompt */
|
|
10
10
|
readonly prompt: string;
|
|
11
|
-
/** 系统提示(可选) */
|
|
12
|
-
readonly systemPrompt?: string;
|
|
13
11
|
readonly temperature?: number;
|
|
14
12
|
readonly maxTokens?: number;
|
|
15
13
|
readonly topP?: number;
|
|
@@ -21,7 +19,6 @@ export interface PredictConfig {
|
|
|
21
19
|
*/
|
|
22
20
|
export interface PredictWithMessagesConfig {
|
|
23
21
|
readonly model: Model;
|
|
24
|
-
readonly systemPrompt?: string;
|
|
25
22
|
readonly temperature?: number;
|
|
26
23
|
readonly maxTokens?: number;
|
|
27
24
|
readonly topP?: number;
|
|
@@ -29,9 +26,9 @@ export interface PredictWithMessagesConfig {
|
|
|
29
26
|
readonly responseFormat?: 'text' | 'json';
|
|
30
27
|
}
|
|
31
28
|
/**
|
|
32
|
-
*
|
|
33
|
-
* @param
|
|
29
|
+
* 构建消息列表。
|
|
30
|
+
* @param _config - 预测配置(保留以兼容接口,暂不消费)
|
|
34
31
|
* @param userMessages - 用户提供的消息列表
|
|
35
|
-
* @returns
|
|
32
|
+
* @returns 完整消息列表
|
|
36
33
|
*/
|
|
37
|
-
export declare function buildMessages(
|
|
34
|
+
export declare function buildMessages(_config: PredictConfig | PredictWithMessagesConfig, userMessages: ReadonlyArray<Message>): Message[];
|
package/dist/predict/config.js
CHANGED
|
@@ -2,16 +2,11 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.buildMessages = buildMessages;
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
6
|
-
* @param
|
|
5
|
+
* 构建消息列表。
|
|
6
|
+
* @param _config - 预测配置(保留以兼容接口,暂不消费)
|
|
7
7
|
* @param userMessages - 用户提供的消息列表
|
|
8
|
-
* @returns
|
|
8
|
+
* @returns 完整消息列表
|
|
9
9
|
*/
|
|
10
|
-
function buildMessages(
|
|
11
|
-
|
|
12
|
-
if (config.systemPrompt) {
|
|
13
|
-
messages.push({ role: 'system', content: config.systemPrompt });
|
|
14
|
-
}
|
|
15
|
-
messages.push(...userMessages);
|
|
16
|
-
return messages;
|
|
10
|
+
function buildMessages(_config, userMessages) {
|
|
11
|
+
return [...userMessages];
|
|
17
12
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Message,
|
|
1
|
+
import type { Message, StreamChunk } from './types';
|
|
2
2
|
import type { PredictConfig, PredictWithMessagesConfig } from './config';
|
|
3
3
|
/**
|
|
4
4
|
* LLM 静态类。封装 Provider 连接、请求组装、故障转移和返回解析。
|
|
@@ -9,7 +9,7 @@ import type { PredictConfig, PredictWithMessagesConfig } from './config';
|
|
|
9
9
|
* import { LLM } from 'axiom';
|
|
10
10
|
*
|
|
11
11
|
* const res = await LLM.predict({ model: 'qwen-max', prompt: '你好' });
|
|
12
|
-
* console.log(res
|
|
12
|
+
* console.log(res);
|
|
13
13
|
* ```
|
|
14
14
|
*/
|
|
15
15
|
export declare class LLM {
|
|
@@ -20,41 +20,33 @@ export declare class LLM {
|
|
|
20
20
|
* 内部自动构造单条 user message,支持可选的 system prompt。
|
|
21
21
|
*
|
|
22
22
|
* 根据 `responseFormat` 返回不同类型:
|
|
23
|
-
* - `'text'` → `string`
|
|
23
|
+
* - 未设置 / `'text'` → `string`
|
|
24
24
|
* - `'json'` → 解析后的对象(`any`)
|
|
25
|
-
* - 未设置 → `LLMResponse`
|
|
26
25
|
*
|
|
27
26
|
* @param config - 预测配置(模型、prompt、温度等)
|
|
28
27
|
* @returns 模型生成的完整响应
|
|
29
28
|
* @throws Error - Provider 未配置、模型不存在、或所有 Provider 均失败时抛出
|
|
30
29
|
*/
|
|
31
|
-
static predict(config: PredictConfig & {
|
|
32
|
-
responseFormat: 'text';
|
|
33
|
-
}): Promise<string>;
|
|
34
30
|
static predict(config: PredictConfig & {
|
|
35
31
|
responseFormat: 'json';
|
|
36
32
|
}): Promise<any>;
|
|
37
|
-
static predict(config: PredictConfig): Promise<
|
|
33
|
+
static predict(config: PredictConfig): Promise<string>;
|
|
38
34
|
/**
|
|
39
35
|
* 使用自定义消息列表调用 LLM。适用于多轮对话等需要精细控制 message 结构的场景。
|
|
40
36
|
*
|
|
41
37
|
* 根据 `responseFormat` 返回不同类型:
|
|
42
|
-
* - `'text'` → `string`
|
|
38
|
+
* - 未设置 / `'text'` → `string`
|
|
43
39
|
* - `'json'` → 解析后的对象(可用泛型指定类型)
|
|
44
|
-
* - 未设置 → `LLMResponse`
|
|
45
40
|
*
|
|
46
41
|
* @param messages - 消息列表(user/assistant 角色)
|
|
47
42
|
* @param config - 预测配置(不含 prompt,因为由 messages 提供)
|
|
48
43
|
* @returns 模型生成的完整响应
|
|
49
44
|
* @throws Error - Provider 未配置、模型不存在、或所有 Provider 均失败时抛出
|
|
50
45
|
*/
|
|
51
|
-
static predictWithMessages(messages: ReadonlyArray<Message>, config: PredictWithMessagesConfig & {
|
|
52
|
-
responseFormat: 'text';
|
|
53
|
-
}): Promise<string>;
|
|
54
46
|
static predictWithMessages(messages: ReadonlyArray<Message>, config: PredictWithMessagesConfig & {
|
|
55
47
|
responseFormat: 'json';
|
|
56
48
|
}): Promise<any>;
|
|
57
|
-
static predictWithMessages(messages: ReadonlyArray<Message>, config: PredictWithMessagesConfig): Promise<
|
|
49
|
+
static predictWithMessages(messages: ReadonlyArray<Message>, config: PredictWithMessagesConfig): Promise<string>;
|
|
58
50
|
/**
|
|
59
51
|
* 流式调用 LLM,逐块返回模型输出。
|
|
60
52
|
*
|
package/dist/predict/predict.js
CHANGED
|
@@ -69,16 +69,14 @@ function toLLMRequest(config, messages) {
|
|
|
69
69
|
}
|
|
70
70
|
function unwrapResponse(response, responseFormat) {
|
|
71
71
|
var _a;
|
|
72
|
-
if (responseFormat === 'text') {
|
|
73
|
-
return (_a = response.content) !== null && _a !== void 0 ? _a : '';
|
|
74
|
-
}
|
|
75
72
|
if (responseFormat === 'json') {
|
|
76
73
|
if (!response.content) {
|
|
77
74
|
throw new Error('Empty response content when responseFormat is json');
|
|
78
75
|
}
|
|
79
76
|
return JSON.parse(response.content);
|
|
80
77
|
}
|
|
81
|
-
|
|
78
|
+
// 默认按 text 返回(包括未设置 responseFormat 的情况)
|
|
79
|
+
return (_a = response.content) !== null && _a !== void 0 ? _a : '';
|
|
82
80
|
}
|
|
83
81
|
/**
|
|
84
82
|
* LLM 静态类。封装 Provider 连接、请求组装、故障转移和返回解析。
|
|
@@ -89,7 +87,7 @@ function unwrapResponse(response, responseFormat) {
|
|
|
89
87
|
* import { LLM } from 'axiom';
|
|
90
88
|
*
|
|
91
89
|
* const res = await LLM.predict({ model: 'qwen-max', prompt: '你好' });
|
|
92
|
-
* console.log(res
|
|
90
|
+
* console.log(res);
|
|
93
91
|
* ```
|
|
94
92
|
*/
|
|
95
93
|
class LLM {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@keo-ai/axiom",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "基于 LLM 的预测与推理库,支持多 Provider 切换",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"scripts": {
|
|
12
12
|
"build": "tsc",
|
|
13
|
-
"test": "vitest run"
|
|
13
|
+
"test": "vitest run --exclude '**/*.integration.test.ts'"
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
16
|
"dotenv": "^17.4.2"
|