基于 LangGraph 一步步实现 Claude

基于LangGraph实现Claude的过程可以分为以下几个步骤:
1. "安装LangGraph库": 首先,确保你已经安装了LangGraph库。如果还没有安装,可以使用pip进行安装: ```bash pip install langgraph ```
2. "创建Claude模型": Claude是一个基于Transformer的预训练语言模型,你可以使用Hugging Face的Transformers库来加载和使用Claude模型。首先,安装Transformers库: ```bash pip install transformers ```
然后,创建一个函数来加载和使用Claude模型: ```python from transformers import AutoModelForCausalLM, AutoTokenizer
def load_claude_model(): tokenizer = AutoTokenizer.from_pretrained("HuggingFace/claude") model = AutoModelForCausalLM.from_pretrained("HuggingFace/claude") return tokenizer, model ```
3. "定义LangGraph节点": LangGraph允许你定义多个节点,每个节点可以是一个函数或一个任务。在这个例子中,我们定义一个节点来处理文本生成任务: ```python def generate_text(node_input, tokenizer, model): input_ids = tokenizer.encode(node_input, return_tensors="pt") outputs = model.generate(input_ids, max_length=50) return tokenizer.decode(outputs[0], skip_special_tokens=True) ```
4. "创建LangGraph图"

相关内容:

本文目标

1.了解claude-code的核心功能实现

目前github有两个比较好的逆向claude-code实现的仓库,但实际阅读起来还是有一些成本。比如内容实在太多、重复较多等等。其实期望能有一个roadmap,先了解最核心功能的最小实现链路,再去了解枝节问题。

所以下文每个功能会先介绍功能描述与实现流程,然后贴上最核心的几个函数实现。


2.了解并实践langgraph,构建简版claude-code

langgraph是当下最流行的agent应用框架(之一),提供了丰富的llm应用相关能力,同时也有很多新的概念,可以实践一下,我觉得最核心需要关心的是 状态机State、图节点workflow、工具Tool 三个方面。

所以下文在每个功能实现上会围绕这三个方面进行设计改动。

期望通过本文的实践可以对claude-code的核心设计以及langgraph框架有一个初步的了解。如何一步步从最初的简单ReActAgent升级到一个简版的claude-code agent。

开工前的背景介绍

1.claude-code的逆向工程

claude-code是anthropic公司出的一款cli工具,最近相当火,实际体验下来也很不错,个人认为是目前Code Agent领域的最佳实践。那么好学的程序员肯定会很好奇背后的实现原理。

1.https://github.com/shareAI-lab/analysis_claude_code

2.https://github.com/Yuyz0112/claude-code-reverse

以上两个仓库对claude-code进行了全面的逆向分析,把相关逆向的提示词也开源了,我们完全可以通过这份repo来了解claude-code的核心实现。

当然,这俩仓库的提示词信息量巨大,在llm逆向的过程中也会出现幻觉(确实会有一些前后矛盾的地方),整体还是不能无脑阅读,下面会结合这些分析以及个人的理解捋一遍核心实现。

2.langgraph介绍

langgraph是目前大模型应用开发最流行的框架之一,langchain衍生出的框架,专注于智能体应用的开发。

详细的介绍可以参见langgraph官网:

https://langchain-ai.github.io/langgraphjs/

这里根据我的理解简要概括一下主要功能:

其中langgraph官方最引以为豪的两个特性:

  • 实现偏底层,上层无过多抽象,用户可精准控制大模型的输入,这对llm上下文优化至关重要。
  • 规则驱动和llm驱动相融合,可以在大模型的自主性和不确定性中间寻找平衡,让我们的系统具备较高的可靠性、可观测性。
对于不了解langgraph的朋友可能感知比较虚,没关系,在接下来的案例中,我们也可以深刻感受到这两点。

3.claude-code整体架构视图

claude-code整体有非常丰富的功能,按照逆向的分析我们整体分为 用户交互层、Agent调度层、工具执行与管理层(含工具生态系统)、存储与记忆层,我们将其实现与langgraph的功能点做一个对比,研究一下还需要建设哪些能力。

