Go AI Agent 实战 08|HTTP 模型 API 的工作原理与请求流程

本系列共 10 阶段,面向有 Go 后端基础的开发者,全程用 Go 手写实现。

当前进度:阶段二 Go 调用大模型 · 第 1 课(08)

上一篇:模型幻觉、能力边界与成本控制

第一阶段,我们已经理解了 LLM、Token、Prompt 和结构化输出。现在开始解决一个更接近实际开发的问题:Go 后端如何与模型服务通信?

这一课不引入 Eino,也不依赖第三方 SDK。我们先使用 Go 标准库 net/http 理解模型 API 的通信原理,并在下一篇完成一次真实的 DeepSeek 调用。

一、本课学习目标

学完本课,你应该能够:

  1. 解释 Go 程序、HTTP 客户端和模型服务之间的关系。
  2. 说明一次模型调用从请求构造到结果解析的完整流程。
  3. 理解模型 API 为什么通常使用 POST + JSON。
  4. 分清 HTTP 请求头、请求体和响应体各自负责什么。
  5. 从模型响应中定位最终回答,并判断"HTTP 成功"是否等于"业务成功"。
  6. 使用分阶段排查的方法定位模型调用问题。

本课重点是通信与数据结构,下一课再编写完整的可运行客户端。

二、为什么模型调用应该放在 Go 后端

假设你的博客系统要增加一个"文章摘要"功能。用户提交文章 ID 后,系统读取正文、调用模型生成摘要,再把结果返回给前端。

一种常见但错误的做法,是让浏览器直接携带 API Key 调用模型服务:

text 复制代码
浏览器 ──携带 API Key──> 模型服务

浏览器中的 JavaScript、网络请求和本地存储都难以真正保密,API Key 很容易泄露。更合理的链路是:

text 复制代码
浏览器
  ↓ 文章 ID
Go 后端
  ├─ 鉴权与参数校验
  ├─ 查询文章正文
  ├─ 管理 API Key
  ├─ 调用模型服务
  └─ 校验并记录结果
  ↓ 摘要
浏览器

模型服务只是调用链中的一个外部依赖。API Key 管理、用户权限、数据脱敏、超时控制、成本限制和日志审计,仍然应该由 Go 后端负责。

三、一次模型调用的完整流程

从通信角度看,模型 API 和常见的后端 HTTP 接口没有本质区别。一次典型调用包含以下步骤:

text 复制代码
1. Go 程序组织 model、messages 等参数
                ↓
2. 将请求参数编码为 JSON
                ↓
3. 通过 HTTPS POST 发送到模型接口
                ↓
4. 服务端校验身份、配额和请求参数
                ↓
5. 模型生成结果
                ↓
6. 服务端返回 HTTP 状态码和 JSON 响应
                ↓
7. Go 程序检查状态码并解析业务字段

这里有三类错误需要分别处理:

  • 请求没有发出去,例如参数编码失败、网络不可达或连接超时。
  • 请求发出后,服务端返回了非成功状态码,例如认证失败或请求频率受限。
  • HTTP 请求成功,但响应内容为空、结构异常或不符合业务要求。

如果把这三类问题混在一起,只记录一句"模型调用失败",后续排查会非常困难。

四、模型 API 的基本请求结构

DeepSeek 提供兼容常见 Chat Completions 风格的聊天接口。以下信息以官方文档和实际账号配置为准:

项目 典型约定
请求方法 POST
API 基础地址 https://api.deepseek.com
聊天接口 /chat/completions
请求格式 application/json
身份认证 Authorization: Bearer YOUR_API_KEY
核心字段 model、messages
响应格式 JSON

一个抽象后的 HTTP 请求如下:

http 复制代码
POST /chat/completions HTTP/1.1
Host: api.deepseek.com
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json

请求体可以是:

json 复制代码
{
  "model": "YOUR_MODEL_ID",
  "messages": [
    {
      "role": "user",
      "content": "Go 语言中什么是 goroutine?"
    }
  ]
}

其中:

  • 请求头负责描述 HTTP 通信方式、数据格式和认证信息。
  • model 指定要调用的模型标识。
  • messages 保存本轮请求需要发送给模型的消息。
  • role 表示消息角色,常见值有 system、user 和 assistant。
  • content 保存消息正文。

