VertexAI(Gemini pro)のAPIでたまに [IndexError: list index out of range]が発生する

Gemini ProをAPIで色々叩いていたら初歩的なエラーに遭遇したので備忘録。
以下のAPIを使用しています。

https://cloud.google.com/vertex-ai/docs/python-sdk/use-vertex-ai-python-sdk?hl=ja

## 発生源を特定する
以下のページの音声をGCPのSpeech-To-Textでテキスト化を行った文章をGeminiに読ませて要約させていました。

https://aozoraroudoku.jp/voice/rdp/rd1163.html

テキスト化された文章

> "夢 の 九 作 作  ペン と インキ  ペンサキ が 陰気 に こう 言い まし た  お前 ぐらい 嫌 な もの は ない  私 が いくら 金 の 異服 を 着 "
                                "て い て も  お前 は すぐ に 錆び さし て 役 に 立た なく し て しまう  私 は お前 みたい な もの 大嫌い さ  インキ は こう 答え まし "
                                "た  ペン は さびる の が 役目 じゃ ない  陰器 は なくなる の が 務め じゃ ない  一緒 に なっ て 字 を 書く の が 役目 さ  さびる の が "
                                "嫌 なら 鉄 に 生まれ て こ ない 方 が いい じゃ ない か  陰器 が 嫌 なら 何 だっ て ペン に 生まれ て き た ん だ え"

すると3回に1回くらいの頻度で表題のエラーが発生。
Pythonのコードはこちら

```python
    def execute(self, request: str) -> str:
        responses = self.model.generate_content(
            self.prompt + request,
            generation_config={
                "max_output_tokens": 2048,
                "temperature": 0.4,
                "top_p": 0.4,
                "top_k": 32
            },
            safety_settings={
                generative_models.HarmCategory.HARM_CATEGORY_HATE_SPEECH: generative_models.HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE,
                generative_models.HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT: generative_models.HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE,
                generative_models.HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT: generative_models.HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE,
                generative_models.HarmCategory.HARM_CATEGORY_HARASSMENT: generative_models.HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE,
            },
        )

        contents = []
        for response in responses:
            contents.append(response.text)

        text = "".join(contents)

        return text
```

ほぼVertexAIのGUIページで自動出力されているコードまんまです。
list index out of rangeが出ている部分は以下

```python
contents.append(response.text)
```

エラー名と、Python不慣れなのもあって配列の扱い間違えてるかなとも思ったのですがどうも合ってるっぽい。
他言語でいうforeachっぽく回しているから勉強したてにありがちなインデックスの外側を指定してるのもないっぽい。

もう少し中を見てみるとモジュールの中のtextの中でエラーが発生していることを発見
```python
    @property
    def text(self) -> str:
        if len(self.candidates) > 1:
            raise ValueError("Multiple candidates are not supported")
        return self.candidates[0].text　# ここでlist index out of range
```

ああ、なるほど？
発生元はわかったけど、なんでたまにしか発生しないんだ？

## 原因判明

ということでGUIのほうで同じ文章を送信しまくっていると原因判明。

![スクリーンショット 2024-03-27 024017.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/855584/79713f7b-d9b3-f33c-92e2-1c8917df11ea.png)

え？
入力された文章をもう一度よく見てみる。

> "嫌 なら 鉄 に 生まれ て こ ない 方 が いい じゃ ない か  陰器 が 嫌 なら 何 だっ て ペン に 生まれ て き た ん だ え"

インキという単語をSpeech-To-Textが陰器とテキスト化しているのが原因っぽい。
だったらレスポンスが生成できなかった用のフィールドがこういうAPIには用意されているはず。

https://cloud.google.com/python/docs/reference/aiplatform/latest/vertexai.generative_models.GenerationResponse

何も書いてないので、REST APIのほうも見てみる。

https://cloud.google.com/vertex-ai/docs/generative-ai/model-reference/gemini?hl=ja&_gl=1*18k3yf3*_ga*ODA5MTUxMjkwLjE3MTExNzk1ODE.*_ga_WH2QY8WWF5*MTcxMTQ2NTgyMi45LjEuMTcxMTQ3MjU0OC4wLjAuMA..&_ga=2.115095950.-809151290.1711179581

こっちにはちゃんとblockedというフィールドがあって、これで判別できるみたい。
なんとなくそれっぽく書いてみるが当たり前だけど使えない。

```python
for response in responses:
    if not response.blocked:
        contents.append(response.text)
```

レスポンスを作っているクラスを見ているとcandidatesというフィールドもあったので、中を見てみる。

```python
for response in responses:
    print(response.candidates)
```

```
[]
```

何も入っていない。
text()はここから拾えなくてエラーになってるんだからそりゃそうだ。
何も入っていないことを条件としていいのかはなんとも言えないので、例外処理を追加することにした。

```python
try:
    for response in responses:
        if response.text:
            contents.append(response.text)
except Exception as e:
    print("This article cannot be summarized.")
    today = datetime.date.today()
    today_str = today.strftime('%Y%m%d')
    with open(f'../out/{today_str}_error_sentence.txt', 'w', encoding='utf-8') as f:
        f.write(request)
```

とりあえず不適切な単語が入っている文章を出力してあげてアプリ自体は落ちないようにした。

## 感想
エラー用のフィールドが用意されていないとは思えないので、自分がドキュメントを読み込めていないだけな気がします。
同じ文章でもレスポンスを作ってくれるときもあるので、リクエストで判別しているわけではなさそうという予想。
temperatureとかを調整すればある程度は回避できそうなような気もします。
それが良いかどうかは置いておくとして。