langgraph版ClaudeCode实现

1.梦开始的地方:最基础的ReActAgent

先从一个最简单的ReactAgent,来了解langgraph最基本的graph相关的操作。

const agentState = Annotation.Root({
  messages: Annotation<BaseMessage>({
    reducer: safeMessagesStateReducer,
    default: () => ,
  })
});


const toolNodeForGraph = new ToolNode(tools)


const shouldContinue = (state: typeof agentState.State) => {
  const { messages } = state;
  const lastMessage = messages;
  if ("tool_calls" in lastMessage && Array.isArray(lastMessage.tool_calls) && lastMessage.tool_calls?.length) {
      return"tools";
  }
  return END;
}


const callModel = async (state: typeof agentState.State) => {
  const { messages } = state;
  const response = await modelWithTools.invoke(messages);
  return { messages: response };
}




const workflow = new StateGraph(agentState)
  .addNode("llm", callModel)
  .addNode("tools", toolNodeForGraph)
  .addEdge(START, "llm")
  .addConditionalEdges("llm", shouldContinue, )
  .addEdge("tools", "llm");


const agent = workflow.compile()
这个简短的实现把langgraph十分核心的node、edge、state展示出来,节点运行图如下,这也是最最基本的一个能够调用tool的agent。

同时,通过streamEvent即可获取agent的流式输出事件,根据这些输出事件选取对应的格式输出给前端进行展示。

const config = {
  configurable: { thread_id: this.sessionId },
  streamMode: ,
  version: 'v2' as const,
}
const stream = agent.streamEvents({messages: }, config);

当然实际上,langgraph官方也提供了预构建的reactAgent,后续我们直接使用即可。我们将从这个图出发,一步步实现langClaudeCode。

2.人在环路:中断审查以及ckpt持久化

在使用claude-code的时候,如果需要查看or编辑某个文件,会询问用户权限,这其实是一种人工协同的方式。为了实现类似的功能,我们需要在原有的workflow基础上添加一个人工审查节点:

// 添加人工审查节点的workflow
const workflowWithReview = new StateGraph(MessagesAnnotation)
  .addNode("agent", callModel)
  .addNode("tools", toolNodeForGraph)
  .addNode("human_review", humanReviewNode)  // 新增:人工审查节点
  .addEdge(START, "agent")
  .addConditionalEdges("agent", shouldContinueWithReview, )  // 修改:路由到审查节点
  .addConditionalEdges("human_review", checkUserApproval, )  // 新增:审查结果路由
  .addEdge("tools", "agent");

2.1 人工审查节点实现

添加的human_review节点主要负责以下功能:

1.中断执行流程:在工具调用前暂停,等待用户决策;

2.提供操作选项:给用户提供批准、拒绝、修改等选项;

3.中断恢复:根据用户的回复决定是否下一个节点。

// 人工审查节点的具体实现
const humanReviewNode = async (state: typeof MessagesAnnotation.State) => {
  const lastMessage = state.messages;
  const humanAnswer = interrupt("确认是否执行");
  if(humanAnswer === "同意"){
    returnnew Command({
      goto: "tool",
      update: {
        messages: 
      }
    })
  }
  
  if(humanAnswer === "拒绝"){
    returnnew Command({
      goto: "agent",
      update: {
        messages: 
      }
    })
  }
};
这部分代码有两个重要知识点,两个重要的api,interrupt和Command

1.其中最重要的是 interrupt的设计,其实就是内部throw了一个error,强制图的运行中断(当前会话的检查点已经保存在图中),当调用resume恢复中断时,结合检查点的恢复,获取之前的运行状态进行恢复,详细介绍可以参见这里:

https://langchain-ai.github.io/langgraphjs/concepts/human_in_the_loop/#interrupt

2.Command是langgraph提供的一个可以动态选择的节点的api,我们可以在图的任意节点,通过Command实现任意节点的调整和状态机的更新。主要和图graph中的edge做对比,edge是代码中预置写死的边,而通过Command,我们可以在代码中通过根据不同情况灵活选择节点。比如这里通过用户输入进入不同的节点、也可以在某些场景下,根据大模型的输入进入不同的节点。

