Goで作るAPIサーバーと大規模言語モデル

[Go 言語 Advent Calendar 2023](https://qiita.com/advent-calendar/2023/go) の16日目の記事です。

## tl;dr

SQLのクエリを大規模言語モデルに書かせて、sqlcでGoのコードを生成すると、APIサーバーの実装が楽になるかもしれません

## はじめに

Goを用いてAPIサーバーを実装することがあります。それが（十分大きな）エンジニア組織である場合、内部向けにAPIサーバー用のテンプレートが用意されていたり、内部基盤によって簡単にデプロイできるようになっていることもあります。しかし、そうでない場合はフルスクラッチあるいはそれに近しい形で実装することになります。

この記事では、特に後者の場合や個人開発において、GoのAPIサーバー[^1]をどのように実装するかについて考えてみます。特に、実装のための時間も人的リソースも限られている状況下で、昨今話題になっている大規模言語モデルをうまく利活用できないか考えてみます。

[^1]: この記事では、スキーマに沿ってJSON over HTTP(s)通信を行うサーバーを単に "APIサーバー" と呼びます。あまり知見がなく、一切の調査や検証を行っていないですが、protobuf over grpcな通信を行うAPIサーバーなどの実装にも役立つかもしれません。

## 2023年にGoでAPIサーバーを実装する

「APIサーバーをGoで実装する」という前提があったとしても、考えることは多くあります。例えばフレームワークの技術選定という点でも、

- [Gin](https://github.com/gin-gonic/gin)
- [Echo](https://github.com/labstack/echo)
- [Fiber](https://github.com/gofiber/fiber)
- [Gorilla](https://github.com/gorilla/mux)

など、様々なものが存在します。そのほかにもORMをどうするか、ロギングやメトリクスをどうするか、エラーハンドリングはどうするのかなど、考えることは多いです。また「Goに入ってはGoに従え」という言葉があるように、Go言語特有の書き方やコード設計などの難しさもあります。

言語特有ではなくAPIサーバーの実装という観点で見てみると、APIの設計やDBのスキーマ定義、DIやclean architecture, onion architecutureの実践、DDDといったソフトウェア設計のレイヤーでも考えることは無限にありそうです。

もっとも設計の自由度が高いこと、技術選定の余地があることや、可読性の高いコードを書くために試行錯誤をすること（やそれを取り囲むコミュニティの発展）は素晴らしいことだと感じますが、とはいえ個人や少人数でスピード感を持って開発する際には、少し懸念事項にもなりえます。

## 大規模言語モデル

いきなり話が変わりますが、2023年に話題になったことは何でしょうか。技術に関連した分野で言えば、ChatGPTをはじめとした大規模言語モデルの登場ではないでしょうか。やはり大規模言語モデルの与えた影響は大きく、連日「〇〇という職業は奪われるのではないか」といった話題が出ていた印象です。

大規模言語モデルの登場は、特に人的リソースの不足している個人や小規模な組織にとっては特に大きな助けとなります。Web開発においても設計の叩き台、壁打ち相手から、実際のコードの生成[^2]やレビューまで、幅広く活用されている印象を受けます。

[^2]: コード生成はChatGPTではなく、GitHub Copilotを用いている人も多いのではないでしょうか

かくいう自分も、前述したほぼ全ての工程で大規模言語モデルの導入を行い、非常に助けられています。

### 大規模言語モデルの得意・不得意

とはいえ、1年近く大規模言語モデルを使ってみた感想の一つとして、「現時点では大規模言語モデルは銀の弾丸ではない」とも明確に感じました。つまり全てを大規模言語モデルに任せることはできず、適材適所で用いないと生産性が落ちる、さらに悪い場合は（致命的な）不具合を含んだままリリースしてしまう可能性があるということです。

この点についてもう少し踏み込んで見ると、**大規模言語モデルは多くのコンテキストを踏まえた回答をすることは難しい**[^3]ということを感じます。
逆に言えば、コンテキストを踏まえる必要がない、そこまで多くのコンテキストを踏まえずに回答できる質問は、極めて高精度に回答してくる印象を受けます[^4]。

[^3]: ここでの"コンテキスト"はGoの[context](https://pkg.go.dev/context)ではなく、単に文脈の意
[^4]: あまりにも専門的な内容、倫理的によろしくない内容などは例外でしょうか

具体例を考えてみます。コンテキストを踏まえずに回答できる質問の一つとして、シェルワンライナーの提示が挙げられるでしょう。

![chatgpt.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1073504/696801da-e0d9-5668-08ca-29421ee448c2.png)

多くの場合、このような処理は簡単なスクリプト言語で実装することが多いと思います。要件を満たすawkコマンドをすぐに書ける人は少ない気がします[^5]。個人差はあるでしょうが、少しググったり、ドキュメントを読んだりする必要があるコマンドの操作であれば、ChatGPTに聞いてしまう方が早いことも多いです。同様の考え方で、「〇〇するクエリを教えて」に類する質問も、大規模言語モデルは高精度に回答してくれる印象を受けます。

[^5]: もちろんシェルワンライナーで実現できることがエラいといった主張ではなく、単にこのような使い方が便利だというだけです。

逆に大規模言語モデルが苦手だと感じる、コンテキストを踏まえた回答が必要な質問として、APIサーバーの実装が挙げられるでしょう。「`hoge` は `fuga/piyo.go` に実装されており、、、`fuga/piyo.go` の実装は以下のようになっています。（中略）」といった質問に対して、多くの場合、ChatGPTは適切な回答を返してくれません。提示されるコードがコンパイルできなかったり、明らかな誤りが含まれていたりすることも多々あります。ソフトウェアが大きくなればなるほど、コンテキストを踏まえた回答が必要になることは当たり前ですし、そもそも文字起こしすらしたくないでしょう。

## APIサーバーの実装における大規模言語モデルの導入

ここまでの話をまとめると、

- 大規模言語モデルは適材適所で用いることが重要
- 大規模言語モデルは、非文脈依存な質問に対して高精度に回答してくれる
- 大規模言語モデルは（多くの）コンテキストを踏まえた回答が難しい

ということが言えます。これを踏まえると、[sqlc](https://sqlc.dev)と大規模言語モデルを組み合わせることは、非常に有効な手段であると考えられます。sqlcは、SQLのクエリをプログラミング言語のコードに変換するツールです。例えば以下のようなクエリを書くと、

```sql
-- name: GetProduct :one
SELECT * FROM products WHERE id = ? LIMIT 1;
```

以下のようなGoのコードを生成してくれます。（一部省略）

```go
func (q *Queries) GetProduct(ctx context.Context, id int64) (Product, error) {
    row := q.db.QueryRowContext(ctx, "get_product", id)
    var i Product
    err := row.Scan(&i.ID, &i.Name, &i.Price)
    return i, err
}
```

要するに、SQLクエリを静的型付けされた言語に変換してくれます。先述した通り、SQLクエリは大規模言語モデルが（比較的）高精度に回答してくれるので、これらを組み合わせることで実質的にdbの操作を行うコードが自動生成できることになります[^6]。

[^6]: もちろん「SQLクエリを自分で書いた方が早いし信頼できる！！」みたいな場合は自分で実装すれば良いと思います。ただ、SQLに関しては大規模言語モデルがかなり高精度であるため、移譲できる箇所であるとは思います

あとは、生成されたコードを用いてハンドラーを実装して、ルーティングを設定すれば、APIサーバーの実装は完了です。[^7]このようにすることで、APIサーバーの実装における大規模言語モデルの導入は、非常に有効な手段であると考えられます。参考までに、実際にこの手法を用いて実装したコードを以下に置いておきます。

[^7]: もちろんこれだけで完了するわけではなく、認証や認可、ロギング、メトリクス、エラーハンドリングなどを必要に応じて実装する必要はありますが、、

https://github.com/Okabe-Junya/api-template-go/

## まとめ

この記事では、APIサーバーをGoで実装する際の大規模言語モデルの利活用について考察してみました。Goのコードを直接書かせるのではなく、sqlcのようなソフトウェアを媒介することによって、大規模言語モデルをより効果的に用いることができるのではないでしょうか。

本記事では触れられませんでしたが、OpenAPIスキーマからインタフェースを生成する [OpenAPITools/openapi-generator](https://github.com/OpenAPITools/openapi-generator) のようなものもあるので、これらと組み合わせても面白いかもしれません。