Files
zclaw_openfang/docs/knowledge-base/troubleshooting.md
iven ae4bf815e3
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
fix(kernel): 使用 Kernel 配置的 model 而非 Agent 持久化的旧值
问题:在"模型与 API"页面切换模型后,对话仍使用旧模型
根因:Agent 配置从数据库恢复,其 model 字段优先于 Kernel 配置

修复:
- kernel.rs: send_message/send_message_stream 始终使用 Kernel 的当前 model
- openai.rs: 添加 User-Agent header 解决 Coding Plan API 405 错误
- kernel_commands.rs: 添加详细调试日志便于追踪配置传递
- troubleshooting.md: 记录此问题的排查过程和解决方案

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-23 22:56:06 +08:00

28 KiB
Raw Blame History

故障排查指南

记录开发过程中遇到的问题、根因分析和解决方案。


1. 连接问题

1.1 WebSocket 连接失败

症状: WebSocket connection failedUnexpected server response: 400

排查步骤:

# 1. 检查 OpenFang 是否运行
curl http://127.0.0.1:50051/api/health

# 2. 检查端口是否正确
netstat -ano | findstr "50051"

# 3. 验证 Agent ID
curl http://127.0.0.1:50051/api/agents

常见原因:

原因 解决方案
OpenFang 未启动 ./openfang.exe start
端口错误 OpenFang 使用 50051不是 4200
Agent ID 无效 使用 /api/agents 获取真实 UUID

1.2 端口被占用

症状: Port 1420 is already in use

解决方案:

# Windows - 查找并终止进程
netstat -ano | findstr "1420"
taskkill /PID <PID> /F

# 或使用 PowerShell
Stop-Process -Id <PID> -Force

1.3 Vite 代理不工作

症状: 前端请求返回 404 或 CORS 错误

检查清单:

  • vite.config.ts 中配置了 /api 代理
  • ws: true 已启用WebSocket 需要)
  • changeOrigin: true 已设置
  • 重启 Vite 开发服务器

2. 聊天问题

2.1 LLM API Key 未配置

症状:

Missing API key: No LLM provider configured.
Set an API key (e.g. GROQ_API_KEY) and restart

根本原因: Agent 使用的 LLM 提供商没有配置 API Key

解决方案:

  1. 检查 Agent 使用的提供商:
curl -s http://127.0.0.1:50051/api/status | jq '.agents[] | {name, model_provider}'
  1. 配置对应的 API Key
# 编辑 ~/.openfang/.env
echo "ZHIPU_API_KEY=your_key" >> ~/.openfang/.env
echo "BAILIAN_API_KEY=your_key" >> ~/.openfang/.env
echo "GEMINI_API_KEY=your_key" >> ~/.openfang/.env
  1. 重启 OpenFang
./openfang.exe restart

快速解决: 使用已配置的 Agent

Agent 提供商 状态
General Assistant zhipu 通常已配置

2.1.1 配置热重载限制(重要)

症状: 修改 config.toml 后,/api/config/api/status 仍然返回旧配置

根本原因: OpenFang 将配置持久化在 SQLite 数据库中,config.toml 只在启动时读取

验证问题:

# 检查 config.toml 内容
cat ~/.openfang/config.toml
# 输出: provider = "zhipu"

# 检查 API 返回的配置
curl -s http://127.0.0.1:50051/api/config
# 输出: {"default_model":{"provider":"bailian",...}}  # 不一致!

解决方案:

  1. 必须完全重启 OpenFang(热重载 /api/config/reload 不会更新持久化配置)
# 方法 1: 通过 API 关闭(然后手动重启)
curl -X POST http://127.0.0.1:50051/api/shutdown

# 方法 2: 使用 CLI
./openfang.exe stop
./openfang.exe start
  1. 验证配置已生效:
curl -s http://127.0.0.1:50051/api/status | grep -E "default_provider|default_model"
# 应输出: "default_provider":"zhipu"

配置文件位置:

文件 用途
~/.openfang/config.toml 主配置(启动时读取)
~/.openfang/.env API Key 环境变量
~/.openfang/secrets.env 敏感信息
~/.openfang/data/openfang.db SQLite 数据库(持久化配置)

支持的 Provider:

Provider 环境变量 模型示例
zhipu ZHIPU_API_KEY glm-4-flash, GLM-4-Plus
bailian BAILIAN_API_KEY qwen3.5-plus, qwen3-coder-next
gemini GEMINI_API_KEY gemini-2.5-flash
deepseek DEEPSEEK_API_KEY deepseek-chat
openai OPENAI_API_KEY gpt-4, gpt-3.5-turbo

2.2 Agent ID 获取失败导致无法对话

症状: Gateway 显示已连接,但发送消息无响应或报错 "No agent available"

根本原因: fetchDefaultAgentId() 使用错误的 API 端点

错误代码:

// ❌ 错误 - /api/status 不返回 agents 字段
const status = await this.restGet('/api/status');
if (status?.agents && status.agents.length > 0) { ... }

修复代码:

// ✅ 正确 - 使用 /api/agents 端点
const agents = await this.restGet<Array<{ id: string; name?: string; state?: string }>>('/api/agents');
if (agents && agents.length > 0) {
  const runningAgent = agents.find(a => a.state === 'Running');
  this.defaultAgentId = (runningAgent || agents[0]).id;
}

历史背景: 这个问题与 OpenClaw 的握手认证问题类似,但根因不同:

  • OpenClaw: 需要 cli/cli/operator 身份 + Ed25519 配对
  • OpenFang: 不需要认证握手,但需要正确的 Agent UUID

验证修复:

# 确认 /api/agents 返回数据
curl http://127.0.0.1:50051/api/agents
# 应返回: [{ "id": "uuid", "name": "...", "state": "Running" }]

2.3 流式响应不显示

症状: 消息发送后无响应或响应不完整

排查步骤:

  1. 确认 WebSocket 连接状态:
console.log(client.getState()); // 应为 'connected'
  1. 检查事件处理:
// 确保处理了 text_delta 事件
ws.on('message', (data) => {
  const event = JSON.parse(data.toString());
  if (event.type === 'text_delta') {
    console.log('Delta:', event.content);
  }
});
  1. 验证消息格式:
// ✅ 正确
{ type: 'message', content: 'Hello', session_id: 'xxx' }

// ❌ 错误
{ type: 'chat', message: { role: 'user', content: 'Hello' } }

2.3 消息格式错误

症状: WebSocket 连接成功,但发送消息后收到错误

根本原因: 使用了文档中的格式,而非实际格式

正确的消息格式:

{
  "type": "message",
  "content": "你的消息内容",
  "session_id": "唯一会话ID"
}

3. 前端问题

3.1 Zustand 状态不更新

症状: UI 不反映状态变化

检查:

  1. 确保使用 selector
// ✅ 正确 - 使用 selector
const messages = useChatStore((state) => state.messages);

// ❌ 错误 - 可能导致不必要的重渲染
const store = useChatStore();
const messages = store.messages;
  1. 检查 immer/persist 配置

3.2 切换 Agent 后对话消失

症状: 点击其他 Agent 后,之前的对话内容丢失

根本原因: setCurrentAgent 切换 Agent 时清空了 messages,但没有恢复该 Agent 之前的对话

解决方案:

修改 chatStore.ts 中的 setCurrentAgent 函数:

setCurrentAgent: (agent) =>
  set((state) => {
    if (state.currentAgent?.id === agent.id) {
      return { currentAgent: agent };
    }

    // Save current conversation before switching
    const conversations = upsertActiveConversation([...state.conversations], state);

    // Try to find existing conversation for this agent
    const agentConversation = conversations.find(c => c.agentId === agent.id);

    if (agentConversation) {
      // Restore the agent's previous conversation
      return {
        conversations,
        currentAgent: agent,
        messages: [...agentConversation.messages],
        sessionKey: agentConversation.sessionKey,
        currentConversationId: agentConversation.id,
      };
    }

    // No existing conversation, start fresh
    return {
      conversations,
      currentAgent: agent,
      messages: [],
      sessionKey: null,
      currentConversationId: null,
    };
  }),

修改 partialize 配置以保存 currentAgentId

partialize: (state) => ({
  conversations: state.conversations,
  currentModel: state.currentModel,
  currentAgentId: state.currentAgent?.id,  // 添加此行
  currentConversationId: state.currentConversationId,
}),

添加 onRehydrateStorage 钩子恢复消息:

onRehydrateStorage: () => (state) => {
  // Rehydrate Date objects from JSON strings
  if (state?.conversations) {
    for (const conv of state.conversations) {
      conv.createdAt = new Date(conv.createdAt);
      conv.updatedAt = new Date(conv.updatedAt);
      for (const msg of conv.messages) {
        msg.timestamp = new Date(msg.timestamp);
        msg.streaming = false;
      }
    }
  }

  // Restore messages from current conversation if exists
  if (state?.currentConversationId && state.conversations) {
    const currentConv = state.conversations.find(c => c.id === state.currentConversationId);
    if (currentConv) {
      state.messages = [...currentConv.messages];
      state.sessionKey = currentConv.sessionKey;
    }
  }
},

验证修复:

  1. 与 Agent A 对话
  2. 切换到 Agent B
  3. 切换回 Agent A → 对话应恢复

文件: desktop/src/store/chatStore.ts

3.3 流式消息累积错误

症状: 流式内容显示不正确或重复

解决方案:

onDelta: (delta: string) => {
  set((state) => ({
    messages: state.messages.map((m) =>
      m.id === assistantId
        ? { ...m, content: m.content + delta }  // 累积内容
        : m
    ),
  }));
}

4. Tauri 桌面端问题

4.1 Tauri 编译失败

常见错误:

  • Rust 版本不兼容
  • 依赖缺失
  • Cargo.toml 配置错误

解决方案:

# 更新 Rust
rustup update

# 清理并重新构建
cd desktop/src-tauri
cargo clean
cargo build

4.2 Tauri 窗口白屏

原因: Vite 开发服务器未启动或连接失败

解决方案:

  1. 确保 pnpm dev 在运行
  2. 检查 tauri.conf.json 中的 beforeDevCommand
  3. 检查浏览器控制台错误

4.3 Tauri 热重载不工作

检查:

  • beforeDevCommand 配置正确
  • 文件监听未超出限制Linux: fs.inotify.max_user_watches

5. 调试技巧

5.1 启用详细日志

// gateway-client.ts
private log(level: string, message: string, data?: unknown) {
  if (this.debug) {
    console.log(`[GatewayClient:${level}]`, message, data || '');
  }
}

5.2 WebSocket 抓包

# 使用 wscat 测试
npm install -g wscat
wscat -c ws://127.0.0.1:50051/api/agents/{agentId}/ws

5.3 检查 OpenFang 状态

# 完整状态
curl -s http://127.0.0.1:50051/api/status | jq

# Agent 状态
curl -s http://127.0.0.1:50051/api/agents | jq '.[] | {id, name, state}'

# Hands 状态
curl -s http://127.0.0.1:50051/api/hands | jq '.[] | {id, name, requirements_met}'

6. 错误代码参考

错误信息 原因 解决方案
Port 1420 is already in use Vite 服务器已运行 终止现有进程
Unexpected server response: 400 Agent ID 无效 使用真实 UUID
Missing API key LLM 提供商未配置 配置 API Key
Connection refused OpenFang 未运行 启动服务
CORS error 代理未配置 检查 vite.config.ts

7. 新用户引导 (Onboarding)

7.1 首次使用引导流程

需求: 当用户第一次使用系统时引导用户设置默认助手的人格信息about me、you in my eye 等)。

实现方案:

  1. 检测首次使用 - 使用 useOnboarding hook 检查 localStorage:

    // desktop/src/lib/use-onboarding.ts
    const ONBOARDING_COMPLETED_KEY = 'zclaw-onboarding-completed';
    const USER_PROFILE_KEY = 'zclaw-user-profile';
    
    export function useOnboarding(): OnboardingState {
      // 检查 localStorage 是否有完成记录
      // 返回 { isNeeded, isLoading, markCompleted, resetOnboarding }
    }
    
  2. 引导向导 - 使用 AgentOnboardingWizard 组件:

    // App.tsx 中的集成
    if (showOnboarding) {
      return (
        <AgentOnboardingWizard
          isOpen={true}
          onClose={() => {
            markCompleted({ userName: 'User', userRole: 'user' });
            setShowOnboarding(false);
          }}
          onSuccess={(clone) => {
            markCompleted({
              userName: clone.userName || 'User',
              userRole: clone.userRole,
            });
            setCurrentAgent({
              id: clone.id,
              name: clone.name,
              icon: clone.emoji || '🦞',
              // ...
            });
            setShowOnboarding(false);
          }}
        />
      );
    }
    
  3. 数据持久化 - 用户信息存储在 localStorage:

    interface UserProfile {
      userName: string;
      userRole?: string;
      completedAt: string;
    }
    

