1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Perplexity AIとInstructorを利用した構造化出力サンプルプログラム

Last updated at Posted at 2025-05-22

既存投稿一覧ページへのリンク

一覧ページ

その他・記事まとめ

Unity関連 / Qiita

Perplexity APIの基本形

PerplexityAPI.py
import requests
import os
import configparser

# 1. 取得したAPIキーをここに入力
config = configparser.ConfigParser()
config.read('../APIConfig/config.ini', encoding='utf-8')
api_key = config['perplexity']['api_key']

model_select = {
    "debug": "llama-3.1-sonar-small-128k-online",
    "normal": "llama-3.1-sonar-large-128k-online",
    "sonor_pro": "sonar-pro",
    "sonor_reasoning_pro": "sonar-reasoning-pro",
    "sonor_reasoning": "sonar-reasoning",
}

if not api_key:
    print("Error: PERPLEXITY_API_KEY環境変数が設定されていません。")
else:
    url = "https://api.perplexity.ai/chat/completions"
    payload = {
        "model": model_select["normal"],
        "messages": [
            {"role": "system", "content": "日本語で完結に回答して下さい。"},
            {"role": "user", "content": "本日の注目ニュースを教えて下さい。"}
        ],
        "max_tokens": 4096,
        "temperature": 0.7
    }
    headers = {
        "Authorization": f"Bearer {api_key}",
        "Content-Type": "application/json"
    }
    try:
        response = requests.post(url, headers=headers, json=payload)
        response.raise_for_status()
        response_data = response.json()
        print(response_data)
    except requests.exceptions.RequestException as e:
        print(f"エラーが発生しました: {e}")
        if e.response is not None:
            print("詳細:", e.response.text)
{'id': 'd39403ea-d450-4757-8ae4-04de56a47c68', 'model': 'llama-3.1-sonar-large-128k-online', 'created': 1748060301, 'usage': {'prompt_tokens': 23, 'completion_tokens': 384, 'total_tokens': 407, 'search_context_size': 'low'}, 'citations': ['https://www.youtube.com/watch?v=460uq2ajCL4', 'https://toyokeizai.net/articles/-/879916', 'https://www.youtube.com/watch?v=xXD7qqIK4cw', 'https://www.kyoiku-press.com/post-date/2025/05/24/', 'https://www.youtube.com/user/ANNnewsCH'], 'object': 'chat.completion', 'choices': [{'index': 0, 'finish_reason': 'stop', 'message': {'role': 'assistant', 'content': '2025年5月24日の注目ニュースは以下の通りです。\n\n## 経済ニュース\n- 日本とEUでは長期金利が上昇しており、注視が必要です。この影響で、ニューヨーク証券取引所のダウ平均株価が同日約800ドル以上下落しています[2|.\n- 日米の超長期金利と金利差の推移、 以及日本国債イールドカーブの変化が注目されています。特に、4月2日から5月21日までの日本国債利回りの変化や、主要国30年金利 の変化幅も分析されています[1].\n\n## 天気ニュース\n- 日本の西日本地域、特に九州や四国、中国地方では激しい雷雨のおそれが あり、土砂災害に警戒が必要です。暖かく湿った空気が流れ込むため、雨雲が発達し、一時的に雷を伴った激しい雨が予想されています[3].\n\n## 社会ニュース\n- 東京の警視庁は、闇バイトで逮捕された10代の約6割が「先輩に誘われた」ことを明らかにし、女子高校生に対して注意喚起を行っています[5].\n\n## 教育ニュース\n- アントレプレナーシップ教育が注目されており、現代社会での技 術革新や社会の急速な変化に対応するために、自ら考え行動する力が求められています。教育現場での多様な価値観の認識と、新たな価値を創造する姿勢の重要性が強調されています[4].'}, 'delta': {'role': 'assistant', 'content': ''}}]}

久しぶりにLangChainを⋯

ChatGPTの情報は多いけれど、Perplexity AIの情報はあまり見つからなかったのでメモ書きです。
Perplexityさんは中々思い通りに構造化データにしてくれないので、下記のベースプログラムを元に色々と検証していくことにします。

