[Golang] 使用 Golang 透過 Google Gemini Pro 來打造一個具有LLM 功能 LINE Bot (一): Chat Completion and Image Vision

image-20231216015206933

前提

上一次開始使用 tmc/langchaingo 打造一些基礎的 Golang 應用後,就在 12 月 13 號 Google 正式公開了 Gemini Pro 的相關 API 。本篇文章開始以下的事項:

  • Google Gemini Pro 有哪些資源可以學習?
  • 如何整合到 LINE Bot?
  • 打造一個回覆,圖片辨識的 Gemini Pro LINE Bot

系列文章:

  1. 使用 Golang 透過 Google Gemini Pro 來打造一個具有LLM 功能 LINE Bot (一): Chat Completion and Image Vision(本篇)
  2. 使用 Golang 透過 Google Gemini Pro 來打造一個具有LLM 功能 LINE Bot (二): 使用 Chat Session 與 LINEBot 快速整合出有記憶的 LINE Bot
  3. 使用 Golang 透過 Google Gemini Pro 來打造一個具有LLM 功能 LINE Bot (三): 使用 Gemini-Pro-Vision 來打造名片管理的聊天機器人

Google Gemini Pro 有哪些資源可以學習

image-20231216014823886

雖然才正式公佈不久,但是網路上其實有不少資源可以學習。我這邊列出一些資源: (直接問 Gemini Pro 出來的)

image-20231216015049307

這裡也有一些我覺得蠻重要的資訊:

如何取得 Google Gemini Pro - API Keys

到 Google AI Studio : https://makersuite.google.com/app/apikey

image-20231216015529412

目前價位如下,還有一些免費額度可以用:

Image

如何整合 LINE Bot?

程式碼: https://github.com/kkdai/linebot-gemini-pro

這邊主要先整入兩個主要功能 : ChatSession 跟 Image ,相關程式碼都在 gemini.go

Chat Completion

// Gemini Chat Complete: Iput a prompt and get the response string.
func GeminiChatComplete(req string) string {
	ctx := context.Background()
	client, err := genai.NewClient(ctx, option.WithAPIKey(geminiKey))
	if err != nil {
		log.Fatal(err)
	}
	defer client.Close()
	model := client.GenerativeModel("gemini-pro")
	cs := model.StartChat()

	send := func(msg string) *genai.GenerateContentResponse {
		fmt.Printf("== Me: %s\n== Model:\n", msg)
		res, err := cs.SendMessage(ctx, genai.Text(msg))
		if err != nil {
			log.Fatal(err)
		}
		return res
	}

	res := send(req)
	return printResponse(res)
}

這個主要是處理一個 Chat Session ,就是類似 OpenAI 的 Chat Completion。 加強解釋一些東西:

  • 回傳資料 res := send(req) 其中的 res 不能直接用,需要透過 printResponse(res) 拿回來。

其實也就是拿出最接近的那個答案中的本文:

func printResponse(resp *genai.GenerateContentResponse) string {
	var ret string
	for _, cand := range resp.Candidates {
		for _, part := range cand.Content.Parts {
			ret = ret + fmt.Sprintf("%v", part)
			fmt.Println(part)
		}
	}
	fmt.Println("---")
	return ret + "\n---"
}

Gemini Pro Vision / 圖片相關 API

func GeminiImage(imgData []byte) (string, error) {
	ctx := context.Background()
	client, err := genai.NewClient(ctx, option.WithAPIKey(geminiKey))
	if err != nil {
		log.Fatal(err)
	}
	defer client.Close()

	model := client.GenerativeModel("gemini-pro-vision")
	prompt := []genai.Part{
		genai.ImageData("png", imgData),
		genai.Text("Describe this image with scientific detail, reply in zh-TW:"),
	}
	log.Println("Begin processing image...")
	resp, err := model.GenerateContent(ctx, prompt...)
	log.Println("Finished processing image...", resp)
	if err != nil {
		log.Fatal(err)
		return "", err
	}

	return printResponse(resp), nil
}

幾件事情要注意:

  • model 不一樣,放錯會出事情: "gemini-pro-vision"
  • 資料一定要指定副檔名,超怪。 不過我都放 "png" 就過了。

快速部署

不想學那麼多,就準備好以下資料,就按下 Deploy

  • ChannelAccessToken
  • ChannelSecret
  • GOOGLE_GEMINI_API_KEY