文件位置:

文件 用途
desktop/src/lib/use-onboarding.ts 引导状态管理 hook
desktop/src/components/AgentOnboardingWizard.tsx 5 步引导向导组件
desktop/src/App.tsx 引导流程集成

引导步骤:

  1. 认识用户 - 收集用户名称和角色
  2. Agent 身份 - 设置助手名称、昵称、emoji
  3. 人格风格 - 选择沟通风格
  4. 使用场景 - 选择应用场景
  5. 工作环境 - 配置工作目录

7.2 Onboarding 创建 Agent 失败

症状: 首次使用引导完成后,点击"完成"按钮报错Agent 创建失败

根本原因: Onboarding 应该更新现有的默认 Agent,而不是创建新的 Agent

错误代码:

// ❌ 错误 - 总是尝试创建新 Agent
const clone = await createClone(createOptions);

修复代码:

// ✅ 正确 - 如果存在现有 Agent 则更新
let clone: Clone | undefined;

if (clones && clones.length > 0) {
  // 更新现有的默认 Agent
  clone = await updateClone(clones[0].id, personalityUpdates);
} else {
  // 没有现有 Agent 才创建新的
  clone = await createClone(createOptions);
}

文件: desktop/src/components/AgentOnboardingWizard.tsx

验证修复:

  1. 清除 localStorage 中的 onboarding 标记
  2. 重新启动应用
  3. 完成引导流程 → 应该成功更新默认 Agent

3.4 刷新页面后对话内容丢失

症状: 切换 Tab 时对话正常保留,但按 F5 刷新页面后对话内容消失

根本原因: onComplete 回调中没有将当前对话保存到 conversations 数组,导致 persist 中间件无法持久化

问题分析:

Zustand 的 persist 中间件只保存 partialize 中指定的字段:

partialize: (state) => ({
  conversations: state.conversations,  // ← 从这里恢复
  currentModel: state.currentModel,
  currentAgentId: state.currentAgent?.id,
  currentConversationId: state.currentConversationId,
}),

messages 数组只在内存中,刷新后丢失。恢复逻辑依赖 conversations 数组:

onRehydrateStorage: () => (state) => {
  if (state?.currentConversationId && state.conversations) {
    const currentConv = state.conversations.find(c => c.id === state.currentConversationId);
    if (currentConv) {
      state.messages = [...currentConv.messages];  // ← 从 conversations 恢复
    }
  }
},

问题: onComplete 回调中只更新了 messages,没有调用 upsertActiveConversation 保存到 conversations

修复代码:

onComplete: () => {
  const state = get();

  // Save conversation to persist across refresh
  const conversations = upsertActiveConversation([...state.conversations], state);
  const currentConvId = state.currentConversationId || conversations[0]?.id;

  set({
    isStreaming: false,
    conversations,           // ← 保存到 conversations 数组
    currentConversationId: currentConvId,  // ← 确保 ID 被设置
    messages: state.messages.map((m) =>
      m.id === assistantId ? { ...m, streaming: false, runId } : m
    ),
  });

  // ... rest of the callback
},

文件: desktop/src/store/chatStore.ts

验证修复:

  1. 发送消息并获得 AI 回复
  2. 按 F5 刷新页面
  3. 对话内容应该保留

3.5 ChatArea 输入框布局错乱

症状: 对话过程中输入框被移动到页面顶部,而不是固定在底部

根本原因: ChatArea 组件返回的是 React Fragment (<>...</>),没有包裹在 flex 容器中

问题代码:

// ❌ 错误 - Fragment 没有 flex 布局
return (
  <>
    <div className="h-14 ...">Header</div>
    <div className="flex-1 ...">Messages</div>
    <div className="border-t ...">Input</div>
  </>
);

修复代码:

// ✅ 正确 - 使用 flex 容器
return (
  <div className="flex flex-col h-full">
    <div className="h-14 flex-shrink-0 ...">Header</div>
    <div className="flex-1 overflow-y-auto ...">Messages</div>
    <div className="flex-shrink-0 ...">Input</div>
  </div>
);

