资讯动态

Go AI Agent 实战 09|用 net/http 真实调用 DeepSeek

发布时间:2026/10/22 13:13:03 来源:尧图企业网站定制
本系列共 10 阶段面向有 Go 后端基础的开发者全程用 Go 手写实现。当前进度阶段二 Go 调用大模型 · 第 2 课09上一篇HTTP 模型 API 的工作原理与请求流程上一课已经讲清了模型 API 的请求与响应结构。这一课把骨架补成完整程序使用 Go 标准库net/http读取环境变量、发送真实请求、解析响应并把失败原因转换成可处理的错误。课程仍然不依赖第三方 SDK。你会看到 HTTP 客户端如何一步步完成模型调用也会知道为什么不能把“拿到了一个 HTTP 响应”直接当成调用成功。一、本课学习目标学完本课你应该能够使用http.NewRequest和http.Client.Do完成一次模型调用。使用 Go 结构体描述请求和响应。通过环境变量读取 API Key 与模型标识。检查 HTTP 状态码并限制错误响应体的读取大小。校验choices和回答内容避免把异常响应当成成功。用可复用的函数组织模型调用而不是把所有逻辑塞进main。二、从接口约定到 Go 程序一次最小模型调用可以对应五个阶段读取配置 ↓ 构造请求 JSON ↓ 创建并发送 HTTP 请求 ↓ 检查状态码并解析响应 ↓ 校验最终回答每个阶段都可能失败。例如没有配置 API Key程序不应该继续发送请求。JSON 编码失败说明本地数据结构有问题。client.Do返回错误通常意味着请求没有正常完成。服务端返回 401 或 429说明请求已经到达服务端但被拒绝。HTTP 返回成功响应缺少choices仍然不能视为业务成功。把这些阶段拆开报错时才能快速定位问题。三、准备项目在 PowerShell 中执行mkdir go-deepseek-demoSet-Locationgo-deepseek-demo go mod init go-deepseek-demo本课只使用 Go 标准库不需要额外安装依赖。四、完整示例新建main.go写入以下代码packagemainimport(bytesencoding/jsonerrorsfmtionet/httposstringstime)const(apiURLhttps://api.deepseek.com/chat/completionsdefaultModeldeepseek-flash)typeChatMessagestruct{Rolestringjson:roleContentstringjson:content}typeChatRequeststruct{Modelstringjson:modelMessages[]ChatMessagejson:messages}typeChatResponsestruct{IDstringjson:idModelstringjson:modelChoices[]struct{Message ChatMessagejson:messageFinishReasonstringjson:finish_reason}json:choices}funccallModel(apiKey,model,promptstring)(string,error){ifstrings.TrimSpace(apiKey){return,errors.New(DEEPSEEK_API_KEY 未设置)}requestData:ChatRequest{Model:model,Messages:[]ChatMessage{{Role:user,Content:prompt,},},}body,err:json.Marshal(requestData)iferr!nil{return,fmt.Errorf(序列化请求失败: %w,err)}req,err:http.NewRequest(http.MethodPost,apiURL,bytes.NewReader(body),)iferr!nil{return,fmt.Errorf(创建 HTTP 请求失败: %w,err)}req.Header.Set(Authorization,Bearer apiKey)req.Header.Set(Content-Type,application/json)client:http.Client{Timeout:90*time.Second,}resp,err:client.Do(req)iferr!nil{return,fmt.Errorf(发送模型请求失败: %w,err)}deferresp.Body.Close()ifresp.StatusCode200||resp.StatusCode300{errorBody,readErr:io.ReadAll(io.LimitReader(resp.Body,120),)ifreadErr!nil{return,fmt.Errorf(HTTP %d读取错误响应失败: %w,resp.StatusCode,readErr,)}return,fmt.Errorf(模型 API 返回 HTTP %d: %s,resp.StatusCode,strings.TrimSpace(string(errorBody)),)}varresult ChatResponseiferr:json.NewDecoder(resp.Body).Decode(result);err!nil{return,fmt.Errorf(解析模型响应失败: %w,err)}iflen(result.Choices)0{return,errors.New(模型响应中 choices 为空)}answer:strings.TrimSpace(result.Choices[0].Message.Content)ifanswer{return,errors.New(模型返回了空回答)}returnanswer,nil}funcrun()error{apiKey:strings.TrimSpace(os.Getenv(DEEPSEEK_API_KEY))model:strings.TrimSpace(os.Getenv(DEEPSEEK_MODEL))ifmodel{modeldefaultModel}answer,err:callModel(apiKey,model,请用 Go 代码举例解释什么是 goroutine。,)iferr!nil{returnerr}fmt.Println(模型回答)fmt.Println(answer)returnnil}funcmain(){iferr:run();err!nil{fmt.Fprintln(os.Stderr,调用失败,err)os.Exit(1)}}deepseek-flash是系列代码中的默认示例。模型名称可能随账号和供应商配置变化可以通过DEEPSEEK_MODEL覆盖也可以直接修改适配器的默认值。五、配置并运行在 PowerShell 中设置当前会话的密钥$env:DEEPSEEK_API_KEY 你的真实API_KEY如果账号使用的模型标识与示例不同再设置$env:DEEPSEEK_MODEL 控制台中的模型ID然后运行go run.成功时终端会输出一段模型回答。请求 ID、耗时、Token 用量等诊断信息还没有在这里打印后续课程会逐步加入。这类环境变量通常只对当前 PowerShell 会话及其子进程有效。关闭终端后可能需要重新设置。不要把真实密钥写进截图、聊天消息或 Git 仓库。六、逐段理解关键代码1. 用结构体表达协议typeChatMessagestruct{Rolestringjson:roleContentstringjson:content}JSON 标签把 Go 字段名和 API 字段名连接起来Go 中的Role序列化为 JSON 中的role。Go 中的Content序列化为 JSON 中的content。直接拼接 JSON 字符串容易漏掉转义、逗号或引号。结构体加encoding/json的做法更可靠也方便后续加入多轮消息、temperature和stream等字段。2.json.Marshal只负责编码body,err:json.Marshal(requestData)它把 Go 值转换成[]byte不会自动发送网络请求。如果更新到json.Marshal这一步就失败问题范围通常已经缩小到输入数据或字段定义。3.NewRequest与Client.Do分工不同req,err:http.NewRequest(http.MethodPost,apiURL,bytes.NewReader(body))NewRequest只是创建请求对象。真正建立网络连接并等待响应的是resp,err:client.Do(req)也要区分两个错误来源err ! nilHTTP 请求没有正常完成例如 DNS、TLS、连接失败或超时。err nil拿到了 HTTP 响应但响应仍然可能是 401、429 或 500。4.defer resp.Body.Close()释放连接资源读取响应体后必须关闭resp.Body。否则在持续发送请求的服务中底层连接和相关资源可能无法及时释放。示例把主逻辑放进run()由main()统一处理退出状态。这能保证函数返回时先执行defer再决定是否结束进程。5. 限制错误响应的读取量io.LimitReader(resp.Body,120)1 20等于 1 MiB。异常服务或代理可能返回很大的 HTML 错误页直接读入内存并不安全。生产日志仍需要对错误内容做脱敏和截断不能因为加了读取上限就无差别记录全部响应。6. 解析成功不代表业务成功iflen(result.Choices)0{return,errors.New(模型响应中 choices 为空)}空切片不包含索引0。先检查长度再读取result.Choices[0]可以避免运行时 panic。之后还要检查回答去掉空白后是否为空。只有协议解析和业务校验都通过调用才算真正成功。七、常见失败如何排查现象优先检查提示 API Key 未设置是否在同一个终端窗口中设置环境变量401API Key 是否有效Bearer请求头是否完整400 或 422模型标识、消息结构和请求 JSON 是否符合要求429请求频率、并发量和账号配额Client.Do返回错误DNS、网络、TLS、代理和超时设置JSON 解析失败响应是否真的是 JSON结构是否与代码一致choices为空服务端是否返回了异常或非预期的成功响应排查时优先保留状态码、请求 ID、错误类型和安全截断后的错误消息不要打印完整Authorization请求头。八、本课总结一次模型调用需要依次完成配置读取、请求编码、HTTP 发送、状态检查、JSON 解析和业务校验。NewRequest构造请求Client.Do才真正发送请求。err nil只表示拿到了 HTTP 响应不代表状态码成功。HTTP 成功也不代表回答字段一定有效。环境变量可以把代码与密钥分离但不能代替权限控制和日志脱敏。可复用函数应返回error由程序入口统一决定如何退出。九、课后练习练习 1请求为什么使用 POST为什么不把问题、API Key 和模型参数全部拼到 URL 中参考答案聊天接口使用 POST 接收结构化 JSON 请求体更适合传输长文本和复杂参数。API Key 应放在 HTTPS 认证请求头中放进 URL 容易出现在访问日志、代理日志和浏览器历史里增加泄露风险。练习 2为什么先检查choices为什么不能直接访问result.Choices[0].Message.Content参考答案choices可能是空数组。直接访问索引0会触发 panic。应先检查长度再检查回答内容是否为空并把异常响应转换为普通错误。练习 3什么情况下不应继续重试如果程序收到 HTTP 401是否应该立即再次发送相同请求参考思路通常不应该。401 表示认证失败重复同一个无效密钥不会修复问题。应先检查密钥、请求头和账号状态。只有明确属于暂时性故障的错误才适合在限制次数的前提下重试。下一篇Go AI Agent 实战 10环境变量、API Key 与密钥安全专栏Go 开发者学 AI Agent从大模型到工程落地

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