chenbhao commited on
Commit
9fb8bdf
·
1 Parent(s): 1bfd51f

test: WebSearchTool log

Browse files
src/services/telegram/TelegramService.ts CHANGED
@@ -15,8 +15,11 @@ import type {
15
  TelegramUpdate,
16
  } from './telegramTypes.js'
17
 
18
- // 日志函数 - 写入文件
19
  function logTelegramDebug(message: string, level: 'debug' | 'error' | 'info' = 'debug'): void {
 
 
 
20
  try {
21
  const fs = require('node:fs')
22
  const path = require('node:path')
@@ -27,6 +30,7 @@ function logTelegramDebug(message: string, level: 'debug' | 'error' | 'info' = '
27
  } catch (error) {
28
  // 忽略文件写入错误
29
  }
 
30
  }
31
 
32
  type Listener = () => void
 
15
  TelegramUpdate,
16
  } from './telegramTypes.js'
17
 
18
+ // 日志函数 - 已禁用,不再写入文件
19
  function logTelegramDebug(message: string, level: 'debug' | 'error' | 'info' = 'debug'): void {
20
+ // Telegram 功能已完成,禁用日志输出到 log.md
21
+ // 如需调试,可以临时取消注释下面的代码
22
+ /*
23
  try {
24
  const fs = require('node:fs')
25
  const path = require('node:path')
 
30
  } catch (error) {
31
  // 忽略文件写入错误
32
  }
33
+ */
34
  }
35
 
36
  type Listener = () => void
src/tools/WebSearchTool/WebSearchTool.ts CHANGED
@@ -10,6 +10,49 @@ import {
10
  renderToolUseMessage,
11
  renderToolUseProgressMessage,
12
  } from './UI.js'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
 
14
  const inputSchema = lazySchema(() =>
15
  z.strictObject({
@@ -53,44 +96,128 @@ import type { WebSearchProgress } from '../../types/tools.js'
53
  async function searchSearXNG(
54
  query: string
55
  ): Promise<Array<{ title: string; url: string; snippet?: string }>> {
56
- console.log(`[WebSearch] Searching SearXNG for: "${query}"`)
 
 
 
 
 
 
 
57
 
58
  try {
59
  const url = new URL('http://localhost:8080/search')
60
  url.searchParams.set('q', query)
61
  url.searchParams.set('format', 'json')
62
 
 
 
 
63
  const controller = new AbortController()
64
  const timeout = setTimeout(() => controller.abort(), 10000)
65
 
66
- const res = await fetch(url.toString(), {
 
 
67
  signal: controller.signal,
68
  headers: {
69
  'User-Agent': 'Mozilla/5.0 (compatible; WebSearchTool/1.0)',
70
  'Accept': 'application/json',
71
  },
72
  })
 
73
 
74
  clearTimeout(timeout)
75
 
 
 
 
 
 
 
 
 
 
 
76
  if (!res.ok) {
77
- throw new Error(`HTTP ${res.status}`)
 
 
78
  }
79
 
 
80
  const data = await res.json()
81
 
82
- return (data.results || [])
 
 
 
 
 
 
 
 
 
 
 
 
 
83
  .slice(0, 10)
84
- .map((r: any) => ({
85
- title: r.title,
86
- url: r.url,
87
- snippet: r.content,
88
- }))
 
 
 
 
 
 
 
 
 
 
 
89
  } catch (error) {
90
- console.error('[WebSearch] SearXNG failed:', error)
 
 
 
 
 
 
 
91
  logError('SearXNG search failed', error)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92
  throw new Error(
93
- `SearXNG search failed: ${error instanceof Error ? error.message : String(error)}`
94
  )
95
  }
96
  }
@@ -133,7 +260,9 @@ export const WebSearchTool = buildTool({
133
  },
134
 
135
  isEnabled() {
136
- return true
 
 
137
  },
138
 
139
  get inputSchema() {
@@ -175,17 +304,34 @@ export const WebSearchTool = buildTool({
175
  },
176
 
177
  async validateInput(input) {
 
 
 
178
  if (!input?.query) {
 
179
  return { result: false, message: 'Missing query', errorCode: 1 }
180
  }
 
 
181
  return { result: true }
182
  },
183
 
184
  async call(input, _context, _canUseTool, _parentMessage, onProgress) {
185
  const start = performance.now()
186
 
 
 
 
 
 
 
 
 
 
187
  try {
 
188
  if (!input?.query || input.query.trim() === '') {
 
189
  return {
190
  query: input?.query || '',
191
  results: ['Error: Missing query'],
@@ -193,19 +339,38 @@ export const WebSearchTool = buildTool({
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
@@ -217,18 +382,37 @@ export const WebSearchTool = buildTool({
217
  },
218
  ]
219
 
 
 
 
 
 
 
 
 
 
 
220
  return {
221
  query: input.query,
222
  results: output,
223
- durationSeconds: (performance.now() - start) / 1000,
224
  }
225
  } catch (error) {
 
 
 
 
 
 
 
 
 
226
  logError(error)
227
 
228
  return {
229
- query: input.query,
230
- results: [`Error: ${error instanceof Error ? error.message : String(error)}`],
231
- durationSeconds: (performance.now() - start) / 1000,
232
  }
233
  }
234
  },
 
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({
 
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
  },
261
 
262
  isEnabled() {
263
+ const enabled = true
264
+ logToMarkdown('DEBUG', `isEnabled() = ${enabled}`)
265
+ return enabled
266
  },
267
 
268
  get inputSchema() {
 
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'],
 
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
 
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
  },