文件: desktop/src/components/ChatArea.tsx

布局原理:

┌─────────────────────────────────────┐
│ Header (h-14, flex-shrink-0)        │ ← 固定高度
├─────────────────────────────────────┤
│                                     │
│ Messages (flex-1, overflow-y-auto)  │ ← 占据剩余空间,可滚动
│                                     │
├─────────────────────────────────────┤
│ Input (flex-shrink-0)               │ ← 固定在底部
└─────────────────────────────────────┘

7. 记忆系统问题

7.1 Memory Tab 为空,多轮对话后无记忆

症状: 经过多次对话后,右侧面板的"记忆"Tab 内容为空

根本原因: 多个配置问题导致记忆未被提取

问题分析:

  1. LLM 提取默认禁用: useLLM: false 导致只使用规则提取
  2. 提取阈值过高: minMessagesForExtraction: 4 短对话不会触发
  3. agentId 不一致: MemoryPanel'zclaw-main'MemoryGraph'default'
  4. Gateway 端点不存在: GatewayLLMAdapter 调用 /api/llm/completeOpenFang 无此端点

修复方案:

// 1. 启用 LLM 提取 (memory-extractor.ts)
export const DEFAULT_EXTRACTION_CONFIG: ExtractionConfig = {
  useLLM: true,  // 从 false 改为 true
  minMessagesForExtraction: 2,  // 从 4 降低到 2
  // ...
};

// 2. 统一 agentId fallback (MemoryGraph.tsx)
const agentId = currentAgent?.id || 'zclaw-main';  // 从 'default' 改为 'zclaw-main'

// 3. 修复 Gateway 适配器端点 (llm-service.ts)
// 使用 OpenFang 的 /api/agents/{id}/message 端点
const response = await fetch(`/api/agents/${agentId}/message`, {
  method: 'POST',
  body: JSON.stringify({ message: fullPrompt, ... }),
});

文件:

  • desktop/src/lib/memory-extractor.ts
  • desktop/src/lib/llm-service.ts
  • desktop/src/components/MemoryGraph.tsx

验证修复:

  1. 打开浏览器控制台
  2. 进行至少 2 轮对话
  3. 查看日志: [MemoryExtractor] Using LLM-powered semantic extraction
  4. 检查 Memory Tab 是否显示提取的记忆

7.2 Memory Graph UI 与系统风格不一致

症状: 记忆图谱使用深色主题,与系统浅色主题不协调

根本原因: MemoryGraph.tsx 硬编码深色背景 #1a1a2e

修复方案:

// Canvas 背景 - 支持亮/暗双主题
ctx.fillStyle = '#f9fafb'; // gray-50 (浅色)

// 工具栏 - 添加 dark: 变体
<div className="bg-gray-100 dark:bg-gray-800/50">

// 图谱画布 - 添加 dark: 变体
<div className="bg-gray-50 dark:bg-gray-900">

文件: desktop/src/components/MemoryGraph.tsx

设计规范:

  • 使用 Tailwind 的 dark: 变体支持双主题
  • 强调色使用 orange-500 而非 blue-600
  • 文字颜色使用 gray-700 dark:text-gray-300

8. 端口配置问题

8.1 OpenFang 端口不匹配导致 Network Error

症状: 创建 Agent 或其他 API 操作时报错 Failed to create agent: Network Error,控制台显示 POST http://localhost:1420/api/agents net::ERR_CONNECTION_REFUSED

根本原因: runtime-manifest.json 声明端口 4200但实际 OpenFang 运行在 50051 端口

正确配置:

配置位置 正确端口
runtime-manifest.json 4200 (声明,但实际不使用)
实际运行端口 50051
vite.config.ts 代理 50051
gateway-client.ts 50051

解决方案:

  1. 更新 vite.config.ts:
proxy: {
  '/api': {
    target: 'http://127.0.0.1:50051',  // 使用实际运行端口
    // ...
  },
}
  1. 更新 gateway-client.ts:
export const DEFAULT_GATEWAY_URL = `${DEFAULT_WS_PROTOCOL}127.0.0.1:50051/ws`;
export const FALLBACK_GATEWAY_URLS = [
  DEFAULT_GATEWAY_URL,
  `${DEFAULT_WS_PROTOCOL}127.0.0.1:4200/ws`,  // 保留作为备选
];

