Claude Code 是如何狙杀中国用户的
继昨天 Claude Code 源码泄露之后,全网已经狂欢了一整天。 除了学习它的 agent 架构、prompt 组织方式,对很多又爱又恨 Claude Code 的中国用户来说,还可以研究下怎么"道高一尺、魔高一丈!" 我们来对比泄露的代码和 @anthropic-ai/claude-code 当前 npm 包里的 cli.js,研究下 Claude Code 是如何精准定位用户的! 一、Claude Code 到底在送什么 1. system prompt 里就带着环境信息 很多人会本能地觉得,“追踪"无非就是打点埋点,把那一路关了就行。 ...
泄露的 Claude Code 源代码解析
今天有人在 npm registry 上发现,Anthropic 发布的 Claude Code 包里附带了 .map 文件——source map。 这东西本来是给浏览器 debug 用的,附带进生产包是个失误。代价是:原始 TypeScript 源码可以被完整还原出来。 一时间社区里传开了,完整的源代码结构被扒了出来。 本着学习 coding agent 工程实现的目的,深入读一下这份代码,看看 Anthropic 是怎么构建一个生产级 coding agent 的。 先看项目结构 整个代码库遵循层级优先的组织方式——顶层目录对应架构子系统,不是按功能分组的。 kadaliao/claude-code (main 分支) ├── main.tsx # CLI 入口 ├── QueryEngine.ts # 核心查询编排引擎 ├── Tool.ts # 工具接口定义与类型 ├── Task.ts # 后台任务类型定义 ├── query.ts # 异步生成器查询循环 ├── tools.ts # 工具注册表 (40+ 内置工具) ├── commands.ts # 斜杠命令注册表 (80+ 命令) ├── context.ts # 系统/用户上下文组装 ├── ink.ts # 自定义 Ink 渲染引擎封装 │ ├── screens/ │ ├── REPL.tsx # 主交互式 REPL(约 5000 行) │ ├── Doctor.tsx # 诊断界面 │ └── ResumeConversation.tsx # 会话恢复界面 │ ├── components/ # 100+ React UI 组件 ├── hooks/ # 自定义 React Hooks ├── state/ # AppState 状态存储 ├── services/ # 后端服务模块 ├── tools/ # 各工具的具体实现 ├── commands/ # 各斜杠命令的实现 ├── skills/ # 动态技能加载系统 ├── tasks/ # 后台 Agent 任务类型 ├── entrypoints/ # 初始化与 SDK 入口 ├── bridge/ # 远程连接与移动端桥接 ├── coordinator/ # 协调器模式编排 ├── assistant/ # 助手(KAIROS)模式 ├── plugins/ # 插件系统 └── voice/ # 语音输入集成 几个马上能引起注意的点: ...
Human 真的需要 in the loop 吗?
这个问题听起来简单,但答案可能比你想象的更微妙。 很多人讨论 AI 的时候,喜欢问这么一句: “Human 真的需要 in the loop 吗?” 这个问题听起来很自然,但我越来越觉得,它问得还是有点太粗了。 真正值得问的,不是"要不要人",而是: 人在什么层级、什么条件、以什么成本,留在什么样的 loop 里。 因为 AI 时代真正发生的变化,不是人突然消失了,而是—— 人的位置上移了。 很多低层、重复性的执行 loop,确实正在被系统接管。但在更高层的目标定义、结果验收、例外处理、责任承担这些环节,人不但没有退出,反而变得更关键。 所以更准确的问题其实是:未来什么样的人,还能留在高价值的 loop 里。 01 / 不是要不要人,而是人留在哪一层 过去很多人理解自动化,总是带着一种二元视角: 要么人亲自做 要么系统自动做 但真实世界并不是这么切的。 同一个系统里,本来就存在很多层不同的 loop。 低层的 loop,比如调接口、改格式、填表单、搬运数据、执行规则明确的操作——这些最容易被替代。它们目标清楚,输入输出稳定,出错后反馈也容易获得。 高层的 loop,就完全不同了: 目标到底是什么? 什么叫"足够好"? 出现例外时该不该破例? 多个目标冲突时先保哪个? 一旦出事,谁来承担责任? 这些问题,很难被一句"让 AI 自己做完闭环"带过去。 所以今天真正的变化,不是 human out of the loop,而是: Human moves up the loop。 人正在从"亲手做每一步",慢慢转向: 定义目标 设置边界 看异常 做验收 接管例外 为结果负责 这才是更准确的现实。 02 / 为什么现阶段人还退不干净 很多人会说,如果需求、方向、标准都定义清楚了,剩下的不就是执行吗?那当然应该尽量 out of the loop。 ...
Harness Engineering 是什么,如何落地
最近将 Token 翻译成词元之后,又有个词突然变热且暂时没有合适的翻译:Harness Engineering。 不是新模型,不是新框架,甚至不是新工具——就是这么一个词,突然开始出现在各种技术讨论里。 它从哪来的?它在说什么?为什么现在火起来? 我们来拆解三篇相关的重要文章,最后讲讲实践中如何落地。 先说这个词本身 Harness,字面意思是"缰绳"或"挽具"。 放进 AI 工程的语境:Harness 是把 agent 纳入工程系统的那套控制结构——让 agent 的工作变得可约束、可验证、可回放,而不是每次运气好就成功、运气不好就不知道哪里出了问题。 它不是某一个工具,也不是某一个 prompt 技巧。它是一套工程思路:当代码主要由 agent 生成,工程师的工作重心从"写代码"转向"设计让 agent 能够有效工作的环境"。 听起来很虚?下面看具体的。 第一篇:OpenAI,2 月 11 日 这个词真正开始传播,是因为 OpenAI 在 2 月 11 日发了一篇工程博客,标题叫《Harness Engineering: Leveraging Codex in an Agent-First World》。 文章里有一组数字,很多人看完沉默了: 5 个月。3 名工程师。约 100 万行代码。约 1,500 个 PR,平均每人每天 3.5 个。 更关键的是:从第一个 commit 开始,仓库里没有一行代码是人手写的。 连最初的 AGENTS.md——用来告诉 agent 怎么在这个项目里工作的文件——都是 agent 自己写的。 但这不是 vibe coding。 OpenAI 团队在文章里说了一句很关键的话: 早期进展比预期慢——不是因为 Codex 没有能力,而是因为环境没有定义好。 ...
微信官方小龙虾插件协议拆解
今天微信发布了官方龙虾插件 微信ClawBot,支持 OpenClaw 接入个人微信。 第一时间尝试之后,深入源代码,分析其协议实现。废话不多说,直接开始。 一张图先看全局 整套协议的结构其实非常规整:控制面走 Bot 网关,��据面走 CDN,插件本地负责把两者接起来。 控制面接口主要有 5 个: getupdates sendmessage getuploadurl getconfig sendtyping 数据面则只有一件事:媒体文件通过 CDN 上传/下载,本地做 AES-128-ECB 加解密。 这就是理解整套协议的入口。 1. 登录链路:二维码授权 -> bot_token 先看登录。 插件登录阶段只依赖两个接口: GET /ilink/bot/get_bot_qrcode?bot_type=3 GET /ilink/bot/get_qrcode_status?qrcode=... 第一步拿二维码,第二步轮询二维码状态。它的状态机大致如下: 确认授权后,服务端会返回: { "status": "confirmed", "bot_token": "<bot_token>", "ilink_bot_id": "xxxxxxxx@im.bot", "ilink_user_id": "xxxxxxxx@im.wechat", "baseurl": "https://ilinkai.weixin.qq.com" } 这里真正关键的是 4 个字段: bot_token:后续所有业务请求的 Bearer 凭证 ilink_bot_id:Bot 身份 ilink_user_id:绑定的微信用户 baseurl:服务端下发的 API 基址 插件会把这些值保存到本地账号文件,后续 gateway 启动时直接复用。 一句话概括登录阶段:二维码只是授权入口,真正建立会话能力的是 bot_token。 2. 统一请求头:协议外壳非常稳定 除了扫码登录用 GET 之外,后续业务接口基本都是 JSON POST。统一请求头如下: Content-Type: application/json AuthorizationType: ilink_bot_token Authorization: Bearer <bot_token> X-WECHAT-UIN: <base64(random uint32 decimal string)> SKRouteTag: <optional> 这里可以顺手记住 3 个实现细节。 ...
小龙虾为何变蠢、失忆?深入理解 OpenClaw 记忆系统
很多人辛辛苦苦养的小龙虾,都会在某个时刻让主人产生一种挫败感: 昨天刚说过的偏好,今天新开一个会话又得重讲; 明明说了"记住这个",过几天再问,它像没听过; 会话越长,助手越像"注意力涣散",前面已经确认过的事,后面又绕回来。 这类问题看起来像"小龙虾变笨了",怀疑给虾使用的大模型不够智能?其实大多数时候都不是模型能力问题,而是记忆机制没有被正确使用。 (本文所说的小龙虾,通过 OpenClaw 部署。研究对象也是 OpenClaw。接下来将统一口径,使用 OpenClaw 来称呼。) OpenClaw 的记忆系统并不神秘。恰恰相反,它非常工程化:记忆不是藏在黑盒里的,而是落在工作区里的 Markdown 文件、会话历史和检索索引里。 你理解了这套机制,很多"为什么它会忘"的问题都会立刻变清楚。 这篇文章在深入研究 OpenClaw 官方文档、拆解源代码之后,讲清楚三件事: OpenClaw 到底靠什么"记住"你; 为什么它会忘; 怎样把它调到一个长期稳定、可维护的状态。 一、先把一个认知纠正过来:模型本身不会跨会话记忆 大语言模型并不会像人一样,把昨天的对话自然带到今天。 它每次能看到的,只有本轮请求被送进上下文窗口的内容。如果某条信息没有进入当前上下文,模型就等于没看见;如果某条信息从来没有被写到磁盘,下次重启后也就无从谈起。 这也是 OpenClaw 记忆设计最核心的一条原则: 文件才是记忆的唯一可信来源。 按照 OpenClaw 当前官方文档,记忆是工作区中的纯 Markdown 文件:模型只会"记住"那些被写入磁盘、并在合适时机重新加载或检索出来的内容。 这件事听上去朴素,但它带来一个非常重要的推论: “我已经在对话里说过了”,不等于"它下次还会记得"; “它刚才答应会记住”,不等于"它已经落盘"; “它知道我是什么意思”,不等于"这条信息已经进入长期记忆层"。 如果你把 OpenClaw 当作一个有天然长期记忆的大脑,就很容易失望;如果你把它当作一个带文件系统、检索和压缩机制的 Agent 运行时,它的行为反而会变得非常可预测。 二、从使用体验看,OpenClaw 实际上有三层"记忆" 官方文档在"记忆文件"这一层主要讲两种载体:MEMORY.md 和 memory/YYYY-MM-DD.md。但从实际使用体验看,OpenClaw 的"记得住"与"记不住",是三层机制共同作用的结果: 层级 载体 作用 典型内容 工作记忆 当前会话历史 维持本轮任务的连续性 刚才的问答、工具调用结果、当前任务状态 短期记忆 memory/ 保留近期上下文和运行日志 会话摘要、日常运行记录、短期上下文 长期记忆 MEMORY.md 保留跨会话稳定信息 偏好、长期事实、重要决策、环境约定 这三层各自解决的是不同问题。 1. 工作记忆:负责"眼前这件事" 当前会话历史就是工作记忆。它保证 OpenClaw 在同一段对话里还能接着前文往下走。 ...
MCP 协议入门:给 AI Agent 装上标准化工具接口
2024 年底,Anthropic 开源了 MCP(Model Context Protocol),一个让 AI 模型与外部工具、数据源交互的标准化协议。2025 年以来,越来越多的工具和平台开始支持 MCP。 MCP 解决了什么问题 在 MCP 之前,每个 AI 应用都要自己实现工具集成: 应用 A → 自己写 Google Drive 集成 应用 B → 自己写 Google Drive 集成 应用 C → 自己写 Google Drive 集成 有了 MCP: Google Drive MCP Server(写一次) ↓ 应用 A、B、C 都能直接接 MCP 是一个客户端-服务端协议,AI 应用是客户端,工具提供方实现 MCP Server。 核心概念 MCP Server 可以暴露三种东西: Tools:模型可以调用的函数(类似 Function Calling) Resources:模型可以读取的数据(文件、数据库等) Prompts:预定义的 Prompt 模板 用 Python 写一个最简 MCP Server from mcp.server import Server from mcp.server.stdio import stdio_server from mcp import types app = Server("my-tools") @app.list_tools() async def list_tools() -> list[types.Tool]: return [ types.Tool( name="get_time", description="获取当前时间", inputSchema={ "type": "object", "properties": { "timezone": { "type": "string", "description": "时区,如 Asia/Shanghai" } } } ) ] @app.call_tool() async def call_tool(name: str, arguments: dict): if name == "get_time": from datetime import datetime import pytz tz = pytz.timezone(arguments.get("timezone", "Asia/Shanghai")) now = datetime.now(tz) return [types.TextContent(type="text", text=now.strftime("%Y-%m-%d %H:%M:%S %Z"))] async def main(): async with stdio_server() as streams: await app.run(*streams, app.create_initialization_options()) if __name__ == "__main__": import asyncio asyncio.run(main()) 在 Claude Desktop 里使用 在 ~/Library/Application Support/Claude/claude_desktop_config.json 里配置: ...
LangGraph 实战:构建一个可控的 ReAct Agent
ReAct(Reasoning + Acting)是让 Agent 先思考再行动的范式。这篇文章用 LangGraph 实现一个完整的 ReAct Agent,并加入一些生产环境需要的工程细节。 ReAct 的执行流程 用户输入 ↓ 思考(Thought):分析问题,决定下一步 ↓ 行动(Action):选择并调用工具 ↓ 观察(Observation):获取工具结果 ↓ 循环,直到能给出最终答案 ↓ 最终答案 用 LangGraph 实现 from typing import TypedDict, Annotated from langchain_core.messages import BaseMessage, HumanMessage, AIMessage, ToolMessage from langchain_openai import ChatOpenAI from langgraph.graph import StateGraph, END import operator class AgentState(TypedDict): messages: Annotated[list[BaseMessage], operator.add] iteration: int # 防止无限循环 # 初始化 LLM(绑定工具) llm = ChatOpenAI(model="gpt-4o", temperature=0) llm_with_tools = llm.bind_tools(tools) def should_continue(state: AgentState) -> str: """决定下一步走哪条边""" last_message = state["messages"][-1] # 超出最大迭代次数,强制结束 if state["iteration"] >= 10: return "end" # 最后一条消息没有工具调用,说明已经有最终答案了 if not hasattr(last_message, "tool_calls") or not last_message.tool_calls: return "end" return "tools" def call_llm(state: AgentState) -> AgentState: """调用 LLM 节点""" response = llm_with_tools.invoke(state["messages"]) return { "messages": [response], "iteration": state["iteration"] + 1 } def call_tools(state: AgentState) -> AgentState: """执行工具调用节点""" last_message = state["messages"][-1] tool_messages = [] for tool_call in last_message.tool_calls: tool_name = tool_call["name"] tool_args = tool_call["args"] try: result = tool_registry[tool_name].invoke(tool_args) content = str(result) except Exception as e: content = f"工具调用失败:{str(e)}" # 错误不崩溃,反馈给模型 tool_messages.append(ToolMessage( content=content, tool_call_id=tool_call["id"] )) return {"messages": tool_messages} # 构建图 graph = StateGraph(AgentState) graph.add_node("llm", call_llm) graph.add_node("tools", call_tools) graph.set_entry_point("llm") graph.add_conditional_edges("llm", should_continue, {"tools": "tools", "end": END}) graph.add_edge("tools", "llm") # 工具执行完,回到 LLM 节点 agent = graph.compile() 加入 Human-in-the-loop LangGraph 支持在某些步骤暂停,等待人工确认: ...
Agent 框架横评:LangGraph vs AutoGen vs CrewAI
2024 年,Agent 框架的竞争进入白热化阶段。我在几个项目里分别用过 LangGraph、AutoGen 和 CrewAI,这篇文章做一个工程视角的横评。 三者定位 框架 核心定位 适合场景 LangGraph 有状态的图执行引擎 需要精确控制流程的复杂 Agent AutoGen Multi-Agent 对话框架 多个 Agent 协作解决问题 CrewAI 角色扮演式 Multi-Agent 任务分工明确的团队协作场景 LangGraph LangGraph 是 LangChain 团队推出的,把 Agent 的执行流程建模成一个有向图。 from langgraph.graph import StateGraph, END from typing import TypedDict class AgentState(TypedDict): messages: list next_step: str def agent_node(state: AgentState): # 调用 LLM 决策下一步 ... return {"next_step": "tool" if needs_tool else "end"} def tool_node(state: AgentState): # 执行工具 ... return {"messages": [...]} graph = StateGraph(AgentState) graph.add_node("agent", agent_node) graph.add_node("tool", tool_node) graph.add_edge("tool", "agent") graph.add_conditional_edges("agent", lambda s: s["next_step"]) 优点:流程完全可控,支持循环、分支、人工介入(human-in-the-loop),适合生产环境。 缺点:上手成本高,要理解图的概念;代码相对冗长。 AutoGen AutoGen 是微软出的,核心是让多个 Agent 通过对话协作: from autogen import AssistantAgent, UserProxyAgent assistant = AssistantAgent( name="助手", llm_config={"model": "gpt-4o"} ) user_proxy = UserProxyAgent( name="用户", human_input_mode="NEVER", # 全自动 code_execution_config={"work_dir": "workspace"} ) user_proxy.initiate_chat( assistant, message="帮我写一个爬虫,抓取 Hacker News 首页" ) 优点:能自动执行代码,适合需要写代码解决问题的场景。 ...
RAG 进阶优化:提升检索质量的七个方向
上一篇文章介绍了基础 RAG 的搭建。基础 RAG 跑起来之后,你会发现效果差强人意——召回的内容不够准、回答有时候答非所问。这篇文章梳理提升 RAG 效果的常见优化方向。 方向 1:切块策略优化 基础的固定大小切块太粗糙,几个更好的策略: 按语义切块(Semantic Chunking): from langchain_experimental.text_splitter import SemanticChunker from langchain_openai import OpenAIEmbeddings splitter = SemanticChunker( OpenAIEmbeddings(), breakpoint_threshold_type="percentile" ) chunks = splitter.split_text(document) 语义切块基于 Embedding 相似度判断段落边界,比按字符数切更合理。 父子文档(Parent-Child):小块用于检索,大块用于生成: from langchain.retrievers import ParentDocumentRetriever retriever = ParentDocumentRetriever( vectorstore=vectorstore, docstore=InMemoryStore(), child_splitter=RecursiveCharacterTextSplitter(chunk_size=200), parent_splitter=RecursiveCharacterTextSplitter(chunk_size=2000), ) 小块召回精准,但上下文不足;大块提供足够上下文,但噪声多。父子文档两全其美。 方向 2:Query 改写 用户的提问往往不是最优的检索 query: async def rewrite_query(query: str) -> list[str]: prompt = f""" 生成 3 个不同角度的检索查询,帮助从文档库中找到回答以下问题的信息。 原始问题:{query} 输出格式:每行一个查询 """ response = await llm.ainvoke(prompt) queries = response.content.strip().split("\n") return [query] + queries # 原始查询 + 改写的查询 用多个 query 检索,再合并去重,召回率显著提升。 方向 3:HyDE(假设文档嵌入) 让模型先生成一个"假设的答案文档",用它来检索: async def hyde_retrieve(query: str) -> list[Document]: # 让模型生成一个假设的答案 hypothetical_doc = await llm.ainvoke( f"写一段简短的文章,回答以下问题(即使你不确定):{query}" ) # 用假设答案的向量来检索,而不是用问题的向量 docs = vectorstore.similarity_search(hypothetical_doc.content, k=4) return docs 假设答案的 Embedding 比问题的 Embedding 更接近文档的分布,检索效果往往更好。 ...