zhitalk 0.0.9 → 0.0.11
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 +13 -3
- package/dist/agent/agent.js +35 -24
- package/dist/agent/cli.js +52 -3
- package/dist/agent/config.js +4 -1
- package/dist/agent/model.js +3 -2
- package/package.json +4 -1
package/README.md
CHANGED
|
@@ -2,22 +2,32 @@
|
|
|
2
2
|
|
|
3
3
|
[智语 Zhitalk](https://zhitalk.chat/) 是一个 AI Agent 个人助手,像 OpenClaw 小龙虾一样。 包含 tools skills memory hook subagent MCP-server 等 Agent 功能。你可以和它聊天,给它分配任务,让它操作文件...
|
|
4
4
|
|
|
5
|
+
📖 你可以免费围观项目进展,也可以加入学习 [跟双越老师从 0 开发一个 AI Agent 智能体](https://www.huashuiai.com/pub/ai-agent-camp-new)
|
|
6
|
+
|
|
5
7
|
## Usage
|
|
6
8
|
|
|
7
|
-
|
|
9
|
+
本地安装 Nodejs 版本>=22 ,执行如下命令安装 Zhitalk
|
|
8
10
|
|
|
9
|
-
|
|
11
|
+
【注意】Windows 操作系统,使用管理员身份打开 cmd ,再执行命令
|
|
10
12
|
|
|
11
13
|
```
|
|
12
14
|
npm install zhitalk -g
|
|
13
15
|
```
|
|
14
16
|
|
|
15
|
-
|
|
17
|
+
能查看到 Zhitalk 的版本号,说明安装成功
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
zhitalk --version
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
运行 zhitalk 命令,初始化
|
|
16
24
|
|
|
17
25
|
```
|
|
18
26
|
zhitalk
|
|
19
27
|
```
|
|
20
28
|
|
|
29
|
+
第一次执行时,需要你修改配置文件,参考 https://zhitalk.chat/#config
|
|
30
|
+
|
|
21
31
|
## Dev
|
|
22
32
|
|
|
23
33
|
### Tech
|
package/dist/agent/agent.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { SqliteSaver } from '@langchain/langgraph-checkpoint-sqlite';
|
|
2
2
|
import { SystemMessage, AIMessage, HumanMessage, ToolMessage, } from '@langchain/core/messages';
|
|
3
|
-
import { StateGraph, Annotation, START, END, interrupt, Command, } from '@langchain/langgraph';
|
|
3
|
+
import { StateGraph, Annotation, START, END, interrupt, Command, GraphRecursionError, } from '@langchain/langgraph';
|
|
4
4
|
import { messagesStateReducer } from '@langchain/langgraph';
|
|
5
5
|
import { DB_PATH } from './db.js';
|
|
6
6
|
import { tools, maybePersistedOutput, initTools } from './tools.js';
|
|
@@ -295,32 +295,43 @@ async function _runAgent(compiledAgent, userMessage, onToken, onToolConfirmation
|
|
|
295
295
|
let fullResponse = '';
|
|
296
296
|
let usageMetadata;
|
|
297
297
|
let input = { messages: [new HumanMessage(userMessage)] };
|
|
298
|
+
const recursionLimit = 50;
|
|
298
299
|
while (true) {
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
300
|
+
try {
|
|
301
|
+
const stream = await compiledAgent.stream(input, {
|
|
302
|
+
...config,
|
|
303
|
+
streamMode: 'messages',
|
|
304
|
+
signal,
|
|
305
|
+
recursionLimit,
|
|
306
|
+
});
|
|
307
|
+
for await (const chunk of stream) {
|
|
308
|
+
if (signal?.aborted) {
|
|
309
|
+
throw new Error('abort');
|
|
310
|
+
}
|
|
311
|
+
const message = chunk[0];
|
|
312
|
+
const metadata = chunk[1];
|
|
313
|
+
if (metadata?.langgraph_node !== 'model_request')
|
|
314
|
+
continue;
|
|
315
|
+
const msgUsage = message.usage_metadata;
|
|
316
|
+
if (msgUsage) {
|
|
317
|
+
usageMetadata = msgUsage;
|
|
318
|
+
}
|
|
319
|
+
// AIMessageChunk 的 content 在 message.content 属性上,不在 kwargs.content
|
|
320
|
+
const content = message.content ?? message.kwargs?.content ?? '';
|
|
321
|
+
const toolCallChunks = message.tool_call_chunks ?? [];
|
|
322
|
+
if (!content || toolCallChunks.length > 0)
|
|
323
|
+
continue;
|
|
324
|
+
onToken(content);
|
|
325
|
+
fullResponse += content;
|
|
308
326
|
}
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
if (
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
usageMetadata = msgUsage;
|
|
327
|
+
}
|
|
328
|
+
catch (err) {
|
|
329
|
+
if (err instanceof GraphRecursionError) {
|
|
330
|
+
const hint = `我已经思考了 ${recursionLimit} 个步骤,仍未得到最终结果,我暂停歇会儿~ 你可以随时让我“继续”`;
|
|
331
|
+
onToken('\n\n' + hint);
|
|
332
|
+
return { response: fullResponse + '\n\n' + hint, usageMetadata };
|
|
316
333
|
}
|
|
317
|
-
|
|
318
|
-
const content = message.content ?? message.kwargs?.content ?? '';
|
|
319
|
-
const toolCallChunks = message.tool_call_chunks ?? [];
|
|
320
|
-
if (!content || toolCallChunks.length > 0)
|
|
321
|
-
continue;
|
|
322
|
-
onToken(content);
|
|
323
|
-
fullResponse += content;
|
|
334
|
+
throw err;
|
|
324
335
|
}
|
|
325
336
|
const state = await compiledAgent.getState(config);
|
|
326
337
|
const interruptedTask = state.tasks?.find((t) => t.interrupts?.length > 0);
|
package/dist/agent/cli.js
CHANGED
|
@@ -49,9 +49,43 @@ function prompt(question) {
|
|
|
49
49
|
});
|
|
50
50
|
});
|
|
51
51
|
}
|
|
52
|
+
function createSpinner(text) {
|
|
53
|
+
const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
54
|
+
let i = 0;
|
|
55
|
+
let timer = null;
|
|
56
|
+
let running = false;
|
|
57
|
+
const tick = () => {
|
|
58
|
+
i = (i + 1) % frames.length;
|
|
59
|
+
readline.cursorTo(process.stdout, 0);
|
|
60
|
+
process.stdout.write(color.gray(`${text} ${frames[i]}`));
|
|
61
|
+
};
|
|
62
|
+
return {
|
|
63
|
+
start() {
|
|
64
|
+
if (running)
|
|
65
|
+
return;
|
|
66
|
+
running = true;
|
|
67
|
+
i = 0;
|
|
68
|
+
readline.cursorTo(process.stdout, 0);
|
|
69
|
+
process.stdout.write(color.gray(`${text} ${frames[0]}`));
|
|
70
|
+
timer = setInterval(tick, 80);
|
|
71
|
+
},
|
|
72
|
+
stop() {
|
|
73
|
+
if (!running)
|
|
74
|
+
return;
|
|
75
|
+
running = false;
|
|
76
|
+
if (timer) {
|
|
77
|
+
clearInterval(timer);
|
|
78
|
+
timer = null;
|
|
79
|
+
}
|
|
80
|
+
readline.cursorTo(process.stdout, 0);
|
|
81
|
+
readline.clearLine(process.stdout, 0);
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
}
|
|
52
85
|
async function chat(userInput) {
|
|
53
86
|
const rl = createInterface();
|
|
54
|
-
|
|
87
|
+
// AI: 前缀延迟到第一个 token 输出,此前显示 spinner
|
|
88
|
+
// process.stdout.write('\n' + color.aiPrefix())
|
|
55
89
|
const controller = new AbortController();
|
|
56
90
|
// 监听 ESC 键,中断 AI 请求
|
|
57
91
|
const escListener = (_str, key) => {
|
|
@@ -64,18 +98,32 @@ async function chat(userInput) {
|
|
|
64
98
|
readline.emitKeypressEvents(process.stdin);
|
|
65
99
|
process.stdin.on('keypress', escListener);
|
|
66
100
|
let usageMetadata;
|
|
101
|
+
let needsPrefix = true;
|
|
102
|
+
const spinner = createSpinner('AI 思考中');
|
|
67
103
|
try {
|
|
104
|
+
spinner.start();
|
|
68
105
|
const result = await runAgentStream(userInput, (token) => {
|
|
106
|
+
if (needsPrefix) {
|
|
107
|
+
needsPrefix = false;
|
|
108
|
+
spinner.stop();
|
|
109
|
+
process.stdout.write('\n' + color.aiPrefix());
|
|
110
|
+
}
|
|
69
111
|
process.stdout.write(token);
|
|
70
112
|
}, async (toolCalls) => {
|
|
113
|
+
spinner.stop();
|
|
71
114
|
for (const call of toolCalls) {
|
|
72
115
|
console.log(formatToolLog(call.name, JSON.stringify(call.args)));
|
|
73
116
|
}
|
|
74
117
|
const answer = await new Promise((resolve) => {
|
|
75
118
|
rl.question('确认执行以上工具? (y/n): ', resolve);
|
|
76
119
|
});
|
|
77
|
-
|
|
78
|
-
answer.trim().toLowerCase() === 'yes'
|
|
120
|
+
const confirmed = answer.trim().toLowerCase() === 'y' ||
|
|
121
|
+
answer.trim().toLowerCase() === 'yes';
|
|
122
|
+
if (confirmed) {
|
|
123
|
+
needsPrefix = true;
|
|
124
|
+
spinner.start();
|
|
125
|
+
}
|
|
126
|
+
return confirmed;
|
|
79
127
|
}, threadId, controller.signal);
|
|
80
128
|
usageMetadata = result.usageMetadata;
|
|
81
129
|
}
|
|
@@ -85,6 +133,7 @@ async function chat(userInput) {
|
|
|
85
133
|
}
|
|
86
134
|
}
|
|
87
135
|
finally {
|
|
136
|
+
spinner.stop();
|
|
88
137
|
process.stdin.removeListener('keypress', escListener);
|
|
89
138
|
rl.close();
|
|
90
139
|
}
|
package/dist/agent/config.js
CHANGED
|
@@ -36,7 +36,10 @@ export function getModelConfig() {
|
|
|
36
36
|
if (!config.model.baseURL) {
|
|
37
37
|
throw new Error(`配置文件 ${CONFIG_PATH} 中缺少 model.baseURL 字段,请参考 https://zhitalk.chat/#config 修改配置`);
|
|
38
38
|
}
|
|
39
|
-
return
|
|
39
|
+
return {
|
|
40
|
+
...config.model,
|
|
41
|
+
model: config.model.model.toLowerCase(),
|
|
42
|
+
};
|
|
40
43
|
}
|
|
41
44
|
export function getEnv(key) {
|
|
42
45
|
const config = loadConfig();
|
package/dist/agent/model.js
CHANGED
|
@@ -3,11 +3,12 @@ import { getModelConfig } from './config.js';
|
|
|
3
3
|
const modelConfig = getModelConfig();
|
|
4
4
|
export function createModel(options) {
|
|
5
5
|
const modelKwargs = {};
|
|
6
|
-
if (modelConfig.model.startsWith('kimi')
|
|
6
|
+
if (modelConfig.model.startsWith('kimi') ||
|
|
7
|
+
modelConfig.model.startsWith('minimax')) {
|
|
7
8
|
modelKwargs.thinking = { type: 'disabled' };
|
|
8
9
|
}
|
|
9
10
|
// GLM 模型的流式 function calling 存在兼容性问题,禁用流式输出
|
|
10
|
-
const isGlm = modelConfig.model.
|
|
11
|
+
const isGlm = modelConfig.model.startsWith('glm');
|
|
11
12
|
return new ChatOpenAI({
|
|
12
13
|
model: modelConfig.model,
|
|
13
14
|
apiKey: modelConfig.apiKey,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zhitalk",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.11",
|
|
4
4
|
"description": "A personal AI Agent like OpenClaw, including tools, skills, memory, hook, sub-agent, MCP server, etc. Run in the terminal.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -21,6 +21,9 @@
|
|
|
21
21
|
"keywords": [],
|
|
22
22
|
"author": "双越",
|
|
23
23
|
"license": "ISC",
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": ">=22"
|
|
26
|
+
},
|
|
24
27
|
"docs": "https://zhitalk.chat/",
|
|
25
28
|
"devDependencies": {
|
|
26
29
|
"@jest/globals": "^29.7.0",
|