基本形

PerplexityAI_structured_output_sample.py
"""
Perplexity AI APIを使用した構造化データ抽出プログラム
特徴:
1. 設定ファイルからのAPIキー読み込み
2. モデル選択機能の実装
3. Pydanticを活用した型安全な出力構造
4. 生レスポンス情報の取得機能
5. トークン使用量の可視化
"""

from openai import OpenAI
import instructor
from pydantic import BaseModel

import configparser

# 1. 取得したAPIキーをここに入力
config = configparser.ConfigParser()
config.read('../APIConfig/config.ini', encoding='utf-8')
api_key = config['perplexity']['api_key']

modeli_select = {
    "debug": "llama-3.1-sonar-small-128k-online",
    "normal": "llama-3.1-sonar-large-128k-online",
    "high": "llama-3.1-sonar-huge-128k-online",
    "debug_chat": "llama-3.1-sonar-small-128k-chat",
    "normal_chat": "llama-3.1-sonar-large-128k-chat",
}

client = instructor.from_perplexity(
    OpenAI(
        api_key=api_key,
        base_url="https://api.perplexity.ai"
        )
    )

# 出力構造を定義
class User(BaseModel):
    name: str
    age: int

# 構造化出力の実行
completion = client.chat.completions.create(
    model=modeli_select["debug"],
    response_model=User,
    messages=[{
        "role": "user", 
        "content": "山田太郎さんは25歳です。"
    }]
)

# 構造化されたデータを取得
user = completion  # 通常の構造化データ
raw_response = completion._raw_response  # 生レスポンス

# 構造化データの表示
print(f"名前: {user.name}, 年齢: {user.age}")

# 生レスポンスの表示
print("\n=== 生レスポンス ===")
print(f"モデル: {raw_response.model}")
print(f"生成ID: {raw_response.id}")
print(f"使用トークン数: {raw_response.usage}")
print(f"生レスポンス: {raw_response}")
名前: 山田太郎, 年齢: 56歳

=== 生レスポンス ===
モデル: llama-3.1-sonar-small-128k-online
生成ID: ee370ad7-030a-407a-bc59-c172b296b64e
使用トークン数: CompletionUsage(completion_tokens=15, prompt_tokens=8, total_tokens=23, completion_tokens_details=CompletionTokensDetails(accepted_prediction_tokens=None, audio_tokens=0, reasoning_tokens=0, rejected_prediction_tokens=None), prompt_tokens_details=PromptTokensDetails(audio_tokens=0, cached_tokens=0))
生レスポンス: ChatCompletion(id='ee370ad7-030a-407a-bc59-c172b296b64e', choices=[Choice(finish_reason='stop', index=0, logprobs=None, message=ChatCompletionMessage(content='{"name": "山田太郎", "age": 56}', refusal=None, role='assistant', annotations=None, audio=None, function_call=None, tool_calls=None), delta={'role': 'assistant', 'content': ''})], created=1747887973, model='llama-3.1-sonar-small-128k-online', object='chat.completion', service_tier=None, system_fingerprint=None, usage=CompletionUsage(completion_tokens=15, prompt_tokens=8, total_tokens=23, completion_tokens_details=CompletionTokensDetails(accepted_prediction_tokens=None, audio_tokens=0, reasoning_tokens=0, rejected_prediction_tokens=None), prompt_tokens_details=PromptTokensDetails(audio_tokens=0, cached_tokens=0)), citations=['https://dexall.co.jp/articles/?p=1562', 'https://www.youtube.com/watch?v=3XXn2f6ucgM', 'https://ja.wikipedia.org/wiki/%E5%B1%B1%E7%94%B0%E5%A4%AA%E9%83%8E_(%E6%AD%8C%E6%89%8B)', 'https://note.com/nijimorikokoro/n/nb8bc9e45b6c2', 'https://www.youtube.com/watch?v=DBqbqJP8qwE'])

基本形(role: systemを追加)