2.2 由大模型驱动决策是否需要人机协同

上面的人机协同节点是硬编码在图中,实际大模型应用开发的时候,人工协同的场景非常多(比如llm需求询问用户需求细节、需要用户确认规划等等),其实我们可以利用工具的特性(指模型根据提示词来驱动),让大模型自主决策是否需要人工协同。我们添加一个人工协同工具。

function createHumanLoopTool(){
    const executor = (arg, config) => {
      const msgs: BaseMessage = ;
      const state = getCurrentTaskInput();
      returnnew Command({
        goto: 'askHuman',
        graph: Command.PARENT,
        update: {
          messages: state.messages.concat(msgs),
        },
      });
    };
    return tool(executor, {
      name: 'ashHuman',
      description: '当需要向用户确认需求、询问补充信息、向用户提问时, 务必调用此工具',
      schema,
    });
  }
我们把这个工具给到大模型,修改工具的提示词,在大模型认为需要人工协同时就会调用此工具。

最核心的实现是“伪造”了一个工具给到大模型,让大模型根据工具的提示词来根据环境进行选择,工具内部的执行逻辑是跳转到人工协同节点(这个节点中会中断状态,寻求用户)。

同时,我们需要维持上下文消息的完整性,需要手动补充一个ToolMessage结果给到message中。

2.3 检查点持久化支持

到目前为之,我们已经成功实现了cc的人工协同功能(当然cc中人机协同的核心目的是工具权限校验),并且更强大。但cc是单机的,实际生产环境都是多个机器,在一次中断后,下一次请求可能会命中别的机器,检查点也就不存在,所以需要支持检查点持久化,这一点langgraph也提供了很成熟的解决方案。

import { MemorySaver } from "@langchain/langgraph";


const app = workflow.compile({
  checkpointer: new MemorySaver(),  // 状态持久化
});


// 使用示例
const config = { configurable: { thread_id: "review-session-1" } };
await app.invoke(initialState, config);


// 在用户审查后继续执行
await app.invoke(null, config);  // 从中断点继续
以上示例的MemorySaver是将检查点存储到机器内存中,而如果要持久化落库langgraph官方则提供了MongoDB等几种数据库持久化的方案。

我们生产环境大概率会用redis,可以参考这个实现 langgraph-redis github仓库 这个是python版本的,其实核心就是实现put、putWrites、getTuple几个方法,即检查点ckpt的存储和读取。这里就不展开了~

3.Agent分身与扩展:SubAgent的实现

3.1cc实现原理简析

claude code 的多agent架构如图所示,整个流程如下:

研究SubAgent的实现机制,我们从下面几个方面逐一研究,SubAgent创建流程、执行上下文分析、并发执行协调分析。

核心技术特点:

1.通过TaskTool根据任务复杂度创建SubAgent

2.完全隔离的执行环境:每个SubAgent在独立上下文中运行

3.智能并发调度:支持多Agent并发执行,动态负载均衡

4.安全权限控制:细粒度的工具权限管理和资源限制

5.结果合成:智能的多Agent结果聚合和冲突解决

3.2cc核心函数实现

以下是分析逆向仓库后,我认为最核心的一些函数,可以通过这些函数对流程有一个比较清晰的了解。

(1)Task工具作为多Agent架构入口点

Task工具 - 多Agent架构入口点

// Task工具对象结构 (p_2)
p_2 = {
    name: "Task",
    async call({ prompt }, context, globalConfig, parentMessage) {
        // 核心:通过Task工具内部创建和管理SubAgent
        if (config.parallelTasksCount > 1) {
            // 多Agent并发执行模式
            const agentTasks = Array(config.parallelTasksCount)
                .fill(`${prompt}

Provide a thorough and complete analysis.`)
                .map((taskPrompt, index) => I2A(taskPrompt, index, executionContext, parentMessage, globalConfig));
        } else {
            // 单Agent执行模式
        }
    }
}

核心设计特点:

1.Task工具是SubAgent创建的唯一入口

2.支持单Agent和多Agent并发两种模式

3.通过配置parallelTasksCount控制并发度

