update at 2025-10-14 21:52:11

This commit is contained in:
douboer
2025-10-14 21:52:11 +08:00
parent ac3ed480ab
commit 4f5eea604e
40 changed files with 15231 additions and 126 deletions

338
CHAT_404_FIX.md Normal file
View File

@@ -0,0 +1,338 @@
# 聊天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`
**改动**:
```typescript
// 原代码
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()` 方法
```typescript
// 添加的日志
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()` 方法
```typescript
// 请求前日志
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
```
## 技术细节
### 服务匹配算法
```typescript
// 查找包含指定模型的服务
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
```typescript
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. 缓存服务-模型映射
**目的**: 避免每次都遍历查找
```typescript
// 在 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. 用户选择优先级
**目的**: 当多个服务有相同模型时,使用用户最后使用的服务
```typescript
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. 模型别名支持
**目的**: 支持同一模型的不同名称
```typescript
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. 更智能的错误提示
**目的**: 帮助用户快速定位配置问题
```typescript
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**: 初始版本,修复服务匹配和调试日志问题
- **待定**: 实现缓存和优先级优化