chenbhao commited on
Commit
09db81c
·
1 Parent(s): c17e1b4

chore: clean WebSearchTool log test

Browse files
log.md DELETED
@@ -1,973 +0,0 @@
1
-
2
- [2026-04-08T06:49:39.608Z] [DEBUG] 🔍 isEnabled() = true
3
- [2026-04-08T06:49:39.613Z] [DEBUG] 🔍 isEnabled() = true
4
- [2026-04-08T06:49:39.630Z] [DEBUG] 🔍 isEnabled() = true
5
- [2026-04-08T06:49:39.630Z] [DEBUG] 🔍 isEnabled() = true
6
- [2026-04-08T06:49:55.581Z] [DEBUG] 🔍 validateInput() 被调用
7
- [2026-04-08T06:49:55.581Z] [DEBUG] 🔍 输入参数
8
- ```json
9
- {
10
- "query": "milet 最新动态 2026"
11
- }
12
- ```
13
- [2026-04-08T06:49:55.583Z] [INFO] 🔵 验证通过
14
- [2026-04-08T06:49:55.584Z] [INFO] 🔵 WebSearchTool.call() 被调用
15
- [2026-04-08T06:49:55.584Z] [DEBUG] 🔍 输入参数
16
- ```json
17
- {
18
- "query": "milet 最新动态 2026"
19
- }
20
- ```
21
- [2026-04-08T06:49:55.584Z] [INFO] 🔵 查询参数验证通过: "milet 最新动态 2026"
22
- [2026-04-08T06:49:55.584Z] [DEBUG] 🔍 发送进度更新
23
- [2026-04-08T06:49:55.584Z] [INFO] 🔵 调用 searchSearXNG()
24
- [2026-04-08T06:49:55.584Z] [INFO] 🔵 开始搜索 SearXNG
25
- [2026-04-08T06:49:55.584Z] [DEBUG] 🔍 搜索查询
26
- ```
27
- milet 最新动态 2026
28
- ```
29
- [2026-04-08T06:49:55.584Z] [DEBUG] 🔍 请求 URL
30
- ```
31
- http://localhost:8080/search?q=milet+%E6%9C%80%E6%96%B0%E5%8A%A8%E6%80%81+2026&format=json
32
- ```
33
- [2026-04-08T06:49:55.584Z] [INFO] 🔵 发起 HTTP 请求 (10秒超时)
34
- [2026-04-08T06:49:58.302Z] [INFO] 🔵 HTTP 响应状态
35
- ```
36
- 200 OK (2718ms)
37
- ```
38
- [2026-04-08T06:49:58.302Z] [DEBUG] 🔍 响应头 Content-Type
39
- ```
40
- application/json
41
- ```
42
- [2026-04-08T06:49:58.302Z] [INFO] 🔵 解析 JSON 响应
43
- [2026-04-08T06:49:58.303Z] [INFO] 🔵 返回结果统计
44
- ```json
45
- {
46
- "query": "milet 最新动态 2026",
47
- "totalResults": 0,
48
- "resultsCount": 30
49
- }
50
- ```
51
- [2026-04-08T06:49:58.304Z] [INFO] 🔵 搜索完成,返回 10 条结果
52
- [2026-04-08T06:49:58.304Z] [INFO] 🔵 清洗搜索结果
53
- [2026-04-08T06:49:58.304Z] [DEBUG] 🔍 清洗结果
54
- ```json
55
- {
56
- "originalTitle": "本作より「The Story of Us」ライヴ映像を1曲まるごと公開! | milet",
57
- "cleanedTitle": "本作より「The Story of Us」ライヴ映像を1曲まるごと公開! | milet",
58
- "hasSnippet": true
59
- }
60
- ```
61
- [2026-04-08T06:49:58.304Z] [DEBUG] 🔍 清洗结果
62
- ```json
63
- {
64
- "originalTitle": "2026年6月17日(水) リリース決定!日本武道館公演「Ray of Water ...",
65
- "cleanedTitle": "2026年6月17日(水) リリース決定!日本武道館公演「Ray of Water ...",
66
- "hasSnippet": true
67
- }
68
- ```
69
- [2026-04-08T06:49:58.305Z] [DEBUG] 🔍 清洗结果
70
- ```json
71
- {
72
- "originalTitle": "milet Full Tour Schedule 2026 & 2027, Tour Dates & Concerts – Songkick",
73
- "cleanedTitle": "milet Full Tour Schedule 2026 & 2027, Tour Dates & Concerts – Songkick",
74
- "hasSnippet": true
75
- }
76
- ```
77
- [2026-04-08T06:49:58.305Z] [DEBUG] 🔍 清洗结果
78
- ```json
79
- {
80
- "originalTitle": "2026年3月4日(水) #milet New Sg 『The Story of Us ... - Instagram",
81
- "cleanedTitle": "2026年3月4日(水) #milet New Sg 『The Story of Us ... - Instagram",
82
- "hasSnippet": true
83
- }
84
- ```
85
- [2026-04-08T06:49:58.305Z] [DEBUG] 🔍 清洗结果
86
- ```json
87
- {
88
- "originalTitle": "milet 最新ライブBlu-ray&DVD『milet Live at Nippon Budokan \"Ray of ...",
89
- "cleanedTitle": "milet 最新ライブBlu-ray&DVD『milet Live at Nippon Budokan \"Ray of ...",
90
- "hasSnippet": true
91
- }
92
- ```
93
- [2026-04-08T06:49:58.305Z] [DEBUG] 🔍 清洗结果
94
- ```json
95
- {
96
- "originalTitle": "milet Tickets - 2026 Concert Tour Dates | Spotify",
97
- "cleanedTitle": "milet Tickets - 2026 Concert Tour Dates | Spotify",
98
- "hasSnippet": true
99
- }
100
- ```
101
- [2026-04-08T06:49:58.305Z] [DEBUG] 🔍 清洗结果
102
- ```json
103
- {
104
- "originalTitle": "「The Story of Us」いよいよ来週リリース!2026年秋スタート全国 ...",
105
- "cleanedTitle": "「The Story of Us」いよいよ来週リリース!2026年秋スタート全国 ...",
106
- "hasSnippet": true
107
- }
108
- ```
109
- [2026-04-08T06:49:58.305Z] [DEBUG] 🔍 清洗结果
110
- ```json
111
- {
112
- "originalTitle": "The Story of Us - song and lyrics by milet | Spotify",
113
- "cleanedTitle": "The Story of Us - song and lyrics by milet | Spotify",
114
- "hasSnippet": true
115
- }
116
- ```
117
- [2026-04-08T06:49:58.306Z] [DEBUG] 🔍 清洗结果
118
- ```json
119
- {
120
- "originalTitle": "milet in Tokyo, 2026-10-17 - Tickets & Event Details",
121
- "cleanedTitle": "milet in Tokyo, 2026-10-17 - Tickets & Event Details",
122
- "hasSnippet": true
123
- }
124
- ```
125
- [2026-04-08T06:49:58.306Z] [DEBUG] 🔍 清洗结果
126
- ```json
127
- {
128
- "originalTitle": "2026年6月17日(水) リリース決定!日本武道館公演「Ray of Water ...",
129
- "cleanedTitle": "2026年6月17日(水) リリース決定!日本武道館公演「Ray of Water ...",
130
- "hasSnippet": true
131
- }
132
- ```
133
- [2026-04-08T06:49:58.306Z] [INFO] 🔵 搜索成功完成
134
- [2026-04-08T06:49:58.306Z] [INFO] 🔵 最终统计
135
- ```json
136
- {
137
- "query": "milet 最新动态 2026",
138
- "resultsCount": 10,
139
- "durationSeconds": "2.722"
140
- }
141
- ```
142
- [2026-04-08T06:49:58.311Z] [DEBUG] 🔍 isEnabled() = true
143
- [2026-04-08T06:50:37.741Z] [DEBUG] 🔍 isEnabled() = true
144
- [2026-04-08T06:50:37.746Z] [DEBUG] 🔍 isEnabled() = true
145
- [2026-04-08T06:50:41.310Z] [DEBUG] 🔍 validateInput() 被调用
146
- [2026-04-08T06:50:41.310Z] [DEBUG] 🔍 输入参数
147
- ```json
148
- {
149
- "query": "2026 年 4 月 8 日 大新闻 头条"
150
- }
151
- ```
152
- [2026-04-08T06:50:41.310Z] [INFO] 🔵 验证通过
153
- [2026-04-08T06:50:41.311Z] [INFO] 🔵 WebSearchTool.call() 被调用
154
- [2026-04-08T06:50:41.311Z] [DEBUG] 🔍 输入参数
155
- ```json
156
- {
157
- "query": "2026 年 4 月 8 日 大新闻 头条"
158
- }
159
- ```
160
- [2026-04-08T06:50:41.311Z] [INFO] 🔵 查询参数验证通过: "2026 年 4 月 8 日 大新闻 头条"
161
- [2026-04-08T06:50:41.311Z] [DEBUG] 🔍 发送进度更新
162
- [2026-04-08T06:50:41.311Z] [INFO] 🔵 调用 searchSearXNG()
163
- [2026-04-08T06:50:41.312Z] [INFO] 🔵 开始搜索 SearXNG
164
- [2026-04-08T06:50:41.312Z] [DEBUG] 🔍 搜索查询
165
- ```
166
- 2026 年 4 月 8 日 大新闻 头条
167
- ```
168
- [2026-04-08T06:50:41.312Z] [DEBUG] 🔍 请求 URL
169
- ```
170
- http://localhost:8080/search?q=2026+%E5%B9%B4+4+%E6%9C%88+8+%E6%97%A5+%E5%A4%A7%E6%96%B0%E9%97%BB+%E5%A4%B4%E6%9D%A1&format=json
171
- ```
172
- [2026-04-08T06:50:41.312Z] [INFO] 🔵 发起 HTTP 请求 (10秒超时)
173
- [2026-04-08T06:50:43.041Z] [INFO] 🔵 HTTP 响应状态
174
- ```
175
- 200 OK (1729ms)
176
- ```
177
- [2026-04-08T06:50:43.041Z] [DEBUG] 🔍 响应头 Content-Type
178
- ```
179
- application/json
180
- ```
181
- [2026-04-08T06:50:43.041Z] [INFO] 🔵 解析 JSON 响应
182
- [2026-04-08T06:50:43.042Z] [INFO] 🔵 返回结果统计
183
- ```json
184
- {
185
- "query": "2026 年 4 月 8 日 大新闻 头条",
186
- "totalResults": 0,
187
- "resultsCount": 29
188
- }
189
- ```
190
- [2026-04-08T06:50:43.042Z] [INFO] 🔵 搜索完成,返回 10 条结果
191
- [2026-04-08T06:50:43.042Z] [INFO] 🔵 清洗搜索结果
192
- [2026-04-08T06:50:43.042Z] [DEBUG] 🔍 清洗结果
193
- ```json
194
- {
195
- "originalTitle": "岡田将生「現在育児でなかなか外食が難しいので楽しみにしてきました ...",
196
- "cleanedTitle": "岡田将生「現在育児でなかなか外食が難しいので楽しみにしてきました ...",
197
- "hasSnippet": true
198
- }
199
- ```
200
- [2026-04-08T06:50:43.042Z] [DEBUG] 🔍 清洗结果
201
- ```json
202
- {
203
- "originalTitle": "国际 - BBC News 中文",
204
- "cleanedTitle": "国际 - BBC News 中文",
205
- "hasSnippet": true
206
- }
207
- ```
208
- [2026-04-08T06:50:43.043Z] [DEBUG] 🔍 清洗结果
209
- ```json
210
- {
211
- "originalTitle": "首页头条--人民网",
212
- "cleanedTitle": "首页头条--人民网",
213
- "hasSnippet": true
214
- }
215
- ```
216
- [2026-04-08T06:50:43.043Z] [DEBUG] 🔍 清洗结果
217
- ```json
218
- {
219
- "originalTitle": "日经中文网--日本经济新闻中文版",
220
- "cleanedTitle": "日经中文网--日本经济新闻中文版",
221
- "hasSnippet": true
222
- }
223
- ```
224
- [2026-04-08T06:50:43.043Z] [DEBUG] 🔍 清洗结果
225
- ```json
226
- {
227
- "originalTitle": "人民日报-人民网",
228
- "cleanedTitle": "人民日报-人民网",
229
- "hasSnippet": true
230
- }
231
- ```
232
- [2026-04-08T06:50:43.043Z] [DEBUG] 🔍 清洗结果
233
- ```json
234
- {
235
- "originalTitle": "東京新聞デジタル:東京新聞のニュースサイト",
236
- "cleanedTitle": "東京新聞デジタル:東京新聞のニュースサイト",
237
- "hasSnippet": true
238
- }
239
- ```
240
- [2026-04-08T06:50:43.043Z] [DEBUG] 🔍 清洗结果
241
- ```json
242
- {
243
- "originalTitle": "國際 - BBC News 中文",
244
- "cleanedTitle": "國際 - BBC News 中文",
245
- "hasSnippet": true
246
- }
247
- ```
248
- [2026-04-08T06:50:43.043Z] [DEBUG] 🔍 清洗结果
249
- ```json
250
- {
251
- "originalTitle": "重要新闻_中华人民共和国外交部",
252
- "cleanedTitle": "重要新闻_中华人民共和国外交部",
253
- "hasSnippet": true
254
- }
255
- ```
256
- [2026-04-08T06:50:43.043Z] [DEBUG] 🔍 清洗结果
257
- ```json
258
- {
259
- "originalTitle": "【A1新闻头条】2026年4月8日(晚报) #光华报动#光华日报",
260
- "cleanedTitle": "【A1新闻头条】2026年4月8日(晚报) #光华报动#光华日报",
261
- "hasSnippet": true
262
- }
263
- ```
264
- [2026-04-08T06:50:43.043Z] [DEBUG] 🔍 清洗结果
265
- ```json
266
- {
267
- "originalTitle": "主页 - BBC News 中文",
268
- "cleanedTitle": "主页 - BBC News 中文",
269
- "hasSnippet": true
270
- }
271
- ```
272
- [2026-04-08T06:50:43.044Z] [INFO] 🔵 搜索成功完成
273
- [2026-04-08T06:50:43.044Z] [INFO] 🔵 最终统计
274
- ```json
275
- {
276
- "query": "2026 年 4 月 8 日 大新闻 头条",
277
- "resultsCount": 10,
278
- "durationSeconds": "1.733"
279
- }
280
- ```
281
- [2026-04-08T06:50:43.046Z] [DEBUG] 🔍 isEnabled() = true
282
- [2026-04-08T06:50:46.599Z] [DEBUG] 🔍 validateInput() 被调用
283
- [2026-04-08T06:50:46.600Z] [DEBUG] 🔍 输入参数
284
- ```json
285
- {
286
- "query": "2026 年 4 月 8 日 最新新闻 国内"
287
- }
288
- ```
289
- [2026-04-08T06:50:46.600Z] [INFO] 🔵 验证通过
290
- [2026-04-08T06:50:46.600Z] [INFO] 🔵 WebSearchTool.call() 被调用
291
- [2026-04-08T06:50:46.600Z] [DEBUG] 🔍 输入参数
292
- ```json
293
- {
294
- "query": "2026 年 4 月 8 日 最新新闻 国内"
295
- }
296
- ```
297
- [2026-04-08T06:50:46.600Z] [INFO] 🔵 查询参数验证通过: "2026 年 4 月 8 日 最新新闻 国内"
298
- [2026-04-08T06:50:46.600Z] [DEBUG] 🔍 发送进度更新
299
- [2026-04-08T06:50:46.600Z] [INFO] 🔵 调用 searchSearXNG()
300
- [2026-04-08T06:50:46.600Z] [INFO] 🔵 开始搜索 SearXNG
301
- [2026-04-08T06:50:46.600Z] [DEBUG] 🔍 搜索查询
302
- ```
303
- 2026 年 4 月 8 日 最新新闻 国内
304
- ```
305
- [2026-04-08T06:50:46.600Z] [DEBUG] 🔍 请求 URL
306
- ```
307
- http://localhost:8080/search?q=2026+%E5%B9%B4+4+%E6%9C%88+8+%E6%97%A5+%E6%9C%80%E6%96%B0%E6%96%B0%E9%97%BB+%E5%9B%BD%E5%86%85&format=json
308
- ```
309
- [2026-04-08T06:50:46.600Z] [INFO] 🔵 发起 HTTP 请求 (10秒超时)
310
- [2026-04-08T06:50:47.970Z] [INFO] 🔵 HTTP 响应状态
311
- ```
312
- 200 OK (1370ms)
313
- ```
314
- [2026-04-08T06:50:47.970Z] [DEBUG] 🔍 响应头 Content-Type
315
- ```
316
- application/json
317
- ```
318
- [2026-04-08T06:50:47.970Z] [INFO] 🔵 解析 JSON 响应
319
- [2026-04-08T06:50:47.970Z] [INFO] 🔵 返回结果统计
320
- ```json
321
- {
322
- "query": "2026 年 4 月 8 日 最新新闻 国内",
323
- "totalResults": 0,
324
- "resultsCount": 31
325
- }
326
- ```
327
- [2026-04-08T06:50:47.971Z] [INFO] 🔵 搜索完成,返回 10 条结果
328
- [2026-04-08T06:50:47.971Z] [INFO] 🔵 清洗搜索结果
329
- [2026-04-08T06:50:47.971Z] [DEBUG] 🔍 清洗结果
330
- ```json
331
- {
332
- "originalTitle": "2026年4月8日の記事一覧 - 朝日新聞",
333
- "cleanedTitle": "2026年4月8日の記事一覧 - 朝日新聞",
334
- "hasSnippet": true
335
- }
336
- ```
337
- [2026-04-08T06:50:47.971Z] [DEBUG] 🔍 清洗结果
338
- ```json
339
- {
340
- "originalTitle": "人民日报-人民网",
341
- "cleanedTitle": "人民日报-人民网",
342
- "hasSnippet": true
343
- }
344
- ```
345
- [2026-04-08T06:50:47.971Z] [DEBUG] 🔍 清洗结果
346
- ```json
347
- {
348
- "originalTitle": "【LIVE】昼のニュース (Japan News Digest Live) 最新情報など(4月8日)",
349
- "cleanedTitle": "【LIVE】昼のニュース (Japan News Digest Live) 最新情報など(4月8日)",
350
- "hasSnippet": true
351
- }
352
- ```
353
- [2026-04-08T06:50:47.971Z] [DEBUG] 🔍 清洗结果
354
- ```json
355
- {
356
- "originalTitle": "朝刊 2026年04月08日(水) - 朝日新聞",
357
- "cleanedTitle": "朝刊 2026年04月08日(水) - 朝日新聞",
358
- "hasSnippet": true
359
- }
360
- ```
361
- [2026-04-08T06:50:47.971Z] [DEBUG] 🔍 清洗结果
362
- ```json
363
- {
364
- "originalTitle": "中华人民共和国国家发展和改革委员会",
365
- "cleanedTitle": "中华人民共和国国家发展和改革委员会",
366
- "hasSnippet": true
367
- }
368
- ```
369
- [2026-04-08T06:50:47.971Z] [DEBUG] 🔍 清洗结果
370
- ```json
371
- {
372
- "originalTitle": "報道資料一覧:2026年4月 - 総務省",
373
- "cleanedTitle": "報道資料一覧:2026年4月 - 総務省",
374
- "hasSnippet": true
375
- }
376
- ```
377
- [2026-04-08T06:50:47.971Z] [DEBUG] 🔍 清洗结果
378
- ```json
379
- {
380
- "originalTitle": "高市総理は護憲派?に「当たり前だ」 憲法改正は別問題 国会で議論",
381
- "cleanedTitle": "高市総理は護憲派?に「当たり前だ」 憲法改正は別問題 国会で議論",
382
- "hasSnippet": true
383
- }
384
- ```
385
- [2026-04-08T06:50:47.971Z] [DEBUG] 🔍 清洗结果
386
- ```json
387
- {
388
- "originalTitle": "主页 - BBC News 中文",
389
- "cleanedTitle": "主页 - BBC News 中文",
390
- "hasSnippet": true
391
- }
392
- ```
393
- [2026-04-08T06:50:47.971Z] [DEBUG] 🔍 清洗结果
394
- ```json
395
- {
396
- "originalTitle": "【速報】名古屋・瑞穂区でマンション火災 けが人など不明 「黒煙が出て ...",
397
- "cleanedTitle": "【速報】名古屋・瑞穂区でマンション火災 けが人など不明 「黒煙が出て ...",
398
- "hasSnippet": true
399
- }
400
- ```
401
- [2026-04-08T06:50:47.971Z] [DEBUG] 🔍 清洗结果
402
- ```json
403
- {
404
- "originalTitle": "国际 - BBC News 中文",
405
- "cleanedTitle": "国际 - BBC News 中文",
406
- "hasSnippet": true
407
- }
408
- ```
409
- [2026-04-08T06:50:47.971Z] [INFO] 🔵 搜索成功完成
410
- [2026-04-08T06:50:47.971Z] [INFO] 🔵 最终统计
411
- ```json
412
- {
413
- "query": "2026 年 4 月 8 日 最新新闻 国内",
414
- "resultsCount": 10,
415
- "durationSeconds": "1.371"
416
- }
417
- ```
418
- [2026-04-08T06:50:47.973Z] [DEBUG] 🔍 isEnabled() = true
419
- [2026-04-08T06:55:11.784Z] [DEBUG] 🔍 isEnabled() = true
420
- [2026-04-08T06:56:09.602Z] [DEBUG] 🔍 isEnabled() = true
421
- [2026-04-08T06:56:09.613Z] [DEBUG] 🔍 isEnabled() = true
422
- [2026-04-08T06:56:12.273Z] [DEBUG] 🔍 validateInput() 被调用
423
- [2026-04-08T06:56:12.273Z] [DEBUG] 🔍 输入参数
424
- ```json
425
- {
426
- "query": "milet 最新动态 2026"
427
- }
428
- ```
429
- [2026-04-08T06:56:12.273Z] [INFO] 🔵 验证通过
430
- [2026-04-08T06:56:12.274Z] [INFO] 🔵 WebSearchTool.call() 被调用
431
- [2026-04-08T06:56:12.274Z] [DEBUG] 🔍 输入参数
432
- ```json
433
- {
434
- "query": "milet 最新动态 2026"
435
- }
436
- ```
437
- [2026-04-08T06:56:12.274Z] [INFO] 🔵 查询参数验证通过: "milet 最新动态 2026"
438
- [2026-04-08T06:56:12.274Z] [DEBUG] 🔍 发送进度更新
439
- [2026-04-08T06:56:12.274Z] [INFO] 🔵 调用 searchSearXNG()
440
- [2026-04-08T06:56:12.274Z] [INFO] 🔵 开始搜索 SearXNG
441
- [2026-04-08T06:56:12.274Z] [DEBUG] 🔍 搜索查询
442
- ```
443
- milet 最新动态 2026
444
- ```
445
- [2026-04-08T06:56:12.274Z] [DEBUG] 🔍 请求 URL
446
- ```
447
- http://localhost:8080/search?q=milet+%E6%9C%80%E6%96%B0%E5%8A%A8%E6%80%81+2026&format=json
448
- ```
449
- [2026-04-08T06:56:12.274Z] [INFO] 🔵 发起 HTTP 请求 (10秒超时)
450
- [2026-04-08T06:56:13.830Z] [INFO] 🔵 HTTP 响应状态
451
- ```
452
- 200 OK (1556ms)
453
- ```
454
- [2026-04-08T06:56:13.830Z] [DEBUG] 🔍 响应头 Content-Type
455
- ```
456
- application/json
457
- ```
458
- [2026-04-08T06:56:13.830Z] [INFO] 🔵 解析 JSON 响应
459
- [2026-04-08T06:56:13.831Z] [INFO] 🔵 返回结果统计
460
- ```json
461
- {
462
- "query": "milet 最新动态 2026",
463
- "totalResults": 0,
464
- "resultsCount": 30
465
- }
466
- ```
467
- [2026-04-08T06:56:13.832Z] [INFO] 🔵 搜索完成,返回 10 条结果
468
- [2026-04-08T06:56:13.832Z] [INFO] 🔵 清洗搜索结果
469
- [2026-04-08T06:56:13.832Z] [DEBUG] 🔍 清洗结果
470
- ```json
471
- {
472
- "originalTitle": "本作より「The Story of Us」ライヴ映像を1曲まるごと公開! | milet",
473
- "cleanedTitle": "本作より「The Story of Us」ライヴ映像を1曲まるごと公開! | milet",
474
- "hasSnippet": true
475
- }
476
- ```
477
- [2026-04-08T06:56:13.833Z] [DEBUG] 🔍 清洗结果
478
- ```json
479
- {
480
- "originalTitle": "2026年6月17日(水) リリース決定!日本武道館公演「Ray of Water ...",
481
- "cleanedTitle": "2026年6月17日(水) リリース決定!日本武道館公演「Ray of Water ...",
482
- "hasSnippet": true
483
- }
484
- ```
485
- [2026-04-08T06:56:13.833Z] [DEBUG] 🔍 清洗结果
486
- ```json
487
- {
488
- "originalTitle": "milet ライブBlu-ray&DVD『milet Live at Nippon Budokan \"Ray of Water ...",
489
- "cleanedTitle": "milet ライブBlu-ray&DVD『milet Live at Nippon Budokan \"Ray of Water ...",
490
- "hasSnippet": true
491
- }
492
- ```
493
- [2026-04-08T06:56:13.833Z] [DEBUG] 🔍 清洗结果
494
- ```json
495
- {
496
- "originalTitle": "milet Full Tour Schedule 2026 & 2027, Tour Dates & Concerts – Songkick",
497
- "cleanedTitle": "milet Full Tour Schedule 2026 & 2027, Tour Dates & Concerts – Songkick",
498
- "hasSnippet": true
499
- }
500
- ```
501
- [2026-04-08T06:56:13.833Z] [DEBUG] 🔍 清洗结果
502
- ```json
503
- {
504
- "originalTitle": "milet 最新ライブBlu-ray&DVD『milet Live at Nippon Budokan \"Ray of ...",
505
- "cleanedTitle": "milet 最新ライブBlu-ray&DVD『milet Live at Nippon Budokan \"Ray of ...",
506
- "hasSnippet": true
507
- }
508
- ```
509
- [2026-04-08T06:56:13.834Z] [DEBUG] 🔍 清洗结果
510
- ```json
511
- {
512
- "originalTitle": "2026年3月4日(水) #milet New Sg 『The Story of Us ... - Instagram",
513
- "cleanedTitle": "2026年3月4日(水) #milet New Sg 『The Story of Us ... - Instagram",
514
- "hasSnippet": true
515
- }
516
- ```
517
- [2026-04-08T06:56:13.834Z] [DEBUG] 🔍 清洗结果
518
- ```json
519
- {
520
- "originalTitle": "milet Tickets - 2026 Concert Tour Dates | Spotify",
521
- "cleanedTitle": "milet Tickets - 2026 Concert Tour Dates | Spotify",
522
- "hasSnippet": true
523
- }
524
- ```
525
- [2026-04-08T06:56:13.834Z] [DEBUG] 🔍 清洗结果
526
- ```json
527
- {
528
- "originalTitle": "milet、2026年2月に日本武道館公演2DAYS | Daily News",
529
- "cleanedTitle": "milet、2026年2月に日本武道館公演2DAYS | Daily News",
530
- "hasSnippet": true
531
- }
532
- ```
533
- [2026-04-08T06:56:13.834Z] [DEBUG] 🔍 清洗结果
534
- ```json
535
- {
536
- "originalTitle": "The Story of Us - song and lyrics by milet | Spotify",
537
- "cleanedTitle": "The Story of Us - song and lyrics by milet | Spotify",
538
- "hasSnippet": true
539
- }
540
- ```
541
- [2026-04-08T06:56:13.835Z] [DEBUG] 🔍 清洗结果
542
- ```json
543
- {
544
- "originalTitle": "2026年6月17日(水) リリース決定!日本武道館公演「Ray of Water ...",
545
- "cleanedTitle": "2026年6月17日(水) リリース決定!日本武道館公演「Ray of Water ...",
546
- "hasSnippet": true
547
- }
548
- ```
549
- [2026-04-08T06:56:13.835Z] [INFO] 🔵 搜索成功完成
550
- [2026-04-08T06:56:13.835Z] [INFO] 🔵 最终统计
551
- ```json
552
- {
553
- "query": "milet 最新动态 2026",
554
- "resultsCount": 10,
555
- "durationSeconds": "1.561"
556
- }
557
- ```
558
- [2026-04-08T06:56:13.839Z] [DEBUG] 🔍 isEnabled() = true
559
- [2026-04-08T06:57:07.875Z] [DEBUG] 🔍 isEnabled() = true
560
- [2026-04-08T06:57:07.884Z] [DEBUG] 🔍 isEnabled() = true
561
- [2026-04-08T06:57:11.209Z] [DEBUG] 🔍 validateInput() 被调用
562
- [2026-04-08T06:57:11.210Z] [DEBUG] 🔍 输入参数
563
- ```json
564
- {
565
- "query": "milet 大港开唱 2026 大港埸"
566
- }
567
- ```
568
- [2026-04-08T06:57:11.210Z] [INFO] 🔵 验证通过
569
- [2026-04-08T06:57:11.210Z] [INFO] 🔵 WebSearchTool.call() 被调用
570
- [2026-04-08T06:57:11.210Z] [DEBUG] 🔍 输入参数
571
- ```json
572
- {
573
- "query": "milet 大港开唱 2026 大港埸"
574
- }
575
- ```
576
- [2026-04-08T06:57:11.211Z] [INFO] 🔵 查询参数验证通过: "milet 大港开唱 2026 大港埸"
577
- [2026-04-08T06:57:11.211Z] [DEBUG] 🔍 发送进度更新
578
- [2026-04-08T06:57:11.211Z] [INFO] 🔵 调用 searchSearXNG()
579
- [2026-04-08T06:57:11.211Z] [INFO] 🔵 开始搜索 SearXNG
580
- [2026-04-08T06:57:11.211Z] [DEBUG] 🔍 搜索查询
581
- ```
582
- milet 大港开唱 2026 大港埸
583
- ```
584
- [2026-04-08T06:57:11.211Z] [DEBUG] 🔍 请求 URL
585
- ```
586
- http://localhost:8080/search?q=milet+%E5%A4%A7%E6%B8%AF%E5%BC%80%E5%94%B1+2026+%E5%A4%A7%E6%B8%AF%E5%9F%B8&format=json
587
- ```
588
- [2026-04-08T06:57:11.211Z] [INFO] 🔵 发起 HTTP 请求 (10秒超时)
589
- [2026-04-08T06:57:12.451Z] [INFO] 🔵 HTTP 响应状态
590
- ```
591
- 200 OK (1240ms)
592
- ```
593
- [2026-04-08T06:57:12.452Z] [DEBUG] 🔍 响应头 Content-Type
594
- ```
595
- application/json
596
- ```
597
- [2026-04-08T06:57:12.452Z] [INFO] 🔵 解析 JSON 响应
598
- [2026-04-08T06:57:12.452Z] [INFO] 🔵 返回结果统计
599
- ```json
600
- {
601
- "query": "milet 大港开唱 2026 大港埸",
602
- "totalResults": 0,
603
- "resultsCount": 29
604
- }
605
- ```
606
- [2026-04-08T06:57:12.453Z] [INFO] 🔵 搜索完成,返回 10 条结果
607
- [2026-04-08T06:57:12.453Z] [INFO] 🔵 清洗搜索结果
608
- [2026-04-08T06:57:12.453Z] [DEBUG] 🔍 清洗结果
609
- ```json
610
- {
611
- "originalTitle": "「2026 大港開唱 MEGAPORT FESTIVAL」milet出演決定!",
612
- "cleanedTitle": "「2026 大港開唱 MEGAPORT FESTIVAL」milet出演決定!",
613
- "hasSnippet": true
614
- }
615
- ```
616
- [2026-04-08T06:57:12.453Z] [DEBUG] 🔍 清洗结果
617
- ```json
618
- {
619
- "originalTitle": "#milet 『2026 大港開唱 MEGAPORT FESTIVAL』 出演決定! ▷日程:3 ...",
620
- "cleanedTitle": "#milet 『2026 大港開唱 MEGAPORT FESTIVAL』 出演決定! ▷日程:3 ...",
621
- "hasSnippet": true
622
- }
623
- ```
624
- [2026-04-08T06:57:12.453Z] [DEBUG] 🔍 清洗结果
625
- ```json
626
- {
627
- "originalTitle": "milet on Instagram: \"非常感謝這段快樂的時光。 無論是表演還是中文,我 ...",
628
- "cleanedTitle": "milet on Instagram: \"非常感謝這段快樂的時光。 無論是表演還是中文,我 ...",
629
- "hasSnippet": true
630
- }
631
- ```
632
- [2026-04-08T06:57:12.453Z] [DEBUG] 🔍 清洗结果
633
- ```json
634
- {
635
- "originalTitle": "2026大港開唱3/21、3/22登場!謝金燕、milet、芒果醬、落日飛車…演出時間表、入場須知、直播一次看",
636
- "cleanedTitle": "2026大港開唱3/21、3/22登場!謝金燕、milet、芒果醬、落日飛車…演出時間表、入場須知、直播一次看",
637
- "hasSnippet": true
638
- }
639
- ```
640
- [2026-04-08T06:57:12.453Z] [DEBUG] 🔍 清洗结果
641
- ```json
642
- {
643
- "originalTitle": "《 2026大港開唱精選現場》 - YouTube",
644
- "cleanedTitle": "《 2026大港開唱精選現場》 - YouTube",
645
- "hasSnippet": true
646
- }
647
- ```
648
- [2026-04-08T06:57:12.454Z] [DEBUG] 🔍 清洗结果
649
- ```json
650
- {
651
- "originalTitle": "「2026 大港開唱 MEGAPORT FESTIVAL」milet出演決定!",
652
- "cleanedTitle": "「2026 大港開唱 MEGAPORT FESTIVAL」milet出演決定!",
653
- "hasSnippet": true
654
- }
655
- ```
656
- [2026-04-08T06:57:12.454Z] [DEBUG] 🔍 清洗结果
657
- ```json
658
- {
659
- "originalTitle": "2026大港開唱20週年直擊百組演出!初代大港女神謝金燕回歸、milet首登台灣音樂祭,李竺芯與變裝皇后妮妃雅世紀對決",
660
- "cleanedTitle": "2026大港開唱20週年直擊百組演出!初代大港女神謝金燕回歸、milet首登台灣音樂祭,李竺芯與變裝皇后妮妃雅世紀對決",
661
- "hasSnippet": true
662
- }
663
- ```
664
- [2026-04-08T06:57:12.454Z] [DEBUG] 🔍 清洗结果
665
- ```json
666
- {
667
- "originalTitle": "【#milet】 台湾にて開催の 「2026 大港開唱 #MEGAPORT FESTIVAL ...",
668
- "cleanedTitle": "【#milet】 台湾にて開催の 「2026 大港開唱 #MEGAPORT FESTIVAL ...",
669
- "hasSnippet": true
670
- }
671
- ```
672
- [2026-04-08T06:57:12.454Z] [DEBUG] 🔍 清洗结果
673
- ```json
674
- {
675
- "originalTitle": "【2026大港開唱】milet《鬼滅之刃》唱紅全球:藝名為什麼全小寫、專屬粉絲日藏巧思! | GQ Taiwan",
676
- "cleanedTitle": "【2026大港開唱】milet《鬼滅之刃》唱紅全球:藝名為什麼全小寫、專屬粉絲日藏巧思! | GQ Taiwan",
677
- "hasSnippet": true
678
- }
679
- ```
680
- [2026-04-08T06:57:12.454Z] [DEBUG] 🔍 清洗结果
681
- ```json
682
- {
683
- "originalTitle": "news | シンガーソングライター milet(ミレイ) Official Web Site",
684
- "cleanedTitle": "news | シンガーソングライター milet(ミレイ) Official Web Site",
685
- "hasSnippet": true
686
- }
687
- ```
688
- [2026-04-08T06:57:12.454Z] [INFO] 🔵 搜索成功完成
689
- [2026-04-08T06:57:12.454Z] [INFO] 🔵 最终统计
690
- ```json
691
- {
692
- "query": "milet 大港开唱 2026 大港埸",
693
- "resultsCount": 10,
694
- "durationSeconds": "1.244"
695
- }
696
- ```
697
- [2026-04-08T06:57:12.456Z] [DEBUG] 🔍 isEnabled() = true
698
- [2026-04-08T08:35:58.574Z] [DEBUG] 🔍 isEnabled() = true
699
- [2026-04-08T08:35:58.584Z] [DEBUG] 🔍 isEnabled() = true
700
- [2026-04-08T08:36:01.725Z] [DEBUG] 🔍 validateInput() 被调用
701
- [2026-04-08T08:36:01.725Z] [DEBUG] 🔍 输入参数
702
- ```json
703
- {
704
- "query": "milet Instagram 最新动态 2026 更新"
705
- }
706
- ```
707
- [2026-04-08T08:36:01.725Z] [INFO] 🔵 验证通过
708
- [2026-04-08T08:36:01.726Z] [INFO] 🔵 WebSearchTool.call() 被调用
709
- [2026-04-08T08:36:01.726Z] [DEBUG] 🔍 输入参数
710
- ```json
711
- {
712
- "query": "milet Instagram 最新动态 2026 更新"
713
- }
714
- ```
715
- [2026-04-08T08:36:01.726Z] [INFO] 🔵 查询参数验证通过: "milet Instagram 最新动态 2026 更新"
716
- [2026-04-08T08:36:01.726Z] [DEBUG] 🔍 发送进度更新
717
- [2026-04-08T08:36:01.726Z] [INFO] 🔵 调用 searchSearXNG()
718
- [2026-04-08T08:36:01.726Z] [INFO] 🔵 开始搜索 SearXNG
719
- [2026-04-08T08:36:01.726Z] [DEBUG] 🔍 搜索查询
720
- ```
721
- milet Instagram 最新动态 2026 更新
722
- ```
723
- [2026-04-08T08:36:01.726Z] [DEBUG] 🔍 请求 URL
724
- ```
725
- http://localhost:8080/search?q=milet+Instagram+%E6%9C%80%E6%96%B0%E5%8A%A8%E6%80%81+2026+%E6%9B%B4%E6%96%B0&format=json
726
- ```
727
- [2026-04-08T08:36:01.726Z] [INFO] 🔵 发起 HTTP 请求 (10秒超时)
728
- [2026-04-08T08:36:04.033Z] [INFO] 🔵 HTTP 响应状态
729
- ```
730
- 200 OK (2307ms)
731
- ```
732
- [2026-04-08T08:36:04.034Z] [DEBUG] 🔍 响应头 Content-Type
733
- ```
734
- application/json
735
- ```
736
- [2026-04-08T08:36:04.034Z] [INFO] 🔵 解析 JSON 响应
737
- [2026-04-08T08:36:04.035Z] [INFO] 🔵 返回结果统计
738
- ```json
739
- {
740
- "query": "milet Instagram 最新动态 2026 更新",
741
- "totalResults": 0,
742
- "resultsCount": 38
743
- }
744
- ```
745
- [2026-04-08T08:36:04.035Z] [INFO] 🔵 ���索完成,返回 10 条结果
746
- [2026-04-08T08:36:04.035Z] [INFO] 🔵 清洗搜索结果
747
- [2026-04-08T08:36:04.036Z] [DEBUG] 🔍 清洗结果
748
- ```json
749
- {
750
- "originalTitle": "milet (@milet_music) • Instagram photos and videos",
751
- "cleanedTitle": "milet (@milet_music) • Instagram photos and videos",
752
- "hasSnippet": true
753
- }
754
- ```
755
- [2026-04-08T08:36:04.036Z] [DEBUG] 🔍 清洗结果
756
- ```json
757
- {
758
- "originalTitle": "本日「The Story of Us」リリースしました。 アニメ「葬送のフリーレン ...",
759
- "cleanedTitle": "本日「The Story of Us」リリースしました。 アニメ「葬送のフリーレン ...",
760
- "hasSnippet": true
761
- }
762
- ```
763
- [2026-04-08T08:36:04.036Z] [DEBUG] 🔍 清洗结果
764
- ```json
765
- {
766
- "originalTitle": "milet_news2026 • Instagram photos and videos",
767
- "cleanedTitle": "milet_news2026 • Instagram photos and videos",
768
- "hasSnippet": true
769
- }
770
- ```
771
- [2026-04-08T08:36:04.036Z] [DEBUG] 🔍 清洗结果
772
- ```json
773
- {
774
- "originalTitle": "【2026大港開唱】milet《鬼滅之刃》唱紅全球:藝名為什麼全小寫、專屬粉絲日藏巧思! | GQ Taiwan",
775
- "cleanedTitle": "【2026大港開唱】milet《鬼滅之刃》唱紅全球:藝名為什麼全小寫、專屬粉絲日藏巧思! | GQ Taiwan",
776
- "hasSnippet": true
777
- }
778
- ```
779
- [2026-04-08T08:36:04.036Z] [DEBUG] 🔍 清洗结果
780
- ```json
781
- {
782
- "originalTitle": "【訪問】milet 大港開唱蛻變回歸:接受不完美的自己,為了等待我的人唱下去 - MeMeOn 迷迷音",
783
- "cleanedTitle": "【訪問】milet 大港開唱蛻變回歸:接受不完美的自己,為了等待我的人唱下去 - MeMeOn 迷迷音",
784
- "hasSnippet": true
785
- }
786
- ```
787
- [2026-04-08T08:36:04.036Z] [DEBUG] 🔍 清洗结果
788
- ```json
789
- {
790
- "originalTitle": "milet - 萌娘百科 万物皆可萌的百科全书",
791
- "cleanedTitle": "milet - 萌娘百科 万物皆可萌的百科全书",
792
- "hasSnippet": true
793
- }
794
- ```
795
- [2026-04-08T08:36:04.037Z] [DEBUG] 🔍 清洗结果
796
- ```json
797
- {
798
- "originalTitle": "In-depth analysis of Instagram's latest algorithm in 2026: A practical guide for cross-border sellers and marketers.",
799
- "cleanedTitle": "In-depth analysis of Instagram's latest algorithm in 2026: A practical guide for cross-border sellers and marketers.",
800
- "hasSnippet": true
801
- }
802
- ```
803
- [2026-04-08T08:36:04.037Z] [DEBUG] 🔍 清洗结果
804
- ```json
805
- {
806
- "originalTitle": "Apple Music - milet",
807
- "cleanedTitle": "Apple Music - milet",
808
- "hasSnippet": true
809
- }
810
- ```
811
- [2026-04-08T08:36:04.037Z] [DEBUG] 🔍 清洗结果
812
- ```json
813
- {
814
- "originalTitle": "2026大港開唱3/21、3/22登場!謝金燕、milet、芒果醬、落日飛車…演出時間表、入場須知、直播一次看",
815
- "cleanedTitle": "2026大港開唱3/21、3/22登場!謝金燕、milet、芒果醬、落日飛車…演出時間表、入場須知、直播一次看",
816
- "hasSnippet": true
817
- }
818
- ```
819
- [2026-04-08T08:36:04.037Z] [DEBUG] 🔍 清洗结果
820
- ```json
821
- {
822
- "originalTitle": "Milet Upcoming Concerts 2026",
823
- "cleanedTitle": "Milet Upcoming Concerts 2026",
824
- "hasSnippet": true
825
- }
826
- ```
827
- [2026-04-08T08:36:04.038Z] [INFO] 🔵 搜索成功完成
828
- [2026-04-08T08:36:04.038Z] [INFO] 🔵 最终统计
829
- ```json
830
- {
831
- "query": "milet Instagram 最新动态 2026 更新",
832
- "resultsCount": 10,
833
- "durationSeconds": "2.312"
834
- }
835
- ```
836
- [2026-04-08T08:36:04.040Z] [DEBUG] 🔍 isEnabled() = true
837
- [2026-04-08T08:36:08.882Z] [DEBUG] 🔍 validateInput() 被调用
838
- [2026-04-08T08:36:08.882Z] [DEBUG] 🔍 输入参数
839
- ```json
840
- {
841
- "query": "milet 2026 全国巡演 17 都市 tour dates"
842
- }
843
- ```
844
- [2026-04-08T08:36:08.882Z] [INFO] 🔵 验证通过
845
- [2026-04-08T08:36:08.883Z] [INFO] 🔵 WebSearchTool.call() 被调用
846
- [2026-04-08T08:36:08.883Z] [DEBUG] 🔍 输入参数
847
- ```json
848
- {
849
- "query": "milet 2026 全国巡演 17 都市 tour dates"
850
- }
851
- ```
852
- [2026-04-08T08:36:08.883Z] [INFO] 🔵 查询参数验证通过: "milet 2026 全国巡演 17 都市 tour dates"
853
- [2026-04-08T08:36:08.883Z] [DEBUG] 🔍 发送进度更新
854
- [2026-04-08T08:36:08.883Z] [INFO] 🔵 调用 searchSearXNG()
855
- [2026-04-08T08:36:08.883Z] [INFO] 🔵 开始搜索 SearXNG
856
- [2026-04-08T08:36:08.883Z] [DEBUG] 🔍 搜索查询
857
- ```
858
- milet 2026 全国巡演 17 都市 tour dates
859
- ```
860
- [2026-04-08T08:36:08.884Z] [DEBUG] 🔍 请求 URL
861
- ```
862
- http://localhost:8080/search?q=milet+2026+%E5%85%A8%E5%9B%BD%E5%B7%A1%E6%BC%94+17+%E9%83%BD%E5%B8%82+tour+dates&format=json
863
- ```
864
- [2026-04-08T08:36:08.884Z] [INFO] 🔵 发起 HTTP 请求 (10秒超时)
865
- [2026-04-08T08:36:11.894Z] [INFO] 🔵 HTTP 响应状态
866
- ```
867
- 200 OK (3010ms)
868
- ```
869
- [2026-04-08T08:36:11.895Z] [DEBUG] 🔍 响应头 Content-Type
870
- ```
871
- application/json
872
- ```
873
- [2026-04-08T08:36:11.895Z] [INFO] 🔵 解析 JSON 响应
874
- [2026-04-08T08:36:11.896Z] [INFO] 🔵 返回结果统计
875
- ```json
876
- {
877
- "query": "milet 2026 全国巡演 17 都市 tour dates",
878
- "totalResults": 0,
879
- "resultsCount": 32
880
- }
881
- ```
882
- [2026-04-08T08:36:11.896Z] [INFO] 🔵 搜索完成,返回 10 条结果
883
- [2026-04-08T08:36:11.896Z] [INFO] 🔵 清洗搜索结果
884
- [2026-04-08T08:36:11.897Z] [DEBUG] 🔍 清���结果
885
- ```json
886
- {
887
- "originalTitle": "2026年秋、全国17都市19公演のホールツアー開催決定!オフィシャルモバイルファンクラブ『miles』にてチケット最速先行受付スタート ...",
888
- "cleanedTitle": "2026年秋、全国17都市19公演のホールツアー開催決定!オフィシャルモバイルファンクラブ『miles』にてチケット最速先行受付スタート ...",
889
- "hasSnippet": true
890
- }
891
- ```
892
- [2026-04-08T08:36:11.897Z] [DEBUG] 🔍 清洗结果
893
- ```json
894
- {
895
- "originalTitle": "2026年秋、全国17都市19公演のホールツアー開催決定!",
896
- "cleanedTitle": "2026年秋、全国17都市19公演のホールツアー開催決定!",
897
- "hasSnippet": true
898
- }
899
- ```
900
- [2026-04-08T08:36:11.897Z] [DEBUG] 🔍 清洗结果
901
- ```json
902
- {
903
- "originalTitle": "2026年秋、全国17都市19公演の全国ホールツアーmiletオフィシャルHP先行スタート! | milet | ソニーミュージックオフィシャルサイト",
904
- "cleanedTitle": "2026年秋、全国17都市19公演の全国ホールツアーmiletオフィシャルHP先行スタート! | milet | ソニーミュージックオフィシャルサイト",
905
- "hasSnippet": true
906
- }
907
- ```
908
- [2026-04-08T08:36:11.897Z] [DEBUG] 🔍 清洗结果
909
- ```json
910
- {
911
- "originalTitle": "2026年秋、全国17都市19公演の全国ホールツアーmiletオフィシャルHP先行スタート![milet] | ニュース | Sony Music Artists",
912
- "cleanedTitle": "2026年秋、全国17都市19公演の全国ホールツアーmiletオフィシャルHP先行スタート![milet] | ニュース | Sony Music Artists",
913
- "hasSnippet": true
914
- }
915
- ```
916
- [2026-04-08T08:36:11.898Z] [DEBUG] 🔍 清洗结果
917
- ```json
918
- {
919
- "originalTitle": "milet、今秋から全国ホール・ツアー開催決定。本日2/15 18時より日本武道館公演\"Ray of Water\"Stationheadセットリスト・リスニング・パーティー実施。CS放送TBSチャンネル1で同公演放送",
920
- "cleanedTitle": "milet、今秋から全国ホール・ツアー開催決定。本日2/15 18時より日本武道館公演\"Ray of Water\"Stationheadセットリスト・リスニング・パーティー実施。CS放送TBSチャンネル1で同公演放送",
921
- "hasSnippet": true
922
- }
923
- ```
924
- [2026-04-08T08:36:11.898Z] [DEBUG] 🔍 清洗结果
925
- ```json
926
- {
927
- "originalTitle": "miletライブ2026情報まとめ!チケット一般発売日・申し込み方法は? | LIGNEA",
928
- "cleanedTitle": "miletライブ2026情報まとめ!チケット一般発売日・申し込み方法は? | LIGNEA",
929
- "hasSnippet": true
930
- }
931
- ```
932
- [2026-04-08T08:36:11.898Z] [DEBUG] 🔍 清洗结果
933
- ```json
934
- {
935
- "originalTitle": "milet Tickets - 2026 Concert Tour Dates | Spotify",
936
- "cleanedTitle": "milet Tickets - 2026 Concert Tour Dates | Spotify",
937
- "hasSnippet": true
938
- }
939
- ```
940
- [2026-04-08T08:36:11.898Z] [DEBUG] 🔍 清洗结果
941
- ```json
942
- {
943
- "originalTitle": "milet Full Tour Schedule 2026 & 2027, Tour Dates & Concerts – Songkick",
944
- "cleanedTitle": "milet Full Tour Schedule 2026 & 2027, Tour Dates & Concerts – Songkick",
945
- "hasSnippet": true
946
- }
947
- ```
948
- [2026-04-08T08:36:11.898Z] [DEBUG] 🔍 清洗结果
949
- ```json
950
- {
951
- "originalTitle": "miletライブチケット2026一般発売いつから?取り方と確実に取るコツも!|チケット情報局by株式会社マーケットプレイス",
952
- "cleanedTitle": "miletライブチケット2026一般発売いつから?取り方と確実に取るコツも!|チケット情報局by株式会社マーケットプレイス",
953
- "hasSnippet": true
954
- }
955
- ```
956
- [2026-04-08T08:36:11.899Z] [DEBUG] 🔍 清洗结果
957
- ```json
958
- {
959
- "originalTitle": "milet Concerts & Live Tour Dates: 2026-2027 Tickets | Bandsintown",
960
- "cleanedTitle": "milet Concerts & Live Tour Dates: 2026-2027 Tickets | Bandsintown",
961
- "hasSnippet": true
962
- }
963
- ```
964
- [2026-04-08T08:36:11.899Z] [INFO] 🔵 搜索成功完成
965
- [2026-04-08T08:36:11.899Z] [INFO] 🔵 最终统计
966
- ```json
967
- {
968
- "query": "milet 2026 全国巡演 17 都市 tour dates",
969
- "resultsCount": 10,
970
- "durationSeconds": "3.016"
971
- }
972
- ```
973
- [2026-04-08T08:36:11.902Z] [DEBUG] 🔍 isEnabled() = true
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/tools/WebSearchTool/WebSearchTool.ts CHANGED
@@ -10,49 +10,6 @@ import {
10
  renderToolUseMessage,
11
  renderToolUseProgressMessage,
12
  } from './UI.js'