4.每个SubAgent都是完全独立的执行实例

(2)SubAgent创建机制 (I2A函数)

I2A函数

// SubAgent启动函数
async function* I2A(taskPrompt, agentIndex, parentContext, globalConfig, options = {}){
    // 1. 生成唯一Agent ID
    const agentId = VN5();
    
    // 2. 创建隔离的执行上下文
    const executionContext = {
        abortController: parentContext.abortController,
        options: { ...parentContext.options },
        getToolPermissionContext: parentContext.getToolPermissionContext,
        readFileState: parentContext.readFileState,
        setInProgressToolUseIDs: parentContext.setInProgressToolUseIDs,
        tools: parentContext.tools.filter(tool => tool.name !== "Task") // 防止递归
    };
    
    // 3. 执行主Agent循环
    forawait(let agentResponse of nO(/* 主Agent循环 */)){
        // 处理Agent响应和工具调用
    }
    
    // 4. 返回最终结果
    yield { type: "result", data: { /* 结果数据 */ } };
}
核心特性:

1.完全隔离的执行环境

2.继承但过滤父级工具集(排除Task工具防止递归)

3.独立的资源限制和权限控制

4.支持进度事件流式输出

(3)并发执行协调器 (UH1函数)

UH1函数

// 并发执行调度器
async function* UH1(generators, maxConcurrency = Infinity){
    const wrapGenerator = (generator) => {
        const promise = generator.next().then(({ done, value }) => ({
            done, value, generator, promise
        }));
        return promise;
    };
    
    const remainingGenerators = ;
    const activePromises = new Set();
    
    // 启动初始并发任务
    while (activePromises.size < maxConcurrency && remainingGenerators.length > 0) {
        const generator = remainingGenerators.shift();
        activePromises.add(wrapGenerator(generator));
    }
    
    // 并发执行循环
    while (activePromises.size > 0) {
        const { done, value, generator, promise } = await Promise.race(activePromises);
        activePromises.delete(promise);
        
        if (!done) {
            activePromises.add(wrapGenerator(generator));
            if (value !== undefined) yield value;
        } elseif (remainingGenerators.length > 0) {
            const nextGenerator = remainingGenerators.shift();
            activePromises.add(wrapGenerator(nextGenerator));
        }
    }
}
核心能力:

1.智能并发调度,支持限制最大并发数

2.Promise.race实现高效并发协调

3.动态任务调度,完成一个启动下一个

4.实时结果流式输出

(4)结果合成机制 (KN5函数)

KN5函数

