You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

103 lines
2.3 KiB

package migu
import (
"bufio"
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"time"
)
func newSys(options Options) (sys *Migu, err error) {
sys = &Migu{
options: options,
}
return
}
type Migu struct {
options Options
}
// Chat 以流式方式请求咪咕灵犀,逐条解析 SSE 数据写入 ch;无论成功失败,结束时都会关闭 ch。
func (this *Migu) Chat(ctx context.Context, req *Request, ch chan *StreamResp) (err error) {
defer close(ch)
var body []byte
// 强制流式
req.Stream = true
if req.DeviceId == "" {
req.DeviceId = this.options.DefaultDeviceId
}
if body, err = json.Marshal(req); err != nil {
return
}
// 用 context 控制整个流式请求的生命周期
ctx, cancel := context.WithTimeout(ctx, time.Duration(this.options.TimeoutSecond)*time.Second)
defer cancel()
httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, this.options.BaseURL, bytes.NewReader(body))
if err != nil {
return
}
httpReq.Header.Set("Content-Type", "application/json")
httpReq.Header.Set("Accept", "text/event-stream")
for k, v := range this.options.Headers {
httpReq.Header.Set(k, v)
}
client := &http.Client{Timeout: 0} // 不用 client 超时,统一交给 context
resp, err := client.Do(httpReq)
if err != nil {
return
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
data, _ := io.ReadAll(resp.Body)
err = fmt.Errorf("migu lingxi http status=%d body=%s", resp.StatusCode, string(data))
this.options.Log.Errorln(err)
return
}
reader := bufio.NewReader(resp.Body)
for {
select {
case <-ctx.Done():
err = ctx.Err()
return
default:
}
line, e := reader.ReadBytes('\n')
if len(line) > 0 {
trimmed := bytes.TrimSpace(line)
if len(trimmed) > 0 && bytes.HasPrefix(trimmed, []byte("data:")) {
payload := bytes.TrimSpace(bytes.TrimPrefix(trimmed, []byte("data:")))
// 结束标语 data:[DONE]
if len(payload) == 0 || bytes.Equal(payload, []byte("[DONE]")) {
return
}
var sr StreamResp
if jerr := json.Unmarshal(payload, &sr); jerr != nil {
// 单行解析失败不中断整个流,记录后跳过
this.options.Log.Errorf("migu lingxi unmarshal chunk err: %v, raw=%s", jerr, string(payload))
} else {
ch <- &sr
}
}
}
if e != nil {
if e == io.EOF {
return
}
err = e
return
}
}
}