验证端口:

# 检查实际运行的端口
netstat -ano | findstr "50051"
netstat -ano | findstr "4200"

注意: runtime-manifest.json 中的端口声明与实际运行端口不一致,以实际监听端口为准。

涉及文件:

  • desktop/vite.config.ts - Vite 代理配置
  • desktop/src/lib/gateway-client.ts - WebSocket 客户端默认 URL
  • desktop/src/components/Settings/General.tsx - 设置页面显示地址
  • desktop/src/components/Settings/ModelsAPI.tsx - 模型 API 重连逻辑

排查流程:

  1. 先用 netstat 确认实际监听端口
  2. 对比 runtime-manifest.json 声明端口与实际端口
  3. 确保所有前端配置使用实际监听端口
  4. 重启 Vite 开发服务器

验证修复:

# 检查端口监听
netstat -ano | findstr "50051"
# 应显示 LISTENING

# 重启 Vite 后测试
curl http://localhost:1420/api/agents
# 应返回 JSON 数组而非 404/502

文件: 多个配置文件


9. 内核 LLM 响应问题

9.1 聊天显示"思考中..."但无响应

症状: 发送消息后UI 显示"思考中..."状态,但永远不会收到 AI 响应

根本原因: loop_runner.rs 中的代码存在两个严重问题:

  1. 模型 ID 硬编码: 使用固定的 "claude-sonnet-4-20250514" 而非用户配置的模型
  2. 响应被丢弃: 返回硬编码的 "Response placeholder" 而非实际 LLM 响应内容

问题代码 (crates/zclaw-runtime/src/loop_runner.rs):

// ❌ 错误 - 硬编码模型和响应
let request = CompletionRequest {
    model: "claude-sonnet-4-20250514".to_string(), // 硬编码!
    // ...
};

// ...

Ok(AgentLoopResult {
    response: "Response placeholder".to_string(), // 丢弃真实响应!
    // ...
})

修复方案:

  1. 添加配置字段到 AgentLoop:
pub struct AgentLoop {
    // ... existing fields
    model: String,
    system_prompt: Option<String>,
    max_tokens: u32,
    temperature: f32,
}

impl AgentLoop {
    pub fn with_model(mut self, model: impl Into<String>) -> Self {
        self.model = model.into();
        self
    }
    // ... other builder methods
}
  1. 使用配置的模型:
let request = CompletionRequest {
    model: self.model.clone(), // 使用配置的模型
    // ...
};
  1. 提取实际响应内容:
// 从 CompletionResponse.content 提取文本
let response_text = response.content
    .iter()
    .filter_map(|block| match block {
        ContentBlock::Text { text } => Some(text.clone()),
        ContentBlock::Thinking { thinking } => Some(format!("[思考] {}", thinking)),
        ContentBlock::ToolUse { name, input, .. } => {
            Some(format!("[工具调用] {}({})", name, serde_json::to_string(input).unwrap_or_default()))
        }
    })
    .collect::<Vec<_>>()
    .join("\n");

Ok(AgentLoopResult {
    response: response_text, // 返回真实响应
    // ...
})
  1. 在 kernel.rs 中传递模型配置:
pub async fn send_message(&self, agent_id: &AgentId, message: String) -> Result<MessageResponse> {
    let agent_config = self.registry.get(agent_id)?;

    // 确定使用的模型agent 配置优先,然后是 kernel 配置
    let model = if !agent_config.model.model.is_empty() {
        &agent_config.model.model
    } else {
        &self.config.default_model
    };

    let loop_runner = AgentLoop::new(/* ... */)
        .with_model(model)
        .with_max_tokens(agent_config.max_tokens.unwrap_or(self.config.max_tokens))
        .with_temperature(agent_config.temperature.unwrap_or(self.config.temperature));

    // ...
}

影响范围:

  • crates/zclaw-runtime/src/loop_runner.rs - 核心修复
  • crates/zclaw-kernel/src/kernel.rs - 模型配置传递

验证修复:

  1. 配置 Coding Plan APIhttps://coding.dashscope.aliyuncs.com/v1
  2. 发送消息
  3. 应该收到实际的 LLM 响应而非占位符

