[Golang][Render] 如何透過 Github Action 讓 Golang App 部署到 Render.com

image-20240505232556431

前情提要:

雖然我近期已經把所有服務都已經從 Heroku.com 搬遷到 GCP 上面,但是 Render.com 本來也是我教學的一個方案。畢竟它擁有不需要信用卡,有免費額度可以讓學生們快速上手。 這邊快速記錄一下該如何透過 Github Action 部署 Golang 服務到 Render.com 的流程。

如果啟動部署,流程會是:

  • 完成 PR Merged 之後
  • Draft a Release 在 Github 上面
  • 然後會啟動自動部署到 Render.com

範例程式碼:

可以參考這個 REPO https://github.com/kkdai/linebot-food-enthusiast

如何開始設定 Github Continuous Deployment

1. 取得 Render.com API Key

2. Render.com API Key 跟 Services ID 填寫到 Github 設定

你到你專案的設定 Secrets and Variables -> Actions

image-20240506121253792

image-20240506121347433

3. render config file render.yaml

放在 github 根目錄

services:
- type: web
  name: linebot-food-enthusiast
  env: go
  buildCommand: go build -o app
  startCommand: ./app
  plan: free
  autoDeploy: false
  envVars:
  - key: ChannelAccessToken
    sync: false
  - key: ChannelSecret
    sync: false
  - key: GOOGLE_GEMINI_API_KEY
    sync: false

4. render.com Github Action

放在 https://github.com/kkdai/linebot-food-enthusiast/blob/main/.github/workflows/render_deploy.yml

name: Render Deploy

on:
  release:
    types: [created]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Deploy to production
        uses: johnbeynon/[email protected]
        with:
          service-id: $
          api-key: $
          wait-for-success: true

其他 Q&A

image-20240506121619207

Q: 看到有一些錯誤顯示,那是否有問題?

A: 目前不會造成部署的錯誤,但是會有錯誤訊息發生。

image-20240506122442086

參考文章:

[Golang][GCP] 透過 Cloud Shell Editor 來部署 Cloud Run 服務

image-20240505134906848

前提:

Cloud Run 其實很方便,可以很快速地將 Heroku 的專案搬移過來。可以透過 Create Services 的方式將 github 的專案直接建立一個 Coontinues Deployment 的流程在 Cloud 。 但是本篇文章會介紹另外一種方式,可以透過 Cloud Shell Editor 的方式也是可以部署你的 github 專案到 Google Cloud Run 的平台。

使用 Cloud Shell Editor 來部署 Cloud Run 服務

image-20240503121609014

image-20240503121650413

image-20240503121810435

image-20240503122134392

image-20240503122152505

  • 點選左下角的 Cloud Code ,啟動認證。

image-20240503122237357

  • 這邊點選 Deploy to Cloud Run

image-20240505132951404

image-20240505132953744

  • 基本上變動都不用改,直接選預設的就可以。

image-20240505133230357

  • 然後回到 Cloud Run 設定,加入原本需要的環境變數。
  • 這樣就可以了。

參考文章:

[Golang][GCP] Cloud Run 造成的 Artifact Registry 空間的清理策略(Cleanup Policy)

前提:

Cloud Run 其實很方便,可以很快速地將 Heroku 的專案搬移過來。在建置 Cloud Functions (第 2 代) 的過程中,會使用到 Cloud Build 跟 Artifact Registry,但建置成功後,舊版本的 Artifact Registry 卻不會自動刪除。

原本從「Heroku 取消免費方案?教你用 Cloud Functions 架設 LINEBOT!」看到了 GCR-Cleanner 。但是卻發現在 GCP 的介面上有更方便的方法可以使用。

image-20240502233826127

透過 Artifact Registry 直接設定 House Keeping 策略

  • Artifact Registry

    image-20240502234018634

  • 點選 size 最大的吧,然後選取上方 Edit Repository

  • 在最下方,選曲 Cleanup Policies

  • 選擇 “Keep most Recent versions”

  • “Keep count” 選 1 (也可以是 2)

image-20240502234314012

如果怕刪除太多,可以用 Dry run 看看結果。

參考文章:

[BwAI workshop][Golang] LINE OA + CloudFunction + GeminiPro + Firebase = 旅行小幫手 LINE 聊天機器人(4): 關於 Gemini Pro 伺服器的相關修改導致 unknown field usageMetadata 的錯誤訊息

