@nickyzj2023/ai 1.5.0 → 1.6.0
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 +80 -64
- package/dist/cli.mjs +3 -1
- package/dist/helper-juLVGxlQ.mjs +65 -0
- package/dist/index.d.mts +20 -197
- package/dist/index.mjs +3 -2
- package/dist/mcp-DcntQg-a.mjs +58 -0
- package/dist/{src-C1Zeveeb.mjs → src-CKKPKPMG.mjs} +54 -160
- package/dist/tools/mcp.d.mts +55 -0
- package/dist/tools/mcp.mjs +2 -0
- package/dist/types--dXFoUow.d.mts +144 -0
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -1,64 +1,80 @@
|
|
|
1
|
-
# ai
|
|
2
|
-
|
|
3
|
-
男生自用Coding Agent,参考了[nanopi教学](https://pi-from-scratch.vercel.app/)
|
|
4
|
-
|
|
5
|
-
不断学习+开发中,当前项目仍属于比nano还简陋的“bare-pi”,but one day...
|
|
6
|
-
|
|
7
|
-

|
|
8
|
-
|
|
9
|
-
## 安装
|
|
10
|
-
|
|
11
|
-
```bash
|
|
12
|
-
# npm
|
|
13
|
-
npm install @nickyzj2023/ai
|
|
14
|
-
|
|
15
|
-
# yarn
|
|
16
|
-
yarn add @nickyzj2023/ai
|
|
17
|
-
|
|
18
|
-
# pnpm
|
|
19
|
-
pnpm add @nickyzj2023/ai
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
## 使用方式
|
|
23
|
-
|
|
24
|
-
### 在项目里使用
|
|
25
|
-
|
|
26
|
-
```typescript
|
|
27
|
-
import { defineModel, runAgent, getWeather } from "@nickyzj2023/ai";
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
1
|
+
# ai
|
|
2
|
+
|
|
3
|
+
男生自用Coding Agent,参考了[nanopi教学](https://pi-from-scratch.vercel.app/)
|
|
4
|
+
|
|
5
|
+
不断学习+开发中,当前项目仍属于比nano还简陋的“bare-pi”,but one day...
|
|
6
|
+
|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
## 安装
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# npm
|
|
13
|
+
npm install @nickyzj2023/ai
|
|
14
|
+
|
|
15
|
+
# yarn
|
|
16
|
+
yarn add @nickyzj2023/ai
|
|
17
|
+
|
|
18
|
+
# pnpm
|
|
19
|
+
pnpm add @nickyzj2023/ai
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## 使用方式
|
|
23
|
+
|
|
24
|
+
### 在项目里使用
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
import { defineModel, runAgent, getWeather } from "@nickyzj2023/ai";
|
|
28
|
+
// MCP体积较大(200kb),有需要时单独引入
|
|
29
|
+
import { loadMCPTools } from "@nickyzj2023/ai/mcp";
|
|
30
|
+
|
|
31
|
+
const model = defineModel({
|
|
32
|
+
baseUrl: "https://api.deepseek.com/v1",
|
|
33
|
+
apiKey: process.env.APIKEY,
|
|
34
|
+
model: "deepseek-v4-flash",
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const tools = [
|
|
38
|
+
// 内置工具
|
|
39
|
+
getWeather,
|
|
40
|
+
getTime,
|
|
41
|
+
// MCP工具
|
|
42
|
+
..(await loadMCPTools({
|
|
43
|
+
exa: {
|
|
44
|
+
type: "streamable_http",
|
|
45
|
+
url: "https://mcp.exa.ai/mcp",
|
|
46
|
+
headers: {
|
|
47
|
+
"x-api-key": "xxxxx",
|
|
48
|
+
},
|
|
49
|
+
}
|
|
50
|
+
})),
|
|
51
|
+
// 自定义工具
|
|
52
|
+
defineTool("skipReply", "跳过本轮回复", {reason: {type: "string", description: "不回复的理由"}}, () => {
|
|
53
|
+
const error = new Error(`模型保持沉默,理由:${reason}`);
|
|
54
|
+
error.name = "skipReply";
|
|
55
|
+
throw error;
|
|
56
|
+
}),
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
const messages = [{ role: "user", content: "随机一个负无穷到正无穷的整数" }];
|
|
60
|
+
|
|
61
|
+
for await (const e of runAgent(model, messages, tools)) {
|
|
62
|
+
console.log(e);
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### 在终端里使用
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
# 首次使用:交互式配置BASE_URL / APIKEY / MODEL / ...
|
|
70
|
+
ai setup
|
|
71
|
+
|
|
72
|
+
# 启动对话
|
|
73
|
+
ai
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
配置保存在 `~/.@nickyzj2023/ai/config.json`,任意目录下执行`ai`都能读取到
|
|
77
|
+
|
|
78
|
+
## License
|
|
79
|
+
|
|
80
|
+
ISC
|
package/dist/cli.mjs
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { i as runAgent, n as get_weather_default, r as get_time_default } from "./src-CKKPKPMG.mjs";
|
|
2
|
+
import { t as defineModel } from "./helper-juLVGxlQ.mjs";
|
|
3
|
+
import { n as loadMCPTools } from "./mcp-DcntQg-a.mjs";
|
|
2
4
|
import { compactStr, extractErrorMessage, isObject } from "@nickyzj2023/utils";
|
|
3
5
|
import readline from "node:readline";
|
|
4
6
|
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
//#region src/utils/helper.ts
|
|
2
|
+
/**
|
|
3
|
+
* 辅助定义一个POST /chat/completions支持的model参数
|
|
4
|
+
* @remarks 只有baseUrl字段是必须的
|
|
5
|
+
*/
|
|
6
|
+
const defineModel = (config) => ({
|
|
7
|
+
modalities: ["text"],
|
|
8
|
+
context: 131072,
|
|
9
|
+
...config
|
|
10
|
+
});
|
|
11
|
+
/**
|
|
12
|
+
* 辅助定义一个POST /chat/completions支持的tool对象
|
|
13
|
+
* @param execute 实际执行工具的函数
|
|
14
|
+
*/
|
|
15
|
+
const defineTool = (name, description, properties, execute) => {
|
|
16
|
+
const _required = [];
|
|
17
|
+
return {
|
|
18
|
+
type: "function",
|
|
19
|
+
function: {
|
|
20
|
+
name,
|
|
21
|
+
description,
|
|
22
|
+
parameters: {
|
|
23
|
+
type: "object",
|
|
24
|
+
properties: Object.entries(properties).reduce((result, [key, property]) => {
|
|
25
|
+
if ("required" in property) {
|
|
26
|
+
_required.push(key);
|
|
27
|
+
delete property.required;
|
|
28
|
+
}
|
|
29
|
+
result[key] = property;
|
|
30
|
+
return result;
|
|
31
|
+
}, {}),
|
|
32
|
+
required: _required
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
execute
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
const segmenter = new Intl.Segmenter([], { granularity: "word" });
|
|
39
|
+
const estimateTextTokens = (text) => {
|
|
40
|
+
let words = 0;
|
|
41
|
+
let others = 0;
|
|
42
|
+
for (const seg of segmenter.segment(text)) if (seg.isWordLike) words++;
|
|
43
|
+
else others++;
|
|
44
|
+
return Math.ceil(words * 1.5 + others / 4);
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* 根据上下文里的中/英文/多模态消息,估算出可能消耗的token
|
|
48
|
+
* - 单词 ≈ 1.5token
|
|
49
|
+
* - 标点/空白等非词字符每4个 ≈ 1token
|
|
50
|
+
* - 图片/音频/视频/文件 ≈ 4096token(不好估算,取个较大的值)
|
|
51
|
+
*/
|
|
52
|
+
const estimateTokens = (messages) => {
|
|
53
|
+
if (!messages?.length) return 0;
|
|
54
|
+
return messages.reduce((acc, message) => {
|
|
55
|
+
const { content, tool_calls, ...metadata } = message;
|
|
56
|
+
if (typeof content === "string") acc += estimateTextTokens(content);
|
|
57
|
+
else for (const part of content) if (part.type === "text") acc += estimateTextTokens(part.text);
|
|
58
|
+
else acc += 4096;
|
|
59
|
+
if (tool_calls) acc += estimateTextTokens(JSON.stringify(tool_calls));
|
|
60
|
+
acc += estimateTextTokens(JSON.stringify(metadata));
|
|
61
|
+
return acc;
|
|
62
|
+
}, 0);
|
|
63
|
+
};
|
|
64
|
+
//#endregion
|
|
65
|
+
export { estimateTokens as i, defineTool as n, estimateTextTokens as r, defineModel as t };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,208 +1,21 @@
|
|
|
1
|
-
import {
|
|
2
|
-
//#region src/types.d.ts
|
|
3
|
-
type Model = {
|
|
4
|
-
baseUrl: string;
|
|
5
|
-
/** 如果不传,会尝试使用GET {baseUrl}/models获取到的第一个模型 */
|
|
6
|
-
model?: string;
|
|
7
|
-
/** 使用本地llama.cpp等服务时可以不填 */
|
|
8
|
-
apiKey?: string;
|
|
9
|
-
/**
|
|
10
|
-
* 模型支持的消息输入类型
|
|
11
|
-
* @default ["text"]
|
|
12
|
-
* @remarks 会在每轮对话前校验消息类型,存在不支持的输入时报错
|
|
13
|
-
*/
|
|
14
|
-
modalities?: Modality[];
|
|
15
|
-
/**
|
|
16
|
-
* 模型的最大上下文
|
|
17
|
-
* @default 131072
|
|
18
|
-
* @remarks 会在每轮对话结束后检查上下文阈值,自动压缩消息
|
|
19
|
-
*/
|
|
20
|
-
context?: number;
|
|
21
|
-
/**
|
|
22
|
-
* 自定义请求体,会在每轮请求时带上
|
|
23
|
-
* @example chat_template_kwargs: { enable_thinking: false }
|
|
24
|
-
*/
|
|
25
|
-
[key: string]: unknown;
|
|
26
|
-
};
|
|
27
|
-
/** 文/图/音/视频,暂未实现文件输入 */
|
|
28
|
-
type Modality = "text" | "image" | "audio" | "video";
|
|
29
|
-
type Message = {
|
|
30
|
-
role: "system" | "user" | "assistant" | "tool";
|
|
31
|
-
reasoning?: string | null;
|
|
32
|
-
content: string | ContentPart[];
|
|
33
|
-
tool_calls?: ToolCall[];
|
|
34
|
-
tool_call_id?: string;
|
|
35
|
-
[key: string]: unknown;
|
|
36
|
-
};
|
|
37
|
-
type ContentPart = TextContent | ImageContent | AudioContent | VideoContent;
|
|
38
|
-
type TextContent = {
|
|
39
|
-
type: "text";
|
|
40
|
-
text: string;
|
|
41
|
-
};
|
|
42
|
-
type ImageContent = {
|
|
43
|
-
type: "image_url";
|
|
44
|
-
image_url: {
|
|
45
|
-
url: string;
|
|
46
|
-
};
|
|
47
|
-
};
|
|
48
|
-
type AudioContent = {
|
|
49
|
-
type: "input_audio";
|
|
50
|
-
input_audio: {
|
|
51
|
-
/** 使用公网可访问的音频链接 */
|
|
52
|
-
url?: string;
|
|
53
|
-
/** 使用base64 */
|
|
54
|
-
data?: string;
|
|
55
|
-
format: string;
|
|
56
|
-
};
|
|
57
|
-
};
|
|
58
|
-
type VideoContent = {
|
|
59
|
-
type: "video_url";
|
|
60
|
-
video_url: {
|
|
61
|
-
url: string;
|
|
62
|
-
};
|
|
63
|
-
};
|
|
64
|
-
type ToolDefinition = {
|
|
65
|
-
type: "function";
|
|
66
|
-
function: {
|
|
67
|
-
name: string;
|
|
68
|
-
description: string;
|
|
69
|
-
parameters: {
|
|
70
|
-
type: "object";
|
|
71
|
-
properties: Record<string, {
|
|
72
|
-
type: string;
|
|
73
|
-
description?: string;
|
|
74
|
-
/** defineTool()提供的语法糖,等价于在parameters.required.push(当前property key) */
|
|
75
|
-
required?: boolean;
|
|
76
|
-
}>;
|
|
77
|
-
required?: string[];
|
|
78
|
-
};
|
|
79
|
-
};
|
|
80
|
-
/** 工具的实际执行函数 */
|
|
81
|
-
execute: (...args: any) => any;
|
|
82
|
-
};
|
|
83
|
-
type ToolCall = {
|
|
84
|
-
id: string;
|
|
85
|
-
type: "function";
|
|
86
|
-
function: {
|
|
87
|
-
name: string;
|
|
88
|
-
arguments: string;
|
|
89
|
-
};
|
|
90
|
-
};
|
|
91
|
-
/** stream()对外输出的事件 */
|
|
92
|
-
type LLMEvent = {
|
|
93
|
-
type: "reasoning_delta";
|
|
94
|
-
delta: string;
|
|
95
|
-
} | {
|
|
96
|
-
type: "content_delta";
|
|
97
|
-
delta: string;
|
|
98
|
-
} | {
|
|
99
|
-
type: "tool_call";
|
|
100
|
-
id: string;
|
|
101
|
-
name: string;
|
|
102
|
-
args: any;
|
|
103
|
-
} | {
|
|
104
|
-
type: "done";
|
|
105
|
-
finishReason: FinishReason;
|
|
106
|
-
usage?: Usage;
|
|
107
|
-
} | {
|
|
108
|
-
type: "error";
|
|
109
|
-
message: string;
|
|
110
|
-
};
|
|
111
|
-
/** stream()内部要处理的事件流 */
|
|
112
|
-
type ChatCompletionsChunk = {
|
|
113
|
-
id: string;
|
|
114
|
-
object: "chat.completion.chunk";
|
|
115
|
-
created: number;
|
|
116
|
-
model: string;
|
|
117
|
-
choices: {
|
|
118
|
-
index: number;
|
|
119
|
-
delta: Pick<Message, "reasoning" | "content"> & {
|
|
120
|
-
tool_calls?: ({
|
|
121
|
-
index: number;
|
|
122
|
-
} & Partial<ToolCall>)[];
|
|
123
|
-
};
|
|
124
|
-
finish_reason: FinishReason;
|
|
125
|
-
}[];
|
|
126
|
-
usage?: Usage;
|
|
127
|
-
} | "[DONE]";
|
|
128
|
-
/** 模型可能返回的finish_reason值:停止/工具调用/上下文超限 */
|
|
129
|
-
type FinishReason = "stop" | "tool_calls" | "length" | null;
|
|
130
|
-
type Usage = {
|
|
131
|
-
prompt_tokens: number;
|
|
132
|
-
completion_tokens: number;
|
|
133
|
-
total_tokens: number;
|
|
134
|
-
/** 每秒生成多少个token */
|
|
135
|
-
speed?: number;
|
|
136
|
-
[key: string]: any;
|
|
137
|
-
};
|
|
138
|
-
type AgentEvent = LLMEvent | {
|
|
139
|
-
type: "tool_result";
|
|
140
|
-
id: string;
|
|
141
|
-
name: string;
|
|
142
|
-
result: string;
|
|
143
|
-
};
|
|
144
|
-
//#endregion
|
|
1
|
+
import { a as FinishReason, c as Message, d as TextContent, f as ToolCall, h as VideoContent, i as ContentPart, l as Modality, m as Usage, n as AudioContent, o as ImageContent, p as ToolDefinition, r as ChatCompletionsChunk, s as LLMEvent, t as AgentEvent, u as Model } from "./types--dXFoUow.mjs";
|
|
145
2
|
//#region src/agent.d.ts
|
|
146
3
|
declare function runAgent(model: Model, messages: Message[], tools: ToolDefinition[]): AsyncGenerator<AgentEvent>;
|
|
147
4
|
//#endregion
|
|
5
|
+
//#region src/llm.d.ts
|
|
6
|
+
/**
|
|
7
|
+
* 流式请求模型,转发
|
|
8
|
+
*/
|
|
9
|
+
declare function stream(model: Model, messages: Message[], tools?: ToolDefinition[], options?: Omit<RequestInit, "body"> & {
|
|
10
|
+
body?: Record<string, unknown>;
|
|
11
|
+
}): AsyncGenerator<LLMEvent>;
|
|
12
|
+
//#endregion
|
|
148
13
|
//#region src/tools/get-time.d.ts
|
|
149
14
|
declare const _default: ToolDefinition;
|
|
150
15
|
//#endregion
|
|
151
16
|
//#region src/tools/get-weather.d.ts
|
|
152
17
|
declare const _default$1: ToolDefinition;
|
|
153
18
|
//#endregion
|
|
154
|
-
//#region src/tools/mcp.d.ts
|
|
155
|
-
type McpServer = {
|
|
156
|
-
type: "streamable_http" | "sse";
|
|
157
|
-
url: string;
|
|
158
|
-
headers?: Record<string, any>;
|
|
159
|
-
ignoredToolNames?: string[];
|
|
160
|
-
};
|
|
161
|
-
declare class MCPRouter {
|
|
162
|
-
private entries;
|
|
163
|
-
/** 注册一个新的MCP客户端 */
|
|
164
|
-
addClient(name: string, url: string, options?: Omit<McpServer, "type" | "url">): Promise<Client<{
|
|
165
|
-
method: string;
|
|
166
|
-
params?: {
|
|
167
|
-
[x: string]: unknown;
|
|
168
|
-
_meta?: {
|
|
169
|
-
[x: string]: unknown;
|
|
170
|
-
progressToken?: string | number | undefined;
|
|
171
|
-
"io.modelcontextprotocol/related-task"?: {
|
|
172
|
-
taskId: string;
|
|
173
|
-
} | undefined;
|
|
174
|
-
} | undefined;
|
|
175
|
-
} | undefined;
|
|
176
|
-
}, {
|
|
177
|
-
method: string;
|
|
178
|
-
params?: {
|
|
179
|
-
[x: string]: unknown;
|
|
180
|
-
_meta?: {
|
|
181
|
-
[x: string]: unknown;
|
|
182
|
-
progressToken?: string | number | undefined;
|
|
183
|
-
"io.modelcontextprotocol/related-task"?: {
|
|
184
|
-
taskId: string;
|
|
185
|
-
} | undefined;
|
|
186
|
-
} | undefined;
|
|
187
|
-
} | undefined;
|
|
188
|
-
}, {
|
|
189
|
-
[x: string]: unknown;
|
|
190
|
-
_meta?: {
|
|
191
|
-
[x: string]: unknown;
|
|
192
|
-
progressToken?: string | number | undefined;
|
|
193
|
-
"io.modelcontextprotocol/related-task"?: {
|
|
194
|
-
taskId: string;
|
|
195
|
-
} | undefined;
|
|
196
|
-
} | undefined;
|
|
197
|
-
}> | undefined>;
|
|
198
|
-
/** 返回OpenAI API兼容的tools数组 */
|
|
199
|
-
getTools(): Promise<ToolDefinition[]>;
|
|
200
|
-
}
|
|
201
|
-
/**
|
|
202
|
-
* 把传入的MCPServer列表转换成OpenAI API兼容的tools数组
|
|
203
|
-
*/
|
|
204
|
-
declare const loadMCPTools: (mcpServers?: Record<string, McpServer>) => Promise<ToolDefinition[]>;
|
|
205
|
-
//#endregion
|
|
206
19
|
//#region src/utils/compact/types.d.ts
|
|
207
20
|
declare namespace Compact {
|
|
208
21
|
type Options = {
|
|
@@ -264,6 +77,8 @@ declare namespace Compact {
|
|
|
264
77
|
/** 指导大模型如何总结消息 */
|
|
265
78
|
systemPrompt: string;
|
|
266
79
|
model: Model;
|
|
80
|
+
/** 触发总结前的钩子 */
|
|
81
|
+
beforeSummarize?: (summarizable: Message[]) => void | Promise<void>;
|
|
267
82
|
};
|
|
268
83
|
type Result = {
|
|
269
84
|
/** 是否压缩了工具调用结果 */
|
|
@@ -321,5 +136,13 @@ declare const defineModel: (config: Model) => Model;
|
|
|
321
136
|
* @param execute 实际执行工具的函数
|
|
322
137
|
*/
|
|
323
138
|
declare const defineTool: (name: ToolDefinition["function"]["name"], description: ToolDefinition["function"]["description"], properties: ToolDefinition["function"]["parameters"]["properties"], execute: ToolDefinition["execute"]) => ToolDefinition;
|
|
139
|
+
declare const estimateTextTokens: (text: string) => number;
|
|
140
|
+
/**
|
|
141
|
+
* 根据上下文里的中/英文/多模态消息,估算出可能消耗的token
|
|
142
|
+
* - 单词 ≈ 1.5token
|
|
143
|
+
* - 标点/空白等非词字符每4个 ≈ 1token
|
|
144
|
+
* - 图片/音频/视频/文件 ≈ 4096token(不好估算,取个较大的值)
|
|
145
|
+
*/
|
|
146
|
+
declare const estimateTokens: (messages?: Message[]) => number;
|
|
324
147
|
//#endregion
|
|
325
|
-
export { type AgentEvent, type AudioContent, type ChatCompletionsChunk, type ContentPart, type FinishReason, type ImageContent, type LLMEvent,
|
|
148
|
+
export { type AgentEvent, type AudioContent, type ChatCompletionsChunk, type ContentPart, type FinishReason, type ImageContent, type LLMEvent, type Message, type Modality, type Model, type TextContent, type ToolCall, type ToolDefinition, type Usage, type VideoContent, compact, defineModel, defineTool, estimateTextTokens, estimateTokens, _default as getTime, _default$1 as getWeather, runAgent, stream };
|
package/dist/index.mjs
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
|
|
1
|
+
import { a as stream, i as runAgent, n as get_weather_default, r as get_time_default, t as compact } from "./src-CKKPKPMG.mjs";
|
|
2
|
+
import { i as estimateTokens, n as defineTool, r as estimateTextTokens, t as defineModel } from "./helper-juLVGxlQ.mjs";
|
|
3
|
+
export { compact, defineModel, defineTool, estimateTextTokens, estimateTokens, get_time_default as getTime, get_weather_default as getWeather, runAgent, stream };
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { n as defineTool } from "./helper-juLVGxlQ.mjs";
|
|
2
|
+
import { extractErrorMessage, isObject, logger, omit } from "@nickyzj2023/utils";
|
|
3
|
+
//#region src/tools/mcp.ts
|
|
4
|
+
/** 全局单例MCP加载器 */
|
|
5
|
+
let router = null;
|
|
6
|
+
var MCPRouter = class {
|
|
7
|
+
entries = /* @__PURE__ */ new Map();
|
|
8
|
+
/** 注册一个新的MCP客户端 */
|
|
9
|
+
async addClient(name, url, options) {
|
|
10
|
+
if (this.entries.has(name)) return;
|
|
11
|
+
const [{ Client }, { StreamableHTTPClientTransport }] = await Promise.all([import("@modelcontextprotocol/sdk/client"), import("@modelcontextprotocol/sdk/client/streamableHttp.js")]);
|
|
12
|
+
const transport = new StreamableHTTPClientTransport(new URL(url), { requestInit: { headers: options?.headers } });
|
|
13
|
+
const client = new Client({
|
|
14
|
+
name,
|
|
15
|
+
version: "1.0.0"
|
|
16
|
+
});
|
|
17
|
+
await client.connect(transport);
|
|
18
|
+
const { tools } = await client.listTools();
|
|
19
|
+
const normalizedTools = tools.filter((tool) => !options?.ignoredToolNames?.includes(tool.name)).map((tool) => {
|
|
20
|
+
const _properties = { ...tool.inputSchema.properties ?? {} };
|
|
21
|
+
tool.inputSchema.required?.forEach((key) => {
|
|
22
|
+
if (isObject(_properties[key])) _properties[key] = {
|
|
23
|
+
..._properties[key],
|
|
24
|
+
required: true
|
|
25
|
+
};
|
|
26
|
+
});
|
|
27
|
+
return defineTool(tool.name, tool.description ?? "", _properties, (args) => client.callTool({
|
|
28
|
+
name: tool.name,
|
|
29
|
+
arguments: args
|
|
30
|
+
}));
|
|
31
|
+
});
|
|
32
|
+
this.entries.set(name, {
|
|
33
|
+
client,
|
|
34
|
+
tools: normalizedTools
|
|
35
|
+
});
|
|
36
|
+
return client;
|
|
37
|
+
}
|
|
38
|
+
/** 返回OpenAI API兼容的tools数组 */
|
|
39
|
+
async getTools() {
|
|
40
|
+
return [...this.entries.values()].flatMap((e) => e.tools);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* 把传入的MCPServer列表转换成OpenAI API兼容的tools数组
|
|
45
|
+
*/
|
|
46
|
+
const loadMCPTools = async (mcpServers = {}) => {
|
|
47
|
+
router ||= new MCPRouter();
|
|
48
|
+
await Promise.allSettled(Object.entries(mcpServers).map(async ([name, server]) => {
|
|
49
|
+
try {
|
|
50
|
+
await router?.addClient(name, server.url, omit(server, ["type", "url"]));
|
|
51
|
+
} catch (e) {
|
|
52
|
+
logger(`MCP服务器${name}加载失败:${extractErrorMessage(e)}`);
|
|
53
|
+
}
|
|
54
|
+
}));
|
|
55
|
+
return router.getTools();
|
|
56
|
+
};
|
|
57
|
+
//#endregion
|
|
58
|
+
export { loadMCPTools as n, MCPRouter as t };
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
1
|
+
import { i as estimateTokens, n as defineTool, r as estimateTextTokens } from "./helper-juLVGxlQ.mjs";
|
|
2
|
+
import { createXMLText, extractErrorMessage, fetcher, logger, parseSSE, pick, to } from "@nickyzj2023/utils";
|
|
4
3
|
//#region src/llm.ts
|
|
5
4
|
/**
|
|
6
5
|
* 分离ToolDefinition中的valid/invalid字段,前者可以传给模型,后者用于本地运算
|
|
@@ -22,7 +21,7 @@ const extractReasoning = (msgLike) => {
|
|
|
22
21
|
/**
|
|
23
22
|
* 流式请求模型,转发
|
|
24
23
|
*/
|
|
25
|
-
async function* stream(model, messages, tools = []) {
|
|
24
|
+
async function* stream(model, messages, tools = [], options) {
|
|
26
25
|
const validTools = tools.map((tool) => detachToolArguments(tool)[0]);
|
|
27
26
|
const api = fetcher(model.baseUrl, {
|
|
28
27
|
headers: { Authorization: `Bearer ${model.apiKey}` },
|
|
@@ -32,9 +31,11 @@ async function* stream(model, messages, tools = []) {
|
|
|
32
31
|
stream: true,
|
|
33
32
|
model: model.model,
|
|
34
33
|
messages,
|
|
35
|
-
tools: validTools
|
|
36
|
-
|
|
34
|
+
tools: validTools,
|
|
35
|
+
...options?.body
|
|
36
|
+
}, options));
|
|
37
37
|
if (error) {
|
|
38
|
+
if (options?.signal?.aborted) return;
|
|
38
39
|
yield {
|
|
39
40
|
type: "error",
|
|
40
41
|
message: error.message
|
|
@@ -44,39 +45,48 @@ async function* stream(model, messages, tools = []) {
|
|
|
44
45
|
const toolCallBuffers = /* @__PURE__ */ new Map();
|
|
45
46
|
let usage;
|
|
46
47
|
let finishReason = null;
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
48
|
+
try {
|
|
49
|
+
for await (const chunk of parseSSE(response)) {
|
|
50
|
+
if (typeof chunk === "string") continue;
|
|
51
|
+
if (chunk.usage) usage = chunk.usage;
|
|
52
|
+
const choice = chunk.choices?.[0];
|
|
53
|
+
if (!choice) continue;
|
|
54
|
+
const { delta } = choice;
|
|
55
|
+
const { content: contentDelta, tool_calls: toolCalls } = delta;
|
|
56
|
+
const reasoning = extractReasoning(delta);
|
|
57
|
+
if (reasoning) yield {
|
|
58
|
+
type: "reasoning_delta",
|
|
59
|
+
delta: reasoning
|
|
60
|
+
};
|
|
61
|
+
if (contentDelta) yield {
|
|
62
|
+
type: "content_delta",
|
|
63
|
+
delta: contentDelta.toString()
|
|
64
|
+
};
|
|
65
|
+
if (toolCalls) for (const call of toolCalls) {
|
|
66
|
+
const { index = 0, type = "function", id, function: fn, ...extra } = call;
|
|
67
|
+
const existing = toolCallBuffers.getOrInsert(index, {
|
|
68
|
+
id: "",
|
|
69
|
+
type,
|
|
70
|
+
function: {
|
|
71
|
+
name: "",
|
|
72
|
+
arguments: ""
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
if (id) existing.id = id;
|
|
76
|
+
if (fn?.name) existing.function.name += fn.name;
|
|
77
|
+
if (fn?.arguments) existing.function.arguments += fn.arguments;
|
|
78
|
+
if (extra) Object.assign(existing, extra);
|
|
79
|
+
toolCallBuffers.set(index, existing);
|
|
80
|
+
}
|
|
81
|
+
if (choice.finish_reason) finishReason = choice.finish_reason;
|
|
78
82
|
}
|
|
79
|
-
|
|
83
|
+
} catch (e) {
|
|
84
|
+
if (options?.signal?.aborted) return;
|
|
85
|
+
yield {
|
|
86
|
+
type: "error",
|
|
87
|
+
message: extractErrorMessage(e)
|
|
88
|
+
};
|
|
89
|
+
return;
|
|
80
90
|
}
|
|
81
91
|
for (const [, call] of toolCallBuffers) yield {
|
|
82
92
|
type: "tool_call",
|
|
@@ -91,70 +101,6 @@ async function* stream(model, messages, tools = []) {
|
|
|
91
101
|
};
|
|
92
102
|
}
|
|
93
103
|
//#endregion
|
|
94
|
-
//#region src/utils/helper.ts
|
|
95
|
-
/**
|
|
96
|
-
* 辅助定义一个POST /chat/completions支持的model参数
|
|
97
|
-
* @remarks 只有baseUrl字段是必须的
|
|
98
|
-
*/
|
|
99
|
-
const defineModel = (config) => ({
|
|
100
|
-
modalities: ["text"],
|
|
101
|
-
context: 131072,
|
|
102
|
-
...config
|
|
103
|
-
});
|
|
104
|
-
/**
|
|
105
|
-
* 辅助定义一个POST /chat/completions支持的tool对象
|
|
106
|
-
* @param execute 实际执行工具的函数
|
|
107
|
-
*/
|
|
108
|
-
const defineTool = (name, description, properties, execute) => {
|
|
109
|
-
const _required = [];
|
|
110
|
-
return {
|
|
111
|
-
type: "function",
|
|
112
|
-
function: {
|
|
113
|
-
name,
|
|
114
|
-
description,
|
|
115
|
-
parameters: {
|
|
116
|
-
type: "object",
|
|
117
|
-
properties: Object.entries(properties).reduce((result, [key, property]) => {
|
|
118
|
-
if ("required" in property) {
|
|
119
|
-
_required.push(key);
|
|
120
|
-
delete property.required;
|
|
121
|
-
}
|
|
122
|
-
result[key] = property;
|
|
123
|
-
return result;
|
|
124
|
-
}, {}),
|
|
125
|
-
required: _required
|
|
126
|
-
}
|
|
127
|
-
},
|
|
128
|
-
execute
|
|
129
|
-
};
|
|
130
|
-
};
|
|
131
|
-
const segmenter = new Intl.Segmenter([], { granularity: "word" });
|
|
132
|
-
const estimateTextTokens = (text) => {
|
|
133
|
-
let words = 0;
|
|
134
|
-
let others = 0;
|
|
135
|
-
for (const seg of segmenter.segment(text)) if (seg.isWordLike) words++;
|
|
136
|
-
else others++;
|
|
137
|
-
return Math.ceil(words * 1.5 + others / 4);
|
|
138
|
-
};
|
|
139
|
-
/**
|
|
140
|
-
* 根据上下文里的中/英文/多模态消息,估算出可能消耗的token
|
|
141
|
-
* - 单词 ≈ 1.5token
|
|
142
|
-
* - 标点/空白等非词字符每4个 ≈ 1token
|
|
143
|
-
* - 图片/音频/视频/文件 ≈ 4096token(不好估算,取个较大的值)
|
|
144
|
-
*/
|
|
145
|
-
const estimateTokens = (messages) => {
|
|
146
|
-
if (!messages?.length) return 0;
|
|
147
|
-
return messages.reduce((acc, message) => {
|
|
148
|
-
const { content, tool_calls, ...metadata } = message;
|
|
149
|
-
if (typeof content === "string") acc += estimateTextTokens(content);
|
|
150
|
-
else for (const part of content) if (part.type === "text") acc += estimateTextTokens(part.text);
|
|
151
|
-
else acc += 4096;
|
|
152
|
-
if (tool_calls) acc += estimateTextTokens(JSON.stringify(tool_calls));
|
|
153
|
-
acc += estimateTextTokens(JSON.stringify(metadata));
|
|
154
|
-
return acc;
|
|
155
|
-
}, 0);
|
|
156
|
-
};
|
|
157
|
-
//#endregion
|
|
158
104
|
//#region src/agent.ts
|
|
159
105
|
async function* runAgent(model, messages, tools) {
|
|
160
106
|
const toolMap = new Map(tools.map((tool) => [tool.function.name, tool]));
|
|
@@ -252,60 +198,6 @@ var get_weather_default = defineTool("get_weather", "查询指定城市的天气
|
|
|
252
198
|
return fetcher("https://wttr.in", { params: { format: "j1" } }).get(`/${city}`);
|
|
253
199
|
});
|
|
254
200
|
//#endregion
|
|
255
|
-
//#region src/tools/mcp.ts
|
|
256
|
-
/** 全局单例MCP加载器 */
|
|
257
|
-
let router = null;
|
|
258
|
-
var MCPRouter = class {
|
|
259
|
-
entries = /* @__PURE__ */ new Map();
|
|
260
|
-
/** 注册一个新的MCP客户端 */
|
|
261
|
-
async addClient(name, url, options) {
|
|
262
|
-
if (this.entries.has(name)) return;
|
|
263
|
-
const transport = new StreamableHTTPClientTransport(new URL(url), { requestInit: { headers: options?.headers } });
|
|
264
|
-
const client = new Client({
|
|
265
|
-
name,
|
|
266
|
-
version: "1.0.0"
|
|
267
|
-
});
|
|
268
|
-
await client.connect(transport);
|
|
269
|
-
const { tools } = await client.listTools();
|
|
270
|
-
const normalizedTools = tools.filter((tool) => !options?.ignoredToolNames?.includes(tool.name)).map((tool) => {
|
|
271
|
-
const _properties = { ...tool.inputSchema.properties ?? {} };
|
|
272
|
-
tool.inputSchema.required?.forEach((key) => {
|
|
273
|
-
if (isObject(_properties[key])) _properties[key] = {
|
|
274
|
-
..._properties[key],
|
|
275
|
-
required: true
|
|
276
|
-
};
|
|
277
|
-
});
|
|
278
|
-
return defineTool(tool.name, tool.description ?? "", _properties, (args) => client.callTool({
|
|
279
|
-
name: tool.name,
|
|
280
|
-
arguments: args
|
|
281
|
-
}));
|
|
282
|
-
});
|
|
283
|
-
this.entries.set(name, {
|
|
284
|
-
client,
|
|
285
|
-
tools: normalizedTools
|
|
286
|
-
});
|
|
287
|
-
return client;
|
|
288
|
-
}
|
|
289
|
-
/** 返回OpenAI API兼容的tools数组 */
|
|
290
|
-
async getTools() {
|
|
291
|
-
return [...this.entries.values()].flatMap((e) => e.tools);
|
|
292
|
-
}
|
|
293
|
-
};
|
|
294
|
-
/**
|
|
295
|
-
* 把传入的MCPServer列表转换成OpenAI API兼容的tools数组
|
|
296
|
-
*/
|
|
297
|
-
const loadMCPTools = async (mcpServers = {}) => {
|
|
298
|
-
router ||= new MCPRouter();
|
|
299
|
-
await Promise.allSettled(Object.entries(mcpServers).map(async ([name, server]) => {
|
|
300
|
-
try {
|
|
301
|
-
await router?.addClient(name, server.url, omit(server, ["type", "url"]));
|
|
302
|
-
} catch (e) {
|
|
303
|
-
logger(`MCP服务器${name}加载失败:${extractErrorMessage(e)}`);
|
|
304
|
-
}
|
|
305
|
-
}));
|
|
306
|
-
return router.getTools();
|
|
307
|
-
};
|
|
308
|
-
//#endregion
|
|
309
201
|
//#region src/utils/compact/helper.ts
|
|
310
202
|
/**
|
|
311
203
|
* 校验assistant(tool_calls)消息
|
|
@@ -410,12 +302,13 @@ const compactMediaMessages = async (compressible, options) => {
|
|
|
410
302
|
return count;
|
|
411
303
|
};
|
|
412
304
|
const summarizeMessages = async (compressible, options) => {
|
|
413
|
-
const { model, systemPrompt } = options ?? {};
|
|
305
|
+
const { model, systemPrompt, beforeSummarize } = options ?? {};
|
|
414
306
|
const summarizable = compressible.slice(1);
|
|
415
307
|
if (summarizable.length === 0) {
|
|
416
308
|
logger("消息太少,无需总结");
|
|
417
309
|
return 0;
|
|
418
310
|
}
|
|
311
|
+
beforeSummarize?.(summarizable);
|
|
419
312
|
const count = summarizable.length;
|
|
420
313
|
summarizable.push({
|
|
421
314
|
role: "system",
|
|
@@ -487,10 +380,11 @@ const compact = Object.assign(async (messages, model, options) => {
|
|
|
487
380
|
model
|
|
488
381
|
}) > 0;
|
|
489
382
|
if (tokens > context * ratioToSummarize) {
|
|
490
|
-
const { systemPrompt = "你现在的任务是总结历史消息" } = summarizeOptions ?? {};
|
|
383
|
+
const { systemPrompt = "你现在的任务是总结历史消息", beforeSummarize } = summarizeOptions ?? {};
|
|
491
384
|
const [error, count] = await to(summarizeMessages(compressible, {
|
|
492
385
|
model,
|
|
493
|
-
systemPrompt
|
|
386
|
+
systemPrompt,
|
|
387
|
+
beforeSummarize
|
|
494
388
|
}));
|
|
495
389
|
if (!error) result.hasSummarized = count > 0;
|
|
496
390
|
else result.hasDiscardMessages = discardMessagesUntil(compressible, (compressible) => {
|
|
@@ -512,4 +406,4 @@ const compact = Object.assign(async (messages, model, options) => {
|
|
|
512
406
|
discardMessagesUntil
|
|
513
407
|
});
|
|
514
408
|
//#endregion
|
|
515
|
-
export {
|
|
409
|
+
export { stream as a, runAgent as i, get_weather_default as n, get_time_default as r, compact as t };
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { p as ToolDefinition } from "../types--dXFoUow.mjs";
|
|
2
|
+
import { Client } from "@modelcontextprotocol/sdk/client";
|
|
3
|
+
//#region src/tools/mcp.d.ts
|
|
4
|
+
type McpServer = {
|
|
5
|
+
type: "streamable_http" | "sse";
|
|
6
|
+
url: string;
|
|
7
|
+
headers?: Record<string, any>;
|
|
8
|
+
ignoredToolNames?: string[];
|
|
9
|
+
};
|
|
10
|
+
declare class MCPRouter {
|
|
11
|
+
private entries;
|
|
12
|
+
/** 注册一个新的MCP客户端 */
|
|
13
|
+
addClient(name: string, url: string, options?: Omit<McpServer, "type" | "url">): Promise<Client<{
|
|
14
|
+
method: string;
|
|
15
|
+
params?: {
|
|
16
|
+
[x: string]: unknown;
|
|
17
|
+
_meta?: {
|
|
18
|
+
[x: string]: unknown;
|
|
19
|
+
progressToken?: string | number | undefined;
|
|
20
|
+
"io.modelcontextprotocol/related-task"?: {
|
|
21
|
+
taskId: string;
|
|
22
|
+
} | undefined;
|
|
23
|
+
} | undefined;
|
|
24
|
+
} | undefined;
|
|
25
|
+
}, {
|
|
26
|
+
method: string;
|
|
27
|
+
params?: {
|
|
28
|
+
[x: string]: unknown;
|
|
29
|
+
_meta?: {
|
|
30
|
+
[x: string]: unknown;
|
|
31
|
+
progressToken?: string | number | undefined;
|
|
32
|
+
"io.modelcontextprotocol/related-task"?: {
|
|
33
|
+
taskId: string;
|
|
34
|
+
} | undefined;
|
|
35
|
+
} | undefined;
|
|
36
|
+
} | undefined;
|
|
37
|
+
}, {
|
|
38
|
+
[x: string]: unknown;
|
|
39
|
+
_meta?: {
|
|
40
|
+
[x: string]: unknown;
|
|
41
|
+
progressToken?: string | number | undefined;
|
|
42
|
+
"io.modelcontextprotocol/related-task"?: {
|
|
43
|
+
taskId: string;
|
|
44
|
+
} | undefined;
|
|
45
|
+
} | undefined;
|
|
46
|
+
}> | undefined>;
|
|
47
|
+
/** 返回OpenAI API兼容的tools数组 */
|
|
48
|
+
getTools(): Promise<ToolDefinition[]>;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* 把传入的MCPServer列表转换成OpenAI API兼容的tools数组
|
|
52
|
+
*/
|
|
53
|
+
declare const loadMCPTools: (mcpServers?: Record<string, McpServer>) => Promise<ToolDefinition[]>;
|
|
54
|
+
//#endregion
|
|
55
|
+
export { MCPRouter, McpServer, loadMCPTools };
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
//#region src/types.d.ts
|
|
2
|
+
type Model = {
|
|
3
|
+
baseUrl: string;
|
|
4
|
+
/** 如果不传,会尝试使用GET {baseUrl}/models获取到的第一个模型 */
|
|
5
|
+
model?: string;
|
|
6
|
+
/** 使用本地llama.cpp等服务时可以不填 */
|
|
7
|
+
apiKey?: string;
|
|
8
|
+
/**
|
|
9
|
+
* 模型支持的消息输入类型
|
|
10
|
+
* @default ["text"]
|
|
11
|
+
* @remarks 会在每轮对话前校验消息类型,存在不支持的输入时报错
|
|
12
|
+
*/
|
|
13
|
+
modalities?: Modality[];
|
|
14
|
+
/**
|
|
15
|
+
* 模型的最大上下文
|
|
16
|
+
* @default 131072
|
|
17
|
+
* @remarks 会在每轮对话结束后检查上下文阈值,自动压缩消息
|
|
18
|
+
*/
|
|
19
|
+
context?: number;
|
|
20
|
+
/**
|
|
21
|
+
* 自定义请求体,会在每轮请求时带上
|
|
22
|
+
* @example chat_template_kwargs: { enable_thinking: false }
|
|
23
|
+
*/
|
|
24
|
+
[key: string]: unknown;
|
|
25
|
+
};
|
|
26
|
+
/** 文/图/音/视频,暂未实现文件输入 */
|
|
27
|
+
type Modality = "text" | "image" | "audio" | "video";
|
|
28
|
+
type Message = {
|
|
29
|
+
role: "system" | "user" | "assistant" | "tool";
|
|
30
|
+
reasoning?: string | null;
|
|
31
|
+
content: string | ContentPart[];
|
|
32
|
+
tool_calls?: ToolCall[];
|
|
33
|
+
tool_call_id?: string;
|
|
34
|
+
[key: string]: unknown;
|
|
35
|
+
};
|
|
36
|
+
type ContentPart = TextContent | ImageContent | AudioContent | VideoContent;
|
|
37
|
+
type TextContent = {
|
|
38
|
+
type: "text";
|
|
39
|
+
text: string;
|
|
40
|
+
};
|
|
41
|
+
type ImageContent = {
|
|
42
|
+
type: "image_url";
|
|
43
|
+
image_url: {
|
|
44
|
+
url: string;
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
type AudioContent = {
|
|
48
|
+
type: "input_audio";
|
|
49
|
+
input_audio: {
|
|
50
|
+
/** 使用公网可访问的音频链接 */
|
|
51
|
+
url?: string;
|
|
52
|
+
/** 使用base64 */
|
|
53
|
+
data?: string;
|
|
54
|
+
format: string;
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
type VideoContent = {
|
|
58
|
+
type: "video_url";
|
|
59
|
+
video_url: {
|
|
60
|
+
url: string;
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
type ToolDefinition = {
|
|
64
|
+
type: "function";
|
|
65
|
+
function: {
|
|
66
|
+
name: string;
|
|
67
|
+
description: string;
|
|
68
|
+
parameters: {
|
|
69
|
+
type: "object";
|
|
70
|
+
properties: Record<string, {
|
|
71
|
+
type: string;
|
|
72
|
+
description?: string;
|
|
73
|
+
/** defineTool()提供的语法糖,等价于在parameters.required.push(当前property key) */
|
|
74
|
+
required?: boolean;
|
|
75
|
+
}>;
|
|
76
|
+
required?: string[];
|
|
77
|
+
};
|
|
78
|
+
};
|
|
79
|
+
/** 工具的实际执行函数 */
|
|
80
|
+
execute: (...args: any) => any;
|
|
81
|
+
};
|
|
82
|
+
type ToolCall = {
|
|
83
|
+
id: string;
|
|
84
|
+
type: "function";
|
|
85
|
+
function: {
|
|
86
|
+
name: string;
|
|
87
|
+
arguments: string;
|
|
88
|
+
};
|
|
89
|
+
};
|
|
90
|
+
/** stream()对外输出的事件 */
|
|
91
|
+
type LLMEvent = {
|
|
92
|
+
type: "reasoning_delta";
|
|
93
|
+
delta: string;
|
|
94
|
+
} | {
|
|
95
|
+
type: "content_delta";
|
|
96
|
+
delta: string;
|
|
97
|
+
} | {
|
|
98
|
+
type: "tool_call";
|
|
99
|
+
id: string;
|
|
100
|
+
name: string;
|
|
101
|
+
args: any;
|
|
102
|
+
} | {
|
|
103
|
+
type: "done";
|
|
104
|
+
finishReason: FinishReason;
|
|
105
|
+
usage?: Usage;
|
|
106
|
+
} | {
|
|
107
|
+
type: "error";
|
|
108
|
+
message: string;
|
|
109
|
+
};
|
|
110
|
+
/** stream()内部要处理的事件流 */
|
|
111
|
+
type ChatCompletionsChunk = {
|
|
112
|
+
id: string;
|
|
113
|
+
object: "chat.completion.chunk";
|
|
114
|
+
created: number;
|
|
115
|
+
model: string;
|
|
116
|
+
choices: {
|
|
117
|
+
index: number;
|
|
118
|
+
delta: Pick<Message, "reasoning" | "content"> & {
|
|
119
|
+
tool_calls?: ({
|
|
120
|
+
index: number;
|
|
121
|
+
} & Partial<ToolCall>)[];
|
|
122
|
+
};
|
|
123
|
+
finish_reason: FinishReason;
|
|
124
|
+
}[];
|
|
125
|
+
usage?: Usage;
|
|
126
|
+
} | "[DONE]";
|
|
127
|
+
/** 模型可能返回的finish_reason值:停止/工具调用/上下文超限 */
|
|
128
|
+
type FinishReason = "stop" | "tool_calls" | "length" | null;
|
|
129
|
+
type Usage = {
|
|
130
|
+
prompt_tokens: number;
|
|
131
|
+
completion_tokens: number;
|
|
132
|
+
total_tokens: number;
|
|
133
|
+
/** 每秒生成多少个token */
|
|
134
|
+
speed?: number;
|
|
135
|
+
[key: string]: any;
|
|
136
|
+
};
|
|
137
|
+
type AgentEvent = LLMEvent | {
|
|
138
|
+
type: "tool_result";
|
|
139
|
+
id: string;
|
|
140
|
+
name: string;
|
|
141
|
+
result: string;
|
|
142
|
+
};
|
|
143
|
+
//#endregion
|
|
144
|
+
export { FinishReason as a, Message as c, TextContent as d, ToolCall as f, VideoContent as h, ContentPart as i, Modality as l, Usage as m, AudioContent as n, ImageContent as o, ToolDefinition as p, ChatCompletionsChunk as r, LLMEvent as s, AgentEvent as t, Model as u };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nickyzj2023/ai",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"description": "我的“pi”,参考了pi-from-scratch",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.mjs",
|
|
@@ -9,6 +9,10 @@
|
|
|
9
9
|
".": {
|
|
10
10
|
"types": "./dist/index.d.mts",
|
|
11
11
|
"import": "./dist/index.mjs"
|
|
12
|
+
},
|
|
13
|
+
"./mcp": {
|
|
14
|
+
"types": "./dist/tools/mcp.d.mts",
|
|
15
|
+
"import": "./dist/tools/mcp.mjs"
|
|
12
16
|
}
|
|
13
17
|
},
|
|
14
18
|
"files": [
|