13
- import { readFileSync, writeFileSync, existsSync } from 'fs'
14
- import { join } from 'path'
15
-
16
- /**
17
- * 日志记录函数 - 将日志写入 log.md 文件
18
- */
19
- function logToMarkdown(level: 'INFO' | 'WARN' | 'ERROR' | 'DEBUG', message: string, data?: any) {
20
- const timestamp = new Date().toISOString()
21
- const emoji = {
22
- INFO: '🔵',
23
- WARN: '⚠️',
24
- ERROR: '❌',
25
- DEBUG: '🔍'
26
- }[level]
27
-
28
- let logEntry = `\n[${timestamp}] [${level}] ${emoji} ${message}`
29
-
30
- if (data !== undefined) {
31
- if (typeof data === 'string') {
32
- logEntry += `\n\`\`\`\n${data}\n\`\`\``
33
- } else {
34
- logEntry += `\n\`\`\`json\n${JSON.stringify(data, null, 2)}\n\`\`\``
35
- }
36
- }
37
-
38
- const logPath = join(process.cwd(), 'log.md')
39
-
40
- try {
41
- // 追加日志到文件
42
- if (existsSync(logPath)) {
43
- const existingContent = readFileSync(logPath, 'utf-8')
44
- writeFileSync(logPath, existingContent + logEntry, 'utf-8')
45
- } else {
46
- writeFileSync(logPath, logEntry, 'utf-8')
47
- }
48
- } catch (error) {
49
- // 如果写入日志失败,仍然输出到控制台
50
- console.error(`[WebSearch] 无法写入日志文件: ${error}`)
51
- }
52
-
53
- // 同时输出到控制台
54
- console.log(`[WebSearch] ${level}: ${message}`, data !== undefined ? data : '')
55
- }
56
 