YOUR_API_KEY 和 YOUR_MODEL_ID 都只是占位符。模型名称会随账号、服务版本和供应商配置变化。本系列后续代码统一使用 deepseek-flash 作为示例,实际运行时请替换为控制台当前可用的模型标识,不要仅凭旧文章猜测。

五、响应中的数据应该如何理解

一次成功响应的简化结构如下:

json 复制代码
{
  "id": "example-request-id",
  "object": "chat.completion",
  "model": "deepseek-flash",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": "Goroutine 是 Go 运行时管理的轻量级并发执行单元。"
      },
      "finish_reason": "stop"
    }
  ],
  "usage": {
    "prompt_tokens": 18,
    "completion_tokens": 21,
    "total_tokens": 39
  }
}

以上只是示意数据,不是一次真实调用结果。

Go 后端最常读取的路径是:

text 复制代码
响应 JSON
    ↓
choices 数组
    ↓
choices[0]
    ↓
message.content

也就是说,模型的最终文本通常位于 choices[0].message.content。不能把整个 HTTP 响应直接当成回答,因为其中还包含请求 ID、模型名称、结束原因和 Token 用量等字段。

这些字段对工程系统也有实际价值:

字段 工程用途
id 关联请求日志和服务端问题排查
model 确认实际响应的模型版本
finish_reason 判断模型为何停止生成
usage 统计 Token 消耗、成本和调用规模

需要特别检查 choices 是否为空,以及 message.content 是否为空。HTTP 返回成功,并不自动意味着模型给出了可用的业务结果。

六、Go 在请求构造阶段负责什么

Go 标准库已经覆盖了模型调用的基础能力:

类型或方法 作用
json.Marshal 将 Go 数据结构编码为 JSON
http.NewRequest 创建 HTTP 请求
http.NewRequestWithContext 创建可关联超时与取消信号的请求
http.Client.Do 发送请求并读取响应
resp.StatusCode 检查 HTTP 状态码
json.NewDecoder 从响应流解析 JSON
resp.Body.Close 释放响应体相关资源

把请求构造与网络发送分开,是排查问题的有效方法。下面先只构造请求体,不真正访问网络:

go 复制代码
type Message struct {
	Role    string `json:"role"`
	Content string `json:"content"`
}

type ChatRequest struct {
	Model    string    `json:"model"`
	Messages []Message `json:"messages"`
}

func buildChatRequestBody(model, prompt string) ([]byte, error) {
	payload := ChatRequest{
		Model: model,
		Messages: []Message{
			{
				Role:    "user",
				Content: prompt,
			},
		},
	}

	return json.Marshal(payload)
}

如果这一步失败,问题在数据结构或 JSON 编码;如果这一步成功,下一步才有必要检查 HTTP 请求、认证和网络。

这种拆分看起来只多做了一步,但出现问题时,可以把故障范围快速缩小到:

  1. 输入参数是否合法。
  2. JSON 是否按预期编码。
  3. HTTP 请求是否发送成功。
  4. 服务端是否返回成功状态。
  5. 响应字段是否能正确解析。
  6. 结果是否满足业务要求。

七、常见错误与工程注意事项

1. 把 HTTP 状态码当成全部结果

不同错误通常对应不同处理方式:

状态码 常见含义 优先检查
400 请求参数不合法 JSON 字段、消息结构、模型标识
401 身份认证失败 API Key 是否正确、是否包含 Bearer
402 账户或余额相关问题 账户状态与服务端错误信息
429 频率或配额受限 请求频率、并发量、重试策略
5xx 服务端错误或暂时不可用 服务状态、稍后重试与告警

具体含义要以模型服务当次返回的错误体为准,不能只根据状态码猜测原因。

2. 把模型输出直接当作可信业务数据

模型生成的摘要可以展示给用户,但如果响应内容被用于退款、授权、库存扣减等关键操作,Go 后端仍必须校验字段、权限和业务规则。

模型可以提出建议,不能成为权限系统的最终裁决者。

3. 把 API Key 写死在代码中

错误示范:

go 复制代码
apiKey := "sk-把真实密钥写在这里"

