基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
AIHT
内容
精选全部 AI 动态热点榜AI 日报收藏
分类
模型行业编程产品技术观点开源
更多
Agent 接入
返回首页开发者导航
AIHT
精选 / 资讯详情
8.0
热点
AI SCORE
技术实践2026-10-29 15:24

175 行 Go 实现轻量 Claude 客户端

dev.to · AI#Go#Claude#流式接口
Editor brief · 编辑速览

作者用 Go 标准库在单文件中实现 Claude 客户端,涵盖 HTTPS、JSON 和 SSE 流处理,并使用 scratch 容器运行。示例展示了按行读取流、筛选 data 字段及解析事件的实现方式。

文章思维导图
Knowledge map
拖拽缩放
AI deep read

AI 深度解读

用 Go 写一个能流式输出、调用工具、继续对话的 Claude 客户端,作者交出的实现只有一个文件、175 行代码,运行镜像以 scratch 为基础。文章真正有意思的地方,是把网络、JSON、时区、证书和运行时的成本都摆到了台面上。

作者测得冷启动不到 2 毫秒,整轮交互的客户端耗时不到 5 毫秒,但二进制仍有 5.8 MB。对想写 AI 命令行工具或独立服务的程序员,这个例子适合用来理解:最小客户端需要保留什么,又有哪些成本藏不掉。

流式输出不难,难的是把工具调用拼完整

SSE(Server-Sent Events)按行传输,包含 event:、data:、空行和偶尔出现的 ping。针对这套 Claude 流,作者只处理 data: 开头的行,再用 encoding/json 解码事件。

下面这段代码同时解决了实时打印文本、保存回复内容和拼接工具参数的问题:

sc := bufio.NewScanner(resp.Body)
sc.Buffer(make([]byte, 64*1024), 1<<20)
for sc.Scan() {
    data, ok := strings.CutPrefix(sc.Text(), "data: ")
    if !ok {
        continue // event:、ping 内容、空白分隔行
    }
    var ev event
    if err := json.Unmarshal([]byte(data), &ev); err != nil {
        return nil, "", err
    }
    switch ev.Type {
    case "content_block_start":
        content = append(content, *ev.ContentBlock)
        partial = append(partial, strings.Builder{})
    case "content_block_delta":
        switch ev.Delta.Type {
        case "text_delta":
            os.Stdout.WriteString(ev.Delta.Text)
            content[ev.Index].Text += ev.Delta.Text
        case "input_json_delta":
            partial[ev.Index].WriteString(ev.Delta.PartialJSON)
        }
    case "content_block_stop":
        if content[ev.Index].Type == "tool_use" {
            if s := partial[ev.Index].String(); s != "" {
                content[ev.Index].Input = json.RawMessage(s)
            }
        }
    case "message_delta":
        stop = ev.Delta.StopReason
    }
}

要盯住第二行:bufio.Scanner 默认的 token 上限是 64 KB,大工具参数可能让单条 data: 超过限制。这里用 64*1024 设置初始缓冲区,用 1<<20 把上限提高到约 1 MB。它仍有边界,只是边界更符合这个任务。

事件结构也做了合并:顶层保留 type、index,下面放 delta 或 content_block,不同事件没用到的字段留零值。这样少写类型,代价是业务代码需要知道每种事件会携带哪些字段。

文本增量可以立即打印,工具参数却要等 input_json_delta 拼完,才能作为完整 JSON 使用。这是两种不同的数据处理节奏。

工具执行完,为什么还要回传模型自己的回复

工具调用的完整性,取决于下一次请求能否还原上一轮对话。

第一次流以 stop_reason: "tool_use" 结束后,客户端不能只发工具结果。第二次请求需要包含原始用户消息、助手的完整回复,以及承载 tool_result 的新用户消息。助手回复里的 tool_use 块也必须带上拼好的输入。

作者让同一个 block 结构体承担接收和发送,通过 json:"text,omitzero" 等标签省略零值字段。文本块输出 type、text;工具调用输出 type、id、name、input;工具结果输出 type、tool_use_id、content。

Input 使用 json.RawMessage 也很贴合这个任务:参数原先就是 JSON 文本,没有必要先解码成 map,再编码回去。真正执行工具时,再用只有一个字段的匿名结构体读取时区。

于是,最小 agent 循环就出来了:发送请求、打印输出;遇到 tool_use,执行所有工具块,追加助手消息和工具结果消息,再发一次请求。代码短,是因为它只保留了这条必要路径。

scratch 镜像很空,HTTPS 和时区仍要付账

