update at 2025-10-15 15:07:45

This commit is contained in:
douboer
2025-10-15 15:07:45 +08:00
parent eb8fb51283
commit 901d00e4e1
21 changed files with 4030 additions and 57 deletions

View File

@@ -301,7 +301,8 @@ class ChatService {
async sendMessageStream(
options: SendMessageOptions,
onChunk: (event: StreamEvent) => void,
mcpServerId?: string // 新增:可选的 MCP 服务器 ID
mcpServerId?: string, // 新增:可选的 MCP 服务器 ID
signal?: AbortSignal // 新增:取消信号
): Promise<void> {
const { topicId, content, role = 'user', model } = options
@@ -378,7 +379,8 @@ class ChatService {
this.saveConversations()
onChunk({ type: 'delta', content: chunk, messageId: assistantMessage.id })
},
mcpServerId // 传递 MCP 服务器 ID
mcpServerId, // 传递 MCP 服务器 ID
signal // 传递取消信号
)
assistantMessage.status = 'success'
@@ -396,14 +398,41 @@ class ChatService {
this.saveTopics()
}
} catch (error) {
assistantMessage.status = 'error'
assistantMessage.error = error instanceof Error ? error.message : '发送失败'
// 检查是否是用户主动取消(参考 cherry-studio 的 PAUSED 状态)
const isAborted = error instanceof Error && error.name === 'AbortError'
if (isAborted) {
// 用户主动停止,保留已生成的内容,状态标记为 paused
console.log('⏸️ [sendMessageStream] 用户主动停止生成,保留已生成内容')
assistantMessage.status = 'paused'
assistantMessage.error = undefined // 清除错误信息
} else {
// 其他错误
assistantMessage.status = 'error'
assistantMessage.error = error instanceof Error ? error.message : '发送失败'
}
conversation.updatedAt = new Date()
this.conversations.set(conversation.id, conversation)
this.saveConversations()
onChunk({
type: 'error',
error: assistantMessage.error,
messageId: assistantMessage.id
})
if (isAborted) {
onChunk({ type: 'paused', messageId: assistantMessage.id })
// 更新话题(暂停)
if (topic) {
topic.messageCount = conversation.messages.length
topic.lastMessage = this.getMessagePreview(assistantMessage.content)
topic.updatedAt = new Date()
this.topics.set(topicId, topic)
this.saveTopics()
}
} else {
onChunk({
type: 'error',
error: assistantMessage.error,
messageId: assistantMessage.id
})
}
}
}
@@ -583,31 +612,57 @@ class ChatService {
conversation: Conversation,
model: string | undefined,
onChunk: (chunk: string) => void,
mcpServerId?: string // 可选的 MCP 服务器 ID
mcpServerId?: string, // 可选的 MCP 服务器 ID
signal?: AbortSignal // 取消信号
): Promise<void> {
const streamStartTime = performance.now()
console.log('⏱️ [callModelStream] 开始真流式处理')
// 获取 MCP 工具列表(如果选择了 MCP 服务器)
let tools: any[] = []
let mcpServerName = ''
if (mcpServerId) {
console.log('🔧 [callModelStream] 获取 MCP 服务器工具:', mcpServerId)
const mcpTools = this.mcpClient.getTools(mcpServerId)
const serverInfo = this.mcpClient.getServerInfo(mcpServerId)
mcpServerName = serverInfo?.name || 'mcp'
console.log('🔧 [callModelStream] MCP 服务器名称:', mcpServerName)
console.log('🔧 [callModelStream] MCP 原始工具列表:', mcpTools)
tools = this.convertToolsToOpenAIFormat(mcpTools)
tools = this.convertToolsToOpenAIFormat(mcpTools, mcpServerName)
console.log('🔧 [callModelStream] 转换后的工具:', tools.length, '个', tools)
} else {
console.log('⚠️ [callModelStream] 未选择 MCP 服务器,不注入工具')
}
// 准备消息历史
const messages = conversation.messages
let messages = conversation.messages
.filter(m => m.status === 'success')
.map(m => ({
role: m.role,
content: m.content
}))
// 如果有工具,添加系统提示词指导 AI 使用工具
if (tools.length > 0 && messages.length > 0 && messages[0].role !== 'system') {
const systemPrompt = this.createSystemPromptWithTools(tools, mcpServerName)
messages = [
{ role: 'system', content: systemPrompt },
...messages
]
}
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')
console.log('🎯 [callModelStream] === 完整的消息列表 ===')
console.log(' 消息总数:', messages.length)
messages.forEach((msg, idx) => {
console.log(` 消息 [${idx}]:`, {
role: msg.role,
content: msg.content?.substring(0, 100) + (msg.content?.length > 100 ? '...' : ''),
contentLength: msg.content?.length || 0
})
})
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')
// 获取已连接的服务
const allServices = modelServiceManager.getAllServices()
const services = allServices.filter(s => s.status === 'connected')
@@ -673,7 +728,8 @@ class ChatService {
onChunk(output)
}
},
tools.length > 0 ? tools : undefined
tools.length > 0 ? tools : undefined,
signal // 传递取消信号
)
// 输出剩余的缓冲区内容
@@ -690,9 +746,21 @@ class ChatService {
}
// 处理工具调用
console.log('🔍 [callModelStream] 检查工具调用:', {
hasData: !!result.data,
hasToolCalls: !!result.data?.toolCalls,
toolCallsCount: result.data?.toolCalls?.length || 0,
hasMcpServerId: !!mcpServerId,
mcpServerId,
toolCalls: result.data?.toolCalls
})
if (result.data?.toolCalls && result.data.toolCalls.length > 0 && mcpServerId) {
console.log('🔧 [callModelStream] 开始执行工具调用')
await this.executeToolCalls(conversation, result.data.toolCalls, mcpServerId, model, onChunk)
console.log('🔧 [callModelStream] 开始执行工具调用,共', result.data.toolCalls.length, '个')
// 传递 tools 参数,让 AI 可以继续调用其他工具
await this.executeToolCalls(conversation, result.data.toolCalls, mcpServerId, model, onChunk, tools)
} else {
console.log('⚠️ [callModelStream] 没有工具调用需要执行')
}
const endTime = performance.now()
@@ -821,13 +889,73 @@ class ChatService {
}
/**
* 将 MCP 工具转换为 OpenAI 函数调用格式
* 创建包含工具信息的系统提示词
* @param tools OpenAI 格式的工具列表
* @param serverName MCP 服务器名称
*/
private convertToolsToOpenAIFormat(mcpTools: any[]): any[] {
private createSystemPromptWithTools(tools: any[], serverName: string): string {
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')
console.log('📝 [createSystemPromptWithTools] 开始生成 System Prompt')
console.log(' - 服务器名称:', serverName)
console.log(' - 工具数量:', tools.length)
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')
const toolDescriptions = tools.map(tool => {
const func = tool.function
const params = func.parameters?.properties || {}
const required = func.parameters?.required || []
console.log(` 工具: ${func.name}`)
console.log(` 描述: ${func.description}`)
// 生成参数描述
const paramDesc = Object.entries(params).map(([name, schema]: [string, any]) => {
const isRequired = required.includes(name)
const requiredMark = isRequired ? '[必填]' : '[可选]'
return ` - ${name} ${requiredMark}: ${schema.description || schema.type}`
}).join('\n')
return `${func.name}\n 描述: ${func.description}\n 参数:\n${paramDesc || ' 无参数'}`
}).join('\n\n')
const systemPrompt = `你是一个智能助手,可以使用以下工具完成任务:
${toolDescriptions}
使用指南:
1. 当用户需要完成某个任务时,请分析哪个工具最合适
2. 如果需要发布内容(如文章、笔记等),请根据用户意图创作完整的内容
3. 为内容生成合适的标题、正文、标签等所有必需参数
4. 自动调用相应工具,将生成的内容作为参数传递
5. 根据工具执行结果,给用户友好的反馈
注意事项:
- **标题必须控制在20字以内**(重要!超过会导致发布失败)
- 保持内容质量和平台特色
- 标签要相关且有吸引力
- 分类要准确
- 如果工具执行失败,给出明确的错误说明和建议
当前连接的 MCP 服务器: ${serverName}`
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')
console.log('📝 [createSystemPromptWithTools] === System Prompt 内容 ===')
console.log(systemPrompt)
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')
return systemPrompt
}
/**
* 将 MCP 工具转换为 OpenAI 函数调用格式
* @param mcpTools MCP 工具列表
* @param serverName 服务器名称,用于工具名称前缀
*/
private convertToolsToOpenAIFormat(mcpTools: any[], serverName: string): any[] {
return mcpTools.map(tool => ({
type: 'function',
function: {
name: tool.name,
name: `${serverName}__${tool.name}`, // 添加服务器前缀避免冲突
description: tool.description || '',
parameters: tool.inputSchema || {
type: 'object',
@@ -846,7 +974,8 @@ class ChatService {
toolCalls: any[],
mcpServerId: string,
model: string | undefined,
onChunk: (chunk: string) => void
onChunk: (chunk: string) => void,
tools?: any[] // 添加 tools 参数
): Promise<void> {
console.log('🔧 [executeToolCalls] 执行', toolCalls.length, '个工具调用')
@@ -861,21 +990,32 @@ class ChatService {
const toolResults = []
for (const toolCall of toolCalls) {
try {
const functionName = toolCall.function.name
const fullFunctionName = toolCall.function.name
// 解析工具名称serverName__toolName
const toolName = fullFunctionName.includes('__')
? fullFunctionName.split('__')[1]
: fullFunctionName
const functionArgs = JSON.parse(toolCall.function.arguments)
console.log(`🔧 [executeToolCalls] 调用工具: ${functionName}`, functionArgs)
onChunk(`\n\n🔧 正在调用工具: ${functionName}...\n`)
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')
console.log(`🔧 [executeToolCalls] 工具调用详情:`)
console.log(` - 完整工具名: ${fullFunctionName}`)
console.log(` - 提取工具名: ${toolName}`)
console.log(` - MCP服务器ID: ${mcpServerId}`)
console.log(` - 参数:`, JSON.stringify(functionArgs, null, 2))
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')
onChunk(`\n\n🔧 正在调用工具: ${toolName}...\n`)
const result = await this.mcpClient.callTool(mcpServerId, functionName, functionArgs)
const result = await this.mcpClient.callTool(mcpServerId, toolName, functionArgs)
console.log(`✅ [executeToolCalls] 工具调用成功: ${functionName}`, result)
console.log(`✅ [executeToolCalls] 工具调用成功: ${toolName}`, result)
onChunk(`✅ 工具执行完成\n`)
toolResults.push({
tool_call_id: toolCall.id,
role: 'tool',
name: functionName,
name: fullFunctionName, // 保持与 AI 调用时的名称一致
content: JSON.stringify(result)
})
} catch (error) {
@@ -925,14 +1065,24 @@ class ChatService {
// 向 AI 发送工具结果,获取最终回复
console.log('🤖 [executeToolCalls] 将工具结果发送给 AI')
console.log('🔧 [executeToolCalls] 继续传递工具列表:', tools?.length || 0, '个')
onChunk('\n\n🤖 正在生成回复...\n')
await modelServiceManager.sendChatRequestStream(
const result = await modelServiceManager.sendChatRequestStream(
service.id,
messages,
selectedModel,
onChunk
onChunk,
tools // ← 传递工具列表,让 AI 可以继续调用工具
)
// 递归处理:如果 AI 再次调用工具,继续执行
if (result.data?.toolCalls && result.data.toolCalls.length > 0) {
console.log('🔁 [executeToolCalls] AI 再次调用工具,递归执行:', result.data.toolCalls.length, '个')
await this.executeToolCalls(conversation, result.data.toolCalls, mcpServerId, model, onChunk, tools)
} else {
console.log('✅ [executeToolCalls] 工具调用链完成')
}
}
/**