Closed10

GeminiのJS SDKを使ってJSON outputでスキーマを指定させるときに型がおかしく特定のスキーマを書くことができない事象の調査

ashphyashphy

どんな事象が起きる?

GeminiにはJSONで出力させるときにスキーマを指定する機能がある。このスキーマはOpenAPI3.0互換のものが書けるはずだが、TypeScript用の型定義に従うと以下のような型定義がエラーになる。

const generationConfig: GenerationConfig = {
  temperature: 1,
  topP: 0.95,
  topK: 64,
  maxOutputTokens: 16384,
  responseMimeType: "application/json",
  responseSchema: {
    type: FunctionDeclarationSchemaType.OBJECT,
    properties: {
      chapters: {
        type: FunctionDeclarationSchemaType.ARRAY,
        items: {
        // オブジェクト リテラルは既知のプロパティのみ指定できます。'items' は
        // 型 'FunctionDeclarationSchema' に存在しません。ts(2353)
          type: FunctionDeclarationSchemaType.OBJECT,
          properties: {
            title: {
              type: FunctionDeclarationSchemaType.STRING,
            },
          },
        },
      },
    },
    required: ["chapters"],
  },
};

これは以下のスキーマ定義であり

{
  "type": "object",
  "properties": {
    "list": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          }
        }
      }
    }
  },
  "required": ["list"]
}

以下のような応答を期待している。

{
  "list": [
    { "name": "Tokyo" },
    { "name": "New York" },
    { "name": "London" },
    { "name": "Paris" },
    { "name": "Sydney" }
  ]
}

よくあるレスポンスだと思うが、OBJECTの下になぜかARRAYを書くことができない。

ashphyashphy

型定義はどうなっているのか?

まずresponseSchemaの型定義は以下のようになっている。

export interface ResponseSchema extends Schema {}
export interface Schema {
  type?: FunctionDeclarationSchemaType;
  format?: string;
  description?: string;
  nullable?: boolean;
  items?: FunctionDeclarationSchema;
  enum?: string[];
  properties?: { [k: string]: FunctionDeclarationSchema };
  required?: string[];
  example?: unknown;
}

propertiesにはFunctionDeclarationSchemaを書くことになっており

export interface FunctionDeclarationSchema {
  type: FunctionDeclarationSchemaType;
  properties: { [k: string]: FunctionDeclarationSchemaProperty };
  description?: string;
  required?: string[];
}

ここにはitemsがないためARRAYを書くことができない定義となっている。

ashphyashphy

スキーマが設定できる物であるのかを確かめる

スキーマはOpenAPI互換と書かれているが、実際には設定できない何かしらの制約が存在し、型定義で絞っている可能性がある。直接APIのリクエストを投げてみて通るかどうか見てみる。

curlしてもいいが、認証通すのが面倒なのでGoogle AI Studioを使って投げてみたが、ちゃんと投げることができて想定通りの応答が返ってくることが確認できた。

ashphyashphy

他の言語ではどう書く?

試しにgoで書いてみた。

model.GenerationConfig = genai.GenerationConfig{
	ResponseMIMEType: "application/json",
	ResponseSchema: &genai.Schema{
		Type: genai.TypeObject,
		Properties: map[string]*genai.Schema{
			"list": {
				Type: genai.TypeArray,
				Items: &genai.Schema{
					Type: genai.TypeObject,
					Properties: map[string]*genai.Schema{
						"name": {
							Type: genai.TypeString,
						},
					},
				},
			},
		},
	},
}

書けた。ならTypeScriptでも書けていいだろう。

ashphyashphy

コードの由来を確かめる

SDKによくあることとして、多数の言語用クライアントを生成しなければならないため、一つのAPI定義からクライアントを自動生成していることがる。なのでSDKのコードを書き換えるPRを出してみ意味がなかったりするのでどこで作られた物か調べる必要がある。

機能が追加されたのはAdd responseSchema to generationConfig by lahirumaramba · Pull Request #158 · google-gemini/generative-ai-js。特に自動生成された形跡はなさそう?

元々Function Calling用のスキーマ定義を流用して書き換えた際にミスったような気配がする。

ashphyashphy

直す場所は分かったが、どう直すかが問題である。コードを読むと他にも書けないようなパターンが結構ある。なんでこうなるかというと何が書けるかという仕様が明言されていないためだと思われる。

公式ドキュメントにはJSON schemaとだけ書かれており、SDKのドキュメントには

a select subset of an OpenAPI 3.0 schema object.

とだけある。

Go SDKのSchema定義を見るとJSにはないプロパティがたくさん定義されており中の人も適当に書いてるのではないか疑惑がある。

内部の仕組みを考えるとふわっとなんでも受付そうな感じではある。

ashphyashphy

Python での型定義を見て仕様の参考にしてみる

Protocol Buffersで定義されている

https://github.com/google-gemini/generative-ai-python/blob/main/google/generativeai/types/generation_types.py#L88C22-L88C35

ここを見ていて思ったが、API定義はどこかにあるんじゃないかと思ったので探してみた。
あった。

https://cloud.google.com/vertex-ai/docs/reference/rest/v1/Schema

この定義ならObjectの下にArrayも書ける。

gRPCの定義はこっち

https://cloud.google.com/vertex-ai/docs/reference/rpc/google.cloud.aiplatform.v1beta1#schema

このスクラップは4ヶ月前にクローズされました