Files
ChatLab/electron/main/ai/tools/definitions/semantic-search-messages.ts
digua f7c427df50 refactor(tools): modularize tool system with AgentTool + TypeBox + i18n
- Delete monolithic registry.ts (−1185 lines)
- Add tools/definitions/ with 12 individual tool files + index.ts,
  each using AgentTool interface and TypeBox schemas
- Add tools/utils/ with shared helpers (format.ts, schemas.ts, time-params.ts)
- Rewrite tools/index.ts to provide getAllTools() factory
- Clean up tools/types.ts, keep only ToolContext and OwnerInfo
- Use i18n keys for tool descriptions, preserve Chinese as comments
2026-02-26 21:05:39 +08:00

83 lines
3.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { Type } from '@mariozechner/pi-ai'
import type { AgentTool } from '@mariozechner/pi-agent-core'
import type { ToolContext } from '../types'
import { executeSemanticPipeline, isEmbeddingEnabled } from '../../rag'
import { getDbPath } from '../../../database/core'
import { parseExtendedTimeParams } from '../utils/time-params'
import { formatTimeRange, isChineseLocale } from '../utils/format'
import { timeParamPropertiesNoHour } from '../utils/schemas'
const schema = Type.Object({
query: Type.String({ description: 'ai.tools.semantic_search_messages.params.query' }),
top_k: Type.Optional(Type.Number({ description: 'ai.tools.semantic_search_messages.params.top_k' })),
candidate_limit: Type.Optional(
Type.Number({ description: 'ai.tools.semantic_search_messages.params.candidate_limit' })
),
...timeParamPropertiesNoHour,
})
/** 使用 Embedding 向量相似度搜索历史对话,理解语义而非关键词匹配。⚠️ 使用场景(优先使用 search_messages 关键词搜索以下场景再考虑本工具1. 找"类似的话"或"类似的表达":如"有没有说过类似'我想你了'这样的话" 2. 关键词搜索结果不足:当 search_messages 返回结果太少或不相关时,可用本工具补充 3. 模糊的情感/关系分析:如"对方对我的态度是怎样的"、"我们之间的氛围"。❌ 不适合的场景(请用 search_messages有明确关键词的搜索如"旅游"、"生日"、"加班")、查找特定人物的发言、查找特定时间段的消息 */
export function createTool(context: ToolContext): AgentTool<typeof schema> {
return {
name: 'semantic_search_messages',
label: 'semantic_search_messages',
description: 'ai.tools.semantic_search_messages.desc',
parameters: schema,
execute: async (_toolCallId, params) => {
const { sessionId, timeFilter: contextTimeFilter, locale } = context
let data: Record<string, unknown>
if (!isEmbeddingEnabled()) {
data = {
error: isChineseLocale(locale)
? '语义搜索未启用。请在设置中添加并启用 Embedding 配置。'
: 'Semantic search is not enabled. Please add and enable an Embedding config in settings.',
}
} else {
const effectiveTimeFilter = parseExtendedTimeParams(params, contextTimeFilter)
const dbPath = getDbPath(sessionId)
const result = await executeSemanticPipeline({
userMessage: params.query,
dbPath,
timeFilter: effectiveTimeFilter,
candidateLimit: params.candidate_limit,
topK: params.top_k,
})
if (!result.success) {
data = {
error: result.error || (isChineseLocale(locale) ? '语义搜索失败' : 'Semantic search failed'),
}
} else if (result.results.length === 0) {
data = {
message: isChineseLocale(locale) ? '未找到相关的历史对话' : 'No relevant conversations found',
rewrittenQuery: result.rewrittenQuery,
}
} else {
data = {
total: result.results.length,
rewrittenQuery: result.rewrittenQuery,
timeRange: formatTimeRange(effectiveTimeFilter, locale),
results: result.results.map((r, i) => ({
rank: i + 1,
score: `${(r.score * 100).toFixed(1)}%`,
sessionId: r.metadata?.sessionId,
timeRange: r.metadata
? formatTimeRange({ startTs: r.metadata.startTs, endTs: r.metadata.endTs }, locale)
: undefined,
participants: r.metadata?.participants,
content: r.content.length > 500 ? r.content.slice(0, 500) + '...' : r.content,
})),
}
}
}
return {
content: [{ type: 'text', text: JSON.stringify(data) }],
details: data,
}
},
}
}