最近在研究本地部署智能体执行框架正好把一个叫 OpenClaw 的开源项目社区习惯叫“龙虾”从拉代码到接上 Claude Sonnet 4.6 模型完整跑通了一遍顺手把里边的技术实现原理也拆了一遍。这篇文章不聊空话就讲讲这个框架要实现什么、模型调用链路长什么样、参数怎么配、踩过哪些坑以及怎么把它改造成一个能长期稳定跑的服务。这个项目解决的核心问题其实很朴素你有很多想法想自动化执行但每次都要自己写脚本、调接口、处理返回结果很烦。龙虾做的事是把“任务拆解、工具调度、模型对话、结果回填”这一整条链路串起来你只需要把目标告诉它它会自己调用注册好的工具去完成。而 Claude Sonnet 4.6 在这个链路里扮演的是“决策大脑”的角色负责理解意图、生成步骤、输出结构化调用指令。适合谁来参考这篇文章如果你正在找一套能本地跑的 AI Agent 框架或者想搞清楚大模型函数调用到底怎么和业务代码联动再或者你手头已经有一个类似项目但总在工具调用解析上报错这篇都值得你看完。1. 整体设计思路与需求拆解1.1 龙虾到底解决了什么问题先还原一下场景。假设你要让 AI 自动帮你统计一个目录里的文件大小、找出最大的几个文件并生成一份 Markdown 报告。最原始的做法是你自己写一段 Python 脚本用 os.walk 扫目录、排序、写文件每一步都写死。但需求一变比如改成“统计最近三天修改过的文件”你又得改脚本。龙虾这类框架的思路是把“业务逻辑”和“模型决策”分开业务逻辑以工具函数的方式注册进去模型负责根据用户输入的描述动态决定调用哪个工具、传什么参数。这个设计的价值在于同样是这组工具你让模型完成“统计文件大小”它能做你让它“找出最近的日志文件并打印尾部内容”它也能做。工具是固定的组合方式是模型在运行时生成的这就是 Agent 框架和普通脚本之间最本质的区别。Claude Sonnet 4.6 在这里承担的就是组合决策这件事它的工具调用稳定性直接决定了整个框架好用不好用。再往深一层说Agent 框架把“能用代码写死的逻辑”和“需要模型现场发挥的逻辑”做了分离。前者是工具函数后者是规划决策。这个分离让系统的可维护性提高了不少你要增加一个新能力不需要改模型提示词只需要注册一个新工具你要调整执行策略不需要改工具函数只需要调模型的系统提示和采样参数。1.2 为什么选 Claude Sonnet 4.6 作为核心模型在选择模型的时候我对比过几个方向。首先是工具调用Function Calling的稳定性简单说就是模型能不能稳定输出结构正确的 JSON。Sonnet 4.6 在工具调用场景里表现比较靠前尤其是面对多工具候选时很少出现参数名拼错、类型对不上这类低级问题。其次是代码解释能力和指令遵循能力当用户描述不够精确时模型能先提出澄清问题而不是盲目开跑。第三是响应速度本地 Agent 场景里每轮调用都有多轮工具执行单次推理如果慢会导致整体体验很拖沓。当然这并不是说其他模型不能跑只是在这个“以工具调用为主”的场景里Sonnet 4.6 的性价比和稳定性比较均衡。框架本身做成了模型可插拔的结构你后续想换成别的模型只需要改配置里的模型标识和对应的消息协议适配器就可以这部分的抽象在龙虾里做得还是相当克制的没有为了支持所有模型而把核心逻辑搞成一团乱麻。1.3 技术路线选型背后的取舍我在搭这套东西之前也考虑过两种完全不同的路线。一种是从零开始自己写工具调度和消息循环另一种是直接用龙虾这样的现成框架。自己写的好处是代码完全可控坏处是坑非常多——上下文裁剪怎么做、工具结果怎么回填、并发执行时怎么跟踪状态这些看起来是小问题实际写起来全是细节。用现成框架的好处是这些基础设施已经有人趟过坑了你只需要关注业务工具本身。我自己选择用龙虾还有一个原因是它对工具注册的抽象比较轻量。有些框架会把工具封装成一套复杂的类继承体系写一个工具要理解一堆父类方法学习成本很高。龙虾这边是用装饰器加函数定义写起来和普通函数差不多心智负担很小。对于想把主要精力放在业务逻辑上的人来说这才是更舒服的姿势。2. 运行时链路与核心模块拆解2.1 一次完整任务请求是怎么流动的我建议你在接触任何 Agent 框架时先画出一条调用链路出来后面所有配置和排查都围绕这条链路展开。龙虾的核心链路大致是这样用户输入一条自然语言指令。调度器把指令和当前可用的工具清单组装进消息上下文。发送给 Claude Sonnet 4.6模型返回两种可能之一直接回答或者返回一批工具调用指令。如果是工具调用指令执行器去注册表里找到对应函数校验参数后执行。执行结果以工具消息回填进上下文再次发给模型。模型根据执行结果决定继续调用还是给出最终答复。最终答复通过输出层返回给用户。这里最需要注意的是第 4 步和第 5 步的回环设计。龙虾没有把工具执行结果只是简单拼到对话里而是把它单独放在工具角色消息中每条消息都带工具调用 ID。这样模型能够精确对应“哪次调用失败了”“哪个参数传错了”在多工具并行执行的场景下尤其重要。我自己在第一版实现时偷懒把所有工具结果塞进一条用户消息里发给模型结果模型经常搞不清楚返回值属于哪个调用后来改成保持角色结构就稳定多了。2.2 消息角色与上下文管理机制聊到消息结构就顺便说说龙虾是怎么管理上下文的。和直接调用大模型 API 不同Agent 框架要维护的不只是用户和助手之间的对话还有大量工具产生的中间结果。如果每一轮工具执行结果都累积在上下文里不用多少轮就会撑爆窗口。龙虾默认的策略是滑动窗口保留最近的 N 轮关键消息早期消息会被裁掉或者压缩成摘要。我建议你在生产中把 N 设在 20 到 30 之间太小会让模型丢失任务开头的重要要求太大则浪费 token。另外要特别强调系统提示词system prompt的组合方式。龙虾会把三部分拼进系统提示项目自身的执行规则、用户自定义的偏好、以及当前时刻可用的工具清单。工具清单不是静态写死的而是每轮请求时动态序列化。为什么要这样设计因为一旦工具数量超过几十个全量塞进去会显著挤占上下文空间模型也容易在选工具时走神。动态裁剪后模型每次看到的是一份精简且和任务强相关的工具列表工具选择的准确率会明显上升。2.3 工具注册表与参数校验工具层是龙虾项目改动成本最高的地方也是最应该提前设计好的地方。每个工具函数在注册时至少要声明三样东西函数名、描述、输入参数的 JSON Schema。函数名要短且语义唯一描述要写清楚“什么场景下用这个函数”“不要什么时候用它”JSON Schema 要严格声明类型、必填项和各字段含义。我踩过一个比较典型的坑给一个“查询订单”的工具写描述时只写了“查询订单状态”结果模型经常把“查询用户列表”这种需求也路由到这个工具上。改成“仅在用户提供订单编号时使用用于查询订单当前处理状态若用户想要查询用户资料请使用 lookup_user”之后路由准确率立刻上来了。这里的关键原因是模型做工具选择时依赖的是文本语义匹配描述里的边界越清晰匹配越准。参数校验建议放在执行器层统一做不要让业务函数自己承接防御逻辑。龙虾的做法是在执行前先按注册时声明的 Schema 做一次 JSON 校验不通过就直接生成一条“参数校验失败”的工具返回消息回填给模型让它重新调用。这个设计让业务函数保持干净也让模型能拿到结构化错误信息去自我纠正而不是面对一段堆栈日志发呆。3. 实操要点与具体配置3.1 环境准备与依赖安装我自己是在一台 Linux 服务器上跑的系统是常见发行版Python 版本用了 3.11Node 环境也装了一份因为龙虾的某个辅助模块依赖 Node。建议你至少准备 Python 3.10 以上环境太老的版本在新语法和类型注解上会有兼容问题。拉取项目之后先看仓库里的依赖清单文件。安装依赖时用虚拟环境不要直接怼到系统 Python 里git clone 项目仓库地址 openclaw cd openclaw python3 -m venv .venv source .venv/bin/activate pip install -r requirements.txt如果你遇到依赖装到一半报编译错误多半是某些原生依赖缺少系统库比如编译 JSON 解析相关的扩展时需要 libffi-dev这类问题在常见发行版里用包管理器装一下即可。我不建议在生产环境里省略虚拟环境这步因为后面配置模型 SDK、异步框架时依赖冲突的概率并不低。另外如果你用的是 Windows建议优先考虑 WSL 环境有些异步文件监控模块在原生 Windows 下的表现确实不如在 Linux 下稳定。3.2 模型接入配置与密钥管理模型接入的核心配置文件通常是一个 YAML 或 JSON 文件。我这里用 YAML 示例主要字段如下model: provider: claude name: claude-sonnet-4-6 base_url: ${CLAUDE_API_URL} api_key_env: CLAUDE_API_KEY temperature: 0.2 max_tokens: 4096 timeout_seconds: 120 scheduler: strategy: sliding_window keep_recent_messages: 25 toolbox: auto_discover: true strict_schema: true这里有几个容易忽略的点。api_key_env指向一个环境变量名而不是直接把密钥写在文件里就是为了避免配置文件被提交后泄露密钥。base_url不要写死用${CLAUDE_API_URL}这种环境变量占位符这样以后切换不同的接入端点时只改环境变量即可。temperature我建议设置在 0 到 0.3 之间工具调用场景需要的是确定性而不是创意发挥设高了以后模型可能在生成 JSON 参数时偶尔冒出多余的注释文字导致解析失败。使用前把环境变量准备好可以放在.env文件里龙虾启动时会读取export CLAUDE_API_KEYsk-xxxxx export CLAUDE_API_URLhttps://api.example.com/v1/messages注意.env文件不要提交到版本管理里建议把它写进.gitignore。密钥如果已经泄露了赶紧去控制台吊销重建别抱着侥幸心理。我还会在配置里额外加一个request_retry字段默认值是 3 次重试这个在模型服务偶尔抖动时相当管用。3.3 写一个最小可跑通的调用示例在跑通整个框架之前我强烈建议先用最裸的 HTTP 请求验证一把模型接口通不通。这样能帮你区分“模型接口的问题”和“框架自身的问题”。下面是我用的最小验证脚本import os import requests from dotenv import load_dotenv load_dotenv() API_URL os.getenv(CLAUDE_API_URL) API_KEY os.getenv(CLAUDE_API_KEY) MODEL_NAME os.getenv(CLAUDE_MODEL_NAME, claude-sonnet-4-6) payload { model: MODEL_NAME, max_tokens: 1024, messages: [ {role: user, content: 请把下面这句话压缩成十个字以内今天天气真的非常好适合出门散步。} ], } resp requests.post( API_URL, headers{ Authorization: fBearer {API_KEY}, Content-Type: application/json, }, jsonpayload, timeout60, ) if resp.status_code 200: data resp.json() print(data[choices][0][message][content]) else: print(Request failed:, resp.status_code, resp.text)跑通这一步之后你至少可以确认三件事密钥有效、API 端点可访问、模型名称写得对。很多人在这一步就发现 401 或 404省下了后面折腾框架的时间。我再多一句嘴max_tokens不要给太小尤其当后续接上工具调用后模型可能需要生成一大段 JSON 结构1024 只是验证用途正式跑任务我至少给到 4096。3.4 对接龙虾执行器并注册一个业务工具接下来把验证好的模型调用接进龙虾。第一步是建一个工具模块比如tools/file_tools.py在里面注册一个“统计目录大小”的工具from openclaw import tool from pathlib import Path tool( namesum_dir_size, description统计指定目录下所有文件的总大小单位字节。仅当用户需要了解目录占用空间时使用。, input_schema{ type: object, properties: { path: {type: string, description: 要统计的目录绝对路径} }, required: [path] } ) def sum_dir_size(path: str) - dict: total 0 for p in Path(path).rglob(*): if p.is_file(): total p.stat().st_size return {total_bytes: total}注册完之后在龙虾的启动配置里通过toolbox.auto_discover或手动 import 的方式加载这个模块然后启动服务给模型发一条“帮我看看 /data 目录总共占了多少空间”。观察日志你会看到大致的执行过程模型先返回一个sum_dir_size的工具调用执行器定位函数并传参/data函数执行返回字节数模型基于返回值生成最终回复。这个流程跑通后你等于打通了“自然语言指令到业务代码”的整条链路。剩下的所有工作都可以基于这套模式扩展多注册几个工具写更复杂的业务流程编排。4. 安装配置后的功能延伸与参数调优4.1 工具描述与提示词模板的优化顺序很多人在框架跑通之后第一反应是“多塞点工具进去”但实际体感是工具一多模型选择就开始飘了。我自己的优化顺序是先改工具描述再精简动态工具列表最后才是调 temperature 之类的采样参数。工具描述里的关键词要和用户请求里的常见说法对齐比如用户习惯说“看一下这个文件夹多大”那描述里最好也出现“文件夹占用”“目录大小”这类近似表达。提示词模板方面龙虾默认模板已经比较可用但建议根据你的业务场景微调系统提示里“工作原则”这一段。比如我希望模型在执行多步任务时每步都以“我先调用 XX 工具确认 XX”开头就可以在模板里加一句每执行一步工具调用前用一句话说明打算调用的工具和目的。这能让最终报告更容易读懂也能让模型在步骤间保持逻辑连贯性。4.2 温度、重试与并发的平衡把配置从开发环境搬到生产之前建议先做一轮压测不然你可能毫无心理准备地撞上速率限制。模型服务方通常会对每分钟请求数或 token 数做限制一旦超限会返回 429。龙虾内置了指数退避重试机制默认从 1 秒开始每次翻倍最多重试 5 次。我在实际跑任务时把单任务并发控制在 3 到 5 个请求以内既能把吞吐提上去又不会频繁触发限流。temperature 的取值有点讲究。纯对话场景你或许会调到 0.7 以上让人物更有“灵魂”但工具调用场景我建议固定为 0。初学者容易忽略的是即便 temperature 为 0模型输出也不是完全确定性的因为推理过程本身有随机性。所以不要因为一次输出差异就怀疑是配置问题多看几次结果再下结论。还有一个容易被忽视的参数是max_tokens。工具调用返回的 JSON 不长但有些模型在生成过程中会先输出思考过程再输出工具调用思考过程也会占用 token 额度。如果 max_tokens 卡得太死模型可能在工具调用生成到一半就被截断导致 JSON 不完整。我一般给到 4096 或更高宁可多留余量也不要因为这种低级截断问题重启任务。4.3 将龙虾接入外部应用的方式龙虾跑起来以后你可以用几种方式对外提供能力。最简单的是直接使用命令行交互模式稍微正式一点的是把龙虾的核心执行器封装成 Python 函数在自己的 Web 服务里调用再进一步可以直接启用内置的 API 服务模式监听本地端口让其他服务通过 HTTP JSON 请求来提交任务。我倾向于用第三种方式因为解耦性好。外部系统不用关心龙虾内部怎么调度工具它们只需要 POST 一个{ task: ... }异步等待回调结果就行。需要注意如果对外暴露服务一定要做鉴权至少加一个简单的 token 头校验否则本地端口一旦被局域网内其他设备扫描到就可能被拿来消耗你的模型额度。我在这个服务前面还加了一个简单的请求日志中间件记录每次请求的来源 IP、任务描述和耗时方便后续排查问题。5. 常见问题与排查实录5.1 鉴权失败与 401 问题出现 401 时先检查环境变量是否真的传进了进程。很多时候是.env文件没被加载或者你在 shell 里 export 之后又改了文件但没重新加载。第二常见的原因是密钥字符串带了不可见字符复制粘贴时尤其容易出问题。我的习惯是在终端里用echo ${#CLAUDE_API_KEY}打印密钥长度跟控制台显示的位数对比一下差太多就说明复制出错。还有一种情况是配置里 base_url 指向的接入点与密钥不匹配这个属于配置错误仔细核对一下就好。5.2 模型返回内容解析失败如果你发现工具调用链路偶尔会断多半是模型生成的参数 JSON 不符合注册 Schema。排查思路是打开龙虾的 debug 日志找到模型返回的原始内容直接肉眼扫一遍。常见现象包括参数名被模型改写成了相近词、字符串类型被传成了数字、多了一个无关字段。对应解法是压缩工具的描述长度、降低 temperature、把 Schema 里每个字段的说明写得更具体。千万别一上来就改代码去“兼容”模型的错误输出那样只会越补越乱。如果模型频繁返回“这个任务我没有合适的工具”不要急着怪模型先看看当前工具清单里有没有和用户意图匹配的描述。工具描述写得太技术化模型理解不了写得太模糊模型又容易选错。比较好的状态是描述包含三层信息触发条件、参数含义、不适用场景。三层都说清楚模型的选择准确率会好很多。5.3 上下文超限或任务中途“失忆”任务执行到一半模型突然忘了最初的目标这种问题我遇到过好几次。最直接的原因是滑动窗口把早期关键信息裁掉了。解决办法有两个在系统提示里维护一份“任务不变式”也就是无论如何都不可违背的核心要求另一个是把用户原始任务单独作为一条固定消息保留在最新上下文中每次回填时都带上。龙虾没有默认开启后者我建议你根据自己的业务手动叠加一层。还有一种“失忆”情况不是上下文问题而是模型在执行大量工具调用后把用户原始意图和工具返回结果混在一起了。这时我会在最终汇总前让模型先复述一遍原始任务和已完成的步骤再生成结论。这个“复述”动作能显著减少模型答非所问的概率成本只是多一次调用但效果很值。5.4 速率限制与超时错误速查表错误现象可能原因处理动作401 Unauthorized密钥无效、环境变量未加载核对密钥、检查 env 加载顺序404 Not FoundAPI 路径错误或模型名缺失修正 base_url、核对模型标识429 Too Many Requests并发过高、触发速率限制降低并发、开启退避重试500 Server Error服务端临时异常保留请求上下文稍后重试工具结果回填后模型反复调用同一工具工具返回信息不足以让模型判断丰富返回值结构加入状态字段6. 生产化改造与经验总结我真的不建议只满足于本地跑通。把龙虾用于实际业务前至少还要做三件事日志链路追踪、任务状态持久化、关键操作人工确认。日志上为每次任务分配一个唯一 ID所有工具调用都带上这个 ID排查问题时能省几个小时。持久化上把任务状态和中间结果写入本地存储这样服务重启后还能恢复未完成任务。人工确认方面对于删除、清空、覆盖这类高影响工具一定要在执行前加一道确认门槛让模型先输出“即将执行 XX影响范围是 XX是否继续”等你确认后再真正执行。从模型策略上说我个人的体会是不要把所有希望都押在单次调用上。复杂的任务尽量由模型拆成多个小步骤每步都基于上一步的结果做判断而不是让它一步到位生成全部结果。这么做虽然会多一些调用次数但整体稳定性显著上升。最后分享一个小技巧如果你经常遇到工具调用字段填错试着在每次调用前给模型把关键参数用伪代码示例展示一遍而不是只给 Schema。模型对“示例”的遵循能力往往比对“抽象描述”的遵循能力强得多。这个办法在我自己的一系列工具接入中基本是立竿见影的。折腾 Agent 框架这件事最怕的是在错误的方向上不断优化先把链路和数据结构理清楚后面的一切都会顺很多。