// 多Agent结果合成器
function KN5(originalTask, agentResults){
    const sortedResults = agentResults.sort((a, b) => a.agentIndex - b.agentIndex);
    
    const agentResponses = sortedResults.map((result, index) => {
        const textContent = result.content
            .filter(content => content.type === "text")
            .map(content => content.text)
            .join("

");
        
        return `== AGENT ${index + 1} RESPONSE ==
${textContent}`;
    }).join("

");
    
    return `Original task: ${originalTask}


I've assigned multiple agents to tackle this task. Each agent has analyzed the problem and provided their findings.


${agentResponses}


Based on all the information provided by these agents, synthesize a comprehensive and cohesive response...`;
}
合成策略:

1.按Agent索引排序保证一致性

2.提取每个Agent的文本内容

3.生成专门的合成提示词

4.创建独立的Synthesis Agent处理结果聚合

3.3langgraph实现思路简析

通过分析上述TaskTool工具作为创建入口、并发执行、结果合成等流程,在langgraph上其实有很多种实现思路,但我们整体思路是llm驱动、收敛到Tool中实现。那么对状态机无需改造,读图节点也无需改造(需要将TaskTool供给到图,以及针对TaskTool等内置工具无需作人工审查,这部分比较简单,就不展开了。)

所以我们重点放在TaskTool的实现,看看如何基于lg的能力快速实现。

3.4Task工具设计

3.4.1TaskTool的提示词和描述

function createTaskTool(baseTools: any, model: any, subAgentConfigs: SubAgentConfig){
  return tool(
    async (args: { description: string; subagent_type: string }, config) => {
      // 具体工具内部实现,下文重点介绍
    },
    {
      name: "TaskTool",
      description: `Launch specialized SubAgents to autonomously handle complex multi-step tasks.


Available Agent types:
- general-purpose: General agent suitable forcomplex queries, file searches, and multi-step task execution (Tools: *)
- code-analyzer: Code analysis expert for code review, architecture analysis, performance optimization
- document-writer: Documentation expert for technical docs, user manuals, API documentation


Usage rules:
1. Use this tool when tasks are complexand require specialized handling
2. Each SubAgent invocation is independent and stateless  
3. Provide detailed task descriptions; SubAgents will complete autonomously
4. After SubAgent completion, summarize key information for the user


When to use SubAgents:
- Complex multi-step analysis tasks
- Tasks requiring specialized skills (code analysis, documentation)
- Large-scale file search and processing
- Independent tasks that can be processed in parallel`,
      schema: z.object({
        description: z.string().describe("Detailed task description for the SubAgent"),
        subagent_type: z.enum().describe("SubAgent type to use")
      })
    }
  );
}
3.4.2核心创建流程与上下文隔离

以下代码展示了工具的核心实现,核心需要关注不同子Agent的上下文隔离,主要体现在新增agent实例、独立的message队列、以及结果合成至主Agent

function createTaskTool(baseTools: any, model: any, subAgentConfigs: SubAgentConfig){
  // 为每种类型创建专门的ReActAgent实例
  const agentInstances = new Map<string, any>();
  
  // 初始化预定义的SubAgent类型
  subAgentConfigs.forEach(config => {
    const filteredTools = config.allowedTools 
      ? baseTools.filter(tool => config.allowedTools.includes(tool.name))
      : baseTools.filter(tool => tool.name !== "TaskTool"); // 防止递归
      
    agentInstances.set(config.type, createReactAgent({
      llm: model,
      tools: filteredTools,
      systemMessage: config.systemPrompt
    }));
  });
  
  return tool(
    async (args: { description: string; subagent_type: string }, config) => {
      const { description, subagent_type } = args;
      
      // 获取指定类型的Agent
      const agent = agentInstances.get(subagent_type);
      if (!agent) {
        thrownew Error(`Unknown SubAgent type: ${subagent_type}`);
      }
      
      // 执行SubAgent
      try {
        console.log(` Launching SubAgent : ${description}`);
        
        // 创建隔离的执行上下文
        const subAgentState = {
          messages: 
        };
        
        // 执行Agent并获取结果
        const result = await agent.invoke(subAgentState);
        
        // 提取最终响应
        const finalMessage = result.messages;
        const responseContent = finalMessage.content;
        
        console.log(`✅ SubAgent  completed`);
        
        // 返回格式化结果 - 模拟claude-code的结果格式
        return `SubAgent  execution completed:


Task: ${description}


Result:
${responseContent}


Note: This result was generated by a specialized SubAgent. Please summarize key information for the user as needed.`;
        
      } catch (error) {
        console.error(`❌ SubAgent  failed:`, error);
        return `SubAgent  execution failed: ${error.message}`;
      }
    },
    {
      name: "TaskTool",
      description: `提示词 略过`,
      schema: z.object({
        description: z.string().describe("Detailed task description for the SubAgent"),
        subagentType: z.enum().describe("SubAgent type to use")
      })
    }
  );
}
这里也仿照claude-code给出subAgent的配置示例:

// SubAgent配置定义
const subAgentConfigs: SubAgentConfig = 
  },
  {
    type: "document-writer",
    systemPrompt: `You are a technical writing expert focused on:
- Clear and accurate technical documentation
- User-friendly operation guides  
- Complete API documentation and examples
- Structured project documentation
Ensure readability and practicality of documentation`,
    allowedTools: 
  }
];

关于作者: 网站小编

码农网专注IT技术教程资源分享平台,学习资源下载网站,58码农网包含计算机技术、网站程序源码下载、编程技术论坛、互联网资源下载等产品服务,提供原创、优质、完整内容的专业码农交流分享平台。

热门文章