94 lines
2.5 KiB
Go
94 lines
2.5 KiB
Go
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
)
|
|
|
|
// llmChat: 调用百炼 OpenAI 兼容接口,客服机器人人设,流式返回。
|
|
func llmChat(ctx context.Context, cfg agentCfg, history []chatMsg) (<-chan string, <-chan error) {
|
|
textCh := make(chan string, 16)
|
|
errCh := make(chan error, 1)
|
|
|
|
msgs := make([]chatMsg, 0, len(history)+1)
|
|
msgs = append(msgs, chatMsg{Role: "system", Content: systemPrompt})
|
|
msgs = append(msgs, history...)
|
|
|
|
body, _ := json.Marshal(map[string]any{
|
|
"model": cfg.LLMModel,
|
|
"messages": msgs,
|
|
"stream": true,
|
|
// qwen3 默认开 thinking(多耗时~0.5s且无播报价值),关掉
|
|
"enable_thinking": false,
|
|
})
|
|
req, err := http.NewRequestWithContext(ctx, "POST", cfg.BailianBaseURL+"/chat/completions",
|
|
bytes.NewReader(body))
|
|
if err != nil {
|
|
close(textCh)
|
|
errCh <- err
|
|
return textCh, errCh
|
|
}
|
|
req.Header.Set("Authorization", "Bearer "+cfg.BailianKey)
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
resp, err := http.DefaultClient.Do(req)
|
|
if err != nil {
|
|
close(textCh)
|
|
errCh <- err
|
|
return textCh, errCh
|
|
}
|
|
|
|
go func() {
|
|
defer resp.Body.Close()
|
|
defer close(textCh)
|
|
if resp.StatusCode != 200 {
|
|
b := new(bytes.Buffer)
|
|
b.ReadFrom(resp.Body)
|
|
errCh <- fmt.Errorf("LLM %s: %s", resp.Status, b.String())
|
|
return
|
|
}
|
|
sc := bufio.NewScanner(resp.Body)
|
|
sc.Buffer(make([]byte, 64*1024), 1024*1024)
|
|
defer func() { // ponytail: buffered errCh,扫完总能写入,避免读端永久阻塞
|
|
errCh <- sc.Err()
|
|
}()
|
|
for sc.Scan() {
|
|
line := sc.Text()
|
|
if !strings.HasPrefix(line, "data: ") {
|
|
continue
|
|
}
|
|
data := strings.TrimPrefix(line, "data: ")
|
|
if data == "[DONE]" {
|
|
return
|
|
}
|
|
var chunk struct {
|
|
Choices []struct {
|
|
Delta struct {
|
|
Content string `json:"content"`
|
|
} `json:"delta"`
|
|
} `json:"choices"`
|
|
}
|
|
if json.Unmarshal([]byte(data), &chunk) != nil {
|
|
continue
|
|
}
|
|
for _, c := range chunk.Choices {
|
|
if c.Delta.Content != "" {
|
|
textCh <- c.Delta.Content
|
|
}
|
|
}
|
|
}
|
|
}()
|
|
return textCh, errCh
|
|
}
|
|
|
|
const systemPrompt = `你是"小智",某电商平台的电话客服机器人。规则:
|
|
- 用口语化、亲切的中文简短回答,每句不超过两句话,适合语音播报。
|
|
- 常见问题直接答:订单查询请提供订单号;退换货支持7天无理由;发货一般48小时内;客服热线工作时间9:00-21:00。
|
|
- 超出范围的问题,礼貌说明并引导转人工客服。
|
|
- 不要使用 markdown、表情符号或列表编号。`
|