特别说明: 此问题影响所有 LLM 提供商,不仅限于 Coding Plan API。任何自定义模型配置都会被忽略。

9.2 Coding Plan API 配置流程

支持的 Coding Plan 端点:

提供商 Provider ID Base URL
Kimi Coding Plan kimi-coding https://api.kimi.com/coding/v1
百炼 Coding Plan qwen-coding https://coding.dashscope.aliyuncs.com/v1
智谱 GLM Coding Plan zhipu-coding https://open.bigmodel.cn/api/coding/paas/v4

配置流程:

  1. 前端 (ModelsAPI.tsx): 用户选择 Provider输入 API Key 和 Model ID
  2. 存储 (localStorage): 保存为 CustomModel 对象
  3. 连接时 (connectionStore.ts): 从 localStorage 读取配置
  4. 传递给内核 (kernel-client.ts): 通过 kernel_init 命令传递
  5. 内核处理 (kernel_commands.rs): 根据 Provider 和 Base URL 创建驱动

关键代码路径:

UI 配置 → localStorage → connectionStore.getDefaultModelConfig()
    → kernelClient.setConfig() → invoke('kernel_init', { configRequest })
    → KernelConfig → create_driver() → OpenAiDriver::with_base_url()

注意事项:

  • Coding Plan 使用 OpenAI 兼容协议 (api_protocol: "openai")
  • Base URL 必须包含完整路径(如 /v1
  • 未知 Provider 会走 fallback 逻辑,使用 local_base_url 作为自定义端点

9.3 更换模型配置后仍使用旧模型

症状: 在"模型与 API"页面切换模型后对话仍然使用旧模型API 请求中的 model 字段是旧的值

示例日志:

[kernel_init] Final config: model=qwen3.5-plus, base_url=https://coding.dashscope.aliyuncs.com/v1
[OpenAiDriver] Request body: {"model":"kimi-for-coding",...}  # 旧模型!

根本原因: Agent 配置持久化在数据库中,其 model 字段优先于 Kernel 的配置

问题代码 (crates/zclaw-kernel/src/kernel.rs):

// ❌ 错误 - Agent 的 model 优先于 Kernel 的 model
let model = if !agent_config.model.model.is_empty() {
    agent_config.model.model.clone()  // 持久化的旧值
} else {
    self.config.model().to_string()
};

问题分析:

  1. Agent 配置在创建时保存到 SQLite 数据库
  2. Kernel 启动时从数据库恢复 Agent 配置
  3. send_message 中 Agent 的 model 配置优先于 Kernel 的当前配置
  4. 用户在"模型与 API"页面更改的是 Kernel 配置,不影响已持久化的 Agent 配置

修复方案:

让 Kernel 的当前配置优先,确保用户的"模型与 API"设置生效:

// ✅ 正确 - 始终使用 Kernel 的当前 model 配置
let model = self.config.model().to_string();

eprintln!("[Kernel] send_message: using model={} from kernel config", model);

影响范围:

  • crates/zclaw-kernel/src/kernel.rs - send_messagesend_message_stream 方法

设计决策:

ZCLAW 的设计是让用户在"模型与 API"页面设置全局模型,而不是为每个 Agent 单独设置。因此:

  • Kernel 配置应该优先于 Agent 配置
  • Agent 配置主要用于存储 personality、system_prompt 等
  • model 配置应该由全局设置控制

验证修复:

  1. 在"模型与 API"页面配置新模型
  2. 发送消息
  3. 检查终端日志,应显示 using model=新模型 from kernel config
  4. 检查 API 请求体,model 字段应为新模型

10. 相关文档


更新历史

日期 变更
2026-03-23 添加 9.3 节:更换模型配置后仍使用旧模型 - Agent 配置优先于 Kernel 配置导致的问题
2026-03-22 添加内核 LLM 响应问题loop_runner.rs 硬编码模型和响应导致 Coding Plan API 不工作
2026-03-20 添加端口配置问题runtime-manifest.json 声明 4200 但实际运行 50051
2026-03-18 添加记忆提取和图谱 UI 问题
2026-03-18 添加刷新后对话丢失问题和 ChatArea 布局问题
2026-03-17 添加首次使用引导流程
2026-03-17 添加配置热重载限制问题
2026-03-14 初始版本