既存投稿一覧ページへのリンク
その他・記事まとめ
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はいいとこ取りで柔軟に使っていくことにします。