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)模板来实现。
- 工具注册与管理中心(Toolbox):Agent需要“手”来执行具体任务。我们将所有可用的功能(如搜索、计算、读写文件)封装成统一的工具(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 或 macOS,Windows用户建议使用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 # 第一次运行会加载模型,你可以先按 Ctrl+C 退出交互模式,服务会在后台运行。验证Ollama是否正常运行:
curl http://localhost:11434/api/generate -d '{ "model": "qwen2.5:7b", "prompt": "Hello", "stream": false }'如果看到返回了一段JSON,里面有生成的文本,说明模型服务正常。
3.3 安装项目Python依赖
在项目根目录下,创建requirements.txt文件,填入以下内容:
openai>=1.0.0 # 用于以OpenAI兼容格式调用Ollama requests>=2.28.0 # 用于实现网络搜索等工具 pyyaml>=6.0 # 用于读取YAML配置文件 python-dotenv>=1.0.0 # 用于管理环境变量然后安装它们:
pip install -r requirements.txt至此,最精简的基础环境就准备好了。我们没有安装任何沉重的Web框架或复杂的中间件,一切以够用、易懂为准。
4. 核心模块实现:手把手编写“小龙虾”的代码
环境就绪,现在开始编写核心代码。我们将按照之前设计的架构,逐个模块实现。
4.1 第一步:定义工具(Tool)的基类与具体工具
工具是Agent的手臂。我们先定义一个所有工具都必须遵循的基类,规定它们必须有名字、描述和一个执行方法。
在项目根目录创建tools.py:
import 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 f"The result of '{expression}' is {result}." except Exception as e: return f"Error 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, params=params, timeout=10) data = resp.json() abstract = data.get("AbstractText", "") if abstract: return f"Search result for '{query}': {abstract}" else: return f"No concise answer found for '{query}'. You may need to browse the full results." except requests.exceptions.RequestException as e: return f"Network 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 f"The 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 f"Error: Tool '{tool_name}' not found." try: return tool.execute(**kwargs) except Exception as e: return f"Error executing tool '{tool_name}': {e}"注意:上面的计算器工具使用了
eval,这在接受不可信用户输入时是极其危险的,因为它可以执行任意Python代码。这里仅用于演示最简单原理。在实际项目中,你必须使用安全的替代方案,例如:
- 使用
ast.literal_eval限制为字面量表达式(但功能有限)。- 使用专门的数学表达式解析库,如
numexpr或simpleeval。- 完全自己解析四则运算字符串。 安全是构建工具的第一要务。
4.2 第二步:实现Agent核心与LLM交互
接下来,我们创建Agent的核心,它负责与LLM对话,并根据LLM的回复决定是调用工具还是直接回答用户。
创建agent.py:
import json import logging from typing import Dict, Any, List from openai import OpenAI from tools import ToolManager logging.basicConfig(level=logging.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_url=base_url, api_key=api_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 user's 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 don't 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(), indent=2) 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(f"User: {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( model=self.model, messages=messages, temperature=0.1, # 低温度,让输出更确定,更适合工具调用 stream=False, ) llm_output = response.choices[0].message.content.strip() logger.info(f"LLM Output (Turn {turn+1}): {llm_output}") except Exception as e: return f"Error 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(f"Agent 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(f"Executing tool: {tool_name} with args: {tool_args}") tool_result = self.tool_manager.execute_tool(tool_name, **tool_args) logger.info(f"Tool 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 = f"Reached maximum turns ({max_turns}) without a final answer." # 将助理的最终回答加入历史 self.add_to_history("assistant", final_answer) logger.info(f"Final Answer: {final_answer}") return final_answer这个MiniAgent类是整个系统的大脑。它的核心逻辑在一个循环中:
- 将用户输入、对话历史、可用工具描述组合成提示词,发送给LLM。
- 解析LLM的回复。如果回复是格式正确的JSON(包含
action),就提取工具名和参数,调用对应的工具。 - 将工具执行结果反馈回去,开启下一轮循环,直到LLM给出自然语言回答或达到最大循环次数。
- 使用
temperature=0.1是为了让LLM的输出更稳定、更可预测,这对于工具调用的准确性至关重要。
4.3 第三步:编写主程序与配置
最后,我们创建一个主程序main.py来把一切串联起来,并添加一个简单的配置文件。
创建config.yaml:
agent: 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.py:
import 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( model=agent_config.get('model', 'qwen2.5:7b'), base_url=agent_config.get('base_url', 'http://localhost:11434/v1'), api_key=agent_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(f"Tool registered: {tool_name}") else: print(f"Warning: 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解析部分增加更健壮的异常处理,并打印出解析前的原始字符串,便于调试。
- 检查:LLM输出的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循环控制——远比单纯部署一个黑盒系统有价值得多。接下来,就根据你的实际需求,尽情地改造和扩展你的“小龙虾”吧。