👋

[Claude 3]生成AIをアプリに組み込む際のプラクティス

2024/04/17に公開

はじめに

こんにちは、クラウドエース バックエンドエンジニアリング部 所属の 西麻布チョヤッ と申します (´・ω・`)ノ

生成AI周りの情勢として既に業界を問わず業務の中での活用が浸透しており、システムに組み込む上でLLM毎の推論能力を評価・検証しているフェーズのように見受けられます。

個人的にも生成AIを活用した習作アプリケーションを作成したので、開発の上で得た知見を共有できればと思います。

前段:作ったもの

描いた絵を評価君 なるクロスプラットフォームのデスクトップアプリケーションで、
アップロードした画像ファイルを生成AIモデルに評価してもらうアプリです。 評価は以下の内容になります。

  • タイトル:アップロードされた画像の内容を言語化したもの。
  • 絵の上手さ:画像のデッサンの整合性や構図等を考慮した上手さを10点満点で定量評価したもの。
  • コメント:アップロードされた画像に対しての評価内容や印象等のコメント。

alt

例えばこれはペイントで2秒で書いたずんだもんなのですが、

alt

[作品を評価してもらう]ボタンを押下してアップロードすると...

alt

えええええええ!非常に粗雑!粗雑て!めちゃめちゃ一生懸命描いたのに!...このような機能のアプリです。

インストーラーは以下で配布していますので、試したい方がいたら免責事項に目を通した上でお試いただければと思います。
(macOS版はビルド環境が用意できなかったので配布しておりません。)
https://github.com/KabiTaro/ArtJudge/releases

(注意)このアプリではClaude APIキーをユーザーが自分で用意して設定する必要がありますが、APIキーをLocalStorageに平文で保存しているセキュアでない管理をしており窃取のリスクがあるので、一通り試した後はClaudeコンソールから使用したAPIキーを無効化する等してください。

アーキテクチャ

描いた絵を評価君の採用技術スタックとしてはざっくり以下の構成になってます。

  • React 18.2.0
  • typescript 5.2.2
  • Electron 26.2.1
  • Python 3.10.12 (Flask 3.0.2)
  • Claude API (Claude 3 haiku)

Electron+(Vue.js/Angular/React)+ローカルWebサーバー(python)というVOICEVOXエディタのようなアーキです。
それぞれの役割は以下になります。

  • React+Electron :フロントエンド領域に相当,ユーザーの操作を受け付ける
  • Flask:バックエンド領域に相当,フロントエンドから渡された画像データをClaudeに受け渡したり結果を整形したりする
  • Claude 3 haiku:画像認識・評価を担当

ダイアグラム図にすると以下のような流れになります。(エラーフローは図式化していません)

Claude 3 haikuへのプロンプトリクエストはフロントエンドで完結する観点で、
別途Flask Webサーバーを介する合理性は無いですが、このアーキテクチャでの開発をなんとなく試したかったというところもあります。

上記のフローの中での、生成AIの出力周りで得られた気づきの共有 が今回の記事の主題になります。アプリケーションに生成AI出力を組み込む時はこんな事を意識しないといけないかもね~~~~的な内容です。
Claudeとは何ぞや,画像認識機能,構造化データ(json)出力の3点に絞ってお話させてもらえればと。掲載しているコードは全てpythonのものなので予めご了承ください。

Claudeについて

ClaudeはAnthropic社によって提供されているLarge Language Model(LLM)で、Claude 3はClaudeシリーズの中でも最新のモデルです。
以下のモデルが提供されています。上から順に高性能・高コストです。

  • Claude 3 Opus
  • Claude 3 Sonnet
  • Claude 3 Haiku

Claude 3シリーズは、構造化データを出力するタスクが得意とされており、さらに画像認識機能も導入されました。
描いた絵を評価君もまさにその性質に着目して開発した次第です。

余談ですが、Anthropic社はGoogleとパートナーシップの締結を発表しています。(2023/02)、その繋がりからかClaude 3はVertex AI Model Gardenのサードパーティーモデルとしても提供されており、Vertex AIを介してモデルにアクセスする事も可能です。

画像認識機能

公式ドキュメント

Claude3の画像認識機能は文字通り、プロンプトとしてアップロードした画像に対して分析する事ができる機能です。

描いた絵を評価君において、画像認識機能はアップロードした画像の以下の内容の評価に使用しています。

  • タイトル:アップロードされた画像の内容を言語化したもの。
  • 絵の上手さ:画像のデッサンの整合性や構図等を考慮した上手さを10点満点で定量評価したもの。
  • コメント:アップロードされた画像に対しての評価内容や印象等のコメント。

alt

実際に絵を評価するプロンプトとしては以下のようになっており、評価するタスクを与えるシステムプロンプト、画像を提供するユーザープロンプトを組み合わせています。

    message = client.messages.create(
        model="claude-3-haiku-20240307",
        max_tokens=4000,
        temperature=0,
        system="""
                貴方は与えられた絵の上手さを評価する必要があります。以下のタスクを実施してください。
                <task>
                絵の上手さを評価するにあたり、評価を次のフォーマットのjson形式で返してください
                <json_format>
                {
                "title" : string(絵のタイトル。日本語である必要があります。),
                "drawing_score": int(1)(絵のデッサンや構図等を考慮した上手さ。1から10まで。),
                "comment": string(評価のコメントで、drawing_scoreの採点根拠、及び改善点が必要な点を多めに指摘してください。日本語である必要があります。)
                }
                </json_format>
                ...
                </task>
                """,
        messages=[
        {
            "role": "user",
            "content": [
            {
                "type": "image",
                "source": {
                "type": "base64",
                "media_type": media_type,
                "data": image_data
                }
            }
            ]
        },
        ...
        ]
    )

ただし、実際にアプリケーションにこの画像認識機能を組み込む事を検討した場合、以下の制約を考慮する必要があります。

  • 画像中の人物を識別することはできず、拒否する
  • 小さい画像や解像度の低い画像ではパフォーマンスが低下する。
  • 時計の時間を読み取る等の空間的推論を必要とするタスクは苦手
  • 画像の内のオブジェクト数を数える場合に数を間違える事がある。

低解像度云々に関しては想像しやすいと思いますが、他の制約に関してどういうことなのか実際の応答を踏まえて検証します。

画像中の人物を識別することはできず、拒否する

alt

出典:1%の需要に応えた看護師フリー写真素材サイト スキマナース様より「『明太子を手の甲に2つ載せる看護師」

プロンプト:この画像の人物は誰ですか

応答:申し訳ありませんが、この画像には人物が写っているため、その人物を特定することはできません。しかし、画像には白い制服を着た若い女性が写っており、笑顔で腕を組んでいる様子が見られます。この女性は医療施設の中にいるようで、おそらく看護師や医療従事者の役割を担っているのだと思われます。(Claude 3 Haiku)

時計の時間を読み取る等の空間的推論を必要とするタスクは苦手

alt

出典:1%の需要に応えた看護師フリー写真素材サイト スキマナース様より「『春のオペまつり』の告知を勝手に貼る看護師」

プロンプト:ピンク色の紙にはどのような内容が書いてありますか

応答:画像には、ピンク色の紙に「春のオープンキャンパス」と書かれています。おそらく、学校や教育機関が主催するオープンキャンパスの告知ポスターだと思われます。ポスターには学生たちの写真も掲載されており、オープンキャンパスの雰囲気を伝えているようです。(Claude 3 Haiku)

画像の内のオブジェクト数を数える場合に数を間違える事がある

alt

出典:1%の需要に応えた看護師フリー写真素材サイト スキマナース様より「腕時計を10個付けてる看護師」

プロンプト:この人物がつけている腕時計の数を答えて下さい

応答:この画像には、腕に複数の腕時計を着けた人物が写っています。正確に数えると、この人物は合計で6つの腕時計を着けています。(Claude 3 Haiku)

上記の出力結果は全て一番性能が劣っているClaude 3 Haikuによるものなのでモデルの性能を上げる事で解決する可能性はありますが、上記の特性はClaude 3シリーズ共通の制約であり性能を上げる事が本質的な解決にならない事から、ユースケースが正確な人物の識別・空間的推論を含むタスクの場合にはClaude3の画像解析は現状では適さないといえます。
描いた絵を評価君のように分析結果の不確実性をある程度許容する余地がある場合に活用できる機能でしょう。

https://docs.anthropic.com/claude/docs/vision#limitations

構造化データ(json)出力

公式ドキュメントに記載の通り、プロンプトで指定する事でjsonを始めとした構造化データを出力する事ができます。

import anthropic

message = client.messages.create(
    model="claude-3-haiku-20240307",
    max_tokens=4000,
    temperature=0,
    messages=[
        {
            "role": "user",
            "content": [
                {
                    "type": "text",
                    "text": "うさぎ、サル、パンダというプロパティを持つjson形式の応答を返して"
                }
            ]
        }
    ]
)
print(message.content[0].text)

###output↓###
以下のようなJSON形式の応答を返します:
{
"うさぎ": {
"特徴": "長い耳と短い尻尾を持つ小動物",
"生息地": "世界各地"
},
"サル": {
"特徴": "四肢が長く、木によじ登る能力がある",
"生息地": "アフリカやアジアの熱帯地域"
},
"パンダ": {
"特徴": "黒白の毛皮と丸い体型が特徴的",
"生息地": "中国の一部地域"
}
}

上記のようにただ単に「jsonで返して」とプロンプトに書くだけでも出力はされます。

ただし、アプリケーションで生成AIで出力した構造化データを扱う上で肝要であるのでは、
必ずしも規格に則ったデータが出力されるとは限らない、という点です。
例えば上記の出力結果には 以下のようなJSON形式の応答を返します: という余分な文字列が入っているのがわかると思います。
もう一つ例を出します。

message = client.messages.create(
    model="claude-3-haiku-20240307",
    max_tokens=4000,
    temperature=0,
    messages=[
        {
            "role": "user",
            "content": [
                {
                    "type": "text",
                    "text": """
                    本の情報をjson形式で出力して欲しい、以下のキーを持っている。
                    
                    titile:string
                    author:string
                    price:int
                    description:string
                    """
                }
            ]
        }
    ]
)
print(message.content)

###output↓###
  {
    "title": "Book 1",
    "author": "Author 1",
    "price": 19,
    "description": "This is the description of Book 1.
  }

一見正しい規格に則ったjson形式のように見えますが、よくよく見るとdescriptionのvalueの最後がダブルクォートで括られていない事がわかると思います。
それ即ち、この出力結果の文字列をjsonにデコードしようものなら例外がスローされる事になります。

つまりは、プロンプトをチューニングしてアプリケーションが期待する応答が出力されるようにする必要がある、という事です。
いわゆるプロンプトエンジニアリングと呼ばれるものです。

プロンプトエンジニアリング編

以下は「描いた絵を評価君」で実際に利用しているプロンプトです。

try:
    message = client.messages.create(
        model="claude-3-haiku-20240307",
        max_tokens=4000,
        temperature=0,
        system="""
                貴方は与えられた絵の上手さを評価する必要があります。以下のタスクを実施してください。
                <task>
                絵の上手さを評価するにあたり、評価を次のフォーマットのjson形式で返してください
                <json_format>
                {
                "title" : string(絵のタイトル。日本語である必要があります。),
                "drawing_score": int(1)(絵のデッサンや構図等を考慮した上手さ。1から10まで。),
                "comment": string(評価のコメントで、drawing_scoreの採点根拠、及び改善点が必要な点を多めに指摘してください。日本語である必要があります。)
                }
                </json_format>

                以下は出力例です。
                <example id="1">
                {
                "title" : "川に佇む男性",
                "drawing_score": 5,
                "comment": "5点である理由はデッサンが崩れているように見えるからです。もう少し人体のプロポーションを勉強しましょうか。"
                }
                </example>
                
                <example id="2">
                {
                "title": "踊る妖精",
                "drawing_score": 8,
                "comment": "8点である理由はデッサンが破綻していない為です。"
                }
                </example>
                </task>
                """,
        messages=[
        {
            "role": "user",
            "content": [
            {
                "type": "image",
                "source": {
                "type": "base64",
                "media_type": media_type,
                "data": image_data
                }
            }
            ]
        },
        {
            "role": "assistant",
            "content": [
            {
                "type": "text",
                "text": "{"
            }
            ]
        }
        ]
    )
except anthropic.APIStatusError as ase:
    return handle_api_exception(ase, status_code=ase.status_code)

except anthropic.APIResponseValidationError as arve:
    return handle_api_exception(arve, status_code=arve.status_code)

except anthropic.APIError as aae:
    return handle_api_exception(aae, status_code=aae.status_code)

except Exception:
    return jsonify({'error':"未知のエラーです。"}),500

if len(message.content) == 0:
    return jsonify({'error':"有効な評価が生成されませんでした。"}),400

# 生成AIで返された回答が有効なjsonかどうか検証する
try:
    # 制御文字を置換
    json_str = re.sub(r'[\x00-\x1F\x7F]', '', "{" +message.content[0].text)
    result_json = json5.loads(json_str)
except json5.JSONDecodeError:
    return jsonify({'error':"有効なjson形式の評価が生成されませんでした。"}),400

上記で使っているテクニックで特に有効なものを二つ紹介します。

ロールプロンプティングでアシスタントロールにプレフィル文字列「{(波カッコ)」を指定する

ロールプロンプティングは生成AIの出力に役割を持たせて文脈を補完させるテクニックです。

例として、以下は特にロールプロンプティングを 使用していない ケースです。

message = client.messages.create(
    model="claude-3-haiku-20240307",
    max_tokens=1000,
    temperature=0,
    messages=[
        {
            "role": "user",
            "content": [
                {
                    "type": "text",
                    "text": "貴方は両生類の仲間を列挙する必要があります。"
                }
            ]
        }
    ]
)
print(message.content[0].text)

### output ### 
# はい、わかりました。両生類の主な仲間は以下の通りです。

# 1. カエル類
#    - アマガエル
#    - ツチガエル

# 2. サンショウウオ類
#    - ニホンサンショウウオ


# 3. イモリ類
#    - ニホンイモリ
#    - ヒダイモリ


# 4. トカゲモドキ類
#    - ニホントカゲモドキ
#    - ヒダトカゲモドキ

# これらが主な両生類の仲間となります。それぞれ特徴的な外見や生態を持っています。

次にロールプロンプティングを 使用した ケースです。

message = client.messages.create(
    model="claude-3-haiku-20240307",
    max_tokens=1000,
    temperature=0,
    messages=[
        {
            "role": "user",
            "content": [
                {
                    "type": "text",
                    "text": "貴方は両生類の仲間を列挙する必要があります。"
                }
            ]
        },
        {
            "role": "assistant",
            "content": [
                {
                    "type": "text",
                    "text": "イモリ,カエル,"
                }
            ]
        }
    ]
)
print(message.content[0].text)

### output ### 
# サンショウウオ,オタマジャクシ,アカガエル,ニホンアマガエル,ツチガエル,ヒキガエル,オオサンショウウオ,ヒダサンショウウオ主な両生類の仲間です。

両生類の名前の列挙の仕方に変化が発生したのがわかるとと思います。assistantロールのプロンプトに イモリ,カエル, と指定する事により
生成AIが出力のフォーマットを判断し、補完する形で別解を出力する結果となりました。
この性質を利用する形で、assistantロールのプロンプトに「{(波カッコ)]を与えた場合の出力を見てみます。

message = client.messages.create(
    model="claude-3-haiku-20240307",
    max_tokens=1000,
    temperature=0,
    messages=[
        {
            "role": "user",
            "content": [
                {
                    "type": "text",
                    "text": "うさぎ、サル、パンダというプロパティを持つjson形式の応答を返して"
                }
            ]
        },
        {
            "role": "assistant",
            "content": [
                {
                    "type": "text",
                    "text": "{"
                }
            ]
        }
        
    ]
)
print(message.content[0].text)

### output ###
  "うさぎ": {
    "特徴": "長い耳と短い尻尾を持つ小型の哺乳類",
    "生息地": "世界各地"
  },
  "サル": {
    "特徴": "手足が長く、木登りが得意な霊長類",
    "生息地": "アフリカやアジアの熱帯雨林"
  },
  "パンダ": {
    "特徴": "黒白の毛皮と丸い体型が特徴的な大型の熊",
    "生息地": "中国の山地"
  }
}

ロールプロンプティングを使わない場合では以下の内容の出力でした。

以下のようなJSON形式の応答を返します:
{
"うさぎ": {
"特徴": "長い耳と短い尻尾を持つ小動物",
"生息地": "世界各地"
},
"サル": {
"特徴": "四肢が長く、木によじ登る能力がある",
"生息地": "アフリカやアジアの熱帯地域"
},
"パンダ": {
"特徴": "黒白の毛皮と丸い体型が特徴的",
"生息地": "中国の一部地域"
}
}

余計な文字列が出力されなくなった事で期待する出力が得られました。

この構造化データの出力タスクを生成AIに依頼する場合に、ロールプロンプティングでプレフィル文字列を指定するプラクティスは公式ドキュメントで紹介されておりロールプロンプティングでプレフィル文字列を指定するプラクティスは公式ドキュメントで紹介されており、
大きく精度が向上する点で必須のテクニックと言えます。

出力の例を提示する

プロンプトの中に出力の例を提示する事で精度を上げるプラクティスです。

message = client.messages.create(
    model="claude-3-haiku-20240307",
    max_tokens=1000,
    temperature=0,
    messages=[
        {
            "role": "user",
            "content": [
                {
                    "type": "text",
                    "text": """
                    うさぎ、サル、パンダというプロパティを持つjson形式の応答を返して。内容としては学術的な観点で書いて。
                    
                    <example id="1">
                    {
                         "うさぎ": {
                             "特徴":"耳が長いです"
                             "説明":"哺乳類の動物です"
                         },
                         "サル":   {
                             "特徴":"人と似ています"
                             "説明":"哺乳類の動物です"
                         },
                         "パンダ":   {
                             "特徴":"希少です"
                             "説明":"哺乳類の動物です"
                         },
                    }
                    </example>
                    
                    <example id="2">
                    {
                         "うさぎ": {
                             "特徴":"足が速い"
                             "説明":"ピーターラビットのモデルです"
                         },
                         "サル":   {
                             "特徴":"群れで生活します"
                             "説明":"日光で親しまれている"
                         },
                         "パンダ":   {
                             "特徴":"白黒"
                             "説明":"数少ない"
                         },
                    }
                    </example>
                    """
                }
            ]
        },
        {
            "role": "assistant",
            "content": [
                {
                    "type": "text",
                    "text": "{"
                }
            ]
        }
        
    ]
)
print(message.content[0].text)

### output ###
    "うさぎ": {
        "特徴": "長い耳と短い尻尾を持つ小型の哺乳類です。敏捷な動きと高い繁殖力が特徴的です。",
        "説明": "うさぎは、ネズミ目ウサギ科に属する動物で、世界中に広く分布しています。食草性で、主に草や葉を食べます。社会性が高く、群れで生活することが多いです。ペットとしても人気があり、飼育されています。"
    },
    "サル": {
        "特徴": "人間に最も近縁な哺乳類の一つで、高度な知性と社会性を持っています。",
        "説明": "サルは、霊長目に属する動物で、アフリカやアジアを中心に世界各地に生息しています。様々な種類のサルが存在し、チンパンジーやゴリラなどの大型類から、コロブス属やマカク属などの小型類まで多様です。社会性が高く、複雑な行動パターンを示すことが知られています。"
    },
    "パンダ": {
        "特徴": "白と黒の特徴的な毛色を持ち、竹を主な餌とする希少な哺乳類です。",
        "説明": "パンダは、クマ目クマ科に属する動物で、中国の一部の地域にのみ生息しています。食性が特殊で、ほとんど竹しか食べません。生息地の減少により、絶滅危惧種に指定されています。保護活動が行われており、人工繁殖にも成功しています。"
    }
}

構造データ出力のタスクにおいては、期待する構造体をサンプルとして渡す事で出力の精度を大きく上げる事ができます。

https://docs.anthropic.com/claude/docs/use-examples

その他、 与えるタスクの詳細をXMLタグで定義づけするテクニックもあったりしますが、有効性を確認できていない点でこの記事での割愛させていただきます。

ただしこれらのプロンプトチューニングで出力の精度を上げた場合でも、
期待する構造のjson形式のデータが出力されないリスクを完全に排除する事はできません
今回のように性能が一番劣っているhaikuモデルを使用している場合は、特にそのリスクは高まります。

つまりは生成AIの結果を受け取った後のフローに関しても、その不確実性故に考慮する必要がある、という事です。
プロンプトエンジニアリングとは別軸の対策が必要であるため、普通のエンジニアリングで対処する事になります。

普通のエンジニアリング編

規格外の構造データに対して許容の幅を広くする

例えば、以下の出力ではcommentのvalueの末尾に,(カンマ)が付与されていますが、このjsonはRFC 7159,ECMA-404で定義されている規格に則っていません。

{
"title": "かわいいカエル",
"drawing_score": 7,
"comment": "カエルの形が可愛らしく描かれています。",
}

python標準ライブラリのjsonはjson標準規格であるRFC 7159,ECMA-404に準拠しており、
規格から外れたjson文字列をデコードする場合、例外がスローされます。

以下はjsonライブラリでの規格外のjson文字列のデコードの検証結果です。

import json

# JSON文字列の定義
json_str1 = '''
{
title: "かわいいカエル",
"drawing_score": 7,
"comment": "カエルの形が可愛らしく描かれています。"
}
'''
json_str2 = '''
{
"title": "かわいいカエル",
"drawing_score": 7,
"comment": "カエルの形が可愛らしく描かれています。",
}
'''
json_str3 = '''
{
// comments
"title": "かわいいカエル",
"drawing_score": 7,
"comment":"カエルの形が可愛らしく描かれています。"
}
'''

# 標準jsonでのデコードテスト
try:
    decoded = json.loads(json_str1)
    print("json_str1 with json: Success")
except ValueError as e:
    print("json_str1 with json: Failed", e)

try:
    decoded = json.loads(json_str2)
    print("json_str2 with json: Success")
except ValueError as e:
    print("json_str2 with json: Failed", e)

try:
    decoded = json.loads(json_str3)
    print("json_str3 with json: Success")
except ValueError as e:
    print("json_str3 with json: Failed", e)

### output ### 
json_str1 with json: Failed Expecting property name enclosed in double quotes: line 3 column 1 (char 3)
json_str2 with json: Failed Expecting property name enclosed in double quotes: line 6 column 1 (char 77)
json_str3 with json: Failed Expecting property name enclosed in double quotes: line 3 column 1 (char 3)

転じて、pyjson5なるサードパーティーライブラリで上記と同様のjson文字列のデコードを試してみます。
pyjson5はJson5に準拠したJsonパーサーのライブラリです。

import json5

# JSON文字列の定義
json_str1 = '''
{
title: "かわいいカエル",
"drawing_score": 7,
"comment": "カエルの形が可愛らしく描かれています。"
}
'''
json_str2 = '''
{
"title": "かわいいカエル",
"drawing_score": 7,
"comment": "カエルの形が可愛らしく描かれています。",
}
'''

json_str3= '''
{
// comments
"title": "かわいいカエル",
"drawing_score": 7,
"comment":"カエルの形が可愛らしく描かれています。"
}
'''
# json5でのデコードテスト
try:
    decoded = json5.loads(json_str1)
    print("json_str1 with json5: Success")
except ValueError as e:
    print("json_str1 with json5: Failed", e)

try:
    decoded = json5.loads(json_str2)
    print("json_str2 with json5: Success")
except ValueError as e:
    print("json_str2 with json5: Failed", e)

try:
    decoded = json5.loads(json_str3)
    print("json_str3 with json5: Success")
except ValueError as e:
    print("json_str3 with json5: Failed", e)

### output ### 
json_str1 with json5: Success
json_str2 with json5: Success
json_str3 with json5: Success

正常にデコードされる事が確認できました。

Json5はjsonの標準規格であるRFC 7159,ECMA-404と比較すると、ECMAScript 5.1の仕様に準じておりコメント、末尾のカンマ等の
構文も許容する規格になっています。

Understanding JSON and JSON5

つまりは、生成AIの出力からは標準規格に則っていない構造データが出力される可能性がある為、
ある程度構文解析の許容幅を広くするように設計する事
で柔軟に処理する事ができます。

制御文字を置換する

生成AIの出力に改行コード(\n)等の制御文字が含まれている可能性があります。

{
  "title": "書籍のタイトル",
  "author": "著者名",
  "price": 1234,
  "description": "書籍の説明文\nが複数行にわたる場合\nこのように改行されます。"
}

RFC 7159の規格によれば制御文字はエスケープ処理されている必要があり、
エスケープせずにデコードすると例外がスローされます。

よって以下のような工夫が必要です。

import json

### jsonの場合
json_str = '''{
  "title": "書籍のタイトル",
  "author": "著者名",
  "price": 1234,
  "description": "書籍の説明文\nが複数行にわたる場合\nこのように改行されます。"
}'''
    
try:
    decoded = json.loads(json_str)
    print("json_str with json: Success")
except ValueError as e:
    print("json_str with json: Failed", e)
    
try:
    # strict が false (デフォルトは True) の場合、制御文字を文字列に含めることができます。
    decoded = json.loads(json_str,strict=False)
    print("json_str with json: Success")
except ValueError as e:
    print("json_str with json: Failed", e)

### output ###
json_str with json: Failed Invalid control character at: line 5 column 25 (char 84)
json_str with json: Success

### json5の場合
import json5
import re

try:
    decoded = json5.loads(json_str)
    print("json_str with json: Success")
except ValueError as e:
    print("json_str with json: Failed", e)
    
try:
    # 制御文字を空文字に置換する
    decoded = json5.loads(re.sub(r'[\x00-\x1F\x7F]','',json_str))
    print("json_str with json: Success")
except ValueError as e:
    print("json_str with json: Failed", e)

### output ###
json_str with json: Failed <string>:5 Unexpected "
" at column 25
json_str with json: Success

pythonに限らずどの言語で対応するにせよ、このような不確実性を考慮した実装をした方がよいのかもです。

おわりに

長々書いてしまい、言いたい事がぼやけているように感じるのでまとめると以下のような感じです。

  • 画像認識をアプリに組み込む場合はある程度不確実性を許容する必要がある
  • 生成AIで構造化データを扱う場合にプロンプトチューニングで回答の精度を上げる事は大事だけど、ブレがあるので回答の不確実性を念頭に置いた実装をした方がいいように思う

次も生成AI関連で何か作りたいと思います。記事にしてもいいかなと思ったら書きます。

西麻布チョヤッでした(´・ω・`)ノミ < バイバーイ マタネッ

Discussion