iT邦幫忙

2023 iThome 鐵人賽

DAY 15
0
SideProject30

HOW TO GO系列 第 15

15. Bard API (2)

  • 分享至 

  • xImage
  •  

延續前一篇文章 14. Bard API (1) 所提到的問題,今天將著手解決問題。

錯誤排除

panic 錯誤是從這一行開始 bard.go#L128

	results := &Response{
		Content:           jsonChatData[0].([]interface{})[0].(string), // 這裡會發生問題
		ConversationID:    jsonChatData[1].([]interface{})[0].(string),
		ResponseID:        jsonChatData[1].([]interface{})[1].(string),
		FactualityQueries: jsonChatData[3].([]interface{}),
		TextQuery:         jsonChatData[2].([]interface{})[0].([]interface{})[0].(string),
		Choices:           choices,
	}

原因是轉型失敗,猜測是回傳格式更動且開源專案沒有維護所以不能使用。參考 utils.py#L226後修正方式如下

	results := &Response{
		Content:        jsonChatData[4].([]interface{})[0].([]interface{})[1].([]interface{})[0].(string), // 修改這裡
		ConversationID: jsonChatData[1].([]interface{})[0].(string),
		ResponseID:     jsonChatData[1].([]interface{})[1].(string),
		// 暫時先註解,錯誤原因一樣,待釐清這個欄位的用途
		// FactualityQueries: jsonChatData[3].([]interface{}),
		// TextQuery:         jsonChatData[2].([]interface{})[0].([]interface{})[0].(string),
		Choices: choices,
	}

OK,驗證完畢

完整程式碼

go get

go get github.com/joho/godotenv

main.go

package main

import (
	"fmt"
	"log"
	"os"

	_ "github.com/joho/godotenv/autoload"
)

func main() {
	bardApiKey := os.Getenv("BARD_API_KEY")

	bot, err := NewChatbot(bardApiKey)
	if err != nil {
		fmt.Println("new error")
		log.Fatalln(err)
		return
	}

	resp, err := bot.Ask("明天是中秋節")
	if err != nil {
		fmt.Println("ask error")
		log.Fatalln(err)
		return
	}

	fmt.Println(resp.ResponseID)
	fmt.Println(resp.ConversationID)
	fmt.Println(resp.Content)
}

bard.go

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"math/rand"
	"net/http"
	"net/http/cookiejar"
	"net/url"
	"regexp"
	"strings"
	"time"
)

type Chatbot struct {
	ReqID          int
	SNlM0e         string
	ConversationID string
	ResponseID     string
	ChoiceID       string
	Client         *http.Client
	Sessionid      string
}

type Response struct {
	Content           string
	ConversationID    string
	ResponseID        string
	FactualityQueries []interface{}
	TextQuery         string
	Choices           []Choice
}

type Choice struct {
	ID      string
	Content string
}

const HOST = "bard.google.com"
const Origin_url = "https://" + HOST
const BASE_URL = "https://" + HOST + "/"
const ASK_URL = BASE_URL + "_/BardChatUi/data/assistant.lamda.BardFrontendService/StreamGenerate"

func getHeader() http.Header {
	return http.Header{
		"Host":          []string{HOST},
		"X-Same-Domain": []string{"1"},
		"User-Agent":    []string{"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36"},
		"Content-Type":  []string{"application/x-www-form-urlencoded;charset=UTF-8"},
		"Origin":        []string{Origin_url},
		"Referer":       []string{BASE_URL},
	}
}

// NewChatbot() function returns a chatbot client which can be used to ask questions.
func NewChatbot(sessionID string) (*Chatbot, error) {

	client := &http.Client{
		Timeout: 100 * time.Second,
	}
	client.Jar, _ = cookiejar.New(nil)
	chatBot := &Chatbot{
		ReqID:          rand.Intn(100000),
		SNlM0e:         "",
		ConversationID: "",
		ResponseID:     "",
		ChoiceID:       "",
		Client:         client,
		Sessionid:      sessionID,
	}
	chatBot.setCookie()
	sNlM0e, err := chatBot.getSNlM0e()
	chatBot.SNlM0e = sNlM0e
	return chatBot, err
}

func (chatBot *Chatbot) setCookie() {
	url, _ := url.Parse(BASE_URL)
	cookie := &http.Cookie{Name: "__Secure-1PSID", Value: chatBot.Sessionid}
	chatBot.Client.Jar.SetCookies(url, []*http.Cookie{cookie})
}

func (c *Chatbot) getSNlM0e() (string, error) {
	resp, err := c.Client.Get(BASE_URL)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return "", fmt.Errorf("could not get google bard")
	}
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Println("Failed to read the response body: ", err)
		return "", err
	}

	// Convert the response body to a string
	bodyString := string(body)

	re := regexp.MustCompile(`SNlM0e":"(.*?)"`)
	match := re.FindStringSubmatch(bodyString)
	if len(match) < 2 {
		return "", fmt.Errorf("Init failed, SNlM0e not found")
	}
	return match[1], nil
}

// Ask() function takes the message string and returns Bard response of type bard.Response
func (c *Chatbot) Ask(message string) (*Response, error) {
	params := url.Values{
		"bl":     {"boq_assistant-bard-web-server_20230514.20_p0"},
		"_reqid": {fmt.Sprintf("%d", c.ReqID)},
		"rt":     {"c"},
	}
	data := url.Values{
		"f.req": []string{fmt.Sprintf(`[null,"[[\"%s\"], null, [\"%s\",\"%s\",\"%s\"]]"]`, message, c.ConversationID, c.ResponseID, c.ChoiceID)},
		"at":    []string{c.SNlM0e},
	}
	url := ASK_URL + "?" + params.Encode()
	req, _ := http.NewRequest("POST", url, strings.NewReader(data.Encode()))
	req.Header = getHeader()
	resp, err := c.Client.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	buf := new(bytes.Buffer)
	_, _ = buf.ReadFrom(resp.Body)
	respLines := strings.Split(buf.String(), "\n")
	respJSON := respLines[3]
	var jsonChatData []interface{}
	err = json.Unmarshal(json.RawMessage(respJSON), &jsonChatData)
	if err != nil {
		return nil, err
	}
	jsonChatData = jsonChatData[0].([]interface{})
	jsonChat := jsonChatData[2].(string)
	err = json.Unmarshal(json.RawMessage(jsonChat), &jsonChatData)
	if err != nil {
		return nil, err
	}
	choices := make([]Choice, 3)
	for i, item := range jsonChatData[4].([]interface{}) {
		choices[i] = Choice{ID: item.([]interface{})[0].(string), Content: item.([]interface{})[1].([]interface{})[0].(string)}
	}
	results := &Response{
		Content:        jsonChatData[4].([]interface{})[0].([]interface{})[1].([]interface{})[0].(string),
		ConversationID: jsonChatData[1].([]interface{})[0].(string),
		ResponseID:     jsonChatData[1].([]interface{})[1].(string),
		// FactualityQueries: jsonChatData[3].([]interface{}),
		// TextQuery:         jsonChatData[2].([]interface{})[0].([]interface{})[0].(string),
		Choices: choices,
	}
	c.ConversationID = results.ConversationID
	c.ResponseID = results.ResponseID
	c.ChoiceID = results.Choices[0].ID
	c.ReqID += 100000
	return results, nil
}

補充 01. godotenv


上一篇
14. Bard API (1)
下一篇
16. ebiten (1)
系列文
HOW TO GO30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言