随着各种AI工具的出现,CLI工具也成为了AI应用的一种重要形式。你有没有想过,用 Go 写一个支持流式输出的 AI 命令行工具?

很多 AI 应用都支持 CLI 模式,比如 Claude Code、 CodeBuddy CLI 等,命令行工具有它的优势:启动快、资源占用少、适合开发调试。而且用 Go 写的话,编译出来就是一个单文件,分发特别方便。

这篇文章就来分享一下如何用 Go 实现一个支持流式输出的 AI 命令行工具。


交互式对话

交互式对话的核心是:持续接收用户输入,发送给 AI,显示回复,然后循环

最简单的实现就是一个无限循环:

func main() {
    scanner := bufio.NewScanner(os.Stdin)
    for {
        fmt.Print("You: ")
        if !scanner.Scan() {
            break
        }
        input := scanner.Text()
        response := callAI(input)
        fmt.Printf("AI: %s\n", response)
    }
}

流式输出

流式输出是 AI 应用的标配功能。用户不用等 AI 全部生成完,就能看到实时输出,体验好很多。

SSE 流式响应

OpenAI 等大模型 API 支持 SSE(Server-Sent Events)流式输出。Go 处理 SSE 很简单:

func streamResponse(prompt string) {
    resp, _ := http.Post("https://api.openai.com/v1/chat/completions", "application/json", body)
    defer resp.Body.Close()
    scanner := bufio.NewScanner(resp.Body)
    for scanner.Scan() {
        line := scanner.Text()
        if strings.HasPrefix(line, "data: [DONE]") {
            break
        }
        if strings.HasPrefix(line, "data: ") {
            data := strings.TrimPrefix(line, "data: ")
            var chunk StreamChunk
            json.Unmarshal([]byte(data), &chunk)
            fmt.Print(chunk.Choices[0].Delta.Content)
        }
    }
}

每个 SSE 事件包含一部分生成的内容,逐个打印出来就是流式输出。

流式输出在命令行中显示

但直接打印有个问题:如果 AI 输出很长,会占满屏幕。更好的方式是逐字显示,模拟打字效果:

func typeWriter(text string, delay time.Duration) {
    for _, char := range text {
        fmt.Printf("%c", char)
        time.Sleep(delay)
    }
    fmt.Println()
}

在流式输出中使用:

for scanner.Scan() {
    data := parseStreamLine(scanner.Text())
    typeWriter(data.Content, 10*time.Millisecond)
}

这样看起来更自然,像真人在打字。一个字一个字蹦出来的,而不是一下子把整段话都吐出来。AI 的流式输出模拟了这个过程,让用户感觉在和一个"人"对话,而不是和一个机器交互。

逐字显示减少等待焦虑,当然速度需要控制。太快了像机关枪,慢了像蜗牛,要既能保持流畅感,又不会让用户等太久。


写在最后

Go 的并发特性和标准库让这些功能实现起来很简洁。编译成单文件后,分发特别方便,适合作为开发工具或内部分发。