🐷

Azure OpenAI の Function calling を Golang で試す

2023/07/21に公開

はじめに

Azure の OpenAI にもようやく Function calling が来たので Golang で試してみました。

https://techcommunity.microsoft.com/t5/azure-ai-services-blog/function-calling-is-now-available-in-azure-openai-service/ba-p/3879241

OpenAI のドキュメント

https://platform.openai.com/docs/guides/gpt/function-calling

Function calling そのものに関しては以下の記事が参考になります。

https://dev.classmethod.jp/articles/understand-openai-function-calling/

コード

以下コードです。ちょっと長いので折りたたんでいます。

やっていることとしては以下の通りです。

  1. ファイルからテキストを読み込む
  2. Function calling でテキストの要約と製品名の抽出を行う
  3. レスポンスを出力する

複数の関数の指定や、結果を使った再呼び出しのようなことはやっていません。

main.go
package main

import (
	"context"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"os"

	openai "github.com/sashabaranov/go-openai"
	"github.com/sashabaranov/go-openai/jsonschema"
)

func main() {
	// Read text from file. Filepath is given as first argument
	filepath := os.Args[1]
	text, err := getTextFromFile(filepath)
	if err != nil {
		log.Fatal(err)
	}

	config := openai.DefaultAzureConfig(os.Getenv("AOAI_KEY"), os.Getenv("AOAI_ENDPOINT"))
	config.APIVersion = "2023-07-01-preview"
	config.AzureModelMapperFunc = func(model string) string {
		modelmapper := map[string]string{
			"gpt-3.5-turbo-16k-0613": "tsunomur-gpt-35-turb-16k",
		}
		if val, ok := modelmapper[model]; ok {
			return val
		}
		return model
	}

	client := openai.NewClientWithConfig(config)

	resp, err := client.CreateChatCompletion(
		context.Background(),
		openai.ChatCompletionRequest{
			Model: openai.GPT3Dot5Turbo16K0613,
			Messages: []openai.ChatCompletionMessage{
				{
					Role: openai.ChatMessageRoleUser,
					Content: fmt.Sprintf(`
					Please provide as Japanese, don't use English.必ず日本語にしてください。
					Provide two sentence summary of the following text, emphasizing the most impactful new feature and main service, product if this text including new feature release.
					Keep the summary extremely brief, ideally within 200 characters. Please translate into Japanese.
					"%s"
					Output:`,
						text),
				},
			},
			Functions: []openai.FunctionDefinition{
				{
					Name: "summarize",
					Parameters: &jsonschema.Definition{
						Type: jsonschema.Object,
						Properties: map[string]jsonschema.Definition{
							"description": {
								Type:        jsonschema.String,
								Description: "summary of contents",
							},
							"product": {
								Type:        jsonschema.Array,
								Description: "list of service names",
								Items: &jsonschema.Definition{
									Type: jsonschema.String,
								},
							},
						},
						Required: []string{"description", "product"},
					},
				},
			},
		},
	)
	if err != nil {
		log.Fatal(err)
	}

	type SummarizedArgs struct {
		Description string   `json:"description"`
		Product     []string `json:"product"`
	}
	log.Println(resp.Choices[0].FinishReason)
	fmt.Printf("%#v\n", resp)
	var summarizedArgs SummarizedArgs
	err = json.Unmarshal([]byte(resp.Choices[0].Message.FunctionCall.Arguments), &summarizedArgs)
	if err != nil {
		log.Println(err)
	}
	// Join products with comma
	var p string
	for _, v := range summarizedArgs.Product {
		p += v + ","
	}
	fmt.Printf("* Summary: %s\n* Products: %s\n", summarizedArgs.Description, p)
}

// getTextFromFile reads text from file
func getTextFromFile(filename string) (string, error) {
	// ファイルを読み込む
	b, err := ioutil.ReadFile(filename)
	if err != nil {
		return "", err
	}

	// ファイルの内容を文字列に変換する
	str := string(b)

	return str, nil
}

結果:

$ go run main.go test2.txt
2023/07/21 10:40:49 function_call
openai.ChatCompletionResponse{ID:"chatcmpl-7ehqwPsSi8EMkew8Gkdk3GR2UMXWu", Object:"chat.completion", Created:1689936046, Model:"gpt-35-turbo-16k", Choices:[]openai.ChatCompletionChoice{openai.ChatCompletionChoice{Index:0, Message:openai.ChatCompletionMessage{Role:"assistant", Content:"", Name:"", FunctionCall:(*openai.FunctionCall)(0xc000607200)}, FinishReason:"function_call"}}, Usage:openai.Usage{PromptTokens:2475, CompletionTokens:171, TotalTokens:2646}}
* Summary: Azure OpenAIサービスでは、gpt-35-turboとgpt-4の最新バージョンによる関数呼び出しが利用可能になりました。この機能は、API呼び出しとデータの構造化出力を提供し、新たなシナリオを可能にします。関数呼び 出しは、データの取得、APIやツールとの統合、構造化された出力の作成など、さまざまな用途に活用できます。
* Products: Azure OpenAI Service,

テキストには、Function calling の発表のブログ(link)をブラウザ上でコピーして貼り付けた結果が入っています。それっぽい感じのサマリーと製品名が入っていますね。
※上2行はデバッグ用の出力です。

ポイント

SDK

残念ながら Golang の SDK は、OpenAI、Azure ともに公式のものはありません。
Open AI のドキュメントに Community libraries として紹介されているものがあり、Azure OpenAI でも利用できるのでそちらを使いました。

https://github.com/sashabaranov/go-openai

API 呼び出しの設定

Function calling を使うには、API のバージョンを 2023-07-01-preview に設定する必要があります。また、モデルは 0613 が付いたものを使用します。
以前のバージョンを指定していると、以下のようなエラーになります。

error, status code: 404, message: Unrecognized request argument supplied: functions

また、これは Function calling 特有の話しではありませんが、モデル名とデプロイ名が違う場合はマッピングする必要があります。

config := openai.DefaultAzureConfig(os.Getenv("AOAI_KEY"), os.Getenv("AOAI_ENDPOINT"))
config.APIVersion = "2023-07-01-preview"
config.AzureModelMapperFunc = func(model string) string {
    modelmapper := map[string]string{
        "gpt-3.5-turbo-16k-0613": "tsunomur-gpt-35-turb-16k",
    }
    if val, ok := modelmapper[model]; ok {
        return val
    }
    return model
}

モデル名、デプロイ名は、Azure OpenAI Studio で確認できます。

また、モデル名はリクエスト時に指定する必要があるので忘れないようにそちらも合わせておきましょう。

resp, err := client.CreateChatCompletion(
    context.Background(),
    openai.ChatCompletionRequest{
        Model: openai.GPT3Dot5Turbo16K0613, //<------- この部分
        Messages: []openai.ChatCompletionMessage{
            {
                Role: openai.ChatMessageRoleUser,

Function calling の定義

今回の肝の部分です。Parameters に JSON のスキーマを渡します。
以下の例では、簡単のために JSON スキーマをgithub.com/sashabaranov/go-openai/jsonschemaで定義していますが、マーシャルできれば他のライブラリを使うこともできるようです。

Required には必須のパラメータを指定すると、可能な限り値を入れて返してくれます。Required であっても OpenAI のお気持ち次第で値が入っていない場合もあります。

Functions: []openai.FunctionDefinition{
    {
        Name: "summarize",
        Parameters: &jsonschema.Definition{
            Type: jsonschema.Object,
            Properties: map[string]jsonschema.Definition{
                "description": {
                    Type:        jsonschema.String,
                    Description: "summary of contents",
                },
                "product": {
                    Type:        jsonschema.Array,
                    Description: "list of service names",
                    Items: &jsonschema.Definition{
                        Type: jsonschema.String,
                    },
                },
            },
            Required: []string{"description", "product"},
        },
    },
},

レスポンスの取得

Function calling で返ってくる結果は、JSON です。struct にアンマーシャルしてあげると扱いやすくなります。

type SummarizedArgs struct {
    Description string   `json:"description"`
    Product     []string `json:"product"`
}
var summarizedArgs SummarizedArgs
err = json.Unmarshal([]byte(resp.Choices[0].Message.FunctionCall.Arguments), &summarizedArgs)
if err != nil {
    log.Println(err)
}
Microsoft (有志)

Discussion