
1. 从“大龙虾”到“小龙虾”为什么我们需要一个迷你版OpenClaw最近在AI智能体圈子里OpenClaw俗称“大龙虾”的热度居高不下。作为一个开源的AI Agent框架它集成了多模型调度、技能编排、工具调用等能力听起来确实很酷。但很多朋友在尝试部署和使用时都遇到了一个共同的“劝退”点太重了。完整的OpenClaw部署动辄需要几十GB的磁盘空间对内存和CPU也有不低的要求更别提那些复杂的依赖和配置了。对于只是想快速体验其核心Agent工作流或者想在个人电脑、小型服务器上跑个轻量级自动化助手的人来说这门槛实在有点高。这就引出了我们今天要聊的话题打造一个迷你版的OpenClaw。这个“小龙虾”的目标不是复刻所有功能而是取其精华去其繁重。我们聚焦于最核心的Agent调度、工具调用和简单的技能管理用最精简的依赖和配置构建一个能跑起来、能干活、能让你理解底层原理的轻量级版本。这不仅是降低体验门槛更是一个绝佳的学习过程。通过亲手搭建你能彻底搞明白一个AI Agent框架的骨架是如何搭建的消息如何流转工具如何被调用这对于你未来使用任何复杂框架甚至自己设计Agent系统都有着不可估量的价值。所以无论你是被OpenClaw的庞大吓退的初学者还是想深入理解Agent架构的开发者跟着这篇指南我们一起动手从零开始打造属于你自己的“小龙虾”。2. 迷你版OpenClaw的核心架构设计我们到底要建什么在动手写代码之前我们必须先想清楚一个“能用”的迷你版OpenClaw至少需要哪些核心模块如果照搬原版我们又会陷入复杂的泥潭。因此我们的设计原则是单一职责、接口清晰、依赖最小。基于对OpenClaw公开资料和社区讨论的分析我们可以将其最核心的流程抽象为以下几个部分Agent核心Brain负责接收用户指令进行意图理解并规划执行步骤。在迷你版中我们可以用一个轻量级的LLM比如通过Ollama本地运行的Qwen2.5-7B或Llama 3.2作为大脑配合一个简单的提示词Prompt模板来实现。工具注册与管理中心ToolboxAgent需要“手”来执行具体任务。我们将所有可用的功能如搜索、计算、读写文件封装成统一的工具Tool。每个工具需要明确定义其名称、描述、参数格式。管理中心负责维护工具列表并在Agent需要时提供调用接口。技能执行器Skill Executor这是工具调用的实际执行单元。当Agent决定使用某个工具时执行器负责解析参数调用对应的函数或API并将执行结果返回给Agent。这里需要处理好错误捕获和结果格式化。会话与记忆管理Memory为了让Agent在连续对话中保持上下文一个简单的短期记忆是必要的。我们可以实现一个基于列表的对话历史记录只保存最近几轮的问答和工具调用结果。主控流程Orchestrator这是连接上述所有部分的“总指挥”。它控制着“用户输入 - Agent思考 - 工具调用 - 结果返回 - 继续思考或输出”这个循环。为了极致轻量我们暂时砍掉这些“豪华”功能复杂的多Agent协作、图形化WebUI、企业级的权限和审计日志、与飞书/微信等IM工具的深度集成这些可以作为后续扩展。我们的“小龙虾”首先要在命令行里健步如飞。2.1 技术栈选型为什么是它们明确了架构接下来选择实现的技术栈。我们的选择标准是流行、轻量、文档丰富、易于集成。编程语言Python 3.8。这是AI和自动化领域的绝对主流生态丰富从LLM调用到网络请求都有成熟的库。LLM接口层litellm或直接使用openai库。litellm的优势在于它统一了数十种模型OpenAI, Anthropic, Cohere, 本地Ollama等的调用接口只需改个参数就能切换模型非常灵活。对于迷你版为了更直接我们可以先用openai库兼容的格式来调用本地Ollama服务。本地模型服务Ollama。它是在本地运行和管理大模型最简单的方式一条命令就能拉取和启动模型完美契合我们“轻量、本地”的需求。工具函数实现标准库 少量第三方库。比如用requests做网络请求用json处理数据用subprocess执行系统命令。避免引入重型框架。配置管理简单的config.yaml或.env文件。将模型地址、API密钥如果有、工具开关等配置外部化。这个选型确保了我们的项目依赖非常干净一个requirements.txt文件可能只需要不到10个包极大降低了部署复杂度。3. 从零开始搭建迷你OpenClaw的运行环境理论说得再多不如动手开始。我们首先需要一个干净的环境来构建我们的“小龙虾”。3.1 基础环境准备假设你使用的是 Ubuntu 20.04/22.04 或 macOSWindows用户建议使用WSL2以获得接近Linux的体验。首先确保系统有Python和pip。然后为项目创建一个独立的虚拟环境这是Python项目的最佳实践可以避免依赖冲突。# 1. 创建项目目录并进入 mkdir mini-openclaw cd mini-openclaw # 2. 创建虚拟环境使用venv python3 -m venv venv # 3. 激活虚拟环境 # Linux/macOS source venv/bin/activate # Windows (cmd) # venv\Scripts\activate # 激活后命令行提示符前通常会显示 (venv)3.2 安装并配置Ollama本地大脑我们的Agent需要一个思考的“大脑”即大语言模型。我们选择Ollama在本地运行一个较小的模型。# 在终端中根据你的系统安装Ollama # 访问 https://ollama.com/download 获取官方一键安装脚本或使用以下命令Linux/macOS curl -fsSL https://ollama.com/install.sh | sh # 安装完成后拉取一个轻量级模型例如Qwen2.5-7B ollama pull qwen2.5:7b # 或者 Llama 3.2 的最新轻量版 # ollama pull llama3.2:1b # 启动模型服务默认会在本地11434端口启动 ollama run qwen2.5:7b # 第一次运行会加载模型你可以先按 CtrlC 退出交互模式服务会在后台运行。验证Ollama是否正常运行curl http://localhost:11434/api/generate -d { model: qwen2.5:7b, prompt: Hello, stream: false }如果看到返回了一段JSON里面有生成的文本说明模型服务正常。3.3 安装项目Python依赖在项目根目录下创建requirements.txt文件填入以下内容openai1.0.0 # 用于以OpenAI兼容格式调用Ollama requests2.28.0 # 用于实现网络搜索等工具 pyyaml6.0 # 用于读取YAML配置文件 python-dotenv1.0.0 # 用于管理环境变量然后安装它们pip install -r requirements.txt至此最精简的基础环境就准备好了。我们没有安装任何沉重的Web框架或复杂的中间件一切以够用、易懂为准。4. 核心模块实现手把手编写“小龙虾”的代码环境就绪现在开始编写核心代码。我们将按照之前设计的架构逐个模块实现。4.1 第一步定义工具Tool的基类与具体工具工具是Agent的手臂。我们先定义一个所有工具都必须遵循的基类规定它们必须有名字、描述和一个执行方法。在项目根目录创建tools.pyimport json import subprocess from abc import ABC, abstractmethod from typing import Any, Dict import requests from datetime import datetime class BaseTool(ABC): 所有工具的基类 name: str description: str abstractmethod def execute(self, **kwargs) - str: 执行工具返回结果字符串 pass def to_dict(self) - Dict[str, Any]: 将工具信息转换为字典用于提供给LLM return { name: self.name, description: self.description, parameters: self._get_parameters_schema() } abstractmethod def _get_parameters_schema(self) - Dict[str, Any]: 返回工具的参数JSON Schema用于让LLM知道如何调用 pass # 具体工具实现示例 class CalculatorTool(BaseTool): 一个简单的计算器工具能进行加减乘除。 name calculator description Useful for performing basic arithmetic calculations. Input should be a mathematical expression like 2 3 * 4. def _get_parameters_schema(self) - Dict[str, Any]: return { type: object, properties: { expression: { type: string, description: The mathematical expression to evaluate, e.g., 2 3 * 4. } }, required: [expression] } def execute(self, **kwargs) - str: expression kwargs.get(expression, ) if not expression: return Error: No expression provided. # 警告这里使用eval有安全风险仅用于演示。生产环境应用更安全的计算库如ast.literal_eval或numexpr。 try: # 简单替换一些常用符号增强兼容性 expr expression.replace(^, **).replace(x, *).replace(÷, /) result eval(expr, {__builtins__: {}}, {}) return fThe result of {expression} is {result}. except Exception as e: return fError calculating expression {expression}: {e} class WebSearchTool(BaseTool): 一个模拟的网络搜索工具实际调用DuckDuckGo Instant Answer API。 name web_search description Useful for searching the web for current information. Input should be a search query. def _get_parameters_schema(self) - Dict[str, Any]: return { type: object, properties: { query: { type: string, description: The search query string. } }, required: [query] } def execute(self, **kwargs) - str: query kwargs.get(query, ) if not query: return Error: No search query provided. try: # 使用DuckDuckGo的Instant Answer API无需API Key url https://api.duckduckgo.com/ params {q: query, format: json, no_html: 1, skip_disambig: 1} resp requests.get(url, paramsparams, timeout10) data resp.json() abstract data.get(AbstractText, ) if abstract: return fSearch result for {query}: {abstract} else: return fNo concise answer found for {query}. You may need to browse the full results. except requests.exceptions.RequestException as e: return fNetwork error during search: {e} class GetDateTimeTool(BaseTool): 获取当前日期和时间的工具。 name get_current_time description Useful for getting the current date and time. def _get_parameters_schema(self) - Dict[str, Any]: return {type: object, properties: {}} def execute(self, **kwargs) - str: now datetime.now() return fThe current date and time is: {now.strftime(%Y-%m-%d %H:%M:%S)} # 工具管理器 class ToolManager: 管理所有可用工具的注册和查找 def __init__(self): self._tools: Dict[str, BaseTool] {} def register_tool(self, tool: BaseTool): self._tools[tool.name] tool def get_tool(self, name: str) - BaseTool: return self._tools.get(name) def list_tools_for_llm(self) - list: 返回给LLM的工具列表描述 return [tool.to_dict() for tool in self._tools.values()] def execute_tool(self, tool_name: str, **kwargs) - str: tool self.get_tool(tool_name) if not tool: return fError: Tool {tool_name} not found. try: return tool.execute(**kwargs) except Exception as e: return fError executing tool {tool_name}: {e}注意上面的计算器工具使用了eval这在接受不可信用户输入时是极其危险的因为它可以执行任意Python代码。这里仅用于演示最简单原理。在实际项目中你必须使用安全的替代方案例如使用ast.literal_eval限制为字面量表达式但功能有限。使用专门的数学表达式解析库如numexpr或simpleeval。完全自己解析四则运算字符串。 安全是构建工具的第一要务。4.2 第二步实现Agent核心与LLM交互接下来我们创建Agent的核心它负责与LLM对话并根据LLM的回复决定是调用工具还是直接回答用户。创建agent.pyimport json import logging from typing import Dict, Any, List from openai import OpenAI from tools import ToolManager logging.basicConfig(levellogging.INFO) logger logging.getLogger(__name__) class MiniAgent: def __init__(self, model: str qwen2.5:7b, base_url: str http://localhost:11434/v1, api_key: str ollama): 初始化迷你Agent。 :param model: 使用的模型名称对应Ollama中的模型名。 :param base_url: Ollama的API地址兼容OpenAI格式。 :param api_key: 对于本地Ollama可以任意填写但不能为空。 self.client OpenAI(base_urlbase_url, api_keyapi_key) self.model model self.tool_manager ToolManager() self.conversation_history: List[Dict[str, str]] [] # 简单的对话记忆 def add_to_history(self, role: str, content: str): 向对话历史添加一条消息 self.conversation_history.append({role: role, content: content}) # 可选限制历史长度避免上下文过长 if len(self.conversation_history) 20: self.conversation_history self.conversation_history[-20:] def _build_messages_for_llm(self, user_input: str, tool_results: List[str] None) - List[Dict[str, str]]: 构建发送给LLM的消息列表包含系统指令、历史对话、工具描述和当前输入。 messages [] # 1. 系统指令告诉LLM它的角色和能力 system_prompt You are a helpful AI assistant with access to tools. You can use tools to help answer the users questions. When you need to use a tool, you MUST respond in the following JSON format: { thought: Your reasoning about what to do next, action: { name: tool_name, args: { arg1: value1, arg2: value2 } } } If you dont need a tool and can answer directly, respond in plain text. The available tools are: # 添加工具描述 tools_info json.dumps(self.tool_manager.list_tools_for_llm(), indent2) system_prompt tools_info \nRemember: Always think step by step. If using a tool, output ONLY the JSON. messages.append({role: system, content: system_prompt}) # 2. 添加历史对话用户和助理的交替 for msg in self.conversation_history[-6:]: # 只取最近几轮 messages.append(msg) # 3. 添加上一轮工具执行的结果如果有 if tool_results: for result in tool_results: # 将工具结果以“系统”或“工具”角色插入这里用“user”角色简单模拟 messages.append({role: user, content: f[Tool Result]: {result}}) # 4. 添加当前用户输入 messages.append({role: user, content: user_input}) return messages def process(self, user_input: str) - str: 处理用户输入的主要循环。 logger.info(fUser: {user_input}) self.add_to_history(user, user_input) max_turns 5 # 防止无限循环 final_answer None for turn in range(max_turns): # 构建消息 messages self._build_messages_for_llm(user_input if turn 0 else ) # 调用LLM try: response self.client.chat.completions.create( modelself.model, messagesmessages, temperature0.1, # 低温度让输出更确定更适合工具调用 streamFalse, ) llm_output response.choices[0].message.content.strip() logger.info(fLLM Output (Turn {turn1}): {llm_output}) except Exception as e: return fError calling LLM: {e} # 尝试解析LLM输出是否为JSON即工具调用 try: action_data json.loads(llm_output) # 检查是否符合我们的工具调用格式 if isinstance(action_data, dict) and action in action_data: thought action_data.get(thought, ) logger.info(fAgent Thought: {thought}) action action_data[action] tool_name action.get(name) tool_args action.get(args, {}) if not tool_name: final_answer LLM returned an action without a tool name. break # 执行工具 logger.info(fExecuting tool: {tool_name} with args: {tool_args}) tool_result self.tool_manager.execute_tool(tool_name, **tool_args) logger.info(fTool Result: {tool_result}) # 将工具结果添加到历史准备下一轮循环 self.add_to_history(assistant, f[Called tool {tool_name}]) # 这里简化处理将结果作为下一轮的“用户输入”的一部分 # 实际上更好的方式是将结果以特定角色插入历史 user_input tool_result # 下一轮LLM将基于工具结果继续思考 continue # 继续下一轮循环 else: # 输出不是有效的工具调用JSON视为最终回答 final_answer llm_output break except json.JSONDecodeError: # 输出不是JSON视为最终回答 final_answer llm_output break if final_answer is None: final_answer fReached maximum turns ({max_turns}) without a final answer. # 将助理的最终回答加入历史 self.add_to_history(assistant, final_answer) logger.info(fFinal Answer: {final_answer}) return final_answer这个MiniAgent类是整个系统的大脑。它的核心逻辑在一个循环中将用户输入、对话历史、可用工具描述组合成提示词发送给LLM。解析LLM的回复。如果回复是格式正确的JSON包含action就提取工具名和参数调用对应的工具。将工具执行结果反馈回去开启下一轮循环直到LLM给出自然语言回答或达到最大循环次数。使用temperature0.1是为了让LLM的输出更稳定、更可预测这对于工具调用的准确性至关重要。4.3 第三步编写主程序与配置最后我们创建一个主程序main.py来把一切串联起来并添加一个简单的配置文件。创建config.yamlagent: model: qwen2.5:7b # 使用的Ollama模型名 base_url: http://localhost:11434/v1 api_key: ollama # 本地运行可任意填写 max_turns: 5 tools: enabled: - calculator - web_search - get_current_time创建main.pyimport yaml from agent import MiniAgent from tools import CalculatorTool, WebSearchTool, GetDateTimeTool def load_config(config_path: str config.yaml): with open(config_path, r) as f: config yaml.safe_load(f) return config def main(): # 加载配置 config load_config() agent_config config.get(agent, {}) # 初始化Agent agent MiniAgent( modelagent_config.get(model, qwen2.5:7b), base_urlagent_config.get(base_url, http://localhost:11434/v1), api_keyagent_config.get(api_key, ollama) ) # 注册工具可以根据配置动态启用 enabled_tools config.get(tools, {}).get(enabled, []) all_tools { calculator: CalculatorTool(), web_search: WebSearchTool(), get_current_time: GetDateTimeTool(), } for tool_name in enabled_tools: if tool_name in all_tools: agent.tool_manager.register_tool(all_tools[tool_name]) print(fTool registered: {tool_name}) else: print(fWarning: Tool {tool_name} not found in available tools.) print(\n Mini OpenClaw Started ) print(Type exit or quit to end the conversation.\n) # 简单的命令行交互循环 while True: try: user_input input(\nYou: ).strip() if user_input.lower() in [exit, quit]: print(Goodbye!) break if not user_input: continue # 处理用户输入 response agent.process(user_input) print(f\nAssistant: {response}) except KeyboardInterrupt: print(\n\nInterrupted by user. Goodbye!) break except Exception as e: print(f\nAn error occurred: {e}) if __name__ __main__: main()5. 运行、测试与效果验证代码写完激动人心的时刻到了。让我们启动“小龙虾”看看它能不能像真正的Agent一样工作。5.1 启动与基础测试首先确保你的Ollama服务正在运行ollama run qwen2.5:7b在另一个终端运行着。然后在你的项目终端中# 确保在虚拟环境中 source venv/bin/activate # 运行主程序 python main.py如果一切顺利你会看到类似下面的输出Tool registered: calculator Tool registered: web_search Tool registered: get_current_time Mini OpenClaw Started Type exit or quit to end the conversation. You:现在让我们问几个问题来测试它的核心能力测试1纯对话不调用工具You: 你好介绍一下你自己。LLM应该会根据系统提示词用自然语言回答说明自己是一个有工具调用能力的助手。测试2调用计算器工具You: 请计算一下 (15 27) * 3 等于多少观察后台日志如果你设置了logging.INFO你应该能看到类似这样的流程INFO:agent:User: 请计算一下 (15 27) * 3 等于多少 INFO:agent:LLM Output (Turn 1): { thought: 用户需要一个数学表达式的计算结果。我有一个计算器工具。, action: { name: calculator, args: { expression: (15 27) * 3 } } } INFO:agent:Agent Thought: 用户需要一个数学表达式的计算结果。我有一个计算器工具。 INFO:agent:Executing tool: calculator with args: {expression: (15 27) * 3} INFO:agent:Tool Result: The result of (15 27) * 3 is 126.0. INFO:agent:LLM Output (Turn 2): 根据计算器工具的结果(15 27) * 3 等于 126。 INFO:agent:Final Answer: 根据计算器工具的结果(15 27) * 3 等于 126。最终你会在命令行看到助理的回答。这个过程完美演示了Agent的思考-行动-观察循环LLM先“思考”需要计算然后“行动”输出JSON调用工具系统执行工具并返回结果“观察”LLM最后基于观察给出最终回答。测试3调用网络搜索工具You: 今天北京的天气怎么样由于我们用的是DuckDuckGo API它可能会返回一些摘要信息。这演示了Agent如何获取实时信息。测试4调用时间工具You: 现在几点了它会返回系统的当前时间。5.2 常见问题与调试技巧在初次运行中你可能会遇到一些问题这里是一些排查思路问题连接Ollama失败报错ConnectionError检查确保ollama run命令正在运行并且监听在11434端口。可以用curl http://localhost:11434/api/tags测试。解决确认config.yaml中的base_url是否正确通常是http://localhost:11434/v1。注意末尾的/v1是OpenAI兼容接口必需的。问题LLM没有返回JSON而是直接说了话原因1提示词System Prompt不够强没有“逼”LLM严格遵守JSON格式。可以尝试强化提示词例如强调“你必须以JSON格式响应”、“只输出JSON不要有任何其他文字”。原因2模型能力或温度temperature设置问题。较小的模型可能对复杂格式指令遵循不佳。尝试将temperature降为0或换用指令遵循能力更强的模型如llama3.2:3b或qwen2.5:7b-instruct。调试打印出发送给LLM的完整消息messages看看系统指令是否清晰传递。问题工具调用参数解析错误检查LLM输出的JSON格式是否正确参数名是否与工具定义的_get_parameters_schema匹配解决在agent.py的JSON解析部分增加更健壮的异常处理并打印出解析前的原始字符串便于调试。问题工具执行出错如计算器eval错误检查工具本身的execute方法是否有bug输入参数是否合法解决在工具函数内部做好异常捕获并返回清晰的错误信息方便Agent进行下一步处理。一个关键的调试习惯充分利用日志。我们在关键步骤都加了logger.info运行程序时确保日志级别是INFO这样你就能清晰地看到Agent内部的思考链Thought、行动Action和观察Observation这是理解和调试Agent行为最重要的依据。6. 从“能用”到“好用”进阶优化与扩展思路我们的“小龙虾”已经能跑起来了但距离一个健壮、好用的系统还有距离。以下是几个关键的优化和扩展方向你可以选择自己感兴趣的去实现。6.1 优化一强化提示词工程Prompt Engineering系统提示词是Agent的“宪法”直接决定了它的行为模式。我们当前的提示词比较简单可以优化更清晰的结构使用XML标签或Markdown代码块来分隔指令、工具描述和示例让LLM更容易理解。加入少样本示例Few-Shot在系统提示词中直接给出一两个完整的“用户问题 - LLM思考并调用工具 - 工具结果 - LLM最终回答”的例子。这是让LLM学会遵循格式最有效的方法之一。约束输出明确要求LLM在“思考”部分进行链式推理Chain-of-Thought并严格限制其输出只能是纯文本或指定的JSON格式。6.2 优化二实现更可靠的工具调用解析当前我们简单地用json.loads()来解析LLM输出这很脆弱。LLM可能在JSON外加多余的解释。更健壮的做法是使用正则表达式从输出中提取第一个JSON块。或者使用LLM本身进行二次解析例如用一个极简的提示词“将以下文本中的JSON对象提取出来...”但这会增加延迟。采用支持“函数调用”Function Calling或“工具调用”Tool Calling的官方API。许多云厂商和新的本地模型如通过Ollama使用qwen2.5:7b-instruct时指定tools参数原生支持此功能能极大提高格式准确性。6.3 扩展一增加更多实用工具工具库是Agent能力的边界。你可以轻松添加新工具文件操作读取、写入、列出目录文件。数据库查询连接SQLite或MySQL执行查询。调用外部API集成天气预报、股票价格、翻译服务等。系统命令在受控环境下执行简单的shell命令注意安全。知识库检索结合本地向量数据库如ChromaDB让Agent能回答基于私有文档的问题。每添加一个工具只需创建一个继承BaseTool的新类并在main.py中注册即可。6.4 扩展二引入简单的技能Skill概念在OpenClaw中“技能”可能是更复杂的、由多个工具调用和逻辑判断组成的流程。我们可以在迷你版中做一个雏形定义一个Skill基类它也有name,description和一个execute方法。Skill的execute方法内部可以调用多个工具或者甚至调用另一个LLM进行子任务规划。在系统提示词中除了工具也把可用的技能描述提供给LLM。当用户请求一个复杂任务时LLM可以直接调用一个“技能”而不是自己一步步规划。例如可以创建一个“天气查询技能”它内部先调用“获取用户位置工具”或询问用户再调用“网络搜索天气API工具”最后将结果格式化输出。6.5 扩展三持久化记忆与状态管理目前的对话历史只在内存中程序重启就丢失。可以引入简单的持久化使用sqlite3数据库或json文件保存对话历史。为每个会话Session分配一个唯一ID实现多轮对话的隔离和恢复。引入摘要记忆Summary Memory当对话历史过长时让LLM自动生成一个摘要然后用摘要代替冗长的历史节省上下文窗口。6.6 部署与集成让它真正跑起来命令行增强使用argparse库支持启动参数如指定配置文件、模型等。简易Web接口使用FastAPI或Flask快速包装一个HTTP API这样就能从浏览器或其他程序调用你的Agent了。计划任务结合schedule或celery库让Agent可以定时执行某些任务如每日简报。集成到IM虽然完整集成飞书/微信很复杂但你可以利用它们的开放Webhook当收到消息时调用你的Agent API再将回复传回去实现一个最简单的聊天机器人。通过以上步骤你已经拥有了一个完全在自己掌控之中、架构清晰、可扩展的迷你AI Agent系统。它可能没有原版OpenClaw那么功能繁多但你完全理解它的每一行代码是如何工作的。这个过程中积累的经验——从提示词设计、工具封装到Agent循环控制——远比单纯部署一个黑盒系统有价值得多。接下来就根据你的实际需求尽情地改造和扩展你的“小龙虾”吧。