Files
Go-Web-Template/server/service/system/auto_code_llm.go
2026-04-07 09:03:48 +08:00

126 lines
3.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package system
import (
"context"
"errors"
"fmt"
"io"
"net/http"
"strings"
"github.com/flipped-aurora/gin-vue-admin/server/global"
"github.com/flipped-aurora/gin-vue-admin/server/model/common"
commonResp "github.com/flipped-aurora/gin-vue-admin/server/model/common/response"
"github.com/flipped-aurora/gin-vue-admin/server/utils/request"
"github.com/goccy/go-json"
)
func (s *AutoCodeService) LLMAuto(ctx context.Context, llm common.JSONMap) (interface{}, error) {
path, err := buildLLMAutoPath(llm)
if err != nil {
return nil, err
}
res, err := request.HttpRequestWithContextAndTimeout(
ctx,
path,
http.MethodPost,
nil,
nil,
llm,
)
if err != nil {
return nil, fmt.Errorf("调用上游大模型服务失败: %w", err)
}
defer res.Body.Close()
body, err := io.ReadAll(res.Body)
if err != nil {
return nil, fmt.Errorf("读取大模型响应失败: %w", err)
}
bodyPreview := previewResponseBody(body)
contentType := res.Header.Get("Content-Type")
if res.StatusCode < 200 || res.StatusCode >= 300 {
return nil, fmt.Errorf("上游大模型服务返回非 2xx: status=%d content-type=%s body=%s", res.StatusCode, contentType, bodyPreview)
}
var resStruct commonResp.Response
if err = json.Unmarshal(body, &resStruct); err != nil {
return nil, fmt.Errorf("解析大模型响应失败: status=%d content-type=%s body=%s err=%w", res.StatusCode, contentType, bodyPreview, err)
}
if resStruct.Code != commonResp.SUCCESS {
return nil, fmt.Errorf("大模型服务返回业务错误: code=%d msg=%s body=%s", resStruct.Code, resStruct.Msg, bodyPreview)
}
return resStruct.Data, nil
}
func (s *AutoCodeService) LLMAutoStream(ctx context.Context, llm common.JSONMap) (*http.Response, error) {
path, err := buildLLMAutoPath(llm)
if err != nil {
return nil, err
}
payload := cloneLLMAutoJSONMap(llm)
responseMode := strings.ToLower(strings.TrimSpace(fmt.Sprintf("%v", payload["response_mode"])))
if responseMode == "" {
payload["response_mode"] = "streaming"
}
res, err := request.HttpRequestWithContextAndTimeout(
ctx,
path,
http.MethodPost,
map[string]string{
"Accept": "text/event-stream",
"Accept-Encoding": "identity", // 禁止 gzip避免 SSE 流被压缩导致缓冲卡住
"Cache-Control": "no-cache",
},
nil,
payload,
-1, // 不设置 client.TimeoutSSE 流的生命周期由 ctx 控制
)
if err != nil {
return nil, fmt.Errorf("调用上游大模型流式服务失败: %w", err)
}
return res, nil
}
func buildLLMAutoPath(llm common.JSONMap) (string, error) {
if global.GVA_CONFIG.AutoCode.AiPath == "" {
return "", errors.New("请先前往插件市场个人中心获取 AiPath 并填写到 config.yaml 中")
}
mode := strings.TrimSpace(fmt.Sprintf("%v", llm["mode"]))
if mode == "" {
return "", errors.New("llmAuto 缺少 mode 参数")
}
return strings.ReplaceAll(global.GVA_CONFIG.AutoCode.AiPath, "{FUNC}", mode), nil
}
func cloneLLMAutoJSONMap(src common.JSONMap) common.JSONMap {
dst := make(common.JSONMap, len(src))
for key, value := range src {
dst[key] = value
}
return dst
}
func previewResponseBody(body []byte) string {
text := strings.TrimSpace(string(body))
text = strings.ReplaceAll(text, "\r", " ")
text = strings.ReplaceAll(text, "\n", " ")
text = strings.Join(strings.Fields(text), " ")
if text == "" {
return "<empty>"
}
runes := []rune(text)
if len(runes) > 300 {
return string(runes[:300]) + "..."
}
return text
}