正确方向是从环境变量或受控的配置系统读取密钥。下一篇会使用环境变量完成真实调用,第三课再进一步讨论密钥安全。

4. 忽略超时和响应体释放

真实网络调用必须设置合理的超时时间,并在读取响应后关闭 resp.Body。没有超时控制的请求可能长时间占用后端 goroutine;没有关闭响应体则会泄漏连接资源。

5. 在日志中泄露完整请求

排查问题时不要原样打印 Authorization 请求头,也不要把用户隐私内容长期写入日志。可以记录请求 ID、状态码、耗时和 Token 用量,并在记录正文前执行脱敏和截断。

八、本课总结

本课需要掌握的不是某个 SDK 的调用方式,而是模型 API 的基础通信模型:

  1. Go 后端是客户端,模型服务是服务端,双方通过 HTTPS 和 JSON 通信。
  2. 请求使用 POST,通过 Bearer Token 认证,通过 messages 描述输入。
  3. 最终回答通常位于 choices[0].message.content。
  4. HTTP 200 只表示这次 HTTP 请求成功,Go 后端仍需检查响应结构和业务结果。
  5. API Key、超时、日志、成本和业务权限都应由后端统一管理。

九、课后练习

练习 1:选择请求方式

向聊天模型发送消息时,以下哪种方式更合理?

A. 使用 GET,把 API Key 和问题都放在 URL 中

B. 使用 POST,通过请求头认证,通过 JSON 请求体提交消息

C. 使用普通 TCP 字符串,不需要 HTTP

参考答案:B。问题正文和结构化参数更适合放在 JSON 请求体中;API Key 应放在受 HTTPS 保护的认证请求头中,不应出现在 URL 和访问日志里。

练习 2:解析模型回答

按照本课的响应结构,模型的最终文本通常位于哪个字段?

A. model.content

B. choices[0].message.content

C. usage.total_tokens

参考答案 :B。model 是模型标识,usage.total_tokens 是 Token 用量,它们都不能代替模型回答。

练习 3:处理异常响应

如果 DeepSeek 返回 HTTP 200,但 choices 是空数组,Go 程序应该如何处理?为什么不能直接返回空字符串?

参考思路:应把这种情况视为响应结构异常或业务失败,记录请求 ID、状态码和经过脱敏的响应摘要,再返回明确的错误。直接返回空字符串会把上游异常伪装成成功结果,前端无法区分"模型确实生成了空内容"和"调用链路存在问题",后续统计和重试也会失真。


下一篇:Go AI Agent 实战 09|用 net/http 真实调用 DeepSeek

专栏:Go 开发者学 AI Agent:从大模型到工程落地

相关推荐
紧跟先前的步伐1 小时前
Go AI Agent 实战 07|模型幻觉、能力边界与成本控制
大数据·人工智能·golang
dghongcheng1 小时前
工业涂装数字化转型:从经验喷涂到数据驱动
人工智能·喷涂·喷漆设备·自动喷涂线·自动喷涂
liuyanqun-parbie1 小时前
从零设计一个通用智能电话平台:FreeSWITCH + ESL + AI 实战架构
人工智能·架构
文慧的科技江湖1 小时前
2026年10月21日智慧停车行业早报:番禺把单次封顶从132元砍到58元,同一天黄埔要往下挖59米只放200个车位——收费在往下走,车位在往深处长
人工智能·开源·智慧城市·智慧停车·城市管理·静态交通·停车saas
comedate1 小时前
【AI】Jev API 接入实战:用 Python 调用 TypeSafe Jev 做结构化判断
人工智能·python·jev·typesafe·快速判断模型
workflower1 小时前
无人机注册量与飞行量
大数据·网络·人工智能·云计算·汽车·无人机
鶴哥只手遮天1 小时前
Spring AI Alibaba 深度技术系列-第 05 章 编译与执行引擎:CompiledGraph、GraphRunner与三段式 Executor
java·人工智能·开源
灵动的艺术1 小时前
如一 Agent 架构解读(六):阈值、联动与坑位——压缩系统的肌理
人工智能·agent·如一
Yyq130208696821 小时前
KTM5220 ——21 位高分辨率、高精度在轴磁性角度编码器IC,校准后INL 可达±0.015°,小封装
人工智能