scratch 没有完整系统环境。HTTPS 验证要用 CA 证书,time.LoadLocation("Europe/London") 要找时区数据库,两者都不能凭空出现。

作者用下面的 Dockerfile 把运行所需文件带进去:

FROM golang:1 AS build
WORKDIR /src
COPY client.go ./
RUN go mod init client && CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o /client client.go

FROM scratch
COPY --from=build /client /app/client
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
COPY --from=build /usr/local/go/lib/time/zoneinfo.zip /usr/local/go/lib/time/zoneinfo.zip
ENTRYPOINT ["/app/client"]

要注意两个 COPY:证书和 zoneinfo.zip 都是运行依赖。后者来自 Go 自带的 $GOROOT/lib/time/zoneinfo.zip,LoadLocation 可以回退到它。

另一种做法是 import _ "time/tzdata",把时区库嵌进二进制,约增加 450 KB。作者选择复制 zip,因为排行榜会统计产物成本。无论放哪,依赖都要算账。

测试环境是 go1.27.2、linux/arm64,在 Apple M4 Max 上通过 Docker 运行。构建只用了 CGO_ENABLED=0、-trimpath、-ldflags="-s -w",没有额外调优。

作者报告二进制为 5.8 MB,主要成本来自 net/http、它依赖的 crypto/tls,以及 encoding/json。文中还称,后者从 1.27 起底层使用 v2 实现。作为对照,作者预告的 .NET Native AOT 客户端二进制为 5.4 MB,另需承担 2.4 MB 的 C 库。

速度方面,从进程启动到首个请求发出不到 2 毫秒;第一次回复结束到第二次请求到达略高于 1 毫秒;整轮客户端耗时不到 5 毫秒。峰值内存约 6 MB,作者将其归因于 Go 运行时的堆 arena,并提到不同轮次会相差几 MB。

我的判断

这篇最有用的,是把「能打印模型回复」和「能完成工具对话」之间的代码补齐了。对前端、Node 开发者而言,事件名称可能熟悉,但完整保存助手回复、拼接工具输入、按正确角色回传结果,才是容易漏掉的部分。

性能结论需要拆开看。作者怀疑那 1 毫秒左右的周转时间耗在读取时区 zip,于是改成直接读取 /usr/share/zoneinfo 下的 TZif 文件,结果相同。这支持「换时区读取方式没有改善」;至于剩余差距全部来自网络轮询器唤醒和 goroutine 切换,文中没有展示足够的剖析数据,仍是作者的解释。

「标准库 HTTPS 客户端很难低于 5 MB」也受系列规则约束:标准库有 HTTP 就必须用。它不能直接推导成所有 Go 网络程序的体积下限。作者提到还有两个实现更快,但没有在本文给出完整对照表。

另外,文中的 golang:1 没有固定到实测的 go1.27.2。照抄 Dockerfile,不能保证得到相同版本。不到 5 毫秒衡量的也是客户端开销,不能当成真实 Claude 请求的总响应时间;复现命令注明不需要 key,更不能据此判断公网模型调用速度。

能拿来做什么

  • 自己写前端配套的 Node 或 Go AI 客户端时,可以照这个状态流程检查工具调用:参数是否拼完整、助手消息是否保留、工具结果是否带上对应的 tool_use_id。
  • 做流式读取时,把单行大小限制作为明确配置。本文的 1 MB 是具体实现选择,接入更大工具参数时,还要检查超限和扫描失败如何向上返回。
  • 将 Go 工具打进 scratch 镜像前,检查 CA 证书和时区来源,并固定构建版本,避免本机运行正常、容器启动后才缺依赖。
  • 想复现作者测试,可以依次运行 git clone https://github.com/AndyCross/claude-hot-path、cd claude-hot-path、./bench/bench.sh go。需要 Docker 和 Python 3,不需要 key;比较结果时,把客户端耗时与模型等待时间分开记录。
Original source

本文由 AI 通读原文后重新编写,观点部分为站内判断,细节以原文为准,来源 dev.to · AI,原文版权归原作者所有。

阅读原文
上一篇
用四个检查点定位代理记忆丢失
下一篇
Mnemosyne 用二分排查定位错误记忆

资讯速览

dev.to · AI · 2026-10-29 15:24

AI 评分 8.0 / 10

相关标签

#Go#Claude#流式接口

继续阅读

  • 英伟达开放多语种转录与说话人归属模型
  • 用四个检查点定位代理记忆丢失
  • Mnemosyne 用二分排查定位错误记忆
  • Claude Code通宵运行前的配置检查
  • 编码代理返工复盘:先查任务单质量