Files
map-client-vue/CHAT_404_FIX.md
2025-10-14 21:52:11 +08:00

9.7 KiB

聊天404错误修复报告

问题描述

用户在发送聊天消息时遇到404错误:

Failed to load resource: the server responded with a status of 404 () (completions, line 0)

根本原因分析

问题1: 服务选择逻辑错误

位置: /web/src/services/chatService.ts 第492-530行 callModel() 方法

原因:

  • 用户在聊天界面选择了特定的AI模型(例如: doubao-seed-1-6-flash-250828)
  • callModel() 方法接收到 model 参数,但没有根据模型名称查找对应的服务
  • 代码直接使用 services[0](第一个连接的服务)
  • 如果第一个服务没有该模型,就会发送错误的请求

示例场景:

  1. 用户配置了两个服务: DashScope 和 Volcengine
  2. DashScope先连接,成为 services[0]
  3. 用户在聊天界面选择 Volcengine 的模型 doubao-seed-1-6-flash-250828
  4. 代码将这个模型发送给 DashScope 服务
  5. DashScope 不认识这个模型,返回404或其他错误

问题2: 缺少调试日志

位置:

  • /web/src/services/chatService.ts callModel() 方法
  • /web/src/services/modelServiceManager.ts sendChatRequest()makeChatRequest() 方法

原因:

  • 没有日志输出当前使用的服务和模型
  • 难以追踪请求的完整路径
  • 无法快速定位URL构建问题

解决方案

修复1: 智能服务匹配

文件: /web/src/services/chatService.ts

改动:

// 原代码
const service = services[0] // 使用第一个可用服务
const selectedModel = model || service.models?.[0] || 'default'

// 修复后
let service = services[0] // 默认使用第一个可用服务
let selectedModel = model || service.models?.[0] || 'default'

// 如果指定了模型,尝试找到拥有该模型的服务
if (model) {
  const foundService = services.find(s => 
    s.models && s.models.includes(model)
  )
  if (foundService) {
    service = foundService
    selectedModel = model
  } else {
    console.warn(`⚠️ 未找到包含模型 "${model}" 的服务,使用默认服务`)
  }
}

console.log('🔍 [callModel] 使用服务:', service.name, '模型:', selectedModel)

效果:

  • 根据模型名称自动匹配正确的服务
  • 避免将模型发送给错误的服务
  • 提供降级方案(找不到服务时使用默认)
  • 记录调试日志

修复2: 增强调试日志

文件: /web/src/services/modelServiceManager.ts

2.1 sendChatRequest() 方法

// 添加的日志
console.log('🔍 [sendChatRequest] serviceId:', serviceId, 'service:', service)

// 添加URL验证
if (!service.url || !service.url.startsWith('http')) {
  console.error('❌ [sendChatRequest] 无效的服务URL:', service.url)
  return {
    success: false,
    error: `服务URL无效: ${service.url}`
  }
}

// 添加异常日志
console.error('❌ [sendChatRequest] 请求异常:', error)

2.2 makeChatRequest() 方法

// 请求前日志
console.log('🔍 [makeChatRequest] 服务信息:', {
  type: service.type,
  name: service.name,
  url: service.url,
  model
})

console.log('🔍 [makeChatRequest] 最终请求URL:', url)
console.log('🔍 [makeChatRequest] 请求体:', body)

// 响应日志
console.log('🔍 [makeChatRequest] 响应状态:', response.status, response.statusText)

// 错误日志
console.error('❌ [makeChatRequest] 请求失败:', {
  status: response.status,
  statusText: response.statusText,
  url,
  errorText
})

效果:

  • 完整记录请求流程
  • 输出服务名称、URL、模型
  • 显示最终构建的URL
  • 记录响应状态和错误详情
  • 使用emoji图标便于快速识别

验证步骤

  1. 配置多个服务:

    • 添加 DashScope 服务(阿里云通义千问)
    • 添加 Volcengine 服务(字节跳动豆包)
    • 确保两个服务都已连接
  2. 测试模型匹配:

    • 在聊天界面选择 Volcengine 的模型(如 doubao-seed-1-6-flash-250828)
    • 发送消息
    • 打开浏览器控制台
    • 应该看到日志: 🔍 [callModel] 使用服务: 火山引擎 模型: doubao-seed-1-6-flash-250828
  3. 测试URL构建:

    • 检查控制台日志中的URL
    • 应该是: https://ark.cn-beijing.volces.com/api/v3/chat/completions
    • 不应该是: /completions 或其他错误格式
  4. 测试错误处理:

    • 暂时输入错误的API Key
    • 应该看到详细的错误日志
    • 包括状态码、URL、错误响应

预期效果

修复后,用户应该能够:

  • 在聊天界面选择任意已配置服务的模型
  • 系统自动找到正确的服务发送请求
  • 看到清晰的调试日志(便于问题追踪)
  • 收到正确的AI回复
  • 不再看到404错误

相关文件

修改的文件

  1. /web/src/services/chatService.ts

    • 修改 callModel() 方法
    • 新增服务匹配逻辑
  2. /web/src/services/modelServiceManager.ts

    • 修改 sendChatRequest() 方法
    • 修改 makeChatRequest() 方法
    • 新增URL验证
    • 新增调试日志