成果與 Google Gemini Pro API 的一些特點:

1. 資料時效相當的新

image-20231216022242789

image-20231216022248154

時效蠻新的,很多資料都是 2023/07 之後。ChatGPT 這種就回答不出來。

  • ChatGPT GPT 3.5 是 2022
  • API GPT4 是 2023/04

image-20231216015049307

  • 像是以上的內容,因為許多資料都有 link 可以使用,貼到 LINE Bot 沒有太多問題。
  • 不過因為直接回 Markdown 可能要把相關語法刪除掉,才能正確顯示。
  • 這裡註解一下,因為 Gemini Pro 的 temperature 預設是 0.9 (refer)

接下來:

可以開始來玩玩 Function Calling 儘可能把相關內容試試看。 然後也可以等等 LangChainGo 什麼時後把 Gemini Pro 接起來

# 參考資料:

[Golang] 透過 Ollama 快速架設免費本地端的 ChatGPT,並且寫一個 LangChainGo 的應用

image-20231212012818576

前提

好久沒來寫 Golang 來寫文章了,就想說把之前看過關於 Golang LangChain 相關的文章來敘述一下。以下這篇文章主要參考了 Eli Bendersky 部落格的文章 - [Using Ollama with LangChainGo](https://eli.thegreenplace.net/2023/using-ollama-with-langchaingo/)

這裡也會詳細一點來介紹以下幾個部分:

  • 什麼是 Ollama 能拿來做些什麼?
  • 要如何使用 Ollama ?
  • 如何透過 Golang 連接 Ollama 並且串接 LangChain

接下來就開始吧!

什麼是 Ollama

image-20231211231527061

Ollama 是一個相當方便的工具,以往需要在本地端使用 llama 的話需要有以下的步驟:

  • 到 Meta AI 申請下載 link
  • 透過 LLAMA.cpp 把 LLAMA2 的 model 去轉換過後,讓你在 Mac OSX 上面可以執行並且讀取。 (當然還有做 Quantization, Optimization )
  • 然後執行 LLAMA.cpp 去跑起來 LLAMA 的 model 來跑你需要的功能。

但是使用 Ollama 卻相當的簡單

安裝 Ollama

參考 github 說明

curl https://ollama.ai/install.sh | sh

執行之後,就會下載模型

ollama run llama2

更多其他 Models :

Model Parameters Size Download
Neural Chat 7B 4.1GB ollama run neural-chat
Starling 7B 4.1GB ollama run starling-lm
Mistral 7B 4.1GB ollama run mistral
Llama 2 7B 3.8GB ollama run llama2
Code Llama 7B 3.8GB ollama run codellama
Llama 2 Uncensored 7B 3.8GB ollama run llama2-uncensored
Llama 2 13B 13B 7.3GB ollama run llama2:13b
Llama 2 70B 70B 39GB ollama run llama2:70b
Orca Mini 3B 1.9GB ollama run orca-mini
Vicuna 7B 3.8GB ollama run vicuna
➜  Documents ollama run llama2
pulling manifest
pulling 22f7f8ef5f4c... 100% ▕██████████████████████████████████████████████████████████████████████████████████▏ 3.8 GB
pulling 8c17c2ebb0ea... 100% ▕██████████████████████████████████████████████████████████████████████████████████▏ 7.0 KB
pulling 7c23fb36d801... 100% ▕██████████████████████████████████████████████████████████████████████████████████▏ 4.8 KB
pulling 2e0493f67d0c... 100% ▕██████████████████████████████████████████████████████████████████████████████████▏   59 B
pulling 2759286baa87... 100% ▕██████████████████████████████████████████████████████████████████████████████████▏  105 B
pulling 5407e3188df9... 100% ▕██████████████████████████████████████████████████████████████████████████████████▏  529 B
verifying sha256 digest
writing manifest
removing any unused layers
success

# 寫一首詩,但是他看不太懂:
>>> write me a potry
Sure, I'd be happy to write a short story for you! Can you give me some details on what you would like the story to be about? For example,
is it a romance, a mystery, a sci-fi adventure, or something else entirely? And how long would you like the story to be?


# 用中文問也是可以,但是回答會是英文:
>>> 寫一個笑話給我
Of course! Here's a short humorous story for you:

One day, a man walked into a library and asked the librarian, "Do you have any books on Pavlov's dogs and Schrödinger's cat?"

The librarian replied, "It rings a bell, but I'm not sure if it's here or not."


# 強迫要他回覆中文,看起來還可以:
➜  ollama-go ollama run llama2
>>> 寫一首詩給我,  reply in zh_tw
 Of course, I'd be happy to write a poem for you! Here it is:

紅色的陽光在天上照亮,
顯示出世界的美好。
每一個人都有自己的story,
filled with laughter, tears, and glory.

在沒有你的時候,我們會繁榮;
在你回來的時候,我們會歡喜。
你是我們的家,是我們的心靈伴侶,
不管 WHERE 你去,我們都会等你回來。

I hope you like it! Let me know if you have any requests or preferences for the poem.

透過 API Gateway 呼叫 Ollama

最方便的,架起了 Ollama 之後除了可以透過 ollama 來呼叫,更可以透過 API 來對本地端呼叫。

curl http://localhost:11434/api/generate -d '{
  "model": "llama2",
  "prompt": "very briefly, tell me the difference between a comet and a meteor",
  "stream": false
}'
------
{"model":"llama2","created_at":"2023-12-11T14:41:36.760949Z","response":"\nSure! Here's the difference between a comet and a meteor:\n\nComets are icy bodies that originate from the outer reaches of the solar system. They are composed of dust, ice, and rock, and they have a long, elliptical orbit around the sun. When a comet approaches the inner solar system, the sun's heat causes the comet to release gas and dust, creating a bright tail that can be seen from Earth.\n\nMeteors, on the other hand, are small rocks or pieces of debris that enter Earth's atmosphere. As they travel through the atmosphere, they burn up due to friction with the air, producing a bright streak of light in the sky, commonly known as a shooting star. The remains of the meteoroid can sometimes survive entry into the atmosphere and land on Earth as a meteorite.\n\nSo, while both comets and meteors are objects in space, the key difference is that comets are icy bodies that originate from outside the solar system, while meteors are small rocks or pieces of debris that originate from within the solar system (primarily from asteroids).","done":true,"context":.......}

