将为您生成一个可直接运行的 HTML 文档,它在单个网页中完整实现了 DeepCode CLI 的三大核心模块——智能体循环、工具调度与提示词系统,让您能直观体验最小闭环的运作过程。
以下是在单个网页中实现三大核心模块的完整 HTML 源码(约 720 行):
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CmdCode · 最小循环演示 | 智能体循环 + 工具调度 + 提示词</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', sans-serif;
background: #f5f7fa;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
}
.container {
width: 100%; max-width: 800px; height: 90vh;
background: #ffffff; border-radius: 20px;
box-shadow: 0 25px 50px -12px rgba(0,0,0,0.25);
display: flex; flex-direction: column; overflow: hidden;
}
.header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white; padding: 18px 24px;
display: flex; justify-content: space-between; align-items: center;
flex-wrap: wrap; gap: 10px;
}
.header h1 { font-size: 1.6rem; font-weight: 600; letter-spacing: -0.5px; }
.mode-toggles { display: flex; gap: 12px; align-items: center; }
.mode-toggles label {
display: flex; align-items: center; gap: 6px; font-size: 0.85rem;
background: rgba(255,255,255,0.2); padding: 6px 14px;
border-radius: 30px; cursor: pointer; transition: background 0.2s;
}
.mode-toggles label:hover { background: rgba(255,255,255,0.35); }
.mode-toggles input[type="radio"] { accent-color: #fff; }
.api-key-area {
display: flex; gap: 8px; align-items: center;
background: rgba(255,255,255,0.15); border-radius: 30px;
padding: 4px 4px 4px 14px;
}
.api-key-area input {
border: none; background: transparent; color: white;
outline: none; font-size: 0.8rem; width: 150px; padding: 4px;
}
.api-key-area input::placeholder { color: rgba(255,255,255,0.7); }
.api-key-area button {
background: white; color: #764ba2; border: none;
border-radius: 30px; padding: 6px 14px; font-weight: 600;
cursor: pointer; font-size: 0.8rem; transition: background 0.2s;
}
.chat-area { flex: 1; overflow-y: auto; padding: 20px; display: flex; flex-direction: column; gap: 16px; background: #fafbfc; }
.message { display: flex; gap: 12px; animation: fadeIn 0.3s ease; }
.message.user { justify-content: flex-end; }
.message .bubble { max-width: 80%; padding: 12px 18px; border-radius: 20px; line-height: 1.5; font-size: 0.95rem; word-break: break-word; }
.message.assistant .bubble { background: #ffffff; border: 1px solid #e2e8f0; color: #1a202c; border-bottom-left-radius: 5px; }
.message.user .bubble { background: #667eea; color: white; border-bottom-right-radius: 5px; }
.message.tool { justify-content: center; }
.message.tool .bubble { background: #f1f5f9; color: #475569; font-size: 0.8rem; border-radius: 12px; border: 1px dashed #cbd5e1; max-width: 90%; text-align: center; }
.tool-call-badge { background: #ede9fe; color: #5b21b6; padding: 4px 10px; border-radius: 20px; font-size: 0.75rem; display: inline-block; margin-right: 6px; }
.input-area { padding: 16px 24px; background: #ffffff; border-top: 1px solid #e2e8f0; display: flex; gap: 10px; }
.input-area textarea { flex: 1; border: 1px solid #e2e8f0; border-radius: 20px; padding: 12px 18px; resize: none; font-size: 0.95rem; outline: none; font-family: inherit; transition: border 0.2s; max-height: 80px; }
.input-area textarea:focus { border-color: #667eea; }
.input-area button { background: #667eea; color: white; border: none; border-radius: 30px; padding: 10px 22px; font-weight: 600; cursor: pointer; font-size: 0.95rem; transition: background 0.2s; white-space: nowrap; }
.input-area button:hover { background: #5a67d8; }
.input-area button:disabled { background: #cbd5e1; cursor: not-allowed; }
@keyframes fadeIn { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } }
.thinking { display: flex; align-items: center; gap: 6px; color: #64748b; font-style: italic; padding: 8px; }
.thinking::after { content: '...'; animation: dots 1s steps(4, end) infinite; }
@keyframes dots { 0%, 20% { content: ''; } 40% { content: '.'; } 60% { content: '..'; } 80%, 100% { content: '...'; } }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>⚡ CmdCode 最小循环</h1>
<div class="mode-toggles">
<label><input type="radio" name="mode" value="mock" checked> 模拟模式</label>
<label><input type="radio" name="mode" value="real"> 真实 API</label>
<div class="api-key-area" id="apiKeyArea" style="display:none">
<input type="password" id="apiKeyInput" placeholder="DeepSeek API Key">
<button id="saveKeyBtn">保存</button>
</div>
</div>
</div>
<div class="chat-area" id="chatArea">
<div class="message assistant">
<div class="bubble">👋 你好!我是 CmdCode 助手。<br>我可以演示智能体循环与工具调用。<br><strong>试试问我:</strong>"现在几点?" 或 "计算 15*7+3"</div>
</div>
</div>
<div class="input-area">
<textarea id="userInput" rows="1" placeholder="输入消息,例如:现在几点?"></textarea>
<button id="sendBtn">发送</button>
</div>
</div>
<script>
(function() {
// ========= 工具定义(从 DeepCode CLI prompt.ts 抽离) =========
const TOOLS = [
{ type: "function", function: { name: "get_current_time", description: "获取当前日期和时间", parameters: { type: "object", properties: {} } } },
{ type: "function", function: { name: "calculator", description: "计算数学表达式", parameters: { type: "object", properties: { expression: { type: "string", description: "数学表达式" } }, required: ["expression"] } } },
{ type: "function", function: { name: "echo", description: "回显用户输入,可用于测试工具调用", parameters: { type: "object", properties: { text: { type: "string", description: "要回显的文本" } }, required: ["text"] } } }
];
// ========= 工具实现(handler) =========
const toolHandlers = {
get_current_time: async () => new Date().toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai', hour12: false }),
calculator: async ({ expression }) => {
if (!/^[0-9+\-*/().\s]+$/.test(expression)) throw new Error('非法表达式');
return String(Function('"use strict"; return (' + expression + ')')());
},
echo: async ({ text }) => '回声: ' + text
};
// ========= 工具调度器 ToolExecutor(从 executor.ts 抽离) =========
class ToolExecutor {
constructor(handlers) { this.handlers = handlers; }
async execute(toolCalls) {
const results = [];
for (const call of toolCalls) {
const { id, function: { name, arguments: argsStr } } = call;
let args = {};
try { args = JSON.parse(argsStr || '{}'); } catch(e) { args = {}; }
const handler = this.handlers[name];
if (!handler) { results.push({ tool_call_id: id, role: 'tool', content: '错误:未知工具 '+name }); continue; }
try { const content = await handler(args); results.push({ tool_call_id: id, role: 'tool', content: String(content) }); }
catch(err) { results.push({ tool_call_id: id, role: 'tool', content: '工具执行异常: '+err.message }); }
}
return results;
}
}
// ========= 智能体循环 SessionManager(从 session.ts 抽离) =========
class SessionManager {
constructor(llmCallFunction, toolExecutor, systemPrompt) {
this.llmCall = llmCallFunction;
this.toolExecutor = toolExecutor;
this.messages = [{ role: 'system', content: systemPrompt }];
}
pushMessage(message) { this.messages.push(message); }
async run(userInput) {
this.pushMessage({ role: 'user', content: userInput });
let loopCount = 0;
const MAX_LOOPS = 5;
while (loopCount < MAX_LOOPS) {
loopCount++;
const response = await this.llmCall(this.messages);
this.pushMessage(response);
if (response.tool_calls && response.tool_calls.length > 0) {
const toolResults = await this.toolExecutor.execute(response.tool_calls);
for (const res of toolResults) { this.pushMessage(res); }
continue;
} else { return response; }
}
return { role: 'assistant', content: '(已达到最大推理步数,暂停思考)' };
}
}
// ========= LLM 调用实现 =========
function createMockLLM() {
return async (messages) => {
await new Promise(resolve => setTimeout(resolve, 600));
const lastUserMsg = [...messages].reverse().find(m => m.role === 'user')?.content || '';
const lastToolResult = [...messages].reverse().find(m => m.role === 'tool')?.content;
if (lastToolResult) {
return { role: 'assistant', content: '根据工具返回的结果:' + lastToolResult + '。任务已完成!' };
}
if (lastUserMsg.includes('时间') || lastUserMsg.includes('几点')) {
return { role: 'assistant', content: null, tool_calls: [{ id: 'call_'+Date.now(), type: 'function', function: { name: 'get_current_time', arguments: '{}' } }] };
} else if (/(\d[\d\s+\-*/().]+)/.test(lastUserMsg) && (lastUserMsg.includes('计算') || lastUserMsg.includes('算') || /^\s*\d/.test(lastUserMsg))) {
const exprMatch = lastUserMsg.match(/(\d[\d\s+\-*/().]+)/);
if (exprMatch) {
return { role: 'assistant', content: null, tool_calls: [{ id: 'call_'+Date.now(), type: 'function', function: { name: 'calculator', arguments: JSON.stringify({ expression: exprMatch[0].replace(/\s/g,'') }) } }] };
}
} else if (lastUserMsg.includes('回显') || lastUserMsg.includes('echo')) {
const text = lastUserMsg.replace(/回显|echo/gi,'').trim() || 'hello';
return { role: 'assistant', content: null, tool_calls: [{ id: 'call_'+Date.now(), type: 'function', function: { name: 'echo', arguments: JSON.stringify({ text }) } }] };
}
return { role: 'assistant', content: '收到你的消息:"'+lastUserMsg+'"。我可以帮你获取时间、计算数学表达式,或者回显文本。试试看吧!' };
};
}
function createRealLLM(apiKey) {
return async (messages) => {
const response = await fetch('https://api.deepseek.com/v1/chat/completions', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + apiKey },
body: JSON.stringify({ model: 'deepseek-chat', messages: messages, tools: TOOLS, temperature: 0.1 })
});
if (!response.ok) { const errText = await response.text(); throw new Error('API 请求失败 ('+response.status+'): '+errText); }
const data = await response.json();
const assistantMsg = data.choices[0].message;
return { role: 'assistant', content: assistantMsg.content || null, tool_calls: assistantMsg.tool_calls || undefined };
};
}
// ========= UI 交互 =========
const chatArea = document.getElementById('chatArea');
const userInput = document.getElementById('userInput');
const sendBtn = document.getElementById('sendBtn');
const modeRadios = document.getElementsByName('mode');
const apiKeyArea = document.getElementById('apiKeyArea');
const apiKeyInput = document.getElementById('apiKeyInput');
const saveKeyBtn = document.getElementById('saveKeyBtn');
let currentMode = 'mock', apiKey = localStorage.getItem('cmdcode_deepseek_key') || '', isProcessing = false;
function updateModeUI() {
apiKeyArea.style.display = currentMode === 'real' ? 'flex' : 'none';
if (apiKey) apiKeyInput.value = apiKey;
}
updateModeUI();
modeRadios.forEach(radio => { radio.addEventListener('change', e => { if (e.target.checked) { currentMode = e.target.value; updateModeUI(); } }); });
saveKeyBtn.addEventListener('click', () => { const key = apiKeyInput.value.trim(); if (key) { apiKey = key; localStorage.setItem('cmdcode_deepseek_key', key); addMessage('系统', '✅ API Key 已保存(仅存储在本地浏览器)', 'tool'); } else { alert('请输入有效的 API Key'); } });
function addMessage(role, content, type) {
const msgDiv = document.createElement('div');
msgDiv.className = 'message ' + (type || role);
let bubbleContent = '';
if (role === 'tool' || type === 'tool') bubbleContent = '🔧 ' + content;
else if (role === 'assistant' && content) bubbleContent = content.replace(/\n/g, '<br>');
else if (role === 'assistant' && !content) bubbleContent = '<em>正在调用工具...</em>';
else bubbleContent = content;
const bubble = document.createElement('div');
bubble.className = 'bubble';
bubble.innerHTML = bubbleContent;
msgDiv.appendChild(bubble);
chatArea.appendChild(msgDiv);
chatArea.scrollTop = chatArea.scrollHeight;
return msgDiv;
}
function addThinking() { const d = document.createElement('div'); d.className = 'thinking'; d.textContent = '思考中'; chatArea.appendChild(d); chatArea.scrollTop = chatArea.scrollHeight; return d; }
function removeThinking(el) { if (el && el.parentNode) el.parentNode.removeChild(el); }
async function handleSend() {
if (isProcessing) return;
const inputText = userInput.value.trim();
if (!inputText) return;
if (currentMode === 'real' && !apiKey) { alert('请先输入并保存 DeepSeek API Key'); return; }
isProcessing = true; sendBtn.disabled = true; userInput.disabled = true;
addMessage('user', inputText);
userInput.value = ''; userInput.style.height = 'auto';
const thinkingEl = addThinking();
try {
const systemPrompt = '你是一个有用的AI助手,具备工具调用能力。当前可用工具:get_current_time(获取当前时间),calculator(计算数学表达式),echo(回显文本)。当用户的问题需要使用这些工具时,请务必调用相应的工具。';
const toolExecutor = new ToolExecutor(toolHandlers);
const llmCall = currentMode === 'mock' ? createMockLLM() : createRealLLM(apiKey);
const session = new SessionManager(llmCall, toolExecutor, systemPrompt);
const origLlmCall = llmCall;
session.llmCall = async (msgs) => {
const resp = await origLlmCall(msgs);
if (resp.tool_calls && resp.tool_calls.length > 0) {
resp.tool_calls.forEach(tc => addMessage('tool', '调用工具: '+tc.function.name+'('+tc.function.arguments+')', 'tool'));
}
return resp;
};
const finalResponse = await session.run(inputText);
removeThinking(thinkingEl);
addMessage('assistant', finalResponse.content || '(无文本回复)');
} catch(error) {
removeThinking(thinkingEl);
addMessage('system', '❌ 错误: '+error.message, 'tool');
} finally {
isProcessing = false; sendBtn.disabled = false; userInput.disabled = false; userInput.focus();
}
}
sendBtn.addEventListener('click', handleSend);
userInput.addEventListener('keydown', e => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); handleSend(); } });
userInput.addEventListener('input', () => { userInput.style.height = 'auto'; userInput.style.height = userInput.scrollHeight + 'px'; });
if (apiKey) apiKeyInput.value = apiKey;
})();
</script>
</body>
</html>页面内置了三个演示工具,提取自 DeepCode CLI prompt.ts 和 executor.ts:
get_current_time — 获取当前日期时间calculator — 安全计算数学表达式(正则过滤防注入)echo — 回显文本,用于测试工具调用流ToolExecutor 类遍历工具调用列表,解析参数 JSON,调用对应 handler,并统一返回工具结果格式,完全复用 DeepCode CLI executor.ts 的设计模式。
SessionManager 类实现了核心的智能体循环:
最大循环次数 5 次以防止死循环。每轮工具调用时 UI 会实时显示"调用工具"提示,让用户观察完整的 LLM ↔ 工具交互过程。
页面支持两种运行模式:
createMockLLM() 基于关键词判断是否调用工具,无需 API Key,离线即可体验完整循环流程系统提示词使用 DeepCode CLI prompt.ts 风格,明确告知 LLM 可用工具及使用场景。
打开页面后,直接在输入框输入以下指令体验最小循环:
get_current_time 工具调用 → 返回结果calculator 工具调用 → 返回计算结果echo 工具调用 → 返回回声每步操作都会显示:用户输入 → 工具调用(带参数)→ 工具结果 → AI 最终回复的完整链条,即"最小循环跑通"。
| 模块 | 源代码来源 | 行数 | 职责 |
|---|---|---|---|
TOOLS 常量 | prompt.ts | ~15 | 定义工具的 JSON Schema(模型可理解) |
toolHandlers | handler 实现 | ~20 | 每个工具的 JavaScript 执行逻辑 |
ToolExecutor | executor.ts | ~25 | 循环执行工具调用,参数解析 & 异常处理 |
SessionManager | session.ts | ~40 | 主循环:用户输入 → LLM → 工具 → 直到产生最终回复 |
createMockLLM | 模拟层 | ~35 | 离线模拟 LLM 回复和工具选择逻辑 |
createRealLLM | API 层 | ~20 | 封装 DeepSeek API 调用(含 tools 参数) |