123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220 |
- package compapi
- import (
- "bytes"
- "encoding/json"
- "errors"
- "fmt"
- "strings"
- "wechat-api/internal/types"
- )
- // 包装了原始的 API 响应,并提供了解析助手方法
- type ChatResult struct {
- *types.CompOpenApiResp
- err error
- }
- // NewChatResult 是 ChatResult 的构造函数
- func NewChatResult(resp any) *ChatResult {
- nresp, err := AnyToCompOpenApiResp(resp)
- res := ChatResult{nresp, err}
- if nresp == nil {
- res.err = errors.New("CompOpenApiRes is nil")
- }
- return &res
- }
- // 以下是ChatResul助手方法
- // GetContentString 返回第一个 Choice 的 Message Content (标准方式)
- func (r *ChatResult) GetContentString() (string, error) {
- var (
- content string = ""
- err error
- )
- if r.err == nil && len(r.Choices) > 0 {
- content = r.Choices[0].Message.Content
- } else if r.err == nil && len(r.Choices) == 0 {
- err = errors.New("choices empty")
- }
- return content, err
- }
- func (r *ChatResult) GetContentJsonStr() (string, error) {
- var (
- content string = ""
- err error
- )
- if r.err == nil && len(r.Choices) > 0 {
- content = r.Choices[0].Message.Content
- } else if r.err == nil && len(r.Choices) == 0 {
- err = errors.New("choices empty")
- }
- if !IsOpenaiModel(r.Model) { //不支持Response Schema的要特殊处理一下
- content, _, err = ExtractJSONContent(content)
- if err != nil {
- return "", fmt.Errorf("GCJS ExtractJSONContent err:'%s'", err)
- }
- }
- return content, err
- }
- // ParseContentAs 解析 Message Content 中的 JSON 到指定的 Go 结构体
- // target 必须是一个指向目标结构体实例的指针 (e.g., &MyStruct{})
- func (r *ChatResult) ParseContentAs(target any) error {
- content, err := r.GetContentString()
- if err != nil {
- return fmt.Errorf("parseContent err: %s", err)
- } else if content == "" {
- return errors.New("parseContent err: content is empty or unavailable")
- }
- if !IsOpenaiModel(r.Model) { //不支持Response Schema的要特殊处理一下
- content, _, err = ExtractJSONContent(content)
- if err != nil {
- return fmt.Errorf("PCA ExtractJSONContent err:'%s'", err)
- }
- }
- return ParseContentAs(content, target, false)
- }
- func AnyToBytes(in any) ([]byte, error) {
- switch v := in.(type) {
- case string:
- return []byte(v), nil
- case []byte:
- return v, nil
- default:
- return json.Marshal(v)
- }
- }
- func AnyToCompOpenApiResp(in any) (*types.CompOpenApiResp, error) {
- if resp, ok := in.(*types.CompOpenApiResp); ok {
- return resp, nil
- }
- if resp, ok := in.(types.CompOpenApiResp); ok {
- return &resp, nil
- }
- bs, err := AnyToBytes(in)
- if err != nil {
- return nil, err
- }
- nresp := &types.CompOpenApiResp{}
- err = json.Unmarshal(bs, nresp)
- if err != nil {
- return nil, err
- }
- return nresp, nil
- }
- func AnyToCompApiReq(in any) (*types.CompApiReq, error) {
- if req, ok := in.(*types.CompApiReq); ok {
- return req, nil
- }
- if req, ok := in.(types.CompApiReq); ok {
- return &req, nil
- }
- bs, err := AnyToBytes(in)
- if err != nil {
- return nil, err
- }
- nreq := &types.CompApiReq{}
- err = json.Unmarshal(bs, nreq)
- if err != nil {
- return nil, err
- }
- return nreq, nil
- }
- func CheckJSON(input any, checkEmpty bool) (bool, error) {
- inputBytes, err := AnyToBytes(input)
- if err != nil {
- return false, err
- }
- var raw json.RawMessage
- err = json.Unmarshal(inputBytes, &raw)
- if err != nil {
- return false, fmt.Errorf("input is not valid JSON: %w", err)
- }
- if checkEmpty {
- trimmed := bytes.TrimSpace(inputBytes)
- if len(trimmed) == 0 {
- return false, fmt.Errorf("input is empty")
- }
- }
- return true, nil
- }
- func WrapJSON(input any, warpKey string, checkValid bool) ([]byte, error) {
- var (
- inputBytes []byte
- outputBytes []byte
- err error
- )
- if inputBytes, err = AnyToBytes(input); err != nil {
- return nil, err
- }
- if checkValid {
- if _, err = CheckJSON(inputBytes, true); err != nil {
- return nil, err
- }
- }
- if len(warpKey) == 0 {
- return inputBytes, nil
- }
- wrapper := map[string]json.RawMessage{
- warpKey: json.RawMessage(inputBytes),
- }
- if outputBytes, err = json.Marshal(wrapper); err != nil {
- return nil, fmt.Errorf("failed to marshal wrapper structure: %w", err)
- }
- return outputBytes, nil
- }
- func ParseContentAs(content string, target any, removeJsonBlock bool) error {
- if removeJsonBlock &&
- strings.HasPrefix(content, "```json") && strings.HasSuffix(content, "```") {
- content = strings.TrimSuffix(strings.TrimPrefix(content, "```json"), "```")
- content = strings.TrimSpace(content)
- }
- if err := json.Unmarshal([]byte(content), target); err != nil {
- return fmt.Errorf("parseContent err:failed to unmarshal"+
- " content JSON into target type '%w'", err)
- }
- return nil
- }
- func ExtractJSONContent(s string) (string, bool, error) {
- startMarker := "```json"
- endMarker := "```"
- // 寻找起始标记
- startIdx := strings.Index(s, startMarker)
- if startIdx == -1 {
- return s, false, nil // 没有起始标记就不进行之后步骤
- }
- // 寻找结束标记(需在起始标记之后查找)
- endIdx := strings.LastIndex(s, endMarker)
- if endIdx == -1 || endIdx <= startIdx {
- return s, false, errors.New("lost endMarker") // 没有结束标记或标记顺序错误
- }
- // 计算内容范围
- contentStart := startIdx + len(startMarker)
- contentEnd := endIdx
- // 提取内容并去除前后空白
- content := strings.TrimSpace(s[contentStart:contentEnd])
- // 若内容为空视为无效
- if content == "" {
- return s, false, errors.New("empty content")
- }
- return content, true, nil
- }
|