xiaozhou-chat 1.0.10 → 1.0.12
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/lib/chat.js +73 -50
- package/package.json +3 -2
package/lib/chat.js
CHANGED
|
@@ -58,43 +58,28 @@ export async function chatStream(context, userInput = null, options = {}) {
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
// 构造 Tools 定义
|
|
61
|
-
// TODO: 从 mcpClients 和 builtInTools 获取
|
|
62
|
-
// 这里我们假设 context 传入了完整的 tools 列表,或者我们在外部组装
|
|
63
|
-
// 为了解耦,建议外部传入 tools array
|
|
64
61
|
const tools = context.tools || [];
|
|
65
62
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
messages: messages,
|
|
69
|
-
stream: true,
|
|
70
|
-
max_tokens: 8192
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
if (tools.length > 0) {
|
|
74
|
-
body.tools = tools;
|
|
75
|
-
}
|
|
76
|
-
|
|
63
|
+
// 处理 System Prompt
|
|
64
|
+
let requestMessages = messages;
|
|
77
65
|
if (config.systemPrompt) {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
// But standard is: messages[0] is system.
|
|
85
|
-
// We'll let the caller handle message structure, or handle it here.
|
|
86
|
-
// For CLI, we usually want to prepend system prompt if not present.
|
|
87
|
-
const sysMsg = { role: "system", content: config.systemPrompt };
|
|
88
|
-
if (messages.length > 0 && messages[0].role === "system") {
|
|
89
|
-
// Replace existing? Or assume caller synced it?
|
|
90
|
-
// Let's use a copy for the request to be safe
|
|
91
|
-
body.messages = [sysMsg, ...messages.filter(m => m.role !== "system")];
|
|
92
|
-
} else {
|
|
93
|
-
body.messages = [sysMsg, ...messages];
|
|
94
|
-
}
|
|
95
|
-
}
|
|
66
|
+
const sysMsg = { role: "system", content: config.systemPrompt };
|
|
67
|
+
if (messages.length > 0 && messages[0].role === "system") {
|
|
68
|
+
requestMessages = [sysMsg, ...messages.filter(m => m.role !== "system")];
|
|
69
|
+
} else {
|
|
70
|
+
requestMessages = [sysMsg, ...messages];
|
|
71
|
+
}
|
|
96
72
|
}
|
|
97
73
|
|
|
74
|
+
// 构造请求 Body
|
|
75
|
+
const createBody = (withTools = true) => ({
|
|
76
|
+
model: config.model,
|
|
77
|
+
messages: requestMessages,
|
|
78
|
+
stream: true,
|
|
79
|
+
max_tokens: 8192,
|
|
80
|
+
tools: (withTools && tools.length > 0) ? tools : undefined
|
|
81
|
+
});
|
|
82
|
+
|
|
98
83
|
const spinner = new Spinner(isRecursion ? "AI 正在分析工具结果..." : "AI 正在思考...");
|
|
99
84
|
spinner.start();
|
|
100
85
|
|
|
@@ -102,44 +87,82 @@ export async function chatStream(context, userInput = null, options = {}) {
|
|
|
102
87
|
|
|
103
88
|
let requestUrl = `${config.baseUrl}/chat/completions`;
|
|
104
89
|
|
|
105
|
-
//
|
|
106
|
-
|
|
90
|
+
// 自动修正 Base URL (如果缺少 /v1)
|
|
91
|
+
if (!config.baseUrl.endsWith("/v1") && !config.baseUrl.endsWith("/v1/")) {
|
|
92
|
+
// console.log(`\n⚠️ 自动追加 /v1 到 Base URL`);
|
|
93
|
+
requestUrl = `${config.baseUrl}/v1/chat/completions`;
|
|
94
|
+
}
|
|
107
95
|
|
|
96
|
+
let shouldRetryWithV1 = false;
|
|
97
|
+
|
|
108
98
|
try {
|
|
109
|
-
let res
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
99
|
+
let res;
|
|
100
|
+
let usedTools = true;
|
|
101
|
+
|
|
102
|
+
// 第一次尝试 (默认带 Tools)
|
|
103
|
+
try {
|
|
104
|
+
res = await requestWithRetry(requestUrl, {
|
|
105
|
+
method: "POST",
|
|
106
|
+
headers: {
|
|
107
|
+
"Content-Type": "application/json",
|
|
108
|
+
Authorization: `Bearer ${config.apiKey}`
|
|
109
|
+
},
|
|
110
|
+
body: JSON.stringify(createBody(true)),
|
|
111
|
+
signal: signal
|
|
112
|
+
}, 1); // 减少内部重试,由外层控制
|
|
113
|
+
} catch (e) {
|
|
114
|
+
// 如果是 400 错误,且我们用了 Tools,尝试降级
|
|
115
|
+
if (e.message.includes("400") || e.message.includes("Improperly formed request")) {
|
|
116
|
+
console.log("\n⚠️ API 不支持工具调用或参数格式错误,尝试自动降级为纯聊天模式...");
|
|
117
|
+
usedTools = false;
|
|
118
|
+
res = await requestWithRetry(requestUrl, {
|
|
119
|
+
method: "POST",
|
|
120
|
+
headers: {
|
|
121
|
+
"Content-Type": "application/json",
|
|
122
|
+
Authorization: `Bearer ${config.apiKey}`
|
|
123
|
+
},
|
|
124
|
+
body: JSON.stringify(createBody(false)),
|
|
125
|
+
signal: signal
|
|
126
|
+
});
|
|
127
|
+
} else {
|
|
128
|
+
throw e;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// 智能检测 HTML (404/BaseUrl 错误)
|
|
120
133
|
const contentType = res.headers.get("content-type");
|
|
121
134
|
if (contentType && contentType.includes("text/html")) {
|
|
122
135
|
if (!config.baseUrl.endsWith("/v1") && !config.baseUrl.endsWith("/v1/")) {
|
|
123
|
-
// 静默重试,不打扰用户
|
|
124
136
|
requestUrl = `${config.baseUrl}/v1/chat/completions`;
|
|
125
137
|
shouldRetryWithV1 = true;
|
|
126
|
-
|
|
127
|
-
// 重试请求
|
|
138
|
+
// 重试 (保持降级状态)
|
|
128
139
|
res = await requestWithRetry(requestUrl, {
|
|
129
140
|
method: "POST",
|
|
130
141
|
headers: {
|
|
131
142
|
"Content-Type": "application/json",
|
|
132
143
|
Authorization: `Bearer ${config.apiKey}`
|
|
133
144
|
},
|
|
134
|
-
body: JSON.stringify(
|
|
145
|
+
body: JSON.stringify(createBody(usedTools)),
|
|
135
146
|
signal: signal
|
|
136
147
|
});
|
|
137
148
|
}
|
|
138
149
|
}
|
|
139
150
|
|
|
140
|
-
// 停止 Spinner
|
|
151
|
+
// 停止 Spinner
|
|
141
152
|
spinner.stop();
|
|
142
153
|
|
|
154
|
+
// 检查是否是非流式响应 (JSON)
|
|
155
|
+
if (contentType && contentType.includes("application/json") && !createBody().stream) {
|
|
156
|
+
const data = await res.json();
|
|
157
|
+
const content = data.choices?.[0]?.message?.content || "";
|
|
158
|
+
printer.print(content);
|
|
159
|
+
printer.stop();
|
|
160
|
+
return { content };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// 准备流式输出
|
|
164
|
+
// spinner.stop(); // 移到上面了
|
|
165
|
+
|
|
143
166
|
if (shouldRetryWithV1 && res.ok && !res.headers.get("content-type")?.includes("text/html")) {
|
|
144
167
|
// 静默保存配置
|
|
145
168
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "xiaozhou-chat",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.12",
|
|
4
4
|
"description": "CLI chatbot based on NewAPI",
|
|
5
5
|
"bin": {
|
|
6
6
|
"xiaozhou-chat": "bin/cli.js"
|
|
@@ -22,7 +22,8 @@
|
|
|
22
22
|
"marked": "^15.0.12",
|
|
23
23
|
"marked-terminal": "^7.3.0",
|
|
24
24
|
"minimist": "^1.2.8",
|
|
25
|
-
"moment": "^2.30.1"
|
|
25
|
+
"moment": "^2.30.1",
|
|
26
|
+
"xiaozhou-chat": "^1.0.1-0.1"
|
|
26
27
|
},
|
|
27
28
|
"pkg": {
|
|
28
29
|
"assets": []
|