@caoguo/maplibre-ai 0.0.4 → 0.0.6
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/dist/chunk-AIQSXKZQ.js +311 -0
- package/dist/chunk-AIQSXKZQ.js.map +1 -0
- package/dist/{chunk-ARYB2X4B.js → chunk-WVEICADA.js} +43 -15
- package/dist/chunk-WVEICADA.js.map +1 -0
- package/dist/copilot/index.d.cts +1 -1
- package/dist/copilot/index.d.ts +1 -1
- package/dist/deepseek-rSUSDXri.d.cts +81 -0
- package/dist/deepseek-rSUSDXri.d.ts +81 -0
- package/dist/index.cjs +205 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -2
- package/dist/llm/index.cjs +164 -0
- package/dist/llm/index.cjs.map +1 -1
- package/dist/llm/index.d.cts +64 -59
- package/dist/llm/index.d.ts +64 -59
- package/dist/llm/index.js +1 -1
- package/dist/nlpg/index.cjs +41 -13
- package/dist/nlpg/index.cjs.map +1 -1
- package/dist/nlpg/index.d.cts +5 -3
- package/dist/nlpg/index.d.ts +5 -3
- package/dist/nlpg/index.js +1 -1
- package/package.json +11 -11
- package/dist/chunk-ARYB2X4B.js.map +0 -1
- package/dist/chunk-ZI6ZF26Q.js +0 -149
- package/dist/chunk-ZI6ZF26Q.js.map +0 -1
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
// src/llm/deepseek.ts
|
|
2
|
+
var DEFAULT_BASE_URL = "https://api.deepseek.com";
|
|
3
|
+
var DEFAULT_MODEL = "deepseek-chat";
|
|
4
|
+
function normalizeError(body) {
|
|
5
|
+
const err = new Error(body || "DeepSeek API \u8BF7\u6C42\u5931\u8D25");
|
|
6
|
+
err.name = "DeepSeekError";
|
|
7
|
+
return err;
|
|
8
|
+
}
|
|
9
|
+
var DeepSeekClient = class {
|
|
10
|
+
config;
|
|
11
|
+
lastRequestId = 0;
|
|
12
|
+
constructor(config) {
|
|
13
|
+
if (!config.apiKey) throw new Error("DeepSeekClient: apiKey \u4E0D\u80FD\u4E3A\u7A7A");
|
|
14
|
+
this.config = {
|
|
15
|
+
apiKey: config.apiKey,
|
|
16
|
+
baseUrl: (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, ""),
|
|
17
|
+
model: config.model ?? DEFAULT_MODEL,
|
|
18
|
+
temperature: config.temperature ?? 0.3,
|
|
19
|
+
maxTokens: config.maxTokens ?? 2048,
|
|
20
|
+
timeoutMs: config.timeoutMs ?? 3e4,
|
|
21
|
+
retries: config.retries ?? 2,
|
|
22
|
+
fetchImpl: config.fetchImpl ?? (globalThis.fetch ?? fetch)
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/** 发起一次聊天补全 */
|
|
26
|
+
async chat(messages, opts = {}) {
|
|
27
|
+
return this.requestWithRetry(messages, opts);
|
|
28
|
+
}
|
|
29
|
+
/** 聊天 + JSON 解析 */
|
|
30
|
+
async chatJson(messages) {
|
|
31
|
+
const result = await this.chat(messages, { json: true });
|
|
32
|
+
const raw = result.content.trim();
|
|
33
|
+
const cleaned = raw.replace(/^```(?:json)?\s*/i, "").replace(/\s*```$/, "");
|
|
34
|
+
let data;
|
|
35
|
+
try {
|
|
36
|
+
data = JSON.parse(cleaned);
|
|
37
|
+
} catch (e) {
|
|
38
|
+
const match = cleaned.match(/\{[\s\S]*\}|\[[\s\S]*\]/);
|
|
39
|
+
if (match) {
|
|
40
|
+
data = JSON.parse(match[0]);
|
|
41
|
+
} else {
|
|
42
|
+
throw new Error(`DeepSeek \u8FD4\u56DE\u4E86\u975E JSON \u5185\u5BB9: ${raw.slice(0, 200)}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return { data, raw, model: result.model };
|
|
46
|
+
}
|
|
47
|
+
async requestWithRetry(messages, opts) {
|
|
48
|
+
let lastError = null;
|
|
49
|
+
for (let attempt = 0; attempt <= this.config.retries; attempt++) {
|
|
50
|
+
try {
|
|
51
|
+
return await this.requestOnce(messages, opts);
|
|
52
|
+
} catch (e) {
|
|
53
|
+
lastError = e;
|
|
54
|
+
const retryable = !(e instanceof Error) || !e.name.includes("DeepSeek") || this.isRetryable(e);
|
|
55
|
+
if (!retryable && !this.isRetryable(e)) break;
|
|
56
|
+
if (attempt < this.config.retries) {
|
|
57
|
+
await this.sleep(Math.min(1e3 * 2 ** attempt, 8e3));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
throw lastError ?? new Error("DeepSeek \u8BF7\u6C42\u5931\u8D25");
|
|
62
|
+
}
|
|
63
|
+
isRetryable(err) {
|
|
64
|
+
const msg = err.message ?? "";
|
|
65
|
+
return /429|500|502|503|504|timeout|network|ECONN|abort|fetch failed/i.test(msg);
|
|
66
|
+
}
|
|
67
|
+
async requestOnce(messages, opts) {
|
|
68
|
+
const { fetchImpl } = this.config;
|
|
69
|
+
const controller = new AbortController();
|
|
70
|
+
const timer = setTimeout(() => controller.abort(), this.config.timeoutMs);
|
|
71
|
+
const body = {
|
|
72
|
+
model: this.config.model,
|
|
73
|
+
messages,
|
|
74
|
+
temperature: this.config.temperature,
|
|
75
|
+
max_tokens: this.config.maxTokens,
|
|
76
|
+
stream: Boolean(opts.onChunk)
|
|
77
|
+
};
|
|
78
|
+
if (opts.json) {
|
|
79
|
+
body.response_format = { type: "json_object" };
|
|
80
|
+
}
|
|
81
|
+
try {
|
|
82
|
+
const res = await fetchImpl(`${this.config.baseUrl}/chat/completions`, {
|
|
83
|
+
method: "POST",
|
|
84
|
+
headers: {
|
|
85
|
+
"Content-Type": "application/json",
|
|
86
|
+
Authorization: `Bearer ${this.config.apiKey}`
|
|
87
|
+
},
|
|
88
|
+
body: JSON.stringify(body),
|
|
89
|
+
signal: controller.signal
|
|
90
|
+
});
|
|
91
|
+
if (!res.ok) {
|
|
92
|
+
const text = await res.text().catch(() => "");
|
|
93
|
+
throw normalizeError(`DeepSeek HTTP ${res.status}: ${text}`);
|
|
94
|
+
}
|
|
95
|
+
if (opts.onChunk && res.body) {
|
|
96
|
+
return this.parseStream(res.body, opts.onChunk);
|
|
97
|
+
}
|
|
98
|
+
const json = await res.json();
|
|
99
|
+
const content = json.choices?.[0]?.message?.content ?? "";
|
|
100
|
+
return {
|
|
101
|
+
content,
|
|
102
|
+
model: json.model ?? this.config.model,
|
|
103
|
+
usage: json.usage ? { promptTokens: json.usage.prompt_tokens, completionTokens: json.usage.completion_tokens, totalTokens: json.usage.total_tokens } : void 0
|
|
104
|
+
};
|
|
105
|
+
} finally {
|
|
106
|
+
clearTimeout(timer);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/** 解析 SSE 流式响应 */
|
|
110
|
+
async parseStream(body, onChunk) {
|
|
111
|
+
const reader = body.getReader();
|
|
112
|
+
const decoder = new TextDecoder();
|
|
113
|
+
let content = "";
|
|
114
|
+
let buffer = "";
|
|
115
|
+
while (true) {
|
|
116
|
+
const { done, value } = await reader.read();
|
|
117
|
+
if (done) break;
|
|
118
|
+
buffer += decoder.decode(value, { stream: true });
|
|
119
|
+
const lines = buffer.split("\n");
|
|
120
|
+
buffer = lines.pop() ?? "";
|
|
121
|
+
for (const line of lines) {
|
|
122
|
+
const trimmed = line.trim();
|
|
123
|
+
if (!trimmed.startsWith("data:")) continue;
|
|
124
|
+
const data = trimmed.slice(5).trim();
|
|
125
|
+
if (data === "[DONE]") continue;
|
|
126
|
+
try {
|
|
127
|
+
const json = JSON.parse(data);
|
|
128
|
+
const delta = json.choices?.[0]?.delta?.content ?? "";
|
|
129
|
+
if (delta) {
|
|
130
|
+
content += delta;
|
|
131
|
+
onChunk(delta);
|
|
132
|
+
}
|
|
133
|
+
} catch {
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return { content, model: this.config.model };
|
|
138
|
+
}
|
|
139
|
+
sleep(ms) {
|
|
140
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
function createDeepSeekClient(config) {
|
|
144
|
+
return new DeepSeekClient(config);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// src/llm/openaiCompatible.ts
|
|
148
|
+
function normalizeError2(body, provider) {
|
|
149
|
+
const err = new Error(body || `${provider} API \u8BF7\u6C42\u5931\u8D25`);
|
|
150
|
+
err.name = `${provider}Error`;
|
|
151
|
+
return err;
|
|
152
|
+
}
|
|
153
|
+
var OpenAICompatibleClient = class {
|
|
154
|
+
apiKey;
|
|
155
|
+
baseUrl;
|
|
156
|
+
model;
|
|
157
|
+
temperature;
|
|
158
|
+
maxTokens;
|
|
159
|
+
timeoutMs;
|
|
160
|
+
retries;
|
|
161
|
+
fetchImpl;
|
|
162
|
+
authScheme;
|
|
163
|
+
extraHeaders;
|
|
164
|
+
providerLabel;
|
|
165
|
+
constructor(config) {
|
|
166
|
+
if (!config.apiKey) throw new Error("OpenAICompatibleClient: apiKey \u4E0D\u80FD\u4E3A\u7A7A");
|
|
167
|
+
if (!config.baseUrl) throw new Error("OpenAICompatibleClient: baseUrl \u4E0D\u80FD\u4E3A\u7A7A");
|
|
168
|
+
if (!config.model) throw new Error("OpenAICompatibleClient: model \u4E0D\u80FD\u4E3A\u7A7A");
|
|
169
|
+
this.apiKey = config.apiKey;
|
|
170
|
+
this.baseUrl = config.baseUrl.replace(/\/+$/, "");
|
|
171
|
+
this.model = config.model;
|
|
172
|
+
this.temperature = config.temperature ?? 0.3;
|
|
173
|
+
this.maxTokens = config.maxTokens ?? 2048;
|
|
174
|
+
this.timeoutMs = config.timeoutMs ?? 3e4;
|
|
175
|
+
this.retries = config.retries ?? 2;
|
|
176
|
+
this.fetchImpl = config.fetchImpl ?? (globalThis.fetch ?? fetch);
|
|
177
|
+
this.authScheme = config.authScheme ?? "Bearer";
|
|
178
|
+
this.extraHeaders = config.extraHeaders ?? {};
|
|
179
|
+
this.providerLabel = (() => {
|
|
180
|
+
try {
|
|
181
|
+
return new URL(this.baseUrl).hostname || "LLM";
|
|
182
|
+
} catch {
|
|
183
|
+
return "LLM";
|
|
184
|
+
}
|
|
185
|
+
})();
|
|
186
|
+
}
|
|
187
|
+
async chat(messages, opts = {}) {
|
|
188
|
+
return this.requestWithRetry(messages, opts);
|
|
189
|
+
}
|
|
190
|
+
async chatJson(messages) {
|
|
191
|
+
const result = await this.chat(messages, { json: true });
|
|
192
|
+
const raw = result.content.trim();
|
|
193
|
+
const cleaned = raw.replace(/^```(?:json)?\s*/i, "").replace(/\s*```$/, "");
|
|
194
|
+
let data;
|
|
195
|
+
try {
|
|
196
|
+
data = JSON.parse(cleaned);
|
|
197
|
+
} catch {
|
|
198
|
+
const match = cleaned.match(/\{[\s\S]*\}|\[[\s\S]*\]/);
|
|
199
|
+
if (match) {
|
|
200
|
+
data = JSON.parse(match[0]);
|
|
201
|
+
} else {
|
|
202
|
+
throw new Error(`${this.providerLabel} \u8FD4\u56DE\u4E86\u975E JSON \u5185\u5BB9: ${raw.slice(0, 200)}`);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return { data, raw, model: result.model };
|
|
206
|
+
}
|
|
207
|
+
async requestWithRetry(messages, opts) {
|
|
208
|
+
let lastError = null;
|
|
209
|
+
for (let attempt = 0; attempt <= this.retries; attempt++) {
|
|
210
|
+
try {
|
|
211
|
+
return await this.requestOnce(messages, opts);
|
|
212
|
+
} catch (e) {
|
|
213
|
+
lastError = e;
|
|
214
|
+
if (attempt < this.retries) {
|
|
215
|
+
await this.sleep(Math.min(1e3 * 2 ** attempt, 8e3));
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
throw lastError ?? new Error(`${this.providerLabel} \u8BF7\u6C42\u5931\u8D25`);
|
|
220
|
+
}
|
|
221
|
+
async requestOnce(messages, opts) {
|
|
222
|
+
const controller = new AbortController();
|
|
223
|
+
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
224
|
+
const body = {
|
|
225
|
+
model: this.model,
|
|
226
|
+
messages,
|
|
227
|
+
temperature: this.temperature,
|
|
228
|
+
max_tokens: this.maxTokens,
|
|
229
|
+
stream: Boolean(opts.onChunk)
|
|
230
|
+
};
|
|
231
|
+
if (opts.json) {
|
|
232
|
+
body.response_format = { type: "json_object" };
|
|
233
|
+
}
|
|
234
|
+
const headers = {
|
|
235
|
+
"Content-Type": "application/json",
|
|
236
|
+
...this.extraHeaders
|
|
237
|
+
};
|
|
238
|
+
if (this.authScheme === "Bearer") {
|
|
239
|
+
headers.Authorization = `Bearer ${this.apiKey}`;
|
|
240
|
+
} else {
|
|
241
|
+
headers["X-Api-Key"] = this.apiKey;
|
|
242
|
+
}
|
|
243
|
+
try {
|
|
244
|
+
const res = await this.fetchImpl(`${this.baseUrl}/chat/completions`, {
|
|
245
|
+
method: "POST",
|
|
246
|
+
headers,
|
|
247
|
+
body: JSON.stringify(body),
|
|
248
|
+
signal: controller.signal
|
|
249
|
+
});
|
|
250
|
+
if (!res.ok) {
|
|
251
|
+
const text = await res.text().catch(() => "");
|
|
252
|
+
throw normalizeError2(`${this.providerLabel} HTTP ${res.status}: ${text}`, this.providerLabel);
|
|
253
|
+
}
|
|
254
|
+
if (opts.onChunk && res.body) {
|
|
255
|
+
return this.parseStream(res.body, opts.onChunk);
|
|
256
|
+
}
|
|
257
|
+
const json = await res.json();
|
|
258
|
+
const content = json.choices?.[0]?.message?.content ?? "";
|
|
259
|
+
return {
|
|
260
|
+
content,
|
|
261
|
+
model: json.model ?? this.model,
|
|
262
|
+
usage: json.usage ? {
|
|
263
|
+
promptTokens: json.usage.prompt_tokens,
|
|
264
|
+
completionTokens: json.usage.completion_tokens,
|
|
265
|
+
totalTokens: json.usage.prompt_tokens + json.usage.completion_tokens
|
|
266
|
+
} : void 0
|
|
267
|
+
};
|
|
268
|
+
} finally {
|
|
269
|
+
clearTimeout(timer);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
async parseStream(body, onChunk) {
|
|
273
|
+
const reader = body.getReader();
|
|
274
|
+
const decoder = new TextDecoder();
|
|
275
|
+
let content = "";
|
|
276
|
+
let buffer = "";
|
|
277
|
+
while (true) {
|
|
278
|
+
const { done, value } = await reader.read();
|
|
279
|
+
if (done) break;
|
|
280
|
+
buffer += decoder.decode(value, { stream: true });
|
|
281
|
+
const lines = buffer.split("\n");
|
|
282
|
+
buffer = lines.pop() ?? "";
|
|
283
|
+
for (const line of lines) {
|
|
284
|
+
const trimmed = line.trim();
|
|
285
|
+
if (!trimmed.startsWith("data:")) continue;
|
|
286
|
+
const data = trimmed.slice(5).trim();
|
|
287
|
+
if (data === "[DONE]") continue;
|
|
288
|
+
try {
|
|
289
|
+
const json = JSON.parse(data);
|
|
290
|
+
const delta = json.choices?.[0]?.delta?.content ?? "";
|
|
291
|
+
if (delta) {
|
|
292
|
+
content += delta;
|
|
293
|
+
onChunk(delta);
|
|
294
|
+
}
|
|
295
|
+
} catch {
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
return { content, model: this.model };
|
|
300
|
+
}
|
|
301
|
+
sleep(ms) {
|
|
302
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
303
|
+
}
|
|
304
|
+
};
|
|
305
|
+
function createOpenAICompatibleClient(config) {
|
|
306
|
+
return new OpenAICompatibleClient(config);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
export { DeepSeekClient, OpenAICompatibleClient, createDeepSeekClient, createOpenAICompatibleClient };
|
|
310
|
+
//# sourceMappingURL=chunk-AIQSXKZQ.js.map
|
|
311
|
+
//# sourceMappingURL=chunk-AIQSXKZQ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/llm/deepseek.ts","../src/llm/openaiCompatible.ts"],"names":["normalizeError"],"mappings":"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","file":"chunk-AIQSXKZQ.js","sourcesContent":["/**\n * DeepSeek LLM 客户端(PRD phase-0 §5.5 / §5.7 的 v1 后端能力)\n *\n * DeepSeek API 与 OpenAI Chat Completions 协议兼容:\n * base_url: https://api.deepseek.com\n * 模型: deepseek-chat(通用对话)/ deepseek-reasoner(深度推理)\n * 鉴权: Authorization: Bearer <API_KEY>\n *\n * 设计:\n * - 依赖注入的 fetch(浏览器/Node 均可运行,Node 18+ 原生 fetch)\n * - 自动重试(指数退避)\n * - 失败降级(可传入 fallback,规则引擎兜底)\n * - 支持流式(onChunk 回调)与 JSON 输出\n */\n\nexport interface DeepSeekConfig {\n /** API Key */\n apiKey: string;\n /** 基础地址,默认 https://api.deepseek.com */\n baseUrl?: string;\n /** 模型,默认 deepseek-chat */\n model?: 'deepseek-chat' | 'deepseek-reasoner' | (string & {});\n /** 温度 0-2 */\n temperature?: number;\n /** 最大 token */\n maxTokens?: number;\n /** 超时(ms),默认 30s */\n timeoutMs?: number;\n /** 重试次数,默认 2 */\n retries?: number;\n /** 注入 fetch(测试/Node 环境),默认 globalThis.fetch */\n fetchImpl?: typeof fetch;\n}\n\nexport interface ChatMessage {\n role: 'system' | 'user' | 'assistant';\n content: string;\n}\n\nexport interface ChatOptions {\n /** 是否启用 JSON 模式(要求模型返回纯 JSON) */\n json?: boolean;\n /** 流式回调 */\n onChunk?: (delta: string) => void;\n}\n\nexport interface ChatResult {\n /** 完整回复文本 */\n content: string;\n /** 消耗 token */\n usage?: { promptTokens: number; completionTokens: number; totalTokens: number };\n /** 使用的模型 */\n model: string;\n}\n\n/** 已解析的 JSON 回复 */\nexport interface JsonChatResult<T> {\n /** 解析出的 JSON 对象 */\n data: T;\n /** 原始文本 */\n raw: string;\n model: string;\n}\n\nconst DEFAULT_BASE_URL = 'https://api.deepseek.com';\nconst DEFAULT_MODEL = 'deepseek-chat';\n\n/** 把中文异常文案简化为错误码 */\nfunction normalizeError(body: string): Error {\n const err = new Error(body || 'DeepSeek API 请求失败');\n err.name = 'DeepSeekError';\n return err;\n}\n\nexport class DeepSeekClient {\n private config: Required<Omit<DeepSeekConfig, 'fetchImpl'>> & { fetchImpl: typeof fetch };\n private lastRequestId = 0;\n\n constructor(config: DeepSeekConfig) {\n if (!config.apiKey) throw new Error('DeepSeekClient: apiKey 不能为空');\n this.config = {\n apiKey: config.apiKey,\n baseUrl: (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/+$/, ''),\n model: config.model ?? DEFAULT_MODEL,\n temperature: config.temperature ?? 0.3,\n maxTokens: config.maxTokens ?? 2048,\n timeoutMs: config.timeoutMs ?? 30000,\n retries: config.retries ?? 2,\n fetchImpl: config.fetchImpl ?? ((globalThis as { fetch?: typeof fetch }).fetch ?? fetch),\n };\n }\n\n /** 发起一次聊天补全 */\n async chat(messages: ChatMessage[], opts: ChatOptions = {}): Promise<ChatResult> {\n return this.requestWithRetry(messages, opts);\n }\n\n /** 聊天 + JSON 解析 */\n async chatJson<T>(messages: ChatMessage[]): Promise<JsonChatResult<T>> {\n const result = await this.chat(messages, { json: true });\n const raw = result.content.trim();\n // 去除可能的 ```json 围栏\n const cleaned = raw.replace(/^```(?:json)?\\s*/i, '').replace(/\\s*```$/, '');\n let data: T;\n try {\n data = JSON.parse(cleaned) as T;\n } catch (e) {\n // 尝试从文本中提取首个 JSON 对象\n const match = cleaned.match(/\\{[\\s\\S]*\\}|\\[[\\s\\S]*\\]/);\n if (match) {\n data = JSON.parse(match[0]) as T;\n } else {\n throw new Error(`DeepSeek 返回了非 JSON 内容: ${raw.slice(0, 200)}`);\n }\n }\n return { data, raw, model: result.model };\n }\n\n private async requestWithRetry(messages: ChatMessage[], opts: ChatOptions): Promise<ChatResult> {\n let lastError: Error | null = null;\n for (let attempt = 0; attempt <= this.config.retries; attempt++) {\n try {\n return await this.requestOnce(messages, opts);\n } catch (e) {\n lastError = e as Error;\n // 仅对可重试错误(网络/429/5xx)重试\n const retryable = !(e instanceof Error) || !e.name.includes('DeepSeek') || this.isRetryable(e as Error);\n if (!retryable && !this.isRetryable(e as Error)) break;\n if (attempt < this.config.retries) {\n await this.sleep(Math.min(1000 * 2 ** attempt, 8000));\n }\n }\n }\n throw lastError ?? new Error('DeepSeek 请求失败');\n }\n\n private isRetryable(err: Error): boolean {\n const msg = err.message ?? '';\n return /429|500|502|503|504|timeout|network|ECONN|abort|fetch failed/i.test(msg);\n }\n\n private async requestOnce(messages: ChatMessage[], opts: ChatOptions): Promise<ChatResult> {\n const { fetchImpl } = this.config;\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.config.timeoutMs);\n\n const body: Record<string, unknown> = {\n model: this.config.model,\n messages,\n temperature: this.config.temperature,\n max_tokens: this.config.maxTokens,\n stream: Boolean(opts.onChunk),\n };\n if (opts.json) {\n body.response_format = { type: 'json_object' };\n }\n\n try {\n const res = await fetchImpl(`${this.config.baseUrl}/chat/completions`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${this.config.apiKey}`,\n },\n body: JSON.stringify(body),\n signal: controller.signal,\n });\n\n if (!res.ok) {\n const text = await res.text().catch(() => '');\n throw normalizeError(`DeepSeek HTTP ${res.status}: ${text}`);\n }\n\n // 流式\n if (opts.onChunk && res.body) {\n return this.parseStream(res.body, opts.onChunk);\n }\n\n // 非流式\n const json = (await res.json()) as {\n choices: Array<{ message: { content: string } }>;\n usage?: { prompt_tokens: number; completion_tokens: number; total_tokens: number };\n model?: string;\n };\n const content = json.choices?.[0]?.message?.content ?? '';\n return {\n content,\n model: json.model ?? this.config.model,\n usage: json.usage\n ? { promptTokens: json.usage.prompt_tokens, completionTokens: json.usage.completion_tokens, totalTokens: json.usage.total_tokens }\n : undefined,\n };\n } finally {\n clearTimeout(timer);\n }\n }\n\n /** 解析 SSE 流式响应 */\n private async parseStream(\n body: ReadableStream<Uint8Array>,\n onChunk: (delta: string) => void\n ): Promise<ChatResult> {\n const reader = body.getReader();\n const decoder = new TextDecoder();\n let content = '';\n let buffer = '';\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split('\\n');\n buffer = lines.pop() ?? '';\n for (const line of lines) {\n const trimmed = line.trim();\n if (!trimmed.startsWith('data:')) continue;\n const data = trimmed.slice(5).trim();\n if (data === '[DONE]') continue;\n try {\n const json = JSON.parse(data) as { choices?: Array<{ delta?: { content?: string } }> };\n const delta = json.choices?.[0]?.delta?.content ?? '';\n if (delta) {\n content += delta;\n onChunk(delta);\n }\n } catch {\n // 忽略无法解析的行\n }\n }\n }\n return { content, model: this.config.model };\n }\n\n private sleep(ms: number): Promise<void> {\n return new Promise((r) => setTimeout(r, ms));\n }\n}\n\n/** 便捷工厂:创建默认配置的客户端 */\nexport function createDeepSeekClient(config: DeepSeekConfig): DeepSeekClient {\n return new DeepSeekClient(config);\n}\n","/**\n * 通用 OpenAI Chat Completions 兼容客户端(PRD phase-0 §5.5/§5.7 扩展)\n *\n * 任何遵循 OpenAI Chat Completions 协议的服务都可直接对接:\n * - OpenAI 官方(https://api.openai.com)\n * - Azure OpenAI\n * - SiliconFlow、DeepSeek、Moonshot、Together、Groq、OpenRouter 等\n *\n * 与 DeepSeekClient 区别:base_url 与鉴权 header 可自定义;接口形态完全一致,\n * 均为 LlmClient 实现,可互换使用。\n *\n * 重试 / 超时 / JSON 解析策略与 DeepSeekClient 完全一致,确保 provider 切换零行为漂移。\n */\n\nimport type { LlmClient } from './types';\nimport type {\n ChatMessage,\n ChatOptions,\n ChatResult,\n JsonChatResult,\n} from './deepseek';\n\nexport interface OpenAICompatibleConfig {\n /** API Key */\n apiKey: string;\n /** 基础地址(如 https://api.openai.com) */\n baseUrl: string;\n /** 模型名(必填,无默认以免误用 OpenAI gpt-4) */\n model: string;\n /** 温度 0-2,默认 0.3 */\n temperature?: number;\n /** 最大 token,默认 2048 */\n maxTokens?: number;\n /** 超时(ms),默认 30s */\n timeoutMs?: number;\n /** 重试次数,默认 2 */\n retries?: number;\n /** 注入 fetch(测试/Node 环境),默认 globalThis.fetch */\n fetchImpl?: typeof fetch;\n /** 鉴权 header 前缀,默认 'Bearer' */\n authScheme?: 'Bearer' | 'X-Api-Key';\n /** 自定义额外 headers(用于 Azure api-version 等) */\n extraHeaders?: Record<string, string>;\n}\n\nfunction normalizeError(body: string, provider: string): Error {\n const err = new Error(body || `${provider} API 请求失败`);\n err.name = `${provider}Error`;\n return err;\n}\n\nexport class OpenAICompatibleClient implements LlmClient {\n private readonly apiKey: string;\n private readonly baseUrl: string;\n private readonly model: string;\n private readonly temperature: number;\n private readonly maxTokens: number;\n private readonly timeoutMs: number;\n private readonly retries: number;\n private readonly fetchImpl: typeof fetch;\n private readonly authScheme: 'Bearer' | 'X-Api-Key';\n private readonly extraHeaders: Record<string, string>;\n private readonly providerLabel: string;\n\n constructor(config: OpenAICompatibleConfig) {\n if (!config.apiKey) throw new Error('OpenAICompatibleClient: apiKey 不能为空');\n if (!config.baseUrl) throw new Error('OpenAICompatibleClient: baseUrl 不能为空');\n if (!config.model) throw new Error('OpenAICompatibleClient: model 不能为空');\n this.apiKey = config.apiKey;\n this.baseUrl = config.baseUrl.replace(/\\/+$/, '');\n this.model = config.model;\n this.temperature = config.temperature ?? 0.3;\n this.maxTokens = config.maxTokens ?? 2048;\n this.timeoutMs = config.timeoutMs ?? 30000;\n this.retries = config.retries ?? 2;\n this.fetchImpl =\n config.fetchImpl ?? ((globalThis as { fetch?: typeof fetch }).fetch ?? fetch);\n this.authScheme = config.authScheme ?? 'Bearer';\n this.extraHeaders = config.extraHeaders ?? {};\n this.providerLabel = (() => {\n try {\n return new URL(this.baseUrl).hostname || 'LLM';\n } catch {\n return 'LLM';\n }\n })();\n }\n\n async chat(messages: ChatMessage[], opts: ChatOptions = {}): Promise<ChatResult> {\n return this.requestWithRetry(messages, opts);\n }\n\n async chatJson<T = unknown>(messages: ChatMessage[]): Promise<JsonChatResult<T>> {\n const result = await this.chat(messages, { json: true });\n const raw = result.content.trim();\n const cleaned = raw.replace(/^```(?:json)?\\s*/i, '').replace(/\\s*```$/, '');\n let data: T;\n try {\n data = JSON.parse(cleaned) as T;\n } catch {\n const match = cleaned.match(/\\{[\\s\\S]*\\}|\\[[\\s\\S]*\\]/);\n if (match) {\n data = JSON.parse(match[0]) as T;\n } else {\n throw new Error(`${this.providerLabel} 返回了非 JSON 内容: ${raw.slice(0, 200)}`);\n }\n }\n return { data, raw, model: result.model };\n }\n\n private async requestWithRetry(messages: ChatMessage[], opts: ChatOptions): Promise<ChatResult> {\n let lastError: Error | null = null;\n for (let attempt = 0; attempt <= this.retries; attempt++) {\n try {\n return await this.requestOnce(messages, opts);\n } catch (e) {\n lastError = e as Error;\n if (attempt < this.retries) {\n await this.sleep(Math.min(1000 * 2 ** attempt, 8000));\n }\n }\n }\n throw lastError ?? new Error(`${this.providerLabel} 请求失败`);\n }\n\n private async requestOnce(messages: ChatMessage[], opts: ChatOptions): Promise<ChatResult> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.timeoutMs);\n\n const body: Record<string, unknown> = {\n model: this.model,\n messages,\n temperature: this.temperature,\n max_tokens: this.maxTokens,\n stream: Boolean(opts.onChunk),\n };\n if (opts.json) {\n body.response_format = { type: 'json_object' };\n }\n\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n ...this.extraHeaders,\n };\n if (this.authScheme === 'Bearer') {\n headers.Authorization = `Bearer ${this.apiKey}`;\n } else {\n headers['X-Api-Key'] = this.apiKey;\n }\n\n try {\n const res = await this.fetchImpl(`${this.baseUrl}/chat/completions`, {\n method: 'POST',\n headers,\n body: JSON.stringify(body),\n signal: controller.signal,\n });\n if (!res.ok) {\n const text = await res.text().catch(() => '');\n throw normalizeError(`${this.providerLabel} HTTP ${res.status}: ${text}`, this.providerLabel);\n }\n if (opts.onChunk && res.body) {\n return this.parseStream(res.body, opts.onChunk);\n }\n const json = (await res.json()) as {\n choices: Array<{ message: { content: string } }>;\n usage?: { prompt_tokens: number; completion_tokens: number; total_tokens: number };\n model?: string;\n };\n const content = json.choices?.[0]?.message?.content ?? '';\n return {\n content,\n model: json.model ?? this.model,\n usage: json.usage\n ? {\n promptTokens: json.usage.prompt_tokens,\n completionTokens: json.usage.completion_tokens,\n totalTokens: json.usage.prompt_tokens + json.usage.completion_tokens,\n }\n : undefined,\n };\n } finally {\n clearTimeout(timer);\n }\n }\n\n private async parseStream(\n body: ReadableStream<Uint8Array>,\n onChunk: (delta: string) => void,\n ): Promise<ChatResult> {\n const reader = body.getReader();\n const decoder = new TextDecoder();\n let content = '';\n let buffer = '';\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split('\\n');\n buffer = lines.pop() ?? '';\n for (const line of lines) {\n const trimmed = line.trim();\n if (!trimmed.startsWith('data:')) continue;\n const data = trimmed.slice(5).trim();\n if (data === '[DONE]') continue;\n try {\n const json = JSON.parse(data) as { choices?: Array<{ delta?: { content?: string } }> };\n const delta = json.choices?.[0]?.delta?.content ?? '';\n if (delta) {\n content += delta;\n onChunk(delta);\n }\n } catch {\n // 忽略无法解析的行\n }\n }\n }\n return { content, model: this.model };\n }\n\n private sleep(ms: number): Promise<void> {\n return new Promise((r) => setTimeout(r, ms));\n }\n}\n\n/** 便捷工厂 */\nexport function createOpenAICompatibleClient(config: OpenAICompatibleConfig): OpenAICompatibleClient {\n return new OpenAICompatibleClient(config);\n}"]}
|
|
@@ -82,17 +82,30 @@ function detectSpatial(text, geometryColumn = "geom") {
|
|
|
82
82
|
if (/公里|km|千米/.test(nearby[2])) radius *= 1e3;
|
|
83
83
|
return { relation: "dwithin", radius, geometryColumn };
|
|
84
84
|
}
|
|
85
|
+
const point = detectReferencePoint(text);
|
|
85
86
|
if (/缓冲区|缓冲|范围内|区域.{0,2}内/.test(text)) {
|
|
86
|
-
return { relation: "buffer", geometryColumn };
|
|
87
|
+
return { relation: "buffer", point, geometryColumn };
|
|
87
88
|
}
|
|
88
|
-
if (
|
|
89
|
-
return { relation: "
|
|
89
|
+
if (/包含|覆盖|涵盖|包围|圈住/.test(text)) {
|
|
90
|
+
return { relation: "contains", point, geometryColumn };
|
|
91
|
+
}
|
|
92
|
+
if (/在.{0,2}内|内部|以内|之中/.test(text)) {
|
|
93
|
+
return { relation: "within", point, geometryColumn };
|
|
90
94
|
}
|
|
91
95
|
if (/相交|叠加|重叠|交叉/.test(text)) {
|
|
92
|
-
return { relation: "intersects", geometryColumn };
|
|
96
|
+
return { relation: "intersects", point, geometryColumn };
|
|
93
97
|
}
|
|
94
98
|
return null;
|
|
95
99
|
}
|
|
100
|
+
function detectReferencePoint(text) {
|
|
101
|
+
const m = text.match(/([1-2]?\d{2,3}(?:\.\d+)?)\s*[,,]\s*(\d{1,3}(?:\.\d+)?)/);
|
|
102
|
+
if (m) {
|
|
103
|
+
const lng = parseFloat(m[1]);
|
|
104
|
+
const lat = parseFloat(m[2]);
|
|
105
|
+
if (lng >= -180 && lng <= 180 && lat >= -90 && lat <= 90) return [lng, lat];
|
|
106
|
+
}
|
|
107
|
+
return void 0;
|
|
108
|
+
}
|
|
96
109
|
function quoteValue(v) {
|
|
97
110
|
return typeof v === "string" ? `'${v.replace(/'/g, "''")}'` : String(v);
|
|
98
111
|
}
|
|
@@ -102,14 +115,26 @@ function buildWhere(conditions, spatial) {
|
|
|
102
115
|
parts.push(`${c.field} ${c.operator} ${quoteValue(c.value)}`);
|
|
103
116
|
}
|
|
104
117
|
if (spatial) {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
118
|
+
const col = spatial.geometryColumn;
|
|
119
|
+
const refGeom = spatial.referenceColumn ? spatial.referenceColumn : spatial.point ? `ST_Buffer(ST_SetSRID(ST_MakePoint(${spatial.point[0]}, ${spatial.point[1]}), 4326), ${spatial.radius ?? 0})` : null;
|
|
120
|
+
switch (spatial.relation) {
|
|
121
|
+
case "dwithin":
|
|
122
|
+
if (spatial.point && spatial.radius !== void 0) {
|
|
123
|
+
parts.push(`ST_DWithin(${col}, ST_SetSRID(ST_MakePoint(${spatial.point[0]}, ${spatial.point[1]}), 4326), ${spatial.radius})`);
|
|
124
|
+
}
|
|
125
|
+
break;
|
|
126
|
+
case "buffer":
|
|
127
|
+
if (refGeom) parts.push(`ST_Intersects(${col}, ${refGeom})`);
|
|
128
|
+
break;
|
|
129
|
+
case "within":
|
|
130
|
+
if (refGeom) parts.push(`ST_Within(${col}, ${refGeom})`);
|
|
131
|
+
break;
|
|
132
|
+
case "contains":
|
|
133
|
+
if (refGeom) parts.push(`ST_Contains(${refGeom}, ${col})`);
|
|
134
|
+
break;
|
|
135
|
+
case "intersects":
|
|
136
|
+
if (refGeom) parts.push(`ST_Intersects(${col}, ${refGeom})`);
|
|
137
|
+
break;
|
|
113
138
|
}
|
|
114
139
|
}
|
|
115
140
|
return parts.length > 0 ? `WHERE ${parts.join(" AND ")}` : "";
|
|
@@ -119,9 +144,12 @@ function generatePostGISQuery(text, opts = {}) {
|
|
|
119
144
|
const geometryColumn = opts.geometryColumn ?? "geom";
|
|
120
145
|
const table = detectTable(text);
|
|
121
146
|
const spatial = detectSpatial(text, geometryColumn);
|
|
122
|
-
if (spatial &&
|
|
147
|
+
if (spatial && !spatial.point) {
|
|
123
148
|
spatial.point = center;
|
|
124
149
|
}
|
|
150
|
+
if (spatial && spatial.relation !== "dwithin" && spatial.radius === void 0) {
|
|
151
|
+
spatial.radius = 1e3;
|
|
152
|
+
}
|
|
125
153
|
const conditions = [];
|
|
126
154
|
const field = detectField(text);
|
|
127
155
|
if (field) {
|
|
@@ -344,5 +372,5 @@ var LlmNlpg = class {
|
|
|
344
372
|
};
|
|
345
373
|
|
|
346
374
|
export { DEFAULT_ALLOWED_TABLES, LlmNlpg, detectField, detectSpatial, detectTable, detectValue, generatePostGISQuery, nlpgQuery, parameterize, validateSql };
|
|
347
|
-
//# sourceMappingURL=chunk-
|
|
348
|
-
//# sourceMappingURL=chunk-
|
|
375
|
+
//# sourceMappingURL=chunk-WVEICADA.js.map
|
|
376
|
+
//# sourceMappingURL=chunk-WVEICADA.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/nlpg/sqlGenerator.ts","../src/nlpg/sqlValidator.ts","../src/nlpg/nlpg.ts","../src/nlpg/llmNlpg.ts"],"names":[],"mappings":"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","file":"chunk-WVEICADA.js","sourcesContent":["/**\n * NLPG 自然语言 → PostGIS SQL 生成(PRD phase-0 §5.7 N-1/N-2/N-3)\n *\n * 支持:\n * - 属性过滤(字段 + 操作符 + 值,AND/OR 组合)\n * - 空间关系(ST_DWithin / ST_Within / ST_Intersects / ST_Contains / ST_Buffer)\n * - 混合查询(空间 + 属性)\n *\n * v1 采用「字段词典 + 规则匹配」方案(不依赖大模型),SQL 生成后必须经\n * `validateSql` 安全校验层通过方可执行(见 sqlValidator.ts)。\n */\n\n// ============================================================\n// 一、类型定义\n// ============================================================\nexport type NlpgIntent = 'attribute_filter' | 'spatial_nearby' | 'spatial_within' | 'mixed' | 'unknown';\n\nexport type Operator = '>' | '<' | '>=' | '<=' | '=' | '!=' | 'LIKE';\n\nexport interface AttributeCondition {\n /** 字段名(白名单字段) */\n field: string;\n operator: Operator;\n /** 字面量值 */\n value: string | number;\n}\n\nexport interface SpatialCondition {\n /** 空间关系 */\n relation: 'dwithin' | 'within' | 'intersects' | 'contains' | 'buffer';\n /** 参考点 [lng, lat]:within/intersects/contains/buffer/dwithin 的参考几何中心 */\n point?: [number, number];\n /** 缓冲/距离半径(米,dwithin/buffer 及以参考点表达的 within·intersects·contains 用) */\n radius?: number;\n /** 参考几何列名(进阶:与某字段几何做空间关系,缺省时用 point 构造缓冲圆) */\n referenceColumn?: string;\n /** 目标几何字段 */\n geometryColumn: string;\n}\n\nexport interface GeneratedQuery {\n intent: NlpgIntent;\n /** 目标表(白名单表) */\n table: string;\n /** 属性条件 */\n conditions: AttributeCondition[];\n /** 空间条件 */\n spatial: SpatialCondition | null;\n /** 生成的 SQL */\n sql: string;\n /** 置信度 0-1 */\n confidence: number;\n}\n\n// ============================================================\n// 二、字段与表词典\n// ============================================================\n/** 字段别名 → 规范字段名 */\nconst FIELD_ALIASES: Array<{ field: string; re: RegExp }> = [\n { field: 'material', re: /材质|材料|铸铁|钢管|PE|PVC|球墨|HDPE|铜管/ },\n { field: 'pressure', re: /压力|水压|气压|MPa/ },\n { field: 'diameter', re: /管径|直径|口径|DN/ },\n { field: 'status', re: /状态|运行|停运|故障|检修/ },\n { field: 'install_date', re: /安装日期|投运|建成|敷设/ },\n { field: 'age', re: /年限|使用.{0,2}年|超过.{0,2}年|.{0,2}年以上|年代/ },\n { field: 'voltage', re: /电压|kV|kv|伏/ },\n { field: 'load_rate', re: /负载率|负荷率|利用率|负载|负荷/ },\n { field: 'fault_rate', re: /故障率/ },\n { field: 'flow_rate', re: /流量|流速/ },\n { field: 'water_level', re: /水位/ },\n { field: 'storage_rate', re: /蓄水率|库容/ },\n { field: 'rsrp', re: /RSRP|信号强度|信号/ },\n];\n\n/** 表别名 → 规范表名 */\nconst TABLE_ALIASES: Array<{ table: string; re: RegExp }> = [\n { table: 'pipelines', re: /管段|管线|管道|管网|燃气管|供水管|排水管|供热管|电力管|通信管/ },\n { table: 'nodes', re: /节点|阀门|泵站|表|井|闸/ },\n { table: 'users', re: /用户|居民|小区|住户|建筑/ },\n { table: 'schools', re: /学校|小学|中学|大学|幼儿园/ },\n { table: 'hospitals', re: /医院|卫生院|诊所/ },\n { table: 'substations', re: /变电站|配变|台区|电站/ },\n { table: 'base_stations', re: /基站|宏站|微站|室分/ },\n { table: 'rivers', re: /河流|水系|河段|支流/ },\n { table: 'reservoirs', re: /水库|大坝/ },\n { table: 'alarms', re: /报警|告警|警报/ },\n { table: 'pois', re: /POI|兴趣点|场所|设施/ },\n];\n\n/** 字段值词典(材质/状态等) */\nconst VALUE_DICT: Array<{ field: string; value: string; re: RegExp }> = [\n { field: 'material', value: 'cast_iron', re: /铸铁/ },\n { field: 'material', value: 'ductile_iron', re: /球墨/ },\n { field: 'material', value: 'steel', re: /钢(?!筋)/ },\n { field: 'material', value: 'pe', re: /PE/ },\n { field: 'material', value: 'pvc', re: /PVC/ },\n { field: 'material', value: 'hdpe', re: /HDPE/ },\n { field: 'status', value: 'normal', re: /正常/ },\n { field: 'status', value: 'fault', re: /故障/ },\n { field: 'status', value: 'maintenance', re: /检修|维修/ },\n { field: 'status', value: 'aging', re: /老化/ },\n];\n\n/** 操作符识别 */\nfunction detectOperator(text: string): Operator {\n if (/超过|高于|大于|超出|以上|不小于/.test(text)) return text.includes('以上') || text.includes('不小于') ? '>=' : '>';\n if (/低于|小于|不到|以下|不足/.test(text)) return text.includes('以下') ? '<=' : '<';\n if (/等于|正好|恰好|为\\s*\\d/.test(text)) return '=';\n if (/不是|非|不等于/.test(text)) return '!=';\n if (/包含|含有|含/.test(text)) return 'LIKE';\n return '=';\n}\n\n// ============================================================\n// 三、表名 / 字段 / 值识别\n// ============================================================\nexport function detectTable(text: string): string {\n for (const t of TABLE_ALIASES) {\n if (t.re.test(text)) return t.table;\n }\n return 'pois';\n}\n\nexport function detectField(text: string): string | null {\n for (const f of FIELD_ALIASES) {\n if (f.re.test(text)) return f.field;\n }\n return null;\n}\n\nexport function detectValue(text: string, field: string): string | number | null {\n // 词典值(材质/状态)\n for (const v of VALUE_DICT) {\n if (v.field === field && v.re.test(text)) return v.value;\n }\n // 数值 + 单位\n const numMatch = text.match(/(\\d+(?:\\.\\d+)?)\\s*(MPa|mpa|kV|kv|米|m|公里|km|%|%|方|立方米)?/);\n if (numMatch) {\n let value = parseFloat(numMatch[1]);\n const unit = numMatch[2] ?? '';\n if (/公里|km/.test(unit)) value *= 1000; // 距离转米\n if (/%|%/.test(unit)) value /= 100; // 百分比转 0-1\n return value;\n }\n // 日期\n const dateMatch = text.match(/(\\d{4}-\\d{2}-\\d{2}|\\d{4}年\\d{1,2}月)/);\n if (dateMatch) return dateMatch[1];\n return null;\n}\n\n// ============================================================\n// 四、空间关系识别\n// ============================================================\nexport function detectSpatial(text: string, geometryColumn = 'geom'): SpatialCondition | null {\n // N 米/公里 内/附近/周边:dwithin(带参考半径)\n const nearby = text.match(/(\\d+(?:\\.\\d+)?)\\s*(米|m|公里|km|千米)\\s*(?:内|以内|范围内|附近|周边)/);\n if (nearby) {\n let radius = parseFloat(nearby[1]);\n if (/公里|km|千米/.test(nearby[2])) radius *= 1000;\n return { relation: 'dwithin', radius, geometryColumn };\n }\n const point = detectReferencePoint(text);\n // 缓冲/范围内:用参考点(或默认中心)构造缓冲圆 → ST_Intersects\n if (/缓冲区|缓冲|范围内|区域.{0,2}内/.test(text)) {\n return { relation: 'buffer', point, geometryColumn };\n }\n // 包含/覆盖(参考几何包含要素):ST_Contains(refGeom, geom)\n if (/包含|覆盖|涵盖|包围|圈住/.test(text)) {\n return { relation: 'contains', point, geometryColumn };\n }\n // 在…内部/以内(要素在某几何内):ST_Within(geom, refGeom)\n if (/在.{0,2}内|内部|以内|之中/.test(text)) {\n return { relation: 'within', point, geometryColumn };\n }\n // 相交/叠加:ST_Intersects(geom, refGeom)\n if (/相交|叠加|重叠|交叉/.test(text)) {\n return { relation: 'intersects', point, geometryColumn };\n }\n return null;\n}\n\n/** 从文本中识别\"某点/坐标/某设施\"作为参考几何中心;当前仅支持显式经纬度,否则返回 undefined(由 generate 补中心) */\nfunction detectReferencePoint(text: string): [number, number] | undefined {\n const m = text.match(/([1-2]?\\d{2,3}(?:\\.\\d+)?)\\s*[,,]\\s*(\\d{1,3}(?:\\.\\d+)?)/);\n if (m) {\n const lng = parseFloat(m[1]);\n const lat = parseFloat(m[2]);\n if (lng >= -180 && lng <= 180 && lat >= -90 && lat <= 90) return [lng, lat];\n }\n return undefined;\n}\n\n// ============================================================\n// 五、SQL 组装\n// ============================================================\nfunction quoteValue(v: string | number): string {\n return typeof v === 'string' ? `'${v.replace(/'/g, \"''\")}'` : String(v);\n}\n\nfunction buildWhere(conditions: AttributeCondition[], spatial: SpatialCondition | null): string {\n const parts: string[] = [];\n for (const c of conditions) {\n parts.push(`${c.field} ${c.operator} ${quoteValue(c.value)}`);\n }\n if (spatial) {\n const col = spatial.geometryColumn;\n // 参考几何:优先 referenceColumn(字段几何),否则用 point 构造缓冲圆\n const refGeom = spatial.referenceColumn\n ? spatial.referenceColumn\n : spatial.point\n ? `ST_Buffer(ST_SetSRID(ST_MakePoint(${spatial.point[0]}, ${spatial.point[1]}), 4326), ${spatial.radius ?? 0})`\n : null;\n switch (spatial.relation) {\n case 'dwithin':\n if (spatial.point && spatial.radius !== undefined) {\n parts.push(`ST_DWithin(${col}, ST_SetSRID(ST_MakePoint(${spatial.point[0]}, ${spatial.point[1]}), 4326), ${spatial.radius})`);\n }\n break;\n case 'buffer':\n // 缓冲区内:以参考几何/参考点缓冲圆判断相交\n if (refGeom) parts.push(`ST_Intersects(${col}, ${refGeom})`);\n break;\n case 'within':\n // 要素在参考几何内\n if (refGeom) parts.push(`ST_Within(${col}, ${refGeom})`);\n break;\n case 'contains':\n // 参考几何包含要素\n if (refGeom) parts.push(`ST_Contains(${refGeom}, ${col})`);\n break;\n case 'intersects':\n // 要素与参考几何相交\n if (refGeom) parts.push(`ST_Intersects(${col}, ${refGeom})`);\n break;\n }\n }\n return parts.length > 0 ? `WHERE ${parts.join(' AND ')}` : '';\n}\n\n// ============================================================\n// 六、主入口\n// ============================================================\nexport interface GenerateOptions {\n /** 参考点(空间查询用,默认武汉中心) */\n center?: [number, number];\n /** 目标几何字段 */\n geometryColumn?: string;\n}\n\nexport function generatePostGISQuery(text: string, opts: GenerateOptions = {}): GeneratedQuery {\n const center = opts.center ?? [114.305, 30.593];\n const geometryColumn = opts.geometryColumn ?? 'geom';\n\n const table = detectTable(text);\n const spatial = detectSpatial(text, geometryColumn);\n // 空间查询需要参考点:默认使用中心坐标(buffer/within/intersects/contains 以参考点缓冲圆表达)\n if (spatial && !spatial.point) {\n spatial.point = center;\n }\n // buffer/within/intersects/contains 缺省半径时给一个合理缓冲半径\n if (spatial && spatial.relation !== 'dwithin' && spatial.radius === undefined) {\n spatial.radius = 1000;\n }\n\n // 属性条件提取\n const conditions: AttributeCondition[] = [];\n const field = detectField(text);\n if (field) {\n const value = detectValue(text, field);\n if (value !== null) {\n conditions.push({ field, operator: detectOperator(text), value });\n }\n }\n\n // 意图判定\n let intent: NlpgIntent;\n if (spatial && conditions.length > 0) intent = 'mixed';\n else if (spatial) intent = spatial.relation === 'dwithin' ? 'spatial_nearby' : 'spatial_within';\n else if (conditions.length > 0) intent = 'attribute_filter';\n else intent = 'unknown';\n\n const where = buildWhere(conditions, spatial);\n const sql = `SELECT * FROM ${table} ${where}`.trim();\n\n // 置信度\n let confidence = 0.3;\n if (conditions.length > 0) confidence += 0.3;\n if (spatial) confidence += 0.3;\n if (table !== 'pois') confidence += 0.1;\n confidence = Math.min(0.95, confidence);\n\n return { intent, table, conditions, spatial, sql, confidence };\n}\n","/**\n * NLPG SQL 安全校验层(PRD phase-0 §5.7 N-5 / §5.7.3)\n *\n * 所有 LLM/规则生成的 SQL 必须经过以下校验方可执行:\n * 1. 语法校验(引号/括号配对、非空)\n * 2. 白名单表检查(只允许查询授权表)\n * 3. 危险操作拦截(DROP/DELETE/UPDATE/INSERT/ALTER/TRUNCATE/GRANT 等全部拒绝)\n * 4. 仅允许 SELECT 只读查询\n * 5. 参数化占位(防止 SQL 注入的字面量转义)\n *\n * 纯函数,可在 Node/浏览器两侧运行。\n */\n\nexport interface ValidationIssue {\n severity: 'error' | 'warning';\n rule: string;\n message: string;\n}\n\nexport interface ValidationResult {\n /** 是否通过(无 error 级问题) */\n valid: boolean;\n issues: ValidationIssue[];\n}\n\n/** 危险关键字(拒绝执行) */\nconst DANGEROUS_KEYWORDS = [\n 'DROP', 'DELETE', 'UPDATE', 'INSERT', 'ALTER', 'TRUNCATE', 'GRANT', 'REVOKE',\n 'MERGE', 'CREATE', 'REPLACE', 'EXEC', 'EXECUTE', 'CALL', 'COPY', 'LOAD',\n 'INTO', 'SET', 'UNION', 'ATTACH', 'DETACH', 'PRAGMA', 'VACUUM', 'REINDEX',\n];\n\n/** 危险注释/注入特征 */\nconst INJECTION_PATTERNS = [\n /--/, // SQL 注释\n /\\/\\*/, // 块注释\n /;\\s*(DROP|DELETE|UPDATE|INSERT|ALTER|TRUNCATE)/i, // 多语句注入\n /'\\s*OR\\s*'/i, // ' OR ' 恒真注入(含 ' OR '1'='1')\n /\"\\s*OR\\s*\"/i, // \" OR \" 恒真注入\n /\\bOR\\s+\\d+\\s*=\\s*\\d+\\b/i, // OR 1=1 恒真\n /\\bOR\\s+'\\w*'\\s*=\\s*'\\w*'/i, // OR 'a'='a' 恒真\n /UNION\\s+SELECT/i,\n];\n\n/** 默认白名单表(只读授权) */\nexport const DEFAULT_ALLOWED_TABLES = [\n 'pipelines', 'nodes', 'users', 'schools', 'hospitals', 'substations',\n 'base_stations', 'rivers', 'reservoirs', 'alarms', 'pois',\n];\n\n/** 允许的空间函数 */\nconst ALLOWED_SPATIAL_FUNCTIONS = [\n 'ST_DWithin', 'ST_Within', 'ST_Intersects', 'ST_Contains', 'ST_Buffer',\n 'ST_MakePoint', 'ST_SetSRID', 'ST_Distance', 'ST_AsGeoJSON', 'ST_Transform',\n];\n\n/**\n * 校验 SQL。\n * @param sql 待校验 SQL\n * @param allowedTables 白名单表(默认 DEFAULT_ALLOWED_TABLES)\n */\nexport function validateSql(sql: string, allowedTables: string[] = DEFAULT_ALLOWED_TABLES): ValidationResult {\n const issues: ValidationIssue[] = [];\n const upper = sql.toUpperCase().trim();\n\n // 1) 非空\n if (!sql.trim()) {\n issues.push({ severity: 'error', rule: 'non_empty', message: 'SQL 不能为空' });\n return { valid: false, issues };\n }\n\n // 2) 必须以 SELECT 开头(只读)\n if (!upper.startsWith('SELECT')) {\n issues.push({ severity: 'error', rule: 'read_only', message: '仅允许 SELECT 只读查询' });\n }\n\n // 3) 危险操作拦截\n for (const kw of DANGEROUS_KEYWORDS) {\n // 用词边界匹配,避免误伤字段名(如 \"status\" 含 \"set\" 的误判)\n const re = new RegExp(`\\\\b${kw}\\\\b`, 'i');\n if (re.test(sql) && !(kw === 'SET' && /ST_SetSRID/i.test(sql))) {\n issues.push({ severity: 'error', rule: 'dangerous_keyword', message: `检测到危险关键字 ${kw}` });\n }\n }\n\n // 4) 注入特征拦截\n for (const p of INJECTION_PATTERNS) {\n if (p.test(sql)) {\n issues.push({ severity: 'error', rule: 'injection', message: '检测到 SQL 注入特征' });\n }\n }\n\n // 5) 白名单表检查\n const fromMatch = upper.match(/FROM\\s+([A-Za-z_][\\w]*)/);\n if (fromMatch) {\n const table = fromMatch[1].toLowerCase();\n if (!allowedTables.includes(table)) {\n issues.push({ severity: 'error', rule: 'table_whitelist', message: `表 ${table} 不在授权白名单内` });\n }\n }\n\n // 6) 引号/括号配对\n const singleQuotes = (sql.match(/'/g) ?? []).length;\n if (singleQuotes % 2 !== 0) {\n issues.push({ severity: 'error', rule: 'syntax', message: '单引号未配对' });\n }\n const openParen = (sql.match(/\\(/g) ?? []).length;\n const closeParen = (sql.match(/\\)/g) ?? []).length;\n if (openParen !== closeParen) {\n issues.push({ severity: 'error', rule: 'syntax', message: '括号未配对' });\n }\n\n // 7) 空间函数白名单检查(若用到空间函数)\n for (const fn of ['ST_']) {\n const fnMatch = sql.match(/ST_(\\w+)/g) ?? [];\n for (const m of fnMatch) {\n if (!ALLOWED_SPATIAL_FUNCTIONS.includes(m)) {\n issues.push({ severity: 'error', rule: 'spatial_whitelist', message: `空间函数 ${m} 不在白名单内` });\n }\n }\n }\n\n return { valid: issues.every((i) => i.severity !== 'error'), issues };\n}\n\n/** 参数化:把 SQL 中的字符串字面量替换为 $n 占位符(防注入) */\nexport function parameterize(sql: string): { sql: string; params: string[] } {\n const params: string[] = [];\n const out = sql.replace(/'([^']*)'/g, (_m, v: string) => {\n params.push(v);\n return `$${params.length}`;\n });\n return { sql: out, params };\n}\n","/**\n * NLPG v1 主入口(PRD phase-0 §5.7)\n *\n * 自然语言 → PostGIS SQL → 安全校验 → 可执行结果。\n *\n * 用法:\n * const result = nlpgQuery('查出使用超过 20 年的铸铁燃气管');\n * result.sql // 生成的 SQL\n * result.valid // 是否通过安全校验\n * result.validation // 校验详情\n */\n\nimport { generatePostGISQuery, type GeneratedQuery, type GenerateOptions } from './sqlGenerator';\nimport { validateSql, parameterize, type ValidationResult } from './sqlValidator';\n\nexport interface NlpgResult {\n /** 生成的查询 */\n query: GeneratedQuery;\n /** 是否通过安全校验 */\n valid: boolean;\n /** 校验详情 */\n validation: ValidationResult;\n /** 参数化后的 SQL($n 占位符) */\n parameterized?: { sql: string; params: string[] };\n}\n\n/**\n * 自然语言查询入口。\n */\nexport function nlpgQuery(text: string, opts: GenerateOptions = {}): NlpgResult {\n const query = generatePostGISQuery(text, opts);\n const validation = validateSql(query.sql);\n const valid = validation.valid;\n const parameterized = valid ? parameterize(query.sql) : undefined;\n\n return { query, valid, validation, parameterized };\n}\n\nexport type { GeneratedQuery, GenerateOptions };\nexport { generatePostGISQuery, validateSql, parameterize };\n","/**\n * NLPG LLM 模式(PRD phase-0 §5.7 N-6「LLM 生成 SQL」)\n *\n * 在规则引擎(sqlGenerator.ts)基础上,接入 DeepSeek 实现:\n * - 复杂自然语言 → PostGIS SQL(突破固定字段/空间模板)\n * - LLM 优先,失败自动降级规则引擎\n * - 生成的 SQL 必经 sqlValidator 安全校验层方可执行\n */\n\nimport type { DeepSeekClient, ChatMessage } from '../llm/deepseek';\nimport { generatePostGISQuery, type GeneratedQuery } from './sqlGenerator';\nimport { validateSql, parameterize, DEFAULT_ALLOWED_TABLES } from './sqlValidator';\n\nexport interface LlmNlpgConfig {\n client: DeepSeekClient;\n enabled?: boolean;\n /** 授权表白名单(传给校验层) */\n allowedTables?: string[];\n}\n\ninterface LlmSqlOutput {\n sql?: string;\n table?: string;\n intent?: string;\n}\n\nconst SYSTEM_PROMPT = `你是\"草果地图\"管网自然语言查询助手。用户用中文描述数据查询需求,你生成 PostGIS SQL(仅 SELECT)。\n\n规则:\n1. 只返回 JSON:{\"sql\":\"SELECT ...\",\"table\":\"...\",\"intent\":\"...\"}\n2. 表名只能来自以下白名单:${DEFAULT_ALLOWED_TABLES.join(', ')}\n3. 空间查询可用 ST_DWithin / ST_Within / ST_Intersects / ST_Contains / ST_Buffer / ST_MakePoint / ST_SetSRID / ST_Distance / ST_AsGeoJSON / ST_Transform\n4. 只生成 SELECT 语句,禁止 DROP/DELETE/UPDATE/INSERT/ALTER 等写操作\n5. 字段名使用 snake_case,坐标使用 WGS84(SRID 4326)\n6. 不要输出 JSON 以外的任何文字。`;\n\nexport class LlmNlpg {\n private client: DeepSeekClient;\n private enabled: boolean;\n private allowedTables: string[];\n\n constructor(config: LlmNlpgConfig) {\n this.client = config.client;\n this.enabled = config.enabled ?? true;\n this.allowedTables = config.allowedTables ?? DEFAULT_ALLOWED_TABLES;\n }\n\n /**\n * 自然语言查询。\n * LLM 优先,失败或校验不过时降级到规则引擎。\n */\n async query(text: string): Promise<{ query: GeneratedQuery; valid: boolean; parameterized?: { sql: string; params: string[] } }> {\n if (this.enabled) {\n try {\n const llmResult = await this.queryWithLlm(text);\n if (llmResult) return llmResult;\n } catch {\n // 降级规则引擎\n }\n }\n return this.queryWithRules(text);\n }\n\n private async queryWithLlm(\n text: string\n ): Promise<{ query: GeneratedQuery; valid: boolean; parameterized?: { sql: string; params: string[] } } | null> {\n const messages: ChatMessage[] = [\n { role: 'system', content: SYSTEM_PROMPT },\n { role: 'user', content: text },\n ];\n const { data } = await this.client.chatJson<LlmSqlOutput>(messages);\n const sql = (data.sql ?? '').trim();\n if (!sql) return null;\n\n // 必经安全校验层\n const validation = validateSql(sql, this.allowedTables);\n if (!validation.valid) return null;\n\n const query: GeneratedQuery = {\n intent: 'mixed',\n table: data.table ?? 'pois',\n conditions: [],\n spatial: null,\n sql,\n confidence: 0.9,\n };\n return { query, valid: true, parameterized: parameterize(sql) };\n }\n\n private queryWithRules(\n text: string\n ): { query: GeneratedQuery; valid: boolean; parameterized?: { sql: string; params: string[] } } {\n const query = generatePostGISQuery(text);\n const validation = validateSql(query.sql, this.allowedTables);\n return {\n query,\n valid: validation.valid,\n parameterized: validation.valid ? parameterize(query.sql) : undefined,\n };\n }\n}\n"]}
|
package/dist/copilot/index.d.cts
CHANGED
package/dist/copilot/index.d.ts
CHANGED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DeepSeek LLM 客户端(PRD phase-0 §5.5 / §5.7 的 v1 后端能力)
|
|
3
|
+
*
|
|
4
|
+
* DeepSeek API 与 OpenAI Chat Completions 协议兼容:
|
|
5
|
+
* base_url: https://api.deepseek.com
|
|
6
|
+
* 模型: deepseek-chat(通用对话)/ deepseek-reasoner(深度推理)
|
|
7
|
+
* 鉴权: Authorization: Bearer <API_KEY>
|
|
8
|
+
*
|
|
9
|
+
* 设计:
|
|
10
|
+
* - 依赖注入的 fetch(浏览器/Node 均可运行,Node 18+ 原生 fetch)
|
|
11
|
+
* - 自动重试(指数退避)
|
|
12
|
+
* - 失败降级(可传入 fallback,规则引擎兜底)
|
|
13
|
+
* - 支持流式(onChunk 回调)与 JSON 输出
|
|
14
|
+
*/
|
|
15
|
+
interface DeepSeekConfig {
|
|
16
|
+
/** API Key */
|
|
17
|
+
apiKey: string;
|
|
18
|
+
/** 基础地址,默认 https://api.deepseek.com */
|
|
19
|
+
baseUrl?: string;
|
|
20
|
+
/** 模型,默认 deepseek-chat */
|
|
21
|
+
model?: 'deepseek-chat' | 'deepseek-reasoner' | (string & {});
|
|
22
|
+
/** 温度 0-2 */
|
|
23
|
+
temperature?: number;
|
|
24
|
+
/** 最大 token */
|
|
25
|
+
maxTokens?: number;
|
|
26
|
+
/** 超时(ms),默认 30s */
|
|
27
|
+
timeoutMs?: number;
|
|
28
|
+
/** 重试次数,默认 2 */
|
|
29
|
+
retries?: number;
|
|
30
|
+
/** 注入 fetch(测试/Node 环境),默认 globalThis.fetch */
|
|
31
|
+
fetchImpl?: typeof fetch;
|
|
32
|
+
}
|
|
33
|
+
interface ChatMessage {
|
|
34
|
+
role: 'system' | 'user' | 'assistant';
|
|
35
|
+
content: string;
|
|
36
|
+
}
|
|
37
|
+
interface ChatOptions {
|
|
38
|
+
/** 是否启用 JSON 模式(要求模型返回纯 JSON) */
|
|
39
|
+
json?: boolean;
|
|
40
|
+
/** 流式回调 */
|
|
41
|
+
onChunk?: (delta: string) => void;
|
|
42
|
+
}
|
|
43
|
+
interface ChatResult {
|
|
44
|
+
/** 完整回复文本 */
|
|
45
|
+
content: string;
|
|
46
|
+
/** 消耗 token */
|
|
47
|
+
usage?: {
|
|
48
|
+
promptTokens: number;
|
|
49
|
+
completionTokens: number;
|
|
50
|
+
totalTokens: number;
|
|
51
|
+
};
|
|
52
|
+
/** 使用的模型 */
|
|
53
|
+
model: string;
|
|
54
|
+
}
|
|
55
|
+
/** 已解析的 JSON 回复 */
|
|
56
|
+
interface JsonChatResult<T> {
|
|
57
|
+
/** 解析出的 JSON 对象 */
|
|
58
|
+
data: T;
|
|
59
|
+
/** 原始文本 */
|
|
60
|
+
raw: string;
|
|
61
|
+
model: string;
|
|
62
|
+
}
|
|
63
|
+
declare class DeepSeekClient {
|
|
64
|
+
private config;
|
|
65
|
+
private lastRequestId;
|
|
66
|
+
constructor(config: DeepSeekConfig);
|
|
67
|
+
/** 发起一次聊天补全 */
|
|
68
|
+
chat(messages: ChatMessage[], opts?: ChatOptions): Promise<ChatResult>;
|
|
69
|
+
/** 聊天 + JSON 解析 */
|
|
70
|
+
chatJson<T>(messages: ChatMessage[]): Promise<JsonChatResult<T>>;
|
|
71
|
+
private requestWithRetry;
|
|
72
|
+
private isRetryable;
|
|
73
|
+
private requestOnce;
|
|
74
|
+
/** 解析 SSE 流式响应 */
|
|
75
|
+
private parseStream;
|
|
76
|
+
private sleep;
|
|
77
|
+
}
|
|
78
|
+
/** 便捷工厂:创建默认配置的客户端 */
|
|
79
|
+
declare function createDeepSeekClient(config: DeepSeekConfig): DeepSeekClient;
|
|
80
|
+
|
|
81
|
+
export { type ChatMessage as C, DeepSeekClient as D, type JsonChatResult as J, type ChatOptions as a, type ChatResult as b, type DeepSeekConfig as c, createDeepSeekClient as d };
|