PerplexityAI_structured_output_sample2.py
# 構造化出力の実行(systemメッセージ追加)
completion = client.chat.completions.create(
    model=modeli_select["debug"],
    response_model=User,
    messages=[
        {
            "role": "system",
            "content": "ユーザーが入力した文章から、名前と年齢を抽出してください。"
        },
        {
            "role": "user", 
            "content": "山田太郎さんは25歳です。"
        }
    ]
)
モデル: llama-3.1-sonar-small-128k-online
生成ID: ff64d64f-3c91-4835-85dd-c7e567334284
使用トークン数: CompletionUsage(completion_tokens=16, prompt_tokens=30, total_tokens=46, completion_tokens_details=CompletionTokensDetails(accepted_prediction_tokens=None, audio_tokens=0, reasoning_tokens=0, rejected_prediction_tokens=None), prompt_tokens_details=PromptTokensDetails(audio_tokens=0, cached_tokens=0))
生レスポンス: ChatCompletion(id='ff64d64f-3c91-4835-85dd-c7e567334284', choices=[Choice(finish_reason='stop', index=0, logprobs=None, message=ChatCompletionMessage(content='{ "name": "山田太郎", "age": 25 }', refusal=None, role='assistant', annotations=None, audio=None, function_call=None, tool_calls=None), delta={'role': 'assistant', 'content': ''})], created=1747889222, model='llama-3.1-sonar-small-128k-online', object='chat.completion', service_tier=None, system_fingerprint=None, usage=CompletionUsage(completion_tokens=16, prompt_tokens=30, total_tokens=46, completion_tokens_details=CompletionTokensDetails(accepted_prediction_tokens=None, audio_tokens=0, reasoning_tokens=0, rejected_prediction_tokens=None), prompt_tokens_details=PromptTokensDetails(audio_tokens=0, cached_tokens=0)), citations=['https://dexall.co.jp/articles/?p=1562', 'https://www.youtube.com/watch?v=3XXn2f6ucgM', 'https://ja.wikipedia.org/wiki/%E5%B1%B1%E7%94%B0%E5%A4%AA%E9%83%8E_(%E6%AD%8C%E6%89%8B)', 'https://note.com/nijimorikokoro/n/nb8bc9e45b6c2', 'https://bunshun.jp/articles/-/66637?page=2'])

基本形3(出力構造を少しだけ複雑に)

PerplexityAI_structured_output_sample3.py
from pydantic import Field
from typing import Literal
# 出力構造を定義
class User(BaseModel):
    first_name: str = Field(..., min_length=1, max_length=20, pattern=r"^[^\d]+$")
    last_name: str = Field(..., min_length=1, max_length=20, pattern=r"^[^\d]+$")
    age: int = Field(..., gt=0, lt=150, description="年齢は0より大きく150未満")
    age_group: Literal["child", "teen", "adult", "senior"]

# 構造化出力の実行(systemメッセージ追加)
completion = client.chat.completions.create(
    model=modeli_select["debug"],
    response_model=User,
    messages=[
        {
            "role": "system",
            "content": f'''
    以下の要件に従ってユーザー入力から情報を抽出してください:

    ### 名前抽出ルール
    1. 姓と名を分割(例:「山田太郎」→ last_name="山田", first_name="太郎")
    2. 数字を含まず1-20文字
    3. 敬称(さん/様など)は除去

    ### 年齢処理
    1. 0 < age < 150 の整数
    2. 年齢グループ分類:
    - child: 0-12歳
    - teen: 13-19歳
    - adult: 20-64歳
    - senior: 65-149歳

    ### バリデーション
    - 入力に複数情報が含まれる場合は最初の該当データを採用
    - 矛盾する情報がある場合は例外を発生
    '''.strip()
        },
        {
            "role": "user", 
            "content": "山田太郎さんは25歳です。"
        }
    ]
)

# 構造化されたデータを取得
user = completion  # 通常の構造化データ
raw_response = completion._raw_response  # 生レスポンス

# 構造化データの表示
print(type(user))
print(f"{user}")

# 生レスポンスの表示
print("\n=== 生レスポンス ===")
print(f"モデル: {raw_response.model}")
print(f"生成ID: {raw_response.id}")
print(f"使用トークン数: {raw_response.usage}")
print(f"生レスポンス: {raw_response}")