寫一個簡單的 LangChain 跟 Ollama 的應用

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/tmc/langchaingo/llms"
	"github.com/tmc/langchaingo/llms/ollama"
	"github.com/tmc/langchaingo/schema"
)

func main() {
	llm, err := ollama.NewChat(ollama.WithLLMOptions(ollama.WithModel("llama2")))
	if err != nil {
		log.Fatal(err)
	}
	ctx := context.Background()
	completion, err := llm.Call(ctx, []schema.ChatMessage{
		schema.SystemChatMessage{Content: "Give a precise answer to the question based on the context. Don't be verbose."},
		schema.HumanChatMessage{Content: "What would be a good company name a company that makes colorful socks? Give me 3 examples."},
	}, llms.WithStreamingFunc(func(ctx context.Context, chunk []byte) error {
		fmt.Print(string(chunk))
		return nil
	}),
	)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(completion)
}

你可以透過 github 找到完整程式碼。

[遊戲天國] 隻狼:暗影雙死 也破關了

終於把隻狼打通關了,黑血狼剩下 「[血緣詛咒」 ,其他都在 Steam Deck 全破。 (可惡索尼獨佔)。

尾王一心比較麻煩就是第一階段,二三階段就乖乖用傘反擊慢慢耗,只識破槍的突刺。「猶豫,就會敗北」

image-20231228095033018image-20231228095035287

比較難的關卡

image-20231228095112511

雙義父打完,其實三年前的比在天守閣的弱一點,天守閣因為我不能卡牆,天守閣放毒倒是比較困擾。 不過慢慢防守用不死斬總算也是打完了 。

經過 「P的謊言」後,隻狼變簡單了

經過 「P的謊言」我的隻狼有變強很多。(明明才剛開始。

不過用 SD 好討厭啊~都不好錄影。為了截圖還差點忘記砍最後一刀,差點就搞笑了。討伐次數大概是五次,不過我有看攻略。

image-20231228095232039

打完 P 的謊言再來打隻狼~會很好過誒

之前在 PS5 卡太久,一直沒時間打。 結果打完 P 的謊言練習很久的阻擋之後。 就很順利的過了 O_0 ? 看來之前全破魂系列都是閃躲王~~

[Kubernetes] 關於 Kubernetes

Kubernetes Secrets Management

前提:

最近在聊天的時候討論到,究竟在 Kubernetes 內管控一些帳號與密碼的時候,我們都知道要使用 Kubernetes Secret 而不要使用 Environment Variables 。 但是在安全性上究竟有哪些差異,我決定查一些書上面的整理:

參考書籍內容

**Kubernetes Security** Liz Rice, Michael Hausenblas

image-20231203204027780

You can pass environment variables into containers at runtime. This means you can take the container image (code) and configure it according to the scenario it is running in. In Kubernetes, “ordinary” environment variables can be specified directly in the pod YAML or via a ConfigMap. But be careful: including secret values in YAML files that you check into code control means those secrets are accessible to the same people who can see the source code.

To mitigate this, Kubernetes also supports the secret resource, which can be passed into a pod as an environment variable. Your pod spec or ConfigMap YAML refers to the secret resource by name rather than directly to the secret value, so that it’s safe to put that YAML under code control.

Kubernetes 1.7 之後的版本,可以加上 node authorization 確保未經授權的 kubelet 來存取機密資訊。

Kubernetes Secrets Management By Andrew Block, Alex Soto

Kubernetes Secrets Management

2 An introduction to Kubernetes and Secrets

image-20231203212545186

As discussed in chapter 1, one of the big differences between Secrets and ConfigMaps is how data is stored inside etcd. Secrets store data in Base64 format; ConfigMaps store data in plain text.

2.4.2 Secrets are mounted in a temporary file system

A Secret is only sent to a Node if there is a Pod that requires it. But what’s important is that a Secret, even though it is mounted as a volume, is never written to disk but in-memory using the tmpfs file system. tmpfs stands for temporal filesystem, and as its name suggests, it is a file system, where data is stored in volatile memory instead of persistent storage. When the Pod containing the Secret is deleted, the kubelet is responsible for deleting it from memory as well.

危險程度 / Hacking 方式

Environment Variables /

export
 
declare -x GREETING_MESSAGE="Hello Anna"     ①
declare -x HOME="/"
declare -x HOSTid="greeting-demo-deployment-5664ffb8c6-2pstn"
...

Secret 放在暫存檔案

mount | grep tmpfs
 
tmpfs on /dev type tmpfs (rw,nosuid,size=65536k,mode=755)
tmpfs on /sys/fs/cgroup type tmpfs (ro,nosuid,nodev,noexec,relatime,mode=755)
tmpfs on /etc/sec type tmpfs (ro,relatime) /   ①
...
 
ls /etc/sec                                    ②
 
greeting.message
 
cat /etc/sec/greeting.message                  ③
 
Hello Anna

利用外部的結構

img

Summary

  • Kubernetes 叢集由主節點和可選的工作節點組成。
  • 任何 Kubernetes 資源和叢集的目前狀態都儲存在etcd 實例中。
  • 我們討論了將應用程式部署到 Kubernetes。
  • 我們介紹了使用 ConfigMaps 在外部配置應用程序,無論是作為環境變數還是作為文件。
  • Secrets 在建構和使用上與 ConfigMap 沒有太大不同。

要加強 configMap 可以參考這一篇

使用 Kubernetes ConfigMap 進行正確的秘密管理

[Heroku] 如何把 Local Python Docker Services 轉換到 Heroku 上面

前提:

同事喬治日報上面推薦的 https://github.com/assafelovic/gpt-researcher 真的很好用,搭配 https://app.tavily.com/home 超讚

來問問 Sam Altman 最近到底發生什麼事情,懶人包馬上出來。 不太好本地端部署,為了部署在 Heroku 稍微修改一些部署端的設定。

image-20231122172208738

image-20231122172211045

實作

分享一下關於部署需要注意的,跟如何如何更改部署流程在雲端的 FaaS 服務上。

TL;DR 直接按下去部署 Heroic

https://github.com/kkdai/gpt-researcher

image-20231122194904129

本地端部署起來

其實真的要部署起來,沒有那麼簡單。首先 local 要克服相關的 python 版本的問題。像是 Python 版本需要 3.11 以上。

PYENV 控制版本

透過 pyenv 控制 python 版本真的很方便,很多時候 LLM 大多使用 python 3.9 。但是有一些人會使用 3.10 甚至是 3.11

  • pyenv versions:可以列出已經安裝在本地端的所有版本。 可以透過 pyenv install 3.11.6 來安裝版本。
  • pyenv local 3.xx.xx: 本地端,這個 shell session 切換版本。
  • pyenv global 3.xx.xx: 讓系統預設版本變換。

部署到 Heroku

由於這個專案,有提供 Dockerfile 其實要本地端部署還是簡單一點。如果希望可以讓更多人使用,可以試試看放在 Heroku 上面。以下分享幾個需要修改的地方。

關於浮動 port

有使用 Cloud Platform 的人都知道,雲端上面 FaaS (Function As A Services) 無法讓你任意選擇開啟的 PORT 。通常會使用 $PORT 帶下去啟動。 關於這部分,可能要檢查一些地方:

Dockerfile 套用 $PORT

原有版本在 https://github.com/assafelovic/gpt-researcher/blob/master/Dockerfile

FROM python:3.11.4-slim-bullseye as install-browser

RUN apt-get update \
    && apt-get satisfy -y \
    "chromium, chromium-driver (>= 115.0)" \
    && chromium --version && chromedriver --version

......

EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

最後一行的 CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"] 由於要帶入環境變數 $PORT 修改如下:

CMD uvicorn main:app --host 0.0.0.0 --port  $PORT

EXPOSE 8000 也可以改成 EXPOSE 5000 (非必須)。

檢查程式內是否有 hard code PORT

原本的 https://github.com/assafelovic/gpt-researcher/blob/master/main.py 會固定使用 8000 port 要修改。

from backend.server import app
import os
port = int(os.environ.get("PORT", 5000))

if __name__ == "__main__":
    import uvicorn

    uvicorn.run(app, host="0.0.0.0", port=port)

讓 Heroku 知道要去跑 Docker build

建立一個檔案 heroky.yml ,然後很簡單加上以下內容。

build:
  docker:
    web: Dockerfile

最後如何讓他可以一鍵部署 - Using Heroku Deployment

{
    "name": "GPT Researcher",
    "description": "GPT Researcher is an autonomous agent designed for comprehensive online research on a variety of tasks.",
    "env": {
        "TAVILY_API_KEY": {
            "description": "The API key for Tavily service",
            "required": true
        },
        "OPENAI_API_KEY": {
            "description": "The API key for OpenAI service",
            "required": true
        }
    },
    "stack": "container",
    "formation": {
        "web": {
            "quantity": 1
        }
    }
}

這邊有幾個需要注意的,條列說明:

  • env 設定固定的系統參數。 這裡是 TAVILY_API_KEYOPENAI_API_KEY

  • 告訴 Heroku 要跑 container stack :"stack": "container",

  • 最後讓他知道裡面有幾個 services :

     "formation": {
            "web": {
                "quantity": 1
            }
        }
    

大功告成:

還蠻簡單的吧? 其實 Heroku 運算能力還蠻夠的,要部署一些服務也蠻簡單的。

[論文心得] 什麼是 LLaVA 與 LLaVA-Interactive

img

前提:

LLaVA-Interactive 週末又在看微軟發表的新的 POC (還有論文) , LLaVA ( (Large Language-and-Vision Assistant: 可以讓你對一圖片透過 LLM 跟他對話,聽說是 GPT-4V 面向的能力),而微軟更依據這個打造黃金三角:

- Visual Chat: 針對圖像來理解 - Grounded Editing: 針對原有圖像的修改 - Interactive Segmentation: 透過一些互動的修改

你可以針對圖片去修改,重新生成後,再來針對新產生的圖片去理解並且去做 Visual Chat 。

論文, Github 跟 demo 都在 https://llava-vl.github.io/llava-interactive/

註解: LLaVA 是根據這個 repo https://github.com/haotian-liu/LLaVA

相關論文摘要:

LLaVA-Interactive是一個用於多模態人工智能互動的研究原型系統。該系統可以通過接收多模態用戶輸入並生成多模態回應,與用戶進行多輪對話。LLaVA-Interactive不僅僅局限於語言提示,還可以通過視覺提示對人類意圖進行對齊。該系統的開發非常節省成本,因為它結合了LLaVA的視覺聊天、SEEM的圖像分割以及GLIGEN的圖像生成和編輯等三個多模態技能,而無需進行額外的模型訓練。通過展示多種應用場景,我們展示了LLaVA-Interactive的潛力,並激發了未來在多模態互動系統方面的研究。

實際展示:

image-20231103215916067

  • 左邊上傳一張湖邊的圖片

  • 透過畫線的方式去修改(增加鳥跟船)。

  • 然後在右邊可以去根據新產生的圖片去聊天跟理解。