AI 对话项目,会用到流式返回。一般客户端不会直接调用 AI 对话,而是调用自己的服务,自己的服务再调用 AI. 服务端可以通过 websocket 的方式来达到这个效果,也可以通过 SSE 的方式来达到这个效果。而 AI 也可能会提供这两种方式来提供服务。这样一来,项目就可能有四种组合方式来进行 AI 对话。如下:
客户端 | 服务端 | AI |
---|---|---|
APP、浏览器 | websocket | websocket |
APP、浏览器 | websocket | SSE |
APP、浏览器 | SSE | websocket |
APP、浏览器 | SSE | SSE |
https://developer.mozilla.org/zh-CN/docs/Web/API/Server-sent_events/Using_server-sent_events
找 AI
websocket 阵营
https://www.xfyun.cn/doc/spark/Web.html#_1-%E6%8E%A5%E5%8F%A3%E8%AF%B4%E6%98%8E
SSE 阵营
https://agents.baidu.com/docs/develop/out-deployment/conversation/
尝试实现四种组合
从最简单的开始。服务端通过 SSE 的方式, AI 也是 SSE 的方式。
先封装一个 curl 工具包,创建 internal/pkg/curl/curl.go,内容如下。
package curl
import (
"bytes"
"context"
"fmt"
"io"
"net/http"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/gclient"
)
type HttpClient struct {
client *gclient.Client
}
func NewHttpClient() *HttpClient {
return &HttpClient{
client: g.Client(),
}
}
func (h *HttpClient) Get(ctx context.Context, url string, headers map[string]string) (string, error) {
res, err := h.client.Get(ctx, url, headers)
if err != nil {
return "", err
}
defer res.Close()
return res.ReadAllString(), nil
}
func (h *HttpClient) Post(ctx context.Context, url string, data interface{}, headers map[string]string) (string, error) {
for k, v := range headers {
h.client.SetHeader(k, v)
}
res, err := h.client.Post(ctx, url, data, headers)
if err != nil {
return "", err
}
defer res.Close()
return res.ReadAllString(), nil
}
func (h *HttpClient) PostStream(ctx context.Context, url string, data interface{}, headers map[string]string) (io.ReadCloser, error) {
// 将 data 转换为 io.Reader
var body io.Reader
switch v := data.(type) {
case string:
body = bytes.NewReader([]byte(v))
case []byte:
body = bytes.NewReader(v)
default:
return nil, fmt.Errorf("unsupported data type: %T", v)
}
// 创建 HTTP 请求
req, err := http.NewRequestWithContext(ctx, "POST", url, body)
if err != nil {
return nil, err
}
// 设置请求头
for k, v := range headers {
req.Header.Set(k, v)
}
// 发起请求
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
// 检查响应状态码
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("request failed with status code: %d", resp.StatusCode)
}
return resp.Body, nil
}
现在来完善 chat 接口。