AI 编程时代,我愿意用 Go 做后端
AI 编程时代,我愿意用 Go 做后端 AI 编程工具越来越会写代码,但真正把一个 AI 应用跑到线上,难点从来不只是"能不能生成一个接口"。请求超时怎么处理,模型服务失败后怎么重试,流式响应如何关闭,服务占多少内存,镜像怎样发布,出了问题能不能快速回滚,这些才是后端每天要面对的事情。 我越来越愿意把 Go 放在 AI 应用的后端位置:让 AI 负责加快实现,让 Go 负责把边界、资源和部署约束固定下来。这里说的不是把模型训练和推理强行改写成 Go,而是模型外围的 API、网关、任务和编排服务。蓝色文字表示语言和工程特性,绿色文字表示我的实践判断,红色文字表示容易被忽略的风险,黄色底色表示可以直接采用的做法。 一、Go 不会让 AI 更聪明,但会让错误更快暴露 我用 AI 写后端时,最常见的情况不是代码完全不能运行,而是它给出了一条顺利路径,却把失败路径一笔带过。比如调用大模型时,AI 很容易生成下面这种代码: func AskModel(prompt string) string { resp, _ := http.Get("http://model:8080/v1/chat") body, _ := io.ReadAll(resp.Body) return string(body) } 这段代码的问题很多:忽略了网络错误,没有关闭响应体,没有设置超时,也没有检查状态码。它可能在本地演示中返回结果,但一旦模型服务变慢,连接就可能长期占用;一旦服务返回 500,调用方拿到的可能仍然是一段错误页面。 Go 的显式语法会把这些问题推到台面上。error 必须被接收,类型转换必须写明,defer、context 和 goroutine 的生命周期也不会凭空消失。 这对 AI 生成代码形成了一种很有价值的约束:编译器先拦住低级问题,人再检查业务决策。 例如,我会要求模型先生成一个"责任完整的版本": var ErrModelUnavailable = errors.New("model service unavailable") func AskModel(ctx context.Context, client *http.Client, endpoint string, prompt string) ([]byte, error) { payload, err := json.Marshal(map[string]string{"prompt": prompt}) if err != nil { return nil, fmt.Errorf("encode model request: %w", err) } req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, bytes.NewReader(payload)) if err != nil { return nil, fmt.Errorf("create model request: %w", err) } req.Header.Set("Content-Type", "application/json") resp, err := client.Do(req) if err != nil { return nil, fmt.Errorf("call model: %w", err) } defer resp.Body.Close() if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices { return nil, fmt.Errorf("model returned status %d: %w", resp.StatusCode, ErrModelUnavailable) } body, err := io.ReadAll(io.LimitReader(resp.Body, 4<<20)) if err != nil { return nil, fmt.Errorf("read model response: %w", err) } return body, nil } 这里不是说 Go 写起来没有重复代码,而是每个关键动作都落到具体的代码位置:编码、请求、响应、释放、超时、错误,全都看得见也改得动。 ...