Bug: 原本的對話忽然都沒有回應

image-20240503212535596

由於 Google Gemini 伺服器今天早上修改相關變動,但是 Golang 官方套件還無法來得及改動。 (https://github.com/google/generative-ai-go/issues/97)

導致我在 BwAI workshop ,政大與台北大學的授課中相關 LINE Bot 範例可能會無法順利取得回覆。

雖然官方正在解決相關問題,本篇文章將分享如何用另外的方式來先避開相關的問題。

狀況:

傳送文字問題,沒有回應。 但是發送照片卻是可以正確回覆,查看 log 會出現 unknown field usageMetadata 的錯誤訊息。

image-20240503213025527

影響專案:

相關被影響文章:

解決方式:

請記得更新最新版本程式碼,到你的 cloud function 將 function.go 替換掉之後重新 deploy 就可以了。

認真探討

根據 issue 97 主要問題出在 SendMessage 回來的資訊處理部分。

func main() {
	ctx := context.Background()
	client, err := genai.NewClient(ctx, option.WithAPIKey(os.Getenv("GEMINI_API_KEY")))
	if err != nil {
		log.Fatal(err)
	}
	defer client.Close()
	model := client.GenerativeModel("gemini-1.0-pro")
	cs := model.StartChat()

	msg := "hello"
	fmt.Printf("== Me: %s\n== Model:\n", msg)
	_, err = cs.SendMessage(ctx, genai.Text(msg))
	if err != nil {
		log.Fatal(err)
	}
}

Workaround

可以考慮將 StartChat() 換成原有的 model.GenerateContent(ctx, genai.Text(text)),但是會無法使用 cs.History = Memory 。 這時候可以透過

totalString := fmt.Sprintf("Memory:(%s), %s", string(jsonStr), req)
				res, err := model.GenerateContent(ctx, genai.Text(totalString))
				if err != nil {
					log.Fatal(err)
				}

將之前的討論資料,當成 Memory 放入 prompt 之內。作為處理的方式。後來發現成果也還不錯。

文章列表:

程式碼列表:

[BwAI workshop][Golang] LINE OA + CloudFunction + GeminiPro + Firebase = 旅行小幫手 LINE 聊天機器人(3): 導入「名片小幫手」跟「收據小幫手」

image-20240418150533579

前言:

這是一篇為了 04/18 跟 Google Developer Group 合作的 BUILD WITH AI (BWAI) WORKSHOP 的最後一篇文章,畢竟晚上就要工作坊了(投影片可以當場弄,文章可來不及當場寫 XD )。

請記得,如果你想知道以下相關知識:

本篇文章將專注在以下幾個部分:

文章列表:

程式碼列表:

事前準備

  • LINE Developer Account: 你只需要有 LINE 帳號就可以申請開發者帳號。
  • Google Cloud Functions: GGo 程式碼的部署平台,生成供 LINEBot 使用的 webhook address。
  • Firebase:建立Realtime database,LINE Bot 可以記得你之前的對話,甚至可以回答許多有趣的問題。
  • Google AI Studio:可以透過這裡取得 Gemini Key 。

務必確定已經有前兩篇文章的環境,並且該 Cloud Functions 已經串接好 LINE Bot 。

image-20240418144300796

名片小幫手的導入:

  • 將程式碼: https://github.com/kkdai/linebot-cf-namecard 裡面的 function.go 打開。
  • 複製到 Cloud Functions 中已經設置好的環境。
  • 記得在 Firebase Realtime Database 建立一個新的 set - namecard

image-20240418144648316

  • 部署 (搭啦)

名片小幫手程式碼修改部分

首先,將原本透過 Notion 作為 DB 改成 Firebase Realtime Database 之後。首先需要定義相關資料結構。

// Person 定義了 JSON 資料的結構體
type Person struct {
	Name    string `json:"name"`
	Title   string `json:"title"`
	Address string `json:"address"`
	Email   string `json:"email"`
	Phone   string `json:"phone"`
	Company string `json:"company"`
}

資料寫入的部分,這邊使用到 Firebase Database 中的 Push 。相關的官方說明如下:

Add to a list of data in the database. Every time you push a new node onto a list, your database generates a unique key, like messages/users/<unique-user-id>/<username>

也就是說,資料是用類似以下方式儲存:

image-20240418145754861

儲存之前很簡單,不需要額外資料轉換。只需要直接 Push 進去,前面會加上一個唯一的 key 值。

				const DBCardPath = "namecard"
				.....
				
				// Insert the person data into firebase
				userPath := fmt.Sprintf("%s/%s", DBCardPath, uID)
				_, err = fireDB.NewRef(userPath).Push(ctx, person)
				if err != nil {
					log.Println("Error inserting data into firebase:", err)
				}

但是在前面取用的時候,就會比較複雜。因為要完整把整包 JSON 都抓下來後處理。需要以下變動。

				// Load all cards from firebase
				var People map[string]Person
				err = fireDB.NewRef(userPath).Get(ctx, &People)
				if err != nil {
					fmt.Println("load memory failed, ", err)
				}

				// Marshall data to JSON
				jsonData, err := json.Marshal(People)
				if err != nil {
					fmt.Println("Error marshalling data to JSON:", err)
				}

透過一個 string key map var People map[string]Person 來處理這樣的資料格式,再來直接轉換成 JSON 。這樣又可以將資料完整抓下來變成 Gemini 可以閱讀的方式來處理。

名片小幫手改進後相關成果

image-20240418141427354

程式碼: 名片小幫手(Go Cloud Functions版本)https://github.com/kkdai/linebot-cf-namecard

這裡備註一些主要修改,跟優化的部分:

  • 由於使用 Firebase Database ,整包 JSON 丟給 LLM 變得更加聰明。

旅遊收據小幫手導入

開發起因

首先快速講一下,旅遊收據小幫手的開發起因:

  • 今年過年去了一趟韓國首爾遊玩,但是在路上會收到一堆的單據。
  • 因為筆者是韓文苦手,無法讀懂韓文。但是又希望可以有效地查詢相關的資訊。
  • 查詢方式可能想知道哪一天買了什麼,或是哪個商品是在哪一天?哪一個店家購買?

導入方式

旅遊收據小幫手修改部分

Python 轉換成 Golang ,這部分就不細講了。 我們來看看跟前一個部分主要修改部分:

const ImgagePrompt = `This is a receipt, and you are a secretary.  
Please organize the details from the receipt into JSON format for me. 
I only need the JSON representation of the receipt data. Eventually, 
I will need to input it into a database with the following structure:

 Receipt(ReceiptID, PurchaseStore, PurchaseDate, PurchaseAddress, TotalAmount) and 
 Items(ItemID, ReceiptID, ItemName, ItemPrice). 

Data format as follow:
- ReceiptID, using PurchaseDate, but Represent the year, month, day, hour, and minute without any separators.
- ItemID, using ReceiptID and sequel number in that receipt. 
Otherwise, if any information is unclear, fill in with "N/A". 
`

先來看收據辨識的 Prompt。首先:

  • 先跟他說他要讀取的是哪一些資料,要如何去處理它。
    This is a receipt, and you are a secretary.  
    Please organize the details from the receipt into JSON format for me. 
    I only need the JSON representation of the receipt data. Eventually, 
    I will need to input it into a database with the following structure:
    
  • 在資料格式定義上,將收據單跟裡面每個商品品項切開成兩個項目。但是因為會一整包處理,裡面的資訊會被整合再一起。
     Receipt(ReceiptID, PurchaseStore, PurchaseDate, PurchaseAddress, TotalAmount) and 
     Items(ItemID, ReceiptID, ItemName, ItemPrice). 
    
  • 額外處理項目需要註解,主要是資料讀不到的時候先補個 N/A。因為 Flex Message 必須每個欄位都要有數值。
    Otherwise, if any information is unclear, fill in with "N/A". 
    

這邊有多做一次的處理,就是收據原文都是韓文。需要額外跑一次將韓文的 JSON 轉換成中文再來儲存。

				// Pass the text content to the gemini-pro model for receipt translation.
				model = client.GenerativeModel("gemini-pro")
				transJson := fmt.Sprintf("%s \n --- \n %s", TranslatePrompt, ret)
				res, err := model.GenerateContent(ctx, genai.Text(qry))
				if err != nil {
					log.Fatal(err)
				}
				var transRet string
				for _, cand := range res.Candidates {
					for _, part := range cand.Content.Parts {
						transRet = transRet + fmt.Sprintf("%v", part)
						log.Println(part)
					}
				}

這樣才能讓自己比較能了解,並且在前面使用中文詢問的時候,也能取得比較好的答案。

雷點分享

  • 請不要一次直接將韓文收據翻譯成中文,這樣成果會非常的糟糕。
  • 建議都先掃描讓他可以直接得知道韓文成果,你也比較方便來比對答案。

旅遊收據小幫手成果

image-20240418160115593

  • 收據的掃描上,可能需要清楚一點的收據效果會更好。
  • 詢問的部分可以變得相當的口語化,各種想問的都可以。

相關開發心得總結:

這裡提供開發「名片小幫手」跟「旅遊收據小幫手」過程中的一些心得。

GPT-Vision 可以讓以往 OCR 痛點被快速解決。

相關的應用開發過程中,筆者發現使用 GPT-Vision 可以讓掃描的成果比起 OCR 更容易接受。甚至可以接受更大的 range ,擺放歪了甚至是有被塗鴉過後,還是可以被識別。 而且以往使用 OCR 往往太複雜,需要對其邊界後,準備好相關套版最後才能掃瞄出答案。

image-20240418163022770

透過優化 Prompt 與前處理可以讓成果更好。

在名片掃描過程中,筆者也發現使用英文名片的姓名辨識效果會比較好。可能的問題出在,因為中文名字就是沒有關連的三個文字,而英文名字在許多文章中都有出現過。 這邊建議可以透過簡單的 OCR 套件,將姓名或是公司名稱抓出來,來優化整體的處理效果。

試著透過 GPT-Vision 與 LINE Bot 來幫助你生活更輕鬆

在做 GPT-Vision 開發上,筆者發現 Prompt 的設定跟相關的前處理與後處理其實非常跟你的問題領域有關。如果用名片,就有名片相關的部分。反之,用收據的領域又大大不同。 如果要使用一些瀏覽器介面的時候,常常會有切換的問題。這時候建議直接開發一個 LINE Bot 讓你使用上相當的輕鬆又自在。

image-20240418163931923

完整原始碼

你可以在這裡找到相關的開源程式碼:

[BwAI workshop][Golang] LINE OA + CloudFunction + GeminiPro + Firebase = 旅行小幫手 LINE 聊天機器人(2): Firebase Database 讓 LINEBot 有個超長記憶

image-20240413210750427

前言:

這是一篇為了 04/18 跟 Google Developer Group 合作的 BUILD WITH AI (BWAI) WORKSHOP 的第二篇系列文章(不知道還需要幾篇)。

本篇文章將專注在以下幾個部分:

  • Firebase Database 設定
  • 如何在 Cloud Function 上透過官方 Golang 存取 Firebase
  • 透過 Firebase Database 來讓你的 Gemini 記住所有講過的事情,優化上一次打造的 LINE Bot

文章列表:

事前準備

  • LINE Developer Account: 你只需要有 LINE 帳號就可以申請開發者帳號。
  • Google Cloud Functions: GGo 程式碼的部署平台,生成供 LINEBot 使用的 webhook address。
  • Firebase:建立Realtime database,LINE Bot 可以記得你之前的對話,甚至可以回答許多有趣的問題。
  • Google AI Studio:可以透過這裡取得 Gemini Key 。

申請 Firebase Database 服務

  • 記得到 Firebase Console,直接選取你現在有的專案。(可能叫做 My First Project?)

  • 建立一個 Firebase Realtime Database 等等會用到

    image-20240413212830827

  • 地區選美國

    image-20240413212903957

  • Start in “lock mode”

  • 為了開發方便,到 “Rules”設定成可以寫跟讀取,千萬注意:

    • 這是為了測試,請勿用在對外環境
    • 這是為了測試,請勿用在對外環境
    • 這是為了測試,請勿用在對外環境

image-20240413213202354

  • 記住哪個 URL (注意!之後要正式上線,需要改回權限),並且加上一個項目: “BwAI

image-20240413213802313

申請 Services Account Credential 讓 Cloud Function 連接 Google 服務

這部分的教學,其實可以參考我另外一篇文章。[學習文件] 如何在 Heroku 上面使用透過 Golang 來存取 Google Cloud 服務,但是這邊還是快速走一次。

  • 進入 Google Cloud Console ,到 IAM & Admin 選擇建立 Services Account

image-20240413221505536

  • Services Account Name 自己決定,要注意好 (上面專案跟 Firebase 專案名稱要一致)

image-20240413222847247

  • Grant this service account access to project 在身份的時候,建議先用 Editor (比較大,需要小心使用)

image-20240413223055288

  • “Grant users access to this service account” 不需要特別設定
  • 按下 “Manage Keys” 來準備下載 Credential

image-20240413223225404

  • 選擇 Add Key -> Create new Key -> 下載 JSON

image-20240413223613244

如何在 Google Cloud Function 導入 Services Account Credential ?

接下來會來分享,要如何正確地能夠在 Cloud Function 內使用呢。 如果你想要直接使用 Cloud Function 去開啟 Credential JSON 檔案,你會一直得到無法正確拿到 credential 的錯誤訊息。

這時候需要先透過環境參數來加入:

  • 將 JSON 檔案中所有內容複製起來。
  • 為了要能夠正確啟動 Firebase 記得新增一個參數, FIREBASE_URL 並且把剛剛的 Firebase link 網址填上去。
  • 設定 GOOGLE_APPLICATION_CREDENTIALS 參數,然後把所有內容貼上環境參數。

image-20240413225710980

相關程式碼要如何修改

到了原始碼那邊,記得要更新兩個檔案: (完整程式碼)

重新 Deploy 之後,就可以來看最新的狀況。

成果與使用 ChatSession 差異:

image-20240413210750427

可以直接看到成果相當的好,並且這樣的記憶長度就會看 Gemini Model Token 的限制,算是相當的好用。

那這個又跟 Gemini Chat Session 有什麼差別呢?

  • Chat Session: 適合使用在 Cloud Run 那種有固定一整台 server 的 LINE Bot 應用。
  • Firebase Database + Chat Session: 就可以放在 Cloud Function 這種 Functional As A Services 上面。

使用 Golang Google Options 套件需要注意:

雖然已經將 Firebase Realtime Database 設定成每個人都可以讀寫,但是如果是透過 Golang 去存取的時候,你會出現 Unauthorized request 的錯誤訊息。 這時候就是因為你的 JSON 檔案的 Project 跟你的 Firebase Project 是不同的。 只要重新建立一個 Services Account 並且更新 JSON 內容即可。

image-20240413220630196

關於修改程式碼的細節

  • 接下來會跟大家講,如何修改相關程式碼?
	// Init firebase related variables
	ctx := context.Background()
	opt := option.WithCredentialsJSON([]byte(os.Getenv("GOOGLE_APPLICATION_CREDENTIALS")))
	config := &firebase.Config{DatabaseURL: os.Getenv("FIREBASE_URL")}
	app, err := firebase.NewApp(ctx, config, opt)
	if err != nil {
		log.Fatalf("error initializing app: %v", err)
	}
	client, err := app.Database(ctx)
	if err != nil {
		log.Fatalf("error initializing database: %v", err)
	}
  • 首先option.WithCredentialsJSON([]byte(os.Getenv("GOOGLE_APPLICATION_CREDENTIALS"))) 可以讓你從環境參數讀取到 credential 。
  • 接下來 &firebase.Config{DatabaseURL: os.Getenv("FIREBASE_URL")} 則是將 FIREBASE_URL 內容設定好。
  • 這樣就可以正確執行了,接下來要來看相關處理 Gemini 聊天記憶的部分了。

Gemini Pro Chat History 要如何正確處理?

首先也要讓大家了解一下,跟 OpenAI 不同的是: Gemini Chat History 的格式不太一樣(也不是很好了解)。根據網頁資料如下:

[parts {
   text: "In one sentence, explain how a computer works to a young child."
 }
 role: "user",
 parts {
   text: "A computer is like a very smart machine that can understand and follow our instructions, help us with our work, and even play games with us!"
 }
 role: "model"]

不是很確定為何 “parts” 會用集合, anyway 在 Python 中還不算難處理,但是在 Golang 裡面的處理方式如下:(參考自 Google Golang GAI Github https://github.com/google/generative-ai-go)

func ExampleChatSession_history() {
	ctx := context.Background()
	client, err := genai.NewClient(ctx, option.WithAPIKey(os.Getenv("GEMINI_API_KEY")))
	if err != nil {
		log.Fatal(err)
	}
	defer client.Close()
	model := client.GenerativeModel("gemini-1.0-pro")
	cs := model.StartChat()

	cs.History = []*genai.Content{
		&genai.Content{
			Parts: []genai.Part{
				genai.Text("Hello, I have 2 dogs in my house."),
			},
			Role: "user",
		},
		&genai.Content{
			Parts: []genai.Part{
				genai.Text("Great to meet you. What would you like to know?"),
			},
			Role: "model",
		},
	}

	res, err := cs.SendMessage(ctx, genai.Text("How many paws are in my house?"))
	if err != nil {
		log.Fatal(err)
	}
	printResponse(res)
}

問題來了, cs.History 竟然是對應到 []*genai.Content。這並不是一個對於 JSON unmarshall 就能夠直接使用的資料格式。還需要有相關的轉換。

透過 Firebase Database 處理 Chat History 的流程:

這邊稍微讓大家知道一下,由於 Firebase Database 其實就是一大包的 JSON Database 。你可以直接存取一整包的 JSON Structure 進這個資料庫。 這樣來說讓整個資料庫處理上,還有寫小型 POC 應用來說相當的方便。(但是效率是可以討論的)

// Define your custom struct for Gemini ChatMemory
type GeminiChat struct {
	Parts []string `json:"parts"`
	Role  string   `json:"role"`
}

.....

	// Get chat history from Firebase
	var InMemory []GeminiChat
	err = fireDB.NewRef("BwAI").Get(ctx, &InMemory)
	if err != nil {
		fmt.Println("load memory failed, ", err)
	}

	fmt.Println("InMemory: %v", InMemory)

	// convert InMemory to Memory
	for _, c := range InMemory {
		parts := make([]genai.Part, len(c.Parts))
		for i, part := range c.Parts {
			parts[i] = genai.Text(part)
		}
		dst := &genai.Content{
			Parts: parts,
			Role:  c.Role,
		}

		Memory = append(Memory, dst)
	}

上面是從 Firebase Database 取出後需要轉換的程式碼,但是使用方式則是直接將 Memory 直接交給 chat.History 即可。需要記得的是,在處理完之後,要把這一次的對話也放進 Memory之中。部分程式碼如下:

				// Pass the text content to the gemini-pro model for text generation
				model := client.GenerativeModel("gemini-pro")
				cs := model.StartChat()
				cs.History = Memory

				res, err := cs.SendMessage(ctx, genai.Text(req))
				if err != nil {
					log.Fatal(err)
				}
				var ret string
				for _, cand := range res.Candidates {
					for _, part := range cand.Content.Parts {
						ret = ret + fmt.Sprintf("%v", part)
						log.Println(part)
					}
				}

				// Save the conversation to the memory
				Memory = append(Memory, &genai.Content{
					Parts: []genai.Part{
						genai.Text(req),
					},
					Role: "user",
				})

				// Save the response to the memory
				Memory = append(Memory, &genai.Content{
					Parts: []genai.Part{
						genai.Text(ret),
					},
					Role: "model",
				})

				// Save the conversation to the firebase
				err = fireDB.NewRef("BwAI").Set(ctx, Memory)
				if err != nil {
					fmt.Println(err)
					return
				}

重點就在:

  • 要把之前講過的話,放入 History
  • 把現在講過的,直接丟給 cs.SendMessage()
  • 最後要記得把後來使用者輸入的文字,跟 Gemini 回覆的文字都加入 Memory

錯誤處理

Q: 如果從第一篇的文章做過來,改了 FIREBASE_URLGOOGLE_APPLICATION_CREDENTIALS 之後,反而傳訊息都不會回覆了?

A: 請幫我做以下檢查:

  • 傳遞一個圖片訊息,看看有沒有回覆。
  • 如果圖片有回覆,那麼就是你的 Services Account 設定權限跟 Firebase 不同。

Q: 怎麼本來可以對話? 但是忽然沒有回覆了? 查看 Log 卻出現 unknown field “usageMetadata”

image-20240503211859408

這是因為由於 Google Gemini 伺服器今天早上修改相關變動,但是 Golang 官方套件還無法來得及改動。 (https://github.com/google/generative-ai-go/issues/97)

目前這部分程式碼有透過直接換成 新版本的程式碼

接下來:

感謝各位的支持,接下來就是要透過 Gemini Vision 去識別並且翻譯收據。還要讓 Gemini 可以快速幫我們找出哪時候買的? 什麼地方買的? 當初花了多少錢的「收據小幫手」。

完整原始碼

你可以在這裡找到相關的開源程式碼: https://github.com/kkdai/linebot-cf-firebase