👤 用户
我想在单个网页上完整实现这三个模块,你做一个网页出来,把这三个模块完整应用上去,实现最小循环跑通
🤖 DeepSeek 助手

将为您生成一个可直接运行的 HTML 文档,它在单个网页中完整实现了 DeepCode CLI 的三大核心模块——智能体循环、工具调度与提示词系统,让您能直观体验最小闭环的运作过程。

完整源码

以下是在单个网页中实现三大核心模块的完整 HTML 源码(约 720 行):

CmdCode 最小循环演示 · 完整源码
<!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>

三大核心模块详解

1. 工具定义与调度(Tool Scheduling)

页面内置了三个演示工具,提取自 DeepCode CLI prompt.tsexecutor.ts

  • get_current_time — 获取当前日期时间
  • calculator — 安全计算数学表达式(正则过滤防注入)
  • echo — 回显文本,用于测试工具调用流

ToolExecutor 类遍历工具调用列表,解析参数 JSON,调用对应 handler,并统一返回工具结果格式,完全复用 DeepCode CLI executor.ts 的设计模式。

2. 智能体循环(Agent Loop)

SessionManager 类实现了核心的智能体循环:

  1. 用户输入 → 追加到消息历史
  2. 调用 LLM 获取回复(含可能工具调用)
  3. 若返回工具调用 → 执行工具 → 结果追加到历史 → 回到步骤 2
  4. 若返回纯文本 → 作为最终回复返回

最大循环次数 5 次以防止死循环。每轮工具调用时 UI 会实时显示"调用工具"提示,让用户观察完整的 LLM ↔ 工具交互过程。

3. 提示词与模式切换(Prompt System)

页面支持两种运行模式:

  • 模拟模式 — 使用 createMockLLM() 基于关键词判断是否调用工具,无需 API Key,离线即可体验完整循环流程
  • 真实 API 模式 — 填入 DeepSeek API Key(仅存储浏览器 localStorage),直连 DeepSeek Chat 模型,体验真实的工具调用能力

系统提示词使用 DeepCode CLI prompt.ts 风格,明确告知 LLM 可用工具及使用场景。

演示方法

打开页面后,直接在输入框输入以下指令体验最小循环:

  • 获取时间:"现在几点?" → 触发 get_current_time 工具调用 → 返回结果
  • 计算表达式:"计算 15*7+3" → 触发 calculator 工具调用 → 返回计算结果
  • 回显测试:"回显 hello world" → 触发 echo 工具调用 → 返回回声

每步操作都会显示:用户输入 → 工具调用(带参数)→ 工具结果 → AI 最终回复的完整链条,即"最小循环跑通"。

代码架构分析

模块源代码来源行数职责
TOOLS 常量prompt.ts~15定义工具的 JSON Schema(模型可理解)
toolHandlershandler 实现~20每个工具的 JavaScript 执行逻辑
ToolExecutorexecutor.ts~25循环执行工具调用,参数解析 & 异常处理
SessionManagersession.ts~40主循环:用户输入 → LLM → 工具 → 直到产生最终回复
createMockLLM模拟层~35离线模拟 LLM 回复和工具选择逻辑
createRealLLMAPI 层~20封装 DeepSeek API 调用(含 tools 参数)