涉及的方法调用链

ChatLayout.vue (用户发送消息)
  ↓
chatService.sendMessageStream(model: string)
  ↓
chatService.callModelStream(model: string)
  ↓
chatService.callModel(model: string) ← 修复点1: 服务匹配
  ↓
modelServiceManager.sendChatRequest(serviceId, messages, model) ← 修复点2: 日志
  ↓
modelServiceManager.makeChatRequest(service, messages, model) ← 修复点3: 日志
  ↓
fetch(url) → AI服务API

技术细节

服务匹配算法

// 查找包含指定模型的服务
const foundService = services.find(s => 
  s.models && s.models.includes(model)
)

特点:

  • 使用 Array.find() 查找第一个匹配的服务
  • 检查 s.models 存在性(避免空指针)
  • 使用 includes() 精确匹配模型名称
  • 找不到时提供降级方案

URL构建规则

不同服务类型的endpoint:

  • OpenAI/Local: {baseUrl}/chat/completions
  • DashScope: {baseUrl}/chat/completions
  • Volcengine: {baseUrl}/chat/completions
  • Claude: {baseUrl}/messages
  • Gemini: {baseUrl}/models/{model}:generateContent?key={apiKey}
  • Azure: {baseUrl}/openai/deployments/{model}/chat/completions?api-version=2023-12-01-preview

预设URL

const defaultUrls = {
  openai: 'https://api.openai.com/v1',
  claude: 'https://api.anthropic.com/v1',
  gemini: 'https://generativelanguage.googleapis.com/v1',
  azure: 'https://your-resource.openai.azure.com',
  dashscope: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
  volcengine: 'https://ark.cn-beijing.volces.com/api/v3',
  local: 'http://localhost:1234/v1'
}

后续优化建议

1. 缓存服务-模型映射

目的: 避免每次都遍历查找

// 在 modelServiceManager 中维护映射
private modelToServiceMap: Map<string, string> = new Map()

// 更新映射
updateModelMapping() {
  this.modelToServiceMap.clear()
  this.services.forEach(service => {
    service.models?.forEach(model => {
      this.modelToServiceMap.set(model, service.id)
    })
  })
}

// 快速查找
getServiceByModel(model: string): ModelService | undefined {
  const serviceId = this.modelToServiceMap.get(model)
  return serviceId ? this.services.get(serviceId) : undefined
}

2. 用户选择优先级

目的: 当多个服务有相同模型时,使用用户最后使用的服务

private lastUsedService: Map<string, string> = new Map() // model -> serviceId

callModel(conversation, model) {
  // 优先使用用户最后一次使用的服务
  const lastServiceId = this.lastUsedService.get(model)
  let service = services.find(s => s.id === lastServiceId) || 
                services.find(s => s.models?.includes(model)) ||
                services[0]
  
  // 记录使用历史
  this.lastUsedService.set(model, service.id)
}

3. 模型别名支持

目的: 支持同一模型的不同名称

private modelAliases: Map<string, string[]> = new Map([
  ['gpt-4', ['gpt-4-0613', 'gpt-4-32k']],
  ['doubao', ['doubao-seed-1-6', 'doubao-seed-1-6-flash']]
])

findServiceByModel(model: string): ModelService | undefined {
  // 尝试直接匹配
  let service = services.find(s => s.models?.includes(model))
  
  // 尝试别名匹配
  if (!service) {
    const aliases = this.modelAliases.get(model) || []
    service = services.find(s => 
      s.models?.some(m => aliases.includes(m))
    )
  }
  
  return service
}

4. 更智能的错误提示

目的: 帮助用户快速定位配置问题

if (!foundService) {
  const availableModels = services
    .flatMap(s => s.models || [])
    .join(', ')
  
  throw new Error(
    `未找到支持模型 "${model}" 的服务。\n` +
    `当前可用模型: ${availableModels}\n` +
    `请检查模型服务配置或选择其他模型。`
  )
}

测试场景

场景1: 单服务单模型

  • 配置: 1个DashScope服务
  • 操作: 选择 qwen-turbo
  • 预期: 正常工作

场景2: 多服务不同模型

  • 配置: DashScope + Volcengine
  • 操作: 交替选择两个服务的模型
  • 预期: 自动切换服务,都能正常工作

场景3: 多服务相同模型名

  • 配置: 两个OpenAI兼容服务,都有 gpt-3.5-turbo
  • 操作: 选择 gpt-3.5-turbo
  • 预期: 使用第一个找到的服务(可后续优化为用户选择)

场景4: 模型不存在

  • 配置: DashScope服务
  • 操作: 选择不存在的模型 nonexistent-model
  • 预期: 降级使用默认服务,输出警告日志

场景5: 服务URL错误

  • 配置: 服务URL为空或不含http
  • 操作: 发送消息
  • 预期: 立即返回错误,不发送请求

更新历史

  • 2024-01-XX: 初始版本,修复服务匹配和调试日志问题
  • 待定: 实现缓存和优先级优化