当前位置:首页>python>我用python重构了openclaw

我用python重构了openclaw

  • 2026-09-02 19:30:48
我用python重构了openclaw
最近一只很火的大龙虾席卷了全球,为什么他能这么火,我自己使用下来无非就是"人味",那我是如何用python复刻这个经典项目的呢
首先我得创建一个入口,我们都知道python中main是一个程序的入口,并且设计的时候尽量保持只有接口在main上.
所以main里面要放什么,首先考虑的就是配置,模型必须要有key的配置,否则咋调用模型呢,第二点就是要有飞书类似的社交媒体的接口,否则就只能在黑乎乎的terminal里用我们的privateclaw了,也可以看到有非常多的地方要用到大模型,我们聊天要用大模型吧,在状态机中我们需要大模型吧,在总结的我们的私人记忆的时候需要大模型把,这时候都要去配置好
def load_personalization() -> dict:    defaults = {        "api_key_env": "DASHSCOPE_API_KEY",        "base_url": "https://dashscope.aliyuncs.com/compatible-mode/v1",        "models": {            "chat": "qwen-max",            "router": "qwen-max",            "fsm": "qwen-max",            "plan": "qwen-max",            "summary": "qwen-max",        },    }    try:        withopen("personalization.yaml", "r", encoding="utf-8") as f:            raw = yaml.safe_load(f) or {}            defaults["api_key_env"] = raw.get("api_key_env", defaults["api_key_env"])            defaults["base_url"] = raw.get("base_url", defaults["base_url"])            models = raw.get("models", {})            for key in defaults["models"]:                defaults["models"][key] = models.get(key, defaults["models"][key])    except Exception:        pass    return defaults
接下来main里还需要什么呢,为什么openclaw能这么智能,就是它能够调用skill,什么是skill呢,简单来说就是给你的模型加上能执行的工具
def load_tool_config() -> list:    def _read_yaml(file_path: str):        try:            with open(file_path, "r", encoding="utf-8") as f:                return yaml.safe_load(f) or []        except Exception:            return []    core_tools = _read_yaml("tool_config.yaml")    dynamic_tools = _read_yaml("dynamic_config.yaml")    if isinstance(dynamic_tools, dict):        dynamic_tools = [dynamic_tools]    if not isinstance(dynamic_tools, list):        dynamic_tools = []    return core_tools + dynamic_tools
之后便是加上记忆这些杂七杂八的配置了,接下来我从三点讲为什么我重构的privateclaw能复刻openclaw的重点,我从三点说
第一点他能够调用工具,并且能更具基座大模型的智能,他调用工具的能力更好,那大模型是怎么调用工具的呢,他的数据在python里面流动,但是他无法看到工具长什么样子,那我们可以创建一个固定的格式在他运行之前告诉他,
- type: function  function:    name: get_system_time    description: Get the current real-time clock of the system.    parameters:      type: object      properties: {}      required: []- type: function  function:    name: web_search    description: 当你需要“快速获取少量最新事实/链接”时使用;适合先做信息探测与候选来源收集,再决定是否进入 deep_search。    parameters:      type: object      properties:        query:          type: string          description: 要在搜索引擎中输入的搜索关键词,一次搜索不到没有关系,可以拆解关键词找到答案。      required:        - query- type: function  function:    name: deep_search    description: 当问题复杂、需要多轮检索+页面阅读+反思总结时使用;它比 web_search 更慢但更全面,适合最终结论前的深挖。    parameters:      type: object      properties:        query:          type: string          description: 需要深度搜索的问题或主题。      required:        - query- type: function  function:    name: execute_python_code    description: 当需要计算、数据处理、格式转换、脚本验证时使用;用于可重复的程序化推导,不用于高风险系统操作。    parameters:      type: object      properties:        code_string:          type: string          description: The valid Python code string to be executed.      required:        - code_string- type: function  function:    name: create_new_skills    description: 当你需要长期拥有一个新能力时,调用此工具编写并保存永久的新技能。你需要提供完整的Python实现代码和匹配的YAML配置。    parameters:      type: object      properties:        skill_name:          type: string          description: 新技能的纯英文函数名,例如 calculate_tax。        python_code:          type: string          description: 完整的Python函数代码。        yaml_config:          type: string          description: |            严格遵守OpenAI工具格式的YAML文本。绝对禁止使用扁平结构!name和description必须嵌套在function内部!必须完全复制并填空以下模板,绝不能遗漏外层的横杠:            - type: function              function:                name: function_name_here                description: description_here                parameters:                  type: object                  properties: {}      required:        - skill_name        - python_code        - yaml_config- type: function  function:    name: exec_cli_command    description: |      受控命令执行工具。用于读取环境信息、运行诊断命令、执行安全脚本。      本工具内部会拒绝危险命令(如 rm、shutdown、mkfs 等);若出现权限/allowlist/审批类报错,不应盲目重试。    parameters:      type: object      properties:        command:          type: string          description: 需要执行的命令字符串。      required:        - command- type: function  function:    name: schedule_cli_command    description: |      定时执行命令工具。适用于用户明确提出“几秒后/几分钟后执行某命令”的需求。      危险命令会被自动拒绝;若命令依赖前台节点或权限,请先确认条件满足再调度。    parameters:      type: object      properties:        delay_seconds:          type: integer          description: 距离执行时间的秒数,必须大于 0。        command:          type: string          description: 到点后要执行的命令。      required:        - delay_seconds        - command
如这里所示,他能调用多种工具,执行自己写的python代码,并且如果自己写错还能返回报错减少了智能体执行任务时产生幻觉的可能性,执行cli代码能让你在千里之外能够知道你机器的状态,深度搜索能给予你机器的账号登陆状态看到你的机器里面专属社群的消息,cron功能能让你执行定时的任务,等等 这些功能你可以自己创建,把你的工作流程定义成一条固定的格式,能让机器自动执行,这就是智能的一个点 在讲第二点前我得说明下我的整个数据是如何一个流动的过程
飞书和cli终端 飞书通过layer层 终端和飞书交汇于agentruntime 之后再发给agentloop 最后再返回agentruntime  这是我给gemini的提示词,也可以看出,飞书或者是微信这个社交媒体的出口上,首先经过layer的清洗数据变得于cli统一,这时候再进入agentruntime,相当于是进入agentloop的管道,负责数据的进入和出来.
这时候我们又讲到激动人心的时刻了,agentloop工程化的又一典范,首先我先show一下mycode
import jsonfrom dataclasses import dataclassfrom datetime import datetimeimport timefrom uuid import uuid4from typing import Optionalfrom channel_layer import RuntimeMessage@dataclassclass LoopDecision:    kind: str  # "answer" | "tool_calls" | "need_approval"    answer: str = ""    tool_calls: Optional[list] = None    approval_request: Optional[dict] = Noneclass AgentLoop:    """Plan / Execute / Observe:planner 只决策,executor 只执行。"""    RUN_TIMEOUT_SECONDS = 60    MAX_STALL_STEPS = 8    MAX_SAME_TOOL_FAILURES = 3    NON_RETRIABLE_ERROR_SIGNATURES = (        "approval required",        "allowlist miss",        "permission denied",        "权限缺失",        "权限不足",        "forbidden",        "not authorized",        "节点不在前台",        "not in foreground",    )    def __init__(self, client, memory_manager, tool_config, available_tools, personalization: dict):        self.client = client        self.memory_manager = memory_manager        self.tool_config = tool_config        self.available_tools = available_tools        self.personalization = personalization        self.session_histories = {}        self.session_conversations = {}    @staticmethod    def _debug(stage: str, detail: str = ""):        now = datetime.now().strftime("%H:%M:%S")        suffix = f" | {detail}" if detail else ""        print(f"[DEBUG] {now}{stage}{suffix}")    @staticmethod    def _new_conversation_id() -> str:        return f"conv-{uuid4().hex[:10]}"    def _resolve_conversation_id(self, session_id: str, requested_conversation_id: str = "") -> str:        conversation_id = (requested_conversation_id or "").strip()        if conversation_id:            self.session_conversations[session_id] = conversation_id            return conversation_id        if session_id not in self.session_conversations:            self.session_conversations[session_id] = self._new_conversation_id()        return self.session_conversations[session_id]    def _reset_conversation(self, session_id: str) -> str:        new_id = self._new_conversation_id()        self.session_conversations[session_id] = new_id        self.session_histories[new_id] = []        return new_id    def _get_or_create_history(self, conversation_id: str):        if conversation_id not in self.session_histories:            self.session_histories[conversation_id] = []        return self.session_histories[conversation_id]    @staticmethod    def _build_tool_error_message(tool_call_id: str, name: str, reason: str) -> dict:        return {            "role": "tool",            "content": f"tool call not completed: {reason}",            "tool_call_id": tool_call_id,            "name": name,        }    def _repair_history(self, history: list[dict]) -> list[dict]:        """修复悬空 tool_calls,保证给模型和持久化前的历史结构合法。"""        repaired = []        i = 0        while i < len(history):            item = history[i]            repaired.append(item)            tool_calls = item.get("tool_calls") if isinstance(item, dict) else None            if item.get("role") == "assistant" and tool_calls:                required_ids = [tc.get("id") for tc in tool_calls if tc.get("id")]                j = i + 1                matched_ids = set()                buffered_following = []                while j < len(history):                    nxt = history[j]                    if isinstance(nxt, dict) and nxt.get("role") == "tool":                        buffered_following.append(nxt)                        tool_call_id = nxt.get("tool_call_id")                        if tool_call_id in required_ids:                            matched_ids.add(tool_call_id)                        j += 1                        continue                    break                repaired.extend(buffered_following)                missing_ids = [tid for tid in required_ids if tid not in matched_ids]                if missing_ids:                    for tc in tool_calls:                        tc_id = tc.get("id")                        if tc_id in missing_ids:                            name = ((tc.get("function") or {}).get("name") if isinstance(tc, dict) else "") or "unknown"                            repaired.append(self._build_tool_error_message(tc_id, name, "missing tool response patched"))                i = j                continue            i += 1        return repaired    def _plan(self, user_scope_id: str, history: list[dict]) -> LoopDecision:        history[:] = self._repair_history(history)        self._debug("plan_start")        response = self.client.chat.completions.create(            model=self.personalization["models"]["fsm"],            messages=[                {                    "role": "system",                    "content": "你是 Planner。优先直接回答;需要工具时发起 tool_calls;当问题需要多轮检索和网页阅读时优先调用 deep_search;危险工具先请求审批。",                },                {"role": "system", "content": self.memory_manager.build_system_context(user_scope_id=user_scope_id)},                *history,            ],            tools=self.tool_config,            stream=False,        )        message = response.choices[0].message        msg_dict = message.model_dump(exclude_none=True)        if msg_dict.get("content") is None:            msg_dict["content"] = ""        history.append(msg_dict)        if message.tool_calls:            tool_calls = [                {                    "id": t.id,                    "name": t.function.name,                    "arguments": t.function.arguments,                }                for t in message.tool_calls            ]            if self._needs_approval(tool_calls):                return LoopDecision(                    kind="need_approval",                    approval_request={"reason": "sensitive_tool", "tool_calls": tool_calls},                )            return LoopDecision(kind="tool_calls", tool_calls=tool_calls)        self._debug("plan_end", "answer")        return LoopDecision(kind="answer", answer=(message.content or "").strip())    @staticmethod    def _needs_approval(tool_calls: list[dict]) -> bool:        sensitive_keywords = ("delete", "remove", "exec", "shell", "write", "drop")        for call in tool_calls:            name = (call.get("name") or "").lower()            if any(word in name for word in sensitive_keywords):                return True        return False    @staticmethod    def _request_approval(_msg: RuntimeMessage, approval_request: dict) -> str:        tool_names = [c.get("name", "") for c in (approval_request or {}).get("tool_calls", [])]        return f"审批结果:自动批准。tools={','.join(tool_names)}"    @staticmethod    def _tool_failure_signature(tool_name: str, args: str) -> str:        return f"{tool_name}::{args}"    @staticmethod    def _is_tool_failure(text: str) -> bool:        low = (text or "").lower()        markers = (            "error",            "failed",            "failure",            "exception",            "命令执行失败",            "命令执行异常",            "json decode error",            "not found",        )        return any(m in low for m in markers)    def _match_non_retriable_signature(self, text: str) -> str:        low = (text or "").lower()        for sig in self.NON_RETRIABLE_ERROR_SIGNATURES:            if sig in low:                return sig        return ""    def _execute(self, tool_calls: list[dict], failure_counts: dict[str, int]) -> tuple[list[dict], str]:        self._debug("execute_start", f"count={len(tool_calls)}")        tool_results = []        hard_stop_reason = ""        for idx, tool_call in enumerate(tool_calls, start=1):            func_name = tool_call.get("name", "")            func_args_str = tool_call.get("arguments", "{}")            call_id = tool_call.get("id", f"tool-{idx}")            sig = self._tool_failure_signature(func_name, func_args_str)            result = f"error: tool '{func_name}' not found."            if func_name in self.available_tools:                func = self.available_tools.get(func_name)                try:                    json_args = json.loads(func_args_str)                    result = func(**json_args)                except json.JSONDecodeError as e:                    result = f"Tool arguments JSON decode error: {str(e)}"                except Exception as e:                    result = f"Error executing tool '{func_name}': {str(e)}"            result_text = str(result)            non_retry_sig = self._match_non_retriable_signature(result_text)            if non_retry_sig:                hard_stop_reason = (                    f"检测到不可重试错误签名: `{non_retry_sig}`。"                    f"工具 `{func_name}` 返回:{result_text}"                )            if self._is_tool_failure(result_text):                failure_counts[sig] = failure_counts.get(sig, 0) + 1                if failure_counts[sig] >= self.MAX_SAME_TOOL_FAILURES and not hard_stop_reason:                    hard_stop_reason = (                        f"同一工具与参数连续失败已达 {self.MAX_SAME_TOOL_FAILURES} 次,"                        f"停止重试。工具=`{func_name}` 参数=`{func_args_str}` 最近报错:{result_text}"                    )            else:                failure_counts[sig] = 0            tool_results.append(                {                    "role": "tool",                    "content": result_text,                    "tool_call_id": call_id,                    "name": func_name,                }            )            if hard_stop_reason:                break        self._debug("execute_end")        return tool_results, hard_stop_reason    def run(self, msg: RuntimeMessage) -> dict:        run_id = f"run-{uuid4().hex[:8]}"        session_id = msg.session_id        user_input = (msg.text or "").strip()        user_scope_id = (msg.user_scope_id or session_id).strip() or session_id        conversation_id = self._resolve_conversation_id(session_id, msg.conversation_id)        queue_wait_ms = max(0, int(time.time() * 1000) - int(getattr(msg, "enqueue_ts_ms", 0) or 0))        llm_ms_total = 0        tool_ms_total = 0        memory_ms_total = 0        if not user_input:            self._debug(                "run_metrics",                f"run_id={run_id} session_id={session_id} conversation_id={conversation_id} "                f"queue_wait_ms={queue_wait_ms} llm_ms={llm_ms_total} tool_ms={tool_ms_total} "                f"memory_ms={memory_ms_total} dedup_key={getattr(msg, 'dedup_key', '')}",            )            return {                "session_id": session_id,                "conversation_id": conversation_id,                "user_scope_id": user_scope_id,                "text": "Empty input.",            }        if user_input == "/reset":            new_conversation_id = self._reset_conversation(session_id)            self._debug(                "run_metrics",                f"run_id={run_id} session_id={session_id} conversation_id={new_conversation_id} "                f"queue_wait_ms={queue_wait_ms} llm_ms={llm_ms_total} tool_ms={tool_ms_total} "                f"memory_ms={memory_ms_total} dedup_key={getattr(msg, 'dedup_key', '')}",            )            return {                "session_id": session_id,                "conversation_id": new_conversation_id,                "user_scope_id": user_scope_id,                "text": f"会话已重置,新短期会话ID: {new_conversation_id}",            }        history = self._get_or_create_history(conversation_id)        history.append({"role": "user", "content": user_input})        state = "PLANNING"        pending_tool_calls = []        final_answer = ""        loop_start = time.perf_counter()        failure_counts: dict[str, int] = {}        last_snapshot = ""        stall_steps = 0        for _ in range(64):            if (time.perf_counter() - loop_start) > self.RUN_TIMEOUT_SECONDS:                final_answer = "本轮处理超过 60 秒已强制结束,请你根据当前报错继续排障,我已把控制权交还给你。"                break            snapshot = f"{state}|{len(history)}|{len(pending_tool_calls)}|{final_answer}"            if snapshot == last_snapshot:                stall_steps += 1            else:                stall_steps = 0                last_snapshot = snapshot            if stall_steps >= self.MAX_STALL_STEPS:                final_answer = "连续 8 步无状态变化,已终止本轮处理。请提供更具体输入或调整权限/参数后重试。"                break            if state == "PLANNING":                t_llm_start = time.perf_counter()                decision = self._plan(user_scope_id=user_scope_id, history=history)                llm_ms_total += int((time.perf_counter() - t_llm_start) * 1000)                if decision.kind == "answer":                    final_answer = decision.answer or ""                    break                if decision.kind == "tool_calls":                    pending_tool_calls = decision.tool_calls or []                    state = "EXECUTING"                    continue                if decision.kind == "need_approval":                    approval_result = self._request_approval(msg, decision.approval_request or {})                    self._debug("approval", approval_result)                    pending_tool_calls = (decision.approval_request or {}).get("tool_calls", [])                    state = "EXECUTING"                    continue            if state == "EXECUTING":                t_tool_start = time.perf_counter()                tool_results, hard_stop_reason = self._execute(pending_tool_calls, failure_counts=failure_counts)                tool_ms_total += int((time.perf_counter() - t_tool_start) * 1000)                history.extend(tool_results)                if hard_stop_reason:                    final_answer = (                        "工具执行已停止,原因如下:\n"                        f"{hard_stop_reason}\n\n"                        "这类错误通常不应继续自动重试,请你确认权限、allowlist、审批状态或前台节点状态后再继续。"                    )                    break                state = "OBSERVING"                continue            if state == "OBSERVING":                state = "PLANNING"                continue        else:            final_answer = "处理超出最大轮次,请简化问题后重试。"        if final_answer:            history.append({"role": "assistant", "content": final_answer})        history[:] = self._repair_history(history)        t_memory_start = time.perf_counter()        self.memory_manager.update_memory(user_input, final_answer, user_scope_id=user_scope_id)        self.memory_manager.maybe_update_soul(user_scope_id=user_scope_id)        compacted_history = self.memory_manager.compact_history_if_needed(            history,            max_chars=256000,            user_scope_id=user_scope_id,        )        memory_ms_total += int((time.perf_counter() - t_memory_start) * 1000)        if compacted_history is not history:            new_conversation_id = self._new_conversation_id()            self.session_conversations[session_id] = new_conversation_id            self.session_histories[new_conversation_id] = compacted_history            self.session_histories.pop(conversation_id, None)            conversation_id = new_conversation_id        else:            self.session_histories[conversation_id] = compacted_history        self._debug(            "run_metrics",            f"run_id={run_id} session_id={session_id} conversation_id={conversation_id} "            f"queue_wait_ms={queue_wait_ms} llm_ms={llm_ms_total} tool_ms={tool_ms_total} "            f"memory_ms={memory_ms_total} dedup_key={getattr(msg, 'dedup_key', '')}",        )        return {            "session_id": session_id,            "conversation_id": conversation_id,            "user_scope_id": user_scope_id,            "text": final_answer,        }
总而言之这段代码是在干嘛的,我先可以把它抽象为四个流程,其实如果有其他层可以加入,你可以加入你想要的状态
计划 → 执行 → 观察 → 再计划 这就是agent执行的流程 被强制从工程层面执行思考执行的过程,这被称为状态机,fsm,通过这样子的措施,能让ai执行更长时间的任务,更高的正确率,其中也有很多鲁棒性的加入,比如设置最大错误次数,防止ai陷入思考的死路径,例如这种优化可以大大优化用户的体验
第三点就是最后一点 记忆功能
这里就不多赘述了,你可以理解成首先我的对话是短期记忆,他能记录你目前还有什么任务没有完成,长期记忆,当你命令他要记住重要的内容,他就可以把这些重要的信息写入自己的文档里.其中的设计包括上下文过长自动压缩,这些可以留到以后再讲

https://github.com/dsadsasdaddas/privateclaw

这是我的仓库,大家可以借鉴学习,如果有交流可以在评论区里交流

最新文章

随机文章