<class '__main__.User'>
first_name='太郎' last_name='山田' age=25 age_group='teen'

=== 生レスポンス ===
モデル: llama-3.1-sonar-small-128k-online
生成ID: 56b3ac43-abb4-45aa-a086-c8c534e7b2cf
使用トークン数: CompletionUsage(completion_tokens=31, prompt_tokens=220, total_tokens=251, completion_tokens_details=CompletionTokensDetails(accepted_prediction_tokens=None, audio_tokens=0, reasoning_tokens=0, rejected_prediction_tokens=None), prompt_tokens_details=PromptTokensDetails(audio_tokens=0, cached_tokens=0))
生レスポンス: ChatCompletion(id='56b3ac43-abb4-45aa-a086-c8c534e7b2cf', choices=[Choice(finish_reason='stop', index=0, logprobs=None, message=ChatCompletionMessage(content='{ "first_name": "太郎", "last_name": "山田", "age": 25, "age_group": "teen" }', refusal=None, role='assistant', annotations=None, audio=None, function_call=None, tool_calls=None), delta={'role': 'assistant', 'content': ''})], created=1747889823, model='llama-3.1-sonar-small-128k-online', object='chat.completion', service_tier=None, system_fingerprint=None, usage=CompletionUsage(completion_tokens=31, prompt_tokens=220, total_tokens=251, completion_tokens_details=CompletionTokensDetails(accepted_prediction_tokens=None, audio_tokens=0, reasoning_tokens=0, rejected_prediction_tokens=None), prompt_tokens_details=PromptTokensDetails(audio_tokens=0, cached_tokens=0)), citations=['https://dexall.co.jp/articles/?p=1562', 'https://www.youtube.com/watch?v=3XXn2f6ucgM', 'https://ja.wikipedia.org/wiki/%E5%B1%B1%E7%94%B0%E5%A4%AA%E9%83%8E_(%E6%AD%8C%E6%89%8B)', 'https://note.com/nijimorikokoro/n/nb8bc9e45b6c2', 'https://bunshun.jp/articles/-/66637?page=2'])
"content": "吾輩は猫である(llama-3.1-sonar-small-128k-online)"
<class '__main__.User'>
first_name='吾輩' last_name='猫' age=1 age_group='child'

=== 生レスポンス ===
モデル: llama-3.1-sonar-small-128k-online
生成ID: 8dd3fbb9-7d4a-4fe4-866c-f20e8aa46456
使用トークン数: CompletionUsage(completion_tokens=69, prompt_tokens=912, total_tokens=981, completion_tokens_details=CompletionTokensDetails(accepted_prediction_tokens=None, audio_tokens=0, reasoning_tokens=0, rejected_prediction_tokens=None), prompt_tokens_details=PromptTokensDetails(audio_tokens=0, cached_tokens=0))
生レスポンス: ChatCompletion(id='8dd3fbb9-7d4a-4fe4-866c-f20e8aa46456', choices=[Choice(finish_reason='stop', index=0, logprobs=None, message=ChatCompletionMessage(content='{"first_name":"吾輩","last_name":"猫","age":1,"age_group":"child"}', refusal=None, role='assistant', annotations=None, audio=None, function_call=None, tool_calls=None), delta={'role': 'assistant', 'content': ''})], created=1747889956, model='llama-3.1-sonar-small-128k-online', object='chat.completion', service_tier=None, system_fingerprint=None, usage=CompletionUsage(completion_tokens=69, prompt_tokens=912, total_tokens=981, completion_tokens_details=CompletionTokensDetails(accepted_prediction_tokens=None, audio_tokens=0, reasoning_tokens=0, rejected_prediction_tokens=None), prompt_tokens_details=PromptTokensDetails(audio_tokens=0, cached_tokens=0)), citations=['https://json-schema.org/learn/miscellaneous-examples', 'https://www.qatouch.com/blog/validating-json-schema/', 'https://json-schema.org/draft/2020-12/json-schema-validation', 'https://zuplo.com/blog/2024/07/19/verify-json-schema', 'https://jweiler.com/json/schema/2020/07/03/json-schema-for-net-derived-class.html'])
"content": "吾輩は猫である(llama-3.1-sonar-large-128k-online)"
<class '__main__.User'>
first_name='なし' last_name='なし' age=149 age_group='senior'

