feat: LLM 调用日志 + ModelSelector 优化 + devtools.bat 编码修复

- 新增 call_log.go: 全局环形缓冲区记录每次 LLM 调用(模型/Token/耗时/错误)
- OpenAIProvider.doChat/ChatStreamWithTools 自动记录调用日志
- ai-core 暴露 GET /api/v1/llm-calls 端点, DevTools 代理 + UI 面板
- ModelSelector.envProvider 改为单例缓存, 避免重复创建 HTTP Client
- 新增 PurposeToolCalling 适配器, 后台思考工具调用走专用路由
- envFallback 超时 120s→180s, 显式设置 MaxRetries
- devtools.bat 全英文, 解决 Windows CMD GBK 编码乱码问题

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-24 15:44:53 +08:00
parent 7eb5e984c2
commit 47f9de2409
8 changed files with 266 additions and 22 deletions
+33
View File
@@ -19,6 +19,7 @@ import { processManager } from './process-manager.js';
import { performanceMonitor } from './performance.js';
import { SERVICES, DEVTOOLS_PORT, LOGS_DIR, logFile, GATEWAY_URL, TOOL_ENGINE_URL, ADMIN_USERNAME, ADMIN_PASSWORD } from './config.js';
const AI_CORE_URL = process.env.AI_CORE_URL || 'http://localhost:8081';
const MEMORY_SERVICE_URL = process.env.MEMORY_SERVICE_URL || 'http://localhost:8091';
const VOICE_SERVICE_URL = process.env.VOICE_SERVICE_URL || 'http://localhost:8093';
const PLATFORM_BRIDGE_URL = process.env.PLATFORM_BRIDGE_URL || 'http://localhost:8095';
@@ -168,6 +169,31 @@ async function proxyToGateway(path, opts = {}) {
}
}
/**
* 代理请求到 AI-Core
*/
async function proxyToAICore(path, opts = {}) {
const url = `${AI_CORE_URL}${path}`;
try {
const resp = await fetch(url, {
...opts,
headers: { 'Content-Type': 'application/json', ...opts.headers },
signal: AbortSignal.timeout(10000),
});
const body = await resp.json().catch(() => null);
return { status: resp.status, body };
} catch (err) {
return {
status: 502,
body: {
error: `AI-Core 不可达: ${err.message}`,
errorType: 'ai_core_unreachable',
hint: 'AI-Core 服务未启动,请先在「服务管理」面板中启动 AI-Core',
},
};
}
}
// ========== REST API 路由 ==========
// ---- 健康检查 ----
@@ -1003,6 +1029,13 @@ app.get('/api/voice/health', async (_req, res) => {
res.status(result.status).json(result.body);
});
// GET /api/llm-calls — LLM 调用日志 (代理到 AI-Core)
app.get('/api/llm-calls', async (req, res) => {
const limit = parseInt(req.query.limit) || 50;
const result = await proxyToAICore(`/api/v1/llm-calls?limit=${Math.min(limit, 500)}`);
res.status(result.status).json(result.body);
});
/**
* 代理请求到 Memory-Service
* @param {string} path - Memory-Service API 路径