57
  const inputSchema = lazySchema(() =>
58
  z.strictObject({
@@ -91,133 +48,47 @@ export type { WebSearchProgress } from '../../types/tools.js'
91
  import type { WebSearchProgress } from '../../types/tools.js'
92
 
93
  /**
94
- * 使用 SearXNG 本地搜索
95
  */
96
  async function searchSearXNG(
97
  query: string
98
  ): Promise<Array<{ title: string; url: string; snippet?: string }>> {
99
- logToMarkdown('INFO', '开始搜索 SearXNG')
100
- logToMarkdown('DEBUG', '搜索查询', query)
101
-
102
- // 断言:查询参数必须有效
103
- console.assert(
104
- typeof query === 'string' && query.trim().length > 0,
105
- `[WebSearch] ❌ 无效的查询参数: ${JSON.stringify(query)}`
106
- )
107
-
108
  try {
109
  const url = new URL('http://localhost:8080/search')
110
  url.searchParams.set('q', query)
111
  url.searchParams.set('format', 'json')
112
 
113
- const finalUrl = url.toString()
114
- logToMarkdown('DEBUG', '请求 URL', finalUrl)
115
-
116
  const controller = new AbortController()
117
  const timeout = setTimeout(() => controller.abort(), 10000)
118
 
119
- logToMarkdown('INFO', '发起 HTTP 请求 (10秒超时)')
120
- const startTime = Date.now()
121
- const res = await fetch(finalUrl, {
122
  signal: controller.signal,
123
  headers: {
124
  'User-Agent': 'Mozilla/5.0 (compatible; WebSearchTool/1.0)',
125
  'Accept': 'application/json',
126
  },
127
  })
128
- const elapsed = Date.now() - startTime
129
 
130
  clearTimeout(timeout)
131
 
132
- const statusInfo = `${res.status} ${res.statusText} (${elapsed}ms)`
133
- logToMarkdown('INFO', 'HTTP 响应状态', statusInfo)
134
- logToMarkdown('DEBUG', '响应头 Content-Type', res.headers.get('content-type'))
135
-
136
- // 断言:HTTP 状态码应该是 2xx
137
- console.assert(
138
- res.ok,
139
- `[WebSearch] ❌ HTTP 请求失败: ${res.status} ${res.statusText}`
140
- )
141
-
142
  if (!res.ok) {
143
  const errorText = await res.text()
144
- logToMarkdown('ERROR', '错误响应内容', errorText)
145
  throw new Error(`HTTP ${res.status}: ${errorText}`)
146
  }
147
 
148
- logToMarkdown('INFO', '解析 JSON 响应')
149
  const data = await res.json()
150
 
151
- const resultStats = {
152
- query: data.query,
153
- totalResults: data.number_of_results,
154
- resultsCount: data.results?.length || 0,
155
- }
156
- logToMarkdown('INFO', '返回结果统计', resultStats)
157
-
158
- // 断言:响应数据应该包含 results 数组
159
- console.assert(
160
- Array.isArray(data.results),
161
- `[WebSearch] ❌ 响应数据格式错误: results 不是数组, 类型: ${typeof data.results}`
162
- )
163
-
164
- const results = (data.results || [])
165
  .slice(0, 10)
166
- .map((r: any, index: number) => {
167
- // 断言:每个结果必须有 title 和 url
168
- console.assert(
169
- r.title && r.url,
170
- `[WebSearch] ⚠️ 结果 ${index} 缺少必要字段: ${JSON.stringify(r)}`
171
- )
172
-
173
- return {
174
- title: r.title,
175
- url: r.url,
176
- snippet: r.content,
177
- }
178
- })
179
-
180
- logToMarkdown('INFO', `搜索完成,返回 ${results.length} 条结果`)
181
- return results
182
  } catch (error) {
183
- const errorMessage = error instanceof Error ? error.message : String(error)
184
- const errorStack = error instanceof Error ? error.stack : undefined
185
-
186
- logToMarkdown('ERROR', '搜索失败', errorMessage)
187
- if (errorStack) {
188
- logToMarkdown('ERROR', '错误堆栈', errorStack)
189
- }
190
-
191
  logError('SearXNG search failed', error)
192
-
193
- // 根据错误类型提供更详细的诊断信息
194
- if (error instanceof Error) {
195
- if (error.name === 'AbortError') {
196
- const timeoutReasons = [
197
- 'SearXNG 服务响应过慢',
198
- '网络连接问题',
199
- 'SearXNG 服务未运行'
200
- ]
201
- logToMarkdown('ERROR', '请求超时 (10秒),可能原因', timeoutReasons)
202
- } else if (error.message.includes('ECONNREFUSED')) {
203
- const connectionReasons = [
204
- 'SearXNG 服务未启动',
205
- '端口 8080 未开放',
206
- '防火墙阻止连接'
207
- ]
208
- logToMarkdown('ERROR', '连接被拒绝,可能原因', connectionReasons)
209
- } else if (error.message.includes('HTTP 403')) {
210
- const forbiddenReasons = [
211
- 'SearXNG 配置不允许 JSON 格式',
212
- 'IP 被限制',
213
- '需要认证'
214
- ]
215
- logToMarkdown('ERROR', 'HTTP 403 禁止访问,可能原因', forbiddenReasons)
216
- }
217
- }
218
-
219
  throw new Error(
220
- `SearXNG search failed: ${errorMessage}`
221
  )
222
  }
223
  }
@@ -260,9 +131,7 @@ export const WebSearchTool = buildTool({
260
  },
261
 
262
  isEnabled() {
263
- const enabled = true
264
- logToMarkdown('DEBUG', `isEnabled() = ${enabled}`)
265
- return enabled
266
  },
267
 
268
  get inputSchema() {
@@ -304,34 +173,17 @@ export const WebSearchTool = buildTool({
304
  },
305
 
306
  async validateInput(input) {
307
- logToMarkdown('DEBUG', 'validateInput() 被调用')
308
- logToMarkdown('DEBUG', '输入参数', input)
309
-
310
  if (!input?.query) {
311
- logToMarkdown('ERROR', '验证失败: Missing query')
312
  return { result: false, message: 'Missing query', errorCode: 1 }
313
  }
314
-
315
- logToMarkdown('INFO', '验证通过')
316
  return { result: true }
317
  },
318
 
319
  async call(input, _context, _canUseTool, _parentMessage, onProgress) {
320
  const start = performance.now()
321
 
322
- logToMarkdown('INFO', 'WebSearchTool.call() 被调用')
323
- logToMarkdown('DEBUG', '输入参数', input)
324
-
325
- // 断言:input 必须存在
326
- console.assert(
327
- input !== null && input !== undefined,
328
- `[WebSearch] ❌ input 参数为 null/undefined`
329
- )
330
-
331
  try {
332
- // 验证查询参数
333
  if (!input?.query || input.query.trim() === '') {
334
- logToMarkdown('WARN', '查询参数为空,返回错误')
335
  return {
336
  data: {
337
  query: input?.query || '',
@@ -341,38 +193,19 @@ export const WebSearchTool = buildTool({
341
  }
342
  }
343
 
344
- // 断言:查询长度必须至少 2 个字符(根据 inputSchema 定义)
345
- console.assert(
346
- input.query.trim().length >= 2,
347
- `[WebSearch] ❌ 查询长度不足: "${input.query}" (长度: ${input.query.length})`
348
- )
349
-
350
- logToMarkdown('INFO', `查询参数验证通过: "${input.query}"`)
351
-
352
  if (onProgress) {
353
- logToMarkdown('DEBUG', '发送进度更新')
354
  onProgress({
355
  toolUseID: 'search-start',
356
  data: { type: 'query_update', query: input.query },
357
  })
358
  }
359
 
360
- logToMarkdown('INFO', '调用 searchSearXNG()')
361
  const results = await searchSearXNG(input.query)
362
 
363
- logToMarkdown('INFO', '清洗搜索结果')
364
- const cleaned = results.map(r => {
365
- const cleanedResult = cleanSearchResult(r)
366
- logToMarkdown('DEBUG', '清洗结果', {
367
- originalTitle: r.title,
368
- cleanedTitle: cleanedResult.title,
369
- hasSnippet: !!r.snippet,
370
- })
371
- return {
372
- ...r,
373
- ...cleanedResult,
374
- }
375
- })
376
 
377
  const output =
378
  cleaned.length === 0
@@ -386,14 +219,6 @@ export const WebSearchTool = buildTool({
386
 
387
  const duration = (performance.now() - start) / 1000
388
 
389
- const finalStats = {
390
- query: input.query,
391
- resultsCount: cleaned.length,
392
- durationSeconds: duration.toFixed(3),
393
- }
394
- logToMarkdown('INFO', '搜索成功完成')
395
- logToMarkdown('INFO', '最终统计', finalStats)
396
-
397
  return {
398
  data: {
399
  query: input.query,
@@ -405,12 +230,6 @@ export const WebSearchTool = buildTool({
405
  const duration = (performance.now() - start) / 1000
406
  const errorMessage = error instanceof Error ? error.message : String(error)
407
 
408
- logToMarkdown('ERROR', 'WebSearchTool.call() 发生异常')
409
- logToMarkdown('ERROR', '异常信息', errorMessage)
410
- if (error instanceof Error && error.stack) {
411
- logToMarkdown('ERROR', '异常堆栈', error.stack)
412
- }
413
-
414
  logError(error)
415
 
416
  return {
@@ -450,4 +269,4 @@ export const WebSearchTool = buildTool({
450
  content: text,
451
  }
452
  },
453
- }) satisfies ToolDef<any, Output, WebSearchProgress>
 
10
  renderToolUseMessage,
11
  renderToolUseProgressMessage,
12
  } from './UI.js'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
 
14
  const inputSchema = lazySchema(() =>
15
  z.strictObject({
 
48
  import type { WebSearchProgress } from '../../types/tools.js'
49
 
50
  /**
51
+ * 使用 SearXNG 本地搜索
52
  */
53
  async function searchSearXNG(
54
  query: string
55
  ): Promise<Array<{ title: string; url: string; snippet?: string }>> {
 
 
 
 
 
 
 
 
 
56
  try {
57
  const url = new URL('http://localhost:8080/search')
58
  url.searchParams.set('q', query)
59
  url.searchParams.set('format', 'json')
60
 
 
 
 
61
  const controller = new AbortController()
62
  const timeout = setTimeout(() => controller.abort(), 10000)
63
 
64
+ const res = await fetch(url.toString(), {
 
 
65
  signal: controller.signal,
66
  headers: {
67
  'User-Agent': 'Mozilla/5.0 (compatible; WebSearchTool/1.0)',
68
  'Accept': 'application/json',
69
  },
70
  })
 
71
 
72
  clearTimeout(timeout)
73
 
 
 
 
 
 
 
 
 
 
 
74
  if (!res.ok) {
75
  const errorText = await res.text()
 
76
  throw new Error(`HTTP ${res.status}: ${errorText}`)
77
  }
78
 
 
79
  const data = await res.json()
80
 
81
+ return (data.results || [])
 
 
 
 
 
 
 
 
 
 
 
 
 
82
  .slice(0, 10)
83
+ .map((r: any) => ({
84
+ title: r.title,
85
+ url: r.url,
86
+ snippet: r.content,
87
+ }))
 
 
 
 
 
 
 
 
 
 
 
88
  } catch (error) {
 
 
 
 
 
 
 
 
89
  logError('SearXNG search failed', error)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
  throw new Error(
91
+ `SearXNG search failed: ${error instanceof Error ? error.message : String(error)}`
92
  )
93
  }
94
  }
 
131
  },
132
 
133
  isEnabled() {
134
+ return true
 
 
135
  },
136
 
137
  get inputSchema() {
 
173
  },
174
 
175
  async validateInput(input) {
 
 
 
176
  if (!input?.query) {
 
177
  return { result: false, message: 'Missing query', errorCode: 1 }
178
  }
 
 
179
  return { result: true }
180
  },
181
 
182
  async call(input, _context, _canUseTool, _parentMessage, onProgress) {
183
  const start = performance.now()
184
 
 
 
 
 
 
 
 
 
 
185
  try {
 
186
  if (!input?.query || input.query.trim() === '') {
 
187
  return {
188
  data: {
189
  query: input?.query || '',
 
193
  }
194
  }
195
 
 
 
 
 
 
 
 
 
196
  if (onProgress) {
 
197
  onProgress({
198
  toolUseID: 'search-start',
199
  data: { type: 'query_update', query: input.query },
200
  })
201
  }
202
 
 
203
  const results = await searchSearXNG(input.query)
204
 
205
+ const cleaned = results.map(r => ({
206
+ ...r,
207
+ ...cleanSearchResult(r),
208
+ }))
 
 
 
 
 
 
 
 
 
209
 
210
  const output =
211
  cleaned.length === 0
 
219
 
220
  const duration = (performance.now() - start) / 1000
221
 
 
 
 
 
 
 
 
 
222
  return {
223
  data: {
224
  query: input.query,
 
230
  const duration = (performance.now() - start) / 1000
231
  const errorMessage = error instanceof Error ? error.message : String(error)
232
 
 
 
 
 
 
 
233
  logError(error)
234
 
235
  return {
 
269
  content: text,
270
  }
271
  },
272
+ }) satisfies ToolDef<any, Output, WebSearchProgress>
src/tools/WebSearchTool/WebSearchTool.ts.backup DELETED
@@ -1,447 +0,0 @@
1
- import type { PermissionResult } from '../../utils/permissions/PermissionResult.js'
2
- import { z } from 'zod/v4'
3
- import { buildTool, type ToolDef } from '../../Tool.js'
4
- import { lazySchema } from '../../utils/lazySchema.js'
5
- import { logError } from '../../utils/log.js'
6
- import { getWebSearchPrompt, WEB_SEARCH_TOOL_NAME } from './prompt.js'
7
- import {
8
- getToolUseSummary,
9
- renderToolResultMessage,
10
- renderToolUseMessage,
11
- renderToolUseProgressMessage,
12
- } from './UI.js'
13
- import { readFileSync, writeFileSync, existsSync } from 'fs'
14
- import { join } from 'path'
15
-
16
- /**
17
- * 日志记录函数 - 将日志写入 log.md 文件
18
- */
19
- function logToMarkdown(level: 'INFO' | 'WARN' | 'ERROR' | 'DEBUG', message: string, data?: any) {
20
- const timestamp = new Date().toISOString()
21
- const emoji = {
22
- INFO: '🔵',
23
- WARN: '⚠️',
24
- ERROR: '❌',
25
- DEBUG: '🔍'
26
- }[level]
27
-
28
- let logEntry = `\n[${timestamp}] [${level}] ${emoji} ${message}`
29
-
30
- if (data !== undefined) {
31
- if (typeof data === 'string') {
32
- logEntry += `\n\`\`\`\n${data}\n\`\`\``
33
- } else {
34
- logEntry += `\n\`\`\`json\n${JSON.stringify(data, null, 2)}\n\`\`\``
35
- }
36
- }
37
-
38
- const logPath = join(process.cwd(), 'log.md')
39
-
40
- try {
41
- // 追加日志到文件
42
- if (existsSync(logPath)) {
43
- const existingContent = readFileSync(logPath, 'utf-8')
44
- writeFileSync(logPath, existingContent + logEntry, 'utf-8')
45
- } else {
46
- writeFileSync(logPath, logEntry, 'utf-8')
47
- }
48
- } catch (error) {
49
- // 如果写入日志失败,仍然输出到控制台
50
- console.error(`[WebSearch] 无法写入日志文件: ${error}`)
51
- }
52
-
53
- // 同时输出到控制台
54
- console.log(`[WebSearch] ${level}: ${message}`, data !== undefined ? data : '')
55
- }
56
-
57
- const inputSchema = lazySchema(() =>
58
- z.strictObject({
59
- query: z.string().min(2).describe('The search query to use'),
60
- }),
61
- )
62
-
63
- type Input = z.infer<ReturnType<typeof inputSchema>>
64
-
65
- const searchResultSchema = lazySchema(() => {
66
- const searchHitSchema = z.object({
67
- title: z.string(),
68
- url: z.string(),
69
- snippet: z.string().optional(),
70
- })
71
-
72
- return z.object({
73
- tool_use_id: z.string(),
74
- content: z.array(searchHitSchema),
75
- })
76
- })
77
-
78
- export type SearchResult = z.infer<ReturnType<typeof searchResultSchema>>
79
-
80
- const outputSchema = lazySchema(() =>
81
- z.object({
82
- query: z.string(),
83
- results: z.array(z.union([searchResultSchema(), z.string()])),
84
- durationSeconds: z.number(),
85
- }),
86
- )
87
-
88
- export type Output = z.infer<ReturnType<typeof outputSchema>>
89
-
90
- export type { WebSearchProgress } from '../../types/tools.js'
91
- import type { WebSearchProgress } from '../../types/tools.js'
92
-
93
- /**
94
- * ✅ 使用 SearXNG 本地搜索
95
- */
96
- async function searchSearXNG(
97
- query: string
98
- ): Promise<Array<{ title: string; url: string; snippet?: string }>> {
99
- logToMarkdown('INFO', '开始搜索 SearXNG')
100
- logToMarkdown('DEBUG', '搜索查询', query)
101
-
102
- // 断言:查询参数必须有效
103
- console.assert(
104
- typeof query === 'string' && query.trim().length > 0,
105
- `[WebSearch] ❌ 无效的查询参数: ${JSON.stringify(query)}`
106
- )
107
-
108
- try {
109
- const url = new URL('http://localhost:8080/search')
110
- url.searchParams.set('q', query)
111
- url.searchParams.set('format', 'json')
112
-
113
- const finalUrl = url.toString()
114
- logToMarkdown('DEBUG', '请求 URL', finalUrl)
115
-
116
- const controller = new AbortController()
117
- const timeout = setTimeout(() => controller.abort(), 10000)
118
-
119
- logToMarkdown('INFO', '发起 HTTP 请求 (10秒超时)')
120
- const startTime = Date.now()
121
- const res = await fetch(finalUrl, {
122
- signal: controller.signal,
123
- headers: {
124
- 'User-Agent': 'Mozilla/5.0 (compatible; WebSearchTool/1.0)',
125
- 'Accept': 'application/json',
126
- },
127
- })
128
- const elapsed = Date.now() - startTime
129
-
130
- clearTimeout(timeout)
131
-
132
- const statusInfo = `${res.status} ${res.statusText} (${elapsed}ms)`
133
- logToMarkdown('INFO', 'HTTP 响应状态', statusInfo)
134
- logToMarkdown('DEBUG', '响应头 Content-Type', res.headers.get('content-type'))
135
-
136
- // 断言:HTTP 状态码应该是 2xx
137
- console.assert(
138
- res.ok,
139
- `[WebSearch] ❌ HTTP 请求失败: ${res.status} ${res.statusText}`
140
- )
141
-
142
- if (!res.ok) {
143
- const errorText = await res.text()
144
- logToMarkdown('ERROR', '错误响应内容', errorText)
145
- throw new Error(`HTTP ${res.status}: ${errorText}`)
146
- }
147
-
148
- logToMarkdown('INFO', '解析 JSON 响应')
149
- const data = await res.json()
150
-
151
- const resultStats = {
152
- query: data.query,
153
- totalResults: data.number_of_results,
154
- resultsCount: data.results?.length || 0,
155
- }
156
- logToMarkdown('INFO', '返回结果统计', resultStats)
157
-
158
- // 断言:响应数据应该包含 results 数组
159
- console.assert(
160
- Array.isArray(data.results),
161
- `[WebSearch] ❌ 响应数据格式错误: results 不是数组, 类型: ${typeof data.results}`
162
- )
163
-
164
- const results = (data.results || [])
165
- .slice(0, 10)
166
- .map((r: any, index: number) => {
167
- // 断言:每个结果必须有 title 和 url
168
- console.assert(
169
- r.title && r.url,
170
- `[WebSearch] ⚠️ 结果 ${index} 缺少必要字段: ${JSON.stringify(r)}`
171
- )
172
-
173
- return {
174
- title: r.title,
175
- url: r.url,
176
- snippet: r.content,
177
- }
178
- })
179
-
180
- logToMarkdown('INFO', `搜索完成,返回 ${results.length} 条结果`)
181
- return results
182
- } catch (error) {
183
- const errorMessage = error instanceof Error ? error.message : String(error)
184
- const errorStack = error instanceof Error ? error.stack : undefined
185
-
186
- logToMarkdown('ERROR', '搜索失败', errorMessage)
187
- if (errorStack) {
188
- logToMarkdown('ERROR', '错误堆栈', errorStack)
189
- }
190
-
191
- logError('SearXNG search failed', error)
192
-
193
- // 根据错误类型提供更详细的诊断信息
194
- if (error instanceof Error) {
195
- if (error.name === 'AbortError') {
196
- const timeoutReasons = [
197
- 'SearXNG 服务响应过慢',
198
- '网络连接问题',
199
- 'SearXNG 服务未运行'
200
- ]
201
- logToMarkdown('ERROR', '请求超时 (10秒),可能原因', timeoutReasons)
202
- } else if (error.message.includes('ECONNREFUSED')) {
203
- const connectionReasons = [
204
- 'SearXNG 服务未启动',
205
- '端口 8080 未开放',
206
- '防火墙阻止连接'
207
- ]
208
- logToMarkdown('ERROR', '连接被拒绝,可能原因', connectionReasons)
209
- } else if (error.message.includes('HTTP 403')) {
210
- const forbiddenReasons = [
211
- 'SearXNG 配置不允许 JSON 格式',
212
- 'IP 被限制',
213
- '需要认证'
214
- ]
215
- logToMarkdown('ERROR', 'HTTP 403 禁止访问,可能原因', forbiddenReasons)
216
- }
217
- }
218
-
219
- throw new Error(
220
- `SearXNG search failed: ${errorMessage}`
221
- )
222
- }
223
- }
224
-
225
- /**
226
- * 文本清洗
227
- */
228
- function stripTags(text: string): string {
229
- return text
230
- .replace(/<script[\s\S]*?<\/script>/gi, '')
231
- .replace(/<style[\s\S]*?<\/style>/gi, '')
232
- .replace(/<[^>]+>/g, '')
233
- .replace(/&amp;/g, '&')
234
- .replace(/&lt;/g, '<')
235
- .replace(/&gt;/g, '>')
236
- .replace(/&quot;/g, '"')
237
- .replace(/&#39;/g, "'")
238
- .replace(/&nbsp;/g, ' ')
239
- .trim()
240
- }
241
-
242
- function normalizeText(text: string): string {
243
- return text.replace(/[ \t]+/g, ' ').replace(/\n{3,}/g, '\n\n').trim()
244
- }
245
-
246
- function cleanSearchResult(result: any) {
247
- return {
248
- title: result.title ? normalizeText(stripTags(result.title)) : undefined,
249
- snippet: result.snippet ? normalizeText(stripTags(result.snippet)) : undefined,
250
- }
251
- }
252
-
253
- export const WebSearchTool = buildTool({
254
- name: WEB_SEARCH_TOOL_NAME,
255
- description: 'Search the web using local SearXNG',
256
-
257
- getToolUseSummary,
258
- getActivityDescription(input) {
259
- return input?.query ? `Searching for "${input.query}"` : 'Searching the web'
260
- },
261
-
262
- isEnabled() {
263
- const enabled = true
264
- logToMarkdown('DEBUG', `isEnabled() = ${enabled}`)
265
- return enabled
266
- },
267
-
268
- get inputSchema() {
269
- return inputSchema()
270
- },
271
-
272
- get outputSchema() {
273
- return outputSchema()
274
- },
275
-
276
- isConcurrencySafe() {
277
- return true
278
- },
279
-
280
- isReadOnly() {
281
- return true
282
- },
283
-
284
- toAutoClassifierInput(input) {
285
- return input?.query ?? ''
286
- },
287
-
288
- async checkPermissions(): Promise<PermissionResult> {
289
- return {
290
- behavior: 'allow',
291
- }
292
- },
293
-
294
- async prompt() {
295
- return getWebSearchPrompt()
296
- },
297
-
298
- renderToolUseMessage,
299
- renderToolUseProgressMessage,
300
- renderToolResultMessage,
301
-
302
- extractSearchText() {
303
- return ''
304
- },
305
-
306
- async validateInput(input) {
307
- logToMarkdown('DEBUG', 'validateInput() 被调用')
308
- logToMarkdown('DEBUG', '输入参数', input)
309
-
310
- if (!input?.query) {
311
- logToMarkdown('ERROR', '验证失败: Missing query')
312
- return { result: false, message: 'Missing query', errorCode: 1 }
313
- }
314
-
315
- logToMarkdown('INFO', '验证通过')
316
- return { result: true }
317
- },
318
-
319
- async call(input, _context, _canUseTool, _parentMessage, onProgress) {
320
- const start = performance.now()
321
-
322
- logToMarkdown('INFO', 'WebSearchTool.call() 被调用')
323
- logToMarkdown('DEBUG', '输入参数', input)
324
-
325
- // 断言:input 必须存在
326
- console.assert(
327
- input !== null && input !== undefined,
328
- `[WebSearch] ❌ input 参数为 null/undefined`
329
- )
330
-
331
- try {
332
- // 验证查询参数
333
- if (!input?.query || input.query.trim() === '') {
334
- logToMarkdown('WARN', '查询参数为空,返回错误')
335
- return {
336
- query: input?.query || '',
337
- results: ['Error: Missing query'],
338
- durationSeconds: (performance.now() - start) / 1000,
339
- }
340
- }
341
-
342
- // 断言:查询长度必须至少 2 个字符(根据 inputSchema 定义)
343
- console.assert(
344
- input.query.trim().length >= 2,
345
- `[WebSearch] ❌ 查询长度不足: "${input.query}" (长度: ${input.query.length})`
346
- )
347
-
348
- logToMarkdown('INFO', `查询参数验证通过: "${input.query}"`)
349
-
350
- if (onProgress) {
351
- logToMarkdown('DEBUG', '发送进度更新')
352
- onProgress({
353
- toolUseID: 'search-start',
354
- data: { type: 'query_update', query: input.query },
355
- })
356
- }
357
-
358
- logToMarkdown('INFO', '调用 searchSearXNG()')
359
- const results = await searchSearXNG(input.query)
360
-
361
- logToMarkdown('INFO', '清洗搜索结果')
362
- const cleaned = results.map(r => {
363
- const cleanedResult = cleanSearchResult(r)
364
- logToMarkdown('DEBUG', '清洗结果', {
365
- originalTitle: r.title,
366
- cleanedTitle: cleanedResult.title,
367
- hasSnippet: !!r.snippet,
368
- })
369
- return {
370
- ...r,
371
- ...cleanedResult,
372
- }
373
- })
374
-
375
- const output =
376
- cleaned.length === 0
377
- ? [`No results for: ${input.query}`]
378
- : [
379
- {
380
- tool_use_id: 'search-1',
381
- content: cleaned,
382
- },
383
- ]
384
-
385
- const duration = (performance.now() - start) / 1000
386
-
387
- const finalStats = {
388
- query: input.query,
389
- resultsCount: cleaned.length,
390
- durationSeconds: duration.toFixed(3),
391
- }
392
- logToMarkdown('INFO', '搜索成功完成')
393
- logToMarkdown('INFO', '最终统计', finalStats)
394
-
395
- return {
396
- query: input.query,
397
- results: output,
398
- durationSeconds: duration,
399
- }
400
- } catch (error) {
401
- const duration = (performance.now() - start) / 1000
402
- const errorMessage = error instanceof Error ? error.message : String(error)
403
-
404
- logToMarkdown('ERROR', 'WebSearchTool.call() 发生异常')
405
- logToMarkdown('ERROR', '异常信息', errorMessage)
406
- if (error instanceof Error && error.stack) {
407
- logToMarkdown('ERROR', '异常堆栈', error.stack)
408
- }
409
-
410
- logError(error)
411
-
412
- return {
413
- query: input?.query || '',
414
- results: [`Error: ${errorMessage}`],
415
- durationSeconds: duration,
416
- }
417
- }
418
- },
419
-
420
- mapToolResultToToolResultBlockParam(output, toolUseID) {
421
- if (!output) {
422
- return {
423
- tool_use_id: toolUseID,
424
- type: 'tool_result',
425
- content: 'Error',
426
- }
427
- }
428
-
429
- let text = `Results for "${output.query}"\n\n`
430
-
431
- for (const r of output.results) {
432
- if (typeof r === 'string') {
433
- text += r + '\n\n'
434
- } else {
435
- r.content.forEach((item: any, i: number) => {
436
- text += `${i + 1}. ${item.title}\n${item.url}\n${item.snippet || ''}\n\n`
437
- })
438
- }
439
- }
440
-
441
- return {
442
- tool_use_id: toolUseID,
443
- type: 'tool_result',
444
- content: text,
445
- }
446
- },
447
- }) satisfies ToolDef<any, Output, WebSearchProgress>