=== 生レスポンス ===
モデル: llama-3.1-sonar-large-128k-online
生成ID: 1a726419-607d-45ac-9198-0874e65b0cf9
使用トークン数: CompletionUsage(completion_tokens=58, prompt_tokens=528, total_tokens=586, completion_tokens_details=CompletionTokensDetails(accepted_prediction_tokens=None, audio_tokens=0, reasoning_tokens=0, rejected_prediction_tokens=None), prompt_tokens_details=PromptTokensDetails(audio_tokens=0, cached_tokens=0))
生レスポンス: ChatCompletion(id='1a726419-607d-45ac-9198-0874e65b0cf9', choices=[Choice(finish_reason='stop', index=0, logprobs=None, message=ChatCompletionMessage(content='{"first_name": "なし", "last_name": "なし", "age": 149, "age_group": "senior"}', refusal=None, role='assistant', annotations=None, audio=None, function_call=None, tool_calls=None), delta={'role': 'assistant', 'content': ''})], created=1747890044, model='llama-3.1-sonar-large-128k-online', object='chat.completion', service_tier=None, system_fingerprint=None, usage=CompletionUsage(completion_tokens=58, prompt_tokens=528, total_tokens=586, completion_tokens_details=CompletionTokensDetails(accepted_prediction_tokens=None, audio_tokens=0, reasoning_tokens=0, rejected_prediction_tokens=None), prompt_tokens_details=PromptTokensDetails(audio_tokens=0, cached_tokens=0)), citations=['https://www.syncfusion.com/blogs/post/using-json-schema-for-json-validation/amp', 'https://zuplo.com/blog/2024/07/19/verify-json-schema', 'https://json-schema.org/draft/2020-12/json-schema-validation', 'https://linkml.io/linkml/intro/tutorial03.html', 'https://github.com/json-schema-org/json-schema-spec/wiki/Scope-of-JSON-Schema-Validation'])

基本形4(出力構造をもう少しだけ複雑に)

PerplexityAI_structured_output_sample4.py
from pydantic import Field
from pydantic import field_validator
from typing import Optional, Literal

# 出力構造を定義
class Address(BaseModel):
    prefecture: str = Field(..., description="都道府県名")
    city: str = Field(..., description="市区町村名")
    street: Optional[str] = Field(None, description="番地・建物名")

class User(BaseModel):
    first_name: str = Field(..., min_length=1, max_length=20, pattern=r"^[^\d]+$")
    last_name: str = Field(..., min_length=1, max_length=20, pattern=r"^[^\d]+$")
    age: int = Field(..., gt=0, lt=150, description="年齢は0より大きく150未満")
    age_group: Literal["child", "teen", "adult", "senior"]  # Literal型で、指定された値以外は許可しません
    address: Optional[Address] = None
    @field_validator('age_group')   # age_groupフィールドの値を検証・変換するためのメソッド
    @classmethod
    def validate_age_group(cls, v, values):
        if 'age' in values.data:
            age = values.data['age']
            if age < 13:
                return "child"
            elif age < 20:
                return "teen"
            elif age < 65:
                return "adult"
            else:
                return "senior"
        return v

