随着各种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 的并发特性和标准库让这些功能实现起来很简洁。编译成单文件后,分发特别方便,适合作为开发工具或内部分发。