# 構造化出力の実行(systemメッセージ追加)
completion = client.chat.completions.create(
    model=modeli_select["debug"],
    response_model=User,
    messages = [
        {
            "role": "system",
            "content": '''
    以下の要件に従ってユーザー入力から情報を抽出してください:

    ## 名前抽出ルール
    1. 姓と名を分割(例:「山田太郎」→ last_name="山田", first_name="太郎")
    2. 数字を含まず1〜20文字であること
    3. 敬称(さん、様など)は除去すること

    ## 年齢処理
    1. 0 < age < 150 の整数のみ抽出すること
    2. 年齢に応じて age_group を自動分類すること
    - child: 0〜12歳
    - teen: 13〜19歳
    - adult: 20〜64歳
    - senior: 65〜149歳

    ## 住所抽出ルール
    1. 住所要素を以下の形式で分割
    - prefecture: 都道府県名(例:「東京都」)
    - city: 市区町村名(例:「新宿区」)
    - street: 番地・建物名(例:「西新宿2-8-1」)
    2. 部分的な情報しかない場合は可能な範囲で抽出(例:「大阪市北区」→ prefecture="大阪府", city="大阪市北区")

    ## バリデーション
    - 入力に複数の情報が含まれる場合は、最初に該当したデータのみを採用すること
    - 矛盾する情報がある場合は例外を発生させること
    '''.strip()
        },
        {
            "role": "user",
            "content": "山田太郎さんは25歳です。東京都新宿区西新宿2-8-1に住んでいます。"
        }
    ]
)

# 構造化されたデータを取得
user = completion  # 通常の構造化データ
raw_response = completion._raw_response  # 生レスポンス

# 構造化データの表示
print(type(user))
print(f"{user}")

# 生レスポンスの表示
print("\n=== 生レスポンス ===")
print(f"モデル: {raw_response.model}")
print(f"生成ID: {raw_response.id}")
print(f"使用トークン数: {raw_response.usage}")
print(f"生レスポンス: {raw_response}")
<class '__main__.User'>
first_name='太郎' last_name='山田' age=25 age_group='adult' address=Address(prefecture='東京都', city='新宿区', street='西新宿2丁目8-1')

=== 生レスポンス ===
モデル: llama-3.1-sonar-small-128k-online
生成ID: 59bec0a5-f802-4251-84b9-d7608f82e377
使用トークン数: CompletionUsage(completion_tokens=65, prompt_tokens=387, total_tokens=452, completion_tokens_details=CompletionTokensDetails(accepted_prediction_tokens=None, audio_tokens=0, reasoning_tokens=0, rejected_prediction_tokens=None), prompt_tokens_details=PromptTokensDetails(audio_tokens=0, cached_tokens=0))
生レスポンス: ChatCompletion(id='59bec0a5-f802-4251-84b9-d7608f82e377', choices=[Choice(finish_reason='stop', index=0, logprobs=None, message=ChatCompletionMessage(content='{ "first_name": "太郎", "last_name": "山田", "age": 25, "age_group": "adult", "address": { "prefecture": "東京都", "city": "新宿区", "street": "西新宿2丁目8-1" } }', refusal=None, role='assistant', annotations=None, audio=None, function_call=None, tool_calls=None), delta={'role': 'assistant', 'content': ''})], created=1747903828, model='llama-3.1-sonar-small-128k-online', object='chat.completion', service_tier=None, system_fingerprint=None, usage=CompletionUsage(completion_tokens=65, prompt_tokens=387, total_tokens=452, completion_tokens_details=CompletionTokensDetails(accepted_prediction_tokens=None, audio_tokens=0, reasoning_tokens=0, rejected_prediction_tokens=None), prompt_tokens_details=PromptTokensDetails(audio_tokens=0, cached_tokens=0)), citations=['https://www.navitime.co.jp/address/131040700020000800001/%E6%9D%B1%E4%BA%AC%E9%83%BD%E6%96%B0%E5%AE%BF%E5%8C%BA%E8%A5%BF%E6%96%B0%E5%AE%BF2%E4%B8%81%E7%9B%AE8-1/', 'https://www.navitime.co.jp/poi?spot=02300-1009192', 'https://www.zaimu.metro.tokyo.lg.jp/tochousha/goannai', 'https://www.metro.tokyo.lg.jp/about/kotsuannai', 'https://www.its-mo.com/addr/13/104/070/002/00008/00001/'])

あとがき

instructorについての理解が足りなかった。。。
perplexity aiでも上手くいくけれど、chat GPTの方が構造化するのが得意なLLMということですね。

LLMはいいとこ取りで柔軟に使っていくことにします。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?